diff --git a/Content/Examples/Cpp/CurveInputExample.cpp b/Content/Examples/Cpp/CurveInputExample.cpp new file mode 100644 index 000000000..c63651218 --- /dev/null +++ b/Content/Examples/Cpp/CurveInputExample.cpp @@ -0,0 +1,146 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "CurveInputExample.h" + +#include "Engine/StaticMesh.h" + +#include "HoudiniAsset.h" +#include "HoudiniPublicAPI.h" +#include "HoudiniPublicAPIBlueprintLib.h" +#include "HoudiniPublicAPIAssetWrapper.h" +#include "HoudiniPublicAPIInputTypes.h" + +ACurveInputExample::ACurveInputExample() + : AssetWrapper(nullptr) +{ + +} + +void ACurveInputExample::RunCurveInputExample_Implementation() +{ + // Get the API instance + UHoudiniPublicAPI* const API = UHoudiniPublicAPIBlueprintLib::GetAPI(); + // Ensure we have a running session + if (!API->IsSessionValid()) + API->CreateSession(); + // Load our HDA uasset + UHoudiniAsset* const ExampleHDA = Cast(StaticLoadObject(UHoudiniAsset::StaticClass(), nullptr, TEXT("/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0"))); + // Create an API wrapper instance for instantiating the HDA and interacting with it + AssetWrapper = API->InstantiateAsset(ExampleHDA, FTransform::Identity); + if (IsValid(AssetWrapper)) + { + // Pre-instantiation is the earliest point where we can set parameter values + AssetWrapper->GetOnPreInstantiationDelegate().AddUniqueDynamic(this, &ACurveInputExample::SetInitialParameterValues); + // Post-instantiation is the earliest point where we can set inputs + AssetWrapper->GetOnPostInstantiationDelegate().AddUniqueDynamic(this, &ACurveInputExample::SetInputs); + // After a cook and after the plugin has created/updated objects/assets from the node's outputs + AssetWrapper->GetOnPostProcessingDelegate().AddUniqueDynamic(this, &ACurveInputExample::PrintOutputs); + } +} + +void ACurveInputExample::SetInitialParameterValues_Implementation(UHoudiniPublicAPIAssetWrapper* InWrapper) +{ + // Uncheck the upvectoratstart parameter + InWrapper->SetBoolParameterValue(TEXT("upvectoratstart"), false); + + // Set the scale to 0.2 + InWrapper->SetFloatParameterValue(TEXT("scale"), 0.2f); + + // Since we are done with setting the initial values, we can unbind from the delegate + InWrapper->GetOnPreInstantiationDelegate().RemoveDynamic(this, &ACurveInputExample::SetInitialParameterValues); +} + +void ACurveInputExample::SetInputs_Implementation(UHoudiniPublicAPIAssetWrapper* InWrapper) +{ + // Create an empty geometry input + UHoudiniPublicAPIGeoInput* const GeoInput = Cast(InWrapper->CreateEmptyInput(UHoudiniPublicAPIGeoInput::StaticClass())); + // Load the cube static mesh asset + UStaticMesh* const Cube = Cast(StaticLoadObject(UStaticMesh::StaticClass(), nullptr, TEXT("/Engine/BasicShapes/Cube.Cube"))); + // Set the input object array for our geometry input, in this case containing only the cube + GeoInput->SetInputObjects({Cube}); + + // Set the input on the instantiated HDA via the wrapper + InWrapper->SetInputAtIndex(0, GeoInput); + + // Create an empty curve input + UHoudiniPublicAPICurveInput* const CurveInput = Cast(InWrapper->CreateEmptyInput(UHoudiniPublicAPICurveInput::StaticClass())); + // Create the curve input object + UHoudiniPublicAPICurveInputObject* const CurveObject = NewObject(CurveInput); + // Make it a Nurbs curve + CurveObject->SetCurveType(EHoudiniPublicAPICurveType::Nurbs); + // Set the points of the curve, for this example we create a helix consisting of 100 points + TArray CurvePoints; + CurvePoints.Reserve(100); + for (int32 i = 0; i < 100; ++i) + { + const float t = i / 20.0f * PI * 2.0f; + const float x = 100.0f * cos(t); + const float y = 100.0f * sin(t); + const float z = i; + CurvePoints.Emplace(FTransform(FVector(x, y, z))); + } + CurveObject->SetCurvePoints(CurvePoints); + // Set the curve wrapper as an input object + CurveInput->SetInputObjects({CurveObject}); + // Copy the input data to the HDA as node input 1 + InWrapper->SetInputAtIndex(1, CurveInput); + + // Since we are done with setting the initial values, we can unbind from the delegate + InWrapper->GetOnPostInstantiationDelegate().RemoveDynamic(this, &ACurveInputExample::SetInputs); +} + +void ACurveInputExample::PrintOutputs_Implementation(UHoudiniPublicAPIAssetWrapper* InWrapper) +{ + // Print out all outputs generated by the HDA + const int32 NumOutputs = InWrapper->GetNumOutputs(); + UE_LOG(LogTemp, Log, TEXT("NumOutputs: %d"), NumOutputs); + if (NumOutputs > 0) + { + for (int32 OutputIndex = 0; OutputIndex < NumOutputs; ++OutputIndex) + { + TArray Identifiers; + InWrapper->GetOutputIdentifiersAt(OutputIndex, Identifiers); + UE_LOG(LogTemp, Log, TEXT("\toutput index: %d"), OutputIndex); + UE_LOG(LogTemp, Log, TEXT("\toutput type: %d"), InWrapper->GetOutputTypeAt(OutputIndex)); + UE_LOG(LogTemp, Log, TEXT("\tnum_output_objects: %d"), Identifiers.Num()); + if (Identifiers.Num() > 0) + { + for (const FHoudiniPublicAPIOutputObjectIdentifier& Identifier : Identifiers) + { + UObject* const OutputObject = InWrapper->GetOutputObjectAt(OutputIndex, Identifier); + UObject* const OutputComponent = InWrapper->GetOutputComponentAt(OutputIndex, Identifier); + const bool bIsProxy = InWrapper->IsOutputCurrentProxyAt(OutputIndex, Identifier); + UE_LOG(LogTemp, Log, TEXT("\t\tidentifier: %s_%s"), *(Identifier.PartName), *(Identifier.SplitIdentifier)); + UE_LOG(LogTemp, Log, TEXT("\t\toutput_object: %s"), IsValid(OutputObject) ? *(OutputObject->GetFName().ToString()) : TEXT("None")) + UE_LOG(LogTemp, Log, TEXT("\t\toutput_component: %s"), IsValid(OutputComponent) ? *(OutputComponent->GetFName().ToString()) : TEXT("None")) + UE_LOG(LogTemp, Log, TEXT("\t\tis_proxy: %d"), bIsProxy) + UE_LOG(LogTemp, Log, TEXT("")) + } + } + } + } +} diff --git a/Content/Examples/Cpp/CurveInputExample.h b/Content/Examples/Cpp/CurveInputExample.h new file mode 100644 index 000000000..6f9bd8898 --- /dev/null +++ b/Content/Examples/Cpp/CurveInputExample.h @@ -0,0 +1,62 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "EditorUtilityActor.h" + +#include "CurveInputExample.generated.h" + +class UHoudiniPublicAPIAssetWrapper; + +UCLASS() +class ACurveInputExample : public AEditorUtilityActor +{ +GENERATED_BODY() + +public: + ACurveInputExample(); + + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, CallInEditor) + void RunCurveInputExample(); + +protected: + /** Set our initial parameter values: disable upvectorstart and set the scale to 0.2. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, CallInEditor) + void SetInitialParameterValues(UHoudiniPublicAPIAssetWrapper* InWrapper); + + /** Configure our inputs: input 0 is a cube and input 1 a helix. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, CallInEditor) + void SetInputs(UHoudiniPublicAPIAssetWrapper* InWrapper); + + /** Print the outputs that were generated by the HDA (after a cook) */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, CallInEditor) + void PrintOutputs(UHoudiniPublicAPIAssetWrapper* InWrapper); + + UPROPERTY(BlueprintReadWrite) + UHoudiniPublicAPIAssetWrapper* AssetWrapper; +}; diff --git a/Content/Examples/Cpp/IterateOverHoudiniActorsExample.cpp b/Content/Examples/Cpp/IterateOverHoudiniActorsExample.cpp new file mode 100644 index 000000000..d08628bff --- /dev/null +++ b/Content/Examples/Cpp/IterateOverHoudiniActorsExample.cpp @@ -0,0 +1,128 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "IterateOverHoudiniActorsExample.h" + +#include "EngineUtils.h" + +#include "HoudiniAssetActor.h" +#include "HoudiniPublicAPI.h" +#include "HoudiniPublicAPIBlueprintLib.h" +#include "HoudiniPublicAPIAssetWrapper.h" + + +// Sets default values +AIterateOverHoudiniActorsExample::AIterateOverHoudiniActorsExample() +{ +} + +void AIterateOverHoudiniActorsExample::RunIterateOverHoudiniActorsExample_Implementation() +{ + // Get the API instance + UHoudiniPublicAPI* const API = UHoudiniPublicAPIBlueprintLib::GetAPI(); + // Ensure we have a running Houdini Engine session + if (!API->IsSessionValid()) + API->CreateSession(); + + // Iterate over HoudiniAssetActors in the world (by default when instantiating an HDA in the world an + // AHoudiniAssetActor is spawned, which has a component that manages the instantiated HDA node in Houdini Engine. + UWorld* const OurWorld = GetWorld(); + for (TActorIterator It(OurWorld); It; ++It) + { + AHoudiniAssetActor* const HDAActor = *It; + if (!IsValid(HDAActor)) + continue; + + // Print the name of the actor + UE_LOG(LogTemp, Display, TEXT("HDA Actor (Name, Label): %s, %s"), *(HDAActor->GetName()), *(HDAActor->GetActorLabel())); + + // Wrap it with the API + UHoudiniPublicAPIAssetWrapper* const Wrapper = UHoudiniPublicAPIAssetWrapper::CreateWrapper(this, HDAActor); + if (!IsValid(Wrapper)) + continue; + + // Print its parameters via the API + TMap ParameterTuples; + if (!Wrapper->GetParameterTuples(ParameterTuples)) + { + // The operation failed, log the error + FString ErrorMessage; + if (Wrapper->GetLastErrorMessage(ErrorMessage)) + UE_LOG(LogTemp, Warning, TEXT("%s"), *ErrorMessage); + + continue; + } + + UE_LOG(LogTemp, Display, TEXT("# Parameter Tuples: %d"), ParameterTuples.Num()); + for (const auto& Entry : ParameterTuples) + { + UE_LOG(LogTemp, Display, TEXT("\tParameter Tuple Name: %s"), *(Entry.Key.ToString())); + + const FHoudiniParameterTuple& ParameterTuple = Entry.Value; + TArray Strings; + FString Type; + if (ParameterTuple.BoolValues.Num() > 0) + { + Type = TEXT("Bool"); + for (const bool Value : ParameterTuple.BoolValues) + { + Strings.Add(Value ? TEXT("1") : TEXT("0")); + } + } + else if (ParameterTuple.FloatValues.Num() > 0) + { + Type = TEXT("Float"); + for (const float Value : ParameterTuple.FloatValues) + { + Strings.Add(FString::Printf(TEXT("%.4f"), Value)); + } + } + else if (ParameterTuple.Int32Values.Num() > 0) + { + Type = TEXT("Int32"); + for (const int32 Value : ParameterTuple.Int32Values) + { + Strings.Add(FString::Printf(TEXT("%d"), Value)); + } + } + else if (ParameterTuple.StringValues.Num() > 0) + { + Type = TEXT("String"); + Strings = ParameterTuple.StringValues; + } + + if (Type.Len() == 0) + { + UE_LOG(LogTemp, Display, TEXT("\t\tEmpty")); + } + else + { + UE_LOG(LogTemp, Display, TEXT("\t\t%s Values: %s"), *Type, *FString::Join(Strings, TEXT("; "))); + } + } + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.h b/Content/Examples/Cpp/IterateOverHoudiniActorsExample.h similarity index 71% rename from Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.h rename to Content/Examples/Cpp/IterateOverHoudiniActorsExample.h index 6830f53b3..76ce2db08 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.h +++ b/Content/Examples/Cpp/IterateOverHoudiniActorsExample.h @@ -1,5 +1,5 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. +/* +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,3 +26,20 @@ #pragma once +#include "CoreMinimal.h" +#include "EditorUtilityActor.h" + +#include "IterateOverHoudiniActorsExample.generated.h" + +UCLASS() +class APIEXAMPLEEDITOR_API AIterateOverHoudiniActorsExample : public AEditorUtilityActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + AIterateOverHoudiniActorsExample(); + + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, CallInEditor) + void RunIterateOverHoudiniActorsExample(); +}; diff --git a/Content/Examples/EUA/EUA_CurveInputExample.uasset b/Content/Examples/EUA/EUA_CurveInputExample.uasset new file mode 100644 index 000000000..60d5db061 Binary files /dev/null and b/Content/Examples/EUA/EUA_CurveInputExample.uasset differ diff --git a/Content/Examples/EUW/EUW_APIExample.uasset b/Content/Examples/EUW/EUW_APIExample.uasset new file mode 100644 index 000000000..cb8db8dfe Binary files /dev/null and b/Content/Examples/EUW/EUW_APIExample.uasset differ diff --git a/Content/Examples/Maps/LandscapeInputExample.umap b/Content/Examples/Maps/LandscapeInputExample.umap new file mode 100644 index 000000000..9c79e2b48 Binary files /dev/null and b/Content/Examples/Maps/LandscapeInputExample.umap differ diff --git a/Content/Examples/Maps/LandscapeInputExample_BuiltData.uasset b/Content/Examples/Maps/LandscapeInputExample_BuiltData.uasset new file mode 100644 index 000000000..c53a5a310 Binary files /dev/null and b/Content/Examples/Maps/LandscapeInputExample_BuiltData.uasset differ diff --git a/Content/Examples/Python/asset_input_example.py b/Content/Examples/Python/asset_input_example.py new file mode 100644 index 000000000..5a47cc35f --- /dev/null +++ b/Content/Examples/Python/asset_input_example.py @@ -0,0 +1,167 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" An example script that uses the API to instantiate two HDAs. The first HDA +will be used as an input to the second HDA. For the second HDA we set 2 inputs: +an asset input (the first instantiated HDA) and a curve input (a helix). The +inputs are set during post instantiation (before the first cook). After the +first cook and output creation (post processing) the input structure is fetched +and logged. + +""" +import math + +import unreal + +_g_wrapper1 = None +_g_wrapper2 = None + + +def get_copy_curve_hda_path(): + return '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0' + + +def get_copy_curve_hda(): + return unreal.load_object(None, get_copy_curve_hda_path()) + + +def get_pig_head_hda_path(): + return '/HoudiniEngine/Examples/hda/pig_head_subdivider_v01.pig_head_subdivider_v01' + + +def get_pig_head_hda(): + return unreal.load_object(None, get_pig_head_hda_path()) + + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Create a geo input + asset_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIAssetInput) + # Set the input objects/assets for this input + # asset_input.set_input_objects((_g_wrapper1.get_houdini_asset_actor().houdini_asset_component, )) + asset_input.set_input_objects((_g_wrapper1, )) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, asset_input) + # We can now discard the API input object + asset_input = None + + # Create a curve input + curve_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPICurveInput) + # Create a curve wrapper/helper + curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input) + # Make it a Nurbs curve + curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS) + # Set the points of the curve, for this example we create a helix + # consisting of 100 points + curve_points = [] + for i in range(10): + t = i / 10.0 * math.pi * 2.0 + x = 100.0 * math.cos(t) + y = 100.0 * math.sin(t) + z = i * 10.0 + curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1])) + curve_object.set_curve_points(curve_points) + # Set the curve wrapper as an input object + curve_input.set_input_objects((curve_object, )) + # Copy the input data to the HDA as node input 1 + in_wrapper.set_input_at_index(1, curve_input) + # We can now discard the API input object + curve_input = None + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPICurveInput): + print('\t\tbCookOnCurveChanged: {0}'.format(in_input.cook_on_curve_changed)) + print('\t\tbAddRotAndScaleAttributesOnCurves: {0}'.format(in_input.add_rot_and_scale_attributes_on_curves)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + if isinstance(input_object, unreal.HoudiniPublicAPICurveInputObject): + print('\t\t\tbClosed: {0}'.format(input_object.is_closed())) + print('\t\t\tCurveMethod: {0}'.format(input_object.get_curve_method())) + print('\t\t\tCurveType: {0}'.format(input_object.get_curve_type())) + print('\t\t\tReversed: {0}'.format(input_object.is_reversed())) + print('\t\t\tCurvePoints: {0}'.format(input_object.get_curve_points())) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper1, _g_wrapper2 + # instantiate the input HDA with auto-cook enabled + _g_wrapper1 = api.instantiate_asset(get_pig_head_hda(), unreal.Transform()) + + # instantiate the copy curve HDA + _g_wrapper2 = api.instantiate_asset(get_copy_curve_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper2.on_post_instantiation_delegate.add_callable(configure_inputs) + # Print the input state after the cook and output creation. + _g_wrapper2.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/bake_all_outputs_example.py b/Content/Examples/Python/bake_all_outputs_example.py new file mode 100644 index 000000000..6daf6555e --- /dev/null +++ b/Content/Examples/Python/bake_all_outputs_example.py @@ -0,0 +1,134 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import unreal + +""" Example script for instantiating an asset, cooking it and baking all of +its outputs. + +""" + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/pig_head_subdivider_v01.pig_head_subdivider_v01' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def on_post_instantiation(in_wrapper): + print('on_post_instantiation') + # in_wrapper.on_post_instantiation_state_exited_delegate_delegate.remove_callable(on_post_instantiation) + + # Set parameter values for the next cook + # in_wrapper.set_bool_parameter_value('add_instances', True) + # in_wrapper.set_int_parameter_value('num_instances', 8) + in_wrapper.set_parameter_tuples({ + 'add_instances': unreal.HoudiniParameterTuple(bool_values=(True, )), + 'num_instances': unreal.HoudiniParameterTuple(int32_values=(8, )), + }) + + # Print all parameter values + param_tuples = in_wrapper.get_parameter_tuples() + print('parameter tuples: {}'.format(len(param_tuples) if param_tuples else 0)) + if param_tuples: + for param_tuple_name, param_tuple in param_tuples.items(): + print('parameter tuple name: {}'.format(param_tuple_name)) + print('\tbool_values: {}'.format(param_tuple.bool_values)) + print('\tfloat_values: {}'.format(param_tuple.float_values)) + print('\tint32_values: {}'.format(param_tuple.int32_values)) + print('\tstring_values: {}'.format(param_tuple.string_values)) + + # Force a cook/recook + in_wrapper.recook() + + +def on_post_bake(in_wrapper, success): + in_wrapper.on_post_bake_delegate.remove_callable(on_post_bake) + print('bake complete ... {}'.format('success' if success else 'failed')) + + # Delete the hda after the bake + in_wrapper.delete_instantiated_asset() + global _g_wrapper + _g_wrapper = None + + +def on_post_process(in_wrapper): + print('on_post_process') + + # in_wrapper.on_post_processing_delegate.remove_callable(on_post_process) + + # Print out all outputs generated by the HDA + num_outputs = in_wrapper.get_num_outputs() + print('num_outputs: {}'.format(num_outputs)) + if num_outputs > 0: + for output_idx in range(num_outputs): + identifiers = in_wrapper.get_output_identifiers_at(output_idx) + print('\toutput index: {}'.format(output_idx)) + print('\toutput type: {}'.format(in_wrapper.get_output_type_at(output_idx))) + print('\tnum_output_objects: {}'.format(len(identifiers))) + if identifiers: + for identifier in identifiers: + output_object = in_wrapper.get_output_object_at(output_idx, identifier) + output_component = in_wrapper.get_output_component_at(output_idx, identifier) + is_proxy = in_wrapper.is_output_current_proxy_at(output_idx, identifier) + print('\t\tidentifier: {}'.format(identifier)) + print('\t\toutput_object: {}'.format(output_object.get_name() if output_object else 'None')) + print('\t\toutput_component: {}'.format(output_component.get_name() if output_component else 'None')) + print('\t\tis_proxy: {}'.format(is_proxy)) + print('') + + # bind to the post bake delegate + in_wrapper.on_post_bake_delegate.add_callable(on_post_bake) + + # Bake all outputs to actors + print('baking all outputs to actors') + in_wrapper.bake_all_outputs_with_settings( + unreal.HoudiniEngineBakeOption.TO_ACTOR, + replace_previous_bake=False, + remove_temp_outputs_on_success=False) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + + # instantiate an asset, disabling auto-cook of the asset (so we have to + # call wrapper.reCook() to cook it) + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform(), enable_auto_cook=False) + + # Bind to the on post instantiation delegate (before the first cook) + _g_wrapper.on_post_instantiation_delegate.add_callable(on_post_instantiation) + # Bind to the on post processing delegate (after a cook and after all + # outputs have been generated in Unreal) + _g_wrapper.on_post_processing_delegate.add_callable(on_post_process) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/bake_output_object_example.py b/Content/Examples/Python/bake_output_object_example.py new file mode 100644 index 000000000..bdcab3974 --- /dev/null +++ b/Content/Examples/Python/bake_output_object_example.py @@ -0,0 +1,103 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import unreal + +""" Example script for instantiating an asset, cooking it and baking an +individual output object. + +""" + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/pig_head_subdivider_v01.pig_head_subdivider_v01' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def on_post_process(in_wrapper): + print('on_post_process') + # Print details about the outputs and record the first static mesh we find + + sm_index = None + sm_identifier = None + + # in_wrapper.on_post_processing_delegate.remove_callable(on_post_process) + + num_outputs = in_wrapper.get_num_outputs() + print('num_outputs: {}'.format(num_outputs)) + if num_outputs > 0: + for output_idx in range(num_outputs): + identifiers = in_wrapper.get_output_identifiers_at(output_idx) + output_type = in_wrapper.get_output_type_at(output_idx) + print('\toutput index: {}'.format(output_idx)) + print('\toutput type: {}'.format(output_type)) + print('\tnum_output_objects: {}'.format(len(identifiers))) + if identifiers: + for identifier in identifiers: + output_object = in_wrapper.get_output_object_at(output_idx, identifier) + output_component = in_wrapper.get_output_component_at(output_idx, identifier) + is_proxy = in_wrapper.is_output_current_proxy_at(output_idx, identifier) + print('\t\tidentifier: {}'.format(identifier)) + print('\t\toutput_object: {}'.format(output_object.get_name() if output_object else 'None')) + print('\t\toutput_component: {}'.format(output_component.get_name() if output_component else 'None')) + print('\t\tis_proxy: {}'.format(is_proxy)) + print('') + + if (output_type == unreal.HoudiniOutputType.MESH and + isinstance(output_object, unreal.StaticMesh)): + sm_index = output_idx + sm_identifier = identifier + + # Bake the first static mesh we found to the CB + if sm_index is not None and sm_identifier is not None: + print('baking {}'.format(sm_identifier)) + success = in_wrapper.bake_output_object_at(sm_index, sm_identifier) + print('success' if success else 'failed') + + # Delete the instantiated asset + in_wrapper.delete_instantiated_asset() + global _g_wrapper + _g_wrapper = None + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Bind to the on post processing delegate (after a cook and after all + # outputs have been generated in Unreal) + _g_wrapper.on_post_processing_delegate.add_callable(on_post_process) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/curve_input_example.py b/Content/Examples/Python/curve_input_example.py new file mode 100644 index 000000000..6d4c93ea6 --- /dev/null +++ b/Content/Examples/Python/curve_input_example.py @@ -0,0 +1,179 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" An example script that uses the API to instantiate an HDA and then +set 2 inputs: a geometry input (a cube) and a curve input (a helix). The +inputs are set during post instantiation (before the first cook). After the +first cook and output creation (post processing) the input structure is fetched +and logged. + +""" +import math + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def get_geo_asset_path(): + return '/Engine/BasicShapes/Cube.Cube' + + +def get_geo_asset(): + return unreal.load_object(None, get_geo_asset_path()) + + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Create a geo input + geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput) + # Set the input objects/assets for this input + geo_object = get_geo_asset() + if not geo_input.set_input_objects((geo_object, )): + # If any errors occurred, get the last error message + print('Error on geo_input: {0}'.format(geo_input.get_last_error_message())) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, geo_input) + # We can now discard the API input object + geo_input = None + + # Create a curve input + curve_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPICurveInput) + # Create a curve wrapper/helper + curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input) + # Make it a Nurbs curve + curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS) + # Set the points of the curve, for this example we create a helix + # consisting of 100 points + curve_points = [] + for i in range(100): + t = i / 20.0 * math.pi * 2.0 + x = 100.0 * math.cos(t) + y = 100.0 * math.sin(t) + z = i + curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1])) + curve_object.set_curve_points(curve_points) + # Error handling/message example: try to set geo_object on curve input + if not curve_input.set_input_objects((geo_object, )): + print('Error (example) while setting \'{0}\' on curve input: {1}'.format( + geo_object.get_name(), curve_input.get_last_error_message() + )) + # Set the curve wrapper as an input object + curve_input.set_input_objects((curve_object, )) + # Copy the input data to the HDA as node input 1 + in_wrapper.set_input_at_index(1, curve_input) + # We can now discard the API input object + curve_input = None + + # Check for errors on the wrapper + last_error = in_wrapper.get_last_error_message() + if last_error: + print('Error on wrapper during input configuration: {0}'.format(last_error)) + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPIGeoInput): + print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) + print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) + print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) + print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) + elif isinstance(in_input, unreal.HoudiniPublicAPICurveInput): + print('\t\tbCookOnCurveChanged: {0}'.format(in_input.cook_on_curve_changed)) + print('\t\tbAddRotAndScaleAttributesOnCurves: {0}'.format(in_input.add_rot_and_scale_attributes_on_curves)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + if isinstance(input_object, unreal.HoudiniPublicAPICurveInputObject): + print('\t\t\tbClosed: {0}'.format(input_object.is_closed())) + print('\t\t\tCurveMethod: {0}'.format(input_object.get_curve_method())) + print('\t\t\tCurveType: {0}'.format(input_object.get_curve_type())) + print('\t\t\tReversed: {0}'.format(input_object.is_reversed())) + print('\t\t\tCurvePoints: {0}'.format(input_object.get_curve_points())) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) + # Print the input state after the cook and output creation. + _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/eau_curve_input_example.py b/Content/Examples/Python/eau_curve_input_example.py new file mode 100644 index 000000000..77658b961 --- /dev/null +++ b/Content/Examples/Python/eau_curve_input_example.py @@ -0,0 +1,134 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import math + +import unreal + +@unreal.uclass() +class CurveInputExample(unreal.PlacedEditorUtilityBase): + # Use a FProperty to hold the reference to the API wrapper we create in + # run_curve_input_example + _asset_wrapper = unreal.uproperty(unreal.HoudiniPublicAPIAssetWrapper) + + @unreal.ufunction(meta=dict(BlueprintCallable=True, CallInEditor=True)) + def run_curve_input_example(self): + # Get the API instance + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + # Ensure we have a running session + if not api.is_session_valid(): + api.create_session() + # Load our HDA uasset + example_hda = unreal.load_object(None, '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0') + # Create an API wrapper instance for instantiating the HDA and interacting with it + wrapper = api.instantiate_asset(example_hda, instantiate_at=unreal.Transform()) + if wrapper: + # Pre-instantiation is the earliest point where we can set parameter values + wrapper.on_pre_instantiation_delegate.add_function(self, '_set_initial_parameter_values') + # Jumping ahead a bit: we also want to configure inputs, but inputs are only available after instantiation + wrapper.on_post_instantiation_delegate.add_function(self, '_set_inputs') + # Jumping ahead a bit: we also want to print the outputs after the node has cook and the plug-in has processed the output + wrapper.on_post_processing_delegate.add_function(self, '_print_outputs') + self.set_editor_property('_asset_wrapper', wrapper) + + @unreal.ufunction(params=[unreal.HoudiniPublicAPIAssetWrapper], meta=dict(CallInEditor=True)) + def _set_initial_parameter_values(self, in_wrapper): + """ Set our initial parameter values: disable upvectorstart and set the scale to 0.2. """ + # Uncheck the upvectoratstart parameter + in_wrapper.set_bool_parameter_value('upvectoratstart', False) + + # Set the scale to 0.2 + in_wrapper.set_float_parameter_value('scale', 0.2) + + # Since we are done with setting the initial values, we can unbind from the delegate + in_wrapper.on_pre_instantiation_delegate.remove_function(self, '_set_initial_parameter_values') + + @unreal.ufunction(params=[unreal.HoudiniPublicAPIAssetWrapper], meta=dict(CallInEditor=True)) + def _set_inputs(self, in_wrapper): + """ Configure our inputs: input 0 is a cube and input 1 a helix. """ + # Create an empty geometry input + geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput) + # Load the cube static mesh asset + cube = unreal.load_object(None, '/Engine/BasicShapes/Cube.Cube') + # Set the input object array for our geometry input, in this case containing only the cube + geo_input.set_input_objects((cube, )) + + # Set the input on the instantiated HDA via the wrapper + in_wrapper.set_input_at_index(0, geo_input) + + # Create a curve input + curve_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPICurveInput) + # Create a curve wrapper/helper + curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input) + # Make it a Nurbs curve + curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS) + # Set the points of the curve, for this example we create a helix + # consisting of 100 points + curve_points = [] + for i in range(100): + t = i / 20.0 * math.pi * 2.0 + x = 100.0 * math.cos(t) + y = 100.0 * math.sin(t) + z = i + curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1])) + curve_object.set_curve_points(curve_points) + # Set the curve wrapper as an input object + curve_input.set_input_objects((curve_object, )) + # Copy the input data to the HDA as node input 1 + in_wrapper.set_input_at_index(1, curve_input) + + # unbind from the delegate, since we are done with setting inputs + in_wrapper.on_post_instantiation_delegate.remove_function(self, '_set_inputs') + + @unreal.ufunction(params=[unreal.HoudiniPublicAPIAssetWrapper], meta=dict(CallInEditor=True)) + def _print_outputs(self, in_wrapper): + """ Print the outputs that were generated by the HDA (after a cook) """ + num_outputs = in_wrapper.get_num_outputs() + print('num_outputs: {}'.format(num_outputs)) + if num_outputs > 0: + for output_idx in range(num_outputs): + identifiers = in_wrapper.get_output_identifiers_at(output_idx) + print('\toutput index: {}'.format(output_idx)) + print('\toutput type: {}'.format(in_wrapper.get_output_type_at(output_idx))) + print('\tnum_output_objects: {}'.format(len(identifiers))) + if identifiers: + for identifier in identifiers: + output_object = in_wrapper.get_output_object_at(output_idx, identifier) + output_component = in_wrapper.get_output_component_at(output_idx, identifier) + is_proxy = in_wrapper.is_output_current_proxy_at(output_idx, identifier) + print('\t\tidentifier: {}'.format(identifier)) + print('\t\toutput_object: {}'.format(output_object.get_name() if output_object else 'None')) + print('\t\toutput_component: {}'.format(output_component.get_name() if output_component else 'None')) + print('\t\tis_proxy: {}'.format(is_proxy)) + print('') + + +def run(): + # Spawn CurveInputExample and call run_curve_input_example + curve_input_example_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(CurveInputExample.static_class(), unreal.Vector.ZERO, unreal.Rotator()) + curve_input_example_actor.run_curve_input_example() + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/geo_input_example.py b/Content/Examples/Python/geo_input_example.py new file mode 100644 index 000000000..3ac7bb1f9 --- /dev/null +++ b/Content/Examples/Python/geo_input_example.py @@ -0,0 +1,181 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" An example script that uses the API to instantiate an HDA and then +set 2 geometry inputs: one node input and one object path parameter +input. The inputs are set during post instantiation (before the first +cook). After the first cook and output creation (post processing) the +input structure is fetched and logged. + +""" + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/subnet_test_2_0.subnet_test_2_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def get_geo_asset_path(): + return '/Engine/BasicShapes/Cube.Cube' + + +def get_geo_asset(): + return unreal.load_object(None, get_geo_asset_path()) + + +def get_cylinder_asset_path(): + return '/Engine/BasicShapes/Cylinder.Cylinder' + + +def get_cylinder_asset(): + return unreal.load_object(None, get_cylinder_asset_path()) + + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Deprecated input functions + # in_wrapper.set_input_type(0, unreal.HoudiniInputType.GEOMETRY) + # in_wrapper.set_input_objects(0, (get_geo_asset(), )) + + # in_wrapper.set_input_type(1, unreal.HoudiniInputType.GEOMETRY) + # in_wrapper.set_input_objects(1, (get_geo_asset(), )) + # in_wrapper.set_input_import_as_reference(1, True) + + # Create a geometry input + geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput) + # Set the input objects/assets for this input + geo_asset = get_geo_asset() + geo_input.set_input_objects((geo_asset, )) + # Set the transform of the input geo + geo_input.set_object_transform_offset( + geo_asset, + unreal.Transform( + (200, 0, 100), + (45, 0, 45), + (2, 2, 2), + ) + ) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, geo_input) + # We can now discard the API input object + geo_input = None + + # Create a another geometry input + geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput) + # Set the input objects/assets for this input (cylinder in this case) + geo_asset = get_cylinder_asset() + geo_input.set_input_objects((geo_asset, )) + # Set the transform of the input geo + geo_input.set_object_transform_offset( + geo_asset, + unreal.Transform( + (-200, 0, 0), + (0, 0, 0), + (2, 2, 2), + ) + ) + # copy the input data to the HDA as input parameter 'objpath1' + in_wrapper.set_input_parameter('objpath1', geo_input) + # We can now discard the API input object + geo_input = None + + # Set the subnet_test HDA to output its first input + in_wrapper.set_int_parameter_value('enable_geo', 1) + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPIGeoInput): + print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) + print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) + print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) + print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) + # Bind on_post_processing, after cook + output creation + _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/instances_example.py b/Content/Examples/Python/instances_example.py new file mode 100644 index 000000000..6e33eb0e3 --- /dev/null +++ b/Content/Examples/Python/instances_example.py @@ -0,0 +1,67 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" Example script that instantiates an HDA using the API and then +setting some parameter values after instantiation but before the +first cook. + +""" + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/pig_head_subdivider_v01.pig_head_subdivider_v01' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def on_post_instantiation(in_wrapper): + print('on_post_instantiation') + # in_wrapper.on_post_instantiation_delegate.remove_callable(on_post_instantiation) + + # Set some parameters to create instances and enable a material + in_wrapper.set_bool_parameter_value('add_instances', True) + in_wrapper.set_int_parameter_value('num_instances', 8) + in_wrapper.set_bool_parameter_value('addshader', True) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Bind on_post_instantiation (before the first cook) callback to set parameters + _g_wrapper.on_post_instantiation_delegate.add_callable(on_post_instantiation) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/iterate_over_houdini_asset_actors_example.py b/Content/Examples/Python/iterate_over_houdini_asset_actors_example.py new file mode 100644 index 000000000..5f42955d2 --- /dev/null +++ b/Content/Examples/Python/iterate_over_houdini_asset_actors_example.py @@ -0,0 +1,103 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +""" Example script that uses unreal.ActorIterator to iterate over all +HoudiniAssetActors in the editor world, creates API wrappers for them, and +logs all of their parameter tuples. + +""" + +import unreal + + +def run(): + # Get the API instance + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + # Get the editor world + editor_subsystem = None + world = None + try: + # In UE5 unreal.EditorLevelLibrary.get_editor_world is deprecated and + # unreal.UnrealEditorSubsystem.get_editor_world is the replacement, + # but unreal.UnrealEditorSubsystem does not exist in UE4 + editor_subsystem = unreal.UnrealEditorSubsystem() + except AttributeError: + world = unreal.EditorLevelLibrary.get_editor_world() + else: + world = editor_subsystem.get_editor_world() + + # Iterate over all Houdini Asset Actors in the editor world + for houdini_actor in unreal.ActorIterator(world, unreal.HoudiniAssetActor): + if not houdini_actor: + continue + + # Print the name and label of the actor + actor_name = houdini_actor.get_name() + actor_label = houdini_actor.get_actor_label() + print(f'HDA Actor (Name, Label): {actor_name}, {actor_label}') + + # Wrap the Houdini asset actor with the API + wrapper = unreal.HoudiniPublicAPIAssetWrapper.create_wrapper(world, houdini_actor) + if not wrapper: + continue + + # Get all parameter tuples of the HDA + parameter_tuples = wrapper.get_parameter_tuples() + if parameter_tuples is None: + # The operation failed, log the error message + error_message = wrapper.get_last_error_message() + if error_message is not None: + print(error_message) + + continue + + print(f'# Parameter Tuples: {len(parameter_tuples)}') + for name, data in parameter_tuples.items(): + print(f'\tParameter Tuple Name: {name}') + + type_name = None + values = None + if data.bool_values: + type_name = 'Bool' + values = '; '.join(('1' if v else '0' for v in data.bool_values)) + elif data.float_values: + type_name = 'Float' + values = '; '.join((f'{v:.4f}' for v in data.float_values)) + elif data.int32_values: + type_name = 'Int32' + values = '; '.join((f'{v:d}' for v in data.int32_values)) + elif data.string_values: + type_name = 'String' + values = '; '.join(data.string_values) + + if not type_name: + print('\t\tEmpty') + else: + print(f'\t\t{type_name} Values: {values}') + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/landscape_input_example.py b/Content/Examples/Python/landscape_input_example.py new file mode 100644 index 000000000..1064b3273 --- /dev/null +++ b/Content/Examples/Python/landscape_input_example.py @@ -0,0 +1,136 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" Example script using the API to instantiate an HDA and set a landscape +input. + +""" + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/hilly_landscape_erode_1_0.hilly_landscape_erode_1_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def load_map(): + return unreal.EditorLoadingAndSavingUtils.load_map('/HoudiniEngine/Examples/Maps/LandscapeInputExample.LandscapeInputExample') + +def get_landscape(): + return unreal.EditorLevelLibrary.get_actor_reference('PersistentLevel.Landscape_0') + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Create a landscape input + landscape_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPILandscapeInput) + # Send landscapes as heightfields + landscape_input.landscape_export_type = unreal.HoudiniLandscapeExportType.HEIGHTFIELD + # Keep world transform + landscape_input.keep_world_transform = True + # Set the input objects/assets for this input + landscape_object = get_landscape() + landscape_input.set_input_objects((landscape_object, )) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, landscape_input) + # We can now discard the API input object + landscape_input = None + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + if isinstance(in_input, unreal.HoudiniPublicAPILandscapeInput): + print('\t\tbLandscapeAutoSelectComponent: {0}'.format(in_input.landscape_auto_select_component)) + print('\t\tbLandscapeExportLighting: {0}'.format(in_input.landscape_export_lighting)) + print('\t\tbLandscapeExportMaterials: {0}'.format(in_input.landscape_export_materials)) + print('\t\tbLandscapeExportNormalizedUVs: {0}'.format(in_input.landscape_export_normalized_u_vs)) + print('\t\tbLandscapeExportSelectionOnly: {0}'.format(in_input.landscape_export_selection_only)) + print('\t\tbLandscapeExportTileUVs: {0}'.format(in_input.landscape_export_tile_u_vs)) + print('\t\tbLandscapeExportType: {0}'.format(in_input.landscape_export_type)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def run(): + # Load the example map + load_map() + + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) + # Print the input state after the cook and output creation. + _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/outputs_example.py b/Content/Examples/Python/outputs_example.py new file mode 100644 index 000000000..6269ab1ce --- /dev/null +++ b/Content/Examples/Python/outputs_example.py @@ -0,0 +1,114 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import unreal + +""" Example script for instantiating an asset, setting parameters, cooking it +and iterating over and logging all of its output objects. + +""" + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/pig_head_subdivider_v01.pig_head_subdivider_v01' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def on_post_instantiation(in_wrapper): + print('on_post_instantiation') + # in_wrapper.on_post_instantiation_state_exited_delegate_delegate.remove_callable(on_post_instantiation) + + # Set parameter values for the next cook + # in_wrapper.set_bool_parameter_value('add_instances', True) + # in_wrapper.set_int_parameter_value('num_instances', 8) + in_wrapper.set_parameter_tuples({ + 'add_instances': unreal.HoudiniParameterTuple(bool_values=(True, )), + 'num_instances': unreal.HoudiniParameterTuple(int32_values=(8, )), + }) + + # Print all parameter values + param_tuples = in_wrapper.get_parameter_tuples() + print('parameter tuples: {}'.format(len(param_tuples) if param_tuples else 0)) + if param_tuples: + for param_tuple_name, param_tuple in param_tuples.items(): + print('parameter tuple name: {}'.format(param_tuple_name)) + print('\tbool_values: {}'.format(param_tuple.bool_values)) + print('\tfloat_values: {}'.format(param_tuple.float_values)) + print('\tint32_values: {}'.format(param_tuple.int32_values)) + print('\tstring_values: {}'.format(param_tuple.string_values)) + + # Force a cook/recook + in_wrapper.recook() + + +def on_post_process(in_wrapper): + print('on_post_process') + + # in_wrapper.on_post_processing_delegate.remove_callable(on_post_process) + + # Print out all outputs generated by the HDA + num_outputs = in_wrapper.get_num_outputs() + print('num_outputs: {}'.format(num_outputs)) + if num_outputs > 0: + for output_idx in range(num_outputs): + identifiers = in_wrapper.get_output_identifiers_at(output_idx) + print('\toutput index: {}'.format(output_idx)) + print('\toutput type: {}'.format(in_wrapper.get_output_type_at(output_idx))) + print('\tnum_output_objects: {}'.format(len(identifiers))) + if identifiers: + for identifier in identifiers: + output_object = in_wrapper.get_output_object_at(output_idx, identifier) + output_component = in_wrapper.get_output_component_at(output_idx, identifier) + is_proxy = in_wrapper.is_output_current_proxy_at(output_idx, identifier) + print('\t\tidentifier: {}'.format(identifier)) + print('\t\toutput_object: {}'.format(output_object.get_name() if output_object else 'None')) + print('\t\toutput_component: {}'.format(output_component.get_name() if output_component else 'None')) + print('\t\tis_proxy: {}'.format(is_proxy)) + print('') + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + + # instantiate an asset, disabling auto-cook of the asset (so we have to + # call wrapper.reCook() to cook it) + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform(), enable_auto_cook=False) + + # Bind to the on post instantiation delegate (before the first cook) + _g_wrapper.on_post_instantiation_delegate.add_callable(on_post_instantiation) + # Bind to the on post processing delegate (after a cook and after all + # outputs have been generated in Unreal) + _g_wrapper.on_post_processing_delegate.add_callable(on_post_process) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/pdg_example.py b/Content/Examples/Python/pdg_example.py new file mode 100644 index 000000000..06cf80e6d --- /dev/null +++ b/Content/Examples/Python/pdg_example.py @@ -0,0 +1,141 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" An example script that instantiates an HDA that contains a TOP network. +After the HDA itself has cooked (in on_post_process) we iterate over all +TOP networks in the HDA and print their paths. Auto-bake is then enabled for +PDG and the TOP networks are cooked. + +""" + +import os + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/pdg_pighead_grid_2_0.pdg_pighead_grid_2_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def get_hda_directory(): + """ Attempt to get the directory containing the .hda files. This is + /HoudiniEngine/Examples/hda. In newer versions of UE we can use + ``unreal.SystemLibrary.get_system_path(asset)``, in older versions + we manually construct the path to the plugin. + + Returns: + (str): the path to the example hda directory or ``None``. + + """ + if hasattr(unreal.SystemLibrary, 'get_system_path'): + return os.path.dirname(os.path.normpath( + unreal.SystemLibrary.get_system_path(get_test_hda()))) + else: + plugin_dir = os.path.join( + os.path.normpath(unreal.Paths.project_plugins_dir()), + 'Runtime', 'HoudiniEngine', 'Content', 'Examples', 'hda' + ) + if not os.path.exists(plugin_dir): + plugin_dir = os.path.join( + os.path.normpath(unreal.Paths.engine_plugins_dir()), + 'Runtime', 'HoudiniEngine', 'Content', 'Examples', 'hda' + ) + if not os.path.exists(plugin_dir): + return None + return plugin_dir + + +def delete_instantiated_asset(): + global _g_wrapper + if _g_wrapper: + result = _g_wrapper.delete_instantiated_asset() + _g_wrapper = None + return result + else: + return False + + +def on_pre_instantiation(in_wrapper): + print('on_pre_instantiation') + + # Set the hda_directory parameter to the directory that contains the + # example .hda files + hda_directory = get_hda_directory() + print('Setting "hda_directory" to {0}'.format(hda_directory)) + in_wrapper.set_string_parameter_value('hda_directory', hda_directory) + + # Cook the HDA (not PDG yet) + in_wrapper.recook() + + +def on_post_bake(in_wrapper, success): + # in_wrapper.on_post_bake_delegate.remove_callable(on_post_bake) + print('bake complete ... {}'.format('success' if success else 'failed')) + + +def on_post_process(in_wrapper): + print('on_post_process') + + # in_wrapper.on_post_processing_delegate.remove_callable(on_post_process) + + # Iterate over all PDG/TOP networks and nodes and log them + print('TOP networks:') + for network_path in in_wrapper.get_pdgtop_network_paths(): + print('\t{}'.format(network_path)) + for node_path in in_wrapper.get_pdgtop_node_paths(network_path): + print('\t\t{}'.format(node_path)) + + # Enable PDG auto-bake (auto bake TOP nodes after they are cooked) + in_wrapper.set_pdg_auto_bake_enabled(True) + # Bind to PDG post bake delegate (called after all baking is complete) + in_wrapper.on_post_pdg_bake_delegate.add_callable(on_post_bake) + # Cook the specified TOP node + in_wrapper.pdg_cook_node('topnet1', 'HE_OUT_PIGHEAD_GRID') + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + + # instantiate an asset, disabling auto-cook of the asset + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform(), enable_auto_cook=False) + + # Bind to the on pre instantiation delegate (before the first cook) and + # set parameters + _g_wrapper.on_pre_instantiation_delegate.add_callable(on_pre_instantiation) + # Bind to the on post processing delegate (after a cook and after all + # outputs have been generated in Unreal) + _g_wrapper.on_post_processing_delegate.add_callable(on_post_process) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/process_hda_example.py b/Content/Examples/Python/process_hda_example.py new file mode 100644 index 000000000..09429845b --- /dev/null +++ b/Content/Examples/Python/process_hda_example.py @@ -0,0 +1,196 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" An example script that uses the API and +HoudiniEngineV2.asyncprocessor.ProcessHDA. ProcessHDA is configured with the +asset to instantiate, as well as 2 inputs: a geometry input (a cube) and a +curve input (a helix). + +ProcessHDA is then activiated upon which the asset will be instantiated, +inputs set, and cooked. The ProcessHDA class's on_post_processing() function is +overridden to fetch the input structure and logged. The other state/phase +functions (on_pre_instantiate(), on_post_instantiate() etc) are overridden to +simply log the function name, in order to observe progress in the log. + +""" +import math + +import unreal + +from HoudiniEngineV2.asyncprocessor import ProcessHDA + + +_g_processor = None + + +class ProcessHDAExample(ProcessHDA): + @staticmethod + def _print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPIGeoInput): + print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) + print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) + print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) + print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) + elif isinstance(in_input, unreal.HoudiniPublicAPICurveInput): + print('\t\tbCookOnCurveChanged: {0}'.format(in_input.cook_on_curve_changed)) + print('\t\tbAddRotAndScaleAttributesOnCurves: {0}'.format(in_input.add_rot_and_scale_attributes_on_curves)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + if isinstance(input_object, unreal.HoudiniPublicAPICurveInputObject): + print('\t\t\tbClosed: {0}'.format(input_object.is_closed())) + print('\t\t\tCurveMethod: {0}'.format(input_object.get_curve_method())) + print('\t\t\tCurveType: {0}'.format(input_object.get_curve_type())) + print('\t\t\tReversed: {0}'.format(input_object.is_reversed())) + print('\t\t\tCurvePoints: {0}'.format(input_object.get_curve_points())) + + def on_failure(self): + print('on_failure') + global _g_processor + _g_processor = None + + def on_complete(self): + print('on_complete') + global _g_processor + _g_processor = None + + def on_pre_instantiation(self): + print('on_pre_instantiation') + + def on_post_instantiation(self): + print('on_post_instantiation') + + def on_post_auto_cook(self, cook_success): + print('on_post_auto_cook, success = {0}'.format(cook_success)) + + def on_pre_process(self): + print('on_pre_process') + + def on_post_processing(self): + print('on_post_processing') + + # Fetch inputs, iterate over it and log + node_inputs = self.asset_wrapper.get_inputs_at_indices() + parm_inputs = self.asset_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + self._print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + self._print_api_input(input_wrapper) + + def on_post_auto_bake(self, bake_success): + print('on_post_auto_bake, succes = {0}'.format(bake_success)) + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def get_geo_asset_path(): + return '/Engine/BasicShapes/Cube.Cube' + + +def get_geo_asset(): + return unreal.load_object(None, get_geo_asset_path()) + + +def build_inputs(): + print('configure_inputs') + + # get the API singleton + houdini_api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + node_inputs = {} + + # Create a geo input + geo_input = houdini_api.create_empty_input(unreal.HoudiniPublicAPIGeoInput) + # Set the input objects/assets for this input + geo_object = get_geo_asset() + geo_input.set_input_objects((geo_object, )) + # store the input data to the HDA as node input 0 + node_inputs[0] = geo_input + + # Create a curve input + curve_input = houdini_api.create_empty_input(unreal.HoudiniPublicAPICurveInput) + # Create a curve wrapper/helper + curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input) + # Make it a Nurbs curve + curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS) + # Set the points of the curve, for this example we create a helix + # consisting of 100 points + curve_points = [] + for i in range(100): + t = i / 20.0 * math.pi * 2.0 + x = 100.0 * math.cos(t) + y = 100.0 * math.sin(t) + z = i + curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1])) + curve_object.set_curve_points(curve_points) + # Set the curve wrapper as an input object + curve_input.set_input_objects((curve_object, )) + # Store the input data to the HDA as node input 1 + node_inputs[1] = curve_input + + return node_inputs + + +def run(): + # Create the processor with preconfigured inputs + global _g_processor + _g_processor = ProcessHDAExample( + get_test_hda(), node_inputs=build_inputs()) + # Activate the processor, this will starts instantiation, and then cook + if not _g_processor.activate(): + unreal.log_warning('Activation failed.') + else: + unreal.log('Activated!') + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/ramp_parameter_example.py b/Content/Examples/Python/ramp_parameter_example.py new file mode 100644 index 000000000..488eaa2c3 --- /dev/null +++ b/Content/Examples/Python/ramp_parameter_example.py @@ -0,0 +1,152 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" An example script that uses the API to instantiate an HDA and then set +the ramp points of a float ramp and a color ramp. + +""" +import math + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/ramp_example_1_0.ramp_example_1_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def set_parameters(in_wrapper): + print('set_parameters') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(set_parameters) + + # There are two ramps: heightramp and colorramp. The height ramp is a float + # ramp. As an example we'll set the number of ramp points and then set + # each point individually + in_wrapper.set_ramp_parameter_num_points('heightramp', 6) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 0, 0.0, 0.1) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 1, 0.2, 0.6) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 2, 0.4, 1.0) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 3, 0.6, 1.4) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 4, 0.8, 1.8) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 5, 1.0, 2.2) + + # For the color ramp, as an example, we can set the all the points via an + # array. + in_wrapper.set_color_ramp_parameter_points('colorramp', ( + unreal.HoudiniPublicAPIColorRampPoint(position=0.0, value=unreal.LinearColor.GRAY), + unreal.HoudiniPublicAPIColorRampPoint(position=0.5, value=unreal.LinearColor.GREEN), + unreal.HoudiniPublicAPIColorRampPoint(position=1.0, value=unreal.LinearColor.RED), + )) + + +def print_parameters(in_wrapper): + print('print_parameters') + + in_wrapper.on_post_processing_delegate.remove_callable(print_parameters) + + # Print the ramp points directly + print('heightramp: num points {0}:'.format(in_wrapper.get_ramp_parameter_num_points('heightramp'))) + heightramp_data = in_wrapper.get_float_ramp_parameter_points('heightramp') + if not heightramp_data: + print('\tNone') + else: + for idx, point_data in enumerate(heightramp_data): + print('\t\t{0}: position={1:.6f}; value={2:.6f}; interpoloation={3}'.format( + idx, + point_data.position, + point_data.value, + point_data.interpolation + )) + + print('colorramp: num points {0}:'.format(in_wrapper.get_ramp_parameter_num_points('colorramp'))) + colorramp_data = in_wrapper.get_color_ramp_parameter_points('colorramp') + if not colorramp_data: + print('\tNone') + else: + for idx, point_data in enumerate(colorramp_data): + print('\t\t{0}: position={1:.6f}; value={2}; interpoloation={3}'.format( + idx, + point_data.position, + point_data.value, + point_data.interpolation + )) + + # Print all parameter values + param_tuples = in_wrapper.get_parameter_tuples() + print('parameter tuples: {}'.format(len(param_tuples) if param_tuples else 0)) + if param_tuples: + for param_tuple_name, param_tuple in param_tuples.items(): + print('parameter tuple name: {}'.format(param_tuple_name)) + print('\tbool_values: {}'.format(param_tuple.bool_values)) + print('\tfloat_values: {}'.format(param_tuple.float_values)) + print('\tint32_values: {}'.format(param_tuple.int32_values)) + print('\tstring_values: {}'.format(param_tuple.string_values)) + if not param_tuple.float_ramp_points: + print('\tfloat_ramp_points: None') + else: + print('\tfloat_ramp_points:') + for idx, point_data in enumerate(param_tuple.float_ramp_points): + print('\t\t{0}: position={1:.6f}; value={2:.6f}; interpoloation={3}'.format( + idx, + point_data.position, + point_data.value, + point_data.interpolation + )) + if not param_tuple.color_ramp_points: + print('\tcolor_ramp_points: None') + else: + print('\tcolor_ramp_points:') + for idx, point_data in enumerate(param_tuple.color_ramp_points): + print('\t\t{0}: position={1:.6f}; value={2}; interpoloation={3}'.format( + idx, + point_data.position, + point_data.value, + point_data.interpolation + )) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Set the float and color ramps on post instantiation, before the first + # cook. + _g_wrapper.on_post_instantiation_delegate.add_callable(set_parameters) + # Print the parameter state after the cook and output creation. + _g_wrapper.on_post_processing_delegate.add_callable(print_parameters) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/start_session_example.py b/Content/Examples/Python/start_session_example.py new file mode 100644 index 000000000..e4f8854e0 --- /dev/null +++ b/Content/Examples/Python/start_session_example.py @@ -0,0 +1,43 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import unreal + +""" Example for getting the API instance and starting/creating the Houdini +Engine Session. + +""" + + +def run(): + # Get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + # Check if there is an existing valid session + if not api.is_session_valid(): + # Create a new session + api.create_session() + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/world_input_example.py b/Content/Examples/Python/world_input_example.py new file mode 100644 index 000000000..7b74e2929 --- /dev/null +++ b/Content/Examples/Python/world_input_example.py @@ -0,0 +1,187 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" An example script that spawns some actors and then uses the API to +instantiate an HDA and set the actors as world inputs. The inputs are set +during post instantiation (before the first cook). After the first cook and +output creation (post processing) the input structure is fetched and logged. + +""" + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/subnet_test_2_0.subnet_test_2_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def get_geo_asset_path(): + return '/Engine/BasicShapes/Cube.Cube' + + +def get_geo_asset(): + return unreal.load_object(None, get_geo_asset_path()) + + +def get_cylinder_asset_path(): + return '/Engine/BasicShapes/Cylinder.Cylinder' + + +def get_cylinder_asset(): + return unreal.load_object(None, get_cylinder_asset_path()) + + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Spawn some actors + actors = spawn_actors() + + # Create a world input + world_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIWorldInput) + # Set the input objects/assets for this input + world_input.set_input_objects(actors) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, world_input) + # We can now discard the API input object + world_input = None + + # Set the subnet_test HDA to output its first input + in_wrapper.set_int_parameter_value('enable_geo', 1) + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPIWorldInput): + print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) + print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) + print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) + print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) + print('\t\tbIsWorldInputBoundSelector: {0}'.format(in_input.is_world_input_bound_selector)) + print('\t\tbWorldInputBoundSelectorAutoUpdate: {0}'.format(in_input.world_input_bound_selector_auto_update)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def spawn_actors(): + actors = [] + # Spawn a static mesh actor and assign a cylinder to its static mesh + # component + actor = unreal.EditorLevelLibrary.spawn_actor_from_class( + unreal.StaticMeshActor, location=(0, 0, 0)) + actor.static_mesh_component.set_static_mesh(get_cylinder_asset()) + actor.set_actor_label('Cylinder') + actor.set_actor_transform( + unreal.Transform( + (-200, 0, 0), + (0, 0, 0), + (2, 2, 2), + ), + sweep=False, + teleport=True + ) + actors.append(actor) + + # Spawn a static mesh actor and assign a cube to its static mesh + # component + actor = unreal.EditorLevelLibrary.spawn_actor_from_class( + unreal.StaticMeshActor, location=(0, 0, 0)) + actor.static_mesh_component.set_static_mesh(get_geo_asset()) + actor.set_actor_label('Cube') + actor.set_actor_transform( + unreal.Transform( + (200, 0, 100), + (45, 0, 45), + (2, 2, 2), + ), + sweep=False, + teleport=True + ) + actors.append(actor) + + return actors + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) + # Bind on_post_processing, after cook + output creation + _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/hda/copy_to_curve.1.0.hda b/Content/Examples/hda/copy_to_curve.1.0.hda new file mode 100644 index 000000000..d3323f0ef Binary files /dev/null and b/Content/Examples/hda/copy_to_curve.1.0.hda differ diff --git a/Content/Examples/hda/copy_to_curve_1_0.uasset b/Content/Examples/hda/copy_to_curve_1_0.uasset new file mode 100644 index 000000000..2245444bd Binary files /dev/null and b/Content/Examples/hda/copy_to_curve_1_0.uasset differ diff --git a/Content/Examples/hda/hilly_landscape_erode.1.0.hda b/Content/Examples/hda/hilly_landscape_erode.1.0.hda new file mode 100644 index 000000000..9573fbb34 Binary files /dev/null and b/Content/Examples/hda/hilly_landscape_erode.1.0.hda differ diff --git a/Content/Examples/hda/hilly_landscape_erode_1_0.uasset b/Content/Examples/hda/hilly_landscape_erode_1_0.uasset new file mode 100644 index 000000000..582999e06 Binary files /dev/null and b/Content/Examples/hda/hilly_landscape_erode_1_0.uasset differ diff --git a/Content/Examples/hda/pdg_pighead_grid.2.0.hda b/Content/Examples/hda/pdg_pighead_grid.2.0.hda new file mode 100644 index 000000000..0a1e73845 Binary files /dev/null and b/Content/Examples/hda/pdg_pighead_grid.2.0.hda differ diff --git a/Content/Examples/hda/pdg_pighead_grid_2_0.uasset b/Content/Examples/hda/pdg_pighead_grid_2_0.uasset new file mode 100644 index 000000000..54e24afb0 Binary files /dev/null and b/Content/Examples/hda/pdg_pighead_grid_2_0.uasset differ diff --git a/Content/Examples/hda/pig_head_subdivider_v01.hda b/Content/Examples/hda/pig_head_subdivider_v01.hda new file mode 100644 index 000000000..327292a9c Binary files /dev/null and b/Content/Examples/hda/pig_head_subdivider_v01.hda differ diff --git a/Content/Examples/hda/pig_head_subdivider_v01.uasset b/Content/Examples/hda/pig_head_subdivider_v01.uasset new file mode 100644 index 000000000..8392103c7 Binary files /dev/null and b/Content/Examples/hda/pig_head_subdivider_v01.uasset differ diff --git a/Content/Examples/hda/ramp_example.1.0.hda b/Content/Examples/hda/ramp_example.1.0.hda new file mode 100644 index 000000000..30d68bcf1 Binary files /dev/null and b/Content/Examples/hda/ramp_example.1.0.hda differ diff --git a/Content/Examples/hda/ramp_example_1_0.uasset b/Content/Examples/hda/ramp_example_1_0.uasset new file mode 100644 index 000000000..170496f39 Binary files /dev/null and b/Content/Examples/hda/ramp_example_1_0.uasset differ diff --git a/Content/Examples/hda/subnet_test.2.0.hda b/Content/Examples/hda/subnet_test.2.0.hda new file mode 100644 index 000000000..0946cdccd Binary files /dev/null and b/Content/Examples/hda/subnet_test.2.0.hda differ diff --git a/Content/Examples/hda/subnet_test_2_0.uasset b/Content/Examples/hda/subnet_test_2_0.uasset new file mode 100644 index 000000000..0e8bd0b3c Binary files /dev/null and b/Content/Examples/hda/subnet_test_2_0.uasset differ diff --git a/Content/MaterialFunctions/MF_VAT_Fluid.uasset b/Content/MaterialFunctions/MF_VAT_Fluid.uasset new file mode 100644 index 000000000..aaf670e17 Binary files /dev/null and b/Content/MaterialFunctions/MF_VAT_Fluid.uasset differ diff --git a/Content/MaterialFunctions/MF_VAT_Rigid.uasset b/Content/MaterialFunctions/MF_VAT_Rigid.uasset new file mode 100644 index 000000000..2c8766beb Binary files /dev/null and b/Content/MaterialFunctions/MF_VAT_Rigid.uasset differ diff --git a/Content/MaterialFunctions/MF_VAT_Soft.uasset b/Content/MaterialFunctions/MF_VAT_Soft.uasset new file mode 100644 index 000000000..c436a8fde Binary files /dev/null and b/Content/MaterialFunctions/MF_VAT_Soft.uasset differ diff --git a/Content/MaterialFunctions/MF_VAT_Sprite.uasset b/Content/MaterialFunctions/MF_VAT_Sprite.uasset new file mode 100644 index 000000000..97388727e Binary files /dev/null and b/Content/MaterialFunctions/MF_VAT_Sprite.uasset differ diff --git a/Content/Materials/HoudiniVertexAnimationFluid.uasset b/Content/Materials/HoudiniVertexAnimationFluid.uasset deleted file mode 100644 index aaed30192..000000000 Binary files a/Content/Materials/HoudiniVertexAnimationFluid.uasset and /dev/null differ diff --git a/Content/Materials/HoudiniVertexAnimationRigid.uasset b/Content/Materials/HoudiniVertexAnimationRigid.uasset deleted file mode 100644 index ec41c0be0..000000000 Binary files a/Content/Materials/HoudiniVertexAnimationRigid.uasset and /dev/null differ diff --git a/Content/Materials/HoudiniVertexAnimationSoft.uasset b/Content/Materials/HoudiniVertexAnimationSoft.uasset deleted file mode 100644 index afd616a7e..000000000 Binary files a/Content/Materials/HoudiniVertexAnimationSoft.uasset and /dev/null differ diff --git a/Content/Materials/HoudiniVertexAnimationSprite.uasset b/Content/Materials/HoudiniVertexAnimationSprite.uasset deleted file mode 100644 index 02ec961d6..000000000 Binary files a/Content/Materials/HoudiniVertexAnimationSprite.uasset and /dev/null differ diff --git a/Content/Materials/M_VAT_Fluid.uasset b/Content/Materials/M_VAT_Fluid.uasset new file mode 100644 index 000000000..0efdcce45 Binary files /dev/null and b/Content/Materials/M_VAT_Fluid.uasset differ diff --git a/Content/Materials/M_VAT_Rigid.uasset b/Content/Materials/M_VAT_Rigid.uasset new file mode 100644 index 000000000..766007310 Binary files /dev/null and b/Content/Materials/M_VAT_Rigid.uasset differ diff --git a/Content/Materials/M_VAT_Soft.uasset b/Content/Materials/M_VAT_Soft.uasset new file mode 100644 index 000000000..aa0756555 Binary files /dev/null and b/Content/Materials/M_VAT_Soft.uasset differ diff --git a/Content/Materials/M_VAT_Sprite.uasset b/Content/Materials/M_VAT_Sprite.uasset new file mode 100644 index 000000000..0809fc5c1 Binary files /dev/null and b/Content/Materials/M_VAT_Sprite.uasset differ diff --git a/Content/Python/HoudiniEngineV2/__init__.py b/Content/Python/HoudiniEngineV2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Content/Python/HoudiniEngineV2/asyncprocessor.py b/Content/Python/HoudiniEngineV2/asyncprocessor.py new file mode 100644 index 000000000..c8cc28f06 --- /dev/null +++ b/Content/Python/HoudiniEngineV2/asyncprocessor.py @@ -0,0 +1,530 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import unreal + + +class ProcessHDA(object): + """ An object that wraps async processing of an HDA (instantiating, + cooking/processing/baking an HDA), with functions that are called at the + various stages of the process, that can be overridden by subclasses for + custom funtionality: + + - on_failure() + - on_complete(): upon successful completion (could be PostInstantiation + if auto cook is disabled, PostProcessing if auto bake is disabled, or + after PostAutoBake if auto bake is enabled. + - on_pre_instantiation(): before the HDA is instantiated, a good place + to set parameter values before the first cook. + - on_post_instantiation(): after the HDA is instantiated, a good place + to set/configure inputs before the first cook. + - on_post_auto_cook(): right after a cook + - on_pre_process(): after a cook but before output objects have been + created/processed + - on_post_processing(): after output objects have been created + - on_post_auto_bake(): after outputs have been baked + + Instantiate the processor via the constructor and then call the activate() + function to start the asynchronous process. + + """ + def __init__( + self, + houdini_asset, + instantiate_at=unreal.Transform(), + parameters=None, + node_inputs=None, + parameter_inputs=None, + world_context_object=None, + spawn_in_level_override=None, + enable_auto_cook=True, + enable_auto_bake=False, + bake_directory_path="", + bake_method=unreal.HoudiniEngineBakeOption.TO_ACTOR, + remove_output_after_bake=False, + recenter_baked_actors=False, + replace_previous_bake=False, + delete_instantiated_asset_on_completion_or_failure=False): + """ Instantiates an HDA in the specified world/level. Sets parameters + and inputs supplied in InParameters, InNodeInputs and parameter_inputs. + If bInEnableAutoCook is true, cooks the HDA. If bInEnableAutoBake is + true, bakes the cooked outputs according to the supplied baking + parameters. + + This all happens asynchronously, with the various output pins firing at + the various points in the process: + + - PreInstantiation: before the HDA is instantiated, a good place + to set parameter values before the first cook (parameter values + from ``parameters`` are automatically applied at this point) + - PostInstantiation: after the HDA is instantiated, a good place + to set/configure inputs before the first cook (inputs from + ``node_inputs`` and ``parameter_inputs`` are automatically applied + at this point) + - PostAutoCook: right after a cook + - PreProcess: after a cook but before output objects have been + created/processed + - PostProcessing: after output objects have been created + - PostAutoBake: after outputs have been baked + - Completed: upon successful completion (could be PostInstantiation + if auto cook is disabled, PostProcessing if auto bake is disabled, + or after PostAutoBake if auto bake is enabled). + - Failed: If the process failed at any point. + + Args: + houdini_asset (HoudiniAsset): The HDA to instantiate. + instantiate_at (Transform): The Transform to instantiate the HDA with. + parameters (Map(Name, HoudiniParameterTuple)): The parameters to set before cooking the instantiated HDA. + node_inputs (Map(int32, HoudiniPublicAPIInput)): The node inputs to set before cooking the instantiated HDA. + parameter_inputs (Map(Name, HoudiniPublicAPIInput)): The parameter-based inputs to set before cooking the instantiated HDA. + world_context_object (Object): A world context object for identifying the world to spawn in, if spawn_in_level_override is null. + spawn_in_level_override (Level): If not nullptr, then the HoudiniAssetActor is spawned in that level. If both spawn_in_level_override and world_context_object are null, then the actor is spawned in the current editor context world's current level. + enable_auto_cook (bool): If true (the default) the HDA will cook automatically after instantiation and after parameter, transform and input changes. + enable_auto_bake (bool): If true, the HDA output is automatically baked after a cook. Defaults to false. + bake_directory_path (str): The directory to bake to if the bake path is not set via attributes on the HDA output. + bake_method (HoudiniEngineBakeOption): The bake target (to actor vs blueprint). @see HoudiniEngineBakeOption. + remove_output_after_bake (bool): If true, HDA temporary outputs are removed after a bake. Defaults to false. + recenter_baked_actors (bool): Recenter the baked actors to their bounding box center. Defaults to false. + replace_previous_bake (bool): If true, on every bake replace the previous bake's output (assets + actors) with the new bake's output. Defaults to false. + delete_instantiated_asset_on_completion_or_failure (bool): If true, deletes the instantiated asset actor on completion or failure. Defaults to false. + + """ + super(ProcessHDA, self).__init__() + self._houdini_asset = houdini_asset + self._instantiate_at = instantiate_at + self._parameters = parameters + self._node_inputs = node_inputs + self._parameter_inputs = parameter_inputs + self._world_context_object = world_context_object + self._spawn_in_level_override = spawn_in_level_override + self._enable_auto_cook = enable_auto_cook + self._enable_auto_bake = enable_auto_bake + self._bake_directory_path = bake_directory_path + self._bake_method = bake_method + self._remove_output_after_bake = remove_output_after_bake + self._recenter_baked_actors = recenter_baked_actors + self._replace_previous_bake = replace_previous_bake + self._delete_instantiated_asset_on_completion_or_failure = delete_instantiated_asset_on_completion_or_failure + + self._asset_wrapper = None + self._cook_success = False + self._bake_success = False + + @property + def asset_wrapper(self): + """ The asset wrapper for the instantiated HDA processed by this node. """ + return self._asset_wrapper + + @property + def cook_success(self): + """ True if the last cook was successful. """ + return self._cook_success + + @property + def bake_success(self): + """ True if the last bake was successful. """ + return self._bake_success + + @property + def houdini_asset(self): + """ The HDA to instantiate. """ + return self._houdini_asset + + @property + def instantiate_at(self): + """ The transform the instantiate the asset with. """ + return self._instantiate_at + + @property + def parameters(self): + """ The parameters to set on on_pre_instantiation """ + return self._parameters + + @property + def node_inputs(self): + """ The node inputs to set on on_post_instantiation """ + return self._node_inputs + + @property + def parameter_inputs(self): + """ The object path parameter inputs to set on on_post_instantiation """ + return self._parameter_inputs + + @property + def world_context_object(self): + """ The world context object: spawn in this world if spawn_in_level_override is not set. """ + return self._world_context_object + + @property + def spawn_in_level_override(self): + """ The level to spawn in. If both this and world_context_object is not set, spawn in the editor context's level. """ + return self._spawn_in_level_override + + @property + def enable_auto_cook(self): + """ Whether to set the instantiated asset to auto cook. """ + return self._enable_auto_cook + + @property + def enable_auto_bake(self): + """ Whether to set the instantiated asset to auto bake after a cook. """ + return self._enable_auto_bake + + @property + def bake_directory_path(self): + """ Set the fallback bake directory, for if output attributes do not specify it. """ + return self._bake_directory_path + + @property + def bake_method(self): + """ The bake method/target: for example, to actors vs to blueprints. """ + return self._bake_method + + @property + def remove_output_after_bake(self): + """ Remove temporary HDA output after a bake. """ + return self._remove_output_after_bake + + @property + def recenter_baked_actors(self): + """ Recenter the baked actors at their bounding box center. """ + return self._recenter_baked_actors + + @property + def replace_previous_bake(self): + """ Replace previous bake output on each bake. For the purposes of this + node, this would mostly apply to .uassets and not actors. + + """ + return self._replace_previous_bake + + @property + def delete_instantiated_asset_on_completion_or_failure(self): + """ Whether or not to delete the instantiated asset after Complete is called. """ + return self._delete_instantiated_asset_on_completion_or_failure + + def activate(self): + """ Activate the process. This will: + + - instantiate houdini_asset and wrap it as asset_wrapper + - call on_failure() for any immediate failures + - otherwise bind to delegates from asset_wrapper so that the + various self.on_*() functions are called as appropriate + + Returns immediately (does not block until cooking/processing is + complete). + + Returns: + (bool): False if activation failed. + + """ + # Get the API instance + houdini_api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + if not houdini_api: + # Handle failures: this will unbind delegates and call on_failure() + self._handle_on_failure() + return False + + # Create an empty API asset wrapper + self._asset_wrapper = unreal.HoudiniPublicAPIAssetWrapper.create_empty_wrapper(houdini_api) + if not self._asset_wrapper: + # Handle failures: this will unbind delegates and call on_failure() + self._handle_on_failure() + return False + + # Bind to the wrapper's delegates for instantiation, cooking, baking + # etc events + self._asset_wrapper.on_pre_instantiation_delegate.add_callable( + self._handle_on_pre_instantiation) + self._asset_wrapper.on_post_instantiation_delegate.add_callable( + self._handle_on_post_instantiation) + self._asset_wrapper.on_post_cook_delegate.add_callable( + self._handle_on_post_auto_cook) + self._asset_wrapper.on_pre_process_state_exited_delegate.add_callable( + self._handle_on_pre_process) + self._asset_wrapper.on_post_processing_delegate.add_callable( + self._handle_on_post_processing) + self._asset_wrapper.on_post_bake_delegate.add_callable( + self._handle_on_post_auto_bake) + + # Begin the instantiation process of houdini_asset and wrap it with + # self.asset_wrapper + if not houdini_api.instantiate_asset_with_existing_wrapper( + self.asset_wrapper, + self.houdini_asset, + self.instantiate_at, + self.world_context_object, + self.spawn_in_level_override, + self.enable_auto_cook, + self.enable_auto_bake, + self.bake_directory_path, + self.bake_method, + self.remove_output_after_bake, + self.recenter_baked_actors, + self.replace_previous_bake): + # Handle failures: this will unbind delegates and call on_failure() + self._handle_on_failure() + return False + + return True + + def _unbind_delegates(self): + """ Unbinds from self.asset_wrapper's delegates (if valid). """ + if not self._asset_wrapper: + return + + self._asset_wrapper.on_pre_instantiation_delegate.add_callable( + self._handle_on_pre_instantiation) + self._asset_wrapper.on_post_instantiation_delegate.add_callable( + self._handle_on_post_instantiation) + self._asset_wrapper.on_post_cook_delegate.add_callable( + self._handle_on_post_auto_cook) + self._asset_wrapper.on_pre_process_state_exited_delegate.add_callable( + self._handle_on_pre_process) + self._asset_wrapper.on_post_processing_delegate.add_callable( + self._handle_on_post_processing) + self._asset_wrapper.on_post_bake_delegate.add_callable( + self._handle_on_post_auto_bake) + + def _check_wrapper(self, wrapper): + """ Checks that wrapper matches self.asset_wrapper. Logs a warning if + it does not. + + Args: + wrapper (HoudiniPublicAPIAssetWrapper): the wrapper to check + against self.asset_wrapper + + Returns: + (bool): True if the wrappers match. + + """ + if wrapper != self._asset_wrapper: + unreal.log_warning( + '[UHoudiniPublicAPIProcessHDANode] Received delegate event ' + 'from unexpected asset wrapper ({0} vs {1})!'.format( + self._asset_wrapper.get_name() if self._asset_wrapper else '', + wrapper.get_name() if wrapper else '' + ) + ) + return False + return True + + def _handle_on_failure(self): + """ Handle any failures during the lifecycle of the process. Calls + self.on_failure() and then unbinds from self.asset_wrapper and + optionally deletes the instantiated asset. + + """ + self.on_failure() + + self._unbind_delegates() + + if self.delete_instantiated_asset_on_completion_or_failure and self.asset_wrapper: + self.asset_wrapper.delete_instantiated_asset() + + def _handle_on_complete(self): + """ Handles completion of the process. This can happen at one of + three stages: + + - After on_post_instantiate(), if enable_auto_cook is False. + - After on_post_auto_cook(), if enable_auto_cook is True but + enable_auto_bake is False. + - After on_post_auto_bake(), if both enable_auto_cook and + enable_auto_bake are True. + + Calls self.on_complete() and then unbinds from self.asset_wrapper's + delegates and optionally deletes the instantiated asset. + + """ + self.on_complete() + + self._unbind_delegates() + + if self.delete_instantiated_asset_on_completion_or_failure and self.asset_wrapper: + self.asset_wrapper.delete_instantiated_asset() + + def _handle_on_pre_instantiation(self, wrapper): + """ Called during pre_instantiation. Sets ``parameters`` on the HDA + and calls self.on_pre_instantiation(). + + """ + if not self._check_wrapper(wrapper): + return + + # Set any parameters specified for the HDA + if self.asset_wrapper and self.parameters: + self.asset_wrapper.set_parameter_tuples(self.parameters) + + self.on_pre_instantiation() + + def _handle_on_post_instantiation(self, wrapper): + """ Called during post_instantiation. Sets inputs (``node_inputs`` and + ``parameter_inputs``) on the HDA and calls self.on_post_instantiation(). + + Completes execution if enable_auto_cook is False. + + """ + if not self._check_wrapper(wrapper): + return + + # Set any inputs specified when the node was created + if self.asset_wrapper: + if self.node_inputs: + self.asset_wrapper.set_inputs_at_indices(self.node_inputs) + if self.parameter_inputs: + self.asset_wrapper.set_input_parameters(self.parameter_inputs) + + self.on_post_instantiation() + + # If not set to auto cook, complete execution now + if not self.enable_auto_cook: + self._handle_on_complete() + + def _handle_on_post_auto_cook(self, wrapper, cook_success): + """ Called during post_cook. Sets self.cook_success and calls + self.on_post_auto_cook(). + + Args: + cook_success (bool): True if the cook was successful. + + """ + if not self._check_wrapper(wrapper): + return + + self._cook_success = cook_success + + self.on_post_auto_cook(cook_success) + + def _handle_on_pre_process(self, wrapper): + """ Called during pre_process. Calls self.on_pre_process(). + + """ + if not self._check_wrapper(wrapper): + return + + self.on_pre_process() + + def _handle_on_post_processing(self, wrapper): + """ Called during post_processing. Calls self.on_post_processing(). + + Completes execution if enable_auto_bake is False. + + """ + if not self._check_wrapper(wrapper): + return + + self.on_post_processing() + + # If not set to auto bake, complete execution now + if not self.enable_auto_bake: + self._handle_on_complete() + + def _handle_on_post_auto_bake(self, wrapper, bake_success): + """ Called during post_bake. Sets self.bake_success and calls + self.on_post_auto_bake(). + + Args: + bake_success (bool): True if the bake was successful. + + """ + if not self._check_wrapper(wrapper): + return + + self._bake_success = bake_success + + self.on_post_auto_bake(bake_success) + + self._handle_on_complete() + + def on_failure(self): + """ Called if the process fails to instantiate or fails to start + a cook. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_complete(self): + """ Called if the process completes instantiation, cook and/or baking, + depending on enable_auto_cook and enable_auto_bake. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_pre_instantiation(self): + """ Called during pre_instantiation. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_post_instantiation(self): + """ Called during post_instantiation. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_post_auto_cook(self, cook_success): + """ Called during post_cook. + + Subclasses can override this function implement custom functionality. + + Args: + cook_success (bool): True if the cook was successful. + + """ + pass + + def on_pre_process(self): + """ Called during pre_process. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_post_processing(self): + """ Called during post_processing. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_post_auto_bake(self, bake_success): + """ Called during post_bake. + + Subclasses can override this function implement custom functionality. + + Args: + bake_success (bool): True if the bake was successful. + + """ + pass diff --git a/Content/Python/__init__.py b/Content/Python/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/HoudiniEngine.uplugin b/HoudiniEngine.uplugin index e5e325ad4..e87839c24 100644 --- a/HoudiniEngine.uplugin +++ b/HoudiniEngine.uplugin @@ -1,14 +1,14 @@ { "FileVersion" : 3, "FriendlyName" : "Houdini Engine v2", - "Version" : 18050408, - "VersionName" : "v2 - Beta2 - H18.5.408", + "Version" : 18050759, + "VersionName" : "v2.0 - H18.5.759", "CreatedBy" : "Side Effects Software Inc.", "CreatedByURL" : "http://www.sidefx.com", "DocsURL" : "http://www.sidefx.com/docs/unreal/", "SupportURL" : "https://www.sidefx.com/bugs/submit/", "Description" : "Houdini Engine for Unreal Engine (v2).", - "IsBetaVersion" : true, + "IsBetaVersion" : false, "Category" : "Rendering", "EnabledByDefault" : true, diff --git a/LICENSE.md b/LICENSE.md index 1fffeda34..e6ad1fabc 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,93 +1,26 @@ - ALPHA AND BETA SOFTWARE - CONFIDENTIAL DISCLOSURE AGREEMENT -Revised 10/2011 -This Agreement is made today between Side Effects Software Inc., a corporation -incorporated under the laws of Ontario, Canada and having a place of business -at 123 Front Street West, Suite 1401, Toronto ("Side Effects Software") and -you ("Beta Tester"). - -BACKGROUND: - -1. Side Effects Software is in the business of developing and marketing certain - computer graphics software and related materials. -2. Beta Tester, in order to permit Side Effects Software in refining and - perfecting such software and materials, has expressed an interest in testing - certain alpha/beta versions of software more fully described in Schedule A - (the "Software & Materials"). -3. Each Animator, as an employee, contractor or agent of the Beta Tester, may - have access to the Software & Materials and perhaps to other confidential - information of Side Effects Software such as trade secrets, business or - product plans, which might be disclosed during the course of the software - testing (the "Confidential Information"). -4. Side Effects Software wishes to ensure that the Software & Materials are not - used by Beta Tester for purposes other than alpha/beta testing and that they - are not disclosed to any other party without the prior written consent of - Side Effects Software; - -NOW THEREFORE, in consideration of this background and the provision of -such materials to Beta Tester and other good and valuable consideration (the -receipt and sufficiency of which are hereby acknowledged), Beta Tester agrees -with Side Effects Software as follows: - -1. Side Effects Software hereby grants to Beta Tester on the terms set out - herein a personal, non-transferable and non-exclusive license to use the - object code version of the Software & Materials for its internal operations - on its computers. Any commercial exploitation of the Software is at the - Beta Tester's risk. Beta Tester's right to use the Software & Materials is - limited to those rights expressly set out in this Agreement. Beta Tester - shall carry out testing of the Software & Materials in accordance with such - reasonable instructions as Side Effects Software may provide to it from time - to time. -2. Beta Tester shall use all reasonable efforts (which shall consist of at - least the same level of diligence as it uses to protect its own proprietary - information and trade secrets) to protect the confidentiality of all - Software & Materials, including all product features, and other Confidential - Information of Side Effects Software that may come to the attention of or - knowledge of Beta Tester as a result of undertaking such testing. Beta - Tester shall not discuss product features or show the Software & Materials - to anyone. Beta Tester shall not copy, publish, disclose, attempt to - recreate the source code version of the Software or make any use other than - as contemplated herein of any of the Software & Material or any such - Confidential Information. For the purposes hereof, Confidential Information - shall not include any information that: - - At the time of such disclosure, is generally available to the public - through no fault of Beta Tester; - - Was in possession of Beta Tester without any obligation of confidentiality - prior to the date hereof and was not acquired directly or indirectly from - Side Effects Software; or - - Was received by Beta Tester after the date hereof from a third party who - imposed no obligation of confidentiality and who did not acquire any such - information directly or indirectly from Side Effects Software. -3. Beta Tester shall not communicate or otherwise disclose to Side Effects - Software during the term of this Agreement any confidential or proprietary - information of any other third party. -4. In accepting this Agreement the Beta Tester agrees to test and evaluate the - Software & Materials and to report all problems, concerns, deficiencies and - suggestions for improvements to Side Effects Software. A representative from - Side Effects Software may be contacting Beta Tester weekly for a report. -5. Upon completion of such testing or at any time on the request of Side - Effects Software, Beta Tester shall promptly return to Side Effects Software - all copies of the Software & Materials, as well as any Confidential - Information, then in its possession or control and shall, if requested, - provide Side Effects Software with a certificate signed by an authorized - representative of Beta Tester to such effect from an officer of Beta Tester. -6. All Software & Materials, as well as any Confidential Information, is - provided "as is". Side Effects Software makes no representation, warranty or - guarantee with respect to any such material and assumes no liability for the - use and performance of any alpha and beta software. Side Effects Software - reserves the right to alter all aspects of the Software and Documentation - from one alpha or beta version to the next, including the user interface, - screen displays, fonts and functionality. -7. The Software will timeout and cease to function one month after its build - date, regardless of when it was downloaded or installed. - -SCHEDULE A -SOFTWARE AND MATERIALS -The following Software and related Materials are bound by the attached Alpha -and Beta Software -Test Agreement: -Software: Houdini Engine for Unreal -Version: Version 2.0 - alpha - -You must accept these terms and conditions to install the Software and -Materials. + + Copyright (c) 2021 + Side Effects Software Inc. All rights reserved. + + Redistribution and use of in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. The names Side Effects Software and SideFX may not be used to endorse or + promote products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + \ No newline at end of file diff --git a/README.md b/README.md index ae931b4f8..4838389bc 100644 --- a/README.md +++ b/README.md @@ -1,104 +1,108 @@ -# Houdini Engine for Unreal - Version 2 - Beta - -Welcome to the repository for the Beta of Version 2 of the Houdini Engine For Unreal Plugin. - -This plug-in brings Houdini's powerful and flexible procedural workflow into Unreal Engine through Houdini Digital Assets. Artists can interactively adjust asset parameters inside the editor and use Unreal assets as inputs. Houdini's procedural engine will then "cook" the asset and the results will be available in the editor without the need for baking. - -Version 2 is a significant rewrite of the core architecture of the existing Houdini Engine plugin, and comes  with many new features, and improvements. - -Here are some of the new features and improvements currently available in Beta1: - - -Core: -- New and redesigned core architecture, more modular and lightweight. - All the Houdini Engine/HAPI logic is now Editor-only and contained in the “HoudiniEngine” module. All the custom runtime components and actors used by the plugin now simply acts as data-holders, and are processed by the HoudiniEngine modules, removing the need to bake HDA before packaging a game. -- The plugin now relies exclusively on native, UProperties based serialization, so operations like cut and paste, move between level, duplicate etc.. do not exhibit any of the issues that version 1 had with those operations. - -Outputs: -- Static Mesh creation time has been optimized and now uses Mesh Descriptions. -- Alternatively, you can also decide to use an even faster Proxy Mesh generation while editing the HDA. -  Those can then be automatically refined to Static Meshes, either on a timer, or when saving/playing the level. -- World composition support: Tiled heightfields can now be baked to multiple landscape actors/steaming proxies, and will create/update the levels needed for world composition. -  You can specify the level's path by using the new “unreal_level_path” attribute, that is also used by meshes and instancers so they can also be baked out to separate levels. -- Material overrides and generic uproperty attributes can either be applied on the instancer, or per instance (when using mesh split instancers or instanced Actors). -- It is possible to create foliage instances directly, without baking, when using the “unreal_foliage” attribute on an instancer. -- A class can be directly instantiated by the "unreal_instance" attribute (ie “PointLight”, “AudioVolume”… ). -- Curves can be outputed to SplineComponents by using the "unreal_output_curve" primitive attribute. - -Inputs: - -- Colliders on a Static Mesh can now be imported as group geometry. -- World inputs can now read data from BSP brushes. -- Instancers and Foliage are now imported as packed primitives. -- World inputs have an improved bound selector mode, that lets them send all the actors and objects contained in the bounds of the selected object. -- World inputs can now import data from all supported input objects (landscape, houdini asset actors..) -- World inputs can now import data from actors placed in a different level than the Houdini Asset Actors's. -- A single curve input can now create and import any number of curves. -- You can alt-click on curve inputs or editable curves to create new points. -  -Parameters: -- HDA parameters and inputs editing now support multi-selection. -- Parameter UI/UX has been improved: -- Folder UI (tabs, radio, collapsible) has been improved -- Ramps UI has been improved, and it is easy to turn off auto-update while editing them. -- When an asset is dropped on a string parameter, it automatically sets its value to the asset ref. -- String parameters can now be turned into an asset picker via the “unreal_ref” tag. -- Support for File parameters has been improved (custom extension, directory, new file...) -- Multi-line strings, Column Labels, Button Strip, Log Int and Floats are now supported. - -General: -- The plugin's UI has been completely revamped, a new Houdini Engine menu has been added to the editor. -- bgeo/bgeo.sc files can be imported natively in the content browser (Mesh and instancers). -- The PDG Asset Link has been added, allowing control of TOP networks nested in HDAs, and works similarly to the one in the Unity plugin. -- Session Sync is supported, allowing the plugin to connect to a session of Houdini Engine running inside Houdini. -  The state of Houdini Engine can be viewed in Houdini while working with the plugin in Unreal, and changes on either end, whether in Unreal via the plugin or in Houdini via its various interfaces, will be synchronized across so that both applications will be able to make changes and see the same results. -- Blueprint support: It is now possible to use Houdini Asset Components in the Blueprint Editor. - This lets you preset and use HDAs on Blueprint Actors, and changing parameters/inputs on the Houdini Asset will automatically update all placed instances of that Blueprint. - - -For more details on the new features and improvements available in the Beta, please visit the [Wiki](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/wiki/What's-new-%3F). - -Some of the new features in the beta are still in their early stage, and will be improved upon in subsequent release of the plugin. - -# Feedback - -Please use this repository's "Issues" to report any bugs, problems, or simply to give us feedback on your experience with version2. - -# Compatibility - -Currently, the [Beta1](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/releases) release of V2 has binaries that have been built for UE4.25 and UE4.24, and is linked with the latest production build of Houdini, H18.0.597. - -Source code for the plugin is available on this repository for UE4.25, UE4.24, UE4.23 and the master branch of Unreal (4.26). - -As of Beta1, Version 2 is now backward compatible with version 1 of the Houdini Engine for Unreal plugin. - -When loading a level that contains Houdini objects made with version 1, the plugin now tries to convert the V1 components, parameters, inputs and outputs to their v2 equivalents. - -Some HDAs might need to be rebuilt after the conversion for their parameters and inputs to be displayed properly by the v2 plugin. - -The conversion of the legacy v1 data is still in progress and will be improved upon in the future. -However, the Houdini Digital Assets themselves (the HDA files), that were created for version 1 of the plugin are fully compatible with version 2, as it supports most of version 1 workflows. - -# Installing the plugin - -01. Download the pre-built binaries of the plugin in the "Releases" section of this repository. - -01. Extract the "HoudiniEngine" folder in the release to the "Plugins/Runtime" folder of Unreal. - You can install the plugin either directly in the engine folder (in "Engine/Plugins/Runtime/HoudiniEngine") or in your project folder (in "Plugins/Runtime/HoudiniEngine"). -01. Start Unreal Engine, open the Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. -01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". -01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. -01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters and inputs in the `Details` panel. Modifying any of the parameters will force the asset to recook and possibly update its geometry. - - -# Building from source - -01. Get the UE4 source code from: https://github.com/EpicGames/UnrealEngine/releases -01. Within the UE4 source, navigate to `Engine/Plugins/Runtime`, and clone this repo into a folder named `HoudiniEngine`. -01. Download and install the correct build of 64-bit Houdini. To get the build number, look at the header of `Source/HoudiniEngine/HoudiniEngine.Build.cs`, under `Houdini Version`. -01. Generate the UE4 Project Files (by running `GenerateProjectFiles`) and build Unreal, either in x64 `Debug Editor` or x64 `Development Editor`. -01. When starting the Unreal Engine editor, go to Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. -01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". -01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. -01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters in the `Details` pane. Modifying any of the parameters will force the asset to recook and possibly update its geometry. +# Houdini Engine for Unreal - Version 2.0 + +Welcome to the repository for Version 2 of the Houdini Engine For Unreal Plugin. + +** As only version 2 of the plugin now ships with Houdini 19.0, regular updates of the version 2 plugin now happen on the [HoudiniEngineForUnreal](https://github.com/sideeffects/HoudiniEngineForUnreal) repository. The source code available on this repository is only for Houdini 18.5, and will not be receiving any new updates. ** + +This plug-in brings Houdini's powerful and flexible procedural workflow into Unreal Engine through Houdini Digital Assets. Artists can interactively adjust asset parameters inside the editor and use Unreal assets as inputs. Houdini's procedural engine will then "cook" the asset and the results will be available in the editor without the need for baking. + +Version 2 is a significant rewrite of the core architecture of the existing Houdini Engine plugin, and comes  with many new features, and improvements. + +Here are some of the new features and improvements currently available: + + +Core: +- New and redesigned core architecture, more modular and lightweight. + All the Houdini Engine/HAPI logic is now Editor-only and contained in the “HoudiniEngine” module. All the custom runtime components and actors used by the plugin now simply acts as data-holders, and are processed by the HoudiniEngine modules, removing the need to bake HDA before packaging a game. +- The plugin now relies exclusively on native, UProperties based serialization, so operations like cut and paste, move between level, duplicate etc.. do not exhibit any of the issues that version 1 had with those operations. + +Outputs: +- Static Mesh creation time has been optimized and now uses Mesh Descriptions. +- Alternatively, you can also decide to use an even faster Proxy Mesh generation while editing the HDA. +  Those can then be automatically refined to Static Meshes, either on a timer, or when saving/playing the level. +- World composition support: Tiled heightfields can now be baked to multiple landscape actors/steaming proxies, and will create/update the levels needed for world composition. +  You can specify the level's path by using the new “unreal_level_path” attribute, that is also used by meshes and instancers so they can also be baked out to separate levels. +- Material overrides and generic uproperty attributes can either be applied on the instancer, or per instance (when using mesh split instancers or instanced Actors). +- It is possible to create foliage instances directly, without baking, when using the “unreal_foliage” attribute on an instancer. +- A class can be directly instantiated by the "unreal_instance" attribute (ie “PointLight”, “AudioVolume”… ). +- Curves can be outputed to SplineComponents by using the "unreal_output_curve" primitive attribute. + +Inputs: + +- Colliders on a Static Mesh can now be imported as group geometry. +- World inputs can now read data from BSP brushes. +- Instancers and Foliage are now imported as packed primitives. +- World inputs have an improved bound selector mode, that lets them send all the actors and objects contained in the bounds of the selected object. +- World inputs can now import data from all supported input objects (landscape, houdini asset actors..) +- World inputs can now import data from actors placed in a different level than the Houdini Asset Actors's. +- A single curve input can now create and import any number of curves. +- You can alt-click on curve inputs or editable curves to create new points. +  +Parameters: +- HDA parameters and inputs editing now support multi-selection. +- Parameter UI/UX has been improved: +- Folder UI (tabs, radio, collapsible) has been improved +- Ramps UI has been improved, and it is easy to turn off auto-update while editing them. +- When an asset is dropped on a string parameter, it automatically sets its value to the asset ref. +- String parameters can now be turned into an asset picker via the “unreal_ref” tag. +- Support for File parameters has been improved (custom extension, directory, new file...) +- Multi-line strings, Column Labels, Button Strip, Log Int and Floats are now supported. + +General: +- The plugin's UI has been completely revamped, a new Houdini Engine menu has been added to the editor. +- bgeo/bgeo.sc files can be imported natively in the content browser (Mesh and instancers). +- The PDG Asset Link has been added, allowing control of TOP networks nested in HDAs, and works similarly to the one in the Unity plugin. +- Session Sync is supported, allowing the plugin to connect to a session of Houdini Engine running inside Houdini. +  The state of Houdini Engine can be viewed in Houdini while working with the plugin in Unreal, and changes on either end, whether in Unreal via the plugin or in Houdini via its various interfaces, will be synchronized across so that both applications will be able to make changes and see the same results. +- Blueprint support: It is now possible to use Houdini Asset Components in the Blueprint Editor. + This lets you preset and use HDAs on Blueprint Actors, and changing parameters/inputs on the Houdini Asset will automatically update all placed instances of that Blueprint. + + +For more details on the new features and improvements available, please visit the [Wiki](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/wiki/What's-new-%3F). +Documentation for version 2.0 of the plugin is also available on the Side FX [Website](https://www.sidefx.com/docs/unreal/). + + +# Feedback + +Please send bug reports, feature requests and questions to [Side FX's support](https://www.sidefx.com/bugs/submit/). + + +# Compatibility + +Currently, [Version 2.0](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/releases) has binaries that have been built for UE4.26 and UE4.25, and is linked with the latest production build of Houdini, H18.5.462. + +Source code for the plugin is available on this repository for UE4.26, UE4.25, UE4.24, UE4.23 and the master branch of Unreal (4.27). + +Version 2 is also partially backward compatible with version 1 of the Houdini Engine for Unreal plugin. + +When loading a level that contains Houdini objects made with version 1, the plugin will attempt to convert the V1 components, parameters, inputs and outputs to their v2 equivalents. + +Some HDAs might need to be rebuilt after the conversion for their parameters and inputs to be displayed properly by the v2 plugin. + +The conversion of the legacy v1 data is still in progress and will be improved upon in the future. +However, the Houdini Digital Assets themselves (the HDA files), that were created for version 1 of the plugin are fully compatible with version 2, as it supports most of version 1 workflows. + +# Installing the plugin + +01. Download the pre-built binaries of the plugin in the "Releases" section of this repository. + +01. Extract the "HoudiniEngine" folder in the release to the "Plugins/Runtime" folder of Unreal. + You can install the plugin either directly in the engine folder (in "Engine/Plugins/Runtime/HoudiniEngine") or in your project folder (in "Plugins/Runtime/HoudiniEngine"). +01. Start Unreal Engine, open the Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. +01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". +01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. +01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters and inputs in the `Details` panel. Modifying any of the parameters will force the asset to recook and possibly update its geometry. + + +# Building from source + +01. Get the UE4 source code from: https://github.com/EpicGames/UnrealEngine/releases +01. Within the UE4 source, navigate to `Engine/Plugins/Runtime`, and clone this repo into a folder named `HoudiniEngine`. +01. Download and install the correct build of 64-bit Houdini. To get the build number, look at the header of `Source/HoudiniEngine/HoudiniEngine.Build.cs`, under `Houdini Version`. +01. Generate the UE4 Project Files (by running `GenerateProjectFiles`) and build Unreal, either in x64 `Debug Editor` or x64 `Development Editor`. +01. When starting the Unreal Engine editor, go to Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. +01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". +01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. +01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters in the `Details` pane. Modifying any of the parameters will force the asset to recook and possibly update its geometry. + diff --git a/Source/HoudiniEngine/HoudiniEngine.Build.cs b/Source/HoudiniEngine/HoudiniEngine.Build.cs index 4a6e8049f..af5a2dda9 100644 --- a/Source/HoudiniEngine/HoudiniEngine.Build.cs +++ b/Source/HoudiniEngine/HoudiniEngine.Build.cs @@ -1,5 +1,5 @@ /* - * Copyright (c) <2020> Side Effects Software Inc. + * Copyright (c) <2021> Side Effects Software Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -32,9 +32,9 @@ /* - Houdini Version: 18.5.408 - Houdini Engine Version: 3.5.1 - Unreal Version: 4.25.0 + Houdini Version: 18.5.759 + Houdini Engine Version: 3.7.3 + Unreal Version: 4.27.0 */ @@ -47,9 +47,9 @@ public class HoudiniEngine : ModuleRules { private string GetHFSPath() { - string HoudiniVersion = "18.5.408"; + string HoudiniVersion = "18.5.759"; bool bIsRelease = true; - string HFSPath = ""; + string HFSPath = "C:/cygwin/home/prisms/builder-new/Nightly18.5CMakePython3/dev/hfs"; string RegistryPath = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Side Effects Software"; string Registry32Path = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Side Effects Software"; string log; @@ -75,8 +75,8 @@ private string GetHFSPath() if ( Directory.Exists( HPath ) ) return HPath; } - - HEngineRegistry = Registry32Path + string.Format(@"\Houdini Engine {0}", HoudiniVersion); + + HEngineRegistry = Registry32Path + string.Format(@"\Houdini Engine {0}", HoudiniVersion); HPath = Microsoft.Win32.Registry.GetValue(HEngineRegistry, "InstallPath", null) as string; if ( HPath != null ) { @@ -106,8 +106,8 @@ private string GetHFSPath() if ( Directory.Exists( HPath ) ) return HPath; } - - // Look for the Houdini registry install path for the version the plug-in was compiled for + + // Look for the Houdini registry install path for the version the plug-in was compiled for HoudiniRegistry = Registry32Path + string.Format(@"\Houdini {0}", HoudiniVersion); HPath = Microsoft.Win32.Registry.GetValue(HoudiniRegistry, "InstallPath", null) as string; if ( HPath != null ) @@ -137,10 +137,10 @@ private string GetHFSPath() // We couldn't find the exact version the plug-in was built for, we can still try with the active version in the registry string ActiveHEngine = Microsoft.Win32.Registry.GetValue(RegistryPath, "ActiveEngineVersion", null) as string; - if ( ActiveHEngine == null ) - { - ActiveHEngine = Microsoft.Win32.Registry.GetValue(Registry32Path, "ActiveEngineVersion", null) as string; - } + if ( ActiveHEngine == null ) + { + ActiveHEngine = Microsoft.Win32.Registry.GetValue(Registry32Path, "ActiveEngineVersion", null) as string; + } if ( ActiveHEngine != null ) { // See if the latest active HEngine version has the proper major/minor version @@ -166,10 +166,10 @@ private string GetHFSPath() // Active HEngine version didn't match, so try with the active Houdini version string ActiveHoudini = Microsoft.Win32.Registry.GetValue(RegistryPath, "ActiveVersion", null) as string; - if ( ActiveHoudini == null ) + if ( ActiveHoudini == null ) { - ActiveHoudini = Microsoft.Win32.Registry.GetValue(Registry32Path, "ActiveVersion", null) as string; - } + ActiveHoudini = Microsoft.Win32.Registry.GetValue(Registry32Path, "ActiveVersion", null) as string; + } if ( ActiveHoudini != null ) { // See if the latest active Houdini version has the proper major/minor version @@ -276,10 +276,10 @@ public HoudiniEngine( ReadOnlyTargetRules Target ) : base( Target ) PrivateIncludePaths.AddRange( new string[] - { + { "HoudiniEngineRuntime/Private" } - ); + ); // Add common dependencies. PublicDependencyModuleNames.AddRange( @@ -294,7 +294,7 @@ public HoudiniEngine( ReadOnlyTargetRules Target ) : base( Target ) "RHI", "Foliage", "Landscape", - "StaticMeshDescription", + "StaticMeshDescription", } ); @@ -309,8 +309,8 @@ public HoudiniEngine( ReadOnlyTargetRules Target ) : base( Target ) if (Target.bBuildEditor == true) { PrivateDependencyModuleNames.AddRange( - new string[] - { + new string[] + { "AppFramework", "AssetTools", "EditorStyle", diff --git a/Source/HoudiniEngine/Private/HBSPOps.cpp b/Source/HoudiniEngine/Private/HBSPOps.cpp index d5d727360..b081d0c80 100644 --- a/Source/HoudiniEngine/Private/HBSPOps.cpp +++ b/Source/HoudiniEngine/Private/HBSPOps.cpp @@ -1,5 +1,28 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ #include "HBSPOps.h" #include "EngineDefines.h" @@ -176,7 +199,7 @@ static void FilterBound FMemMark Mark(FMemStack::Get()); FBspNode& Node = Model->Nodes [iNode]; FBspSurf& Surf = Model->Surfs [Node.iSurf]; - FVector Base = Surf.Plane * Surf.Plane.W; + FVector Base = Surf.Plane * Surf.Plane.W; FVector& Normal = Model->Vectors[Surf.vNormal]; FBox Bound(ForceInit); @@ -642,11 +665,11 @@ ABrush* FHBSPOps::csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType B } /** Add a new point to the model (preventing duplicates) and return its index. */ -static int32 AddThing( TArray& Vectors, FVector& V, float Thresh, int32 Check ) +static int32 AddThing( TArray& Vectors, const FVector& V, float Thresh, int32 Check ) { if( Check ) { - // See if this is very close to an existing point/vector. + // See if this is very close to an existing point/vector. for( int32 i=0; iSurfs[NewIndex]; // This node has a new polygon being added by bspBrushCSG; must set its properties here. - Surf->pBase = bspAddPoint (Model,&EdPoly->Base,1,BspPoints); - Surf->vNormal = bspAddVector (Model,&EdPoly->Normal,1,BspVectors); - Surf->vTextureU = bspAddVector (Model,&EdPoly->TextureU,0,BspVectors); - Surf->vTextureV = bspAddVector (Model,&EdPoly->TextureV,0,BspVectors); + FVector Base = EdPoly->Base, Normal = EdPoly->Normal, TextureU = EdPoly->TextureU, TextureV = EdPoly->TextureV; + Surf->pBase = bspAddPoint (Model,&Base,1,BspPoints); + Surf->vNormal = bspAddVector (Model,&Normal,1,BspVectors); + Surf->vTextureU = bspAddVector (Model,&TextureU,0,BspVectors); + Surf->vTextureV = bspAddVector (Model,&TextureV,0,BspVectors); Surf->Material = EdPoly->Material; Surf->Actor = NULL; @@ -1246,7 +1270,8 @@ int32 FHBSPOps::bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePl FVert* VertPool = &Model->Verts[ Node.iVertPool ]; for( uint8 i=0; iVertices.Num(); i++ ) { - int32 pVertex = bspAddPoint(Model,&EdPoly->Vertices[i],0, BspPoints); + FVector Vertex = EdPoly->Vertices[i]; + int32 pVertex = bspAddPoint(Model,&Vertex,0, BspPoints); if( Node.NumVertices==0 || VertPool[Node.NumVertices-1].pVertex!=pVertex ) { VertPool[Node.NumVertices].iSide = INDEX_NONE; @@ -1459,4 +1484,3 @@ int32 UHBspPointsGrid::FindOrAddPoint(const FVector& Point, int32 Index, float P return Index; } - diff --git a/Source/HoudiniEngine/Private/HBSPOps.h b/Source/HoudiniEngine/Private/HBSPOps.h index 5df41616e..8e359b070 100644 --- a/Source/HoudiniEngine/Private/HBSPOps.h +++ b/Source/HoudiniEngine/Private/HBSPOps.h @@ -1,4 +1,28 @@ -// Copyright Epic Games, Inc. All Rights Reserved. +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ #pragma once diff --git a/Source/HoudiniEngine/Private/HCsgUtils.cpp b/Source/HoudiniEngine/Private/HCsgUtils.cpp index d3ac93f33..55503b4d3 100644 --- a/Source/HoudiniEngine/Private/HCsgUtils.cpp +++ b/Source/HoudiniEngine/Private/HCsgUtils.cpp @@ -1,3 +1,28 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ #include "HCsgUtils.h" @@ -1434,3 +1459,4 @@ void UHCsgUtils::bspCleanup( UModel *Model ) if( Model->Nodes.Num() > 0 ) CleanupNodes( Model, 0, INDEX_NONE ); } + diff --git a/Source/HoudiniEngine/Private/HCsgUtils.h b/Source/HoudiniEngine/Private/HCsgUtils.h index cd6e1ce51..cc48c0b48 100644 --- a/Source/HoudiniEngine/Private/HCsgUtils.h +++ b/Source/HoudiniEngine/Private/HCsgUtils.h @@ -1,3 +1,28 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ #pragma once diff --git a/Source/HoudiniEngine/Private/HoudiniApi.cpp b/Source/HoudiniEngine/Private/HoudiniApi.cpp index c7050344c..b024e09f6 100644 --- a/Source/HoudiniEngine/Private/HoudiniApi.cpp +++ b/Source/HoudiniEngine/Private/HoudiniApi.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) <2020> Side Effects Software Inc. * + * Copyright (c) <2021> Side Effects Software Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -78,6 +78,12 @@ FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStu FHoudiniApi::ComposeObjectListFuncPtr FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; +FHoudiniApi::CompositorOptions_CreateFuncPtr +FHoudiniApi::CompositorOptions_Create = &FHoudiniApi::CompositorOptions_CreateEmptyStub; + +FHoudiniApi::CompositorOptions_InitFuncPtr +FHoudiniApi::CompositorOptions_Init = &FHoudiniApi::CompositorOptions_InitEmptyStub; + FHoudiniApi::ConnectNodeInputFuncPtr FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; @@ -210,12 +216,24 @@ FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStu FHoudiniApi::GetAttributeInfoFuncPtr FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; +FHoudiniApi::GetAttributeInt16ArrayDataFuncPtr +FHoudiniApi::GetAttributeInt16ArrayData = &FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeInt16DataFuncPtr +FHoudiniApi::GetAttributeInt16Data = &FHoudiniApi::GetAttributeInt16DataEmptyStub; + FHoudiniApi::GetAttributeInt64ArrayDataFuncPtr FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; FHoudiniApi::GetAttributeInt64DataFuncPtr FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; +FHoudiniApi::GetAttributeInt8ArrayDataFuncPtr +FHoudiniApi::GetAttributeInt8ArrayData = &FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeInt8DataFuncPtr +FHoudiniApi::GetAttributeInt8Data = &FHoudiniApi::GetAttributeInt8DataEmptyStub; + FHoudiniApi::GetAttributeIntArrayDataFuncPtr FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; @@ -231,6 +249,12 @@ FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArray FHoudiniApi::GetAttributeStringDataFuncPtr FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; +FHoudiniApi::GetAttributeUInt8ArrayDataFuncPtr +FHoudiniApi::GetAttributeUInt8ArrayData = &FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeUInt8DataFuncPtr +FHoudiniApi::GetAttributeUInt8Data = &FHoudiniApi::GetAttributeUInt8DataEmptyStub; + FHoudiniApi::GetAvailableAssetCountFuncPtr FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; @@ -255,6 +279,9 @@ FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStu FHoudiniApi::GetComposedObjectTransformsFuncPtr FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; +FHoudiniApi::GetCompositorOptionsFuncPtr +FHoudiniApi::GetCompositorOptions = &FHoudiniApi::GetCompositorOptionsEmptyStub; + FHoudiniApi::GetConnectionErrorFuncPtr FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; @@ -282,6 +309,9 @@ FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; FHoudiniApi::GetDisplayGeoInfoFuncPtr FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; +FHoudiniApi::GetEdgeCountOfEdgeGroupFuncPtr +FHoudiniApi::GetEdgeCountOfEdgeGroup = &FHoudiniApi::GetEdgeCountOfEdgeGroupEmptyStub; + FHoudiniApi::GetEnvIntFuncPtr FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; @@ -387,6 +417,12 @@ FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; FHoudiniApi::GetObjectTransformFuncPtr FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; +FHoudiniApi::GetOutputGeoCountFuncPtr +FHoudiniApi::GetOutputGeoCount = &FHoudiniApi::GetOutputGeoCountEmptyStub; + +FHoudiniApi::GetOutputGeoInfosFuncPtr +FHoudiniApi::GetOutputGeoInfos = &FHoudiniApi::GetOutputGeoInfosEmptyStub; + FHoudiniApi::GetOutputNodeIdFuncPtr FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; @@ -786,18 +822,30 @@ FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmpt FHoudiniApi::SetAttributeFloatDataFuncPtr FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; +FHoudiniApi::SetAttributeInt16DataFuncPtr +FHoudiniApi::SetAttributeInt16Data = &FHoudiniApi::SetAttributeInt16DataEmptyStub; + FHoudiniApi::SetAttributeInt64DataFuncPtr FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; +FHoudiniApi::SetAttributeInt8DataFuncPtr +FHoudiniApi::SetAttributeInt8Data = &FHoudiniApi::SetAttributeInt8DataEmptyStub; + FHoudiniApi::SetAttributeIntDataFuncPtr FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; FHoudiniApi::SetAttributeStringDataFuncPtr FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; +FHoudiniApi::SetAttributeUInt8DataFuncPtr +FHoudiniApi::SetAttributeUInt8Data = &FHoudiniApi::SetAttributeUInt8DataEmptyStub; + FHoudiniApi::SetCachePropertyFuncPtr FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; +FHoudiniApi::SetCompositorOptionsFuncPtr +FHoudiniApi::SetCompositorOptions = &FHoudiniApi::SetCompositorOptionsEmptyStub; + FHoudiniApi::SetCurveCountsFuncPtr FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; @@ -980,6 +1028,8 @@ FHoudiniApi::InitializeHAPI(void* LibraryHandle) FHoudiniApi::ComposeChildNodeList = (ComposeChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeChildNodeList")); FHoudiniApi::ComposeNodeCookResult = (ComposeNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeNodeCookResult")); FHoudiniApi::ComposeObjectList = (ComposeObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeObjectList")); + FHoudiniApi::CompositorOptions_Create = (CompositorOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CompositorOptions_Create")); + FHoudiniApi::CompositorOptions_Init = (CompositorOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CompositorOptions_Init")); FHoudiniApi::ConnectNodeInput = (ConnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConnectNodeInput")); FHoudiniApi::ConvertMatrixToEuler = (ConvertMatrixToEulerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToEuler")); FHoudiniApi::ConvertMatrixToQuat = (ConvertMatrixToQuatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToQuat")); @@ -1024,13 +1074,19 @@ FHoudiniApi::InitializeHAPI(void* LibraryHandle) FHoudiniApi::GetAttributeFloatArrayData = (GetAttributeFloatArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatArrayData")); FHoudiniApi::GetAttributeFloatData = (GetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatData")); FHoudiniApi::GetAttributeInfo = (GetAttributeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInfo")); + FHoudiniApi::GetAttributeInt16ArrayData = (GetAttributeInt16ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt16ArrayData")); + FHoudiniApi::GetAttributeInt16Data = (GetAttributeInt16DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt16Data")); FHoudiniApi::GetAttributeInt64ArrayData = (GetAttributeInt64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64ArrayData")); FHoudiniApi::GetAttributeInt64Data = (GetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64Data")); + FHoudiniApi::GetAttributeInt8ArrayData = (GetAttributeInt8ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt8ArrayData")); + FHoudiniApi::GetAttributeInt8Data = (GetAttributeInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt8Data")); FHoudiniApi::GetAttributeIntArrayData = (GetAttributeIntArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntArrayData")); FHoudiniApi::GetAttributeIntData = (GetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntData")); FHoudiniApi::GetAttributeNames = (GetAttributeNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeNames")); FHoudiniApi::GetAttributeStringArrayData = (GetAttributeStringArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringArrayData")); FHoudiniApi::GetAttributeStringData = (GetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringData")); + FHoudiniApi::GetAttributeUInt8ArrayData = (GetAttributeUInt8ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeUInt8ArrayData")); + FHoudiniApi::GetAttributeUInt8Data = (GetAttributeUInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeUInt8Data")); FHoudiniApi::GetAvailableAssetCount = (GetAvailableAssetCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssetCount")); FHoudiniApi::GetAvailableAssets = (GetAvailableAssetsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssets")); FHoudiniApi::GetBoxInfo = (GetBoxInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetBoxInfo")); @@ -1039,6 +1095,7 @@ FHoudiniApi::InitializeHAPI(void* LibraryHandle) FHoudiniApi::GetComposedNodeCookResult = (GetComposedNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedNodeCookResult")); FHoudiniApi::GetComposedObjectList = (GetComposedObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectList")); FHoudiniApi::GetComposedObjectTransforms = (GetComposedObjectTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectTransforms")); + FHoudiniApi::GetCompositorOptions = (GetCompositorOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCompositorOptions")); FHoudiniApi::GetConnectionError = (GetConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionError")); FHoudiniApi::GetConnectionErrorLength = (GetConnectionErrorLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionErrorLength")); FHoudiniApi::GetCookingCurrentCount = (GetCookingCurrentCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingCurrentCount")); @@ -1048,6 +1105,7 @@ FHoudiniApi::InitializeHAPI(void* LibraryHandle) FHoudiniApi::GetCurveKnots = (GetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveKnots")); FHoudiniApi::GetCurveOrders = (GetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveOrders")); FHoudiniApi::GetDisplayGeoInfo = (GetDisplayGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetDisplayGeoInfo")); + FHoudiniApi::GetEdgeCountOfEdgeGroup = (GetEdgeCountOfEdgeGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetEdgeCountOfEdgeGroup")); FHoudiniApi::GetEnvInt = (GetEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetEnvInt")); FHoudiniApi::GetFaceCounts = (GetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFaceCounts")); FHoudiniApi::GetFirstVolumeTile = (GetFirstVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFirstVolumeTile")); @@ -1083,6 +1141,8 @@ FHoudiniApi::InitializeHAPI(void* LibraryHandle) FHoudiniApi::GetNumWorkitems = (GetNumWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNumWorkitems")); FHoudiniApi::GetObjectInfo = (GetObjectInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectInfo")); FHoudiniApi::GetObjectTransform = (GetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectTransform")); + FHoudiniApi::GetOutputGeoCount = (GetOutputGeoCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputGeoCount")); + FHoudiniApi::GetOutputGeoInfos = (GetOutputGeoInfosFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputGeoInfos")); FHoudiniApi::GetOutputNodeId = (GetOutputNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputNodeId")); FHoudiniApi::GetPDGEvents = (GetPDGEventsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGEvents")); FHoudiniApi::GetPDGGraphContextId = (GetPDGGraphContextIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContextId")); @@ -1216,10 +1276,14 @@ FHoudiniApi::InitializeHAPI(void* LibraryHandle) FHoudiniApi::SetAnimCurve = (SetAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAnimCurve")); FHoudiniApi::SetAttributeFloat64Data = (SetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloat64Data")); FHoudiniApi::SetAttributeFloatData = (SetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloatData")); + FHoudiniApi::SetAttributeInt16Data = (SetAttributeInt16DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt16Data")); FHoudiniApi::SetAttributeInt64Data = (SetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt64Data")); + FHoudiniApi::SetAttributeInt8Data = (SetAttributeInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt8Data")); FHoudiniApi::SetAttributeIntData = (SetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeIntData")); FHoudiniApi::SetAttributeStringData = (SetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeStringData")); + FHoudiniApi::SetAttributeUInt8Data = (SetAttributeUInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeUInt8Data")); FHoudiniApi::SetCacheProperty = (SetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCacheProperty")); + FHoudiniApi::SetCompositorOptions = (SetCompositorOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCompositorOptions")); FHoudiniApi::SetCurveCounts = (SetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveCounts")); FHoudiniApi::SetCurveInfo = (SetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveInfo")); FHoudiniApi::SetCurveKnots = (SetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveKnots")); @@ -1296,6 +1360,8 @@ FHoudiniApi::FinalizeHAPI() FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; + FHoudiniApi::CompositorOptions_Create = &FHoudiniApi::CompositorOptions_CreateEmptyStub; + FHoudiniApi::CompositorOptions_Init = &FHoudiniApi::CompositorOptions_InitEmptyStub; FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; @@ -1340,13 +1406,19 @@ FHoudiniApi::FinalizeHAPI() FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; + FHoudiniApi::GetAttributeInt16ArrayData = &FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub; + FHoudiniApi::GetAttributeInt16Data = &FHoudiniApi::GetAttributeInt16DataEmptyStub; FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; + FHoudiniApi::GetAttributeInt8ArrayData = &FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub; + FHoudiniApi::GetAttributeInt8Data = &FHoudiniApi::GetAttributeInt8DataEmptyStub; FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; + FHoudiniApi::GetAttributeUInt8ArrayData = &FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub; + FHoudiniApi::GetAttributeUInt8Data = &FHoudiniApi::GetAttributeUInt8DataEmptyStub; FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; @@ -1355,6 +1427,7 @@ FHoudiniApi::FinalizeHAPI() FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; + FHoudiniApi::GetCompositorOptions = &FHoudiniApi::GetCompositorOptionsEmptyStub; FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; @@ -1364,6 +1437,7 @@ FHoudiniApi::FinalizeHAPI() FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; + FHoudiniApi::GetEdgeCountOfEdgeGroup = &FHoudiniApi::GetEdgeCountOfEdgeGroupEmptyStub; FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; @@ -1399,6 +1473,8 @@ FHoudiniApi::FinalizeHAPI() FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; + FHoudiniApi::GetOutputGeoCount = &FHoudiniApi::GetOutputGeoCountEmptyStub; + FHoudiniApi::GetOutputGeoInfos = &FHoudiniApi::GetOutputGeoInfosEmptyStub; FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; @@ -1532,10 +1608,14 @@ FHoudiniApi::FinalizeHAPI() FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; + FHoudiniApi::SetAttributeInt16Data = &FHoudiniApi::SetAttributeInt16DataEmptyStub; FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; + FHoudiniApi::SetAttributeInt8Data = &FHoudiniApi::SetAttributeInt8DataEmptyStub; FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; + FHoudiniApi::SetAttributeUInt8Data = &FHoudiniApi::SetAttributeUInt8DataEmptyStub; FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; + FHoudiniApi::SetCompositorOptions = &FHoudiniApi::SetCompositorOptionsEmptyStub; FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; @@ -1718,6 +1798,20 @@ FHoudiniApi::ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeI } +HAPI_CompositorOptions +FHoudiniApi::CompositorOptions_CreateEmptyStub() +{ + return HAPI_CompositorOptions(); +} + + +void +FHoudiniApi::CompositorOptions_InitEmptyStub(HAPI_CompositorOptions * in) +{ + return; +} + + HAPI_Result FHoudiniApi::ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index) { @@ -2026,6 +2120,20 @@ FHoudiniApi::GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId } +HAPI_Result +FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) { @@ -2040,6 +2148,20 @@ FHoudiniApi::GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_N } +HAPI_Result +FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) { @@ -2075,6 +2197,20 @@ FHoudiniApi::GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_ } +HAPI_Result +FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count) { @@ -2131,6 +2267,13 @@ FHoudiniApi::GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, } +HAPI_Result +FHoudiniApi::GetCompositorOptionsEmptyStub(const HAPI_Session * session, HAPI_CompositorOptions * compositor_options) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear) { @@ -2194,6 +2337,13 @@ FHoudiniApi::GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeI } +HAPI_Result +FHoudiniApi::GetEdgeCountOfEdgeGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * group_name, int * edge_count) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value) { @@ -2439,6 +2589,20 @@ FHoudiniApi::GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_Node } +HAPI_Result +FHoudiniApi::GetOutputGeoCountEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, int* count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetOutputGeoInfosEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, HAPI_GeoInfo* geo_infos_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id) { @@ -3245,7 +3409,7 @@ FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session HAPI_Result -FHoudiniApi::RemoveCustomStringEmptyStub(const HAPI_Session * session, const int string_handle) +FHoudiniApi::RemoveCustomStringEmptyStub(const HAPI_Session * session, const HAPI_StringHandle string_handle) { return HAPI_RESULT_FAILURE; } @@ -3370,6 +3534,13 @@ FHoudiniApi::SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_N } +HAPI_Result +FHoudiniApi::SetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length) { @@ -3377,6 +3548,13 @@ FHoudiniApi::SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_N } +HAPI_Result +FHoudiniApi::SetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length) { @@ -3391,6 +3569,13 @@ FHoudiniApi::SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_ } +HAPI_Result +FHoudiniApi::SetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value) { @@ -3398,6 +3583,13 @@ FHoudiniApi::SetCachePropertyEmptyStub(const HAPI_Session * session, const char } +HAPI_Result +FHoudiniApi::SetCompositorOptionsEmptyStub(const HAPI_Session * session, const HAPI_CompositorOptions * compositor_options) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length) { @@ -3427,7 +3619,7 @@ FHoudiniApi::SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId n HAPI_Result -FHoudiniApi::SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, int * handle_value) +FHoudiniApi::SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value) { return HAPI_RESULT_FAILURE; } diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.cpp b/Source/HoudiniEngine/Private/HoudiniEngine.cpp index 21e046a44..2cee1b690 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngine.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,8 +24,6 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#pragma once - #include "HoudiniEngine.h" #include "HoudiniEnginePrivatePCH.h" @@ -87,9 +85,11 @@ FHoudiniEngine::FHoudiniEngine() Session.type = HAPI_SESSION_MAX; Session.id = -1; + SetSessionStatus(EHoudiniSessionStatus::Invalid); #if WITH_EDITOR HapiNotificationStarted = 0.0; + TimeSinceLastPersistentNotification = 0.0; #endif } @@ -206,18 +206,23 @@ FHoudiniEngine::StartupModule() // Create Houdini Asset Manager HoudiniEngineManager = new FHoudiniEngineManager(); + // Set the session status to Not Started + SetSessionStatus(EHoudiniSessionStatus::NotStarted); + // Set the default value for pausing houdini engine cooking const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); bEnableCookingGlobal = !HoudiniRuntimeSettings->bPauseCookingOnStart; // Check if a null session is set bool bNoneSession = (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None); + if (bNoneSession) + SetSessionStatus(EHoudiniSessionStatus::None); // Initialize the singleton with this instance FHoudiniEngine::HoudiniEngineInstance = this; // See if we need to start the manager ticking if needed - // Don tick if we failed to load HAPI, if cooking is disabled or if we're using a null session + // Dont tick if we failed to load HAPI, if cooking is disabled or if we're using a null session if (FHoudiniApi::IsHAPIInitialized()) { if (bEnableCookingGlobal && !bNoneSession) @@ -323,6 +328,7 @@ FHoudiniEngine::ShutdownModule() { FHoudiniApi::Cleanup(GetSession()); FHoudiniApi::CloseSession(GetSession()); + SessionStatus = EHoudiniSessionStatus::Invalid; } FHoudiniApi::FinalizeHAPI(); @@ -376,7 +382,7 @@ FHoudiniEngine::RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo void FHoudiniEngine::AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; if (HoudiniEngineManager) @@ -390,12 +396,99 @@ FHoudiniEngine::GetLibHAPILocation() const return LibHAPILocation; } +const FString +FHoudiniEngine::GetHoudiniExecutable() +{ + FString HoudiniExecutable = TEXT("houdini"); + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + { + switch (HoudiniRuntimeSettings->HoudiniExecutable) + { + case EHoudiniExecutableType::HRSHE_HoudiniFX: + HoudiniExecutable = TEXT("houdinifx"); + break; + + case EHoudiniExecutableType::HRSHE_HoudiniCore: + HoudiniExecutable = TEXT("houdinicore"); + break; + + case EHoudiniExecutableType::HRSHE_HoudiniIndie: + HoudiniExecutable = TEXT("hindie"); + break; + + default: + case EHoudiniExecutableType::HRSHE_Houdini: + HoudiniExecutable = TEXT("houdini"); + break; + + } + } + + return HoudiniExecutable; +} + const HAPI_Session * FHoudiniEngine::GetSession() const { return Session.type == HAPI_SESSION_MAX ? nullptr : &Session; } +const EHoudiniSessionStatus& +FHoudiniEngine::GetSessionStatus() const +{ + return SessionStatus; +} + +void +FHoudiniEngine::SetSessionStatus(const EHoudiniSessionStatus& InSessionStatus) +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None) + { + // Check for none sessions first + SessionStatus = EHoudiniSessionStatus::None; + return; + } + + if (!bFirstSessionCreated) + { + // Don't change the status unless we've attempted to start the session once + SessionStatus = EHoudiniSessionStatus::NotStarted; + return; + } + + switch (InSessionStatus) + { + case EHoudiniSessionStatus::NotStarted: + case EHoudiniSessionStatus::NoLicense: + case EHoudiniSessionStatus::Lost: + case EHoudiniSessionStatus::None: + case EHoudiniSessionStatus::Invalid: + case EHoudiniSessionStatus::Connected: + { + SessionStatus = InSessionStatus; + } + break; + + case EHoudiniSessionStatus::Stopped: + { + // Only set to stop status if the session was valid + if (SessionStatus == EHoudiniSessionStatus::Connected) + SessionStatus = EHoudiniSessionStatus::Stopped; + } + break; + + case EHoudiniSessionStatus::Failed: + { + // Preserve No License / Lost status + if (SessionStatus != EHoudiniSessionStatus::NoLicense && SessionStatus != EHoudiniSessionStatus::Lost) + SessionStatus = EHoudiniSessionStatus::Failed; + } + break; + } +} + HAPI_CookOptions FHoudiniEngine::GetDefaultCookOptions() { @@ -436,6 +529,10 @@ FHoudiniEngine::StartSession(HAPI_Session*& SessionPtr, if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) return true; + // Set the HAPI_CLIENT_NAME environment variable to "unreal" + // We need to do this before starting HARS. + FPlatformMisc::SetEnvironmentVar(TEXT("HAPI_CLIENT_NAME"), TEXT("unreal")); + HAPI_Result SessionResult = HAPI_RESULT_FAILURE; HAPI_ThriftServerOptions ServerOptions; @@ -464,7 +561,12 @@ FHoudiniEngine::StartSession(HAPI_Session*& SessionPtr, FPlatformMisc::SetEnvironmentVar(TEXT("PATH"), *ModifiedPath); }; - switch ( SessionType ) + + // Clear the connection error before starting a new session + if(SessionType != EHoudiniRuntimeSettingsSessionType::HRSST_None) + FHoudiniApi::ClearConnectionError(); + + switch (SessionType) { case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: { @@ -518,8 +620,13 @@ FHoudiniEngine::StartSession(HAPI_Session*& SessionPtr, break; } - // As of Unreal 4.19, InProcess sessions are not supported anymore - case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: + case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: + // As of Unreal 4.19, InProcess sessions are not supported anymore + SessionResult = FHoudiniApi::CreateInProcessSession(SessionPtr); + // Disable session sync + bEnableSessionSync = false; + break; + default: HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine session type")); // Disable session sync @@ -527,10 +634,24 @@ FHoudiniEngine::StartSession(HAPI_Session*& SessionPtr, break; } + // Stop here if we used a none session + if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None) + return false; + + FHoudiniEngine::Get().SetFirstSessionCreated(true); + if (SessionResult != HAPI_RESULT_SUCCESS || !SessionPtr) { // Disable session sync as well? bEnableSessionSync = false; + + if (SessionType != EHoudiniRuntimeSettingsSessionType::HRSST_InProcess) + { + FString ConnectionError = FHoudiniEngineUtils::GetConnectionError(); + if(!ConnectionError.IsEmpty()) + HOUDINI_LOG_ERROR(TEXT("Houdini Engine Session failed to connect - %s"), *ConnectionError); + } + return false; } @@ -556,6 +677,9 @@ FHoudiniEngine::SessionSyncConnect( if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(&Session)) return true; + // Consider the session failed as long as we dont connect + SetSessionStatus(EHoudiniSessionStatus::Failed); + HAPI_Result SessionResult = HAPI_RESULT_FAILURE; const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); @@ -595,6 +719,7 @@ FHoudiniEngine::SessionSyncConnect( // Enable session sync bEnableSessionSync = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); // Update this session's license type HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( @@ -697,7 +822,7 @@ FHoudiniEngine::InitializeHAPISession() else { HOUDINI_LOG_ERROR( - TEXT("Starting up the Houdini Engine module failed: %s"), + TEXT("Houdini Engine API initialization failed: %s"), *FHoudiniEngineUtils::GetErrorDescription(Result)); return false; @@ -727,6 +852,8 @@ FHoudiniEngine::OnSessionLost() // Mark the session as invalid Session.id = -1; Session.type = HAPI_SESSION_MAX; + SetSessionStatus(EHoudiniSessionStatus::Lost); + bEnableSessionSync = false; HoudiniEngineManager->StopHoudiniTicking(); @@ -760,6 +887,7 @@ FHoudiniEngine::StopSession(HAPI_Session*& SessionPtr) Session.id = -1; Session.type = HAPI_SESSION_MAX; + SetSessionStatus(EHoudiniSessionStatus::Stopped); bEnableSessionSync = false; HoudiniEngineManager->StopHoudiniTicking(); @@ -796,17 +924,20 @@ FHoudiniEngine::RestartSession() HoudiniRuntimeSettings->ServerHost)) { HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to start the new Session")); + SetSessionStatus(EHoudiniSessionStatus::Failed); } else { // Now initialize HAPI with this session if (!InitializeHAPISession()) { - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to initialize HAPI")); + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to initialize HAPI")); + SetSessionStatus(EHoudiniSessionStatus::Failed); } else { bSuccess = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); } } } @@ -847,6 +978,7 @@ FHoudiniEngine::CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionT HoudiniRuntimeSettings->ServerHost)) { HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine Session")); + SetSessionStatus(EHoudiniSessionStatus::Failed); } else { @@ -854,10 +986,12 @@ FHoudiniEngine::CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionT if (!InitializeHAPISession()) { HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine session - Failed to initialize HAPI")); + SetSessionStatus(EHoudiniSessionStatus::Failed); } else { bSuccess = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); } } @@ -897,6 +1031,7 @@ FHoudiniEngine::ConnectSession(const EHoudiniRuntimeSettingsSessionType& Session HoudiniRuntimeSettings->ServerHost)) { HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine Session")); + SetSessionStatus(EHoudiniSessionStatus::Failed); } else { @@ -904,10 +1039,12 @@ FHoudiniEngine::ConnectSession(const EHoudiniRuntimeSettingsSessionType& Session if (!InitializeHAPISession()) { HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine session - Failed to initialize HAPI")); + SetSessionStatus(EHoudiniSessionStatus::Failed); } else { bSuccess = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); } } @@ -947,6 +1084,13 @@ FHoudiniEngine::StopTicking() StopSession(SessionPtr); } +bool FHoudiniEngine::IsTicking() const +{ + if (!HoudiniEngineManager) + return false; + return HoudiniEngineManager->IsTicking(); +} + bool FHoudiniEngine::IsCookingEnabled() const { @@ -1002,6 +1146,7 @@ FHoudiniEngine::CreateTaskSlateNotification( */ NotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); + //FSlateNotificationManager::Get().Tick(); } #endif @@ -1017,6 +1162,8 @@ FHoudiniEngine::UpdateTaskSlateNotification(const FText& InText) TSharedPtr NotificationItem = NotificationPtr.Pin(); if (NotificationItem.IsValid()) NotificationItem->SetText(InText); + + //FSlateNotificationManager::Get().Tick(); #endif return true; @@ -1042,6 +1189,80 @@ FHoudiniEngine::FinishTaskSlateNotification(const FText& InText) return true; } +bool FHoudiniEngine::UpdateCookingNotification(const FText& InText, const bool bExpireAndFade) +{ +#if WITH_EDITOR + // Check whether we want to display Slate cooking and instantiation notifications. + bool bDisplaySlateCookingNotifications = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (!bDisplaySlateCookingNotifications) + return false; + + UpdatePersistentNotification(InText, bExpireAndFade); + +#endif + return true; +} + +bool +FHoudiniEngine::UpdatePersistentNotification(const FText& InText, const bool bExpireAndFade) +{ +#if WITH_EDITOR + TimeSinceLastPersistentNotification = 0.0; + + if (!PersistentNotificationPtr.IsValid()) + { + FNotificationInfo Info(InText); + Info.bFireAndForget = false; + Info.FadeOutDuration = HAPI_UNREAL_NOTIFICATION_FADEOUT; + Info.ExpireDuration = HAPI_UNREAL_NOTIFICATION_EXPIRE; + const TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); + if (HoudiniBrush.IsValid()) + Info.Image = HoudiniBrush.Get(); + + + PersistentNotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); + //FSlateNotificationManager::Get().Tick(); + } + + TSharedPtr NotificationItem = PersistentNotificationPtr.Pin(); + + if (NotificationItem.IsValid()) + { + // Update the persistent notification. + NotificationItem->SetText(InText); + bPersistentAllowExpiry = bExpireAndFade; + } + + //FSlateNotificationManager::Get().Tick(); +#endif + + return true; +} + +void FHoudiniEngine::TickPersistentNotification(const float DeltaTime) +{ + if (PersistentNotificationPtr.IsValid() && DeltaTime > 0.0f) + { + TimeSinceLastPersistentNotification += DeltaTime; + if (bPersistentAllowExpiry && TimeSinceLastPersistentNotification > HAPI_UNREAL_NOTIFICATION_EXPIRE) + { + TSharedPtr NotificationItem = PersistentNotificationPtr.Pin(); + if (NotificationItem.IsValid()) + { + NotificationItem->Fadeout(); + PersistentNotificationPtr.Reset(); + } + } + } + + // Tick the notification manager + //FSlateNotificationManager::Get().Tick(); +} + void FHoudiniEngine::UpdateSessionSyncInfoFromHoudini() { diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.h b/Source/HoudiniEngine/Private/HoudiniEngine.h index 4b7a1f4e4..07e2df662 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.h +++ b/Source/HoudiniEngine/Private/HoudiniEngine.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,6 +44,20 @@ struct FSlateDynamicImageBrush; enum class EHoudiniBGEOCommandletStatus : uint8; +UENUM() +enum class EHoudiniSessionStatus : int8 +{ + Invalid = -1, + + NotStarted, // Session not initialized yet + Connected, // Session successfully started + None, // Session type set to None + Stopped, // Session stopped + Failed, // Session failed to connect + Lost, // Session Lost (HARS/Houdini Crash?) + NoLicense, // Failed to acquire a license +}; + // Not using the IHoudiniEngine interface for now class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface { @@ -60,11 +74,18 @@ class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface static bool IsInitialized(); // Return the location of the currently loaded LibHAPI - virtual const FString & GetLibHAPILocation() const; + virtual const FString& GetLibHAPILocation() const; + + // Return the houdini executable to use + static const FString GetHoudiniExecutable(); // Session accessor virtual const HAPI_Session* GetSession() const; + virtual const EHoudiniSessionStatus& GetSessionStatus() const; + + virtual void SetSessionStatus(const EHoudiniSessionStatus& InSessionStatus); + // Default cook options static HAPI_CookOptions GetDefaultCookOptions(); @@ -102,6 +123,8 @@ class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface // Stops the HoudiniEngineManager ticking and invalidate the session void StopTicking(); + bool IsTicking() const; + // Initialize HAPI bool InitializeHAPISession(); @@ -117,6 +140,15 @@ class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface bool UpdateTaskSlateNotification(const FText& InText); bool FinishTaskSlateNotification(const FText& InText); + // Only update persistent notification if cooking notification has been enabled in the settings. + bool UpdateCookingNotification(const FText& InText, const bool bExpireAndFade); + + // Update persistent notification irrespective of any notification enable/disable settings. + bool UpdatePersistentNotification(const FText& InText, const bool bExpireAndFade); + + // If the time since last persistent notification has expired, fade out the persistent notification. + void TickPersistentNotification(float DeltaTime); + void SetHapiNotificationStartedTime(const double& InTime) { HapiNotificationStarted = InTime; }; // Register task for execution. @@ -226,6 +258,9 @@ class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface // The Houdini Engine session. HAPI_Session Session; + // The Houdini Engine session's status + EHoudiniSessionStatus SessionStatus; + // The type of HE license used by the current session HAPI_License LicenseType; @@ -302,6 +337,12 @@ class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface #if WITH_EDITOR /** Notification used by this component. **/ TWeakPtr NotificationPtr; + + /** Persistent notification. **/ + bool bPersistentAllowExpiry; + TWeakPtr PersistentNotificationPtr; + float TimeSinceLastPersistentNotification; + /** Used to delay notification updates for HAPI asynchronous work. **/ double HapiNotificationStarted; #endif diff --git a/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.cpp b/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.cpp deleted file mode 100644 index c5fdea932..000000000 --- a/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.h b/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.h deleted file mode 100644 index 05b5eb527..000000000 --- a/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.h +++ /dev/null @@ -1,27 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp index 2d94a68a0..a510b7804 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp @@ -1,6 +1,5 @@ - /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,6 +31,7 @@ #include "HoudiniEngineRuntime.h" #include "HoudiniAsset.h" #include "HoudiniAssetComponent.h" +#include "HoudiniEngineString.h" #include "HoudiniEngineUtils.h" #include "HoudiniParameterTranslator.h" #include "HoudiniPDGManager.h" @@ -39,22 +39,30 @@ #include "HoudiniOutputTranslator.h" #include "HoudiniHandleTranslator.h" #include "HoudiniSplineTranslator.h" + #include "Misc/MessageDialog.h" #include "Misc/ScopedSlowTask.h" +#include "Containers/Ticker.h" +#include "HAL/IConsoleManager.h" #if WITH_EDITOR #include "Editor.h" #include "EditorViewportClient.h" #include "Kismet/KismetMathLibrary.h" - + //#include "UnrealEd.h" #include "UnrealEdGlobals.h" #include "Editor/UnrealEdEngine.h" #include "IPackageAutoSaver.h" #endif -const float -FHoudiniEngineManager::TickTimerDelay = 0.01f; +static TAutoConsoleVariable CVarHoudiniEngineTickTimeLimit( + TEXT("HoudiniEngine.TickTimeLimit"), + 1.0, + TEXT("Time limit after which HDA processing will be stopped, until the next tick of the Houdini Engine Manager.\n") + TEXT("<= 0.0: No Limit\n") + TEXT("1.0: Default\n") +); FHoudiniEngineManager::FHoudiniEngineManager() : CurrentIndex(0) @@ -81,10 +89,10 @@ void FHoudiniEngineManager::StartHoudiniTicking() { // If we have no timer delegate spawned, spawn one. - if (!TimerDelegateProcess.IsBound() && GEditor) + if (!TickerHandle.IsValid() && GEditor) { - TimerDelegateProcess = FTimerDelegate::CreateRaw(this, &FHoudiniEngineManager::Tick); - GEditor->GetTimerManager()->SetTimer(TimerHandleProcess, TimerDelegateProcess, TickTimerDelay, true); + // We use the ticker manager so we get ticked once per frame, no more. + TickerHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FHoudiniEngineManager::Tick)); // Grab current time for delayed notification. FHoudiniEngine::Get().SetHapiNotificationStartedTime(FPlatformTime::Seconds()); @@ -94,12 +102,12 @@ FHoudiniEngineManager::StartHoudiniTicking() void FHoudiniEngineManager::StopHoudiniTicking() { - if (TimerDelegateProcess.IsBound() && GEditor) + if (TickerHandle.IsValid() && GEditor) { if (IsInGameThread()) { - GEditor->GetTimerManager()->ClearTimer(TimerHandleProcess); - TimerDelegateProcess.Unbind(); + FTicker::GetCoreTicker().RemoveTicker(TickerHandle); + TickerHandle.Reset(); // Reset time for delayed notification. FHoudiniEngine::Get().SetHapiNotificationStartedTime(0.0); @@ -116,68 +124,141 @@ FHoudiniEngineManager::StopHoudiniTicking() } } -void -FHoudiniEngineManager::Tick() +bool FHoudiniEngineManager::IsTicking() const +{ + return TickerHandle.IsValid(); +} + +bool +FHoudiniEngineManager::Tick(float DeltaTime) { + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineManager::Tick); + EnableEditorAutoSave(nullptr); + FHoudiniEngine::Get().TickPersistentNotification(DeltaTime); + if (bMustStopTicking) { // Ticking should be stopped immediately StopHoudiniTicking(); - return; + return true; } - // Process the current component if possible - while (true) + // Build a set of components that need to be processed + // 1 - selected HACs + // 2 - "Active" HACs + // 3 - The "next" inactive HAC + TArray ComponentsToProcess; + if (FHoudiniEngineRuntime::IsInitialized()) { - UHoudiniAssetComponent * CurrentComponent = nullptr; - if (FHoudiniEngineRuntime::IsInitialized()) + FHoudiniEngineRuntime::Get().CleanUpRegisteredHoudiniComponents(); + + //FScopeLock ScopeLock(&CriticalSection); + ComponentCount = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount(); + + // Wrap around if needed + if (CurrentIndex >= ComponentCount) + CurrentIndex = 0; + + for (uint32 nIdx = 0; nIdx < ComponentCount; nIdx++) { - FHoudiniEngineRuntime::Get().CleanUpRegisteredHoudiniComponents(); + UHoudiniAssetComponent * CurrentComponent = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentAt(nIdx); + if (!CurrentComponent || !CurrentComponent->IsValidLowLevelFast()) + { + // Invalid component, do not process + continue; + } + else if (!IsValid(CurrentComponent) || CurrentComponent->GetAssetState() == EHoudiniAssetState::Deleting) + { + // Component being deleted, do not process + continue; + } + + { + UWorld* World = CurrentComponent->GetWorld(); + if (World && World->IsPlayingReplay() || World->IsPlayInEditor()) + { + if (!CurrentComponent->IsPlayInEditorRefinementAllowed()) + { + // This component's world is current in PIE and this HDA is NOT allowed to cook / refine in PIE. + continue; + } + } + } - //FScopeLock ScopeLock(&CriticalSection); - ComponentCount = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount(); + if (!CurrentComponent->IsFullyLoaded()) + { + // Let the component figure out whether it's fully loaded or not. + CurrentComponent->HoudiniEngineTick(); + if (!CurrentComponent->IsFullyLoaded()) + continue; // We need to wait some more. + } - // No work to be done - if (ComponentCount <= 0) - break; + if (!CurrentComponent->IsValidComponent()) + { + // This component is no longer valid. Prevent it from being processed, and remove it. + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); + continue; + } - // Wrap around if needed - if (CurrentIndex >= ComponentCount) - CurrentIndex = 0; + AActor* Owner = CurrentComponent->GetOwner(); + if (Owner && Owner->IsSelectedInEditor()) + { + // 1. Add selected HACs + // If the component's owner is selected, add it to the set + ComponentsToProcess.Add(CurrentComponent); + } + else if (CurrentComponent->GetAssetState() != EHoudiniAssetState::NeedInstantiation + && CurrentComponent->GetAssetState() != EHoudiniAssetState::None) + { + // 2. Add "Active" HACs, the only two non-active states are: + // NeedInstantiation (loaded, not instantiated in H yet, not modified) + // None (no processing currently) + ComponentsToProcess.Add(CurrentComponent); + } + else if(nIdx == CurrentIndex) + { + // 3. Add the "Current" HAC + ComponentsToProcess.Add(CurrentComponent); + } - CurrentComponent = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentAt(CurrentIndex); - CurrentIndex++; + // Set the LastTickTime on the "current" HAC to 0 to ensure it's treated first + if (nIdx == CurrentIndex) + { + CurrentComponent->LastTickTime = 0.0; + } } - if (!CurrentComponent || !CurrentComponent->IsValidLowLevelFast()) - { - // Invalid component, do not process - break; - } - else if (CurrentComponent->IsPendingKill() - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Deleting) - { - // Component being deleted, do not process - break; - } + // Increment the current index for the next tick + CurrentIndex++; + } - if (!CurrentComponent->IsFullyLoaded()) - { - // Let the component figure out whether it's fully loaded or not. - CurrentComponent->HoudiniEngineTick(); - if (!CurrentComponent->IsFullyLoaded()) - continue; // We need to wait some more. - } + // Sort the components by last tick time + ComponentsToProcess.Sort([](const UHoudiniAssetComponent& A, const UHoudiniAssetComponent& B) { return A.LastTickTime < B.LastTickTime; }); + + // Time limit for processing + double dProcessTimeLimit = CVarHoudiniEngineTickTimeLimit.GetValueOnAnyThread(); + double dProcessStartTime = FPlatformTime::Seconds(); - if (!CurrentComponent->IsValidComponent()) + // Process all the components in the list + for(UHoudiniAssetComponent* CurrentComponent : ComponentsToProcess) + { + // Tick the notification manager + //FHoudiniEngine::Get().TickPersistentNotification(0.0f); + + double dNow = FPlatformTime::Seconds(); + if (dProcessTimeLimit > 0.0 + && dNow - dProcessStartTime > dProcessTimeLimit) { - // This component is no longer valid. Prevent it from being processed, and remove it. - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); - continue; + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Manager: Stopped processing after %F seconds."), (dNow - dProcessStartTime)); + break; } + // Update the tick time for this component + CurrentComponent->LastTickTime = dNow; + + // Handle template processing (for BP) first // We don't want to the template component processing to trigger session creation if (CurrentComponent->GetAssetState() == EHoudiniAssetState::ProcessTemplate) { @@ -213,51 +294,63 @@ FHoudiniEngineManager::Tick() { // TODO: Transfer template output changes over to the preview instance. } - - break; + continue; } - // See if we should start the default "first" session - if(!FHoudiniEngine::Get().GetSession() && !FHoudiniEngine::Get().GetFirstSessionCreated()) + // Process the component + bool bKeepProcessing = true; + while (bKeepProcessing) { - // Only try to start the default session if we have an "active" HAC - if (CurrentComponent->GetAssetState() == EHoudiniAssetState::PreInstantiation - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Instantiating - || CurrentComponent->GetAssetState() == EHoudiniAssetState::PreCook - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Cooking) - { - FString StatusText = TEXT("Initializing Houdini Engine..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + // Tick the notification manager + FHoudiniEngine::Get().TickPersistentNotification(0.0f); - // We want to yield for a bit. - //FPlatformProcess::Sleep(0.5f); + // See if we should start the default "first" session + AutoStartFirstSessionIfNeeded(CurrentComponent); - // Indicates that we've tried to start the session once no matter if it failed or succeed - FHoudiniEngine::Get().SetFirstSessionCreated(true); + EHoudiniAssetState PrevState = CurrentComponent->GetAssetState(); + ProcessComponent(CurrentComponent); + EHoudiniAssetState NewState = CurrentComponent->GetAssetState(); - // Attempt to restart the session - if (!FHoudiniEngine::Get().RestartSession()) - { - // We failed to start the session - // Stop ticking until it's manually restarted - StopHoudiniTicking(); + // In order to process components faster / with less ticks, + // we may continue processing the component if it ends up in certain states + switch (NewState) + { + case EHoudiniAssetState::NewHDA: + case EHoudiniAssetState::PreInstantiation: + case EHoudiniAssetState::PreCook: + case EHoudiniAssetState::PostCook: + case EHoudiniAssetState::PreProcess: + case EHoudiniAssetState::Processing: + bKeepProcessing = true; + break; + + case EHoudiniAssetState::NeedInstantiation: + case EHoudiniAssetState::Instantiating: + case EHoudiniAssetState::Cooking: + case EHoudiniAssetState::None: + case EHoudiniAssetState::ProcessTemplate: + case EHoudiniAssetState::NeedRebuild: + case EHoudiniAssetState::NeedDelete: + case EHoudiniAssetState::Deleting: + bKeepProcessing = false; + break; + } - StatusText = TEXT("Houdini Engine failed to initialize."); - } - else - { - StatusText = TEXT("Houdini Engine successfully initialized."); - } + // Safeguard, useless? + // Stop processing if the state hasn't changed + if (PrevState == NewState) + bKeepProcessing = false; - // Finish the notification and display the results - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + dNow = FPlatformTime::Seconds(); + if (dProcessTimeLimit > 0.0 && dNow - dProcessStartTime > dProcessTimeLimit) + { + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Manager: Stopped processing after %F seconds."), (dNow - dProcessStartTime)); + break; } - } - // Process the component - // try to catch (apache::thrift::transport::TTransportException * e) for session loss? - ProcessComponent(CurrentComponent); - break; + // Update the tick time for this component + CurrentComponent->LastTickTime = dNow; + } } // Handle Asset delete @@ -309,20 +402,74 @@ FHoudiniEngineManager::Tick() if (bOffsetZeroed) bOffsetZeroed = false; } + + // Tick the notification manager + FHoudiniEngine::Get().TickPersistentNotification(0.0f); + + return true; +} + +void +FHoudiniEngineManager::AutoStartFirstSessionIfNeeded(UHoudiniAssetComponent* InCurrentHAC) +{ + // See if we should start the default "first" session + if (FHoudiniEngine::Get().GetSession() + || FHoudiniEngine::Get().GetFirstSessionCreated() + || !InCurrentHAC) + return; + + // Only try to start the default session if we have an "active" HAC + const EHoudiniAssetState CurrentState = InCurrentHAC->GetAssetState(); + if (CurrentState == EHoudiniAssetState::NewHDA + || CurrentState == EHoudiniAssetState::PreInstantiation + || CurrentState == EHoudiniAssetState::Instantiating + || CurrentState == EHoudiniAssetState::PreCook + || CurrentState == EHoudiniAssetState::Cooking) + { + FString StatusText = TEXT("Initializing Houdini Engine..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // We want to yield for a bit. + //FPlatformProcess::Sleep(0.5f); + + // Indicates that we've tried to start the session once no matter if it failed or succeed + FHoudiniEngine::Get().SetFirstSessionCreated(true); + + // Attempt to restart the session + if (!FHoudiniEngine::Get().RestartSession()) + { + // We failed to start the session + // Stop ticking until it's manually restarted + StopHoudiniTicking(); + + StatusText = TEXT("Houdini Engine failed to initialize."); + } + else + { + StatusText = TEXT("Houdini Engine successfully initialized."); + } + + // Finish the notification and display the results + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + } } void FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineManager::ProcessComponent); + + if (!IsValid(HAC)) return; // No need to process component not tied to an asset if (!HAC->GetHoudiniAsset()) return; - // If cooking is paused, stay in the current state until cooking's resumed - if (!FHoudiniEngine::Get().IsCookingEnabled()) + const EHoudiniAssetState AssetStateToProcess = HAC->GetAssetState(); + + // If cooking is paused, stay in the current state until cooking's resumed, unless we are in NewHDA + if (!FHoudiniEngine::Get().IsCookingEnabled() && AssetStateToProcess != EHoudiniAssetState::NewHDA) { // We can only handle output updates if (HAC->GetAssetState() == EHoudiniAssetState::None && HAC->NeedOutputUpdate()) @@ -345,7 +492,7 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) return; } - switch (HAC->GetAssetState()) + switch (AssetStateToProcess) { case EHoudiniAssetState::NeedInstantiation: { @@ -355,7 +502,7 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) HAC->OnPrePreInstantiation(); HAC->bForceNeedUpdate = false; // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::PreInstantiation; + HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); } else if (HAC->NeedOutputUpdate()) { @@ -369,29 +516,47 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) break; } + case EHoudiniAssetState::NewHDA: + { + // Update parameters. Since there is no instantiated node yet, this will only fetch the defaults from + // the asset definition. + FHoudiniParameterTranslator::UpdateParameters(HAC); + // Since the HAC only has the asset definition's default parameter interface, without any asset or node ids, + // we mark it has requiring a parameter definition sync. This will be carried out pre-cook. + HAC->bParameterDefinitionUpdateNeeded = true; + + HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + break; + } + case EHoudiniAssetState::PreInstantiation: { // Only proceed forward if we don't need to wait for our input HoudiniAssets to finish cooking/instantiating if (HAC->NeedsToWaitForInputHoudiniAssets()) break; + // Make sure we empty the nodes to cook array to avoid cook errors caused by stale nodes + HAC->ClearOutputNodes(); + FGuid TaskGuid; + FString HapiAssetName; UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); - if (StartTaskAssetInstantiation(HoudiniAsset, HAC->GetDisplayName(), TaskGuid)) + if (StartTaskAssetInstantiation(HoudiniAsset, HAC->GetDisplayName(), TaskGuid, HapiAssetName)) { // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::Instantiating; - //HAC->AssetStateResult = EHoudiniAssetStateResult::None; + HAC->SetAssetState(EHoudiniAssetState::Instantiating); // Update the Task GUID HAC->HapiGUID = TaskGuid; + + // Update the HapiAssetName + HAC->HapiAssetName = HapiAssetName; } else { // If we couldnt instantiate the asset - // Change the state to NeedInstantiating - HAC->AssetState = EHoudiniAssetState::NeedInstantiation; - //HAC->AssetStateResult = EHoudiniAssetStateResult::None; + // Change the state back to NeedInstantiating + HAC->SetAssetState(EHoudiniAssetState::NeedInstantiation); } break; } @@ -402,7 +567,7 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) if (UpdateInstantiating(HAC, NewState)) { // We need to update the HAC's state - HAC->AssetState = NewState; + HAC->SetAssetState(NewState); EnableEditorAutoSave(HAC); } else @@ -428,11 +593,22 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) bool bCookStarted = false; if (IsCookingEnabledForHoudiniAsset(HAC)) { + // Gather output nodes for the HAC + TArray OutputNodes; + FHoudiniEngineUtils::GatherAllAssetOutputs(HAC->GetAssetId(), HAC->bUseOutputNodes, HAC->bOutputTemplateGeos, OutputNodes); + HAC->SetOutputNodeIds(OutputNodes); + FGuid TaskGUID = HAC->GetHapiGUID(); - if ( StartTaskAssetCooking(HAC->GetAssetId(), HAC->GetDisplayName(), TaskGUID) ) + if ( StartTaskAssetCooking( + HAC->GetAssetId(), + OutputNodes, + HAC->GetDisplayName(), + HAC->bUseOutputNodes, + HAC->bOutputTemplateGeos, + TaskGUID) ) { // Updates the HAC's state - HAC->AssetState = EHoudiniAssetState::Cooking; + HAC->SetAssetState(EHoudiniAssetState::Cooking); HAC->HapiGUID = TaskGUID; bCookStarted = true; } @@ -444,7 +620,7 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); // TODO: Check! update state? - HAC->AssetState = EHoudiniAssetState::None; + HAC->SetAssetState(EHoudiniAssetState::None); } break; } @@ -456,7 +632,7 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) if (state) { // We need to update the HAC's state - HAC->AssetState = NewState; + HAC->SetAssetState(NewState); EnableEditorAutoSave(HAC); } else @@ -482,7 +658,7 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) // Cook failed, skip output processing NewState = EHoudiniAssetState::None; } - HAC->AssetState = NewState; + HAC->SetAssetState(NewState); break; } @@ -497,6 +673,7 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) UpdateProcess(HAC); int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + HAC->SetAssetCookCount(CookCount); HAC->OnPostOutputProcessing(); FHoudiniEngineUtils::UpdateBlueprintEditor(HAC); @@ -510,7 +687,7 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) { HAC->bForceNeedUpdate = false; // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::PreCook; + HAC->SetAssetState(EHoudiniAssetState::PreCook); } else if (HAC->NeedTransformUpdate()) { @@ -527,14 +704,14 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) // See if we need to get an update from Session Sync if(FHoudiniEngine::Get().IsSessionSyncEnabled() && FHoudiniEngine::Get().IsSyncWithHoudiniCookEnabled() - && HAC->AssetState == EHoudiniAssetState::None) + && HAC->GetAssetState() == EHoudiniAssetState::None) { int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); if (CookCount >= 0 && CookCount != HAC->GetAssetCookCount()) { // The cook count has changed on the Houdini side, // this indicates that the user has changed something in Houdini so we need to trigger an update - HAC->AssetState = EHoudiniAssetState::PreCook; + HAC->SetAssetState(EHoudiniAssetState::PreCook); } } break; @@ -545,7 +722,7 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) StartTaskAssetRebuild(HAC->AssetId, HAC->HapiGUID); HAC->MarkAsNeedCook(); - HAC->AssetState = EHoudiniAssetState::PreInstantiation; + HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); break; } @@ -556,7 +733,7 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) //HAC->AssetId = -1; // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::Deleting; + HAC->SetAssetState(EHoudiniAssetState::Deleting); break; } @@ -570,7 +747,7 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) bool -FHoudiniEngineManager::StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID) +FHoudiniEngineManager::StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID, FString& OutHAPIAssetName) { // Make sure we have a valid session before attempting anything if (!FHoudiniEngine::Get().GetSession()) @@ -579,7 +756,7 @@ FHoudiniEngineManager::StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, OutTaskGUID.Invalidate(); // Load the HDA file - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + if (!IsValid(HoudiniAsset)) { HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - null or invalid Houdini Asset.")); return false; @@ -630,6 +807,8 @@ FHoudiniEngineManager::StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, Task.AssetLibraryId = AssetLibraryId; Task.AssetHapiName = PickedAssetName; + FHoudiniEngineString(PickedAssetName).ToFString(OutHAPIAssetName); + // Add the task to the stack FHoudiniEngine::Get().AddTask(Task); @@ -650,7 +829,7 @@ FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudini // Get the current task's progress FHoudiniEngineTaskInfo TaskInfo; - if ( !UpdateTaskStatus(HAC->HapiGUID, TaskInfo) + if (!UpdateTaskStatus(HAC->HapiGUID, TaskInfo) || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetInstantiation) { // Couldnt get a valid task info @@ -688,20 +867,20 @@ FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudini } } - if ( !bFinished ) + if (!bFinished) { // Task is still in progress, nothing to do for now return false; } - if ( bSuccess && (TaskInfo.AssetId < 0) ) + if (bSuccess && (TaskInfo.AssetId < 0)) { // Task finished successfully but we received an invalid asset ID, error out HOUDINI_LOG_ERROR(TEXT(" %s Finished Instantiation but received invalid asset id."), *DisplayName); bSuccess = false; } - if ( bSuccess ) + if (bSuccess) { HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedInstantiation."), *DisplayName); @@ -718,6 +897,7 @@ FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudini // Reset the cook counter. HAC->SetAssetCookCount(0); + HAC->ClearOutputNodes(); // If necessary, set asset transform. if (HAC->bUploadTransformsToHoudiniEngine) @@ -735,6 +915,16 @@ FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudini PDGManager.InitializePDGAssetLink(HAC); } + // Initial update/create of inputs + if (HAC->HasBeenLoaded()) + { + FHoudiniInputTranslator::UpdateLoadedInputs(HAC); + } + else + { + FHoudiniInputTranslator::UpdateInputs(HAC); + } + // Update the HAC's state NewState = EHoudiniAssetState::PreCook; return true; @@ -747,13 +937,15 @@ FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudini switch (TaskInfo.Result) { case HAPI_RESULT_NO_LICENSE_FOUND: + case HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND: { + // No license / Apprentice license found //FHoudiniEngine::Get().SetHapiState(HAPI_RESULT_NO_LICENSE_FOUND); + FHoudiniEngine::Get().SetSessionStatus(EHoudiniSessionStatus::NoLicense); bLicensingIssue = true; break; } - case HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND: case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE: case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE: case HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE: @@ -786,8 +978,11 @@ FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudini // Make sure the asset ID is invalid HAC->AssetId = -1; + // Prevent the HAC from triggering updates in its current state + HAC->PreventAutoUpdates(); + // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::NeedInstantiation; + HAC->SetAssetState(EHoudiniAssetState::NeedInstantiation); //HAC->AssetStateResult = EHoudiniAssetStateResult::Success; return true; @@ -795,7 +990,13 @@ FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudini } bool -FHoudiniEngineManager::StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID) +FHoudiniEngineManager::StartTaskAssetCooking( + const HAPI_NodeId& AssetId, + const TArray& NodeIdsToCook, + const FString& DisplayName, + bool bUseOutputNodes, + bool bOutputTemplateGeos, + FGuid& OutTaskGUID) { // Make sure we have a valid session before attempting anything if (!FHoudiniEngine::Get().GetSession()) @@ -816,6 +1017,13 @@ FHoudiniEngineManager::StartTaskAssetCooking(const HAPI_NodeId& AssetId, const F FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetCooking, OutTaskGUID); Task.ActorName = DisplayName; Task.AssetId = AssetId; + + if (NodeIdsToCook.Num() > 0) + Task.OtherNodeIds = NodeIdsToCook; + + Task.bUseOutputNodes = bUseOutputNodes; + Task.bOutputTemplateGeos = bOutputTemplateGeos; + FHoudiniEngine::Get().AddTask(Task); return true; @@ -919,12 +1127,20 @@ FHoudiniEngineManager::PreCook(UHoudiniAssetComponent* HAC) FHoudiniParameterTranslator::OnPreCookParameters(HAC); + if (HAC->HasBeenLoaded() || HAC->IsParameterDefinitionUpdateNeeded()) + { + // This will sync parameter definitions but not upload values to HAPI or fetch values for existing parameters + // in Unreal. It will creating missing parameters in Unreal. + FHoudiniParameterTranslator::UpdateLoadedParameters(HAC); + HAC->bParameterDefinitionUpdateNeeded = false; + } + // Upload the changed/parameters back to HAPI // If cooking is disabled, we still try to upload parameters if (HAC->HasBeenLoaded()) { - // Handle loaded parameters - FHoudiniParameterTranslator::UpdateLoadedParameters(HAC); + // // Handle loaded parameters + // FHoudiniParameterTranslator::UpdateLoadedParameters(HAC); // Handle loaded inputs FHoudiniInputTranslator::UpdateLoadedInputs(HAC); @@ -970,19 +1186,13 @@ FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSucces } // Update the asset cook count using the node infos - int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + const int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); HAC->SetAssetCookCount(CookCount); - /* - if(CookCount >= 0 ) - HAC->SetAssetCookCount(CookCount); - else - HAC->SetAssetCookCount(HAC->GetAssetCookCount()+1); - */ bool bNeedsToTriggerViewportUpdate = false; if (bCookSuccess) { - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString("Processing outputs...")); + FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Processing outputs..."), false); // Set new asset id. HAC->AssetId = TaskAssetId; @@ -1011,11 +1221,13 @@ FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSucces HAC->SetHasBeenDuplicated(false); } - // TODO: Need to update rendering information. - // UpdateRenderingInformation(); + // Update rendering information. + HAC->UpdateRenderingInformation(); + + // Since we have new asset, we need to update bounds. HAC->UpdateBounds(); - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString("Finished processing outputs")); + FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Finished processing outputs"), true); // Trigger a details panel update FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); @@ -1054,7 +1266,7 @@ FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSucces { OnPostCookBakeDelegate.Unbind(); // Notify the user that the bake failed since the cook failed. - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString("Cook failed, therefore the bake also failed...")); + FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Cook failed, therefore the bake also failed..."), true); } } @@ -1063,6 +1275,15 @@ FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSucces HAC->ApplyInputPresets(); } + // Cache the current cook counts of the nodes so that we can more reliable determine + // whether content has changed next time build outputs. + const TArray OutputNodes = HAC->GetOutputNodeIds(); + for (int32 NodeId : OutputNodes) + { + int32 NodeCookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + HAC->SetOutputNodeCookCount(NodeId, NodeCookCount); + } + // If we have downstream HDAs, we need to tell them we're done cooking HAC->NotifyCookedToDownstreamAssets(); @@ -1088,7 +1309,7 @@ FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSucces bool FHoudiniEngineManager::StartTaskAssetProcess(UHoudiniAssetComponent* HAC) { - HAC->AssetState = EHoudiniAssetState::Processing; + HAC->SetAssetState(EHoudiniAssetState::Processing); return true; } @@ -1096,7 +1317,7 @@ FHoudiniEngineManager::StartTaskAssetProcess(UHoudiniAssetComponent* HAC) bool FHoudiniEngineManager::UpdateProcess(UHoudiniAssetComponent* HAC) { - HAC->AssetState = EHoudiniAssetState::None; + HAC->SetAssetState(EHoudiniAssetState::None); return true; } @@ -1184,7 +1405,7 @@ FHoudiniEngineManager::UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskIn if (EHoudiniEngineTaskState::None != OutTaskInfo.TaskState && bDisplaySlateCookingNotifications) { - FHoudiniEngine::Get().CreateTaskSlateNotification(OutTaskInfo.StatusText); + FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, false); } switch (OutTaskInfo.TaskState) @@ -1198,7 +1419,7 @@ FHoudiniEngineManager::UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskIn // Terminate the slate notification if they exist and delete/invalidate the task if (bDisplaySlateCookingNotifications) { - FHoudiniEngine::Get().FinishTaskSlateNotification(OutTaskInfo.StatusText); + FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, true); } FHoudiniEngine::Get().RemoveTaskInfo(OutTaskGUID); @@ -1211,7 +1432,7 @@ FHoudiniEngineManager::UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskIn // The current task is still running, simply update the current notification if (bDisplaySlateCookingNotifications) { - FHoudiniEngine::Get().UpdateTaskSlateNotification(OutTaskInfo.StatusText); + FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, false); } } break; @@ -1231,7 +1452,7 @@ FHoudiniEngineManager::IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* H { bool bManualRecook = false; bool bComponentEnable = false; - if (HAC && !HAC->IsPendingKill()) + if (IsValid(HAC)) { bManualRecook = HAC->HasRecookBeenRequested(); bComponentEnable = HAC->IsCookingEnabled(); @@ -1249,7 +1470,7 @@ FHoudiniEngineManager::IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* H void FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) { HOUDINI_LOG_ERROR(TEXT("FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes called with HAC=nullptr")); return; @@ -1490,7 +1711,7 @@ void FHoudiniEngineManager::DisableEditorAutoSave(const UHoudiniAssetComponent* HAC) { #if WITH_EDITOR - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; if (!GUnrealEd) @@ -1526,11 +1747,15 @@ FHoudiniEngineManager::EnableEditorAutoSave(const UHoudiniAssetComponent* HAC = if (DisableAutoSavingHACs.Num() <= 0) return; + TSet ValidComponents; for (auto& CurHAC : DisableAutoSavingHACs) { - if (!CurHAC || CurHAC->IsPendingKill()) - DisableAutoSavingHACs.Remove(CurHAC); + if (IsValid(CurHAC)) + { + ValidComponents.Add(CurHAC); + } } + DisableAutoSavingHACs = MoveTemp(ValidComponents); } else { @@ -1544,7 +1769,6 @@ FHoudiniEngineManager::EnableEditorAutoSave(const UHoudiniAssetComponent* HAC = // When no HAC disables cooking, reset min time till auto-save to default value, then reset the timer IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); - AutoSaver.ForceMinimumTimeTillAutoSave(); // use default value AutoSaver.ResetAutoSaveTimer(); #endif -} \ No newline at end of file +} diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.h b/Source/HoudiniEngine/Private/HoudiniEngineManager.h index 58fd0690d..599162864 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,7 +52,9 @@ class FHoudiniEngineManager void StartHoudiniTicking(); void StopHoudiniTicking(); - void Tick(); + bool IsTicking() const; + + bool Tick(float DeltaTime); // Updates / Process a component void ProcessComponent(UHoudiniAssetComponent* HAC); @@ -86,23 +88,39 @@ class FHoudiniEngineManager // Updates a given task's status // Returns true if the given task's status was properly found - bool UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo); + bool UpdateTaskStatus( + FGuid& OutTaskGUID, + FHoudiniEngineTaskInfo& OutTaskInfo); // Start a task to instantiate the given HoudiniAsset // Return true if the task was successfully created - bool StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID); + bool StartTaskAssetInstantiation( + UHoudiniAsset* HoudiniAsset, + const FString& DisplayName, + FGuid& OutTaskGUID, + FString& OutHAPIAssetName); // Updates progress of the instantiation task // Returns true if a state change should be made - bool UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); + bool UpdateInstantiating( + UHoudiniAssetComponent* HAC, + EHoudiniAssetState& NewState); // Start a task to instantiate the Houdini Asset with the given node Id // Returns true if the task was successfully created - bool StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID); + bool StartTaskAssetCooking( + const HAPI_NodeId& AssetId, + const TArray& NodeIdsToCook, + const FString& DisplayName, + bool bUseOutputNodes, + bool bOutputTemplateGeos, + FGuid& OutTaskGUID); // Updates progress of the cooking task // Returns true if a state change should be made - bool UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); + bool UpdateCooking( + UHoudiniAssetComponent* HAC, + EHoudiniAssetState& NewState); // Called to update template components. bool PreCookTemplate(UHoudiniAssetComponent* HAC); @@ -111,7 +129,10 @@ class FHoudiniEngineManager bool PreCook(UHoudiniAssetComponent* HAC); // Called after a cook has finished - bool PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId); + bool PostCook( + UHoudiniAssetComponent* HAC, + const bool& bSuccess, + const HAPI_NodeId& TaskAssetId); bool StartTaskAssetProcess(UHoudiniAssetComponent* HAC); @@ -119,11 +140,16 @@ class FHoudiniEngineManager // Starts a rebuild task (delete then re instantiate) // The NodeID should be invalidated after a successful call - bool StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID); + bool StartTaskAssetRebuild( + const HAPI_NodeId& InAssetId, + FGuid& OutTaskGUID); // Starts a node delete task // The NodeID should be invalidated after a successful call - bool StartTaskAssetDelete(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID, bool bShouldDeleteParent); + bool StartTaskAssetDelete( + const HAPI_NodeId& InAssetId, + FGuid& OutTaskGUID, + bool bShouldDeleteParent); bool IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC); @@ -140,16 +166,13 @@ class FHoudiniEngineManager void EnableEditorAutoSave(const UHoudiniAssetComponent* HAC); -private: - - // Delay between each update of the manager - static const float TickTimerDelay; + // Automatically try to start the First HE session if needed + void AutoStartFirstSessionIfNeeded(UHoudiniAssetComponent* InCurrentHAC); - // Timer handle, this timer is used for processing HAC. - FTimerHandle TimerHandleProcess; +private: - // Timer delegate, we use it for ticking during processing. - FTimerDelegate TimerDelegateProcess; + // Ticker handle, used for processing HAC. + FDelegateHandle TickerHandle; // Current position in the array uint32 CurrentIndex; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp index a8648c131..ce7231f85 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp @@ -1,4 +1,29 @@ - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + #include "HoudiniEngineOutputStats.h" FHoudiniEngineOutputStats::FHoudiniEngineOutputStats() diff --git a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h index edeae4726..6234dca1b 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h @@ -1,5 +1,5 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. +/* +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h index 0955274f4..870091235 100644 --- a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h +++ b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -186,6 +186,9 @@ #define HAPI_UNREAL_ATTRIB_INSTANCE_COLOR "unreal_instance_color" #define HAPI_UNREAL_ATTRIB_SPLIT_ATTR "unreal_split_attr" #define HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM "unreal_hierarchical_instancer" +#define HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS "unreal_num_custom_floats" +#define HAPI_UNREAL_ATTRIB_INSTANCE_CUSTOM_DATA_PREFIX "unreal_per_instance_custom_data" +#define HAPI_UNREAL_ATTRIB_FORCE_INSTANCER "unreal_force_instancer" #define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME HAPI_ATTRIB_NAME @@ -194,6 +197,29 @@ #define HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS "unreal_landscape_layer_nonweightblended" #define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" #define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO "unreal_landscape_layer_info" +// Landscape output mode: +// 0 - Generate (generate a landscape from scratch) +// 1 - Modify Layer (modify one or more landscape layers only) +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_OUTPUT_MODE "unreal_landscape_output_mode" +#define HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_GENERATE 0 +#define HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_MODIFY_LAYER 1 + +// Edit layer +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_NAME "unreal_landscape_editlayer_name" +// Clear the editlayer before blitting new data +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_CLEAR "unreal_landscape_editlayer_clear" +// Place the output layer "after" the given layer +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_AFTER "unreal_landscape_editlayer_after" +// Landscape that is being targeted by "edit layer" outputs (only used in Modify Layer mode) +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TARGET "unreal_landscape_editlayer_target" + +// Edit Layer types: +// 0 - Base layer: Values will be fit to the min/max height range in UE for optimal resolution. +// 1 - Additive layer: Values will be scaled similar to the base layer but will NOT be offset +// so that it will remain centered around the zero value. +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TYPE "unreal_landscape_editlayer_type" +#define HAPI_UNREAL_LANDSCAPE_EDITLAYER_TYPE_BASE 0 +#define HAPI_UNREAL_LANDSCAPE_EDITLAYER_TYPE_ADDITIVE 1 #define HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX "unreal_uproperty_" #define HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX "unreal_material_parameter_" @@ -202,6 +228,7 @@ #define HAPI_UNREAL_ATTRIB_TEMP_FOLDER "unreal_temp_folder" #define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1 "unreal_generated_mesh_name" #define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2 "unreal_output_name" +#define HAPI_UNREAL_ATTRIB_BAKE_NAME "unreal_bake_name" #define HAPI_UNREAL_ATTRIB_BAKE_ACTOR "unreal_bake_actor" #define HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER "unreal_bake_outliner_folder" @@ -237,6 +264,15 @@ #define HOUDINI_PARAMETER_STRING_REF_CLASS_TAG TEXT("unreal_ref_class") #define HOUDINI_PARAMETER_STRING_MULTILINE_TAG TEXT("editor") +// Parameter tags +#define HAPI_PARAM_TAG_NOSWAP "hengine_noswap" +#define HAPI_PARAM_TAG_FILE_READONLY "filechooser_mode" +#define HAPI_PARAM_TAG_UNITS "units" +#define HAPI_PARAM_TAG_DEFAULT_DIR "default_dir" + +// TODO: unused, remove! +#define HAPI_PARAM_TAG_ASSET_REF "asset_ref" + // Groups #define HAPI_UNREAL_GROUP_LOD_PREFIX TEXT("lod") #define HAPI_UNREAL_GROUP_SOCKET_PREFIX TEXT("mesh_socket") @@ -257,13 +293,15 @@ // Default material name. #define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) +// Visibility layer name +#define HAPI_UNREAL_VISIBILITY_LAYER_NAME TEXT( "visibility" ) + // Various variable names used to store meta information in generated packages. #define HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT TEXT( "HoudiniGeneratedObject" ) #define HAPI_UNREAL_PACKAGE_META_GENERATED_NAME TEXT( "HoudiniGeneratedName" ) #define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE TEXT( "HoudiniGeneratedTextureType" ) #define HAPI_UNREAL_PACKAGE_META_NODE_PATH TEXT( "HoudiniNodePath" ) #define HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER TEXT( "HoudiniPackageBakeCounter" ) -#define HAPI_UNREAL_PACKAGE_META_TEMP_GUID TEXT( "HoudiniPackageTempGUID" ) #define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL TEXT( "N" ) #define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE TEXT( "C_A" ) @@ -280,62 +318,90 @@ #define HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL "N" // Materials Diffuse. -#define HAPI_UNREAL_PARAM_TEXTURE_LAYERS_NUM "ogl_numtex" +#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL "ogl_diff" +#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE "basecolor" + +#define HAPI_UNREAL_PARAM_TEXTURE_LAYERS_NUM "ogl_numtex" -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_0 "ogl_tex1" -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_1 "basecolor_texture" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL "ogl_tex1" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED "ogl_use_tex1" -#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0 "ogl_diff" -#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1 "basecolor" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE "basecolor_texture" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED "basecolor_useTexture" -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_COLOR_SPACE "basecolor_textureColorSpace" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_COLOR_SPACE "basecolor_textureColorSpace" // Materials Normal. -#define HAPI_UNREAL_PARAM_MAP_NORMAL_0 "ogl_normalmap" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_1 "normalTexture" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_OGL "ogl_normalmap" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE "ogl_normalmap_type" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT "Tangent Space" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD "World Space" +//#define HAPI_UNREAL_PARAM_MAP_NORMAL "normalTexture" +//#define HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED "normalUseTexture" +#define HAPI_UNREAL_PARAM_MAP_NORMAL "baseNormal_texture" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED "baseBumpAndNormal_enable" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_COLOR_SPACE "normalTexColorSpace" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE "ogl_normalmap_type" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT "Tangent Space" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD "World Space" + +#define HAPI_UNREAL_PARAM_MAP_NORMAL_COLOR_SPACE "normalTexColorSpace" // Materials Specular. -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_0 "ogl_specmap" -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_1 "reflect_texture" +#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL "ogl_spec" +#define HAPI_UNREAL_PARAM_COLOR_SPECULAR "reflect" + +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL "ogl_specmap" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL_ENABLED "ogl_use_specmap" -#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_0 "ogl_spec" -#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_1 "reflect" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR "reflect_texture" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_ENABLED "reflect_useTexture" -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_COLOR_SPACE "reflect_textureColorSpace" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_COLOR_SPACE "reflect_textureColorSpace" // Materials Roughness. -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0 "ogl_roughmap" -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1 "rough_texture" +#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL "ogl_rough" +#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS "rough" + +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL "ogl_roughmap" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL_ENABLED "ogl_use_roughmap" -#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0 "ogl_rough" -#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1 "rough" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS "rough_texture" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_ENABLED "rough_useTexture" -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_COLOR_SPACE "rough_textureColorSpace" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_COLOR_SPACE "rough_textureColorSpace" // Materials Metallic. -#define HAPI_UNREAL_PARAM_MAP_METALLIC "metallic_texture" -#define HAPI_UNREAL_PARAM_VALUE_METALLIC "metallic" -#define HAPI_UNREAL_PARAM_MAP_METALLIC_COLOR_SPACE "metallic_textureColorSpace" +#define HAPI_UNREAL_PARAM_VALUE_METALLIC "metallic" +#define HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL "ogl_metallic" + +#define HAPI_UNREAL_PARAM_MAP_METALLIC_OGL "ogl_metallicmap" +#define HAPI_UNREAL_PARAM_MAP_METALLIC_OGL_ENABLED "ogl_use_metallicmap" + +#define HAPI_UNREAL_PARAM_MAP_METALLIC "metallic_texture" +#define HAPI_UNREAL_PARAM_MAP_METALLIC_ENABLED "metallic_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_METALLIC_COLOR_SPACE "metallic_textureColorSpace" // Materials Emissive. -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE "emitcolor_texture" -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_COLOR_SPACE "emitcolor_textureColorSpace" +#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL "ogl_emit" +#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE "emitcolor" -#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0 "ogl_emit" -#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1 "emitcolor" +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL "ogl_emissionmap" +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL_ENABLED "ogl_use_emissionmap" + +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE "emitcolor_texture" +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_ENABLED "emitcolor_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_COLOR_SPACE "emitcolor_textureColorSpace" // Materials Opacity. -#define HAPI_UNREAL_PARAM_ALPHA_0 "ogl_alpha" -#define HAPI_UNREAL_PARAM_ALPHA_1 "opac" +#define HAPI_UNREAL_PARAM_ALPHA_OGL "ogl_alpha" +#define HAPI_UNREAL_PARAM_ALPHA "opac" + +#define HAPI_UNREAL_PARAM_MAP_OPACITY_OGL "ogl_opacitymap" +#define HAPI_UNREAL_PARAM_MAP_OPACITY_OGL_ENABLED "ogl_use_opacitymap" -#define HAPI_UNREAL_PARAM_MAP_OPACITY_0 "ogl_opacitymap" -#define HAPI_UNREAL_PARAM_MAP_OPACITY_1 "opaccolor_texture" +#define HAPI_UNREAL_PARAM_MAP_OPACITY "opaccolor_texture" +#define HAPI_UNREAL_PARAM_MAP_OPACITY_ENABLED "opaccolor_useTexture" // Number of GUID characters to keep for packages #define PACKAGE_GUID_LENGTH 8 @@ -356,19 +422,3 @@ #define HAPI_UNREAL_NOTIFICATION_FADEOUT 2.0f #define HAPI_UNREAL_NOTIFICATION_EXPIRE 2.0f -// Struct to enable global silent flag - this will force dialogs to not show up. -struct FHoudiniScopedGlobalSilence -{ - FHoudiniScopedGlobalSilence() - { - bGlobalSilent = GIsSilent; - GIsSilent = true; - } - - ~FHoudiniScopedGlobalSilence() - { - GIsSilent = bGlobalSilent; - } - - bool bGlobalSilent; -}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineProcessor.cpp b/Source/HoudiniEngine/Private/HoudiniEngineProcessor.cpp deleted file mode 100644 index c5fdea932..000000000 --- a/Source/HoudiniEngine/Private/HoudiniEngineProcessor.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp index 4b73aed40..478cdec79 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -288,82 +288,134 @@ FHoudiniEngineScheduler::TaskCookAsset(const FHoudiniEngineTask & Task) return; } + // Get the extra node Ids that we want to process if needed + TArray NodesToCook; + NodesToCook.Add(AssetId); + for (auto& CurrentNodeId : Task.OtherNodeIds) + { + if (CurrentNodeId < 0) + continue; + + NodesToCook.AddUnique(CurrentNodeId); + } + // Default CookOptions HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - Result = FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), AssetId, &CookOptions); - if (Result != HAPI_RESULT_SUCCESS) + + EHoudiniEngineTaskState GlobalTaskResult = EHoudiniEngineTaskState::Success; + for (auto& CurrentNodeId : NodesToCook) { + Result = FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CookOptions); + if (Result != HAPI_RESULT_SUCCESS) + { + AddResponseMessageTaskInfo( + Result, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithFatalError, + AssetId, + Task, + TEXT("Error cooking asset.")); + + return; + } + + // Add processing notification. AddResponseMessageTaskInfo( - Result, EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::FinishedWithFatalError, - AssetId, Task, TEXT("Error cooking asset.")); + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Working, + AssetId, Task, TEXT("Started Cooking")); - return; - } + // Initialize last update time. + double LastUpdateTime = FPlatformTime::Seconds(); - // Add processing notification. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::Working, - AssetId, Task, TEXT("Started Cooking")); + // We need to spin until cooking is finished. + while (true) + { + int32 Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); - // Initialize last update time. - double LastUpdateTime = FPlatformTime::Seconds(); + if (Status == HAPI_STATE_READY) + { + // Cooking has been successful. + // Break to process the next node + break; + } + else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + GlobalTaskResult = EHoudiniEngineTaskState::FinishedWithFatalError; - // We need to spin until cooking is finished. - while (true) - { - int32 Status = HAPI_STATE_STARTING_COOK; - HOUDINI_CHECK_ERROR_GET( &Result, FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); + if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + GlobalTaskResult = EHoudiniEngineTaskState::FinishedWithError; - if (Status == HAPI_STATE_READY) + break; + } + + static const double NotificationUpdateFrequency = 0.5; + if (FPlatformTime::Seconds() - LastUpdateTime >= NotificationUpdateFrequency) + { + // Reset update time. + LastUpdateTime = FPlatformTime::Seconds(); + + // Retrieve status string. + const FString & CookStateMessage = FHoudiniEngineUtils::GetCookState(); + + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Working, + AssetId, Task, CookStateMessage); + } + + // We want to yield. + FPlatformProcess::SleepNoStats(UpdateFrequency); + } + } + + switch (GlobalTaskResult) + { + case EHoudiniEngineTaskState::Success: { - // Cooking has been successful. + // Cooking has been successful AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, + HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetCooking, EHoudiniEngineTaskState::Success, - AssetId, Task, TEXT("Finished Cooking")); - - break; + AssetId, + Task, + TEXT("Finished Cooking")); } - else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - EHoudiniEngineTaskState TaskResult = EHoudiniEngineTaskState::FinishedWithFatalError; - if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - TaskResult = EHoudiniEngineTaskState::FinishedWithError; + break; - // There was an error while instantiating. + case EHoudiniEngineTaskState::FinishedWithError: + { + // There was an error while Cooking. AddResponseMessageTaskInfo( HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetCooking, - TaskResult, - AssetId, Task, + EHoudiniEngineTaskState::FinishedWithError, + AssetId, + Task, TEXT("Finished Cooking with Errors")); - - break; } + break; - static const double NotificationUpdateFrequency = 0.5; - if (FPlatformTime::Seconds() - LastUpdateTime >= NotificationUpdateFrequency) + case EHoudiniEngineTaskState::FinishedWithFatalError: + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::None: + case EHoudiniEngineTaskState::Working: { - // Reset update time. - LastUpdateTime = FPlatformTime::Seconds(); - - // Retrieve status string. - const FString & CookStateMessage = FHoudiniEngineUtils::GetCookState(); - + // There was an error while cooking. AddResponseMessageTaskInfo( HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::Working, - AssetId, Task, CookStateMessage); + EHoudiniEngineTaskState::FinishedWithFatalError, + AssetId, + Task, + TEXT("Finished Cooking with Fatal Errors")); } - - // We want to yield. - FPlatformProcess::SleepNoStats(UpdateFrequency); + break; } } @@ -537,6 +589,12 @@ FHoudiniEngineScheduler::TaskProccessAsset(const FHoudiniEngineTask & Task) // TODO: Process results! } +bool FHoudiniEngineScheduler::HasPendingTasks() +{ + FScopeLock ScopeLock(&CriticalSection); + return (PositionWrite != PositionRead); +} + void FHoudiniEngineScheduler::AddTask(const FHoudiniEngineTask & Task) { diff --git a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h index 24f326dc6..2c9354540 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,6 +50,8 @@ class FHoudiniEngineScheduler : public FRunnable, FSingleThreadRunnable // Adds a task. void AddTask(const FHoudiniEngineTask & Task); + bool HasPendingTasks(); + // Adds instantiation response task info. void AddResponseTaskInfo( HAPI_Result Result, diff --git a/Source/HoudiniEngine/Private/HoudiniEngineString.cpp b/Source/HoudiniEngine/Private/HoudiniEngineString.cpp index c0738fddc..78d9871f8 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineString.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineString.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,12 +40,12 @@ FHoudiniEngineString::FHoudiniEngineString(int32 InStringId) : StringId(InStringId) {} -FHoudiniEngineString::FHoudiniEngineString(const FHoudiniEngineString & Other) +FHoudiniEngineString::FHoudiniEngineString(const FHoudiniEngineString& Other) : StringId(Other.StringId) {} FHoudiniEngineString & -FHoudiniEngineString::operator=(const FHoudiniEngineString & Other) +FHoudiniEngineString::operator=(const FHoudiniEngineString& Other) { if (this != &Other) StringId = Other.StringId; @@ -54,13 +54,13 @@ FHoudiniEngineString::operator=(const FHoudiniEngineString & Other) } bool -FHoudiniEngineString::operator==(const FHoudiniEngineString & Other) const +FHoudiniEngineString::operator==(const FHoudiniEngineString& Other) const { return Other.StringId == StringId; } bool -FHoudiniEngineString::operator!=(const FHoudiniEngineString & Other) const +FHoudiniEngineString::operator!=(const FHoudiniEngineString& Other) const { return Other.StringId != StringId; } @@ -78,7 +78,7 @@ FHoudiniEngineString::HasValidId() const } bool -FHoudiniEngineString::ToStdString(std::string & String) const +FHoudiniEngineString::ToStdString(std::string& String) const { String = ""; @@ -99,7 +99,7 @@ FHoudiniEngineString::ToStdString(std::string & String) const if (NameLength <= 0) return false; - std::vector< char > NameBuffer(NameLength, '\0'); + std::vector NameBuffer(NameLength, '\0'); if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetString( FHoudiniEngine::Get().GetSession(), StringId, &NameBuffer[0], NameLength ) ) @@ -113,7 +113,7 @@ FHoudiniEngineString::ToStdString(std::string & String) const } bool -FHoudiniEngineString::ToFName(FName & Name) const +FHoudiniEngineString::ToFName(FName& Name) const { Name = NAME_None; FString NameString = TEXT(""); @@ -127,7 +127,7 @@ FHoudiniEngineString::ToFName(FName & Name) const } bool -FHoudiniEngineString::ToFString(FString & String) const +FHoudiniEngineString::ToFString(FString& String) const { String = TEXT(""); std::string NamePlain = ""; @@ -142,7 +142,7 @@ FHoudiniEngineString::ToFString(FString & String) const } bool -FHoudiniEngineString::ToFText(FText & Text) const +FHoudiniEngineString::ToFText(FText& Text) const { Text = FText::GetEmpty(); FString NameString = TEXT(""); @@ -182,4 +182,104 @@ FHoudiniEngineString::ToFText(const int32& InStringId, FText& OutText) { FHoudiniEngineString HAPIString(InStringId); return HAPIString.ToFText(OutText); +} + +bool +FHoudiniEngineString::SHArrayToFStringArray(const TArray& InStringIdArray, TArray& OutStringArray) +{ + if (SHArrayToFStringArray_Batch(InStringIdArray, OutStringArray)) + return true; + + return SHArrayToFStringArray_Singles(InStringIdArray, OutStringArray); +} + +bool +FHoudiniEngineString::SHArrayToFStringArray_Batch(const TArray& InStringIdArray, TArray& OutStringArray) +{ + bool bReturn = true; + OutStringArray.SetNumZeroed(InStringIdArray.Num()); + + TArray UniqueSH; + for (const auto& CurrentSH : InStringIdArray) + { + UniqueSH.AddUnique(CurrentSH); + } + + int32 BufferSize = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStringBatchSize( + FHoudiniEngine::Get().GetSession(), UniqueSH.GetData(), UniqueSH.Num(), &BufferSize)) + return false; + + if (BufferSize <= 0) + return false; + + std::vector Buffer(BufferSize, '\0'); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStringBatch( + FHoudiniEngine::Get().GetSession(), &Buffer[0], BufferSize)) + return false; + + // Parse the buffer to a string array + TArray ConvertedString; + std::vector::iterator CurrentBegin = Buffer.begin(); + for (std::vector::iterator it = Buffer.begin(); it != Buffer.end(); it++) + { + if (*it != '\0') + continue; + + std::string stdString = std::string(CurrentBegin, it); + ConvertedString.Add(UTF8_TO_TCHAR(stdString.c_str())); + + CurrentBegin = it; + CurrentBegin++; + } + + if (ConvertedString.Num() != UniqueSH.Num()) + return false; + + // Build a map to map string handles to indices + TMap SHToIndexMap; + for (int32 Idx = 0; Idx < UniqueSH.Num(); Idx++) + SHToIndexMap.Add(UniqueSH[Idx], Idx); + + // Fill the output array using the map + for (int32 IdxSH = 0; IdxSH < InStringIdArray.Num(); IdxSH++) + { + const int32* FoundIndex = SHToIndexMap.Find(InStringIdArray[IdxSH]); + if (!FoundIndex || !ConvertedString.IsValidIndex(*FoundIndex)) + return false; + + // Already resolved earlier, copy the string instead of calling HAPI. + OutStringArray[IdxSH] = ConvertedString[*FoundIndex]; + } + + return true; +} +bool +FHoudiniEngineString::SHArrayToFStringArray_Singles(const TArray& InStringIdArray, TArray& OutStringArray) +{ + bool bReturn = true; + OutStringArray.SetNumZeroed(InStringIdArray.Num()); + + // Avoid calling HAPI to resolve the same strings again and again + TMap ResolvedStrings; + for (int32 IdxSH = 0; IdxSH < InStringIdArray.Num(); IdxSH++) + { + const int32* ResolvedString = ResolvedStrings.Find(InStringIdArray[IdxSH]); + if (ResolvedString) + { + // Already resolved earlier, copy the string instead of calling HAPI. + OutStringArray[IdxSH] = OutStringArray[*ResolvedString]; + } + else + { + FString CurrentString = FString(); + if(!FHoudiniEngineString::ToFString(InStringIdArray[IdxSH], CurrentString)) + bReturn = false; + + OutStringArray[IdxSH] = CurrentString; + ResolvedStrings.Add(InStringIdArray[IdxSH], IdxSH); + } + } + + return bReturn; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineString.h b/Source/HoudiniEngine/Private/HoudiniEngineString.h index 7d443fd6e..c6872eda5 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineString.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineString.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,12 +26,13 @@ #pragma once +#include +#include "HoudiniApi.h" + class FText; class FString; class FName; -#include - class HOUDINIENGINE_API FHoudiniEngineString { public: @@ -57,6 +58,15 @@ class HOUDINIENGINE_API FHoudiniEngineString static bool ToFString(const int32& InStringId, FString & String); static bool ToFText(const int32& InStringId, FText & Text); + // Array converter, uses a map to avoid redudant calls to HAPI + static bool SHArrayToFStringArray(const TArray& InStringIdArray, TArray& OutStringArray); + + // Array converter, uses string batches and a map to reduce HAPI calls + static bool SHArrayToFStringArray_Batch(const TArray& InStringIdArray, TArray& OutStringArray); + + // Array converter, uses a map to reduce HAPI calls + static bool SHArrayToFStringArray_Singles(const TArray& InStringIdArray, TArray& OutStringArray); + // Return id of this string. int32 GetId() const; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp b/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp index 6bd564493..4202afc28 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,10 +32,13 @@ FHoudiniEngineTask::FHoudiniEngineTask() : TaskType(EHoudiniEngineTaskType::None) , ActorName(TEXT("")) , AssetId(-1) + , bUseOutputNodes(false) + , bOutputTemplateGeos(false) , AssetLibraryId(-1) , AssetHapiName(-1) { HapiGUID.Invalidate(); + OtherNodeIds.Empty(); } FHoudiniEngineTask::FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID) @@ -43,6 +46,10 @@ FHoudiniEngineTask::FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid , TaskType(InTaskType) , ActorName(TEXT("")) , AssetId(-1) + , bUseOutputNodes(false) + , bOutputTemplateGeos(false) , AssetLibraryId(-1) , AssetHapiName(-1) -{} +{ + OtherNodeIds.Empty(); +} diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTask.h b/Source/HoudiniEngine/Private/HoudiniEngineTask.h index 60916a342..b3d5182a8 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTask.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineTask.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -89,6 +89,15 @@ struct HOUDINIENGINE_API FHoudiniEngineTask // Asset Id. HAPI_NodeId AssetId; + // Additional Node Id for the task + // Can be used to apply a task to multiple nodes in the same HDA + TArray OtherNodeIds; + // Cook results for each output node. + TMap CookResults; + + bool bUseOutputNodes; + bool bOutputTemplateGeos; + // Library Id. HAPI_AssetLibraryId AssetLibraryId; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp index 14e3283c1..f104eacde 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h index 31d11e127..c5831d8a6 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp index 61764f81d..70d6d04ae 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,6 +51,7 @@ #include "HoudiniAssetComponent.h" #include "HoudiniParameter.h" #include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniEngineRuntime.h" #if WITH_EDITOR #include "SAssetSelectionWidget.h" @@ -281,6 +282,31 @@ FHoudiniEngineUtils::GetStatusString(HAPI_StatusType status_type, HAPI_StatusVer return FString(TEXT("")); } +FString +FHoudiniEngineUtils::HapiGetString(int32 StringHandle) +{ + int32 StringLength = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStringBufLength( + FHoudiniEngine::Get().GetSession(), StringHandle, &StringLength)) + { + return FString(); + } + + if (StringLength <= 0) + return FString(); + + std::vector NameBuffer(StringLength, '\0'); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetString( + FHoudiniEngine::Get().GetSession(), + StringHandle, &NameBuffer[0], StringLength ) ) + { + return FString(); + } + + return FString(std::string(NameBuffer.begin(), NameBuffer.end()).c_str()); +} + + const FString FHoudiniEngineUtils::GetCookResult() { @@ -299,6 +325,22 @@ FHoudiniEngineUtils::GetErrorDescription() return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_CALL_RESULT, HAPI_STATUSVERBOSITY_ERRORS); } +const FString +FHoudiniEngineUtils::GetConnectionError() +{ + int32 ErrorLength = 0; + FHoudiniApi::GetConnectionErrorLength(&ErrorLength); + + if(ErrorLength <= 0) + return FString(TEXT("")); + + TArray ConnectionStringBuffer; + ConnectionStringBuffer.SetNumZeroed(ErrorLength); + FHoudiniApi::GetConnectionError(&ConnectionStringBuffer[0], ErrorLength, true); + + return FString(UTF8_TO_TCHAR(&ConnectionStringBuffer[0])); +} + const FString FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId) { @@ -347,7 +389,7 @@ FHoudiniEngineUtils::GetCookLog(TArray& InHACs) // Iterates on all the selected HAC and get their node errors for (auto& HAC : InHACs) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) continue; // Get the node errors, warnings and messages @@ -410,6 +452,9 @@ FHoudiniEngineUtils::GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent) if (!FHoudiniEngineString::ToFString(AssetInfo.helpTextSH, HelpString)) return HelpString; + if (!FHoudiniEngineString::ToFString(AssetInfo.helpURLSH, HelpString)) + return HelpString; + if (HelpString.IsEmpty()) HelpString = TEXT("No Asset Help Found"); @@ -434,20 +479,18 @@ FHoudiniEngineUtils::FindWorldInPackage(const FString& PackagePath, bool bCreate UPackage* Package = FindPackage(nullptr, *PackagePath); if (!Package) Package = LoadPackage(nullptr, *PackagePath, LOAD_None); - if (Package) + + if (IsValid(Package)) + { + PackageWorld = UWorld::FindWorldInPackage(Package); + } + else if (Package != nullptr) { // If the package is not valid (pending kill) rename it - if (Package->IsPendingKill()) - { - if (bCreateMissingPackage) - { - Package->Rename( - *MakeUniqueObjectName(Package->GetOuter(), Package->GetClass(), FName(PackagePath + TEXT("_pending_kill"))).ToString()); - } - } - else + if (bCreateMissingPackage) { - PackageWorld = UWorld::FindWorldInPackage(Package); + Package->Rename( + *MakeUniqueObjectName(Package->GetOuter(), Package->GetClass(), FName(PackagePath + TEXT("_pending_kill"))).ToString()); } } @@ -478,7 +521,8 @@ FHoudiniEngineUtils::FindWorldInPackage(const FString& PackagePath, bool bCreate return PackageWorld; } -bool FHoudiniEngineUtils::FindWorldAndLevelForSpawning( +bool +FHoudiniEngineUtils::FindWorldAndLevelForSpawning( UWorld* CurrentWorld, const FString& PackagePath, bool bCreateMissingPackage, @@ -516,7 +560,8 @@ bool FHoudiniEngineUtils::FindWorldAndLevelForSpawning( return true; } -void FHoudiniEngineUtils::RescanWorldPath(UWorld* InWorld) +void +FHoudiniEngineUtils::RescanWorldPath(UWorld* InWorld) { FString WorldPath = FPaths::GetPath(InWorld->GetPathName()); IAssetRegistry& AssetRegistry = FAssetRegistryModule::GetRegistry(); @@ -525,34 +570,43 @@ void FHoudiniEngineUtils::RescanWorldPath(UWorld* InWorld) AssetRegistry.ScanPathsSynchronous(Packages, true); } -AActor* FHoudiniEngineUtils::FindOrRenameInvalidActorGeneric(UClass* InClass, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) +AActor* +FHoudiniEngineUtils::FindOrRenameInvalidActorGeneric(UClass* InClass, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) { // AActor* NamedActor = FindObject(Outer, *InName, false); // Find ANY actor in the world matching the given name. AActor* NamedActor = FindActorInWorld(InWorld, FName(InName)); OutFoundActor = NamedActor; - bool bShouldRename = false; - if (NamedActor) + + FString Suffix; + if (IsValid(NamedActor)) { - if (NamedActor->GetClass()->IsChildOf(InClass) && !NamedActor->IsPendingKill()) + if (NamedActor->GetClass()->IsChildOf(InClass)) { return NamedActor; } else { - FString Suffix; - bool bShouldUpdateLabel = false; - if (NamedActor->IsPendingKill()) - Suffix = "_pendingkill"; - else - Suffix = "_0"; // A previous actor that had the same name. - const FName NewName = FHoudiniEngineUtils::RenameToUniqueActor(NamedActor, InName+Suffix); + // A previous actor that had the same name. + Suffix = "_0"; } } + else + { + if (!NamedActor) + return nullptr; + else + Suffix = "_pendingkill"; + } + + // Rename the invalid/previous actor + const FName NewName = FHoudiniEngineUtils::RenameToUniqueActor(NamedActor, InName + Suffix); + return nullptr; } -void FHoudiniEngineUtils::LogPackageInfo(const FString& InLongPackageName) +void +FHoudiniEngineUtils::LogPackageInfo(const FString& InLongPackageName) { LogPackageInfo( LoadPackage(nullptr, *InLongPackageName, 0) ); } @@ -569,7 +623,7 @@ void FHoudiniEngineUtils::LogPackageInfo(const UPackage* InPackage) } HOUDINI_LOG_MESSAGE(TEXT(" = Filename: %s"), *(InPackage->FileName.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = Package Id: %d"), InPackage->GetPackageId().ToIndexForDebugging()); + HOUDINI_LOG_MESSAGE(TEXT(" = Package Id: %d"), InPackage->GetPackageId().ValueForDebugging()); HOUDINI_LOG_MESSAGE(TEXT(" = File size: %d"), InPackage->FileSize); HOUDINI_LOG_MESSAGE(TEXT(" = Contains map: %d"), InPackage->ContainsMap()); HOUDINI_LOG_MESSAGE(TEXT(" = Is Fully Loaded: %d"), InPackage->IsFullyLoaded()); @@ -592,7 +646,8 @@ void FHoudiniEngineUtils::LogPackageInfo(const UPackage* InPackage) HOUDINI_LOG_MESSAGE(DebugTextLine); } -void FHoudiniEngineUtils::LogWorldInfo(const FString& InLongPackageName) +void +FHoudiniEngineUtils::LogWorldInfo(const FString& InLongPackageName) { UPackage* Package = LoadPackage(nullptr, *InLongPackageName, 0); UWorld* World = nullptr; @@ -605,7 +660,8 @@ void FHoudiniEngineUtils::LogWorldInfo(const FString& InLongPackageName) LogWorldInfo(World); } -void FHoudiniEngineUtils::LogWorldInfo(const UWorld* InWorld) +void +FHoudiniEngineUtils::LogWorldInfo(const UWorld* InWorld) { HOUDINI_LOG_MESSAGE(DebugTextLine); @@ -699,25 +755,25 @@ FHoudiniEngineUtils::HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventT case HAPI_PDG_EVENT_NODE_DISCONNECT: return TEXT("HAPI_PDG_EVENT_NODE_DISCONNECT"); - case HAPI_PDG_EVENT_WORKITEM_SET_INT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_INT"); + case HAPI_PDG_EVENT_WORKITEM_SET_INT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_INT"); // DEPRECATED case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FLOAT"); + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FLOAT"); // DEPRECATED case HAPI_PDG_EVENT_WORKITEM_SET_STRING: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_STRING"); + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_STRING"); // DEPRECATED case HAPI_PDG_EVENT_WORKITEM_SET_FILE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FILE"); + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FILE"); // DEPRECATED case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT"); + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT"); // DEPRECATED case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY"); + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY"); // DEPRECATED case HAPI_PDG_EVENT_WORKITEM_MERGE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_MERGE"); + return TEXT("HAPI_PDG_EVENT_WORKITEM_MERGE"); // DEPRECATED case HAPI_PDG_EVENT_WORKITEM_RESULT: return TEXT("HAPI_PDG_EVENT_WORKITEM_RESULT"); - case HAPI_PDG_EVENT_WORKITEM_PRIORITY: - return TEXT("HAPI_PDG_EVENT_WORKITEM_PRIORITY"); + case HAPI_PDG_EVENT_WORKITEM_PRIORITY: // DEPRECATED + return TEXT("HAPI_PDG_EVENT_WORKITEM_PRIORITY"); case HAPI_PDG_EVENT_COOK_START: return TEXT("HAPI_PDG_EVENT_COOK_START"); @@ -789,32 +845,62 @@ FHoudiniEngineUtils::HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& return FString::Printf(TEXT("Unknown HAPI_PDG_WorkitemState %d"), InWorkitemState); } + +// Centralized call to track renaming of objects +bool FHoudiniEngineUtils::RenameObject(UObject* Object, const TCHAR* NewName /*= nullptr*/, UObject* NewOuter /*= nullptr*/, ERenameFlags Flags /*= REN_None*/) +{ + check(Object); + if (AActor* Actor = Cast(Object)) + { + if (Actor->IsPackageExternal()) + { + // There should be no need to choose a specific name for an actor in Houdini Engine, instead setting its label should be enough. + if (FHoudiniEngineRuntimeUtils::SetActorLabel(Actor, NewName)) + { + HOUDINI_LOG_WARNING(TEXT("Called SetActorLabel(%s) on external actor %s instead of Rename : Explicit naming of an actor that is saved in its own external package is prone to cause name clashes when submitting the file.)"), NewName, *Actor->GetName()); + } + // Force to return false (make sure nothing in Houdini Engine plugin relies on actor being renamed to provided name) + return false; + } + } + return Object->Rename(NewName, NewOuter, Flags); +} + FName FHoudiniEngineUtils::RenameToUniqueActor(AActor* InActor, const FString& InName) { const FName NewName = MakeUniqueObjectName(InActor->GetOuter(), InActor->GetClass(), FName(InName)); - InActor->Rename( *(NewName.ToString()) ); - // TODO: Can we set actor label when actor is pending kill? - InActor->SetActorLabel(NewName.ToString()); + + FHoudiniEngineUtils::RenameObject(InActor, *(NewName.ToString())); + FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, NewName.ToString()); + return NewName; } -UObject* FHoudiniEngineUtils::SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel) +UObject* +FHoudiniEngineUtils::SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel) { check(InActor); - + UObject* PrevObj = nullptr; UObject* ExistingObject = StaticFindObject(/*Class=*/ NULL, InActor->GetOuter(), *InName, true); if (ExistingObject && ExistingObject != InActor) { // Rename the existing object - const FName NewName = MakeUniqueObjectName(ExistingObject->GetOuter(), ExistingObject->GetClass(), FName(InName+TEXT("_old")) ); - ExistingObject->Rename(*(NewName.ToString())); + const FName NewName = MakeUniqueObjectName(ExistingObject->GetOuter(), ExistingObject->GetClass(), FName(InName + TEXT("_old"))); + FHoudiniEngineUtils::RenameObject(ExistingObject, *(NewName.ToString())); PrevObj = ExistingObject; } - InActor->Rename(*InName); + + FHoudiniEngineUtils::RenameObject(InActor, *InName); + if (UpdateLabel) - InActor->SetActorLabel(InName, true); + { + //InActor->SetActorLabel(InName, true); + FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, InName); + InActor->Modify(true); + } + return PrevObj; } @@ -825,7 +911,9 @@ FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( const FString &BakeFolder, const FString &ObjectName, const FString &HoudiniAssetName, - EPackageReplaceMode InReplaceMode) + const FString &HoudiniAssetActorName, + EPackageReplaceMode InReplaceMode, + bool bAutomaticallySetAttemptToLoadMissingPackages) { OutPackageParams.GeoId = InIdentifier.GeoId; OutPackageParams.ObjectId = InIdentifier.ObjectId; @@ -834,10 +922,127 @@ FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( OutPackageParams.PackageMode = EPackageMode::Bake; OutPackageParams.ReplaceMode = InReplaceMode; OutPackageParams.HoudiniAssetName = HoudiniAssetName; - OutPackageParams.HoudiniAssetActorName = HoudiniAssetName; + OutPackageParams.HoudiniAssetActorName = HoudiniAssetActorName; OutPackageParams.ObjectName = ObjectName; } +void +FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + UWorld* const InWorldContext, + const UHoudiniAssetComponent* HoudiniAssetComponent, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FHoudiniOutputObject& InOutputObject, + const FString &InDefaultObjectName, + FHoudiniPackageParams& OutPackageParams, + FHoudiniAttributeResolver& OutResolver, + const FString &InDefaultBakeFolder, + EPackageReplaceMode InReplaceMode, + const FString &InHoudiniAssetName, + const FString &InHoudiniAssetActorName, + bool bAutomaticallySetAttemptToLoadMissingPackages, + bool bInSkipObjectNameResolutionAndUseDefault, + bool bInSkipBakeFolderResolutionAndUseDefault) +{ + // Configure OutPackageParams with the default (UI value first then fallback to default from settings) object name + // and bake folder. We use the "initial" PackageParams as a helper to populate tokens for the resolver. + // + // User specified attributes (eg unreal_bake_folder) are then resolved, with the defaults being those tokens configured + // from the initial PackageParams. Once resolved, we updated the relevant fields in PackageParams + // (ObjectName and BakeFolder), and update the resolver tokens with these final values. + // + // The resolver is then ready to be used to resolve the rest of the user attributes, such as unreal_level_path. + // + const FString DefaultBakeFolder = !InDefaultBakeFolder.IsEmpty() ? InDefaultBakeFolder : + FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + // If InHoudiniAssetName was specified, use that, otherwise use the name of the UHoudiniAsset used by the + // HoudiniAssetComponent + FString HoudiniAssetName(TEXT("")); + if (!InHoudiniAssetName.IsEmpty()) + { + HoudiniAssetName = InHoudiniAssetName; + } + else if (IsValid(HoudiniAssetComponent) && IsValid(HoudiniAssetComponent->GetHoudiniAsset())) + { + HoudiniAssetName = HoudiniAssetComponent->GetHoudiniAsset()->GetName(); + } + + // If InHoudiniAssetActorName was specified, use that, otherwise use the name of the owner of HoudiniAssetComponent + FString HoudiniAssetActorName(TEXT("")); + if (!InHoudiniAssetActorName.IsEmpty()) + { + HoudiniAssetActorName = InHoudiniAssetActorName; + } + else if (IsValid(HoudiniAssetComponent) && IsValid(HoudiniAssetComponent->GetOwner())) + { + HoudiniAssetActorName = HoudiniAssetComponent->GetOwner()->GetName(); + } + + const bool bHasBakeNameUIOverride = !InOutputObject.BakeName.IsEmpty(); + FillInPackageParamsForBakingOutput( + OutPackageParams, + InIdentifier, + DefaultBakeFolder, + bHasBakeNameUIOverride ? InOutputObject.BakeName : InDefaultObjectName, + HoudiniAssetName, + HoudiniAssetActorName, + InReplaceMode, + bAutomaticallySetAttemptToLoadMissingPackages); + + const TMap& CachedAttributes = InOutputObject.CachedAttributes; + TMap Tokens = InOutputObject.CachedTokens; + OutPackageParams.UpdateTokensFromParams(InWorldContext, HoudiniAssetComponent, Tokens); + OutResolver.SetCachedAttributes(CachedAttributes); + OutResolver.SetTokensFromStringMap(Tokens); + +#if defined(HOUDINI_ENGINE_DEBUG_BAKING) && HOUDINI_ENGINE_DEBUG_BAKING + // Log the cached attributes and tokens for debugging + OutResolver.LogCachedAttributesAndTokens(); +#endif + + if (!bInSkipObjectNameResolutionAndUseDefault) + { + // Resolve the object name + // TODO: currently the UI override is checked first (this should probably change so that attributes are used first) + FString ObjectName; + if (bHasBakeNameUIOverride) + { + ObjectName = InOutputObject.BakeName; + } + else + { + constexpr bool bForBake = true; + ObjectName = OutResolver.ResolveOutputName(bForBake); + if (ObjectName.IsEmpty()) + ObjectName = InDefaultObjectName; + } + // Update the object name in the package params and also update its token + OutPackageParams.ObjectName = ObjectName; + OutResolver.SetToken("object_name", OutPackageParams.ObjectName); + } + + if (!bInSkipBakeFolderResolutionAndUseDefault) + { + // Now resolve the bake folder + const FString BakeFolder = OutResolver.ResolveBakeFolder(); + if (!BakeFolder.IsEmpty()) + OutPackageParams.BakeFolder = BakeFolder; + } + + if (!bInSkipObjectNameResolutionAndUseDefault || !bInSkipBakeFolderResolutionAndUseDefault) + { + // Update the tokens from the package params + OutPackageParams.UpdateTokensFromParams(InWorldContext, HoudiniAssetComponent, Tokens); + OutResolver.SetTokensFromStringMap(Tokens); + +#if defined(HOUDINI_ENGINE_DEBUG_BAKING) && HOUDINI_ENGINE_DEBUG_BAKING + // Log the final tokens + OutResolver.LogCachedAttributesAndTokens(); +#endif + } +} + + bool FHoudiniEngineUtils::RepopulateFoliageTypeListInUI() { @@ -854,20 +1059,84 @@ FHoudiniEngineUtils::RepopulateFoliageTypeListInUI() return false; } -bool -FHoudiniEngineUtils::IsOuterHoudiniAssetComponent(UObject* Obj) +void +FHoudiniEngineUtils::GatherLandscapeInputs( + UHoudiniAssetComponent* HAC, + TArray& AllInputLandscapes, + TArray& InputLandscapesToUpdate) { - if (!Obj) - return false; - return Obj->GetOuter() && Obj->GetOuter()->IsA(); + if (!IsValid(HAC)) + return; + + int32 NumInputs = HAC->GetNumInputs(); + + for (int32 InputIndex = 0; InputIndex < NumInputs; InputIndex++ ) + { + UHoudiniInput* CurrentInput = HAC->GetInputAt(InputIndex); + if (!CurrentInput) + continue; + + if (CurrentInput->GetInputType() == EHoudiniInputType::World) + { + // Check if we have any landscapes as world inputs. + CurrentInput->ForAllHoudiniInputObjects([&AllInputLandscapes](UHoudiniInputObject* InputObject) + { + UHoudiniInputLandscape* InputLandscape = Cast(InputObject); + if (InputLandscape) + { + ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); + if (IsValid(LandscapeProxy)) + { + AllInputLandscapes.Add(LandscapeProxy); + } + } + }, true); + } + + if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) + continue; + + // Get the landscape input's landscape + ALandscapeProxy* InputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); + if (!InputLandscape) + continue; + + AllInputLandscapes.Add(InputLandscape); + + if (CurrentInput->GetUpdateInputLandscape()) + { + InputLandscapesToUpdate.Add(InputLandscape); + } + } } + UHoudiniAssetComponent* -FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(UObject* Obj) +FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(const UObject* Obj) { - return Cast(Obj->GetOuter()); + if (!IsValid(Obj)) + return nullptr; + + // Check the direct Outer + UHoudiniAssetComponent* OuterHAC = Cast(Obj->GetOuter()); + if(IsValid(OuterHAC)) + return OuterHAC; + + // Check the whole outer chain + OuterHAC = Obj->GetTypedOuter(); + if (IsValid(OuterHAC)) + return OuterHAC; + + // Finally check if the Object itself is a HaC + UObject* NonConstObj = const_cast(Obj); + OuterHAC = Cast(NonConstObj); + if (IsValid(OuterHAC)) + return OuterHAC; + + return nullptr; } + FString FHoudiniEngineUtils::ComputeVersionString(bool ExtraDigit) { @@ -884,6 +1153,7 @@ FHoudiniEngineUtils::ComputeVersionString(bool ExtraDigit) return HoudiniVersionString; } + void * FHoudiniEngineUtils::LoadLibHAPI(FString & StoredLibHAPILocation) { @@ -1091,7 +1361,10 @@ FHoudiniEngineUtils::IsInitialized() if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) return false; - return (FHoudiniApi::IsInitialized(SessionPtr) == HAPI_RESULT_SUCCESS); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) + return false; + + return true; } bool @@ -1192,15 +1465,25 @@ FHoudiniEngineUtils::LocateLibHAPIInRegistry( #endif bool -FHoudiniEngineUtils::LoadHoudiniAsset(UHoudiniAsset * HoudiniAsset, HAPI_AssetLibraryId & OutAssetLibraryId) +FHoudiniEngineUtils::LoadHoudiniAsset(const UHoudiniAsset * HoudiniAsset, HAPI_AssetLibraryId& OutAssetLibraryId) { OutAssetLibraryId = -1; - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + if (!IsValid(HoudiniAsset)) return false; if (!FHoudiniEngineUtils::IsInitialized()) + { + // If we're not initialized now, it likely means the session has been lost + FHoudiniEngine::Get().OnSessionLost(); return false; + } + + // Get the preferences + bool bMemoryCopyFirst = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bMemoryCopyFirst = HoudiniRuntimeSettings->bPreferHdaMemoryCopyOverHdaSourceFile; // Get the HDA's file path // We need to convert relative file path to absolute @@ -1216,54 +1499,135 @@ FHoudiniEngineUtils::LoadHoudiniAsset(UHoudiniAsset * HoudiniAsset, HAPI_AssetLi AssetFileName = FPaths::GetPath(AssetFileName); } - // If the hda file exists, we can simply load it directly the file - HAPI_Result Result = HAPI_RESULT_FAILURE; + //Check whether we can Load from file/memory + bool bCanLoadFromMemory = (!HoudiniAsset->IsExpandedHDA() && HoudiniAsset->GetAssetBytesCount() > 0); + + // If the hda file exists, we can simply load it directly + bool bCanLoadFromFile = false; if ( !AssetFileName.IsEmpty() ) { - if ( FPaths::FileExists(AssetFileName) - || (HoudiniAsset->IsExpandedHDA() && FPaths::DirectoryExists(AssetFileName) ) ) + if (FPaths::FileExists(AssetFileName) + || (HoudiniAsset->IsExpandedHDA() && FPaths::DirectoryExists(AssetFileName))) { - // Load the asset from file. - std::string AssetFileNamePlain; - FHoudiniEngineUtils::ConvertUnrealString(AssetFileName, AssetFileNamePlain); - Result = FHoudiniApi::LoadAssetLibraryFromFile( - FHoudiniEngine::Get().GetSession(), AssetFileNamePlain.c_str(), true, &OutAssetLibraryId); + bCanLoadFromFile = true; } } - // Detect license issues - // HoudiniEngine aquires a license when creating/loading a node, not when creating a session - if (Result >= HAPI_RESULT_NO_LICENSE_FOUND && Result < HAPI_RESULT_ASSET_INVALID) + HAPI_Result Result = HAPI_RESULT_FAILURE; + + // Lambda to detect license issues + auto CheckLicenseValid = [&AssetFileName](const HAPI_Result& Result) { - FString ErrorDesc = GetErrorDescription(Result); - HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: License failed: %s."), *AssetFileName, *ErrorDesc); + // HoudiniEngine acquires a license when creating/loading a node, not when creating a session + if (Result >= HAPI_RESULT_NO_LICENSE_FOUND && Result < HAPI_RESULT_ASSET_INVALID) + { + FString ErrorDesc = GetErrorDescription(Result); + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: License failed: %s."), *AssetFileName, *ErrorDesc); - // We must stop the session to prevent further attempts at loading an HDA - // as this could lead to unreal becoming stuck and unresponsive due to license timeout - FHoudiniEngine::Get().StopSession(); + // We must stop the session to prevent further attempts at loading an HDA + // as this could lead to unreal becoming stuck and unresponsive due to license timeout + FHoudiniEngine::Get().StopSession(); - return false; - } + // Set the HE status to "no license" + FHoudiniEngine::Get().SetSessionStatus(EHoudiniSessionStatus::NoLicense); - // If loading from file failed, try to load using the memory copy - if (Result != HAPI_RESULT_SUCCESS) - { - // Expanded hdas cannot be loaded from Memory - if (HoudiniAsset->IsExpandedHDA() || HoudiniAsset->GetAssetBytesCount() <= 0) - { - HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); return false; } else { - // Warn the user that we are loading from memory + return true; + } + }; + + // Lambda to load an HDA from file + auto LoadAssetFromFile = [&Result, &OutAssetLibraryId](const FString& InAssetFileName) + { + // Load the asset from file. + std::string AssetFileNamePlain; + FHoudiniEngineUtils::ConvertUnrealString(InAssetFileName, AssetFileNamePlain); + Result = FHoudiniApi::LoadAssetLibraryFromFile( + FHoudiniEngine::Get().GetSession(), AssetFileNamePlain.c_str(), true, &OutAssetLibraryId); + + }; + + // Lambda to load an HDA from memory + auto LoadAssetFromMemory = [&Result, &OutAssetLibraryId](const UHoudiniAsset* InHoudiniAsset) + { + // Load the asset from the cached memory buffer + Result = FHoudiniApi::LoadAssetLibraryFromMemory( + FHoudiniEngine::Get().GetSession(), + reinterpret_cast(InHoudiniAsset->GetAssetBytes()), + InHoudiniAsset->GetAssetBytesCount(), + true, + &OutAssetLibraryId); + }; + + if (!bMemoryCopyFirst) + { + // Load from File first + if (bCanLoadFromFile) + { + LoadAssetFromFile(AssetFileName); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + + // If we failed to load from file ... + if (Result != HAPI_RESULT_SUCCESS) + { + // ... warn the user that we will be loading from memory. HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from Memory: source asset file not found."), *AssetFileName); - // Otherwise we will try to load from buffer we've cached. - Result = FHoudiniApi::LoadAssetLibraryFromMemory( - FHoudiniEngine::Get().GetSession(), - reinterpret_cast(HoudiniAsset->GetAssetBytes()), - HoudiniAsset->GetAssetBytesCount(), true, &OutAssetLibraryId); + // Attempt to load from memory + if (bCanLoadFromMemory) + { + LoadAssetFromMemory(HoudiniAsset); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); + return false; + } + } + } + else + { + // Load from Memory first + if(bCanLoadFromMemory) + { + LoadAssetFromMemory(HoudiniAsset); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + + // If we failed to load from memory ... + if (Result != HAPI_RESULT_SUCCESS) + { + // ... warn the user that we will be loading from file + HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from File: no memory copy available."), *AssetFileName); + + // Attempt to load from file + if (bCanLoadFromFile) + { + LoadAssetFromFile(AssetFileName); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); + return false; + } } } @@ -1426,6 +1790,29 @@ FHoudiniEngineUtils::GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char return true; } +bool +FHoudiniEngineUtils::HapiGetAbsNodePath(const HAPI_NodeId& InNodeId, FString& OutPath) +{ + // Retrieve Path to the given Node, relative to the other given Node + if (InNodeId < 0) + return false; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InNodeId)) + return false; + + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodePath( + FHoudiniEngine::Get().GetSession(), + InNodeId, -1, &StringHandle)) + { + if(FHoudiniEngineString::ToFString(StringHandle, OutPath)) + { + return true; + } + } + return false; +} + bool FHoudiniEngineUtils::HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath) @@ -1500,7 +1887,7 @@ FHoudiniEngineUtils::HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FStrin bool -FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos) +FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos, TArray& OutObjectTransforms) { HAPI_NodeInfo NodeInfo; FHoudiniApi::NodeInfo_Init(&NodeInfo); @@ -1518,6 +1905,16 @@ FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( + FHoudiniEngine::Get().GetSession(), InNodeId, nullptr, &ObjectCount), false); + + // Increment the object count by one if we should add ourself + OutObjectInfos.SetNumUninitialized(bAddSelf ? ObjectCount + 1 : ObjectCount); + OutObjectTransforms.SetNumUninitialized(bAddSelf ? ObjectCount + 1 : ObjectCount); for (int32 Idx = 0; Idx < OutObjectInfos.Num(); Idx++) - FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); + { + FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[Idx])); + FHoudiniApi::Transform_Init(&(OutObjectTransforms[Idx])); + } + // Get our object info in 0 if needed + if (bAddSelf) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, + &OutObjectInfos[0]), false); + + // Use the identity transform + OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; + OutObjectTransforms[0].scale[0] = 1.0f; + OutObjectTransforms[0].scale[1] = 1.0f; + OutObjectTransforms[0].scale[2] = 1.0f; + OutObjectTransforms[0].rstOrder = HAPI_SRT; + } + + // Get the other object infos HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectList( FHoudiniEngine::Get().GetSession(), InNodeId, - &OutObjectInfos[0], 0, ObjectCount), false); + &OutObjectInfos[bAddSelf ? 1 : 0], 0, ObjectCount), false); + + // Get the composed object transforms for the others (1 - Count) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectTransforms( + FHoudiniEngine::Get().GetSession(), + InNodeId, HAPI_SRT, &OutObjectTransforms[bAddSelf ? 1 : 0], 0, ObjectCount), false); } } else @@ -1551,52 +1997,497 @@ FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& AllObjectIds, const HAPI_NodeId& InRootNodeId, const HAPI_NodeId& InChildNodeId) +{ + // Walk up the hierarchy from child to root. + // If any node in that hierarchy is not in the "AllObjectIds" set, the OBJ node is considered to + // be hidden. + + if (InChildNodeId == InRootNodeId) + return true; + + HAPI_NodeId ChildNodeId = InChildNodeId; + + HAPI_ObjectInfo ChildObjInfo; + HAPI_NodeInfo ChildNodeInfo; + + FHoudiniApi::ObjectInfo_Init(&ChildObjInfo); + FHoudiniApi::NodeInfo_Init(&ChildNodeInfo); + + do + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), + ChildNodeId, &ChildObjInfo)) + { + // If can't get info for this object, we can't say whether it's visible (or not). + return false; + } + + if (!ChildObjInfo.isVisible || ChildObjInfo.nodeId < 0) + { + // We have an object in the chain that is not visible. Return false immediately! + return false; + } + + if (ChildNodeId != InChildNodeId) + { + // Only perform this check for 'parents' of the incoming child node + if ( !AllObjectIds.Contains(ChildNodeId)) + { + // There is a non-object node in the hierarchy between the child and asset root, rendering the + // child object node invisible. + return false; + } + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), + ChildNodeId, &ChildNodeInfo)) + { + // Could not retrieve node info. + return false; + } + + // Go up the hierarchy. + ChildNodeId = ChildNodeInfo.parentId; + } while (ChildNodeId != InRootNodeId && ChildNodeId >= 0); + + // We have traversed the whole hierarchy up to the root and nothing indicated that + // we _shouldn't_ be visible. + return true; +} + + bool -FHoudiniEngineUtils::HapiGetObjectTransforms(const HAPI_NodeId& InNodeId, TArray& OutObjectTransforms) +FHoudiniEngineUtils::IsSopNode(const HAPI_NodeId& NodeId) { HAPI_NodeInfo NodeInfo; FHoudiniApi::NodeInfo_Init(&NodeInfo); HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InNodeId,&NodeInfo), false); + FHoudiniEngine::Get().GetSession(), + NodeId, + &NodeInfo + ), + false + ); + return NodeInfo.type == HAPI_NODETYPE_SOP; +} - int32 ObjectCount = 1; - OutObjectTransforms.SetNumUninitialized(1); - FHoudiniApi::Transform_Init(&(OutObjectTransforms[0])); - OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; - OutObjectTransforms[0].scale[0] = 1.0f; - OutObjectTransforms[0].scale[1] = 1.0f; - OutObjectTransforms[0].scale[2] = 1.0f; - OutObjectTransforms[0].rstOrder = HAPI_SRT; +bool FHoudiniEngineUtils::ContainsSopNodes(const HAPI_NodeId& NodeId) +{ + int ChildCount = 0; + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + NodeId, + HAPI_NODETYPE_SOP, + HAPI_NODEFLAGS_ANY, + false, + &ChildCount + ), + false + ); + return ChildCount > 0; +} - if (NodeInfo.type == HAPI_NODETYPE_SOP) +bool FHoudiniEngineUtils::GetOutputIndex(const HAPI_NodeId& InNodeId, int32& OutOutputIndex) +{ + int TempValue = -1; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmIntValue( + FHoudiniEngine::Get().GetSession(), + InNodeId, + TCHAR_TO_ANSI(TEXT("outputidx")), + 0, // index + &TempValue)) { - // Do nothing. Identity transform will be used for the main parent object. + OutOutputIndex = TempValue; + return true; } - else if (NodeInfo.type == HAPI_NODETYPE_OBJ) + + return false; +} + +bool +FHoudiniEngineUtils::GatherAllAssetOutputs( + const HAPI_NodeId& AssetId, + const bool bUseOutputNodes, + const bool bOutputTemplatedGeos, + TArray& OutOutputNodes) +{ + OutOutputNodes.Empty(); + + // Ensure the asset has a valid node ID + if (AssetId < 0) { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( + return false; + } + + // Get the AssetInfo + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + // Get the Asset NodeInfo + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetNodeInfo), false); + + FString CurrentAssetName; + { + FHoudiniEngineString hapiSTR(AssetInfo.nameSH); + hapiSTR.ToFString(CurrentAssetName); + } + + // In certain cases, such as PDG output processing we might end up with a SOP node instead of a + // container. In that case, don't try to run child queries on this node. They will fail. + const bool bAssetHasChildren = !(AssetNodeInfo.type == HAPI_NODETYPE_SOP && AssetNodeInfo.childNodeCount == 0); + + // Retrieve information about each object contained within our asset. + TArray ObjectInfos; + TArray ObjectTransforms; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos, ObjectTransforms)) + return false; + + // Find the editable nodes in the asset. + TArray EditableGeoInfos; + int32 EditableNodeCount = 0; + if (bAssetHasChildren) + { + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, + true, &EditableNodeCount)); + } + + // All editable nodes will be output, regardless + // of whether the subnet is considered visible or not. + if (EditableNodeCount > 0) + { + TArray EditableNodeIds; + EditableNodeIds.SetNumUninitialized(EditableNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( FHoudiniEngine::Get().GetSession(), - InNodeId, nullptr, &ObjectCount), false); + AssetId, EditableNodeIds.GetData(), EditableNodeCount)); - if (ObjectCount <= 0) + for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) { - // Do nothing. Identity transform will be used for the main asset object. + HAPI_GeoInfo CurrentEditableGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); + + // TODO: Check whether this display geo is actually being output + // Just because this is a display node doesn't mean that it will be output (it + // might be in a hidden subnet) + + // Do not process the main display geo twice! + if (CurrentEditableGeoInfo.isDisplayGeo) + continue; + + // We only handle editable curves for now + if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) + continue; + + // Add this geo to the geo info array + EditableGeoInfos.Add(CurrentEditableGeoInfo); + } + } + + const bool bIsSopAsset = AssetInfo.nodeId != AssetInfo.objectNodeId; + bool bUseOutputFromSubnets = true; + if (bAssetHasChildren) + { + if (FHoudiniEngineUtils::ContainsSopNodes(AssetInfo.nodeId)) + { + // This HDA contains immediate SOP nodes. Don't look for subnets to output. + bUseOutputFromSubnets = false; } else { - OutObjectTransforms.SetNumUninitialized(ObjectCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectTransforms( - FHoudiniEngine::Get().GetSession(), - InNodeId, HAPI_SRT, &OutObjectTransforms[0], 0, ObjectCount), false); + // Assume we're using a subnet-based HDA + bUseOutputFromSubnets = true; } } else - return false; + { + // This asset doesn't have any children. Don't try to find subnets. + bUseOutputFromSubnets = false; + } + // Before we can perform visibility checks on the Object nodes, we have + // to build a set of all the Object node ids. The 'AllObjectIds' act + // as a visibility filter. If an Object node is not present in this + // list, the content of that node will not be displayed (display / output / templated nodes). + // NOTE that if the HDA contains immediate SOP nodes we will ignore + // all subnets and only use the data outputs directly from the HDA. + + TSet AllObjectIds; + if (bUseOutputFromSubnets) + { + int NumObjSubnets; + TArray ObjectIds; + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, + HAPI_NODETYPE_OBJ, + HAPI_NODEFLAGS_OBJ_SUBNET, + true, + &NumObjSubnets + ), + false); + + ObjectIds.SetNumUninitialized(NumObjSubnets); + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, + ObjectIds.GetData(), + NumObjSubnets + ), + false); + AllObjectIds.Append(ObjectIds); + } + else + { + AllObjectIds.Add(AssetInfo.objectNodeId); + } + + // Iterate through all objects to determine visibility and + // gather output nodes that needs to be cooked. + int32 OutputIdx = 1; + for (int32 ObjectIdx = 0; ObjectIdx < ObjectInfos.Num(); ObjectIdx++) + { + // Retrieve the object info + const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectIdx]; + + // Determine whether this object node is fully visible. + bool bObjectIsVisible = false; + HAPI_NodeId GatherOutputsNodeId = -1; // Outputs will be gathered from this node. + if (!bAssetHasChildren) + { + // If the asset doesn't have children, we have to gather outputs from the asset's parent in order to output + // this asset node + bObjectIsVisible = true; + GatherOutputsNodeId = AssetNodeInfo.parentId; + } + else if (bIsSopAsset && CurrentHapiObjectInfo.nodeId == AssetInfo.objectNodeId) + { + // When dealing with a SOP asset, be sure to gather outputs from the SOP node, not the + // outer object node. + bObjectIsVisible = true; + GatherOutputsNodeId = AssetInfo.nodeId; + } + else + { + bObjectIsVisible = FHoudiniEngineUtils::IsObjNodeFullyVisible(AllObjectIds, AssetId, CurrentHapiObjectInfo.nodeId); + GatherOutputsNodeId = CurrentHapiObjectInfo.nodeId; + } + + // Build an array of the geos we'll need to process + // In most case, it will only be the display geo, + // but we may also want to process editable geos as well + TArray GeoInfos; + + // These node ids may need to be cooked in order to extract part counts. + TSet ForceNodesToCook; + + // Append the initial set of editable geo infos here + // then clear the editable geo infos array since we + // only want to process them once. + GeoInfos.Append(EditableGeoInfos); + EditableGeoInfos.Empty(); + + if (bObjectIsVisible) + { + // NOTE: The HAPI_GetDisplayGeoInfo will not always return the expected Geometry subnet's + // Display flag geometry. If the Geometry subnet contains an Object subnet somewhere, the + // GetDisplayGeoInfo will sometimes fetch the display SOP from within the subnet which is + // not what we want. + + // Resolve and gather outputs (display / output / template nodes) from the GatherOutputsNodeId. + FHoudiniEngineUtils::GatherImmediateOutputGeoInfos(GatherOutputsNodeId, + bUseOutputNodes, + bOutputTemplatedGeos, + GeoInfos, + ForceNodesToCook); + + } // if (bObjectIsVisible) + + for (const HAPI_NodeId& NodeId : ForceNodesToCook) + { + OutOutputNodes.AddUnique(NodeId); + } + } return true; } +bool FHoudiniEngineUtils::GatherImmediateOutputGeoInfos(const HAPI_NodeId& InNodeId, + const bool bUseOutputNodes, + const bool bGatherTemplateNodes, + TArray& OutGeoInfos, + TSet& OutForceNodesCook +) +{ + TSet GatheredNodeIds; + + // NOTE: This function assumes that the incoming node is a Geometry container that contains immediate + // outputs / display nodes / template nodes. + + // First we look for (immediate) output nodes (if bUseOutputNodes have been enabled). + // If we didn't find an output node, we'll look for a display node. + + bool bHasOutputs = false; + if (bUseOutputNodes) + { + int NumOutputs = -1; + FHoudiniApi::GetOutputGeoCount( + FHoudiniEngine::Get().GetSession(), + InNodeId, + &NumOutputs + ); + + if (NumOutputs > 0) + { + bHasOutputs = true; + + // ------------------------------------------------- + // Extract GeoInfo from the immediate output nodes. + // ------------------------------------------------- + TArray OutputGeoInfos; + OutputGeoInfos.SetNumUninitialized(NumOutputs); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetOutputGeoInfos( + FHoudiniEngine::Get().GetSession(), + InNodeId, + OutputGeoInfos.GetData(), + NumOutputs)) + { + // Gather all the output nodes + for (const HAPI_GeoInfo& OutputGeoInfo : OutputGeoInfos) + { + OutGeoInfos.Add(OutputGeoInfo); + GatheredNodeIds.Add(OutputGeoInfo.nodeId); + OutForceNodesCook.Add(OutputGeoInfo.nodeId); // Ensure this output node gets cooked + } + } + } + } + + if (!bHasOutputs) + { + // If we didn't get any output data, pull our output data directly from the Display node. + + // --------------------------------- + // Look for display nodes. + // --------------------------------- + int DisplayNodeCount = 0; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + InNodeId, + HAPI_NODETYPE_SOP, + HAPI_NODEFLAGS_DISPLAY, + false, + &DisplayNodeCount + )) + { + if (DisplayNodeCount > 0) + { + // Retrieve all the display node ids + TArray DisplayNodeIds; + DisplayNodeIds.SetNumUninitialized(DisplayNodeCount); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + InNodeId, + DisplayNodeIds.GetData(), + DisplayNodeCount + )) + { + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + // Retrieve the Geo Infos for each display node + for(const HAPI_NodeId& DisplayNodeId : DisplayNodeIds) + { + if (GatheredNodeIds.Contains(DisplayNodeId)) + continue; // This node has already been gathered from this subnet. + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + DisplayNodeId, + &GeoInfo) + ) + { + OutGeoInfos.Add(GeoInfo); + GatheredNodeIds.Add(DisplayNodeId); + // If this node doesn't have a part_id count, ensure it gets cooked. + OutForceNodesCook.Add(DisplayNodeId); + } + } + } + } // if (DisplayNodeCount > 0) + } + } // if (!HasOutputNode) + + // Gather templated nodes. + if (bGatherTemplateNodes) + { + int NumTemplateNodes = 0; + // Gather all template nodes + if (HAPI_RESULT_SUCCESS == FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + InNodeId, + HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_TEMPLATED, + false, + &NumTemplateNodes + )) + { + TArray TemplateNodeIds; + TemplateNodeIds.SetNumUninitialized(NumTemplateNodes); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + InNodeId, + TemplateNodeIds.GetData(), + NumTemplateNodes + )) + { + + for(const HAPI_NodeId& TemplateNodeId : TemplateNodeIds) + { + if (GatheredNodeIds.Contains(TemplateNodeId)) + { + continue; // This geometry has already been gathered. + } + + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + TemplateNodeId, + &GeoInfo + ); + // For some reason the return type is always "HAPI_RESULT_INVALID_ARGUMENT", so we + // just check the GeoInfo.type manually. + if (GeoInfo.type != HAPI_GEOTYPE_INVALID) + { + // Add this template node to the gathered outputs. + GatheredNodeIds.Add(TemplateNodeId); + OutGeoInfos.Add(GeoInfo); + // If this node doesn't have a part_id count, ensure it gets cooked. + OutForceNodesCook.Add(TemplateNodeId); + } + } + } + } + } + return true; +} + + bool FHoudiniEngineUtils::HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform) { @@ -1853,7 +2744,7 @@ FHoudiniEngineUtils::HapiGetParentNodeId(const HAPI_NodeId& NodeId) void FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; // TODO: Necessary?? @@ -1925,6 +2816,12 @@ FHoudiniEngineUtils::GetLicenseType(FString & LicenseType) break; } + case HAPI_LICENSE_HOUDINI_ENGINE_UNITY_UNREAL: + { + LicenseType = TEXT("Houdini Engine for Unity/Unreal"); + break; + } + case HAPI_LICENSE_MAX: default: { @@ -2045,7 +2942,7 @@ FHoudiniEngineUtils::UpdateEditorProperties_Internal(TArray ObjectsToU TArray AllSceneComponents; for (auto CurrentObject : ObjectsToUpdate) { - if (!CurrentObject || CurrentObject->IsPendingKill()) + if (!IsValid(CurrentObject)) continue; // In some case, the object itself is the component @@ -2055,7 +2952,7 @@ FHoudiniEngineUtils::UpdateEditorProperties_Internal(TArray ObjectsToU SceneComp = Cast(CurrentObject->GetOuter()); } - if (SceneComp && !SceneComp->IsPendingKill()) + if (IsValid(SceneComp)) { AllSceneComponents.Add(SceneComp); continue; @@ -2065,11 +2962,11 @@ FHoudiniEngineUtils::UpdateEditorProperties_Internal(TArray ObjectsToU TArray AllActors; for (auto CurrentSceneComp : AllSceneComponents) { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill()) + if (!IsValid(CurrentSceneComp)) continue; AActor* Actor = CurrentSceneComp->GetOwner(); - if (Actor && !Actor->IsPendingKill()) + if (IsValid(Actor)) AllActors.Add(Actor); } @@ -2244,10 +3141,10 @@ FHoudiniEngineUtils::SetAttributeStringData( char * FHoudiniEngineUtils::ExtractRawString(const FString& InString) { - if (InString.IsEmpty()) - return nullptr; - - std::string ConvertedString = TCHAR_TO_UTF8(*InString); + // Return an empty string instead of returning null to avoid potential crashes + std::string ConvertedString(""); + if (!InString.IsEmpty()) + ConvertedString = TCHAR_TO_UTF8(*InString); // Allocate space for unique string. int32 UniqueStringBytes = ConvertedString.size() + 1; @@ -2287,7 +3184,7 @@ FHoudiniEngineUtils::FreeRawStringMemory(TArray& InRawStringArray) bool FHoudiniEngineUtils::AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // No need to add another component if we already show the logo @@ -2317,7 +3214,7 @@ FHoudiniEngineUtils::AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC) bool FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // Get the Houdini Logo SM @@ -2328,12 +3225,12 @@ FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC) // Iterate on the HAC's component for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) + if (!IsValid(CurrentSceneComp) || !CurrentSceneComp->IsA()) continue; // Get the static mesh component UStaticMeshComponent* SMC = Cast(CurrentSceneComp); - if (!SMC || SMC->IsPendingKill()) + if (!IsValid(SMC)) continue; // Check if the SMC is the Houdini Logo @@ -2353,7 +3250,7 @@ FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC) bool FHoudiniEngineUtils::HasHoudiniLogo(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // Get the Houdini Logo SM @@ -2364,12 +3261,12 @@ FHoudiniEngineUtils::HasHoudiniLogo(UHoudiniAssetComponent* HAC) // Iterate on the HAC's component for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) + if (!IsValid(CurrentSceneComp) || !CurrentSceneComp->IsA()) continue; // Get the static mesh component UStaticMeshComponent* SMC = Cast(CurrentSceneComp); - if (!SMC || SMC->IsPendingKill()) + if (!IsValid(SMC)) continue; // Check if the SMC is the Houdini Logo @@ -2509,7 +3406,8 @@ FHoudiniEngineUtils::HapiGetGroupNames( FHoudiniEngine::Get().GetSession(), GeoId, PartId, GroupType, &GroupNameStringHandles[0], GroupCount), false); } - + + /* OutGroupNames.SetNum(GroupCount); for (int32 NameIdx = 0; NameIdx < GroupCount; ++NameIdx) { @@ -2517,6 +3415,9 @@ FHoudiniEngineUtils::HapiGetGroupNames( FHoudiniEngineString::ToFString(GroupNameStringHandles[NameIdx], CurrentGroupName); OutGroupNames[NameIdx] = CurrentGroupName; } + */ + + FHoudiniEngineString::SHArrayToFStringArray(GroupNameStringHandles, OutGroupNames); return true; } @@ -2559,7 +3460,9 @@ FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( HAPI_AttributeInfo& OutAttributeInfo, TArray& OutData, int32 InTupleSize, - HAPI_AttributeOwner InOwner) + HAPI_AttributeOwner InOwner, + const int32& InStartIndex, + const int32& InCount) { TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniEngineUtils::HapiGetAttributeDataAsFloat")); @@ -2602,16 +3505,31 @@ FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( // Store the retrieved attribute information. OutAttributeInfo = AttributeInfo; + // Handle partial reading of attributes + int32 Start = 0; + if (InStartIndex > 0 && InStartIndex < AttributeInfo.count) + Start = InStartIndex; + + int32 Count = AttributeInfo.count; + if (InCount > 0) + { + if ((Start + InCount) <= AttributeInfo.count) + Count = InCount; + else + Count = AttributeInfo.count - Start; + } + if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) { // Allocate sufficient buffer for data. - OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + OutData.SetNum(Count * AttributeInfo.tupleSize); // Fetch the values HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( FHoudiniEngine::Get().GetSession(), InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); + &AttributeInfo, -1, &OutData[0], + Start, Count), false); return true; } @@ -2621,13 +3539,14 @@ FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( // Allocate sufficient buffer for data. TArray IntData; - IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + IntData.SetNum(Count * AttributeInfo.tupleSize); // Fetch the values if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( FHoudiniEngine::Get().GetSession(), InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) + &AttributeInfo, -1, &IntData[0], + Start, Count)) { OutData.SetNum(IntData.Num()); for (int32 Idx = 0; Idx < IntData.Num(); Idx++) @@ -2644,7 +3563,9 @@ FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( // Expected Float, found a string, try to convert the attribute TArray StringData; if (FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) + InGeoId, InPartId, InAttribName, + AttributeInfo, StringData, + Start, Count)) { bool bConversionError = false; OutData.SetNum(StringData.Num()); @@ -2676,7 +3597,9 @@ FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( HAPI_AttributeInfo& OutAttributeInfo, TArray& OutData, const int32& InTupleSize, - const HAPI_AttributeOwner& InOwner) + const HAPI_AttributeOwner& InOwner, + const int32& InStartIndex, + const int32& InCount) { OutAttributeInfo.exists = false; @@ -2717,16 +3640,30 @@ FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( // Store the retrieved attribute information. OutAttributeInfo = AttributeInfo; + // Handle partial reading of attributes + int32 Start = 0; + if (InStartIndex > 0 && InStartIndex < AttributeInfo.count) + Start = InStartIndex; + + int32 Count = AttributeInfo.count; + if (InCount > 0) + { + if ((Start + InCount) <= AttributeInfo.count) + Count = InCount; + else + Count = AttributeInfo.count - Start; + } + if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) { // Allocate sufficient buffer for data. - OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + OutData.SetNum(Count * AttributeInfo.tupleSize); // Fetch the values HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( FHoudiniEngine::Get().GetSession(), InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); + &AttributeInfo, -1, &OutData[0], Start, Count), false); return true; } @@ -2736,13 +3673,13 @@ FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( // Allocate sufficient buffer for data. TArray FloatData; - FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + FloatData.SetNum(Count * AttributeInfo.tupleSize); // Fetch the float values if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( FHoudiniEngine::Get().GetSession(), InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) + &AttributeInfo, -1, &FloatData[0], Start, Count)) { OutData.SetNum(FloatData.Num()); for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) @@ -2759,7 +3696,10 @@ FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( { // Expected Int, found a string, try to convert the attribute TArray StringData; - if(FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) + if(FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InGeoId, InPartId, InAttribName, + AttributeInfo, StringData, + Start, Count)) { bool bConversionError = false; OutData.SetNum(StringData.Num()); @@ -2792,7 +3732,9 @@ FHoudiniEngineUtils::HapiGetAttributeDataAsString( HAPI_AttributeInfo& OutAttributeInfo, TArray& OutData, int32 InTupleSize, - HAPI_AttributeOwner InOwner) + HAPI_AttributeOwner InOwner, + const int32& InStartIndex, + const int32& InCount) { OutAttributeInfo.exists = false; @@ -2833,9 +3775,26 @@ FHoudiniEngineUtils::HapiGetAttributeDataAsString( if (OriginalTupleSize > 0) AttributeInfo.tupleSize = OriginalTupleSize; + // Handle partial reading of attributes + int32 Start = 0; + if (InStartIndex > 0 && InStartIndex < AttributeInfo.count) + Start = InStartIndex; + + int32 Count = AttributeInfo.count; + if (InCount > 0) + { + if ((Start + InCount) <= AttributeInfo.count) + Count = InCount; + else + Count = AttributeInfo.count - Start; + } + if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) { - return FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, OutData); + return FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InGeoId, InPartId, InAttribName, + AttributeInfo, OutData, + Start, Count); } else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) { @@ -2843,13 +3802,14 @@ FHoudiniEngineUtils::HapiGetAttributeDataAsString( // Allocate sufficient buffer for data. TArray FloatData; - FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + FloatData.SetNum(Count * AttributeInfo.tupleSize); // Fetch the float values if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( FHoudiniEngine::Get().GetSession(), InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) + &AttributeInfo, -1, &FloatData[0], + Start, Count)) { OutData.SetNum(FloatData.Num()); for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) @@ -2867,13 +3827,14 @@ FHoudiniEngineUtils::HapiGetAttributeDataAsString( // Allocate sufficient buffer for data. TArray IntData; - IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + IntData.SetNum(Count * AttributeInfo.tupleSize); // Fetch the values if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( FHoudiniEngine::Get().GetSession(), InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) + &AttributeInfo, -1, &IntData[0], + Start, Count)) { OutData.SetNum(IntData.Num()); for (int32 Idx = 0; Idx < IntData.Num(); Idx++) @@ -2896,52 +3857,47 @@ FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( const HAPI_PartId& InPartId, const char * InAttribName, HAPI_AttributeInfo& InAttributeInfo, - TArray& OutData) + TArray& OutData, + const int32& InStartIndex, + const int32& InCount) { if (!InAttributeInfo.exists) return false; + // Handle partial reading of attributes + int32 Start = 0; + if (InStartIndex > 0 && InStartIndex < InAttributeInfo.count) + Start = InStartIndex; + + int32 Count = InAttributeInfo.count; + if (InCount > 0) + { + if ((Start + InCount) <= InAttributeInfo.count) + Count = InCount; + else + Count = InAttributeInfo.count - Start; + } + // Extract the StringHandles TArray StringHandles; - StringHandles.Init(-1, InAttributeInfo.count * InAttributeInfo.tupleSize); + StringHandles.Init(-1, Count * InAttributeInfo.tupleSize); HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, &InAttributeInfo, - &StringHandles[0], 0, InAttributeInfo.count), false); + InGeoId, InPartId, InAttribName, + &InAttributeInfo, &StringHandles[0], + Start, Count), false); // Set the output data size OutData.SetNum(StringHandles.Num()); // Convert the StringHandles to FString. - // We'll use a map to minimize the number of HAPI calls - TMap StringHandleToStringMap; - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - const HAPI_StringHandle& CurrentSH = StringHandles[Idx]; - if (CurrentSH < 0) - { - OutData[Idx] = TEXT(""); - continue; - } - - FString* FoundString = StringHandleToStringMap.Find(CurrentSH); - if (FoundString) - { - OutData[Idx] = *FoundString; - } - else - { - FString HapiString = TEXT(""); - FHoudiniEngineString::ToFString(CurrentSH, HapiString); - - StringHandleToStringMap.Add(CurrentSH, HapiString); - OutData[Idx] = HapiString; - } - } - + // using a map to minimize the number of HAPI calls + FHoudiniEngineString::SHArrayToFStringArray(StringHandles, OutData); + return true; } + bool FHoudiniEngineUtils::HapiCheckAttributeExists( const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, @@ -3107,48 +4063,44 @@ FHoudiniEngineUtils::HapiGetParameterDataAsFloat( return true; } -HAPI_ParmId FHoudiniEngineUtils::HapiFindParameterByNameOrTag(const HAPI_NodeId& NodeId, const std::string& ParmName, HAPI_ParmInfo& FoundParmInfo) +HAPI_ParmId +FHoudiniEngineUtils::HapiFindParameterByName(const HAPI_NodeId& InNodeId, const std::string& InParmName, HAPI_ParmInfo& OutFoundParmInfo) { - FHoudiniApi::ParmInfo_Init(&FoundParmInfo); - - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo); - if (NodeInfo.parmCount <= 0) - return -1; + // Try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + InNodeId, InParmName.c_str(), &ParmId), -1); - HAPI_ParmId ParmId = HapiFindParameterByNameOrTag(NodeInfo.id, ParmName); - if ((ParmId < 0) || (ParmId >= NodeInfo.parmCount)) + if (ParmId < 0) return -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniApi::ParmInfo_Init(&OutFoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmInfo( FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParmInfo), -1); + InNodeId, ParmId, &OutFoundParmInfo), -1); return ParmId; } - -HAPI_ParmId FHoudiniEngineUtils::HapiFindParameterByNameOrTag(const HAPI_NodeId& NodeId, const std::string& ParmName) +HAPI_ParmId +FHoudiniEngineUtils::HapiFindParameterByTag(const HAPI_NodeId& InNodeId, const std::string& InParmTag, HAPI_ParmInfo& OutFoundParmInfo) { - // First, try to find the parameter by its name + // Try to find the parameter by its tag HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmWithTag( FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), -1); + InNodeId, InParmTag.c_str(), &ParmId), -1); - if (ParmId >= 0) - return ParmId; + if (ParmId < 0) + return -1; - // Second, try to find it by its tag - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmWithTag( + FHoudiniApi::ParmInfo_Init(&OutFoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmInfo( FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), -1); + InNodeId, ParmId, &OutFoundParmInfo), -1); - if (ParmId >= 0) - return ParmId; - - return -1; + return ParmId; } int32 @@ -3157,8 +4109,8 @@ FHoudiniEngineUtils::HapiGetAttributeOfType( const HAPI_NodeId& PartId, const HAPI_AttributeOwner& AttributeOwner, const HAPI_AttributeTypeInfo& AttributeType, - TArray< HAPI_AttributeInfo >& MatchingAttributesInfo, - TArray< FString >& MatchingAttributesName) + TArray& MatchingAttributesInfo, + TArray& MatchingAttributesName) { int32 NumberOfAttributeFound = 0; @@ -3180,12 +4132,13 @@ FHoudiniEngineUtils::HapiGetAttributeOfType( GeoId, PartId, AttributeOwner, AttribNameSHArray.GetData(), nAttribCount), NumberOfAttributeFound); - // Iterate on all the attributes, and get their part infos to get their type - for (int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx) + TArray AttribNameArray; + FHoudiniEngineString::SHArrayToFStringArray(AttribNameSHArray, AttribNameArray); + + // Iterate on all the attributes, and get their part infos to get their type + for (int32 Idx = 0; Idx < AttribNameArray.Num(); Idx++) { - // Get the name ... - FString HapiString = TEXT(""); - FHoudiniEngineString::ToFString(AttribNameSHArray[Idx], HapiString); + FString HapiString = AttribNameArray[Idx]; // ... then the attribute info HAPI_AttributeInfo AttrInfo; @@ -3745,7 +4698,7 @@ FHoudiniEngineUtils::AddMeshSocketsToStaticMesh( TArray& AllSockets, const bool& CleanImportSockets) { - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return false; // Remove the sockets from the previous cook! @@ -3786,7 +4739,7 @@ FHoudiniEngineUtils::AddMeshSocketsToStaticMesh( { // Create a new Socket UStaticMeshSocket* Socket = NewObject(StaticMesh); - if (!Socket || Socket->IsPendingKill()) + if (!IsValid(Socket)) continue; Socket->RelativeLocation = AllSockets[nSocket].Transform.GetLocation(); @@ -3977,15 +4930,15 @@ FHoudiniEngineUtils::GetUnrealTagAttributes( FString TagValue = FString(); // Create an AttributeInfo + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + TArray StringData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), + AttributeInfo, StringData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) { - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - TArray StringData; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), AttributeInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) - { + if (StringData.Num() > 0) TagValue = StringData[0]; - } } FName NameTag = *TagValue; @@ -3996,24 +4949,6 @@ FHoudiniEngineUtils::GetUnrealTagAttributes( } -int32 -FHoudiniEngineUtils::GetPropertyAttributeList( - const FHoudiniGeoPartObject& InHGPO, TArray& OutFoundPropertyAttributes) -{ - // Get all the detail uprop attributes on the HGPO - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InHGPO.GeoInfo.NodeId, (HAPI_PartId)InHGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutFoundPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then the primitive uprop attributes - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InHGPO.GeoInfo.NodeId, (HAPI_PartId)InHGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutFoundPropertyAttributes, HAPI_ATTROWNER_PRIM); - - return FoundCount; -} - - int32 FHoudiniEngineUtils::GetGenericAttributeList( const HAPI_NodeId& InGeoNodeId, @@ -4023,6 +4958,8 @@ FHoudiniEngineUtils::GetGenericAttributeList( const HAPI_AttributeOwner& AttributeOwner, const int32& InAttribIndex) { + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineUtils::GetGenericAttributeList); + // Get the part info to get the attribute counts for the specified owner HAPI_PartInfo PartInfo; FHoudiniApi::PartInfo_Init(&PartInfo); @@ -4143,6 +5080,51 @@ FHoudiniEngineUtils::GetGenericAttributeList( } else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT64) { +#if PLATFORM_LINUX + // On Linux, we unfortunately cannot guarantee that int64 and HAPI_Int64 + // are of the same type, to properly read the value, we must first check the + // size, then either cast them (if sizes match) or convert the values (if sizes don't match) + if (sizeof(int64) != sizeof(HAPI_Int64)) + { + // int64 and HAPI_Int64 are of different size, we need to cast + TArray HAPIIntValues; + HAPIIntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, HAPIIntValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert them to int64 + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + for (int32 n = 0; n < HAPIIntValues.Num(); n++) + CurrentGenericAttribute.IntValues[n] = (int64)HAPIIntValues[n]; + } + else + { + // Initialize the value array + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) with a reinterpret_cast since sizes match + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, reinterpret_cast(CurrentGenericAttribute.IntValues.GetData()), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + } +#else // Initialize the value array CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); @@ -4157,6 +5139,7 @@ FHoudiniEngineUtils::GetGenericAttributeList( // failed to get that attribute's data continue; } +#endif } else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT) { @@ -4200,15 +5183,9 @@ FHoudiniEngineUtils::GetGenericAttributeList( continue; } - // Convert them to FString - CurrentGenericAttribute.StringValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - for (int32 IdxSH = 0; IdxSH < HapiSHArray.Num(); IdxSH++) - { - FString CurrentString; - FHoudiniEngineString::ToFString(HapiSHArray[IdxSH], CurrentString); - CurrentGenericAttribute.StringValues[IdxSH] = CurrentString; - } + // Convert the String Handles to FStrings + // using a map to minimize the number of HAPI calls + FHoudiniEngineString::SHArrayToFStringArray(HapiSHArray, CurrentGenericAttribute.StringValues); } else { @@ -4225,46 +5202,250 @@ FHoudiniEngineUtils::GetGenericAttributeList( } -void -FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject( - UObject* InObject, const FHoudiniGeoPartObject& InHGPO) +bool +FHoudiniEngineUtils::GetGenericPropertiesAttributes(const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, + const bool InbFindDetailAttributes, const int32& InFirstValidPrimIndex, const int32& InFirstValidVertexIndex, const int32& InFirstValidPointIndex, + TArray& OutPropertyAttributes) { - if (!InObject || InObject->IsPendingKill()) - return; + int32 FoundCount = 0; - // Get the list of all the Properties to modify from the HGPO's attributes - TArray PropertiesAttributesToModify; - if (!FHoudiniEngineUtils::GetPropertyAttributeList(InHGPO, PropertiesAttributesToModify)) - return; + // List all the generic property detail attributes ... + if (InbFindDetailAttributes) + { + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); + } - // Iterate over the found Property attributes - for (auto CurrentPropAttribute : PropertiesAttributesToModify) + // .. then the primitive property attributes for the given prim + if (InFirstValidPrimIndex != INDEX_NONE) { - // Get the current Property Attribute - const FString& CurrentPropertyName = CurrentPropAttribute.AttributeName; - if (CurrentPropertyName.IsEmpty()) - continue; + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InFirstValidPrimIndex); + } + + if (InFirstValidVertexIndex != INDEX_NONE) + { + // .. then finally, point uprop attributes for the given point + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_VERTEX, InFirstValidVertexIndex); + } + + if (InFirstValidPointIndex != INDEX_NONE) + { + // .. then finally, point uprop attributes for the given point + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InFirstValidPointIndex); + } + + return FoundCount > 0; +} + +bool +FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(UObject* InObject, + const TArray& InAllPropertyAttributes) +{ + if (!IsValid(InObject)) + return false; + // Iterate over the found Property attributes + int32 NumSuccess = 0; + for (const auto& CurrentPropAttribute : InAllPropertyAttributes) + { + // Update the current Property Attribute if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) continue; // Success! - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropertyName, *ClassName, *ObjectName); + NumSuccess++; +#if defined(HOUDINI_ENGINE_LOGGING) + const FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); + const FString ObjectName = InObject->GetName(); + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); +#endif } + + return (NumSuccess > 0); } +bool +FHoudiniEngineUtils::SetGenericPropertyAttribute( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FHoudiniGenericAttribute& InPropertyAttribute) +{ + HAPI_AttributeOwner AttribOwner; + switch (InPropertyAttribute.AttributeOwner) + { + case EAttribOwner::Point: + AttribOwner = HAPI_ATTROWNER_POINT; + break; + case EAttribOwner::Vertex: + AttribOwner = HAPI_ATTROWNER_VERTEX; + break; + case EAttribOwner::Prim: + AttribOwner = HAPI_ATTROWNER_PRIM; + break; + case EAttribOwner::Detail: + AttribOwner = HAPI_ATTROWNER_DETAIL; + break; + case EAttribOwner::Invalid: + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Owner: %d"), InPropertyAttribute.AttributeOwner); + return false; + } + + // Create the attribute via HAPI + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.tupleSize = InPropertyAttribute.AttributeTupleSize; + AttributeInfo.count = InPropertyAttribute.AttributeCount; + AttributeInfo.exists = true; + AttributeInfo.owner = AttribOwner; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + switch(InPropertyAttribute.AttributeType) + { + case (EAttribStorageType::INT): + AttributeInfo.storage = HAPI_STORAGETYPE_INT; + break; + case (EAttribStorageType::INT64): + AttributeInfo.storage = HAPI_STORAGETYPE_INT64; + break; + case (EAttribStorageType::FLOAT): + AttributeInfo.storage = HAPI_STORAGETYPE_FLOAT; + break; + case (EAttribStorageType::FLOAT64): + AttributeInfo.storage = HAPI_STORAGETYPE_FLOAT64; + break; + case (EAttribStorageType::STRING): + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + break; + case (EAttribStorageType::Invalid): + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Storage Type: %d"), InPropertyAttribute.AttributeType); + return false; + } + + // Create the new attribute + if (HAPI_RESULT_SUCCESS != FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo)) + { + return false; + } + + // The New attribute has been successfully created, set its value + switch (InPropertyAttribute.AttributeType) + { + case EAttribStorageType::INT: + { + TArray TempArray; + TempArray.Reserve(InPropertyAttribute.IntValues.Num()); + for (auto Value : InPropertyAttribute.IntValues) + { + TempArray.Add(static_cast(Value)); + } + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + TempArray.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + + case EAttribStorageType::INT64: + { +#if PLATFORM_LINUX + // On Linux, we unfortunately cannot guarantee that int64 and HAPI_Int64 are of the same type, + TArray HAPIIntValues; + HAPIIntValues.SetNumZeroed(InPropertyAttribute.IntValues.Num()); + for (int32 n = 0; n < HAPIIntValues.Num(); n++) + HAPIIntValues[n] = (HAPI_Int64)InPropertyAttribute.IntValues[n]; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + HAPIIntValues.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } +#else + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + InPropertyAttribute.IntValues.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } +#endif + break; + } + + case EAttribStorageType::FLOAT: + { + + TArray TempArray; + TempArray.Reserve(InPropertyAttribute.DoubleValues.Num()); + for (auto Value : InPropertyAttribute.DoubleValues) + { + TempArray.Add(static_cast(Value)); + } + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + TempArray.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + + case EAttribStorageType::FLOAT64: + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloat64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + InPropertyAttribute.DoubleValues.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + + case EAttribStorageType::STRING: + { + if (HAPI_RESULT_SUCCESS != FHoudiniEngineUtils::SetAttributeStringData( + InPropertyAttribute.StringValues, + InGeoNodeId, + InPartId, + InPropertyAttribute.AttributeName, + AttributeInfo)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + + default: + // Unsupported storage type + HOUDINI_LOG_WARNING(TEXT("Unsupported storage type: %d"), InPropertyAttribute.AttributeType); + break; + } + + return true; +} void FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( UPackage * Package, UObject * Object, const FString& Key, const FString& Value) { - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return; UMetaData * MetaData = Package->GetMetaData(); - if (MetaData && !MetaData->IsPendingKill()) + if (IsValid(MetaData)) MetaData->SetValue(Object, *Key, *Value); } @@ -4279,7 +5460,7 @@ FHoudiniEngineUtils::AddLevelPathAttribute( if (InNodeId < 0 || InCount <= 0) return false; - if (!InLevel || InLevel->IsPendingKill()) + if (!IsValid(InLevel)) return false; // Extract the level path from the level @@ -4349,7 +5530,7 @@ FHoudiniEngineUtils::AddActorPathAttribute( if (InNodeId < 0 || InCount <= 0) return false; - if (!InActor || InActor->IsPendingKill()) + if (!IsValid(InActor)) return false; // Extract the actor path @@ -4601,7 +5782,9 @@ FHoudiniEngineUtils::GetLevelPathAttribute( const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutLevelPaths, - HAPI_AttributeOwner InAttributeOwner) + HAPI_AttributeOwner InAttributeOwner, + const int32& InStartIndex, + const int32& InCount) { // --------------------------------------------- // Attribute: unreal_level_path @@ -4609,8 +5792,9 @@ FHoudiniEngineUtils::GetLevelPathAttribute( HAPI_AttributeInfo AttributeInfo; FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_LEVEL_PATH, AttributeInfo, OutLevelPaths, 1, InAttributeOwner)) + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_LEVEL_PATH, + AttributeInfo, OutLevelPaths, 1, InAttributeOwner, InStartIndex, InCount)) { if (OutLevelPaths.Num() > 0) return true; @@ -4621,7 +5805,12 @@ FHoudiniEngineUtils::GetLevelPathAttribute( } bool -FHoudiniEngineUtils::GetOutputNameAttribute(const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutOutputNames) +FHoudiniEngineUtils::GetOutputNameAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutOutputNames, + const int32& InStartIndex, + const int32& InCount) { // --------------------------------------------- // Attribute: unreal_output_name @@ -4629,16 +5818,18 @@ FHoudiniEngineUtils::GetOutputNameAttribute(const HAPI_NodeId& InGeoId, const HA HAPI_AttributeInfo AttributeInfo; FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, AttributeInfo, OutOutputNames, 1)) + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, + AttributeInfo, OutOutputNames, 1, HAPI_ATTROWNER_INVALID, InStartIndex, InCount)) { if (OutOutputNames.Num() > 0) return true; } OutOutputNames.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, AttributeInfo, OutOutputNames, 1)) + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, + AttributeInfo, OutOutputNames, 1, HAPI_ATTROWNER_INVALID, InStartIndex, InCount)) { if (OutOutputNames.Num() > 0) return true; @@ -4648,12 +5839,40 @@ FHoudiniEngineUtils::GetOutputNameAttribute(const HAPI_NodeId& InGeoId, const HA return false; } +bool +FHoudiniEngineUtils::GetBakeNameAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeNames, + const int32& InStartIndex, + const int32& InCount) +{ + // --------------------------------------------- + // Attribute: unreal_bake_name + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_NAME, + AttributeInfo, OutBakeNames, 1, HAPI_ATTROWNER_INVALID, InStartIndex, InCount)) + { + if (OutBakeNames.Num() > 0) + return true; + } + + OutBakeNames.Empty(); + return false; +} + bool FHoudiniEngineUtils::GetTileAttribute( const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutTileValues, - const HAPI_AttributeOwner& InAttribOwner) + const HAPI_AttributeOwner& InAttribOwner, + const int32& InStart, + const int32& InCount) { // --------------------------------------------- // Attribute: tile @@ -4662,45 +5881,111 @@ FHoudiniEngineUtils::GetTileAttribute( FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, + AttribInfoTile, OutTileValues, 0, InAttribOwner, InStart, InCount)) + { + if (OutTileValues.Num() > 0) + return true; + } + + OutTileValues.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetEditLayerName( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + FString& EditLayerName, + const HAPI_AttributeOwner& InAttribOwner) +{ + // --------------------------------------------- + // Attribute: tile + // --------------------------------------------- + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + + TArray StrData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, - AttribInfoTile, - OutTileValues, + HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_NAME, + AttribInfo, + StrData, 0, InAttribOwner)) { - if (OutTileValues.Num() > 0) + if (StrData.Num() > 0) + { + EditLayerName = StrData[0]; return true; + } } - OutTileValues.Empty(); + EditLayerName = FString(); return false; } +bool FHoudiniEngineUtils::HasEditLayerName(const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, + const HAPI_AttributeOwner& InAttribOwner) +{ + // --------------------------------------------- + // Attribute: unreal_landscape_ + // --------------------------------------------- + + return FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_NAME, + InAttribOwner); +} + bool FHoudiniEngineUtils::GetBakeFolderAttribute( const HAPI_NodeId& InGeoId, + const HAPI_AttributeOwner& InAttributeOwner, TArray& OutBakeFolder, - HAPI_PartId InPartId) + const HAPI_PartId& InPartId, + const int32& InStart, + const int32& InCount) { OutBakeFolder.Empty(); HAPI_AttributeInfo BakeFolderAttribInfo; FHoudiniApi::AttributeInfo_Init(&BakeFolderAttribInfo); if (HapiGetAttributeDataAsString( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, HAPI_ATTROWNER_DETAIL)) + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, + BakeFolderAttribInfo, OutBakeFolder, 1, InAttributeOwner, + InStart, InCount)) { if (OutBakeFolder.Num() > 0) return true; } - if (HapiGetAttributeDataAsString( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, HAPI_ATTROWNER_PRIM)) + OutBakeFolder.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + TArray& OutBakeFolder, + const HAPI_PartId& InPartId, + const int32& InStart, + const int32& InCount) +{ + OutBakeFolder.Empty(); + + if (GetBakeFolderAttribute(InGeoId, HAPI_ATTROWNER_PRIM, OutBakeFolder, InPartId, InStart, InCount)) { if (OutBakeFolder.Num() > 0) return true; } + if (GetBakeFolderAttribute(InGeoId, HAPI_ATTROWNER_DETAIL, OutBakeFolder, InPartId, InStart, InCount)) + { + if (OutBakeFolder.Num() > 0) + return true; + } + OutBakeFolder.Empty(); return false; } @@ -4710,7 +5995,9 @@ FHoudiniEngineUtils::GetBakeActorAttribute( const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutBakeActorNames, - HAPI_AttributeOwner InAttributeOwner) + const HAPI_AttributeOwner& InAttributeOwner, + const int32& InStart, + const int32& InCount) { // --------------------------------------------- // Attribute: unreal_bake_actor @@ -4718,8 +6005,9 @@ FHoudiniEngineUtils::GetBakeActorAttribute( HAPI_AttributeInfo AttributeInfo; FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_BAKE_ACTOR, AttributeInfo, OutBakeActorNames, 1, InAttributeOwner)) + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_ACTOR, + AttributeInfo, OutBakeActorNames, 1, InAttributeOwner, InStart, InCount)) { if (OutBakeActorNames.Num() > 0) return true; @@ -4734,7 +6022,9 @@ FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutBakeOutlinerFolders, - HAPI_AttributeOwner InAttributeOwner) + const HAPI_AttributeOwner& InAttributeOwner, + const int32& InStart, + const int32& InCount) { // --------------------------------------------- // Attribute: unreal_bake_outliner_folder @@ -4742,8 +6032,9 @@ FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( HAPI_AttributeInfo AttributeInfo; FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, AttributeInfo, OutBakeOutlinerFolders, 1, InAttributeOwner)) + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, + AttributeInfo, OutBakeOutlinerFolders, 1, InAttributeOwner, InStart, InCount)) { if (OutBakeOutlinerFolders.Num() > 0) return true; @@ -4753,54 +6044,6 @@ FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( return false; } -bool -FHoudiniEngineUtils::GetBakeFolderOverridePath( - const HAPI_NodeId& InGeoId, - FString& OutBakeFolder, - HAPI_PartId InPartId) -{ - FString BakeFolderOverride; - - TArray StringData; - if (GetBakeFolderAttribute(InGeoId, StringData, InPartId)) - { - BakeFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); - } - - if (BakeFolderOverride.StartsWith("Game/")) - { - BakeFolderOverride = "/" + BakeFolderOverride; - } - - FString AbsoluteOverridePath; - if (BakeFolderOverride.StartsWith("/Game/")) - { - const FString RelativePath = FPaths::ProjectContentDir() + BakeFolderOverride.Mid(6, BakeFolderOverride.Len() - 6); - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - if (!BakeFolderOverride.IsEmpty()) - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*BakeFolderOverride); - } - - // Check Validity of the path - if (AbsoluteOverridePath.IsEmpty() || !FPaths::DirectoryExists(AbsoluteOverridePath)) - { - // Only display a warning if the path is invalid, empty is fine - if (!AbsoluteOverridePath.IsEmpty()) - HOUDINI_LOG_WARNING(TEXT("Invalid override bake path: %s"), *BakeFolderOverride); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - OutBakeFolder = HoudiniRuntimeSettings->DefaultBakeFolder; - - return false; - } - - OutBakeFolder = BakeFolderOverride; - return true; -} - bool FHoudiniEngineUtils::MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel) { @@ -4816,7 +6059,7 @@ FHoudiniEngineUtils::MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel) CurrentWorld->RemoveActor(InActor, true); //Set the outer of Actor to NewLevel - InActor->Rename((const TCHAR *)0, InDesiredLevel); + FHoudiniEngineUtils::RenameObject(InActor, (const TCHAR*)0, InDesiredLevel); InDesiredLevel->Actors.Add(InActor); return true; @@ -4875,4 +6118,4 @@ FHoudiniEngineUtils::HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* } } -#undef LOCTEXT_NAMESPACE \ No newline at end of file +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h index 8acd1ba74..3e0adde6c 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,9 +35,6 @@ #include "HoudiniPackageParams.h" #include "Containers/UnrealString.h" -#include "SSCSEditor.h" - - class FString; class UStaticMesh; class UHoudiniAsset; @@ -76,6 +73,9 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // Return a specified HAPI status string. static const FString GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity); + // HAPI : Return the string that corresponds to the given string handle. + static FString HapiGetString(int32 StringHandle); + // Return a string representing cooking result. static const FString GetCookResult(); @@ -88,6 +88,9 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // Return a string description of error from a given error code. static const FString GetErrorDescription(HAPI_Result Result); + // Return a string description for a Houdini Engine session connection error. + static const FString GetConnectionError(); + // Return the errors, warning and messages on a specified node static const FString GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId); @@ -115,9 +118,6 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // HAPI : Retrieve the asset node's object transform. **/ static bool HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform); - // HAPI : Retrieve object transforms from given asset node id. - static bool HapiGetObjectTransforms(const HAPI_NodeId& InNodeId, TArray& OutObjectTransforms); - // HAPI : Translate HAPI transform to Unreal one. static void TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform); @@ -134,7 +134,45 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils static bool IsHoudiniNodeValid(const HAPI_NodeId& AssetId); // HAPI : Retrieve HAPI_ObjectInfo's from given asset node id. - static bool HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos); + static bool HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos, TArray& OutObjectTransforms); + + // Traverse from the Child up to the Root node to determine whether the ChildNode is fully visible + // inside the RootNode. + // - The Obj node itself is visible + // - All parent nodes are visible + // - Only has Object subnet parents (if we find a parent with non-Object nodetype then it's not visible). + static bool IsObjNodeFullyVisible(const TSet& AllObjectIds, const HAPI_NodeId& RootNodeId, const HAPI_NodeId& ChildNodeId); + + static bool IsSopNode(const HAPI_NodeId& NodeId); + + static bool ContainsSopNodes(const HAPI_NodeId& NodeId); + + // Get the output index of InNodeId (assuming InNodeId is an Output node). + // This is done by getting the value of the outputidx parameter on + // InNodeId. + // Returns false if outputidx could not be found/read. Sets OutOutputIndex to the + // value of the outputidx parameter. + static bool GetOutputIndex(const HAPI_NodeId& InNodeId, int32& OutOutputIndex); + + static bool GatherAllAssetOutputs( + const HAPI_NodeId& InAssetId, + const bool bUseOutputNodes, + const bool bOutputTemplatedGeos, + TArray& OutOutputNodes); + + // Get the immediate output geo infos for the given Geometry object network. + // Find immediate Display and output nodes (if enabled). + // If bIgnoreOutputNodes is false, only Display nodes will be retrieved. + // If bIgnoreOutputNodes is true, any output nodes will take precedence over display nodes. + static bool GatherImmediateOutputGeoInfos( + const int& InNodeId, + const bool bUseOutputNodes, + const bool bGatherTemplateNodes, + TArray& OutGeoInfos, + TSet& OutForceNodesCook); + + // HAPI: Retrieve absolute path to the given Node + static bool HapiGetAbsNodePath(const HAPI_NodeId& InNodeId, FString& OutPath); // HAPI: Retrieve Path to the given Node, relative to the given Node static bool HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath); @@ -177,7 +215,9 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils HAPI_AttributeInfo& OutAttributeInfo, TArray& OutData, int32 InTupleSize = 0, - HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); + HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID, + const int32& InStartIndex = 0, + const int32& InCount = -1); // HAPI : Get attribute data as Integer. static bool HapiGetAttributeDataAsInteger( @@ -187,7 +227,9 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils HAPI_AttributeInfo& OutAttributeInfo, TArray& OutData, const int32& InTupleSize = 0, - const HAPI_AttributeOwner& InOwner = HAPI_ATTROWNER_INVALID); + const HAPI_AttributeOwner& InOwner = HAPI_ATTROWNER_INVALID, + const int32& InStartIndex = 0, + const int32& InCount = -1); // HAPI : Get attribute data as strings. static bool HapiGetAttributeDataAsString( @@ -197,7 +239,9 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils HAPI_AttributeInfo& OutAttributeInfo, TArray& OutData, int32 InTupleSize = 0, - HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); + HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID, + const int32& InStartIndex = 0, + const int32& InCount = -1); // HAPI : Get attribute data as strings. static bool HapiGetAttributeDataAsStringFromInfo( @@ -205,7 +249,9 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils const HAPI_PartId& InPartId, const char * InAttribName, HAPI_AttributeInfo& InAttributeInfo, - TArray& OutData); + TArray& OutData, + const int32& InStartIndex = 0, + const int32& InCount = -1); // HAPI : Check if given attribute exists. static bool HapiCheckAttributeExists( @@ -223,22 +269,22 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils TArray& MatchingAttributesInfo, TArray& MatchingAttributesName); - // HAPI : Look for a parameter by name or tag and returns its index. Returns -1 if not found. - static HAPI_ParmId HapiFindParameterByNameOrTag( - const HAPI_NodeId& NodeId, const std::string& ParmName); - static HAPI_ParmId HapiFindParameterByNameOrTag( - const HAPI_NodeId& NodeId, const std::string& ParmName, HAPI_ParmInfo& FoundParmInfo); + // HAPI : Look for a parameter by name and returns its index. Returns -1 if not found. + static HAPI_ParmId HapiFindParameterByName( + const HAPI_NodeId& InNodeId, const std::string& InParmName, HAPI_ParmInfo& OutFoundParmInfo); - // Returns true is the given Geo-Part is an attribute instancer - static bool IsAttributeInstancer(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType); + // HAPI : Look for a parameter by tag and returns its index. Returns -1 if not found. + static HAPI_ParmId HapiFindParameterByTag( + const HAPI_NodeId& InNodeId, const std::string& InParmTag, HAPI_ParmInfo& OutFoundParmInfo); - // Return true if given asset id is valid. - //static bool IsValidNodeId(HAPI_NodeId AssetId); + // Returns true is the given Geo-Part is an attribute instancer + static bool IsAttributeInstancer( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType); // HAPI : Return a give node's parent ID, -1 if none static HAPI_NodeId HapiGetParentNodeId(const HAPI_NodeId& NodeId); - /** HAPI : Marshaling, disconnect input asset from a given slot. **/ + // HAPI : Marshaling, disconnect input asset from a given slot. static bool HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex); // Destroy asset, returns the status. @@ -246,7 +292,7 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // Loads an HDA file and returns its AssetLibraryId static bool LoadHoudiniAsset( - UHoudiniAsset * HoudiniAsset, + const UHoudiniAsset * HoudiniAsset, HAPI_AssetLibraryId & OutAssetLibraryId); // Returns the name of the available subassets in a loaded HDA @@ -318,14 +364,6 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils const std::string& ParmName, const float& DefaultValue, float& OutValue); - - // Returns a list with all the Property attributes found on a HGPO - static int32 GetPropertyAttributeList( - const FHoudiniGeoPartObject& InHGPO, TArray& OutFoundUProps); - - // Updates all FProperty attributes found on a given object - static void UpdateAllPropertyAttributesOnObject( - UObject* InObject, const FHoudiniGeoPartObject& InHGPO); // Returns a list of all the generic attributes for a given attribute owner static int32 GetGenericAttributeList( @@ -336,6 +374,25 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils const HAPI_AttributeOwner& AttributeOwner, const int32& InAttribIndex = -1); + // Helper functions for generic property attributes + static bool GetGenericPropertiesAttributes( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const bool InFindDetailAttributes, // if true, find default attributes + const int32& InFirstValidPrimIndex, // If not INDEX_NONE, look for primitive attribute + const int32& InFirstValidVertexIndex, // If this is not INDEX_NONE, look for vertex attribute + const int32& InFirstValidPointIndex, // If this is not INDEX_NONE, look for point attribute + TArray& OutPropertyAttributes); + + static bool UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes); + + // Helper function for setting a generic attribute on geo (UE -> HAPI) + static bool SetGenericPropertyAttribute( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FHoudiniGenericAttribute& InPropertyAttribute); + /* // Tries to update values for all the UProperty attributes to apply on the object. static void ApplyUPropertyAttributesOnObject( @@ -395,47 +452,82 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutLevelPath, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); + HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID, + const int32& InStart = 0, + const int32& InCount = -1); // Helper function to access the custom output name attribute static bool GetOutputNameAttribute( const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, - TArray& OutOutputName); + TArray& OutOutputName, + const int32& InStart = 0, + const int32& InCount = -1); + + // Helper function to access the custom bake name attribute + static bool GetBakeNameAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeName, + const int32& InStart = 0, + const int32& InCount = -1); // Helper function to access the "tile" attribute static bool GetTileAttribute( const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutTileValue, + const HAPI_AttributeOwner& InAttribOwner = HAPI_ATTROWNER_INVALID, + const int32& InStart = 0, + const int32& InCount = -1); + + static bool GetEditLayerName( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + FString& EditLayerName, + const HAPI_AttributeOwner& InAttribOwner = HAPI_ATTROWNER_INVALID); + + static bool HasEditLayerName( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, const HAPI_AttributeOwner& InAttribOwner = HAPI_ATTROWNER_INVALID); // Helper function to access the "unreal_bake_folder" attribute + static bool GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_AttributeOwner& InAttributeOwner, + TArray& OutBakeFolder, + const HAPI_PartId& InPartId = 0, + const int32& InStart = 0, + const int32& InCount = -1); + + // Helper function to access the "unreal_bake_folder" attribute + // We check for a primitive attribute first, if the primitive attribute does not exist, we check for a + // detail attribute. static bool GetBakeFolderAttribute( const HAPI_NodeId& InGeoId, TArray& OutBakeFolder, - HAPI_PartId InPartId=0); + const HAPI_PartId& InPartId = 0, + const int32& InStart = 0, + const int32& InCount = -1); // Helper function to access the bake output actor attribute (unreal_bake_actor) static bool GetBakeActorAttribute( const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutBakeActorNames, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); + const HAPI_AttributeOwner& InAttributeOwner = HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID, + const int32& InStart = 0, + const int32& InCount = -1); // Helper function to access the bake output actor attribute (unreal_bake_outliner_folder) static bool GetBakeOutlinerFolderAttribute( const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutBakeOutlinerFolders, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); - - // Helper function to get the bake folder override path. This is the "unreal_bake_folder" attribute, or if this - // does not exist or is invalid, the default bake folder path configured in the settings. - static bool GetBakeFolderOverridePath( - const HAPI_NodeId& InGeoId, - FString& OutBakeFolder, - HAPI_PartId InPartId=0); + const HAPI_AttributeOwner& InAttributeOwner = HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID, + const int32& InStart = 0, + const int32& InCount = -1); // Adds the "unreal_level_path" primitive attribute static bool AddLevelPathAttribute( @@ -485,8 +577,23 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // UWorld and UPackage utilities // ------------------------------------------------- + // Find actor in a given world by label + template + static T* FindActorInWorldByLabel(UWorld* InWorld, FString ActorLabel, EActorIteratorFlags Flags = EActorIteratorFlags::AllActors) + { + T* OutActor = nullptr; + for (TActorIterator ActorIt(InWorld, T::StaticClass(), Flags); ActorIt; ++ActorIt) + { + OutActor = *ActorIt; + if (!OutActor) + continue; + if (OutActor->GetActorLabel() == ActorLabel) + return OutActor; + } + return nullptr; + } + // Find actor in a given world by name - // Note that by default this will return all actors template static T* FindActorInWorld(UWorld* InWorld, FName ActorName, EActorIteratorFlags Flags = EActorIteratorFlags::AllActors) { @@ -567,6 +674,8 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // Generic naming / pathing utilities // ------------------------------------------------- + static bool RenameObject(UObject* Object, const TCHAR* NewName = nullptr, UObject* NewOuter = nullptr, ERenameFlags Flags = REN_None); + // Rename the actor to a unique / generated name. static FName RenameToUniqueActor(AActor* InActor, const FString& InName); @@ -578,13 +687,40 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // PackageParam utilities // ------------------------------------------------- + // Helper for populating FHoudiniPackageParams. + // If bAutomaticallySetAttemptToLoadMissingPackages is true, then + // OutPackageParams.bAttemptToLoadMissingPackages is set to true in EPackageReplaceMode::CreateNewAssets mode. static void FillInPackageParamsForBakingOutput( FHoudiniPackageParams& OutPackageParams, const FHoudiniOutputObjectIdentifier& InIdentifier, const FString &BakeFolder, const FString &ObjectName, const FString &HoudiniAssetName, - EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets); + const FString &HoudiniAssetActorName, + EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets, + bool bAutomaticallySetAttemptToLoadMissingPackages=true); + + // Helper for populating FHoudiniPackageParams when baking. This includes configuring the resolver to + // resolve the object name and unreal_bake_folder and setting these resolved values on the PackageParams. + // If bAutomaticallySetAttemptToLoadMissingPackages is true, then + // OutPackageParams.bAttemptToLoadMissingPackages is set to true in EPackageReplaceMode::CreateNewAssets mode. + // If InHoudiniAssetName or InHoudiniAssetActorName is blank, then the values are determined via + // HoudiniAssetComponent. + static void FillInPackageParamsForBakingOutputWithResolver( + UWorld* const InWorldContext, + const UHoudiniAssetComponent* HoudiniAssetComponent, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FHoudiniOutputObject& InOutputObject, + const FString &InDefaultObjectName, + FHoudiniPackageParams& OutPackageParams, + FHoudiniAttributeResolver& OutResolver, + const FString &InDefaultBakeFolder=FString(), + EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets, + const FString& InHoudiniAssetName=TEXT(""), + const FString& InHoudiniAssetActorName=TEXT(""), + bool bAutomaticallySetAttemptToLoadMissingPackages=true, + bool bInSkipObjectNameResolutionAndUseDefault=false, + bool bInSkipBakeFolderResolutionAndUseDefault=false); // ------------------------------------------------- // Foliage utilities @@ -596,10 +732,15 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // Returns true if the list was repopulated. static bool RepopulateFoliageTypeListInUI(); - public: + // ------------------------------------------------- + // Landscape utilities + // ------------------------------------------------- + + // Iterate over the input objects and gather only the landscape inputs. + static void GatherLandscapeInputs(UHoudiniAssetComponent* HAC, TArray& AllInputLandscapes, TArray& InputLandscapesToUpdate); + - static bool IsOuterHoudiniAssetComponent(UObject* Obj); - static UHoudiniAssetComponent* GetOuterHoudiniAssetComponent(UObject* Obj); + static UHoudiniAssetComponent* GetOuterHoudiniAssetComponent(const UObject* Obj); protected: @@ -621,4 +762,4 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // Trigger an update of the Blueprint Editor on the game thread static void UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC); -}; \ No newline at end of file +}; diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp index 54e4dbce6..eaeedd373 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp +++ b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -149,8 +149,6 @@ void UHoudiniGeoImportCommandlet::PopulatePackageParams(const FString &InBGEOFil // TODO: will need to reuse the GUID when reimporting? OutPackageParams.ComponentGUID = FGuid::NewGuid(); } - - OutPackageParams.bAttemptToLoadMissingPackages = true; } void UHoudiniGeoImportCommandlet::TickDiscoveredFiles() @@ -307,9 +305,6 @@ void UHoudiniGeoImportCommandlet::HandleImportBGEOMessage( FHoudiniPackageParams PackageParams; InMessage.PopulatePackageParams(PackageParams); - // The commandlet must try to load packages if FindPackage fails, since we unload packages when done - PackageParams.bAttemptToLoadMissingPackages = true; - TArray Outputs; TMap> OutputObjectAttributes; TMap InstancedOutputPartData; @@ -477,12 +472,18 @@ int32 UHoudiniGeoImportCommandlet::ImportBGEO( FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), NodeId, &DisplayGeoInfo)) { + TArray BakeFolderOverrideArray; FString BakeFolderOverride; - const bool bFoundOverride = FHoudiniEngineUtils::GetBakeFolderOverridePath(DisplayGeoInfo.nodeId, BakeFolderOverride); - if (bFoundOverride && !BakeFolderOverride.IsEmpty()) + const bool bFoundOverride = FHoudiniEngineUtils::GetBakeFolderAttribute( + DisplayGeoInfo.nodeId, HAPI_ATTROWNER_DETAIL, BakeFolderOverrideArray, 0, 1); + + if (bFoundOverride && BakeFolderOverrideArray.Num() > 0) + BakeFolderOverride = BakeFolderOverrideArray[0]; + + if (!BakeFolderOverride.IsEmpty()) { PackageParams.BakeFolder = BakeFolderOverride; - HOUDINI_LOG_DISPLAY(TEXT("Found bake folder override: %s"), *PackageParams.BakeFolder); + HOUDINI_LOG_DISPLAY(TEXT("Found bake folder override (detail attrib): %s"), *PackageParams.BakeFolder); } else { @@ -558,9 +559,9 @@ int32 UHoudiniGeoImportCommandlet::ImportBGEO( { const FHoudiniOutputObjectIdentifier OutputIdentifier = Entry.Key; TArray PropertyAttributes; - FHoudiniMeshTranslator::GetGenericPropertiesAttributes( + FHoudiniEngineUtils::GetGenericPropertiesAttributes( OutputIdentifier.GeoId, OutputIdentifier.PartId, - OutputIdentifier.PointIndex, OutputIdentifier.PrimitiveIndex, + true, OutputIdentifier.PrimitiveIndex, INDEX_NONE, OutputIdentifier.PointIndex, PropertyAttributes); OutGenericAttributes->Add(OutputIdentifier, PropertyAttributes); } diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h index d4293bb64..b6bc33ced 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h +++ b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp index 69424ffcf..13c652bc8 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp +++ b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -161,6 +161,7 @@ UHoudiniGeoImporter::BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) { + TMap AllOutputMaterials; for (auto& CurOutput : InOutputs) { if (CurOutput->GetType() != EHoudiniOutputType::Mesh) @@ -188,15 +189,26 @@ UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObj if (PackageParams.PackageMode == EPackageMode::Bake) { TArray OutputNames; - if (FHoudiniEngineUtils::GetOutputNameAttribute(CurHGPO.GeoId, CurHGPO.PartId, OutputNames)) + if (FHoudiniEngineUtils::GetOutputNameAttribute(CurHGPO.GeoId, CurHGPO.PartId, OutputNames, 0, 1)) { if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) { PackageParams.ObjectName = OutputNames[0]; } } + // Could have prim attribute unreal_bake_folder override + TArray BakeFolderNames; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(CurHGPO.GeoId, BakeFolderNames, CurHGPO.PartId, 0, 1)) + { + if (BakeFolderNames.Num() > 0 && !BakeFolderNames[0].IsEmpty()) + { + PackageParams.BakeFolder = BakeFolderNames[0]; + } + } } + FHoudiniStaticMeshGenerationProperties SMGP = FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties(); + FMeshBuildSettings MBS = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( CurHGPO, PackageParams, @@ -204,15 +216,26 @@ UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObj NewOutputObjects, AssignementMaterials, ReplacementMaterials, + AllOutputMaterials, true, - EHoudiniStaticMeshMethod::RawMesh); + EHoudiniStaticMeshMethod::RawMesh, + SMGP, + MBS); + + for (auto& CurMat : AssignementMaterials) + { + // Adds the newly generated materials to the output materials array + // This is to avoid recreating those same materials again + if (!AllOutputMaterials.Contains(CurMat.Key)) + AllOutputMaterials.Add(CurMat); + } } // Add all output objects and materials for (auto CurOutputPair : NewOutputObjects) { UObject* CurObj = CurOutputPair.Value.OutputObject; - if (!CurObj || CurObj->IsPendingKill()) + if (!IsValid(CurObj)) continue; OutputObjects.Add(CurObj); @@ -222,7 +245,7 @@ UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObj for (auto CurAssignmentMatPair : AssignementMaterials) { UObject* CurObj = CurAssignmentMatPair.Value; - if (!CurObj || CurObj->IsPendingKill()) + if (!IsValid(CurObj)) continue; OutputObjects.Add(CurObj); @@ -259,24 +282,43 @@ UHoudiniGeoImporter::CreateCurves(TArray& InOutputs, UObject* I for (auto& CurOutput : CurveOutputs) { bool bFoundOutputName = false; + bool bFoundBakeFolder = PackageParams.PackageMode != EPackageMode::Bake; for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) { if (HGPO.Type != EHoudiniPartType::Curve) continue; - TArray Strings; - if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) + if (!bFoundOutputName) { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + TArray Strings; + if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings, 0, 1)) { - PackageParams.ObjectName = Strings[0]; - bFoundOutputName = true; - break; + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.ObjectName = Strings[0]; + bFoundOutputName = true; + } } } + + if (!bFoundBakeFolder) + { + TArray Strings; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, Strings, HGPO.PartId, 0, 1)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.BakeFolder = Strings[0]; + bFoundBakeFolder = true; + } + } + } + + if (bFoundOutputName && bFoundBakeFolder) + break; } - if (bFoundOutputName) + if (bFoundOutputName && bFoundBakeFolder) break; } @@ -310,7 +352,7 @@ UHoudiniGeoImporter::CreateCurves(TArray& InOutputs, UObject* I for (auto CurOutputPair : CurOutput->GetOutputObjects()) { UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); - if (!CurObj || CurObj->IsPendingKill()) + if (!IsValid(CurObj)) continue; OutputComp.Add(CurObj); @@ -319,7 +361,11 @@ UHoudiniGeoImporter::CreateCurves(TArray& InOutputs, UObject* I // Transfer all the instancer components to the BP if (OutputComp.Num() > 0) { - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, FKismetEditorUtilities::EAddComponentToBPHarvestMode::None, nullptr, false); + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; + Params.OptionalNewRootNode = nullptr; + Params.bKeepMobility = false; + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); } } @@ -403,7 +449,7 @@ UHoudiniGeoImporter::CreateLandscapes(TArray& InOutputs, UObjec for (auto CurOutputPair : CurOutput->GetOutputObjects()) { UObject* CurObj = CurOutputPair.Value.OutputObject; - if (!CurObj || CurObj->IsPendingKill()) + if (!IsValid(CurObj)) continue; OutputObjects.Add(CurObj); @@ -443,24 +489,45 @@ UHoudiniGeoImporter::CreateInstancers(TArray& InOutputs, UObjec continue; bool bFoundOutputName = false; + bool bFoundBakeFolder = PackageParams.PackageMode != EPackageMode::Bake; for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) { if (HGPO.Type != EHoudiniPartType::Instancer) continue; - TArray Strings; - if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) + if (!bFoundOutputName) { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + TArray Strings; + if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings, 0, 1)) { - PackageParams.ObjectName = Strings[0]; - bFoundOutputName = true; - break; + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.ObjectName = Strings[0]; + bFoundOutputName = true; + break; + } } } + + if (!bFoundBakeFolder) + { + TArray Strings; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, Strings, HGPO.PartId, 0, 1)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.BakeFolder = Strings[0]; + bFoundBakeFolder = true; + break; + } + } + } + + if (bFoundOutputName && bFoundBakeFolder) + break; } - if (bFoundOutputName) + if (bFoundOutputName && bFoundBakeFolder) break; } @@ -487,14 +554,14 @@ UHoudiniGeoImporter::CreateInstancers(TArray& InOutputs, UObjec // Create all the instancers and attach them to a fake outer component FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( - CurOutput, InOutputs, OuterComponent); + CurOutput, InOutputs, OuterComponent, InPackageParams); // Prepare an ActorComponent array for AddComponentsToBlueprint() TArray OutputComp; for (auto CurOutputPair : CurOutput->GetOutputObjects()) { UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); - if (!CurObj || CurObj->IsPendingKill()) + if (!IsValid(CurObj)) continue; OutputComp.Add(CurObj); @@ -503,7 +570,11 @@ UHoudiniGeoImporter::CreateInstancers(TArray& InOutputs, UObjec // Transfer all the instancer components to the BP if (OutputComp.Num() > 0) { - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, FKismetEditorUtilities::EAddComponentToBPHarvestMode::None, nullptr, false); + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; + Params.OptionalNewRootNode = nullptr; + Params.bKeepMobility = false; + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); } } @@ -717,6 +788,7 @@ UHoudiniGeoImporter::OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNod HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( -1, "SOP/file", "bgeo", true, &OutNodeId), false); + /* // Set the file path parameter HAPI_ParmId ParmId = -1; HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( @@ -726,6 +798,11 @@ UHoudiniGeoImporter::OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNod const std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( FHoudiniEngine::Get().GetSession(), OutNodeId, ConvertedString.c_str(), ParmId, 0), false); + */ + + // Simply use LoadGeoFrom file + std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); + FHoudiniApi::LoadGeoFromFile(FHoudiniEngine::Get().GetSession(), OutNodeId, ConvertedString.c_str()); return true; } @@ -762,6 +839,7 @@ UHoudiniGeoImporter::LoadBGEOFileInHAPI(HAPI_NodeId& NodeId) HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( -1, "SOP/file", "bgeo", true, &NodeId), false); + /* // Set the file path parameter HAPI_ParmId ParmId = -1; HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( @@ -771,6 +849,11 @@ UHoudiniGeoImporter::LoadBGEOFileInHAPI(HAPI_NodeId& NodeId) std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( FHoudiniEngine::Get().GetSession(), NodeId, ConvertedString.c_str(), ParmId, 0), false); + */ + + // Simply use LoadGeoFrom file + std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); + FHoudiniApi::LoadGeoFromFile(FHoudiniEngine::Get().GetSession(), NodeId, ConvertedString.c_str()); return CookFileNode(NodeId); } @@ -816,7 +899,9 @@ bool UHoudiniGeoImporter::BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet) { // TArray OldOutputs; - if (!FHoudiniOutputTranslator::BuildAllOutputs(InNodeId, InOuter, InOldOutputs, OutNewOutputs, false)) + TArray NodeIdsToCook; + TMap OutputNodeCookCount; + if (!FHoudiniOutputTranslator::BuildAllOutputs(InNodeId, InOuter, NodeIdsToCook, OutputNodeCookCount, InOldOutputs, OutNewOutputs, false, true)) { // Couldn't create the package HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Failed to process the File SOP's outputs!")); diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImporter.h b/Source/HoudiniEngine/Private/HoudiniGeoImporter.h index 8cf5422ef..0ca0a5965 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImporter.h +++ b/Source/HoudiniEngine/Private/HoudiniGeoImporter.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp index 964f28a2f..aa1413b3c 100644 --- a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,7 +24,6 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - #include "HoudiniHandleTranslator.h" #include "HoudiniApi.h" @@ -43,7 +42,7 @@ bool FHoudiniHandleTranslator::UpdateHandles(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; TArray NewHandles; @@ -59,9 +58,11 @@ FHoudiniHandleTranslator::UpdateHandles(UHoudiniAssetComponent* HAC) } bool -FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAssetComponent* OuterObject, - TArray& CurrentHandles, - TArray& NewHandles) +FHoudiniHandleTranslator::BuildAllHandles( + const HAPI_NodeId& AssetId, + UHoudiniAssetComponent* OuterObject, + TArray& CurrentHandles, + TArray& NewHandles) { if (AssetId < 0) return false; @@ -135,13 +136,12 @@ FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAs if (HandleType == EHoudiniHandleType::Unsupported) { HOUDINI_LOG_DISPLAY(TEXT("%s: Unsupported Handle Type %s for handle %s"), - OuterObject ? *(OuterObject->GetName()) : *(OuterObject->GetName()), *TypeName, *HandleName); + OuterObject ? *(OuterObject->GetName()) : TEXT("?"), *TypeName, *HandleName); continue; } UHoudiniHandleComponent* HandleComponent = nullptr; UHoudiniHandleComponent** FoundHandleComponent = CurrentHandlesByName.Find(HandleName); - if (FoundHandleComponent) { HandleComponent = *FoundHandleComponent; @@ -151,7 +151,8 @@ FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAs } else { - HandleComponent = NewObject(OuterObject, + HandleComponent = NewObject( + OuterObject, UHoudiniHandleComponent::StaticClass(), NAME_None, RF_Public | RF_Transactional); @@ -160,7 +161,6 @@ FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAs // Change the creation method so the component is listed in the details panels HandleComponent->CreationMethod = EComponentCreationMethod::Instance; - } if (!HandleComponent) @@ -182,17 +182,18 @@ FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAs TArray BindingInfos; BindingInfos.SetNumZeroed(HandleInfo.bindingsCount); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetHandleBindingInfo(FHoudiniEngine::Get().GetSession(), - AssetId, HandleIdx, &BindingInfos[0], 0, HandleInfo.bindingsCount)) - continue; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetHandleBindingInfo( + FHoudiniEngine::Get().GetSession(), + AssetId, HandleIdx, &BindingInfos[0], 0, HandleInfo.bindingsCount)) + continue; HAPI_TransformEuler HapiEulerXform; - FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); + FMemory::Memzero(HapiEulerXform); HapiEulerXform.position[0] = HapiEulerXform.position[1] = HapiEulerXform.position[2] = 0.0f; HapiEulerXform.rotationEuler[0] = HapiEulerXform.rotationEuler[1] = HapiEulerXform.rotationEuler[2] = 0.0f; HapiEulerXform.scale[0] = HapiEulerXform.scale[1] = HapiEulerXform.scale[2] = 1.0f; - TSharedPtr< FString > RSTOrderStrPtr, XYZOrderStrPtr; + TSharedPtr RSTOrderStrPtr, XYZOrderStrPtr; for (const auto& BindingInfo : BindingInfos) { @@ -237,7 +238,6 @@ FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAs ); } - HapiEulerXform.rstOrder = GetHapiRSTOrder(RSTOrderStrPtr); HapiEulerXform.rotationOrder = GetHapiXYZOrder(XYZOrderStrPtr); @@ -265,7 +265,7 @@ FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAs void FHoudiniHandleTranslator::ClearHandles(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; for (auto& HandleComponent : HAC->HandleComponents) @@ -321,7 +321,7 @@ FHoudiniHandleTranslator::GetHapiXYZOrder(const TSharedPtr & StrPtr) void FHoudiniHandleTranslator::UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent) { - if (!HandleComponent || HandleComponent->IsPendingKill()) + if (!IsValid(HandleComponent)) return; if (!HandleComponent->CheckHandleValid()) diff --git a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h index 92945fc9e..93bf71fbe 100644 --- a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -27,6 +27,9 @@ #pragma once #include "HAPI/HAPI_Common.h" +#include "HoudiniApi.h" + +#include "Templates/SharedPointer.h" class UHoudiniAssetComponent; class UHoudiniHandleComponent; diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp index 140ccaf1d..48045999e 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,6 +46,7 @@ #include "UnrealMeshTranslator.h" #include "UnrealInstanceTranslator.h" #include "UnrealLandscapeTranslator.h" +#include "UnrealFoliageTypeTranslator.h" #include "Engine/StaticMesh.h" #include "Engine/SkeletalMesh.h" @@ -56,6 +57,7 @@ #include "Engine/Brush.h" #include "Engine/DataTable.h" #include "Camera/CameraComponent.h" +#include "FoliageType_InstancedStaticMesh.h" #include "Engine/SimpleConstructionScript.h" #include "Engine/SCS_Node.h" @@ -67,7 +69,7 @@ #endif #include "HCsgUtils.h" - +#include "LandscapeInfo.h" #include "Async/Async.h" #define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE @@ -94,7 +96,7 @@ struct FHoudiniMoveTracker bool FHoudiniInputTranslator::UpdateInputs(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) @@ -135,11 +137,22 @@ FHoudiniInputTranslator::BuildAllInputs( } */ // Also look for object path parameters inputs + + // Helper map to get the parameter index, given the parameter name + TMap ParameterNameToIndexMap; + TArray> InputParameters; for (auto Param : Parameters) { + if (!Param) + continue; + if (Param->GetParameterType() == EHoudiniParameterType::Input) + { + int InsertionIndex = InputParameters.Num(); + ParameterNameToIndexMap.Add(Param->GetParameterName(), InsertionIndex); InputParameters.Add(Param); + } } InputCount += InputParameters.Num(); @@ -157,7 +170,7 @@ FHoudiniInputTranslator::BuildAllInputs( FName(*InputObjectName), RF_Transactional); - if (!NewInput || NewInput->IsPendingKill()) + if (!IsValid(NewInput)) { //HOUDINI_LOG_WARNING("Failed to create asset input"); continue; @@ -174,7 +187,7 @@ FHoudiniInputTranslator::BuildAllInputs( for (int32 InputIdx = Inputs.Num() - 1; InputIdx >= InputCount; InputIdx--) { UHoudiniInput* CurrentInput = Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; FHoudiniInputTranslator::DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); @@ -188,13 +201,69 @@ FHoudiniInputTranslator::BuildAllInputs( Inputs.SetNum(InputCount); } + // Input index -> InputParameter index + // Special values: -1 = SOP input. Ignore completely. -2 = To be determined later + // Used to preserve inputs after insertion/deletion + TArray InputIdxToInputParamIndex; + InputIdxToInputParamIndex.SetNum(Inputs.Num()); + + // Keep a set of used indices, to figure out the unused indices later + TSet UsedParameterIndices; + + for (int32 InputIdx = 0; InputIdx < Inputs.Num(); InputIdx++) + { + // SOP input -> Parameter map doesn't make sense - ignore this + if (InputIdx < AssetInfo.geoInputCount) + { + // Ignore completely + InputIdxToInputParamIndex[InputIdx] = -1; + } + else + { + UHoudiniInput* CurrentInput = Inputs[InputIdx]; + if (!IsValid(CurrentInput)) + continue; + + if (ParameterNameToIndexMap.Contains(CurrentInput->GetName())) + { + const int32 ParameterIndex = ParameterNameToIndexMap[CurrentInput->GetName()]; + InputIdxToInputParamIndex[InputIdx] = ParameterIndex; + UsedParameterIndices.Add(ParameterIndex); + } + else + { + // To be determined in the second pass + InputIdxToInputParamIndex[InputIdx] = -2; + } + } + } + + // Second pass for InputIdxToInputParamIndex + // Fill in the inputs that could not be mapped onto old inputs. Used when inserting a new element. + for (int32 NewInputIndex = 0; NewInputIndex < Inputs.Num(); NewInputIndex++) + { + if (InputIdxToInputParamIndex[NewInputIndex] == -2) + { + // Find the first free index + for (int32 FreeIdx = 0; FreeIdx < InputParameters.Num(); FreeIdx++) + { + if (!UsedParameterIndices.Contains(FreeIdx)) + { + InputIdxToInputParamIndex[NewInputIndex] = FreeIdx; + UsedParameterIndices.Add(FreeIdx); + break; + } + } + } + } + // Now, check the inputs in the array match the geo inputs //for (int32 GeoInIdx = 0; GeoInIdx < AssetInfo.geoInputCount; GeoInIdx++) bool bBlueprintStructureChanged = false; for (int32 InputIdx = 0; InputIdx < Inputs.Num(); InputIdx++) { UHoudiniInput* CurrentInput = Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; // Create default Name/Label/Help @@ -225,7 +294,7 @@ FHoudiniInputTranslator::BuildAllInputs( else { // Get this input's parameter index in the objpath param array - int32 CurrentParmIdx = InputIdx - AssetInfo.geoInputCount; + int32 CurrentParmIdx = InputIdxToInputParamIndex[InputIdx]; UHoudiniParameter* CurrentParm = nullptr; if (InputParameters.IsValidIndex(CurrentParmIdx)) @@ -235,7 +304,7 @@ FHoudiniInputTranslator::BuildAllInputs( } int32 ParmId = -1; - if (CurrentParm && !CurrentParm->IsPendingKill()) + if (IsValid(CurrentParm)) { ParmId = CurrentParm->GetParmId(); CurrentInputName = CurrentParm->GetParameterName(); @@ -244,7 +313,7 @@ FHoudiniInputTranslator::BuildAllInputs( } UHoudiniParameterOperatorPath* CurrentObjPathParm = Cast(CurrentParm); - if (CurrentObjPathParm && !CurrentObjPathParm->IsPendingKill()) + if (IsValid(CurrentObjPathParm)) { CurrentObjPathParm->HoudiniInput = CurrentInput; } @@ -270,7 +339,7 @@ FHoudiniInputTranslator::BuildAllInputs( CurrentInput->SetInputType(GetDefaultInputTypeFromLabel(CurrentInputLabel), bBlueprintStructureChanged); // Preset the default HDA for objpath input - SetDefaultAssetFromHDA(CurrentInput); + SetDefaultAssetFromHDA(CurrentInput, bBlueprintStructureChanged); } // Update input objects data on UE side for all types of inputs. @@ -301,7 +370,7 @@ FHoudiniInputTranslator::BuildAllInputs( bool FHoudiniInputTranslator::DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) { - if (!InputToDestroy || InputToDestroy->IsPendingKill()) + if (!IsValid(InputToDestroy)) return false; // Start by disconnecting the input / nullifying the object path parameter @@ -348,7 +417,7 @@ FHoudiniInputTranslator::DisconnectInput(UHoudiniInput* InputToDestroy, const EH bool FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) { - if (!InputToDestroy || InputToDestroy->IsPendingKill()) + if (!IsValid(InputToDestroy)) return false; if (!InputToDestroy->CanDeleteHoudiniNodes()) @@ -366,7 +435,7 @@ FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const { for (auto CurInputObject : *InputObjectNodes) { - if (!CurInputObject || CurInputObject->IsPendingKill()) + if (!IsValid(CurInputObject)) continue; if (CurInputObject->Type == EHoudiniInputObjectType::HoudiniAssetComponent) @@ -385,9 +454,9 @@ FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const UHoudiniInputActor* CurActorInputObject = Cast(CurInputObject); if (CurActorInputObject) { - for (auto & CurActorComponent : CurActorInputObject->ActorComponents) + for (auto & CurActorComponent : CurActorInputObject->GetActorComponents()) { - if (!CurActorComponent || CurActorComponent->IsPendingKill()) + if (!IsValid(CurActorComponent)) continue; // No need to delete the nodes created for an asset component manually here, @@ -423,7 +492,7 @@ FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const if (IsValid(HoudiniSplineInputObject) && !IsGarbageCollecting()) { UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (SplineComponent && !SplineComponent->IsPendingKill()) + if (IsValid(SplineComponent)) { SplineComponent->SetNodeId(-1); } @@ -519,7 +588,7 @@ FHoudiniInputTranslator::GetDefaultInputTypeFromLabel(const FString& InputName) bool FHoudiniInputTranslator::ChangeInputType(UHoudiniInput* InInput, const bool& bForce) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; if (!InInput->HasInputTypeChanged() && !bForce) @@ -544,24 +613,16 @@ FHoudiniInputTranslator::ChangeInputType(UHoudiniInput* InInput, const bool& bFo } bool -FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input) +FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input, bool& bOutBlueprintStructureModified) { // - if (!Input || Input->IsPendingKill()) - return false; - - // We just handle geo inputs - if (EHoudiniInputType::Geometry != Input->GetInputType()) + if (!IsValid(Input)) return false; // Make sure we're linked to a valid object path parameter if (Input->GetParameterId() < 0) return false; - // There is a default slot, don't add if slot is already filled - //if (InputObjects.Num() > 1) - // return false; - // Get our ParmInfo HAPI_ParmInfo FoundParamInfo; FHoudiniApi::ParmInfo_Init(&FoundParamInfo); @@ -572,50 +633,153 @@ FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input) return false; } - // TODO: FINISH ME! - - /* // Get our string value HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - Input->GetInputNodeId(), false, - &StringHandle, FoundParamInfo.stringValuesIndex, 1) ) - { - FString OutValue; - FHoudiniEngineString HoudiniEngineString(StringHandle); - if (HoudiniEngineString.ToFString(OutValue)) - { - // Set default object on the HDA instance - will override the parameter string - // and apply the object input local-path thing for the HDA cook. - if (OutValue.Len() > 0) - { - UObject * pObject = LoadObject(nullptr, *OutValue); - if (pObject) - { - return AddInputObject(pObject); - } - } + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + Input->GetAssetNodeId(), + false, + &StringHandle, + FoundParamInfo.stringValuesIndex, + 1)) + { + return false; + } + + FString ParamValue; + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (!HoudiniEngineString.ToFString(ParamValue)) + { + return false; + } + + if (ParamValue.Len() <= 0) + { + return false; + } + + // Chop the default value using semi-colons as separators + TArray Tokens; + ParamValue.ParseIntoArray(Tokens, TEXT(";"), true); + + // Start by setting geometry input objects + int32 GeoIdx = 0; + for (auto& CurToken : Tokens) + { + if (CurToken.IsEmpty()) + continue; + + // Set default objects on the HDA instance - will override the parameter string + // and apply the object input local-path thing for the HDA cook. + UObject * pObject = LoadObject(nullptr, *CurToken); + if (!pObject) + continue; + + Input->SetInputObjectAt(EHoudiniInputType::Geometry, GeoIdx++, pObject); + } + + // See if we can preset world objects as well + int32 WorldIdx = 0; + int32 LandscapedIdx = 0; + int32 HDAIdx = 0; + for (TActorIterator ActorIt(Input->GetWorld(), AActor::StaticClass(), EActorIteratorFlags::SkipPendingKill); ActorIt; ++ActorIt) + { + AActor* CurActor = *ActorIt; + if (!CurActor) + continue; + + AActor* FoundActor = nullptr; + int32 FoundIdx = Tokens.Find(CurActor->GetFName().ToString()); + if (FoundIdx == INDEX_NONE) + FoundIdx = Tokens.Find(CurActor->GetActorLabel()); + + if(FoundIdx != INDEX_NONE) + FoundActor = CurActor; + + if (!FoundActor) + continue; + + // Select the found actor in the world input + Input->SetInputObjectAt(EHoudiniInputType::World, WorldIdx++, FoundActor); + + if (FoundActor->IsA()) + { + // Select the HDA in the asset input + Input->SetInputObjectAt(EHoudiniInputType::Asset, HDAIdx++, FoundActor); + } + else if (FoundActor->IsA()) + { + // Select the landscape in the landscape input + Input->SetInputObjectAt(EHoudiniInputType::Landscape, LandscapedIdx++, FoundActor); } + + // Remove the Found Token + Tokens.RemoveAt(FoundIdx); } - */ - return false; + // See if we should change the default input type + if (Input->GetInputType() == EHoudiniInputType::Geometry && WorldIdx > 0 && GeoIdx == 0) + { + if (LandscapedIdx == WorldIdx) + { + // We've only selected landscapes, set to landscape IN + Input->SetInputType(EHoudiniInputType::Landscape, bOutBlueprintStructureModified); + } + else if (HDAIdx == WorldIdx) + { + // We've only selected Houdini Assets, set to Asset IN + Input->SetInputType(EHoudiniInputType::Asset, bOutBlueprintStructureModified); + } + else + { + // Set to world input + Input->SetInputType(EHoudiniInputType::World, bOutBlueprintStructureModified); + } + } + + return true; } bool FHoudiniInputTranslator::UploadChangedInputs(UHoudiniAssetComponent * HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; //for (auto CurrentInput : HAC->Inputs) for(int32 InputIdx = 0; InputIdx < HAC->GetNumInputs(); InputIdx++) { UHoudiniInput*& CurrentInput = HAC->Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill() || !CurrentInput->HasChanged()) + if (!IsValid(CurrentInput) || !CurrentInput->HasChanged()) continue; + // Delete any previous InputNodeIds of this HoudiniInput that are pending delete + for (const HAPI_NodeId InputNodeIdPendingDelete : CurrentInput->GetInputNodesPendingDelete()) + { + if (InputNodeIdPendingDelete < 0) + continue; + + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InputNodeIdPendingDelete, &NodeInfo)) + { + continue; + } + + HAPI_NodeId NodeToDelete = InputNodeIdPendingDelete; + if (NodeInfo.type == HAPI_NODETYPE_SOP) + { + // Input nodes are Merge SOPs in a geo object, delete the geo object + const HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeIdPendingDelete); + NodeToDelete = ParentId != -1 ? ParentId : InputNodeIdPendingDelete; + } + + HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), NodeToDelete)); + } + CurrentInput->ClearInputNodesPendingDelete(); + // First thing, see if we need to change the input type if (CurrentInput->HasInputTypeChanged()) { @@ -632,7 +796,14 @@ FHoudiniInputTranslator::UploadChangedInputs(UHoudiniAssetComponent * HAC) bool bSuccess = true; if (CurrentInput->IsDataUploadNeeded()) { - bSuccess &= UploadInputData(CurrentInput); + FTransform OwnerTransform = FTransform::Identity; + AActor * OwnerActor = HAC->GetOwner(); + if (OwnerActor) + { + OwnerTransform = OwnerActor->GetTransform(); + } + + bSuccess &= UploadInputData(CurrentInput, OwnerTransform); CurrentInput->MarkDataUploadNeeded(!bSuccess); } @@ -675,7 +846,7 @@ FHoudiniInputTranslator::UpdateInputProperties(UHoudiniInput* InInput) bool FHoudiniInputTranslator::UpdateTransformType(UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; bool nTransformType = InInput->GetKeepWorldTransform(); @@ -690,7 +861,7 @@ FHoudiniInputTranslator::UpdateTransformType(UHoudiniInput* InInput) HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); bool bSuccess = true; - const std::string sXformType = "xformtype"; + const std::string sXformType = "xformtype"; if (InInput->IsObjectPathParameter()) { // Directly change the Parameter xformtype @@ -748,7 +919,7 @@ FHoudiniInputTranslator::UpdateTransformType(UHoudiniInput* InInput) bool FHoudiniInputTranslator::UpdatePackBeforeMerge(UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; // Pack before merge is only available for Geo/World input @@ -801,7 +972,7 @@ FHoudiniInputTranslator::UpdatePackBeforeMerge(UHoudiniInput* InInput) bool FHoudiniInputTranslator::UpdateTransformOffset(UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; // Transform offsets are only for geometry inputs @@ -821,7 +992,7 @@ FHoudiniInputTranslator::UpdateTransformOffset(UHoudiniInput* InInput) for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) { UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + if (!IsValid(CurrentInputObject)) continue; // If the Input mesh has a Transform offset @@ -843,9 +1014,9 @@ FHoudiniInputTranslator::UpdateTransformOffset(UHoudiniInput* InInput) } bool -FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) +FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput, const FTransform & InActorTransform) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; EHoudiniInputType InputType = InInput->GetInputType(); @@ -856,54 +1027,30 @@ FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) // Iterate on all the input objects and see if they need to be uploaded bool bSuccess = true; TArray CreatedNodeIds; + TArray ValidNodeIds; + TArray ChangedInputObjects; for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) { UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + if (!IsValid(CurrentInputObject)) continue; - int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; - if (!CurrentInputObject->HasChanged() && CurrentInputObjectNodeId >= 0) - { - // If this object hasn't changed, no need to upload it - // but we need to keep its created input node - if (CurrentInputObject->Type == EHoudiniInputObjectType::Actor) - { - // If this input object is an actor, it actually contains other input - // objects for each of his components, keep them as well - UHoudiniInputActor* InputActor = Cast(CurrentInputObject); - if (InputActor && !InputActor->IsPendingKill()) - { - for (auto CurrentComp : InputActor->ActorComponents) - { - if (!CurrentComp || CurrentComp->IsPendingKill()) - continue; + ValidNodeIds.Reset(); + ChangedInputObjects.Reset(); + // The input object could have child objects: GetChangedObjectsAndValidNodes finds if the object itself or + // any its children has changed, and also returns the NodeIds of those objects that are still valid and + // unchanged + CurrentInputObject->GetChangedObjectsAndValidNodes(ChangedInputObjects, ValidNodeIds); - int32& CurrentCompNodeId = CurrentComp->InputObjectNodeId; - if (!CurrentComp->HasChanged() && CurrentCompNodeId >= 0) - { - // If the component hasnt changed and is valid, keep it - CreatedNodeIds.Add(CurrentCompNodeId); - } - else - { - // Upload the component input object to Houdini - if (!UploadHoudiniInputObject(InInput, CurrentComp, CreatedNodeIds)) - bSuccess = false; - } - } - } - } - else - { - // No changes, keep it - CreatedNodeIds.Add(CurrentInputObjectNodeId); - } - } - else + // Keep track of the node ids for unchanged objects that already exist + if (ValidNodeIds.Num() > 0) + CreatedNodeIds.Append(ValidNodeIds); + + // Upload the changed input objects + for (UHoudiniInputObject* ChangedInputObject : ChangedInputObjects) { // Upload the current input object to Houdini - if (!UploadHoudiniInputObject(InInput, CurrentInputObject, CreatedNodeIds)) + if (!UploadHoudiniInputObject(InInput, ChangedInputObject, InActorTransform, CreatedNodeIds)) bSuccess = false; } } @@ -936,7 +1083,7 @@ FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) { if (InputNodeId >= 0) { - for (int32 Idx = 0; Idx < PreviousInputObjectNodeIds.Num(); Idx++) + for (int32 Idx = PreviousInputObjectNodeIds.Num() - 1; Idx >= 0; --Idx) { // Get the object merge connected to the merge node @@ -980,7 +1127,7 @@ FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) { FTransform ComponentTransform = FTransform::Identity; USceneComponent* OuterComp = Cast(InInput->GetOuter()); - if (OuterComp && !OuterComp->IsPendingKill()) + if (IsValid(OuterComp)) ComponentTransform = OuterComp->GetComponentTransform(); FHoudiniEngineUtils::HapiSetAssetTransform(InputNodeId, ComponentTransform); @@ -1008,7 +1155,7 @@ FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) TArray& PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); if (!InInput->HasInputTypeChanged()) { - for (int32 Idx = CreatedNodeIds.Num(); Idx < PreviousInputObjectNodeIds.Num(); Idx++) + for (int32 Idx = PreviousInputObjectNodeIds.Num() - 1; Idx >= CreatedNodeIds.Num(); --Idx) { // Get the object merge connected to the merge node HAPI_NodeId InputObjectMergeId = -1; @@ -1041,7 +1188,7 @@ FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) bool FHoudiniInputTranslator::UploadInputTransform(UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; EHoudiniInputType InputType = InInput->GetInputType(); @@ -1054,7 +1201,7 @@ FHoudiniInputTranslator::UploadInputTransform(UHoudiniInput* InInput) for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) { UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + if (!IsValid(CurrentInputObject)) continue; int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; @@ -1075,7 +1222,7 @@ FHoudiniInputTranslator::UploadInputTransform(UHoudiniInput* InInput) bool FHoudiniInputTranslator::ConnectInputNode(UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; HAPI_NodeId AssetNodeId = InInput->GetAssetNodeId(); @@ -1114,6 +1261,7 @@ bool FHoudiniInputTranslator::UploadHoudiniInputObject( UHoudiniInput* InInput, UHoudiniInputObject* InInputObject, + const FTransform& InActorTransform, TArray& OutCreatedNodeIds) { if (!InInput || !InInputObject) @@ -1139,7 +1287,7 @@ FHoudiniInputTranslator::UploadHoudiniInputObject( UHoudiniInputStaticMesh* InputSM = Cast(InInputObject); bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( ObjBaseName, InputSM, InInput->GetExportLODs(), InInput->GetExportSockets(), - InInput->GetExportColliders(), InInput->GetImportAsReference()); + InInput->GetExportColliders(), InInput->GetImportAsReference(), InInput->GetImportAsReferenceRotScaleEnabled()); if (bSuccess) { @@ -1184,7 +1332,15 @@ FHoudiniInputTranslator::UploadHoudiniInputObject( { UHoudiniInputMeshComponent* InputSMC = Cast(InInputObject); bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( - ObjBaseName, InputSMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders(), InInput->GetImportAsReference()); + ObjBaseName, + InputSMC, + InInput->GetExportLODs(), + InInput->GetExportSockets(), + InInput->GetExportColliders(), + InInput->GetKeepWorldTransform(), + InInput->GetImportAsReference(), + InInput->GetImportAsReferenceRotScaleEnabled(), + InActorTransform); if (bSuccess) OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); @@ -1218,11 +1374,12 @@ FHoudiniInputTranslator::UploadHoudiniInputObject( case EHoudiniInputObjectType::HoudiniSplineComponent: { UHoudiniInputHoudiniSplineComponent* InputCurve = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent(ObjBaseName, InputCurve); - + + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent(ObjBaseName, InputCurve, InInput->IsAddRotAndScaleAttributesEnabled()); + if (bSuccess) OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - + break; } @@ -1230,7 +1387,7 @@ FHoudiniInputTranslator::UploadHoudiniInputObject( case EHoudiniInputObjectType::HoudiniAssetComponent: { UHoudiniInputHoudiniAsset* InputHAC = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniAssetComponent(ObjBaseName, InputHAC, InInput->GetImportAsReference()); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniAssetComponent(ObjBaseName, InputHAC, InInput->GetKeepWorldTransform(), InInput->GetImportAsReference(), InInput->GetImportAsReferenceRotScaleEnabled()); if (bSuccess) OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); @@ -1241,7 +1398,8 @@ FHoudiniInputTranslator::UploadHoudiniInputObject( case EHoudiniInputObjectType::Actor: { UHoudiniInputActor* InputActor = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForActor(InInput, InputActor, OutCreatedNodeIds); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForActor(InInput, InputActor, + InActorTransform, OutCreatedNodeIds); break; } @@ -1289,6 +1447,19 @@ FHoudiniInputTranslator::UploadHoudiniInputObject( break; } + case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + { + UHoudiniInputFoliageType_InstancedStaticMesh* const InputFoliageTypeSM = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + ObjBaseName, InputFoliageTypeSM, InInput->GetExportLODs(), InInput->GetExportSockets(), + InInput->GetExportColliders(), InInput->GetImportAsReference(), InInput->GetImportAsReferenceRotScaleEnabled()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + case EHoudiniInputObjectType::Invalid: //default: break; @@ -1344,17 +1515,22 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( break; } + case EHoudiniInputObjectType::StaticMeshComponent: + case EHoudiniInputObjectType::SplineComponent: { - // Update using the static mesh component's transform - UHoudiniInputMeshComponent* InSMC = Cast(InInputObject); - if (!InSMC || InSMC->IsPendingKill()) + // Default behaviour for components derived from SceneComponent. + + // Update using the component's transform + UHoudiniInputSceneComponent* InComponent = Cast(InInputObject); + if (!IsValid(InComponent)) { bSuccess = false; break; } - FTransform NewTransform = InSMC->GetStaticMeshComponent() ? InSMC->GetStaticMeshComponent()->GetComponentTransform() : InInputObject->Transform; + const USceneComponent* SceneComponent = InComponent->GetSceneComponent(); + const FTransform NewTransform = IsValid(SceneComponent) ? SceneComponent->GetComponentTransform() : InInputObject->Transform; if(!UpdateTransform(NewTransform, InInputObject->InputObjectNodeId)) bSuccess = false; @@ -1386,7 +1562,7 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( case EHoudiniInputObjectType::Actor: { UHoudiniInputActor* InputActor = Cast(InInputObject); - if (!InputActor || InputActor->IsPendingKill()) + if (!IsValid(InputActor)) { bSuccess = false; break; @@ -1399,9 +1575,9 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( // Iterate on all the actor input objects and see if their transform needs to be uploaded // TODO? Also update the component's actor transform?? - for (auto& CurrentComponent : InputActor->ActorComponents) + for (auto& CurrentComponent : InputActor->GetActorComponents()) { - if (!CurrentComponent || CurrentComponent->IsPendingKill()) + if (!IsValid(CurrentComponent)) continue; if (!CurrentComponent->HasTransformChanged()) @@ -1416,11 +1592,11 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( } break; } - + case EHoudiniInputObjectType::SceneComponent: { UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); - if (!InputSceneComp || InputSceneComp->IsPendingKill()) + if (!IsValid(InputSceneComp)) { bSuccess = false; break; @@ -1437,7 +1613,7 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( { // UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); - if (!InputLandscape || InputLandscape->IsPendingKill()) + if (!IsValid(InputLandscape)) { bSuccess = false; break; @@ -1445,49 +1621,51 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( // ALandscapeProxy* Landscape = InputLandscape->GetLandscapeProxy(); - if (!Landscape || Landscape->IsPendingKill()) - { - bSuccess = false; - break; - } - - // Only apply diff for landscape since the HF's transform is used for value conversion as well - FTransform CurrentTransform = InputLandscape->Transform; - FTransform NewTransform = Landscape->ActorToWorld(); - - // Only handle position/rotation differences - FVector PosDiff = NewTransform.GetLocation() - CurrentTransform.GetLocation(); - FQuat RotDiff = NewTransform.GetRotation() - CurrentTransform.GetRotation(); - - // Now get the HF's current transform - HAPI_Transform HapiTransform; - FHoudiniApi::Transform_Init(&HapiTransform); - - if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetObjectTransform( - FHoudiniEngine::Get().GetSession(), - InputLandscape->InputObjectNodeId, -1, HAPI_SRT, &HapiTransform)) + if (!IsValid(Landscape)) { bSuccess = false; break; } - // Convert it to unreal - FTransform HFTransform; - FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, HFTransform); + // // Only apply diff for landscape since the HF's transform is used for value conversion as well + // FTransform CurrentTransform = InputLandscape->Transform; + const FTransform NewTransform = Landscape->ActorToWorld(); - // Apply the position offset if needed - if (!PosDiff.IsZero()) - HFTransform.AddToTranslation(PosDiff); - - // Apply the rotation offset if needed - if (!RotDiff.IsIdentity()) - HFTransform.ConcatenateRotation(RotDiff); + // // Only handle position/rotation differences + // FVector PosDiff = NewTransform.GetLocation() - CurrentTransform.GetLocation(); + // FQuat RotDiff = NewTransform.GetRotation() - CurrentTransform.GetRotation(); + // + // // Now get the HF's current transform + // HAPI_Transform HapiTransform; + // FHoudiniApi::Transform_Init(&HapiTransform); + // + // if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetObjectTransform( + // FHoudiniEngine::Get().GetSession(), + // InputLandscape->InputObjectNodeId, -1, HAPI_SRT, &HapiTransform)) + // { + // bSuccess = false; + // break; + // } + // + // // Convert it to unreal + // FTransform HFTransform; + // FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, HFTransform); + // + // // Apply the position offset if needed + // if (!PosDiff.IsZero()) + // HFTransform.AddToTranslation(PosDiff); + // + // // Apply the rotation offset if needed + // if (!RotDiff.IsIdentity()) + // HFTransform.ConcatenateRotation(RotDiff); // Convert back to a HAPI Transform and update the HF's transform HAPI_TransformEuler NewHAPITransform; FHoudiniApi::TransformEuler_Init(&NewHAPITransform); - FHoudiniEngineUtils::TranslateUnrealTransform(HFTransform, NewHAPITransform); - NewHAPITransform.position[1] = 0.0f; + // FHoudiniEngineUtils::TranslateUnrealTransform(HFTransform, NewHAPITransform); + FHoudiniEngineUtils::TranslateUnrealTransform( + FTransform(NewTransform.GetRotation(), NewTransform.GetTranslation(), FVector::OneVector), NewHAPITransform); + // NewHAPITransform.position[1] = 0.0f; if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetObjectTransform( FHoudiniEngine::Get().GetSession(), InputLandscape->InputObjectNodeId, &NewHAPITransform)) @@ -1506,10 +1684,18 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( break; } + case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + { + // Simply update the Input mesh's Transform offset + if (!UpdateTransform(InInputObject->Transform, InInputObject->InputObjectNodeId)) + bSuccess = false; + + break; + } + // Unsupported case EHoudiniInputObjectType::Object: case EHoudiniInputObjectType::SkeletalMesh: - case EHoudiniInputObjectType::SplineComponent: { break; } @@ -1541,7 +1727,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForObject(const FString& InObjNodeNa return false; UObject* Object = InObject->GetObject(); - if (!Object || Object->IsPendingKill()) + if (!IsValid(Object)) return true; FString NodeName = InObjNodeName + TEXT("_") + Object->GetName(); @@ -1635,9 +1821,10 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( const bool& bExportLODs, const bool& bExportSockets, const bool& bExportColliders, - const bool& bImportAsReference) + const bool& bImportAsReference, + const bool& bImportAsReferenceRotScaleEnabled) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; UBlueprint* BP = nullptr; @@ -1649,7 +1836,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( if (InObject->bIsBlueprint()) { BP = InObject->GetBlueprint(); - if (!BP || BP->IsPendingKill()) + if (!IsValid(BP)) return true; SMName += BP->GetName(); @@ -1657,7 +1844,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( else { SM = InObject->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) + if (!IsValid(SM)) return true; SMName += SM->GetName(); @@ -1690,7 +1877,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( AssetReference += FString("'"); bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference( - InObject->InputNodeId, AssetReference, SMName, InObject->Transform); + InObject->InputNodeId, AssetReference, SMName, InObject->Transform, bImportAsReferenceRotScaleEnabled); } else { @@ -1700,24 +1887,24 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( if (BP) { USimpleConstructionScript* SCS = BP->SimpleConstructionScript; - if (SCS && !SCS->IsPendingKill()) + if (IsValid(SCS)) { const TArray& Nodes = SCS->GetAllNodes(); for (auto & CurNode : Nodes) { - if (!CurNode || CurNode->IsPendingKill()) + if (!IsValid(CurNode)) continue; UActorComponent * CurComp = CurNode->ComponentTemplate; - if (!CurComp || CurComp->IsPendingKill()) + if (!IsValid(CurComp)) continue; UStaticMeshComponent* CurSMC = Cast(CurComp); - if (!CurSMC || CurSMC->IsPendingKill()) + if (!IsValid(CurSMC)) continue; UStaticMesh* CurSM = CurSMC->GetStaticMesh(); - if (CurSM && !CurSM->IsPendingKill()) + if (IsValid(CurSM)) StaticMeshComponents.Add(CurSMC); } @@ -1732,13 +1919,13 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( { for (auto & CurSMC : StaticMeshComponents) { - if (!CurSMC || CurSMC->IsPendingKill()) + if (!IsValid(CurSMC)) continue; UHoudiniInputStaticMesh* SMObject = Cast( UHoudiniInputObject::CreateTypedInputObject(CurSMC->GetStaticMesh(), InObject, InObject->GetName() + TEXT("_") + CurSMC->GetName())); - if (!SMObject || SMObject->IsPendingKill()) + if (!IsValid(SMObject)) continue; bSuccess &= FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( @@ -1801,11 +1988,11 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( bool FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; USkeletalMesh* SkelMesh = InObject->GetSkeletalMesh(); - if (!SkelMesh || SkelMesh->IsPendingKill()) + if (!IsValid(SkelMesh)) return true; // Get the SM's transform offset @@ -1821,11 +2008,11 @@ FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(const FString& InObj bool FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; USceneComponent* SceneComp = InObject->GetSceneComponent(); - if (!SceneComp || SceneComp->IsPendingKill()) + if (!IsValid(SceneComp)) return true; // Get the Scene Component's transform @@ -1849,18 +2036,21 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( const bool& bExportLODs, const bool& bExportSockets, const bool& bExportColliders, - const bool& bImportAsReference) + const bool& bKeepWorldTransform, + const bool& bImportAsReference, + const bool& bImportAsReferenceRotScaleEnabled, + const FTransform& InActorTransform) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; UStaticMeshComponent* SMC = InObject->GetStaticMeshComponent(); - if (!SMC || SMC->IsPendingKill()) + if (!IsValid(SMC)) return true; // Get the component's Static Mesh - UStaticMesh* SM = InObject->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) + UStaticMesh* SM = SMC->GetStaticMesh(); + if (!IsValid(SM)) return true; // Marshall the Static Mesh to Houdini @@ -1885,7 +2075,18 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( // Attach another '\'' to the end AssetReference += FString("'"); - bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InObject->InputNodeId, SMCName, AssetReference, InObject->Transform); + FTransform ImportAsReferenceTransform = InObject->Transform; + + if (!bKeepWorldTransform) + { + ImportAsReferenceTransform.SetLocation(FVector::ZeroVector); + } + else + { + ImportAsReferenceTransform *= InActorTransform.Inverse(); + } + + bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InObject->InputNodeId, AssetReference, SMCName, ImportAsReferenceTransform, bImportAsReferenceRotScaleEnabled); } else @@ -1927,16 +2128,16 @@ FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( const bool& bExportSockets, const bool& bExportColliders) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; UObject* Object = InObject->GetObject(); - if (!Object || Object->IsPendingKill()) + if (!IsValid(Object)) return true; // Get the ISMC UInstancedStaticMeshComponent* ISMC = InObject->GetInstancedStaticMeshComponent(); - if (!ISMC || ISMC->IsPendingKill()) + if (!IsValid(ISMC)) return true; HAPI_NodeId NewNodeId = -1; @@ -1957,11 +2158,11 @@ FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( bool FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; USplineComponent* Spline = InObject->GetSplineComponent(); - if (!Spline || Spline->IsPendingKill()) + if (!IsValid(Spline)) return true; @@ -2005,16 +2206,16 @@ FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(const FString& In bool FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, UHoudiniInputHoudiniSplineComponent* InObject) + const FString& InObjNodeName, UHoudiniInputHoudiniSplineComponent* InObject, bool bInAddRotAndScaleAttributes) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; UHoudiniSplineComponent* Curve = InObject->GetCurveComponent(); - if (!Curve || Curve->IsPendingKill()) + if (!IsValid(Curve)) return true; - if (!FHoudiniSplineTranslator::HapiCreateInputNodeForHoudiniSplineComponent(InObjNodeName, Curve)) + if (!FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(Curve, bInAddRotAndScaleAttributes)) return false; // See if the component needs it node Id invalidated @@ -2037,24 +2238,24 @@ FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent( bool FHoudiniInputTranslator:: -HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference) +HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool bKeepWorldTransform, const bool& bImportAsReference, const bool& bImportAsReferenceRotScaleEnabled) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; UHoudiniAssetComponent* InputHAC = InObject->GetHoudiniAssetComponent(); - if (!InputHAC || InputHAC->IsPendingKill()) + if (!IsValid(InputHAC)) return true; if (!InputHAC->CanDeleteHoudiniNodes()) return true; UHoudiniInput* HoudiniInput = Cast(InObject->GetOuter()); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) + if (!IsValid(HoudiniInput)) return true; UHoudiniAssetComponent* OuterHAC = Cast(HoudiniInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return true; // Do not allow using ourself as an input, terrible things would happen @@ -2112,7 +2313,7 @@ HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudi AssetReference += FString("'"); if (!FHoudiniInputTranslator::CreateInputNodeForReference( - InObject->InputNodeId, AssetReference, InObject->GetName(), InObject->Transform)) // do not delete previous node if it was HAC + InObject->InputNodeId, AssetReference, InObject->GetName(), InObject->Transform, bImportAsReferenceRotScaleEnabled)) // do not delete previous node if it was HAC return false; if (bIsAssetInput) @@ -2128,10 +2329,10 @@ HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudi // TODO: This might be uneeded as this function should only be called // after we're not wiating on the input asset... - if (InputHAC->AssetState == EHoudiniAssetState::NeedInstantiation) + if (InputHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) { // If the input HAC needs to be instantiated, tell it do so - InputHAC->AssetState = EHoudiniAssetState::PreInstantiation; + InputHAC->SetAssetState(EHoudiniAssetState::PreInstantiation); // Mark this object's input as changed so we can properly update after the input HDA's done instantiating/cooking HoudiniInput->MarkChanged(true); } @@ -2159,25 +2360,25 @@ HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudi bool FHoudiniInputTranslator::HapiCreateInputNodeForActor( - UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds) + UHoudiniInput* InInput, UHoudiniInputActor* InObject, const FTransform & InActorTransform, TArray& OutCreatedNodeIds) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; AActor* Actor = InObject->GetActor(); - if (!Actor || Actor->IsPendingKill()) + if (!IsValid(Actor)) return true; // Check if this is a world input and if this is a HoudiniAssetActor // If so we need to build static meshes for any proxy meshes if (InInput->GetInputType() == EHoudiniInputType::World && Actor->IsA()) - { +{ AHoudiniAssetActor *HAA = Cast(Actor); UHoudiniAssetComponent *HAC = HAA->GetHoudiniAssetComponent(); - if (HAC && !HAC->IsPendingKill()) + if (IsValid(HAC)) { if (HAC->HasAnyCurrentProxyOutput()) { @@ -2190,7 +2391,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForActor( FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); // Update the input object since a new StaticMeshComponent could have been created UObject *InputObject = InObject->GetObject(); - if (InputObject && !InputObject->IsPendingKill()) + if (IsValid(InputObject)) { InObject->Update(InputObject); TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); @@ -2203,14 +2404,14 @@ FHoudiniInputTranslator::HapiCreateInputNodeForActor( HAC->SetNoProxyMeshNextCookRequested(true); } } - else if (InObject->ActorComponents.Num() == 0 && HAC->HasAnyOutputComponent()) + else if (InObject->GetActorComponents().Num() == 0 && HAC->HasAnyOutputComponent()) { // The HAC has non-proxy output components, but the InObject does not have any // actor components. This can arise after a cook if previously there were only // proxies and the input was created when there were only proxies // Try to update the input to find new components UObject *InputObject = InObject->GetObject(); - if (InputObject && !InputObject->IsPendingKill()) + if (IsValid(InputObject)) { InObject->Update(InputObject); TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); @@ -2221,9 +2422,9 @@ FHoudiniInputTranslator::HapiCreateInputNodeForActor( // Now, commit all of this actor's component int32 ComponentIdx = 0; - for (UHoudiniInputSceneComponent* CurComponent : InObject->ActorComponents) + for (UHoudiniInputSceneComponent* CurComponent : InObject->GetActorComponents()) { - if(UploadHoudiniInputObject(InInput, CurComponent, OutCreatedNodeIds)) + if(UploadHoudiniInputObject(InInput, CurComponent, InActorTransform, OutCreatedNodeIds)) ComponentIdx++; } @@ -2252,22 +2453,46 @@ bool FHoudiniInputTranslator::HapiCreateInputNodeForLandscape( const FString& InObjNodeName, UHoudiniInputLandscape* InObject, UHoudiniInput* InInput) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; ALandscapeProxy* Landscape = InObject->GetLandscapeProxy(); - if (!Landscape || Landscape->IsPendingKill()) + if (!IsValid(Landscape)) return true; EHoudiniLandscapeExportType ExportType = InInput->GetLandscapeExportType(); + // Get selected components if bLandscapeExportSelectionOnly or bLandscapeAutoSelectComponent is true + bool bExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; + bool bLandscapeAutoSelectComponent = InInput->bLandscapeAutoSelectComponent; + + TSet< ULandscapeComponent * > SelectedComponents = InInput->GetLandscapeSelectedComponents(); + if (bExportSelectionOnly && SelectedComponents.Num() == 0) + { + InInput->UpdateLandscapeInputSelection(); + SelectedComponents = InInput->GetLandscapeSelectedComponents(); + } + bool bSucess = false; if (ExportType == EHoudiniLandscapeExportType::Heightfield) { - bSucess = FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape(Landscape, InObject->InputNodeId, InObjNodeName); + // Ensure we destroy any (Houdini) input nodes before clobbering this object with a new heightfield. + //DestroyInputNodes(InInput, InInput->GetInputType()); + + int32 NumComponents = Landscape->LandscapeComponents.Num(); + if ( !bExportSelectionOnly || ( SelectedComponents.Num() == NumComponents ) ) + { + // Export the whole landscape and its layer as a single heightfield node + bSucess = FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape(Landscape, InObject->InputNodeId, InObjNodeName); + } + else + { + // Each selected landscape component will be exported as separate volumes in a single heightfield + bSucess = FUnrealLandscapeTranslator::CreateHeightfieldFromLandscapeComponentArray( Landscape, SelectedComponents, InObject->InputNodeId, InObjNodeName ); + } } else { @@ -2275,7 +2500,6 @@ FHoudiniInputTranslator::HapiCreateInputNodeForLandscape( bool bExportMaterials = InInput->bLandscapeExportMaterials; bool bExportNormalizedUVs = InInput->bLandscapeExportNormalizedUVs; bool bExportTileUVs = InInput->bLandscapeExportTileUVs; - bool bExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; bool bExportAsMesh = InInput->LandscapeExportType == EHoudiniLandscapeExportType::Mesh; bSucess = FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( @@ -2314,11 +2538,11 @@ FHoudiniInputTranslator::HapiCreateInputNodeForBrush(const FString& InObjNodeNam bool FHoudiniInputTranslator::HapiCreateInputNodeForCamera(const FString& InNodeName, UHoudiniInputCameraComponent* InInputObject) { - if (!InInputObject || InInputObject->IsPendingKill()) + if (!IsValid(InInputObject)) return false; UCameraComponent* Camera = InInputObject->GetCameraComponent(); - if (!Camera || Camera->IsPendingKill()) + if (!IsValid(Camera)) return true; FString NodeName = InNodeName + TEXT("_") + Camera->GetName(); @@ -2386,7 +2610,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForCamera(const FString& InNodeName, bool FHoudiniInputTranslator::UpdateLoadedInputs(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // We need to call BuildAllInputs here to update all the inputs, @@ -2399,7 +2623,7 @@ FHoudiniInputTranslator::UpdateLoadedInputs(UHoudiniAssetComponent* HAC) int32 HACAssetId = HAC->GetAssetId(); for (auto CurrentInput : HAC->Inputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; // @@ -2419,7 +2643,7 @@ FHoudiniInputTranslator::UpdateLoadedInputs(UHoudiniAssetComponent* HAC) bool FHoudiniInputTranslator::UpdateWorldInputs(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // Only tick/cook when in Editor @@ -2456,7 +2680,7 @@ FHoudiniInputTranslator::UpdateWorldInputs(UHoudiniAssetComponent* HAC) bool FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; if (InInput->GetInputType() != EHoudiniInputType::World) @@ -2480,11 +2704,12 @@ FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) for(int32 InputObjIdx = 0; InputObjIdx < InputObjectsPtr->Num(); InputObjIdx++) { UHoudiniInputActor* ActorObject = Cast((*InputObjectsPtr)[InputObjIdx]); - if (!ActorObject || ActorObject->IsPendingKill()) + if (!IsValid(ActorObject)) continue; // Make sure the actor is still valid - bool bValidActorObject = ActorObject->GetActor() && !ActorObject->GetActor()->IsPendingKill(); + AActor* const Actor = ActorObject->GetActor(); + bool bValidActorObject = IsValid(Actor); // For BrushActors, the brush and actors must be valid as well UHoudiniInputBrush* BrushActorObject = Cast(ActorObject); @@ -2512,10 +2737,15 @@ FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) continue; } + // We'll keep track of whether the actor transform changed so that + // we can mark all the components as having changed transforms -- everything + // needs to be updated. + bool bActorTransformChanged = false; if (ActorObject->HasActorTransformChanged()) { ActorObject->MarkTransformChanged(true); bHasChanged = true; + bActorTransformChanged = true; } if (ActorObject->HasContentChanged()) @@ -2524,33 +2754,13 @@ FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) bHasChanged = true; } - // Iterates on all of the actor's component - TArray ComponentToDeleteIndices; - for (int32 CompIdx = 0; CompIdx < ActorObject->ActorComponents.Num(); CompIdx++) - { - UHoudiniInputSceneComponent* CurActorComp = ActorObject->ActorComponents[CompIdx]; - if (!CurActorComp || CurActorComp->IsPendingKill()) - continue; + // Ensure we are aware of all the components of the actor + ActorObject->Update(Actor); - // Make sure the actor is still valid - if (!CurActorComp->InputObject || CurActorComp->InputObject->IsPendingKill()) - { - // If it's not, mark it for deletion - if ((CurActorComp->InputNodeId > 0) || (CurActorComp->InputObjectNodeId > 0)) - { - CurActorComp->InvalidateData(); - - // We only need to update the input if the object were created in Houdini - bHasChanged = true; - } - - // Delete the component object - ComponentToDeleteIndices.Add(CompIdx); - - continue; - } - - if (CurActorComp->HasComponentTransformChanged()) + // Check if any components have content or transform changes + for (auto CurActorComp : ActorObject->GetActorComponents()) + { + if (bActorTransformChanged || CurActorComp->HasComponentTransformChanged()) { CurActorComp->MarkTransformChanged(true); bHasChanged = true; @@ -2561,11 +2771,21 @@ FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) CurActorComp->MarkChanged(true); bHasChanged = true; } + + USceneComponent* Component = CurActorComp->GetSceneComponent(); + if (IsValid(Component)) + { + CurActorComp->Update(Component); + } } - // Delete the components objects on the actor that were marked for deletion - for (int32 ToDeleteIdx = ComponentToDeleteIndices.Num() - 1; ToDeleteIdx >= 0; ToDeleteIdx--) - ActorObject->ActorComponents.RemoveAt(ComponentToDeleteIndices[ToDeleteIdx]); + // Check if we added/removed any components in the call to update + if (ActorObject->GetLastUpdateNumComponentsAdded() > 0 || ActorObject->GetLastUpdateNumComponentsRemoved() > 0) + { + bHasChanged = true; + if (ActorObject->GetLastUpdateNumComponentsRemoved() > 0) + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + } } // Delete the actor objects that were marked for deletion @@ -2583,9 +2803,10 @@ FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) bool FHoudiniInputTranslator::CreateInputNodeForReference( HAPI_NodeId& InputNodeId, - const FString & InRef, - const FString & InputNodeName, - const FTransform& InTransform) + const FString& InRef, + const FString& InputNodeName, + const FTransform& InTransform, + const bool& bImportAsReferenceRotScaleEnabled) { HAPI_NodeId NewNodeId = -1; @@ -2655,9 +2876,9 @@ FHoudiniInputTranslator::CreateInputNodeForReference( FVector ObjectPosition = InTransform.GetLocation(); TArray Position = { - ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION + ObjectPosition.X / HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION }; // Now that we have raw positions, we can upload them for our attribute. @@ -2668,6 +2889,78 @@ FHoudiniInputTranslator::CreateInputNodeForReference( AttributeInfoPoint.count), false); } + if (bImportAsReferenceRotScaleEnabled) + { + // Create ROTATION attribute info + HAPI_AttributeInfo AttributeInfoRotation; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); + AttributeInfoRotation.count = 1; + AttributeInfoRotation.tupleSize = 4; + AttributeInfoRotation.exists = true; + AttributeInfoRotation.owner = HAPI_ATTROWNER_POINT; + AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRotation.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NewNodeId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRotation), false); + + TArray< float > InputRotations; + InputRotations.SetNumZeroed(4); + + FQuat InputRotation = InTransform.GetRotation(); + + InputRotations[0] = InputRotation.X; + InputRotations[1] = InputRotation.Z; + InputRotations[2] = InputRotation.Y; + InputRotations[3] = -InputRotation.W; + + //we can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NewNodeId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRotation, + InputRotations.GetData(), + 0, AttributeInfoRotation.count), false); + + // Create SCALE attribute info + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = 1; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NewNodeId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale), false); + + TArray< float > InputScales; + InputScales.SetNumZeroed(3); + + FVector InputScale = InTransform.GetScale3D(); + InputScales[0] = InputScale.X; + InputScales[1] = InputScale.Z; + InputScales[2] = InputScale.Y; + + //we can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NewNodeId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale, + InputScales.GetData(), + 0, AttributeInfoScale.count), false); + + } + // String Attribute { // Create point attribute info. @@ -2710,11 +3003,11 @@ bool FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(const FString& InNodeName, UHoudiniInputDataTable* InInputObject) { //TODO - if (!InInputObject || InInputObject->IsPendingKill()) + if (!IsValid(InInputObject)) return false; UDataTable* DataTable = InInputObject->GetDataTable(); - if (!DataTable || DataTable->IsPendingKill()) + if (!IsValid(DataTable)) return true; // Get the DataTable data as string @@ -2846,7 +3139,14 @@ FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(const FString& InNodeNa for (int32 ColIdx = 0; ColIdx < NumAttributes; ColIdx++) { // attribute name is "unreal_data_table_COL_NAME" - FString CurAttrName = TEXT(HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX) + FString::FromInt(ColIdx) + TEXT("_") + TableData[0][ColIdx]; + + FString DataName = TableData[0][ColIdx]; + + // Validate struct variable names + DataName = DataName.Replace(TEXT(" "), TEXT("_")); + DataName = DataName.Replace(TEXT(":"), TEXT("_")); + + FString CurAttrName = TEXT(HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX) + FString::FromInt(ColIdx) + TEXT("_") + DataName; // We need to gt all values for that attribute TArray AttributeValues; @@ -2887,4 +3187,82 @@ FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(const FString& InNodeNa return true; } +bool +FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + const FString& InObjNodeName, + UHoudiniInputFoliageType_InstancedStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference, + const bool& bImportAsReferenceRotScaleEnabled) +{ + if (!IsValid(InObject)) + return false; + + FString FTName = InObjNodeName + TEXT("_"); + + UFoliageType_InstancedStaticMesh* FoliageType = Cast(InObject->GetObject()); + if (!IsValid(FoliageType)) + return true; + + UStaticMesh* const SM = FoliageType->GetStaticMesh(); + if (!IsValid(SM)) + return true; + + FTName += FoliageType->GetName(); + + // Marshall the Static Mesh to Houdini + bool bSuccess = true; + + if (bImportAsReference) + { + // Start by getting the Object's full name + FString AssetReference; + AssetReference += SM->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + bSuccess = FUnrealFoliageTypeTranslator::CreateInputNodeForReference( + FoliageType, InObject->InputNodeId, AssetReference, FTName, InObject->Transform, InObject->GetImportAsReferenceRotScaleEnabled()); + } + else + { + bSuccess = FUnrealFoliageTypeTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + FoliageType, InObject->InputNodeId, FTName, bExportLODs, bExportSockets, bExportColliders); + } + + InObject->SetImportAsReference(bImportAsReference); + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + // If the Input mesh has a Transform offset + const FTransform TransformOffset = InObject->Transform; + if (!TransformOffset.Equals(FTransform::Identity)) + { + // Updating the Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + return bSuccess; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h index 5737604f9..888adbffb 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,6 +28,9 @@ #include "HAPI/HAPI_Common.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "CoreMinimal.h" + class AActor; class UHoudiniInput; @@ -49,6 +52,7 @@ class UHoudiniInputBrush; class UHoudiniSplineComponent; class UHoudiniInputCameraComponent; class UHoudiniInputDataTable; +class UHoudiniInputFoliageType_InstancedStaticMesh; class AActor; @@ -92,14 +96,14 @@ struct HOUDINIENGINE_API FHoudiniInputTranslator static bool UpdateTransformOffset(UHoudiniInput* InInput); // Upload all the input's data to Houdini - static bool UploadInputData(UHoudiniInput* InInput); + static bool UploadInputData(UHoudiniInput* InInput, const FTransform & InActorTransform = FTransform::Identity); // Upload all the input's transforms to Houdini static bool UploadInputTransform(UHoudiniInput* InInput); // Upload data for an input's InputObject static bool UploadHoudiniInputObject( - UHoudiniInput* InInput, UHoudiniInputObject* InInputObject, TArray& OutCreatedNodeIds); + UHoudiniInput* InInput, UHoudiniInputObject* InInputObject, const FTransform& InActorTransform, TArray& OutCreatedNodeIds); // Upload transform for an input's InputObject static bool UploadHoudiniInputTransform( @@ -121,7 +125,7 @@ struct HOUDINIENGINE_API FHoudiniInputTranslator static EHoudiniInputType GetDefaultInputTypeFromLabel(const FString& InputName); - static bool SetDefaultAssetFromHDA(UHoudiniInput* Input); + static bool SetDefaultAssetFromHDA(UHoudiniInput* Input, bool& bOutBlueprintStructureModified); static bool ChangeInputType(UHoudiniInput* Input, const bool& bForce); @@ -134,11 +138,13 @@ struct HOUDINIENGINE_API FHoudiniInputTranslator const bool& bExportLODs, const bool& bExportSockets, const bool& bExportColliders, - const bool& bImportAsReference = false); + const bool& bImportAsReference = false, + const bool& bImportAsReferenceRotScaleEnabled = false); static bool HapiCreateInputNodeForHoudiniSplineComponent( const FString& InObjNodeName, - UHoudiniInputHoudiniSplineComponent* InObject); + UHoudiniInputHoudiniSplineComponent* InObject, + bool bInSetRotAndScaleAttributes); static bool HapiCreateInputNodeForLandscape( const FString& InObjNodeName, @@ -157,7 +163,10 @@ struct HOUDINIENGINE_API FHoudiniInputTranslator const bool& bExportLODs, const bool& bExportSockets, const bool& bExportColliders, - const bool& bImportAsReference); + const bool& bKeepWorldTransform, + const bool& bImportAsReference, + const bool& bImportAsReferenceRotScaleEnabled = false, + const FTransform& InActorTransform = FTransform::Identity); static bool HapiCreateInputNodeForInstancedStaticMeshComponent( const FString& InObjNodeName, @@ -170,10 +179,10 @@ struct HOUDINIENGINE_API FHoudiniInputTranslator const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution); static bool HapiCreateInputNodeForHoudiniAssetComponent( - const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference); + const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool bKeepWorldTransform, const bool& bImportAsReference, const bool& bImportAsReferenceRotScaleEnabled); static bool HapiCreateInputNodeForActor( - UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds); + UHoudiniInput* InInput, UHoudiniInputActor* InObject, const FTransform & InActorTransform, TArray& OutCreatedNodeIds); static bool HapiCreateInputNodeForCamera( const FString& InObjNodeName, UHoudiniInputCameraComponent* InObject); @@ -190,12 +199,22 @@ struct HOUDINIENGINE_API FHoudiniInputTranslator static bool HapiCreateInputNodeForDataTable( const FString& InNodeName, UHoudiniInputDataTable* InInputObject); + static bool HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + const FString& InObjNodeName, + UHoudiniInputFoliageType_InstancedStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference = false, + const bool& bImportAsReferenceRotScaleEnabled = false); + // HAPI: Create an input node for reference static bool CreateInputNodeForReference( HAPI_NodeId& InputNodeId, const FString & InRef, const FString & InputNodeName, - const FTransform & InTransform); + const FTransform & InTransform, + const bool& bImportAsReferenceRotScaleEnabled); //static bool HapiUpdateInputNodeTransform(const HAPI_NodeId InputNodeId, const FTransform& Transform); diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp index 73386f040..bcc8ba6aa 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,6 +39,7 @@ #include "Engine/StaticMesh.h" #include "ComponentReregisterContext.h" +#include "HoudiniMaterialTranslator.h" #include "Components/StaticMeshComponent.h" #include "Components/InstancedStaticMeshComponent.h" #include "Components/HierarchicalInstancedStaticMeshComponent.h" @@ -70,12 +71,16 @@ FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( // Get if force to use HISM from attribute OutInstancedOutputPartData.bForceHISM = HasHISMAttribute(InHGPO.GeoId, InHGPO.PartId); + // Should we create an instancer even for single instances? + OutInstancedOutputPartData.bForceInstancer = HasForceInstancerAttribute(InHGPO.GeoId, InHGPO.PartId); + // Extract the object and transforms for this instancer if (!GetInstancerObjectsAndTransforms( InHGPO, InAllOutputs, OutInstancedOutputPartData.OriginalInstancedObjects, OutInstancedOutputPartData.OriginalInstancedTransforms, + OutInstancedOutputPartData.OriginalInstancedIndices, OutInstancedOutputPartData.SplitAttributeName, OutInstancedOutputPartData.SplitAttributeValues, OutInstancedOutputPartData.PerSplitAttributes)) @@ -89,6 +94,9 @@ FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( // Extract the generic attributes GetGenericPropertiesAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllPropertyAttributes); + // Check for per instance custom data + GetPerInstanceCustomData(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData); + //Get the level path attribute on the instancer if (!FHoudiniEngineUtils::GetLevelPathAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllLevelPaths)) { @@ -103,6 +111,13 @@ FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( OutInstancedOutputPartData.OutputNames.Empty(); } + // Get the bake name attribute + if (!FHoudiniEngineUtils::GetBakeNameAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.BakeNames)) + { + // No attribute specified + OutInstancedOutputPartData.BakeNames.Empty(); + } + // See if we have a tile attribute if (!FHoudiniEngineUtils::GetTileAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.TileValues)) { @@ -117,6 +132,13 @@ FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( OutInstancedOutputPartData.AllBakeActorNames.Empty(); } + // Get the unreal_bake_folder attribute + if (!FHoudiniEngineUtils::GetBakeFolderAttribute(InHGPO.GeoId, OutInstancedOutputPartData.AllBakeFolders, InHGPO.PartId)) + { + // No attribute specified + OutInstancedOutputPartData.AllBakeFolders.Empty(); + } + // Get the bake outliner folder attribute if (!FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeOutlinerFolders)) { @@ -125,7 +147,7 @@ FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( } // See if we have instancer material overrides - if (!GetMaterialOverridesFromAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.MaterialAttributes)) + if (!GetMaterialOverridesFromAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.MaterialAttributes, OutInstancedOutputPartData.bMaterialOverrideNeedsCreateInstance)) OutInstancedOutputPartData.MaterialAttributes.Empty(); return true; @@ -136,12 +158,14 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( UHoudiniOutput* InOutput, const TArray& InAllOutputs, UObject* InOuterComponent, - const TMap* InPreBuiltInstancedOutputPartData) + const FHoudiniPackageParams& InPackageParms, + const TMap* InPreBuiltInstancedOutputPartData +) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; - if (!InOuterComponent || InOuterComponent->IsPendingKill()) + if (!IsValid(InOuterComponent)) return false; // Keep track of the previous cook's component to clean them up after @@ -166,10 +190,10 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( { // Foliage instancers store a HISMC in the components UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentPair.Value.OutputComponent); - if (!FoliageHISMC || FoliageHISMC->IsPendingKill()) + if (!IsValid(FoliageHISMC)) continue; - CleanupFoliageInstances(FoliageHISMC, ParentComponent); + CleanupFoliageInstances(FoliageHISMC, CurrentPair.Value.OutputObject, ParentComponent); bHaveAnyFoliageInstancers = true; } @@ -198,7 +222,7 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( } if (!InstancedOutputPartDataPtr) { - if (!PopulateInstancedOutputPartData(CurHGPO, InAllOutputs,InstancedOutputPartDataTmp)) + if (!PopulateInstancedOutputPartData(CurHGPO, InAllOutputs, InstancedOutputPartDataTmp)) continue; InstancedOutputPartDataPtr = &InstancedOutputPartDataTmp; } @@ -206,8 +230,17 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( const FHoudiniInstancedOutputPartData& InstancedOutputPartData = *InstancedOutputPartDataPtr; TArray InstancerMaterials; - if (!GetInstancerMaterials(InstancedOutputPartData.MaterialAttributes,InstancerMaterials)) - InstancerMaterials.Empty(); + if (!InstancedOutputPartData.bMaterialOverrideNeedsCreateInstance) + { + if (!GetInstancerMaterials(InstancedOutputPartData.MaterialAttributes, InstancerMaterials)) + InstancerMaterials.Empty(); + } + else + { + if (!GetInstancerMaterialInstances(InstancedOutputPartData.MaterialAttributes, CurHGPO, InPackageParms, InstancerMaterials)) + InstancerMaterials.Empty(); + } + if (InstancedOutputPartData.bIsFoliageInstancer) bHaveAnyFoliageInstancers = true; @@ -239,15 +272,25 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( UpdateInstanceVariationObjects( OutputIdentifier, InstancedOutputPartData.OriginalInstancedObjects, - InstancedOutputPartData.OriginalInstancedTransforms, InOutput->GetInstancedOutputs(), - VariationInstancedObjects, VariationInstancedTransforms, - VariationOriginalObjectIndices, VariationIndices); + InstancedOutputPartData.OriginalInstancedTransforms, + InstancedOutputPartData.OriginalInstancedIndices, + InOutput->GetInstancedOutputs(), + VariationInstancedObjects, + VariationInstancedTransforms, + VariationOriginalObjectIndices, + VariationIndices); + + // Preload objects so we can benefit from async compilation as much as possible + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) + { + VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); + } // Create the instancer components now for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) { UObject* InstancedObject = VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); - if (!InstancedObject || InstancedObject->IsPendingKill()) + if (!IsValid(InstancedObject)) continue; if (!VariationInstancedTransforms.IsValidIndex(InstanceObjectIdx)) @@ -257,19 +300,22 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( if (InstancedObjectTransforms.Num() <= 0) continue; + // Get the original Index of that variations + int32 VariationOriginalIndex = VariationOriginalObjectIndices[InstanceObjectIdx]; + // Find the matching instance output now FHoudiniInstancedOutput* FoundInstancedOutput = nullptr; { // Instanced output only use the original object index for their split identifier FHoudiniOutputObjectIdentifier InstancedOutputIdentifier = OutputIdentifier; - InstancedOutputIdentifier.SplitIdentifier = FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]); + InstancedOutputIdentifier.SplitIdentifier = FString::FromInt(VariationOriginalIndex); FoundInstancedOutput = InstancedOutputs.Find(InstancedOutputIdentifier); } // Update the split identifier for this object // We use both the original object index and the variation index: ORIG_VAR OutputIdentifier.SplitIdentifier = - FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]) + FString::FromInt(VariationOriginalIndex) + TEXT("_") + FString::FromInt(VariationIndices[InstanceObjectIdx]); @@ -292,18 +338,25 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( // Extract the material for this variation TArray VariationMaterials; - if (!GetVariationMaterials(FoundInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) + if (!GetVariationMaterials(FoundInstancedOutput, InstanceObjectIdx, InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex], InstancerMaterials, VariationMaterials)) VariationMaterials.Empty(); USceneComponent* NewInstancerComponent = nullptr; if (!CreateOrUpdateInstanceComponent( - InstancedObject, InstancedObjectTransforms, - InstancedOutputPartData.AllPropertyAttributes, CurHGPO, - ParentComponent, OldInstancerComponent, NewInstancerComponent, + InstancedObject, + InstancedObjectTransforms, + InstancedOutputPartData.AllPropertyAttributes, + CurHGPO, + ParentComponent, + OldInstancerComponent, + NewInstancerComponent, InstancedOutputPartData.bSplitMeshInstancer, InstancedOutputPartData.bIsFoliageInstancer, VariationMaterials, - InstancedOutputPartData.bForceHISM)) + InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex], + InstanceObjectIdx, + InstancedOutputPartData.bForceHISM, + InstancedOutputPartData.bForceInstancer)) { // TODO?? continue; @@ -312,6 +365,13 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( if (!NewInstancerComponent) continue; + // Copy the per-instance custom data if we have any + if (InstancedOutputPartData.PerInstanceCustomData.Num() > 0) + { + UpdateChangedPerInstanceCustomData( + InstancedOutputPartData.PerInstanceCustomData[VariationOriginalIndex], NewInstancerComponent); + } + // If the instanced object (by ref) wasn't found, hide the component if(InstancedObject == DefaultReferenceSM) NewInstancerComponent->SetHiddenInGame(true); @@ -322,10 +382,12 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( if (bIsProxyMesh) { NewOutputObject.ProxyComponent = NewInstancerComponent; + NewOutputObject.ProxyObject = InstancedObject; } else { NewOutputObject.OutputComponent = NewInstancerComponent; + NewOutputObject.OutputObject = InstancedObject; } // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before @@ -333,32 +395,40 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( NewOutputObject.CachedAttributes.Empty(); NewOutputObject.CachedTokens.Empty(); - // Todo: get the proper attribute value per variation... - // Cache the level path, output name and tile attributes on the output object - // So they can be reused for baking - if(InstancedOutputPartData.AllLevelPaths.Num() > 0 && !InstancedOutputPartData.AllLevelPaths[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, InstancedOutputPartData.AllLevelPaths[0]); + // Cache the level path, output name and tile attributes on the output object So they can be reused for baking + int32 FirstOriginalInstanceIndex = 0; + if(InstancedOutputPartData.OriginalInstancedIndices.IsValidIndex(VariationOriginalIndex) && InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex].Num() > 0) + FirstOriginalInstanceIndex = InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex][0]; + + if(InstancedOutputPartData.AllLevelPaths.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllLevelPaths[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, InstancedOutputPartData.AllLevelPaths[FirstOriginalInstanceIndex]); + + if(InstancedOutputPartData.OutputNames.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.OutputNames[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), InstancedOutputPartData.OutputNames[FirstOriginalInstanceIndex]); - if(InstancedOutputPartData.OutputNames.Num() > 0 && !InstancedOutputPartData.OutputNames[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), InstancedOutputPartData.OutputNames[0]); + if(InstancedOutputPartData.BakeNames.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.BakeNames[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_BAKE_NAME), InstancedOutputPartData.BakeNames[FirstOriginalInstanceIndex]); - if(InstancedOutputPartData.TileValues.Num() > 0 && InstancedOutputPartData.TileValues[0] >= 0) + // TODO: Check! maybe accessed with just VariationOriginalIndex + if(InstancedOutputPartData.TileValues.IsValidIndex(FirstOriginalInstanceIndex) && InstancedOutputPartData.TileValues[FirstOriginalInstanceIndex] >= 0) { // cache the tile attribute as a token on the output object - NewOutputObject.CachedTokens.Add(TEXT("tile"), FString::FromInt(InstancedOutputPartData.TileValues[0])); + NewOutputObject.CachedTokens.Add(TEXT("tile"), FString::FromInt(InstancedOutputPartData.TileValues[FirstOriginalInstanceIndex])); } - if (InstancedOutputPartData.AllBakeActorNames.Num() > 0 && !InstancedOutputPartData.AllBakeActorNames[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, InstancedOutputPartData.AllBakeActorNames[0]); + if(InstancedOutputPartData.AllBakeActorNames.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllBakeActorNames[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, InstancedOutputPartData.AllBakeActorNames[FirstOriginalInstanceIndex]); - if (InstancedOutputPartData.AllBakeOutlinerFolders.Num() > 0 && !InstancedOutputPartData.AllBakeOutlinerFolders[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, InstancedOutputPartData.AllBakeOutlinerFolders[0]); + if(InstancedOutputPartData.AllBakeFolders.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllBakeFolders[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, InstancedOutputPartData.AllBakeFolders[FirstOriginalInstanceIndex]); - if (InstancedOutputPartData.SplitAttributeValues.Num() > 0 - && !InstancedOutputPartData.SplitAttributeName.IsEmpty() - && InstancedOutputPartData.SplitAttributeValues.IsValidIndex(VariationOriginalObjectIndices[InstanceObjectIdx])) + if(InstancedOutputPartData.AllBakeOutlinerFolders.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllBakeOutlinerFolders[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, InstancedOutputPartData.AllBakeOutlinerFolders[FirstOriginalInstanceIndex]); + + if(InstancedOutputPartData.SplitAttributeValues.IsValidIndex(VariationOriginalIndex) + && !InstancedOutputPartData.SplitAttributeName.IsEmpty()) { - FString SplitValue = InstancedOutputPartData.SplitAttributeValues[VariationOriginalObjectIndices[InstanceObjectIdx]]; + FString SplitValue = InstancedOutputPartData.SplitAttributeValues[VariationOriginalIndex]; // Cache the split attribute both as attribute and token NewOutputObject.CachedAttributes.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); @@ -377,6 +447,8 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, PerSplitAttributes->BakeActorName); if (!PerSplitAttributes->BakeOutlinerFolder.IsEmpty()) NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, PerSplitAttributes->BakeOutlinerFolder); + if (!PerSplitAttributes->BakeFolder.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, PerSplitAttributes->BakeFolder); } } } @@ -400,7 +472,7 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( if (NewComponent) { UObject* FoundOldComponent = FoundOldOutputObject->OutputComponent; - if (FoundOldComponent && !FoundOldComponent->IsPendingKill()) + if (IsValid(FoundOldComponent)) { bKeep = (FoundOldComponent == NewComponent); } @@ -410,7 +482,7 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( if (NewProxyComponent) { UObject* FoundOldProxyComponent = FoundOldOutputObject->ProxyComponent; - if (FoundOldProxyComponent && !FoundOldProxyComponent->IsPendingKill()) + if (IsValid(FoundOldProxyComponent)) { bKeep = (FoundOldProxyComponent == NewProxyComponent); } @@ -441,16 +513,18 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( } if(bDestroy) - RemoveAndDestroyComponent(OldComponent); + RemoveAndDestroyComponent(OldComponent, OldPair.Value.OutputObject); OldPair.Value.OutputComponent = nullptr; + OldPair.Value.OutputObject = nullptr; } UObject* OldProxyComponent = OldPair.Value.ProxyComponent; if (OldProxyComponent) { - RemoveAndDestroyComponent(OldProxyComponent); + RemoveAndDestroyComponent(OldProxyComponent, OldPair.Value.ProxyObject); OldPair.Value.ProxyComponent = nullptr; + OldPair.Value.ProxyObject = nullptr; } } OldOutputObjects.Empty(); @@ -473,7 +547,8 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( FHoudiniInstancedOutput& InInstancedOutput, const FHoudiniOutputObjectIdentifier& InOutputIdentifier, UHoudiniOutput* InParentOutput, - USceneComponent* InParentComponent) + USceneComponent* InParentComponent, + const FHoudiniPackageParams& InPackageParams) { FHoudiniOutputObjectIdentifier OutputIdentifier; OutputIdentifier.ObjectId = InOutputIdentifier.ObjectId; @@ -483,7 +558,10 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( OutputIdentifier.PartName = InOutputIdentifier.PartName; // Get if force using HISM from attribute - bool bForceHISM = HasHISMAttribute(InOutputIdentifier.GeoId, InOutputIdentifier.PartId); + const bool bForceHISM = HasHISMAttribute(InOutputIdentifier.GeoId, InOutputIdentifier.PartId); + + // Should we create an instancer even for single instances? + const bool bForceInstancer = HasForceInstancerAttribute(InOutputIdentifier.GeoId, InOutputIdentifier.PartId); TArray OriginalInstancedObjects; OriginalInstancedObjects.Add(InInstancedOutput.OriginalObject.LoadSynchronous()); @@ -491,6 +569,9 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( TArray> OriginalInstancedTransforms; OriginalInstancedTransforms.Add(InInstancedOutput.OriginalTransforms); + TArray> OriginalInstanceIndices; + OriginalInstanceIndices.Add(InInstancedOutput.OriginalInstanceIndices); + // Update our variations using the changed instancedoutputs objects TArray> InstancedObjects; TArray> InstancedTransforms; @@ -498,10 +579,14 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( TArray VariationIndices; UpdateInstanceVariationObjects( OutputIdentifier, - OriginalInstancedObjects, OriginalInstancedTransforms, + OriginalInstancedObjects, + OriginalInstancedTransforms, + OriginalInstanceIndices, InParentOutput->GetInstancedOutputs(), - InstancedObjects, InstancedTransforms, - VariationOriginalObjectIndices, VariationIndices); + InstancedObjects, + InstancedTransforms, + VariationOriginalObjectIndices, + VariationIndices); // Find the HGPO for this instanced output bool FoundHGPO = false; @@ -533,9 +618,15 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( // See if we have instancer material overrides TArray InstancerMaterials; - if (!GetInstancerMaterials(OutputIdentifier.GeoId, OutputIdentifier.PartId, InstancerMaterials)) + if (!GetInstancerMaterials(OutputIdentifier.GeoId, OutputIdentifier.PartId, HGPO, InPackageParams, InstancerMaterials)) InstancerMaterials.Empty(); + // Preload objects so we can benefit from async compilation as much as possible + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) + { + InstancedObjects[InstanceObjectIdx].LoadSynchronous(); + } + // Keep track of the new instancer component in order to be able to clean up the unused/stale ones after. TMap& OutputObjects = InParentOutput->GetOutputObjects(); TMap ToDeleteOutputObjects = InParentOutput->GetOutputObjects(); @@ -544,7 +635,7 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) { UObject* InstancedObject = InstancedObjects[InstanceObjectIdx].LoadSynchronous(); - if (!InstancedObject || InstancedObject->IsPendingKill()) + if (!IsValid(InstancedObject)) continue; if (!InstancedTransforms.IsValidIndex(InstanceObjectIdx)) @@ -573,15 +664,25 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( // Extract the material for this variation // FHoudiniInstancedOutput* FoundInstancedOutput = InstancedOutputs.Find(OutputIdentifier); TArray VariationMaterials; - if (!GetVariationMaterials(&InInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) + if (!GetVariationMaterials(&InInstancedOutput, InstanceObjectIdx, OriginalInstanceIndices[0], InstancerMaterials, VariationMaterials)) VariationMaterials.Empty(); USceneComponent* NewInstancerComponent = nullptr; if (!CreateOrUpdateInstanceComponent( - InstancedObject, InstancedObjectTransforms, - AllPropertyAttributes, HGPO, - InParentComponent, OldInstancerComponent, NewInstancerComponent, - bSplitMeshInstancer, bIsFoliageInstancer, InstancerMaterials, bForceHISM)) + InstancedObject, + InstancedObjectTransforms, + AllPropertyAttributes, + HGPO, + InParentComponent, + OldInstancerComponent, + NewInstancerComponent, + bSplitMeshInstancer, + bIsFoliageInstancer, + InstancerMaterials, + OriginalInstanceIndices[0], + InstanceObjectIdx, + bForceHISM, + bForceInstancer)) { // TODO?? continue; @@ -593,7 +694,7 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( if (OldInstancerComponent != NewInstancerComponent) { // Previous component wasn't reused, detach and delete it - RemoveAndDestroyComponent(OldInstancerComponent); + RemoveAndDestroyComponent(OldInstancerComponent, nullptr); // Replace it with the new component if (FoundOutputObject) @@ -620,14 +721,14 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( UObject* OldComponent = ToDeletePair.Value.OutputComponent; if (OldComponent) { - RemoveAndDestroyComponent(OldComponent); + RemoveAndDestroyComponent(OldComponent, ToDeletePair.Value.OutputObject); ToDeletePair.Value.OutputComponent = nullptr; } UObject* OldProxyComponent = ToDeletePair.Value.ProxyComponent; if (OldProxyComponent) { - RemoveAndDestroyComponent(OldProxyComponent); + RemoveAndDestroyComponent(OldProxyComponent, ToDeletePair.Value.ProxyObject); ToDeletePair.Value.ProxyComponent = nullptr; } @@ -646,15 +747,18 @@ FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( const TArray& InAllOutputs, TArray& OutInstancedObjects, TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, FString& OutSplitAttributeName, TArray& OutSplitAttributeValues, TMap& OutPerSplitAttributes) { TArray InstancedObjects; TArray> InstancedTransforms; + TArray> InstancedIndices; TArray InstancedHGPOs; TArray> InstancedHGPOTransforms; + TArray> InstancedHGPOIndices; bool bSuccess = false; switch (InHGPO.InstancerType) @@ -666,6 +770,7 @@ FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( InHGPO, InstancedHGPOs, InstancedHGPOTransforms, + InstancedHGPOIndices, OutSplitAttributeName, OutSplitAttributeValues, OutPerSplitAttributes); @@ -679,6 +784,7 @@ FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( InHGPO, InstancedObjects, InstancedTransforms, + InstancedIndices, OutSplitAttributeName, OutSplitAttributeValues, OutPerSplitAttributes); @@ -688,14 +794,14 @@ FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( case EHoudiniInstancerType::OldSchoolAttributeInstancer: { // Old school attribute override instancer - instance attribute w/ a HoudiniPath - bSuccess = GetOldSchoolAttributeInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); + bSuccess = GetOldSchoolAttributeInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms, InstancedHGPOIndices); } break; case EHoudiniInstancerType::ObjectInstancer: { // Old School object instancer - bSuccess = GetObjectInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); + bSuccess = GetObjectInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms, InstancedHGPOIndices); } break; } @@ -731,11 +837,11 @@ FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( // In the case of a single-instance we can use the proxy (if it is current) // FHoudiniOutputTranslator::UpdateOutputs doesn't allow proxies if there is more than one instance in an output if (InstancedHGPOTransforms[HGPOIdx].Num() <= 1 && CurrentOutputObject.bProxyIsCurrent - && CurrentOutputObject.ProxyObject && !CurrentOutputObject.ProxyObject->IsPendingKill()) + && IsValid(CurrentOutputObject.ProxyObject)) { ObjectsToInstance.Add(CurrentOutputObject.ProxyObject); } - else if (CurrentOutputObject.OutputObject && !CurrentOutputObject.OutputObject->IsPendingKill()) + else if (IsValid(CurrentOutputObject.OutputObject)) { ObjectsToInstance.Add(CurrentOutputObject.OutputObject); } @@ -747,12 +853,13 @@ FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( { InstancedObjects.Add(MatchingOutputObj); InstancedTransforms.Add(InstancedHGPOTransforms[HGPOIdx]); + InstancedIndices.Add(InstancedHGPOIndices[HGPOIdx]); } } } // - if (InstancedObjects.Num() <= 0 || InstancedTransforms.Num() != InstancedObjects.Num() ) + if (InstancedObjects.Num() <= 0 || InstancedTransforms.Num() != InstancedObjects.Num() || InstancedIndices.Num() != InstancedObjects.Num()) { // TODO // Error / warning @@ -761,6 +868,7 @@ FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( OutInstancedObjects = InstancedObjects; OutInstancedTransforms = InstancedTransforms; + OutInstancedIndices = InstancedIndices; return true; } @@ -771,6 +879,7 @@ FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( const FHoudiniOutputObjectIdentifier& InOutputIdentifier, const TArray& InOriginalObjects, const TArray>& InOriginalTransforms, + const TArray>& InOriginalInstancedIndices, TMap& InstancedOutputs, TArray>& OutVariationsInstancedObjects, TArray>& OutVariationsInstancedTransforms, @@ -781,7 +890,7 @@ FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( for (int32 InstObjIdx = 0; InstObjIdx < InOriginalObjects.Num(); InstObjIdx++) { UObject* OriginalObj = InOriginalObjects[InstObjIdx]; - if (!OriginalObj || OriginalObj->IsPendingKill()) + if (!IsValid(OriginalObj)) continue; // Build this output object's split identifier @@ -816,6 +925,7 @@ FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( CurInstancedOutput.OriginalObject = OriginalObj; CurInstancedOutput.OriginalObjectIndex = InstObjIdx; CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; + CurInstancedOutput.OriginalInstanceIndices = InOriginalInstancedIndices[InstObjIdx]; CurInstancedOutput.VariationObjects.Add(OriginalObj); CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); @@ -843,6 +953,7 @@ FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( } CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; + CurInstancedOutput.OriginalInstanceIndices = InOriginalInstancedIndices[InstObjIdx]; // Shouldnt be needed... CurInstancedOutput.OriginalObjectIndex = InstObjIdx; @@ -852,7 +963,7 @@ FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( for (int32 VarIdx = CurInstancedOutput.VariationObjects.Num() - 1; VarIdx >= 0; --VarIdx) { UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); - if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill() || (ReplacedOriginalObject && ReplacedOriginalObject == CurrentVariationObject)) + if (!IsValid(CurrentVariationObject) || (ReplacedOriginalObject && ReplacedOriginalObject == CurrentVariationObject)) { ObjsToRemove.Add(VarIdx); } @@ -886,7 +997,7 @@ FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( for (int32 VarIdx = 0; VarIdx < CurInstancedOutput.VariationObjects.Num(); VarIdx++) { UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); - if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill()) + if (!IsValid(CurrentVariationObject)) continue; // Get the transforms assigned to that variation @@ -1008,6 +1119,7 @@ FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancedHGPO, TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, FString& OutSplitAttributeName, TArray& OutSplitAttributeValue, TMap& OutPerSplitAttributes) @@ -1055,12 +1167,17 @@ FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_PRIM); + // Get the unreal_bake_folder attribute + TArray AllBakeFolders; + const bool bHasBakeFolders = FHoudiniEngineUtils::GetBakeFolderAttribute( + InHGPO.GeoId, HAPI_ATTROWNER_PRIM, AllBakeFolders, InHGPO.PartId); + // Get the bake outliner folder attribute TArray AllBakeOutlinerFolders; const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_PRIM); - const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders; + const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders || bHasBakeFolders; for (const auto& InstancedPartId : InstancedPartIds) { @@ -1079,6 +1196,15 @@ FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( OutInstancedHGPO.Add(InstancedHGPO); OutInstancedTransforms.Add(InstancerUnrealTransforms); + + TArray Indices; + Indices.SetNum(InstancerUnrealTransforms.Num()); + for (int32 Index = 0; Index < Indices.Num(); ++Index) + { + Indices[Index] = Index; + } + + OutInstancedIndices.Add(Indices); } // If we don't need to split the instances, we're done @@ -1091,20 +1217,24 @@ FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( // Move the output arrays to temp arrays TArray UnsplitInstancedHGPOs = OutInstancedHGPO; TArray> UnsplitInstancedTransforms = OutInstancedTransforms; + TArray> UnsplitInstancedIndices = OutInstancedIndices; // Empty the output arrays OutInstancedHGPO.Empty(); OutInstancedTransforms.Empty(); + OutInstancedIndices.Empty(); OutSplitAttributeValue.Empty(); for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedHGPOs.Num(); ObjIdx++) { // Map of split values to transform arrays TMap> SplitTransformMap; + TMap> SplitIndicesMap; TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; + TArray& CurrentIndices = UnsplitInstancedIndices[ObjIdx]; int32 NumInstances = CurrentTransforms.Num(); - if (AllSplitAttributeValues.Num() != NumInstances) + if (AllSplitAttributeValues.Num() != NumInstances || CurrentIndices.Num() != NumInstances) continue; // Split the transforms using the split values @@ -1112,6 +1242,7 @@ FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( { const FString& SplitAttrValue = AllSplitAttributeValues[InstIdx]; SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); + SplitIndicesMap.FindOrAdd(SplitAttrValue).Add(CurrentIndices[InstIdx]); // Record attributes for any split value we have not yet seen if (bHasAnyPerSplitAttributes) @@ -1125,6 +1256,10 @@ FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( { PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; } + if (bHasBakeFolders && PerSplitAttributes.BakeFolder.IsEmpty() && AllBakeFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeFolder = AllBakeFolders[InstIdx]; + } if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) { PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; @@ -1138,6 +1273,7 @@ FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( OutSplitAttributeValue.Add(Iterator.Key); OutInstancedHGPO.Add(UnsplitInstancedHGPOs[ObjIdx]); OutInstancedTransforms.Add(Iterator.Value); + OutInstancedIndices.Add(SplitIndicesMap[Iterator.Key]); } } @@ -1152,6 +1288,7 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancedObjects, TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, FString& OutSplitAttributeName, TArray& OutSplitAttributeValue, TMap& OutPerSplitAttributes) @@ -1226,12 +1363,17 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_POINT); + // Get the unreal_bake_folder attribute + TArray AllBakeFolders; + const bool bHasBakeFolders = FHoudiniEngineUtils::GetBakeFolderAttribute( + InHGPO.GeoId, HAPI_ATTROWNER_POINT, AllBakeFolders, InHGPO.PartId); + // Get the bake outliner folder attribute TArray AllBakeOutlinerFolders; const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_POINT); - const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders; + const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders || bHasBakeFolders; // Array used to store the split values per objects // Will only be used if we have a split attribute @@ -1279,7 +1421,7 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( // Couldn't load the referenced object, use the default reference mesh UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (!DefaultReferenceSM || DefaultReferenceSM->IsPendingKill()) + if (!IsValid(DefaultReferenceSM)) { HOUDINI_LOG_WARNING(TEXT("Failed to load the default instance mesh.")); return false; @@ -1294,6 +1436,15 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( OutInstancedObjects.Add(AttributeObject); OutInstancedTransforms.Add(InstancerUnrealTransforms); + TArray Indices; + Indices.SetNum(InstancerUnrealTransforms.Num()); + for (int32 Index = 0; Index < Indices.Num(); ++Index) + { + Indices[Index] = Index; + } + + OutInstancedIndices.Add(Indices); + if(bHasSplitAttribute) SplitAttributeValuesPerObject.Add(AllSplitAttributeValues); } @@ -1329,8 +1480,10 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( { // To avoid trying to load an object that fails multiple times, // still add it to the array if null so we can still skip further attempts - UObject * AttributeObject = StaticLoadObject( - UObject::StaticClass(), nullptr, *Iter, nullptr, LOAD_None, nullptr); + UObject* AttributeObject = StaticFindObjectSafe(UObject::StaticClass(), nullptr, *Iter); + if (IsValid(AttributeObject)) + AttributeObject = StaticLoadObject( + UObject::StaticClass(), nullptr, *Iter, nullptr, LOAD_None, nullptr); if (!AttributeObject) { @@ -1361,7 +1514,7 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( // If failed to load this object, add default reference mesh UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) + if (IsValid(DefaultReferenceSM)) { AttributeObject = DefaultReferenceSM; bHiddenInGame = true; @@ -1382,14 +1535,20 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( // Extract the transform values that correspond to this object, and add them to the output arrays const FString & InstancePath = Iter.Key; TArray ObjectTransforms; + TArray ObjectIndices; + for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) { if (InstancePath.Equals(PointInstanceValues[Idx])) + { ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); + ObjectIndices.Add(Idx); + } } OutInstancedObjects.Add(AttributeObject); OutInstancedTransforms.Add(ObjectTransforms); + OutInstancedIndices.Add(ObjectIndices); Success = true; } else @@ -1399,18 +1558,21 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( // add them to the output arrays, and we will process the splits after const FString & InstancePath = Iter.Key; TArray ObjectTransforms; + TArray ObjectIndices; TArray ObjectSplitValues; for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) { if (InstancePath.Equals(PointInstanceValues[Idx])) { ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); + ObjectIndices.Add(Idx); ObjectSplitValues.Add(AllSplitAttributeValues[Idx]); } } OutInstancedObjects.Add(AttributeObject); OutInstancedTransforms.Add(ObjectTransforms); + OutInstancedIndices.Add(ObjectIndices); SplitAttributeValuesPerObject.Add(ObjectSplitValues); Success = true; } @@ -1429,10 +1591,12 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( // Move the output arrays to temp arrays TArray UnsplitInstancedObjects = OutInstancedObjects; TArray> UnsplitInstancedTransforms = OutInstancedTransforms; + TArray> UnsplitInstancedIndices = OutInstancedIndices; // Empty the output arrays OutInstancedObjects.Empty(); OutInstancedTransforms.Empty(); + OutInstancedIndices.Empty(); // TODO: Output the split values as well! OutSplitAttributeValue.Empty(); @@ -1442,12 +1606,14 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( // Map of split values to transform arrays TMap> SplitTransformMap; + TMap> SplitIndicesMap; TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; + TArray& CurrentIndices = UnsplitInstancedIndices[ObjIdx]; TArray& CurrentSplits = SplitAttributeValuesPerObject[ObjIdx]; int32 NumInstances = CurrentTransforms.Num(); - if (CurrentSplits.Num() != NumInstances) + if (CurrentSplits.Num() != NumInstances || CurrentIndices.Num() != NumInstances) continue; // Split the transforms using the split values @@ -1455,6 +1621,7 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( { const FString& SplitAttrValue = CurrentSplits[InstIdx]; SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); + SplitIndicesMap.FindOrAdd(SplitAttrValue).Add(CurrentIndices[InstIdx]); // Record attributes for any split value we have not yet seen if (bHasAnyPerSplitAttributes) @@ -1468,6 +1635,10 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( { PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; } + if (bHasBakeFolders && PerSplitAttributes.BakeFolder.IsEmpty() && AllBakeFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeFolder = AllBakeFolders[InstIdx]; + } if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) { PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; @@ -1480,7 +1651,8 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( { OutSplitAttributeValue.Add(Iterator.Key); OutInstancedObjects.Add(InstancedObject); - OutInstancedTransforms.Add(Iterator.Value); + OutInstancedTransforms.Add(Iterator.Value); + OutInstancedIndices.Add(SplitIndicesMap[Iterator.Key]); } } @@ -1495,7 +1667,8 @@ FHoudiniInstanceTranslator::GetOldSchoolAttributeInstancerHGPOsAndTransforms( const FHoudiniGeoPartObject& InHGPO, const TArray& InAllOutputs, TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms) + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices) { if (InHGPO.InstancerType != EHoudiniInstancerType::OldSchoolAttributeInstancer) return false; @@ -1546,11 +1719,13 @@ FHoudiniInstanceTranslator::GetOldSchoolAttributeInstancerHGPOsAndTransforms( // Extract only the transforms that correspond to that specific object ID TArray InstanceTransforms; + TArray InstanceIndices; for (int32 Ix = 0; Ix < InstancedObjectIds.Num(); ++Ix) { if ((InstancedObjectIds[Ix] == InstancedObjectId) && (InstancerUnrealTransforms.IsValidIndex(Ix))) { InstanceTransforms.Add(InstancerUnrealTransforms[Ix]); + InstanceIndices.Add(Ix); } } @@ -1559,10 +1734,11 @@ FHoudiniInstanceTranslator::GetOldSchoolAttributeInstancerHGPOsAndTransforms( { OutInstancedHGPO.Add(PartToInstance); OutInstancedTransforms.Add(InstanceTransforms); + OutInstancedIndices.Add(InstanceIndices); } } - if(OutInstancedHGPO.Num() > 0 && OutInstancedTransforms.Num() > 0) + if(OutInstancedHGPO.Num() > 0 && OutInstancedTransforms.Num() > 0 && OutInstancedIndices.Num() > 0) return true; return false; @@ -1574,7 +1750,8 @@ FHoudiniInstanceTranslator::GetObjectInstancerHGPOsAndTransforms( const FHoudiniGeoPartObject& InHGPO, const TArray& InAllOutputs, TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms) + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices) { if (InHGPO.InstancerType != EHoudiniInstancerType::ObjectInstancer) return false; @@ -1625,6 +1802,15 @@ FHoudiniInstanceTranslator::GetObjectInstancerHGPOsAndTransforms( OutInstancedHGPO.Add(InstanceHGPO); OutInstancedTransforms.Add(InstancerUnrealTransforms); + + TArray Indices; + Indices.SetNum(InstancerUnrealTransforms.Num()); + for (int32 Index = 0; Index < Indices.Num(); ++Index) + { + Indices[Index] = Index; + } + + OutInstancedIndices.Add(Indices); } return true; @@ -1642,8 +1828,10 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( const bool& InIsSplitMeshInstancer, const bool& InIsFoliageInstancer, const TArray& InstancerMaterials, + const TArray& OriginalInstancerObjectIndices, const int32& InstancerObjectIdx, - const bool& bForceHISM) + const bool& bForceHISM, + const bool& bForceInstancer) { enum InstancerComponentType { @@ -1659,9 +1847,13 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( // See if we can reuse the old component InstancerComponentType OldType = InstancerComponentType::Invalid; - if (OldComponent && !OldComponent->IsPendingKill()) + + // The old component could be marked as pending kill, dont IsValid() here + if (OldComponent != nullptr) { - if (OldComponent->GetOwner() && OldComponent->GetOwner()->IsA()) + if(OldComponent->IsA()) + OldType = Foliage; + else if (OldComponent->GetOwner() && OldComponent->GetOwner()->IsA()) OldType = Foliage; else if (OldComponent->IsA()) OldType = HierarchicalInstancedStaticMeshComponent; @@ -1689,16 +1881,17 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( if (StaticMesh) { - if (InstancedObjectTransforms.Num() == 1) - NewType = StaticMeshComponent; - else if (InIsFoliageInstancer) + const bool bMustUseInstancerComponent = InstancedObjectTransforms.Num() > 1 || bForceInstancer; + if (InIsFoliageInstancer) NewType = Foliage; else if (InIsSplitMeshInstancer) NewType = MeshSplitInstancerComponent; - else if(StaticMesh->GetNumLODs() > 1 || bForceHISM) + else if (bForceHISM || (bMustUseInstancerComponent && StaticMesh->GetNumLODs() > 1)) NewType = HierarchicalInstancedStaticMeshComponent; - else + else if (bMustUseInstancerComponent) NewType = InstancedStaticMeshComponent; + else + NewType = StaticMeshComponent; } else if (HSM) { @@ -1723,14 +1916,14 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( if (OldType == NewType) NewComponent = OldComponent; - UMaterialInterface* InstancerMaterial = nullptr; - if (InstancerMaterials.Num() > 0) - { - if (InstancerMaterials.IsValidIndex(InstancerObjectIdx)) - InstancerMaterial = InstancerMaterials[InstancerObjectIdx]; - else - InstancerMaterial = InstancerMaterials[0]; - } + // First valid index in the original instancer part + // This should be used to access attributes that are store for the whole part, not split + // (ie, GenericProperty Attributes) + int32 FirstOriginalIndex = OriginalInstancerObjectIndices.Num() > 0 ? OriginalInstancerObjectIndices[0] : 0; + + // InstancerMaterials has all the material assignement per instance, + // Fetch the first one for the components that can only use one material + UMaterialInterface* InstancerMaterial = InstancerMaterials.Num() > 0 ? InstancerMaterials[0] : nullptr; bool bSuccess = false; switch (NewType) @@ -1740,7 +1933,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( { // Create an Instanced Static Mesh Component bSuccess = CreateOrUpdateInstancedStaticMeshComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial, bForceHISM); + StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial, bForceHISM, FirstOriginalIndex); } break; @@ -1754,7 +1947,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( case HoudiniInstancedActorComponent: { bSuccess = CreateOrUpdateInstancedActorComponent( - InstancedObject, InstancedObjectTransforms, AllPropertyAttributes, ParentComponent, NewComponent); + InstancedObject, InstancedObjectTransforms, OriginalInstancerObjectIndices, AllPropertyAttributes, ParentComponent, NewComponent); } break; @@ -1762,7 +1955,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( { // Create a Static Mesh Component bSuccess = CreateOrUpdateStaticMeshComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + StaticMesh, InstancedObjectTransforms, FirstOriginalIndex, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); } break; @@ -1770,14 +1963,14 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( { // Create a Houdini Static Mesh Component bSuccess = CreateOrUpdateHoudiniStaticMeshComponent( - HSM, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + HSM, InstancedObjectTransforms, FirstOriginalIndex, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); } break; case Foliage: { bSuccess = CreateOrUpdateFoliageInstances( - StaticMesh, FoliageType, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + StaticMesh, FoliageType, InstancedObjectTransforms, FirstOriginalIndex, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); } } @@ -1797,9 +1990,9 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( NewComponent->RegisterComponent(); // If the old component couldn't be reused, dettach/ destroy it - if (OldComponent && !OldComponent->IsPendingKill() && (OldComponent != NewComponent)) + if (IsValid(OldComponent) && (OldComponent != NewComponent)) { - RemoveAndDestroyComponent(OldComponent); + RemoveAndDestroyComponent(OldComponent, nullptr); } return bSuccess; @@ -1814,21 +2007,22 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( USceneComponent* ParentComponent, USceneComponent*& CreatedInstancedComponent, UMaterialInterface * InstancerMaterial, /*=nullptr*/ - const bool & bForceHISM) + const bool & bForceHISM, + const int32& InstancerObjectIdx) { if (!InstancedStaticMesh) return false; - if (!ParentComponent || ParentComponent->IsPendingKill()) + if (!IsValid(ParentComponent)) return false; UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + if (IsValid(ParentComponent->GetOwner())) ComponentOuter = ParentComponent->GetOwner(); bool bCreatedNewComponent = false; UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast(CreatedInstancedComponent); - if (!InstancedStaticMeshComponent || InstancedStaticMeshComponent->IsPendingKill()) + if (!IsValid(InstancedStaticMeshComponent)) { if (InstancedStaticMesh->GetNumLODs() > 1 || bForceHISM) { @@ -1844,7 +2038,10 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( } // Change the creation method so the component is listed in the details panels - InstancedStaticMeshComponent->CreationMethod = EComponentCreationMethod::Instance; + if (InstancedStaticMeshComponent) + { + InstancedStaticMeshComponent->CreationMethod = EComponentCreationMethod::Instance; + } bCreatedNewComponent = true; } @@ -1853,40 +2050,31 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( return false; InstancedStaticMeshComponent->SetStaticMesh(InstancedStaticMesh); - InstancedStaticMeshComponent->GetBodyInstance()->bAutoWeld = false; + + if (InstancedStaticMeshComponent->GetBodyInstance()) + { + InstancedStaticMeshComponent->GetBodyInstance()->bAutoWeld = false; + } InstancedStaticMeshComponent->OverrideMaterials.Empty(); if (InstancerMaterial) { - int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); + int32 MeshMaterialCount = InstancedStaticMesh->GetStaticMaterials().Num(); for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) InstancedStaticMeshComponent->SetMaterial(Idx, InstancerMaterial); } // Now add the instances themselves - // TODO: We should be calling UHoudiniInstancedActorComponent::UpdateInstancerComponentInstances( ... ) InstancedStaticMeshComponent->ClearInstances(); - InstancedStaticMeshComponent->PreAllocateInstancesMemory(InstancedObjectTransforms.Num()); - for (const auto& Transform : InstancedObjectTransforms) - { - InstancedStaticMeshComponent->AddInstance(Transform); - } + InstancedStaticMeshComponent->AddInstances(InstancedObjectTransforms, false); // Apply generic attributes if we have any - // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(InstancedStaticMeshComponent, AllPropertyAttributes, 0); - } + UpdateGenericPropertiesAttributes(InstancedStaticMeshComponent, AllPropertyAttributes, InstancerObjectIdx); // Assign the new ISMC / HISMC to the output component if we created a new one if(bCreatedNewComponent) CreatedInstancedComponent = InstancedStaticMeshComponent; - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - return true; } @@ -1894,23 +2082,24 @@ bool FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( UObject* InstancedObject, const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, + const TArray& OriginalInstancerObjectIndices, + const TArray& AllPropertyAttributes, USceneComponent* ParentComponent, USceneComponent*& CreatedInstancedComponent) { if (!InstancedObject) return false; - if (!ParentComponent || ParentComponent->IsPendingKill()) + if (!IsValid(ParentComponent)) return false; UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + if (IsValid(ParentComponent->GetOwner())) ComponentOuter = ParentComponent->GetOwner(); bool bCreatedNewComponent = false; UHoudiniInstancedActorComponent* InstancedActorComponent = Cast(CreatedInstancedComponent); - if (!InstancedActorComponent || InstancedActorComponent->IsPendingKill()) + if (!IsValid(InstancedActorComponent)) { // If the mesh doesnt have LOD, we can use a regular ISMC InstancedActorComponent = NewObject( @@ -1943,6 +2132,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( // Set the number of needed instances InstancedActorComponent->SetNumberOfInstances(InstancedObjectTransforms.Num()); + for (int32 Idx = 0; Idx < InstancedObjectTransforms.Num(); Idx++) { // if we already have an actor, we can reuse it @@ -1951,7 +2141,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( // Get the current instance // If null, we need to create a new one, else we can reuse the actor AActor* CurInstance = InstancedActorComponent->GetInstancedActorAt(Idx); - if (!CurInstance || CurInstance->IsPendingKill()) + if (!IsValid(CurInstance)) { CurInstance = SpawnInstanceActor(CurTransform, SpawnLevel, InstancedActorComponent); InstancedActorComponent->SetInstanceAt(Idx, CurTransform, CurInstance); @@ -1963,8 +2153,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( } // Update the generic properties for that instance if any - // TODO: Handle instance variations w/ Idx - UpdateGenericPropertiesAttributes(CurInstance, AllPropertyAttributes, Idx); + UpdateGenericPropertiesAttributes(CurInstance, AllPropertyAttributes, OriginalInstancerObjectIndices[Idx]); } // Assign the new ISMC / HISMC to the output component if we created a new one @@ -1990,16 +2179,16 @@ FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( if (!InstancedStaticMesh) return false; - if (!ParentComponent || ParentComponent->IsPendingKill()) + if (!IsValid(ParentComponent)) return false; UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + if (IsValid(ParentComponent->GetOwner())) ComponentOuter = ParentComponent->GetOwner(); bool bCreatedNewComponent = false; UHoudiniMeshSplitInstancerComponent* MeshSplitComponent = Cast(CreatedInstancedComponent); - if (!MeshSplitComponent || MeshSplitComponent->IsPendingKill()) + if (!IsValid(MeshSplitComponent)) { // If the mesh doesn't have LOD, we can use a regular ISMC MeshSplitComponent = NewObject( @@ -2106,7 +2295,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) { UStaticMeshComponent* CurSMC = Instances[InstIndex]; - if (!CurSMC || CurSMC->IsPendingKill()) + if (!IsValid(CurSMC)) continue; if (!InstanceColors.IsValidIndex(InstIndex)) @@ -2144,13 +2333,10 @@ FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) { UStaticMeshComponent* CurSMC = Instances[InstIndex]; - if (!CurSMC || CurSMC->IsPendingKill()) + if (!IsValid(CurSMC)) continue; - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(CurSMC, AllPropertyAttributes, InstIndex); - } + UpdateGenericPropertiesAttributes(CurSMC, AllPropertyAttributes, InstIndex); } } @@ -2169,6 +2355,7 @@ bool FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( UStaticMesh* InstancedStaticMesh, const TArray& InstancedObjectTransforms, + const int32& InOriginalIndex, const TArray& AllPropertyAttributes, const FHoudiniGeoPartObject& InstancerGeoPartObject, USceneComponent* ParentComponent, @@ -2178,16 +2365,16 @@ FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( if (!InstancedStaticMesh) return false; - if (!ParentComponent || ParentComponent->IsPendingKill()) + if (!IsValid(ParentComponent)) return false; UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + if (IsValid(ParentComponent->GetOwner())) ComponentOuter = ParentComponent->GetOwner(); bool bCreatedNewComponent = false; UStaticMeshComponent* SMC = Cast(CreatedInstancedComponent); - if (!SMC || SMC->IsPendingKill()) + if (!IsValid(SMC)) { // Create a new StaticMeshComponent SMC = NewObject( @@ -2208,20 +2395,19 @@ FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( SMC->OverrideMaterials.Empty(); if (InstancerMaterial) { - int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); + int32 MeshMaterialCount = InstancedStaticMesh->GetStaticMaterials().Num(); for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) SMC->SetMaterial(Idx, InstancerMaterial); } // Now add the instances Transform - SMC->SetRelativeTransform(InstancedObjectTransforms[0]); + if (InstancedObjectTransforms.Num() > 0) + { + SMC->SetRelativeTransform(InstancedObjectTransforms[0]); + } // Apply generic attributes if we have any - // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(SMC, AllPropertyAttributes, 0); - } + UpdateGenericPropertiesAttributes(SMC, AllPropertyAttributes, InOriginalIndex); // Assign the new ISMC / HISMC to the output component if we created a new one if (bCreatedNewComponent) @@ -2238,6 +2424,7 @@ bool FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( UHoudiniStaticMesh* InstancedProxyStaticMesh, const TArray& InstancedObjectTransforms, + const int32& InOriginalIndex, const TArray& AllPropertyAttributes, const FHoudiniGeoPartObject& InstancerGeoPartObject, USceneComponent* ParentComponent, @@ -2247,16 +2434,16 @@ FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( if (!InstancedProxyStaticMesh) return false; - if (!ParentComponent || ParentComponent->IsPendingKill()) + if (!IsValid(ParentComponent)) return false; UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + if (IsValid(ParentComponent->GetOwner())) ComponentOuter = ParentComponent->GetOwner(); bool bCreatedNewComponent = false; UHoudiniStaticMeshComponent* HSMC = Cast(CreatedInstancedComponent); - if (!HSMC || HSMC->IsPendingKill()) + if (!IsValid(HSMC)) { // Create a new StaticMeshComponent HSMC = NewObject( @@ -2269,7 +2456,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( } if (!HSMC) - return false; + return false; HSMC->SetMesh(InstancedProxyStaticMesh); @@ -2286,10 +2473,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( // Apply generic attributes if we have any // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(HSMC, AllPropertyAttributes, 0); - } + UpdateGenericPropertiesAttributes(HSMC, AllPropertyAttributes, InOriginalIndex); // Assign the new HSMC to the output component if we created a new one if (bCreatedNewComponent) @@ -2308,38 +2492,39 @@ FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( UStaticMesh* InstancedStaticMesh, UFoliageType* InFoliageType, const TArray& InstancedObjectTransforms, + const int32& FirstOriginalIndex, const TArray& AllPropertyAttributes, const FHoudiniGeoPartObject& InstancerGeoPartObject, USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, + USceneComponent*& NewInstancedComponent, UMaterialInterface * InstancerMaterial /*=nullptr*/) { // We need either a valid SM or a valid Foliage Type - if ((!InstancedStaticMesh || InstancedStaticMesh->IsPendingKill()) - && (!InFoliageType || InFoliageType->IsPendingKill())) + if ((!IsValid(InstancedStaticMesh)) + && (!IsValid(InFoliageType))) return false; - if (!ParentComponent || ParentComponent->IsPendingKill()) + if (!IsValid(ParentComponent)) return false; UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + if (IsValid(ParentComponent->GetOwner())) ComponentOuter = ParentComponent->GetOwner(); AActor* OwnerActor = ParentComponent->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) return false; ULevel* DesiredLevel = GWorld->GetCurrentLevel(); AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + if (!IsValid(InstancedFoliageActor)) return false; // See if we already have a FoliageType for that static mesh bool bCreatedNew = false; UFoliageType *FoliageType = InFoliageType; - if (!FoliageType || FoliageType->IsPendingKill()) + if (!IsValid(FoliageType)) { // Foliage Type wasnt specified, only the mesh, try to find an existing foliage for that SM FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InstancedStaticMesh); @@ -2359,7 +2544,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( bCreatedNew = InstancedFoliageActor->FindInfo(FoliageType) == nullptr; } - if (!FoliageType || FoliageType->IsPendingKill()) + if (!IsValid(FoliageType)) { // We need to create a new FoliageType for this Static Mesh // TODO: Add foliage default settings @@ -2367,7 +2552,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( bCreatedNew = true; } - if (!bCreatedNew && CreatedInstancedComponent) + if (!bCreatedNew && NewInstancedComponent) { // TODO: Shouldnt be needed anymore // Clean up the instances previously generated for that component @@ -2382,6 +2567,8 @@ FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( FTransform HoudiniAssetTransform = ParentComponent->GetComponentTransform(); FFoliageInstance FoliageInstance; int32 CurrentInstanceCount = 0; + + FoliageInfo->ReserveAdditionalInstances(InstancedFoliageActor, FoliageType, InstancedObjectTransforms.Num()); for (auto CurrentTransform : InstancedObjectTransforms) { // Use our parent component for the base component of the instances, @@ -2408,37 +2595,30 @@ FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( CurrentInstanceCount++; } - UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = FoliageInfo->GetComponent(); - // TODO: This was due to a bug in UE4.22-20, check if still needed! - if (FoliageHISMC) - FoliageHISMC->BuildTreeIfOutdated(true, true); - - if (InstancerMaterial) + UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = FoliageInfo->GetComponent(); + if (IsValid(FoliageHISMC)) { - FoliageHISMC->OverrideMaterials.Empty(); - int32 MeshMaterialCount = InstancedStaticMesh ? InstancedStaticMesh->StaticMaterials.Num() : 1; - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - FoliageHISMC->SetMaterial(Idx, InstancerMaterial); - } + // TODO: This was due to a bug in UE4.22-20, check if still needed! + FoliageHISMC->BuildTreeIfOutdated(true, true); - // Apply generic attributes if we have any - /* - // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); + if (InstancerMaterial) + { + FoliageHISMC->OverrideMaterials.Empty(); + int32 MeshMaterialCount = InstancedStaticMesh ? InstancedStaticMesh->GetStaticMaterials().Num() : 1; + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + FoliageHISMC->SetMaterial(Idx, InstancerMaterial); + } } - */ - // Try to aplly generic properties attributes + // Try to apply generic properties attributes // either on the instancer, mesh or foliage type // TODO: Use proper atIndex!! - UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); - UpdateGenericPropertiesAttributes(InstancedStaticMesh, AllPropertyAttributes, 0); - UpdateGenericPropertiesAttributes(FoliageType, AllPropertyAttributes, 0); + UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, FirstOriginalIndex); + UpdateGenericPropertiesAttributes(InstancedStaticMesh, AllPropertyAttributes, FirstOriginalIndex); + UpdateGenericPropertiesAttributes(FoliageType, AllPropertyAttributes, FirstOriginalIndex); - if (bCreatedNew && FoliageHISMC) - CreatedInstancedComponent = FoliageHISMC; + if (IsValid(FoliageHISMC)) + NewInstancedComponent = FoliageHISMC; // TODO: // We want to make this invisible if it's a collision instancer. @@ -2507,40 +2687,47 @@ bool FHoudiniInstanceTranslator::UpdateGenericPropertiesAttributes( UObject* InObject, const TArray& InAllPropertyAttributes, const int32& AtIndex) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; // Iterate over the found Property attributes int32 NumSuccess = 0; - for (auto CurrentPropAttribute : InAllPropertyAttributes) + for (const auto& CurrentPropAttribute : InAllPropertyAttributes) { + if (CurrentPropAttribute.AttributeName.Equals(TEXT("NumCustomDataFloats"), ESearchCase::IgnoreCase)) + { + // Skip, as setting NumCustomDataFloats this way causes Unreal to crash! + HOUDINI_LOG_WARNING( + TEXT("Skipping UProperty %s on %s, custom data floats should be modified via the unreal_num_custom_floats and unreal_per_instance_custom_dataX attributes"), + *CurrentPropAttribute.AttributeName, *InObject->GetName()); + continue; + } + // Update the current property for the given instance index if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute, AtIndex)) continue; // Success! NumSuccess++; - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, InObject->GetClass() ? *InObject->GetClass()->GetName() : TEXT("Object"), *InObject->GetName()); } return (NumSuccess > 0); } bool -FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent) +FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent, UObject* InFoliageObject) { - if (!InComponent || InComponent->IsPendingKill()) + if (!IsValid(InComponent)) return false; UFoliageInstancedStaticMeshComponent* FISMC = Cast(InComponent); - if (FISMC && !FISMC->IsPendingKill()) + if (IsValid(FISMC)) { // Make sure foliage our foliage instances have been removed USceneComponent* ParentComponent = Cast(FISMC->GetOuter()); - if (ParentComponent && !ParentComponent->IsPendingKill()) - CleanupFoliageInstances(FISMC, ParentComponent); + if (IsValid(ParentComponent)) + CleanupFoliageInstances(FISMC, InFoliageObject, ParentComponent); // do not delete FISMC that still have instances left // as we have cleaned up our instances before, these have been hand-placed @@ -2549,7 +2736,7 @@ FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent) } USceneComponent* SceneComponent = Cast(InComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) + if (IsValid(SceneComponent)) { // Remove from the HoudiniAssetActor if (SceneComponent->GetOwner()) @@ -2568,25 +2755,37 @@ FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent) bool FHoudiniInstanceTranslator::GetMaterialOverridesFromAttributes( - const int32& InGeoNodeId, const int32& InPartId, TArray& OutMaterialAttributes) + const int32& InGeoNodeId, const int32& InPartId, TArray& OutMaterialAttributes, bool& OutMaterialOverrideNeedsCreateInstance) { HAPI_AttributeInfo MaterialAttributeInfo; FHoudiniApi::AttributeInfo_Init(&MaterialAttributeInfo); + OutMaterialOverrideNeedsCreateInstance = false; + FHoudiniEngineUtils::HapiGetAttributeDataAsString( InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL, MaterialAttributeInfo, OutMaterialAttributes); - /* - // TODO: Support material instances on instancers... - // see FHoudiniMaterialTranslator::CreateMaterialInstances() + // If material attribute was not found, check fallback compatibility attribute. + if (!MaterialAttributeInfo.exists) + { + OutMaterialAttributes.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoNodeId, InPartId, + HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK, + MaterialAttributeInfo, OutMaterialAttributes); + } + // If material attribute and fallbacks were not found, check the material instance attribute. - if (!AttribInfoFaceMaterialOverrides.exists) + if (!MaterialAttributeInfo.exists) { - PartFaceMaterialOverrides.Empty(); + OutMaterialAttributes.Empty(); FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, MaterialAttributeInfo, OutMaterialAttributes); + InGeoNodeId, InPartId, + HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, + MaterialAttributeInfo, OutMaterialAttributes); + + OutMaterialOverrideNeedsCreateInstance = true; } - */ if (!MaterialAttributeInfo.exists /*&& MaterialAttributeInfo.owner != HAPI_ATTROWNER_PRIM @@ -2594,6 +2793,7 @@ FHoudiniInstanceTranslator::GetMaterialOverridesFromAttributes( { //HOUDINI_LOG_WARNING(TEXT("Instancer: the unreal_material attribute must be a primitive or detail attribute, ignoring the attribute.")); OutMaterialAttributes.Empty(); + OutMaterialOverrideNeedsCreateInstance = false; return false; } @@ -2608,6 +2808,8 @@ FHoudiniInstanceTranslator::GetInstancerMaterials( TMap MaterialMap; bool bHasValidMaterial = false; + + // Non-instanced materials check material attributes one by one for (auto& CurrentMatString : MaterialAttributes) { UMaterialInterface* CurrentMaterialInterface = nullptr; @@ -2619,7 +2821,7 @@ FHoudiniInstanceTranslator::GetInstancerMaterials( StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentMatString, nullptr, LOAD_NoWarn, nullptr)); // Check validity - if (!CurrentMaterialInterface || CurrentMaterialInterface->IsPendingKill()) + if (!IsValid(CurrentMaterialInterface)) CurrentMaterialInterface = nullptr; else bHasValidMaterial = true; @@ -2643,38 +2845,78 @@ FHoudiniInstanceTranslator::GetInstancerMaterials( return true; } +bool FHoudiniInstanceTranslator::GetInstancerMaterialInstances(const TArray& MaterialAttribute, + const FHoudiniGeoPartObject& InHGPO, const FHoudiniPackageParams& InPackageParams, + TArray& OutInstancerMaterials) +{ + TArray MaterialAndTexturePackages; + + // Purposefully empty material since it is satisfied by the override parameter + TMap InputAssignmentMaterials; + TMap OutputAssignmentMaterials; + + // The function in FHoudiniMaterialTranslator should already do the duplicate checks + if (FHoudiniMaterialTranslator::SortUniqueFaceMaterialOverridesAndCreateMaterialInstances(MaterialAttribute, InHGPO, InPackageParams, + MaterialAndTexturePackages, + InputAssignmentMaterials, OutputAssignmentMaterials, + false)) + { + OutputAssignmentMaterials.GenerateValueArray(OutInstancerMaterials); + if (OutInstancerMaterials.Num() > 0) + { + return true; + } + } + + return false; +} + bool FHoudiniInstanceTranslator::GetInstancerMaterials( - const int32& InGeoNodeId, const int32& InPartId, TArray& OutInstancerMaterials) + const int32& InGeoNodeId, const int32& InPartId, const FHoudiniGeoPartObject& InHGPO, const FHoudiniPackageParams& InPackageParams, TArray& OutInstancerMaterials) { TArray MaterialAttributes; - if (!GetMaterialOverridesFromAttributes(InGeoNodeId, InPartId, MaterialAttributes)) + bool bMaterialOverrideNeedsCreateInstance = false; + if (!GetMaterialOverridesFromAttributes(InGeoNodeId, InPartId, MaterialAttributes, bMaterialOverrideNeedsCreateInstance)) MaterialAttributes.Empty(); - return GetInstancerMaterials(MaterialAttributes, OutInstancerMaterials); + if (!bMaterialOverrideNeedsCreateInstance) + { + return GetInstancerMaterials(MaterialAttributes, OutInstancerMaterials); + } + else + { + return GetInstancerMaterialInstances(MaterialAttributes, InHGPO, InPackageParams, OutInstancerMaterials); + } } bool FHoudiniInstanceTranslator::GetVariationMaterials( - FHoudiniInstancedOutput* InInstancedOutput , const int32& InVariationIndex, - const TArray& InInstancerMaterials, TArray& OutVariationMaterials) + FHoudiniInstancedOutput* InInstancedOutput, + const int32& InVariationIndex, + const TArray& InOriginalIndices, + const TArray& InInstancerMaterials, + TArray& OutVariationMaterials) { if (!InInstancedOutput || InInstancerMaterials.Num() <= 0) return false; - - // TODO: This also need to be improved and wont work 100%!! - // Use the instancedoutputs original object index? - if(!InInstancedOutput->VariationObjects.IsValidIndex(InVariationIndex)) - return false; - /* - // No variations, reuse the array + + // No variations, reuse the full array if (InInstancedOutput->VariationObjects.Num() == 1) { - OutVariationMaterials = InInstancerMaterials; + for (int32 Idx = 0; Idx < InOriginalIndices.Num(); Idx++) + { + if (InInstancerMaterials.IsValidIndex(InOriginalIndices[Idx])) + OutVariationMaterials.Add(InInstancerMaterials[InOriginalIndices[Idx]]); + else + OutVariationMaterials.Add(InInstancerMaterials[0]); + } + return true; } - */ + // If we have variations, see if we can use the instancer mat array + // TODO: FIX ME! this wont work if we have split the instancer and added variations at the same time! if (InInstancedOutput->TransformVariationIndices.Num() == InInstancerMaterials.Num()) { for (int32 Idx = 0; Idx < InInstancedOutput->TransformVariationIndices.Num(); Idx++) @@ -2688,10 +2930,13 @@ FHoudiniInstanceTranslator::GetVariationMaterials( } else { - if (InInstancerMaterials.IsValidIndex(InVariationIndex)) - OutVariationMaterials.Add(InInstancerMaterials[InVariationIndex]); - else - OutVariationMaterials.Add(InInstancerMaterials[0]); + for (int32 Idx = 0; Idx < InOriginalIndices.Num(); Idx++) + { + if (InInstancerMaterials.IsValidIndex(InOriginalIndices[Idx])) + OutVariationMaterials.Add(InInstancerMaterials[InOriginalIndices[Idx]]); + else + OutVariationMaterials.Add(InInstancerMaterials[0]); + } } return true; @@ -2717,22 +2962,16 @@ FHoudiniInstanceTranslator::IsSplitInstancer(const int32& InGeoId, const int32& return false; HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), + TArray IntData; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, - Owner, &AttributeInfo), false); + AttributeInfo, IntData, 0, Owner, 0, 1)) + { + return false; + } if (!AttributeInfo.exists || AttributeInfo.count <= 0) return false; - - TArray IntData; - // Allocate sufficient buffer for data. - IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, - &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); return (IntData[0] != 0); } @@ -2764,54 +3003,33 @@ FHoudiniInstanceTranslator::IsFoliageInstancer(const int32& InGeoId, const int32 if (!bIsFoliageInstancer) return false; + TArray IntData; HAPI_AttributeInfo AttributeInfo; FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), + + // Get the first attribute value as Int + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - Owner, &AttributeInfo), false); + AttributeInfo, IntData, 0, Owner, 0, 1); if (!AttributeInfo.exists || AttributeInfo.count <= 0) return false; - // We only support int/float attributes - if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - TArray IntData; - // Allocate sufficient buffer for data. - IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); - - return (IntData[0] != 0); - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - TArray FloatData; - // Allocate sufficient buffer for data. - FloatData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - &AttributeInfo, 0, &FloatData[0], 0, AttributeInfo.count), false); - - return (FloatData[0] != 0); - } - - return false; + return (IntData[0] != 0); } AActor* -FHoudiniInstanceTranslator::SpawnInstanceActor(const FTransform& InTransform, ULevel* InSpawnLevel, UHoudiniInstancedActorComponent* InIAC) +FHoudiniInstanceTranslator::SpawnInstanceActor( + const FTransform& InTransform, + ULevel* InSpawnLevel, + UHoudiniInstancedActorComponent* InIAC) { - if (!InIAC || InIAC->IsPendingKill()) + if (!IsValid(InIAC)) return nullptr; UObject* InstancedObject = InIAC->GetInstancedObject(); - if (!InstancedObject || InstancedObject->IsPendingKill()) + if (!IsValid(InstancedObject)) return nullptr; AActor* NewActor = nullptr; @@ -2824,7 +3042,7 @@ FHoudiniInstanceTranslator::SpawnInstanceActor(const FTransform& InTransform, UL TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject(InSpawnLevel, InstancedObject, false, RF_Transactional, nullptr); if (NewActors.Num() > 0) { - if (NewActors[0] && !NewActors[0]->IsPendingKill()) + if (IsValid(NewActors[0])) { NewActor = NewActors[0]; } @@ -2839,24 +3057,34 @@ FHoudiniInstanceTranslator::SpawnInstanceActor(const FTransform& InTransform, UL void -FHoudiniInstanceTranslator::CleanupFoliageInstances(/*const FHoudiniInstancedOutput& InInstancedOutput,*/ UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, USceneComponent* InParentComponent) +FHoudiniInstanceTranslator::CleanupFoliageInstances( + UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, + UObject* InInstancedObject, + USceneComponent* InParentComponent) { - if (!InFoliageHISMC || InFoliageHISMC->IsPendingKill()) + if (!IsValid(InFoliageHISMC)) return; UStaticMesh* FoliageSM = InFoliageHISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) + if (!IsValid(FoliageSM)) return; // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, // if it is not, then we are just a "regular" HISMC AInstancedFoliageActor* InstancedFoliageActor = Cast(InFoliageHISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + if (!IsValid(InstancedFoliageActor)) return; - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) - return; + // Get the Foliage Type + UFoliageType *FoliageType = Cast(InInstancedObject); + if (!IsValid(FoliageType)) + { + // Try to get the foliage type for the instanced mesh from the actor + FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InInstancedObject); + + if (!IsValid(FoliageType)) + return; + } // Clean up the instances previously generated for that component InstancedFoliageActor->DeleteInstancesForComponent(InParentComponent, FoliageType); @@ -2875,7 +3103,7 @@ FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(UObject* InObject) USceneComponent* InComponent = Cast(InObject); FString InstancerType = TEXT("Instancer"); - if (InComponent && !InComponent->IsPendingKill()) + if (IsValid(InComponent)) { if (InComponent->IsA()) { @@ -2925,8 +3153,8 @@ FHoudiniInstanceTranslator::GetInstancerSplitAttributesAndValues( TArray StringData; bHasSplitAttribute = FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_SPLIT_ATTR, SplitAttribInfo, StringData, 1); + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_ATTR, + SplitAttribInfo, StringData, 1, InSplitAttributeOwner, 0, 1); if (!bHasSplitAttribute || !SplitAttribInfo.exists || StringData.Num() <= 0) return false; @@ -2950,13 +3178,22 @@ FHoudiniInstanceTranslator::GetInstancerSplitAttributesAndValues( if (!bSplitAttrFound || OutAllSplitAttributeValues.Num() <= 0) { - // We couldn't properly get the point values, clean up everything - // to ensure that we'll ignore the split attribute + // We couldn't properly get the point values bHasSplitAttribute = false; - OutAllSplitAttributeValues.Empty(); - OutSplitAttributeName = FString(); } } + else + { + // We couldn't properly get the split attribute + bHasSplitAttribute = false; + } + + if (!bHasSplitAttribute) + { + // Clean up everything to ensure that we'll ignore the split attribute + OutAllSplitAttributeValues.Empty(); + OutSplitAttributeName = FString(); + } return bHasSplitAttribute; } @@ -2967,30 +3204,59 @@ FHoudiniInstanceTranslator::HasHISMAttribute(const HAPI_NodeId& GeoId, const HAP bool bHISM = false; HAPI_AttributeInfo AttriInfo; FHoudiniApi::AttributeInfo_Init(&AttriInfo); - TArray IntData; + + TArray IntData; + IntData.Empty(); + + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + GeoId, PartId, HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM, + AttriInfo, IntData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + return false; + } + + if (!AttriInfo.exists || IntData.Num() <= 0) + return false; + + return IntData[0] != 0; +} + +bool +FHoudiniInstanceTranslator::HasForceInstancerAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId) +{ + bool bHISM = false; + HAPI_AttributeInfo AttriInfo; + FHoudiniApi::AttributeInfo_Init(&AttriInfo); + + TArray IntData; IntData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM, AttriInfo, IntData, 1)) + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + GeoId, PartId, HAPI_UNREAL_ATTRIB_FORCE_INSTANCER, + AttriInfo, IntData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) { - if (IntData.Num() > 0) - bHISM = IntData[0] == 1; + return false; } - return bHISM; + if (!AttriInfo.exists || IntData.Num() <= 0) + return false; + + return IntData[0] != 0; } -void FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() +void +FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() { - NumInstancedTransformsPerObject.Empty(); - OriginalInstancedTransformsFlat.Empty(); + NumInstancedTransformsPerObject.Empty(OriginalInstancedTransforms.Num()); + // We expect to have one or more entries per object + OriginalInstancedTransformsFlat.Empty(OriginalInstancedTransforms.Num()); for (const TArray& Transforms : OriginalInstancedTransforms) { NumInstancedTransformsPerObject.Add(Transforms.Num()); OriginalInstancedTransformsFlat.Append(Transforms); } - OriginalInstanceObjectPackagePaths.Empty(); + OriginalInstanceObjectPackagePaths.Empty(OriginalInstancedObjects.Num()); for (const UObject* Obj : OriginalInstancedObjects) { if (IsValid(Obj)) @@ -3002,25 +3268,49 @@ void FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths OriginalInstanceObjectPackagePaths.Add(FString()); } } + + NumInstancedIndicesPerObject.Empty(OriginalInstancedIndices.Num()); + // We expect to have one or more entries per object + OriginalInstancedIndicesFlat.Empty(OriginalInstancedIndices.Num()); + for (const TArray& InstancedIndices : OriginalInstancedIndices) + { + NumInstancedIndicesPerObject.Add(InstancedIndices.Num()); + OriginalInstancedIndicesFlat.Append(InstancedIndices); + } + + NumPerInstanceCustomDataPerObject.Empty(PerInstanceCustomData.Num()); + // We expect to have one or more entries per object + PerInstanceCustomDataFlat.Empty(PerInstanceCustomData.Num()); + for (const TArray& PerInstanceCustomDataArray : PerInstanceCustomData) + { + NumPerInstanceCustomDataPerObject.Add(PerInstanceCustomDataArray.Num()); + PerInstanceCustomDataFlat.Append(PerInstanceCustomDataArray); + } } -void FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectArrays() +void +FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectArrays() { - const int32 NumObjects = NumInstancedTransformsPerObject.Num(); - OriginalInstancedTransforms.Init(TArray(), NumObjects); - int32 ObjectIndexOffset = 0; - for (int32 ObjIndex = 0; ObjIndex < NumObjects; ++ObjIndex) { - TArray& Transforms = OriginalInstancedTransforms[ObjIndex]; - const int32 NumInstances = NumInstancedTransformsPerObject[ObjIndex]; - for (int32 Index = 0; Index < NumInstances; ++Index) + const int32 NumObjects = NumInstancedTransformsPerObject.Num(); + OriginalInstancedTransforms.Init(TArray(), NumObjects); + int32 ObjectIndexOffset = 0; + for (int32 ObjIndex = 0; ObjIndex < NumObjects; ++ObjIndex) { - Transforms.Add(OriginalInstancedTransformsFlat[ObjectIndexOffset + Index]); + TArray& Transforms = OriginalInstancedTransforms[ObjIndex]; + const int32 NumInstances = NumInstancedTransformsPerObject[ObjIndex]; + Transforms.Reserve(NumInstances); + for (int32 Index = 0; Index < NumInstances; ++Index) + { + Transforms.Add(OriginalInstancedTransformsFlat[ObjectIndexOffset + Index]); + } + ObjectIndexOffset += NumInstances; } - ObjectIndexOffset += NumInstances; + NumInstancedTransformsPerObject.Empty(); + OriginalInstancedTransformsFlat.Empty(); } - OriginalInstancedObjects.Empty(); + OriginalInstancedObjects.Empty(OriginalInstanceObjectPackagePaths.Num()); for (const FString& PackageFullPath : OriginalInstanceObjectPackagePaths) { FString PackagePath; @@ -3044,6 +3334,237 @@ void FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectA OriginalInstancedObjects.Add(nullptr); } } + OriginalInstanceObjectPackagePaths.Empty(); + + { + const int32 NumObjects = NumInstancedIndicesPerObject.Num(); + OriginalInstancedIndices.Init(TArray(), NumObjects); + int32 ObjectIndexOffset = 0; + for (int32 EntryIndex = 0; EntryIndex < NumObjects; ++EntryIndex) + { + TArray& InstancedIndices = OriginalInstancedIndices[EntryIndex]; + const int32 NumInstancedIndices = NumInstancedIndicesPerObject[EntryIndex]; + InstancedIndices.Reserve(NumInstancedIndices); + for (int32 Index = 0; Index < NumInstancedIndices; ++Index) + { + InstancedIndices.Add(OriginalInstancedIndicesFlat[ObjectIndexOffset + Index]); + } + ObjectIndexOffset += NumInstancedIndices; + } + NumInstancedIndicesPerObject.Empty(); + OriginalInstancedIndicesFlat.Empty(); + } + + { + const int32 NumObjects = NumPerInstanceCustomDataPerObject.Num(); + PerInstanceCustomData.Init(TArray(), NumObjects); + int32 ObjectIndexOffset = 0; + for (int32 EntryIndex = 0; EntryIndex < NumObjects; ++EntryIndex) + { + TArray& PerInstanceCustomDataArray = PerInstanceCustomData[EntryIndex]; + const int32 NumPerInstanceCustomData = NumPerInstanceCustomDataPerObject[EntryIndex]; + PerInstanceCustomDataArray.Reserve(NumPerInstanceCustomData); + for (int32 Index = 0; Index < NumPerInstanceCustomData; ++Index) + { + PerInstanceCustomDataArray.Add(PerInstanceCustomDataFlat[ObjectIndexOffset + Index]); + } + ObjectIndexOffset += NumPerInstanceCustomData; + } + NumPerInstanceCustomDataPerObject.Empty(); + PerInstanceCustomDataFlat.Empty(); + } +} + +bool +FHoudiniInstanceTranslator::GetPerInstanceCustomData( + const int32& InGeoNodeId, + const int32& InPartId, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) +{ + // Initialize sizes to zero + OutInstancedOutputPartData.PerInstanceCustomData.SetNum(0); + + // First look for the number of custom floats + // If we dont have the attribute, or it is set to zero, we dont have PerInstanceCustomData + // HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS "unreal_num_custom_floats" + HAPI_AttributeInfo AttribInfoNumCustomFloats; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNumCustomFloats); + + TArray CustomFloatsArray; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + InGeoNodeId, InPartId, + HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS, + AttribInfoNumCustomFloats, + CustomFloatsArray)) + { + return false; + } + + if (CustomFloatsArray.Num() <= 0) + return false; + + int32 NumCustomFloats = 0; + for (int32 CustomFloatCount : CustomFloatsArray) + { + NumCustomFloats = FMath::Max(NumCustomFloats, CustomFloatCount); + } + + if (NumCustomFloats <= 0) + return false; + + // We do have custom float, now read the per instance custom data + // They are stored in attributes that uses the "unreal_per_instance_custom" prefix + // ie, unreal_per_instance_custom0, unreal_per_instance_custom1 etc... + // We do not supprot tuples/arrays attributes for now. + TArray> AllCustomDataAttributeValues; + AllCustomDataAttributeValues.SetNum(NumCustomFloats); + + // Read the custom data attributes + int32 NumInstance = 0; + for (int32 nIdx = 0; nIdx < NumCustomFloats; nIdx++) + { + // Build the custom data attribute + FString CurrentAttr = TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_CUSTOM_DATA_PREFIX) + FString::FromInt(nIdx); + + // TODO? Tuple values Array attributes? + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + + // Retrieve the custom data values + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + InGeoNodeId, InPartId, + TCHAR_TO_ANSI(*CurrentAttr), + AttribInfo, + AllCustomDataAttributeValues[nIdx], + 1)) + { + // Skip, we'll fill the values with zeros later on + continue; + } + + if (NumInstance < AllCustomDataAttributeValues[nIdx].Num()) + NumInstance = AllCustomDataAttributeValues[nIdx].Num(); + + if (NumInstance != AllCustomDataAttributeValues[nIdx].Num()) + { + HOUDINI_LOG_ERROR(TEXT("Instancer: Invalid number of Per-Instance Custom data attributes, ignoring...")); + return false; + } + } + + // Check sizes + if (AllCustomDataAttributeValues.Num() != NumCustomFloats) + { + HOUDINI_LOG_ERROR(TEXT("Instancer: Number of Per-Instance Custom data attributes don't match the number of custom floats, ignoring...")); + return false; + } + + OutInstancedOutputPartData.PerInstanceCustomData.SetNum(OutInstancedOutputPartData.OriginalInstancedObjects.Num()); + + for (int32 ObjIdx = 0; ObjIdx < OutInstancedOutputPartData.OriginalInstancedObjects.Num(); ++ObjIdx) + { + OutInstancedOutputPartData.PerInstanceCustomData[ObjIdx].Reset(); + } + + for(int32 ObjIdx = 0; ObjIdx < OutInstancedOutputPartData.OriginalInstancedObjects.Num(); ++ObjIdx) + { + const TArray& InstanceIndices = OutInstancedOutputPartData.OriginalInstancedIndices[ObjIdx]; + + if (InstanceIndices.Num() == 0) + { + continue; + } + + // Perform some validation + int32 NumCustomFloatsForInstance = CustomFloatsArray[InstanceIndices[0]]; + for (int32 InstIdx : InstanceIndices) + { + if (CustomFloatsArray[InstIdx] != NumCustomFloatsForInstance) + { + NumCustomFloatsForInstance = -1; + break; + } + } + + if (NumCustomFloatsForInstance == -1) + { + continue; + } + + // Now that we have read all the custom data values, we need to "interlace" them + // in the final per-instance custom data array, fill missing values with zeroes + TArray& PerInstanceCustomData = OutInstancedOutputPartData.PerInstanceCustomData[ObjIdx]; + PerInstanceCustomData.Reserve(InstanceIndices.Num() * NumCustomFloatsForInstance); + + if(NumCustomFloatsForInstance == 0) + { + continue; + } + + for (int32 InstIdx : InstanceIndices) + { + for (int32 nCustomIdx = 0; nCustomIdx < NumCustomFloatsForInstance; ++nCustomIdx) + { + float CustomData = (InstIdx < AllCustomDataAttributeValues[nCustomIdx].Num() ? AllCustomDataAttributeValues[nCustomIdx][InstIdx] : 0.0f); + PerInstanceCustomData.Add(CustomData); + } + } + } + + return true; +} + + +bool +FHoudiniInstanceTranslator::UpdateChangedPerInstanceCustomData( + const TArray& InPerInstanceCustomData, + USceneComponent* InComponentToUpdate) +{ + // Checks + UInstancedStaticMeshComponent* ISMC = Cast(InComponentToUpdate); + if (!IsValid(ISMC)) + return false; + + // No Custom data to add/remove + if (ISMC->NumCustomDataFloats == 0 && InPerInstanceCustomData.Num() == 0) + return false; + + // We can copy the per instance custom data if we have any + // TODO: Properly extract only needed values! + int32 InstanceCount = ISMC->GetInstanceCount(); + int32 NumCustomFloats = InPerInstanceCustomData.Num() / InstanceCount; + + if (NumCustomFloats * InstanceCount != InPerInstanceCustomData.Num()) + { + ISMC->NumCustomDataFloats = 0; + ISMC->PerInstanceSMCustomData.Reset(); + return false; + } + + ISMC->NumCustomDataFloats = NumCustomFloats; + + // Clear out and reinit to 0 the PerInstanceCustomData array + ISMC->PerInstanceSMCustomData.SetNumZeroed(InstanceCount * NumCustomFloats); + + // Behaviour copied From UInstancedStaticMeshComponent::SetCustomData() + // except we modify all the instance/custom values at once + ISMC->Modify(); + + // MemCopy + const int32 NumToCopy = FMath::Min(ISMC->PerInstanceSMCustomData.Num(), InPerInstanceCustomData.Num()); + if (NumToCopy > 0) + { + FMemory::Memcpy(&ISMC->PerInstanceSMCustomData[0], InPerInstanceCustomData.GetData(), NumToCopy * InPerInstanceCustomData.GetTypeSize()); + } + + // Force recreation of the render data when proxy is created + //NewISMC->InstanceUpdateCmdBuffer.Edit(); + // Cant call the edit function above because the function is defined in a different cpp file than the .h it is declared in... + ISMC->InstanceUpdateCmdBuffer.NumEdits++; + + ISMC->MarkRenderStateDirty(); + + return true; } #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h index 7ec309048..ad5a132c6 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,6 +40,7 @@ class UStaticMesh; class UFoliageType; class UHoudiniStaticMesh; class UHoudiniInstancedActorComponent; +struct FHoudiniPackageParams; USTRUCT() struct HOUDINIENGINE_API FHoudiniInstancedOutputPerSplitAttributes @@ -59,6 +60,10 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPerSplitAttributes // bake outliner folder attribute value UPROPERTY() FString BakeOutlinerFolder; + + // unreal_bake_folder attribute value + UPROPERTY() + FString BakeFolder; }; USTRUCT() @@ -69,22 +74,51 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData GENERATED_BODY() UPROPERTY() - bool bForceHISM; + bool bForceHISM = false; + + // Should we create an instancer even for single instances? + UPROPERTY() + bool bForceInstancer = false; UPROPERTY() TArray OriginalInstancedObjects; + // Object paths of OriginalInstancedObjects. Used by message passing system + // when sending messages from the async importer to the PDG manager. UObject*/references + // are not directly supported by the messaging system. See BuildFlatInstancedTransformsAndObjectPaths(). UPROPERTY() TArray OriginalInstanceObjectPackagePaths; TArray> OriginalInstancedTransforms; + TArray> OriginalInstancedIndices; + + // Number of entries in OriginalInstancedTransforms. Populated when building + // OriginalInstancedTransformsFlat in BuildFlatInstancedTransformsAndObjectPaths() and used when rebuilding + // OriginalInstancedTransforms from OriginalInstancedTransformsFlat in BuildOriginalInstancedTransformsAndObjectArrays(). UPROPERTY() TArray NumInstancedTransformsPerObject; + // Flattened version of OriginalInstancedTransforms. Used by message passing system + // when sending messages from the async importer to the PDG manager. Nested arrays + // are not supported by UPROPERTIES and thus not by the messaging system. + // See BuildFlatInstancedTransformsAndObjectPaths(). UPROPERTY() TArray OriginalInstancedTransformsFlat; + // Number of entries in OriginalInstancedIndices. Populated when building + // OriginalInstancedIndicesFlat in BuildFlatInstancedTransformsAndObjectPaths() and used when rebuilding + // OriginalInstancedIndices from OriginalInstancedIndicesFlat in BuildOriginalInstancedTransformsAndObjectArrays(). + UPROPERTY() + TArray NumInstancedIndicesPerObject; + + // Flattened version of OriginalInstancedIndices. Used by message passing system + // when sending messages from the async importer to the PDG manager. Nested arrays + // are not supported by UPROPERTIES and thus not by the messaging system. See + // BuildFlatInstancedTransformsAndObjectPaths(). + UPROPERTY() + TArray OriginalInstancedIndicesFlat; + UPROPERTY() FString SplitAttributeName; @@ -92,10 +126,10 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData TArray SplitAttributeValues; UPROPERTY() - bool bSplitMeshInstancer; + bool bSplitMeshInstancer = false; UPROPERTY() - bool bIsFoliageInstancer; + bool bIsFoliageInstancer = false; UPROPERTY() TArray AllPropertyAttributes; @@ -108,6 +142,10 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData UPROPERTY() TArray AllBakeActorNames; + // All unreal_bake_folder attributes (prim attr is checked first then detail) + UPROPERTY() + TArray AllBakeFolders; + // All bake outliner folder attributes from the first attribute owner we could find UPROPERTY() TArray AllBakeOutlinerFolders; @@ -120,12 +158,36 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData UPROPERTY() TArray OutputNames; + UPROPERTY() + TArray BakeNames; + UPROPERTY() TArray TileValues; UPROPERTY() TArray MaterialAttributes; + // Specifies that the materials in MaterialAttributes are to be created as an instance + UPROPERTY() + bool bMaterialOverrideNeedsCreateInstance; + + // Custom float array per original instanced object + // Size is NumCustomFloat * NumberOfInstances + TArray> PerInstanceCustomData; + + // Number of entries in PerInstanceCustomData. Populated when building + // PerInstanceCustomDataFlat in BuildFlatInstancedTransformsAndObjectPaths() and used when rebuilding + // PerInstanceCustomData from PerInstanceCustomDataFlat in BuildOriginalInstancedTransformsAndObjectArrays(). + UPROPERTY() + TArray NumPerInstanceCustomDataPerObject; + + // Flattened version of OriginalInstancedTransforms. Used by message passing system + // when sending messages from the async importer to the PDG manager. Nested arrays + // are not supported by UPROPERTIES and thus not by the messaging system. + // See BuildFlatInstancedTransformsAndObjectPaths(). + UPROPERTY() + TArray PerInstanceCustomDataFlat; + void BuildFlatInstancedTransformsAndObjectPaths(); void BuildOriginalInstancedTransformsAndObjectArrays(); @@ -144,13 +206,15 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator UHoudiniOutput* InOutput, const TArray& InAllOutputs, UObject* InOuterComponent, - const TMap* InPreBuiltInstancedOutputPartData=nullptr); + const FHoudiniPackageParams& InPackageParms, + const TMap* InPreBuiltInstancedOutputPartData = nullptr); static bool GetInstancerObjectsAndTransforms( const FHoudiniGeoPartObject& InHGPO, const TArray& InAllOutputs, TArray& OutInstancedObjects, TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, FString& OutSplitAttributeName, TArray& OutSplitAttributeValues, TMap& OutPerSplitAttributes); @@ -159,6 +223,7 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancedHGPO, TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, FString& OutSplitAttributeName, TArray& OutSplitAttributeValue, TMap& OutPerSplitAttributes); @@ -167,6 +232,7 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancedObjects, TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, FString& OutSplitAttributeName, TArray& OutSplitAttributeValue, TMap& OutPerSplitAttributes); @@ -175,19 +241,22 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator const FHoudiniGeoPartObject& InHGPO, const TArray& InAllOutputs, TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms); + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices); static bool GetObjectInstancerHGPOsAndTransforms( const FHoudiniGeoPartObject& InHGPO, const TArray& InAllOutputs, TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms); + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices); // Updates the variations array using the instanced outputs static void UpdateInstanceVariationObjects( const FHoudiniOutputObjectIdentifier& InOutputIdentifier, const TArray& InOriginalObjects, const TArray>& InOriginalTransforms, + const TArray>& OriginalInstancedIndices, TMap& InstancedOutputs, TArray>& OutVariationsInstancedObjects, TArray>& OutVariationsInstancedTransforms, @@ -199,7 +268,8 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator FHoudiniInstancedOutput& InInstancedOutput, const FHoudiniOutputObjectIdentifier& OutputIdentifier, UHoudiniOutput* InParentOutput, - USceneComponent* InParentComponent); + USceneComponent* InParentComponent, + const FHoudiniPackageParams& InPackageParams); // Recomputes the variation assignements for a given instanced output static void UpdateVariationAssignements( @@ -223,8 +293,10 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator const bool& InIsSplitMeshInstancer, const bool& InIsFoliageInstancer, const TArray& InstancerMaterials, - const int32& InstancerObjectIdx = 0, - const bool& bForceHISM = false); + const TArray& OriginalInstancerObjectIndices, + const int32& InstancerObjectIdx = 0, + const bool& bForceHISM = false, + const bool& bForceInstancer = false); // Create or update an ISMC / HISMC static bool CreateOrUpdateInstancedStaticMeshComponent( @@ -235,12 +307,14 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator USceneComponent* ParentComponent, USceneComponent*& CreatedInstancedComponent, UMaterialInterface * InstancerMaterial = nullptr, - const bool& bForceHISM = false); + const bool& bForceHISM = false, + const int32& InstancerObjectIdx = 0); // Create or update an IAC static bool CreateOrUpdateInstancedActorComponent( UObject* InstancedObject, const TArray& InstancedObjectTransforms, + const TArray& OriginalInstancerObjectIndices, const TArray& AllPropertyAttributes, USceneComponent* ParentComponent, USceneComponent*& CreatedInstancedComponent); @@ -259,6 +333,7 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator static bool CreateOrUpdateStaticMeshComponent( UStaticMesh* InstancedStaticMesh, const TArray& InstancedObjectTransforms, + const int32& InOriginalIndex, const TArray& AllPropertyAttributes, const FHoudiniGeoPartObject& InstancerGeoPartObject, USceneComponent* ParentComponent, @@ -269,6 +344,7 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator static bool CreateOrUpdateHoudiniStaticMeshComponent( UHoudiniStaticMesh* InstancedProxyStaticMesh, const TArray& InstancedObjectTransforms, + const int32& InOriginalIndex, const TArray& AllPropertyAttributes, const FHoudiniGeoPartObject& InstancerGeoPartObject, USceneComponent* ParentComponent, @@ -280,15 +356,17 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator UStaticMesh* InstancedStaticMesh, UFoliageType* InFoliageType, const TArray& InstancedObjectTransforms, + const int32& FirstOriginalIndex, const TArray& AllPropertyAttributes, const FHoudiniGeoPartObject& InstancerGeoPartObject, USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, + USceneComponent*& NewInstancedComponent, UMaterialInterface * InstancerMaterial /*=nullptr*/); // Helper fumction to properly remove/destroy a component static bool RemoveAndDestroyComponent( - UObject* InComponent); + UObject* InComponent, + UObject* InFoliageObject); // Utility function // Fetches instance transforms and convert them to ue4 coordinates @@ -317,20 +395,29 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator static bool GetMaterialOverridesFromAttributes( const int32& InGeoNodeId, const int32& InPartId, - TArray& OutMaterialAttributes); + TArray& OutMaterialAttributes, + bool& OutMaterialOverrideNeedsCreateInstance); static bool GetInstancerMaterials( - const TArray& MaterialAttributes, + const TArray& MaterialAttribute, TArray& OutInstancerMaterials); + static bool GetInstancerMaterialInstances( + const TArray& MaterialAttribute, + const FHoudiniGeoPartObject& InHGPO, const FHoudiniPackageParams& InPackageParams, + TArray& OutInstancerMaterials); + static bool GetInstancerMaterials( const int32& InGeoNodeId, const int32& InPartId, + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, TArray& OutInstancerMaterials); static bool GetVariationMaterials( FHoudiniInstancedOutput* InInstancedOutput, const int32& InVariationIndex, + const TArray& InOriginalIndices, const TArray& InInstancerMaterials, TArray& OutVariationMaterials); @@ -344,6 +431,7 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator static void CleanupFoliageInstances( UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, + UObject* InInstancedObject, USceneComponent* InParentComponent); static FString GetInstancerTypeFromComponent( @@ -360,4 +448,19 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator // Get if force using HISM from attribute static bool HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId); + + // Return true if HAPI_UNREAL_ATTRIB_FORCE_INSTANCER is set to non-zero (this controls + // if an instancer is created even for single instances (static mesh vs instanced static mesh for example) + static bool HasForceInstancerAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId); + + // Checks for PerInstanceCustomData on the instancer part + static bool GetPerInstanceCustomData( + const int32& InGeoNodeId, + const int32& InPartId, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData); + + // Update PerInstanceCustom data on the given component if possible + static bool UpdateChangedPerInstanceCustomData( + const TArray& InPerInstanceCustomData, + USceneComponent* InComponentToUpdate); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp index 88113c562..7f86f4d76 100644 --- a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,8 +23,11 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + #include "HoudiniLandscapeTranslator.h" +#include "HoudiniMaterialTranslator.h" + #include "HoudiniAssetComponent.h" #include "HoudiniGeoPartObject.h" #include "HoudiniEngineString.h" @@ -52,32 +55,23 @@ #include "UObject/UnrealType.h" #include "GameFramework/WorldSettings.h" -#include "HAL/PlatformFilemanager.h" #include "Misc/Paths.h" -#include "Engine/LevelStreamingDynamic.h" #include "Modules/ModuleManager.h" #include "AssetToolsModule.h" #include "HoudiniEngineRuntimeUtils.h" -#include "LevelUtils.h" +#include "LandscapeDataAccess.h" #include "Factories/WorldFactory.h" #include "Misc/Guid.h" #include "Engine/LevelBounds.h" #include "HAL/IConsoleManager.h" #include "Engine/AssetManager.h" -#include "Engine/LevelStreamingAlwaysLoaded.h" -#include "LandscapeEditor/Private/LandscapeEdMode.h" -#include "Misc/AssetRegistryInterface.h" -#include "Misc/StringFormatArg.h" -#include "Engine/WorldComposition.h" +#include "Misc/ScopedSlowTask.h" #if WITH_EDITOR + #include "EditorLevelUtils.h" #include "LandscapeEditorModule.h" #include "LandscapeFileFormatInterface.h" - #include "EditorLevelUtils.h" - #include "WorldBrowserModule.h" - #include "EditorLevelUtils.h" - #include "Misc/WorldCompositionUtility.h" #endif static TAutoConsoleVariable CVarHoudiniEngineExportLandscapeTextures( @@ -90,6 +84,10 @@ static TAutoConsoleVariable CVarHoudiniEngineExportLandscapeTextures( typedef FHoudiniEngineUtils FHUtils; +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY(); + bool FHoudiniLandscapeTranslator::CreateLandscape( UHoudiniOutput* InOutput, @@ -101,21 +99,275 @@ FHoudiniLandscapeTranslator::CreateLandscape( UWorld* InWorld, // Persistent / root world for the landscape const TMap& LayerMinimums, const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + TSet& ClearedLayers, + TArray& OutCreatedPackages +) +{ + // Do the absolute minimum in order to determine which output mode we're dealing with (Temp or Editable Layers). + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("CreateLandscape!")); + + if (!IsValid(InOutput)) + return false; + + // Get the height map. + const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput, false, NAME_None); + if (!Heightfield) + return false; + + if (Heightfield->Type != EHoudiniPartType::Volume) + return false; + + const HAPI_NodeId GeoId = Heightfield->GeoId; + const HAPI_PartId PartId = Heightfield->PartId; + + // Check whether we're running in edit layer mode, or the usual temp mode + + TArray IntData; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + // --------------------------------------------- + // Attribute: unreal_landscape_output_mode + // --------------------------------------------- + IntData.Empty(); + int32 LandscapeOutputMode = 0; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_OUTPUT_MODE, + AttributeInfo, IntData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (IntData.Num() > 0) + { + LandscapeOutputMode = IntData[0]; + } + } + + switch (LandscapeOutputMode) + { + case HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_MODIFY_LAYER: + { + return OutputLandscape_ModifyLayer(InOutput, + CreatedUntrackedOutputs, + InputLandscapesToUpdate, + InAllInputLandscapes, + SharedLandscapeActorParent, + DefaultLandscapeActorPrefix, + InWorld, + LayerMinimums, + LayerMaximums, + LandscapeExtent, + LandscapeTileSizeInfo, + LandscapeReferenceLocation, + InPackageParams, + false, + NAME_None, + ClearedLayers, + OutCreatedPackages); + } + break; + case HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_GENERATE: + default: + { + return OutputLandscape_Generate(InOutput, + CreatedUntrackedOutputs, + InputLandscapesToUpdate, + InAllInputLandscapes, + SharedLandscapeActorParent, + DefaultLandscapeActorPrefix, + InWorld, + LayerMinimums, + LayerMaximums, + LandscapeExtent, + LandscapeTileSizeInfo, + LandscapeReferenceLocation, + ClearedLayers, + InPackageParams, + OutCreatedPackages + ); + } + break; + } +} + +bool +FHoudiniLandscapeTranslator::OutputLandscape_Generate( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedOutputs, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* InWorld, // Persistent / root world for the landscape + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + TSet& ClearedLayers, FHoudiniPackageParams InPackageParams, TArray& OutCreatedPackages ) { + TArray EditLayerNames; + const bool bHasEditLayers = GetEditLayersFromOutput(InOutput, EditLayerNames); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::Output_Landscape_Generate] Generating landscape tile. Has edit layers?: %d"), bHasEditLayers); + + TArray AllLayerNames; + for (const FString& LayerName : EditLayerNames) + { + AllLayerNames.Add(FName(LayerName)); + } + + if (!bHasEditLayers) + { + // Add a dummy edit layer to simply get us into the following for-loop. + EditLayerNames.Add(FString()); + } + + FName AfterLayerName = NAME_None; + + TSet ActiveLandscapes; + TArray StaleOutputObjects; + + InOutput->GetOutputObjects().GenerateKeyArray(StaleOutputObjects); + + // Collect current edit layers and their respective landscapes so that we can detect and remove stale edit layers. + TMap StaleEditLayers; + { + TMap& OutputObjects = InOutput->GetOutputObjects(); + for (auto& Entry : OutputObjects) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(Entry.Value.OutputObject); + if (LandscapePtr) + { + ALandscapeProxy* Proxy = LandscapePtr->GetRawPtr(); + if (Proxy) + { + ALandscape* Landscape = Proxy->GetLandscapeActor(); + if (Landscape) + { + StaleEditLayers.Add(LandscapePtr->EditLayerName, Landscape); + } + } + } + } + } + + for (const FString& LayerName : EditLayerNames) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::Output_Landscape_Generate] Generating tile for heightfield: %s"), *LayerName); + // If edit layers are enabled for this volume, create each layer for this landscape tile + FName LayerFName(LayerName); + StaleEditLayers.Remove(LayerFName); + + OutputLandscape_GenerateTile(InOutput, + StaleOutputObjects, + CreatedUntrackedOutputs, + InputLandscapesToUpdate, + InAllInputLandscapes, + SharedLandscapeActorParent, + DefaultLandscapeActorPrefix, + InWorld, + LayerMinimums, + LayerMaximums, + LandscapeExtent, + LandscapeTileSizeInfo, + LandscapeReferenceLocation, + InPackageParams, + bHasEditLayers, + LayerName, + AfterLayerName, + AllLayerNames, + ClearedLayers, + OutCreatedPackages, + ActiveLandscapes); + AfterLayerName = LayerFName; + } + + // Clean up stale output objects + TMap& OutputObjects = InOutput->GetOutputObjects(); + for(FHoudiniOutputObjectIdentifier ObjectId : StaleOutputObjects) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::OutputLandscape_Generate] Processing stale output: %s"), *ObjectId.PartName); + FHoudiniOutputObject* OutputObject = OutputObjects.Find(ObjectId); + if (!OutputObject) + continue; + + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject->OutputObject); + if (LandscapePtr) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::OutputLandscape_Generate] LandscapePtr: %s"), *LandscapePtr->GetFullName()); + // Cleanup stale landscape outputs + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + if (IsValid(LandscapeProxy)) + { + if (!ActiveLandscapes.Contains(LandscapeProxy)) + { + // This landscape actor is no longer in use. Trash it. + LandscapeProxy->Destroy(); + } + } + LandscapePtr->SetSoftPtr(nullptr); + } + OutputObjects.Remove(ObjectId); + } + + // Clean up stale layers + for (auto& Entry : StaleEditLayers) + { + ALandscape* Landscape = Entry.Value; + if (IsValid(Landscape) && Landscape->HasLayersContent()) + { + const int32 LayerIndex = Landscape->GetLayerIndex(Entry.Key); + Landscape->DeleteLayer(LayerIndex); + } + } + + return true; +} + +bool +FHoudiniLandscapeTranslator::OutputLandscape_GenerateTile( + UHoudiniOutput* InOutput, + TArray &StaleOutputObjects, + TArray>& CreatedUntrackedOutputs, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* InWorld, // Persistent / root world for the landscape + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + bool bHasEditLayers, + const FString& InEditLayerName, + const FName& InAfterLayerName, + const TArray& AllLayerNames, + TSet& ClearedLayers, + TArray& OutCreatedPackages, + TSet& OutActiveLandscapes +) +{ + FName InEditLayerFName = FName(InEditLayerName); check(LayerMinimums.Contains(TEXT("height"))); check(LayerMaximums.Contains(TEXT("height"))); float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; // Get the height map. - const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); + const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput, bHasEditLayers, InEditLayerFName); if (!Heightfield) return false; @@ -130,6 +382,10 @@ FHoudiniLandscapeTranslator::CreateLandscape( HeightfieldIdentifier.PartName = Heightfield->PartName; FString NodeNameSuffix = GetActorNameSuffix(InPackageParams.PackageMode); + bool bAddLandscapeNameSuffix = true; + bool bAddLandscapeTileNameSuffix = true; + + const UHoudiniAssetComponent* HoudiniAssetComponent = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); TArray IntData; TArray StrData; @@ -139,9 +395,11 @@ FHoudiniLandscapeTranslator::CreateLandscape( TMap OutputAttributes; TMap OutputTokens; FHoudiniAttributeResolver Resolver; - InPackageParams.UpdateTokensFromParams(InWorld, OutputTokens); + InPackageParams.UpdateTokensFromParams(InWorld, HoudiniAssetComponent, OutputTokens); bool bHasTile = Heightfield->VolumeTileIndex >= 0; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Volume Tile Index: %d"), Heightfield->VolumeTileIndex); // --------------------------------------------- // Attribute: unreal_landscape_tile_actor_type, unreal_landscape_streaming_proxy (v1) @@ -153,16 +411,18 @@ FHoudiniLandscapeTranslator::CreateLandscape( LandscapeActorType TileActorType = LandscapeActorType::LandscapeActor; IntData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, AttributeInfo, IntData, 1)) + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, + AttributeInfo, IntData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) { if (IntData.Num() > 0) { TileActorType = static_cast(IntData[0]); } } - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY, AttributeInfo, IntData, 1)) + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY, + AttributeInfo, IntData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) { if (IntData.Num() > 0 && IntData[0] != 0) TileActorType = LandscapeActorType::LandscapeStreamingProxy; @@ -176,8 +436,9 @@ FHoudiniLandscapeTranslator::CreateLandscape( // Retrieve the name of the main Landscape actor to look for FString SharedLandscapeActorName = DefaultLandscapeActorPrefix + "SharedLandscape"; // If this is an empty string, don't affirm a root landscape actor? StrData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, AttributeInfo, StrData, 1)) + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, + AttributeInfo, StrData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) { if (StrData.Num() > 0 && !StrData[0].IsEmpty()) SharedLandscapeActorName = StrData[0]; @@ -187,11 +448,11 @@ FHoudiniLandscapeTranslator::CreateLandscape( // --------------------------------------------- // Attribute: unreal_level_path - // --------------------------------------------- + // --------------------------------------------- // FString LevelPath = bHasTile ? "{world}/Landscape/Tile{tile}" : "{world}/Landscape"; FString LevelPath; TArray LevelPaths; - if (FHoudiniEngineUtils::GetLevelPathAttribute(GeoId, PartId, LevelPaths)) + if (FHoudiniEngineUtils::GetLevelPathAttribute(GeoId, PartId, LevelPaths, HAPI_ATTROWNER_INVALID, 0, 1)) { if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) LevelPath = LevelPaths[0]; @@ -204,13 +465,77 @@ FHoudiniLandscapeTranslator::CreateLandscape( // --------------------------------------------- FString LandscapeTileActorName = bHasTile ? "LandscapeTile{tile}" : "Landscape"; TArray AllOutputNames; - if (!FHoudiniEngineUtils::GetOutputNameAttribute(GeoId, PartId, AllOutputNames)) + if (FHoudiniEngineUtils::GetOutputNameAttribute(GeoId, PartId, AllOutputNames, 0, 1)) { if (AllOutputNames.Num() > 0 && !AllOutputNames[0].IsEmpty()) + { LandscapeTileActorName = AllOutputNames[0]; + } } OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), LandscapeTileActorName); + // --------------------------------------------- + // Attribute: unreal_bake_name + // --------------------------------------------- + FString LandscapeTileBakeName = bHasTile ? "LandscapeTile{tile}" : "Landscape"; + TArray AllBakeNames; + if (FHoudiniEngineUtils::GetBakeNameAttribute(GeoId, PartId, AllBakeNames, 0, 1)) + { + if (AllBakeNames.Num() > 0 && !AllBakeNames[0].IsEmpty()) + { + LandscapeTileBakeName = AllBakeNames[0]; + } + } + OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_BAKE_NAME), LandscapeTileBakeName); + + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_after + // --------------------------------------------- + StrData.Empty(); + FName AfterLayerName = InAfterLayerName; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_AFTER, AttributeInfo, StrData, 1)) + { + if (StrData.Num() > 0) + { + AfterLayerName = FName(StrData[0]); + } + } + + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_type + // --------------------------------------------- + int32 EditLayerType = HAPI_UNREAL_LANDSCAPE_EDITLAYER_TYPE_BASE; + IntData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TYPE, AttributeInfo, IntData, 1)) + { + if (IntData.Num() > 0) + { + EditLayerType = IntData[0]; + } + } + + + // By default, if we're in EditLayer mode, always 'check' this option + // otherwise the paint layers will be additively blended which is + // typically not expected. + // The user should be able to override this behaviour by setting + // the HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS attribute on the paint layer heightfields in Houdini. + bool bLayerNoWeightBlend = bHasEditLayers ? true : false; + + // --------------------------------------------- + // Attribute: unreal_bake_folder + // --------------------------------------------- + TArray AllBakeFolders; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(GeoId, AllBakeFolders, PartId, 0, 1)) + { + FString BakeFolder; + if (AllBakeFolders.Num() > 0 && !AllBakeFolders[0].IsEmpty()) + BakeFolder = AllBakeFolders[0]; + OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_BAKE_FOLDER), BakeFolder); + } + // Streaming proxy actors/tiles requires a "main" landscape actor // that contains the shared landscape state. bool bRequiresSharedLandscape = false; @@ -309,7 +634,7 @@ FHoudiniLandscapeTranslator::CreateLandscape( UMaterialInterface* LandscapeMaterial = nullptr; UMaterialInterface* LandscapeHoleMaterial = nullptr; UPhysicalMaterial* LandscapePhysicalMaterial = nullptr; - FHoudiniLandscapeTranslator::GetLandscapeMaterials(*Heightfield, LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial); + FHoudiniLandscapeTranslator::GetLandscapeMaterials(*Heightfield, InPackageParams, LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial); // Extract the float data from the Heightfield. const FHoudiniVolumeInfo &VolumeInfo = Heightfield->VolumeInfo; @@ -324,20 +649,33 @@ FHoudiniLandscapeTranslator::CreateLandscape( FloatMax = fGlobalMax; // Get the Unreal landscape size - int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; - int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; - int32 UnrealTileSizeX = -1; - int32 UnrealTileSizeY = -1; - int32 NumSectionPerLandscapeComponent = -1; - int32 NumQuadsPerLandscapeSection = -1; - - if (!FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( - HoudiniHeightfieldXSize, HoudiniHeightfieldYSize, - UnrealTileSizeX, UnrealTileSizeY, - NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection)) + const int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; + const int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; + + if (!LandscapeTileSizeInfo.bIsCached) { - return false; + // Calculate a landscape size info from this heightfield to be + // used by subsequent tiles on the same landscape + if (FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( + HoudiniHeightfieldXSize, + HoudiniHeightfieldYSize, + LandscapeTileSizeInfo.UnrealSizeX, + LandscapeTileSizeInfo.UnrealSizeY, + LandscapeTileSizeInfo.NumSectionsPerComponent, + LandscapeTileSizeInfo.NumQuadsPerSection)) + { + LandscapeTileSizeInfo.bIsCached = true; + } + else + { + return false; + } } + + const int32 UnrealTileSizeX = LandscapeTileSizeInfo.UnrealSizeX; + const int32 UnrealTileSizeY = LandscapeTileSizeInfo.UnrealSizeY; + const int32 NumSectionPerLandscapeComponent = LandscapeTileSizeInfo.NumSectionsPerComponent; + const int32 NumQuadsPerLandscapeSection = LandscapeTileSizeInfo.NumQuadsPerSection; // ---------------------------------------------------- // Export of layer textures @@ -360,13 +698,18 @@ FHoudiniLandscapeTranslator::CreateLandscape( // Look for all the layers/masks corresponding to the current heightfield. TArray< const FHoudiniGeoPartObject* > FoundLayers; - FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, FoundLayers); + FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, bHasEditLayers, InEditLayerFName, FoundLayers); // Get the updated layers. TArray LayerInfos; + + // If we are operating in EditLayer mode, then any layers we create or manage should + // have their bNoWeightBlend set to true otherwise all the paint layers will act as additive + // which, most of the time, is not what we expect. + const bool bDefaultNoWeightBlend = bHasEditLayers ? true : false; - if (!CreateOrUpdateLandscapeLayers(FoundLayers, *Heightfield, UnrealTileSizeX, UnrealTileSizeY, - LayerMinimums, LayerMaximums, LayerInfos, false, + if (!CreateOrUpdateLandscapeLayerData(FoundLayers, *Heightfield, UnrealTileSizeX, UnrealTileSizeY, + LayerMinimums, LayerMaximums, LayerInfos, false, bDefaultNoWeightBlend, TilePackageParams, LayerPackageParams, OutCreatedPackages)) @@ -379,7 +722,11 @@ FHoudiniLandscapeTranslator::CreateLandscape( FloatValues, VolumeInfo, UnrealTileSizeX, UnrealTileSizeY, FloatMin, FloatMax, - IntHeightData, TileTransform)) + IntHeightData, TileTransform, + true, + false, + 100.f, + EditLayerType == HAPI_UNREAL_LANDSCAPE_EDITLAYER_TYPE_ADDITIVE)) return false; // ---------------------------------------------------- @@ -402,12 +749,33 @@ FHoudiniLandscapeTranslator::CreateLandscape( // ---------------------------------------------------- // Calculate Tile location and landscape offset // ---------------------------------------------------- - FTransform LandscapeTransform, NewTileTransform; + FTransform LandscapeTransform; FIntPoint TileLoc; // Calculate the tile location (in quad space) as well as the corrected TileTransform which will compensate - // for any landscape shifts due to section base aligment offsets. - CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeTransform, NewTileTransform, TileLoc); + // for any landscape shifts due to section base alignment offsets. + CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeReferenceLocation, LandscapeTransform, TileLoc); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Tile Transform: %s"), *TileTransform.ToString()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Landscape Transform: %s"), *LandscapeTransform.ToString()); + + // Determine the level of the owning HAC (if we have a valid owning HAC), and use that as the Tile and shared + // landscape's world and level + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + if (IsValid(HAC)) + { + TileWorld = HAC->GetWorld(); + TileLevel = HAC->GetComponentLevel(); + } + else + { + // If we don't have a valid HAC fallback to the persistent level of InWorld + TileWorld = InWorld; + TileLevel = InWorld->PersistentLevel; + } + + check(TileWorld); + check(TileLevel); // ---------------------------------------------------- // Find or create *shared* landscape @@ -421,42 +789,62 @@ FHoudiniLandscapeTranslator::CreateLandscape( // Streaming proxy tiles always require a "shared landscape" that contains the // various landscape properties to be shared amongst all the tiles. AActor* FoundActor = nullptr; - SharedLandscapeActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, SharedLandscapeActorName, FoundActor); + SharedLandscapeActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, SharedLandscapeActorName, FoundActor); bool bIsValidSharedLandscape = IsValid(SharedLandscapeActor); if (bIsValidSharedLandscape) { - // We have a possible valid shared landscape. Check whether it is compatible with the Houdini volume. + // We have a target landscape. Check whether it is compatible with the Houdini volume. + ULandscapeInfo* LandscapeInfo = SharedLandscapeActor->GetLandscapeInfo(); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Found existing landscape: %s"), *(SharedLandscapeActor->GetPathName())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Found existing landscape with num proxies: %d"), LandscapeInfo->Proxies.Num()); + + if (!LandscapeExtent.bIsCached) + { + LandscapeInfo->FixupProxiesTransform(); + // Cache the landscape extents. Note that GetLandscapeExtent() will only take into account the currently loaded landscape tiles. + PopulateLandscapeExtents(LandscapeExtent, LandscapeInfo); + } + bool bIsCompatible = IsLandscapeInfoCompatible( - SharedLandscapeActor->GetLandscapeInfo(), + LandscapeInfo, NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Checking landscape compatibility ...")); bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(SharedLandscapeActor, LandscapeActorType::LandscapeActor); if (!bIsCompatible) { - // Current landscape actor is not compatible. Destroy it. - SharedLandscapeActor->Destroy(); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Shared landscape is incompatible. Cannot reuse.")); + // We can't resize the landscape in-place. We have to create a new one. + DestroyLandscape(SharedLandscapeActor); SharedLandscapeActor = nullptr; bIsValidSharedLandscape = false; } + else + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Existing shared landscape is compatible.")); + } } if (!bIsValidSharedLandscape) { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Create new shared landscape...")); // Create and configure the main landscape actor. // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos - SharedLandscapeActor = InWorld->SpawnActor(); + FActorSpawnParameters SpawnParameters; + if (IsValid(TileLevel)) + SpawnParameters.OverrideLevel = TileLevel; + SharedLandscapeActor = TileWorld->SpawnActor(SpawnParameters); if (SharedLandscapeActor) { CreatedUntrackedOutputs.Add( SharedLandscapeActor ); - // NOTE that share landscape is always located at the origin, but not the tile actors. The + // NOTE that shared landscape is always located at the origin, but not the tile actors. The // tiles are properly transformed. - SharedLandscapeActor->SetActorTransform(LandscapeTransform); - // If we working with landscape tiles, this actor will become the "Main landscape" actor but - // doesn't actually contain any content. Landscape Streaming Proxies will contain the layer content. - SharedLandscapeActor->bCanHaveLayersContent = false; + + SharedLandscapeActor->bCanHaveLayersContent = bHasEditLayers; SharedLandscapeActor->ComponentSizeQuads = NumQuadsPerLandscapeSection*NumSectionPerLandscapeComponent; SharedLandscapeActor->NumSubsections = NumSectionPerLandscapeComponent; SharedLandscapeActor->SubsectionSizeQuads = NumQuadsPerLandscapeSection; @@ -468,30 +856,37 @@ FHoudiniLandscapeTranslator::CreateLandscape( } SharedLandscapeActor->CreateLandscapeInfo(); bCreatedSharedLandscape = true; - + + // NOTE: It is important to set Landscape materials BEFORE blitting layer data. For example, setting + // data in the visibility layer (on tiles) will have no effect until Landscape materials have been applied / processed. + SharedLandscapeActor->LandscapeMaterial = LandscapeMaterial; + SharedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + // Ensure the landscape actor name and label matches `LandscapeActorName`. FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, SharedLandscapeActorName); + SharedLandscapeActor->MarkPackageDirty(); } else { - HOUDINI_LOG_ERROR(TEXT("Could not create main landscape actor (%s) in world (%s)"), *(SharedLandscapeActorName), *(InWorld->GetPathName()) ); + HOUDINI_LOG_ERROR(TEXT("Could not create main landscape actor (%s) in world (%s)"), *(SharedLandscapeActorName), *(TileWorld->GetPathName()) ); return false; } } - else - { - // HOUDINI_LOG_DISPLAY(TEXT("[CreateLandscape] Reusing existing shared landscape...")); - } + // else -- Reusing shared landscape } if (SharedLandscapeActor) { + SharedLandscapeActor->bCanHaveLayersContent = bHasEditLayers; + // Ensure the existing landscape actor transform is correct. - SharedLandscapeActor->SetActorTransform(LandscapeTransform); + SharedLandscapeActor->SetActorRelativeTransform(LandscapeTransform); + + bSharedLandscapeMaterialChanged = (SharedLandscapeActor->LandscapeMaterial != LandscapeMaterial); + bSharedLandscapeHoleMaterialChanged = SharedLandscapeActor->LandscapeHoleMaterial != LandscapeHoleMaterial; - bSharedLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeMaterial() != LandscapeMaterial) : false; - bSharedLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) { DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); @@ -513,7 +908,13 @@ FHoudiniLandscapeTranslator::CreateLandscape( { DoPreEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); SharedLandscapeActor->DefaultPhysMaterial = LandscapePhysicalMaterial; - //SharedLandscapeActor->ChangedPhysMaterial(); + SharedLandscapeActor->ChangedPhysMaterial(); + } + + if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) + { + check(SharedLandscapeActor); + DoPostEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); } } @@ -532,21 +933,6 @@ FHoudiniLandscapeTranslator::CreateLandscape( // Currently the Temp Cook mode is not concerned with creating packages. This will, at the time of writing, // exclusively be dealt with during Bake mode so don't bother with searching / creating other packages. - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); - if (IsValid(HAC)) - { - TileWorld = HAC->GetWorld(); - TileLevel = HAC->GetComponentLevel(); - } - else - { - TileWorld = InWorld; - TileLevel = InWorld->PersistentLevel; - } - - check(TileWorld); - check(TileLevel); - AActor* FoundActor = nullptr; if (InPackageParams.PackageMode == EPackageMode::Bake) { @@ -578,6 +964,10 @@ FHoudiniLandscapeTranslator::CreateLandscape( if (!CurrentInputLandscape) continue; + if (SharedLandscapeActor && CurrentInputLandscape->GetLandscapeActor() != SharedLandscapeActor) + // This tile actor no longer associated with the current shared landscape + continue; + ULandscapeInfo* CurrentInfo = CurrentInputLandscape->GetLandscapeInfo(); if (!CurrentInfo) continue; @@ -586,7 +976,21 @@ FHoudiniLandscapeTranslator::CreateLandscape( int32 InputMinY = 0; int32 InputMaxX = 0; int32 InputMaxY = 0; - CurrentInfo->GetLandscapeExtent(InputMinX, InputMinY, InputMaxX, InputMaxY); + if (!LandscapeExtent.bIsCached) + { + PopulateLandscapeExtents(LandscapeExtent, CurrentInfo); + } + + if (!LandscapeExtent.bIsCached) + { + HOUDINI_LOG_WARNING(TEXT("Warning: Could not determine landscape extents. Cannot re-use input landscape actor.")); + continue; + } + + InputMinX = LandscapeExtent.MinY; + InputMinY = LandscapeExtent.MinY; + InputMaxX = LandscapeExtent.MaxX; + InputMaxY = LandscapeExtent.MaxY; // If the full size matches, we'll update that input landscape bool SizeMatch = false; @@ -607,6 +1011,8 @@ FHoudiniLandscapeTranslator::CreateLandscape( if (!FoundLandscapeProxy) { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Could not find input landscape to update. Searching output objects...")); + // Try to see if we can reuse one of our previous output landscape. // Keep track of the previous cook's landscapes TMap& OldOutputObjects = InOutput->GetOutputObjects(); @@ -637,6 +1043,14 @@ FHoudiniLandscapeTranslator::CreateLandscape( continue; } + if (SharedLandscapeActor && FoundLandscapeProxy->GetLandscapeActor() != SharedLandscapeActor) + { + // This landscape proxy is no longer part of the shared landscape. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Output landscape proxy is no longer part of the landscape. Skipping")); + FoundLandscapeProxy = nullptr; + continue; + } + // If we found a possible candidate, make sure that its size matches ours as we can only update a landscape tile of the same size if (!IsLandscapeTileCompatible( FoundLandscapeProxy, @@ -662,7 +1076,9 @@ FHoudiniLandscapeTranslator::CreateLandscape( // We found a valid Candidate! if (FoundLandscapeProxy) + { break; + } } } @@ -682,8 +1098,6 @@ FHoudiniLandscapeTranslator::CreateLandscape( if (IsValid(TileActor)) { - check(!(TileActor->IsPendingKill())); - // ---------------------------------------------------- // Check landscape compatibility // ---------------------------------------------------- @@ -709,9 +1123,14 @@ FHoudiniLandscapeTranslator::CreateLandscape( LandscapeInfo->UnregisterActor(TileActor); } } + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Incompatible tile actor. Destroying: %s"), *(TileActor->GetPathName())); TileActor->Destroy(); TileActor = nullptr; } + else + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Landscape tile is compatible: %s"), *(TileActor->GetPathName())); + } } // ---------------------------------------------------- @@ -726,68 +1145,110 @@ FHoudiniLandscapeTranslator::CreateLandscape( ALandscape* CachedLandscapeActor = nullptr; ULandscapeInfo *LandscapeInfo; +#if defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Tile Loc: %d, %d"), TileLoc.X, TileLoc.Y); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Tile Size: %d, %d"), UnrealTileSizeX, UnrealTileSizeY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Tile Num Quads/Section: %d"), NumQuadsPerLandscapeSection); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Tile Num Sections/Component: %d"), NumSectionPerLandscapeComponent); +#endif - if (!TileActor) + // ---------------------------------------------------- + // Update tile materials + // ---------------------------------------------------- + auto UpdateTileMaterialsFn = [&] () { - // Create a new Landscape tile in the TileWorld - TileActor = FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( - IntHeightData, LayerInfos, TileTransform, - UnrealTileSizeX, UnrealTileSizeY, - NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, - LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial, - LandscapeTileActorName, + bTileLandscapeMaterialChanged = (TileActor->LandscapeMaterial != LandscapeMaterial); + bTileLandscapeHoleMaterialChanged = (TileActor->LandscapeHoleMaterial != LandscapeHoleMaterial); + + if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) + DoPreEditChangeProperty(TileActor, "LandscapeMaterial"); + + if (bTileLandscapeMaterialChanged) + TileActor->LandscapeMaterial = LandscapeMaterial; + + if (bTileLandscapeHoleMaterialChanged) + TileActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + + bTilePhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? TileActor->DefaultPhysMaterial != LandscapePhysicalMaterial : false; + if (bTilePhysicalMaterialChanged) + { + DoPreEditChangeProperty(TileActor, "DefaultPhysMaterial"); + TileActor->DefaultPhysMaterial = LandscapePhysicalMaterial; + //TileActor->ChangedPhysMaterial(); + } + + if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) + { + DoPostEditChangeProperty(TileActor, "LandscapeMaterial"); + } + }; + + if (!TileActor) + { + // Create a new Landscape tile + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Creating new tile actor: %s"), *(LandscapeTileActorName)); + TileActor = FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( + IntHeightData, LayerInfos, TileTransform, TileLoc, + UnrealTileSizeX, UnrealTileSizeY, + NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, + LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial, + LandscapeTileActorName, TileActorType, SharedLandscapeActor, TileWorld, TileLevel, - InPackageParams); + InPackageParams, + bHasEditLayers, + InEditLayerFName, + AfterLayerName); if (!TileActor || !TileActor->IsValidLowLevel()) return false; - // Update the visibility mask / layer if we have any - for (auto CurrLayerInfo : LayerInfos) - { - if (CurrLayerInfo.LayerInfo && CurrLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) - { - TileActor->VisibilityLayer = CurrLayerInfo.LayerInfo; - TileActor->VisibilityLayer->bNoWeightBlend = true; - TileActor->VisibilityLayer->AddToRoot(); - } - } - + TileActor->GetLandscapeActor()->bCanHaveLayersContent = bHasEditLayers; LandscapeInfo = TileActor->GetLandscapeInfo(); + UpdateTileMaterialsFn(); + + TMap ExistingLayers; + UpdateLandscapeMaterialLayers( + TileActor->GetLandscapeActor(), + LayerInfos, + ExistingLayers, + bLayerNoWeightBlend, + bHasEditLayers, + InEditLayerFName); + bCreatedTileActor = true; - bTileLandscapeMaterialChanged = true; - bTileLandscapeHoleMaterialChanged = true; - bTilePhysicalMaterialChanged = true; bHeightLayerDataChanged = true; bCustomLayerDataChanged = true; } - else + else { + // Re-use existing tile actor + ALandscape* TargetLandscape = TileActor->GetLandscapeActor(); + check(TargetLandscape); + LandscapeInfo = TileActor->GetLandscapeInfo(); + TargetLandscape->bCanHaveLayersContent = bHasEditLayers; // Always update the transform, even if the HGPO transform hasn't changed, // If we change the number of tiles, or switch from outputting single tile to multiple, // then its fairly likely that the unreal transform has changed even if the // Houdini Transform remained the same - if (!TileActor->GetTransform().Equals(TileTransform)) - { - // HOUDINI_LOG_DISPLAY(TEXT("[CreateLandscape] Updating tile transform: %s"), *(TileTransform.ToString())); - TileActor->SetActorTransform(TileTransform); - TileActor->SetAbsoluteSectionBase(TileLoc); -#if WITH_EDITOR - GEngine->BroadcastOnActorMoved(TileActor); -#endif - LandscapeInfo->RecreateLandscapeInfo(InWorld,true); - } - + bool bUpdateTransform = !TileActor->GetActorTransform().Equals(TileTransform); + // Update existing landscape / tile if (SharedLandscapeActor && TileActorType == LandscapeActorType::LandscapeStreamingProxy) { TileActor->FixupSharedData(SharedLandscapeActor); + if (bUpdateTransform) + { + TileActor->SetAbsoluteSectionBase(TileLoc); + LandscapeInfo->FixupProxiesTransform(); + LandscapeInfo->RecreateLandscapeInfo(TileWorld,true); + } // This is a tile with a shared landscape. // Check whether the LandscapeActor changed. If so, update the proxy's shared properties. @@ -816,13 +1277,41 @@ FHoudiniLandscapeTranslator::CreateLandscape( else { // This is a standalone tile / landscape actor. - CachedLandscapeActor = Cast(TileActor); + if (bUpdateTransform) + { + TileActor->SetActorRelativeTransform(TileTransform); + TileActor->SetAbsoluteSectionBase(TileLoc); + } } + + UpdateTileMaterialsFn(); + + // Update landscape edit layers to match layer infos + TMap ExistingLayers; + UpdateLandscapeMaterialLayers(TargetLandscape, LayerInfos, ExistingLayers, bLayerNoWeightBlend, bHasEditLayers, InEditLayerFName); + CachedLandscapeActor = TileActor->GetLandscapeActor(); + ULandscapeInfo* PreviousInfo = TileActor->GetLandscapeInfo(); if (!PreviousInfo) return false; + int32 EditLayerIndex = INDEX_NONE; + FLandscapeLayer* TargetLayer = nullptr; + FGuid LayerGuid; + if (bHasEditLayers) + { + EditLayerIndex = FindOrCreateEditLayer(CachedLandscapeActor, InEditLayerFName, AfterLayerName); + TargetLayer = CachedLandscapeActor->GetLayer(EditLayerIndex); + if (TargetLayer) + { + // Ensure target layer blend mode is additive + TargetLayer->BlendMode = ELandscapeBlendMode::LSBM_AdditiveBlend; + LayerGuid = TargetLayer->Guid; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::OutputLandscape_GenerateTile] Setting edit layer to additive: %d, %s"), EditLayerIndex, *(TargetLayer->Name.ToString())); + } + } + FIntRect BoundingRect = TileActor->GetBoundingRect(); FIntPoint SectionOffset = TileActor->GetSectionBaseOffset(); @@ -832,63 +1321,117 @@ FHoudiniLandscapeTranslator::CreateLandscape( const int32 MinY = TileLoc.Y; const int32 MaxY = TileLoc.Y + UnrealTileSizeY - 1; - // NOTE: Use HeightmapAccessor / AlphamapAccessor instead of FLandscapeEditDataInterface. - // FLandscapeEditDataInterface is a more low level data interface, used internally by the *Accessor tools - // though the *Accessors do additional things like update normals and foliage. - - // Update height if it has been changed. - if (Heightfield->bHasGeoChanged) + if (!bHasEditLayers || (bHasEditLayers && LayerGuid.IsValid())) { - // It is important to update the heightmap through the this since it will properly - // update normals and foliage. - FHeightmapAccessor HeightmapAccessor(LandscapeInfo); - HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, IntHeightData.GetData()); + FScopedSetLandscapeEditingLayer Scope(CachedLandscapeActor, LayerGuid, [=] { CachedLandscapeActor->RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All); }); + FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); - bHeightLayerDataChanged = true; - } + bool bClearLayers = false; + if (!ClearedLayers.Contains(InEditLayerName)) + { + // We haven't cleared layers for this edit layer yet. + ClearedLayers.Add(InEditLayerName); + bClearLayers = true; + } - // Update the layers on the landscape. - for (FLandscapeImportLayerInfo &NextUpdatedLayerInfo : LayerInfos) - { - FAlphamapAccessor AlphaAccessor(LandscapeInfo, NextUpdatedLayerInfo.LayerInfo); - AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); - - if (NextUpdatedLayerInfo.LayerInfo && NextUpdatedLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + // NOTE: Use HeightmapAccessor / AlphamapAccessor instead of FLandscapeEditDataInterface. + // FLandscapeEditDataInterface is a more low level data interface, used internally by the *Accessor tools + // though the *Accessors do additional things like update normals and foliage. + + // Update height if it has been changed. + if (Heightfield->bHasGeoChanged) { - TileActor->VisibilityLayer = NextUpdatedLayerInfo.LayerInfo; - TileActor->VisibilityLayer->bNoWeightBlend = true; - TileActor->VisibilityLayer->AddToRoot(); + // It is important to update the heightmap through HeightmapAccessor this since it will properly + // update normals and foliage. + FHeightmapAccessor HeightmapAccessor(LandscapeInfo); + HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, IntHeightData.GetData()); + bHeightLayerDataChanged = true; + } + + // Collect the stale layers so that we can clear them afterward. + TSet StaleLayers; + for (FLandscapeInfoLayerSettings& Layer : LandscapeInfo->Layers) + { + StaleLayers.Add(Layer.LayerName); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("Tracking potential stale layer: %s"), *(Layer.LayerName.ToString())); + } + + // Update the layers on the landscape. + for (FLandscapeImportLayerInfo &LayerInfo : LayerInfos) + { + FString PaintLayerName(LayerInfo.LayerName.ToString()); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::OutputLandscape_GenerateTile] Drawing found layer: %s"), *(PaintLayerName) ); + + if (LayerInfo.LayerName.IsEqual(HAPI_UNREAL_VISIBILITY_LAYER_NAME)) + { + // Visibility layer has 'special' naming. + StaleLayers.Remove(ALandscape::VisibilityLayer->LayerName); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("Removing visibility from stale layer set: %s"), *(ALandscape::VisibilityLayer->LayerName.ToString())); + } + else + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("Removing layer from stale layer set: %s"), *(LayerInfo.LayerName.ToString())); + StaleLayers.Remove(LayerInfo.LayerName); + } + + if (LayerInfo.LayerName.IsEqual(HAPI_UNREAL_VISIBILITY_LAYER_NAME)) + { + if (LayerInfo.LayerInfo) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::OutputLandscape_GenerateTile] Drawing visibility layer: %s on EditLayer (%s) (bNoWeightBlend: %d)"), *(LayerInfo.LayerName.ToString()), *(InEditLayerName), (LayerInfo.LayerInfo->bNoWeightBlend) ); + // NOTE: AProxyLandscape::VisibilityLayer is a STATIC property (Info objects is being shared by ALL landscapes). Don't try to update / replace it. + FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); + AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, LayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Cannot draw on NULL visibility layer")); + } + + } + else + { + ULandscapeLayerInfoObject* TargetLayerInfo = LandscapeInfo->GetLayerInfoByName(LayerInfo.LayerName); + if (TargetLayerInfo) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::OutputLandscape_GenerateTile] Drawing paint layer: %s on EditLayer (%s) (bNoWeightBlend: %d)"), *(LayerInfo.LayerName.ToString()), *(InEditLayerName), (LayerInfo.LayerInfo->bNoWeightBlend) ); + FAlphamapAccessor AlphaAccessor(LandscapeInfo, TargetLayerInfo); + AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, LayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Cannot draw on NULL landscape layer: %s"), *(LayerInfo.LayerName.ToString())); + } + } + + bCustomLayerDataChanged = true; } - bCustomLayerDataChanged = true; + // Clear stale layers + if (bClearLayers) + { + for (FName& StaleLayerName : StaleLayers) + { + ULandscapeLayerInfoObject* LayerInfo = LandscapeInfo->GetLayerInfoByName(StaleLayerName); + if (!LayerInfo) + { + continue; + } + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::OutputLandscape_GenerateTile] Clearing stale layer name %s, layer info name: %s on EditLayer (%s)"), *(StaleLayerName.ToString()), *(LayerInfo->LayerName.ToString()), *(InEditLayerName) ); + TargetLandscape->ClearPaintLayer(LayerGuid, LayerInfo); + } + } + } + else + { + HOUDINI_LOG_ERROR(TEXT("Could not retrieve valid Guid for edit layer: %s"), *(InEditLayerName)); } bModifiedLandscapeActor = true; } - // ---------------------------------------------------- - // Update tile materials - // ---------------------------------------------------- - // TODO: These material updates can possibly be skipped if we have already performed this - // check on a SharedLandscape. - bTileLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (TileActor->GetLandscapeMaterial() != LandscapeMaterial) : false; - bTileLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (TileActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; - if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) - DoPreEditChangeProperty(TileActor, "LandscapeMaterial"); - if (bTileLandscapeMaterialChanged) - TileActor->LandscapeMaterial = LandscapeMaterial; - - if (bTileLandscapeHoleMaterialChanged) - TileActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - - bTilePhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? TileActor->DefaultPhysMaterial != LandscapePhysicalMaterial : false; - if (bTilePhysicalMaterialChanged) - { - DoPreEditChangeProperty(TileActor, "DefaultPhysMaterial"); - TileActor->DefaultPhysMaterial = LandscapePhysicalMaterial; - //TileActor->ChangedPhysMaterial(); - } // ---------------------------------------------------- // Apply actor tags @@ -908,19 +1451,19 @@ FHoudiniLandscapeTranslator::CreateLandscape( // effect appropriate state updates based on the property updates that was performed in // the above code. - if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) - { - check(SharedLandscapeActor); - DoPostEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); - } + // if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) + // { + // check(SharedLandscapeActor); + // DoPostEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + // } - if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) - { - check(TileActor); - // Tile material changes are only processed if it wasn't already done for a shared - // landscape since the shared landscape should have already propagated the changes to associated proxies. - DoPostEditChangeProperty(TileActor, "LandscapeMaterial"); - } + // if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) + // { + // check(TileActor); + // // Tile material changes are only processed if it wasn't already done for a shared + // // landscape since the shared landscape should have already propagated the changes to associated proxies. + // DoPostEditChangeProperty(TileActor, "LandscapeMaterial"); + // } if (bSharedPhysicalMaterialChanged) { @@ -951,15 +1494,55 @@ FHoudiniLandscapeTranslator::CreateLandscape( if (LandscapeInfo) { - LandscapeInfo->RecreateLandscapeInfo(InWorld, true); + LandscapeInfo->RecreateLandscapeInfo(TileWorld, true); + LandscapeInfo->RecreateCollisionComponents(); + } + + { + // Update UProperties + + // Apply detail attributes to both the Shared Landscape and the Landscape Tile actor + TArray PropertyAttributes; + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + GeoId, PartId, + true, + INDEX_NONE, INDEX_NONE, INDEX_NONE, + PropertyAttributes)) + { + if (IsValid(TileActor)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(TileActor, PropertyAttributes); + } + if (IsValid(SharedLandscapeActor)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(SharedLandscapeActor, PropertyAttributes); + } + } + + // Apply point attributes only to the Shared Landscape and the Landscape Tile actor + PropertyAttributes.Empty(); + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + GeoId, PartId, + false, + 0, INDEX_NONE, 0, + PropertyAttributes)) + { + if (IsValid(TileActor)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(TileActor, PropertyAttributes); + } + } } // Add objects to the HAC output. SetLandscapeActorAsOutput( InOutput, + StaleOutputObjects, + OutActiveLandscapes, InAllInputLandscapes, OutputAttributes, OutputTokens, + InEditLayerFName, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedSharedLandscape, @@ -967,9 +1550,495 @@ FHoudiniLandscapeTranslator::CreateLandscape( TileActor, InPackageParams.PackageMode); +#if defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) + if (LandscapeInfo) + { + int32 MinX, MinY, MaxX, MaxY; + LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Landscape extent: %d, %d -> %d, %d"), MinX, MinY, MaxX, MaxY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Cached extent: %d, %d -> %d, %d"), LandscapeExtent.MinX, LandscapeExtent.MinY, LandscapeExtent.MaxX, LandscapeExtent.MaxY); + } + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Ending with num of output objects: %d"), InOutput->GetOutputObjects().Num()); +#endif + return true; } +bool FHoudiniLandscapeTranslator::OutputLandscape_ModifyLayer(UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedActors, TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, UWorld* World, const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, FHoudiniPackageParams InPackageParams, + const bool bHasEditLayers, + const FName& EditLayerName, + TSet& ClearedLayers, + TArray& OutCreatedPackages) +{ + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::OutputLandscape_EditableLayer] =======================================================================")); + + check(LayerMinimums.Contains(TEXT("height"))); + check(LayerMaximums.Contains(TEXT("height"))); + + float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); + float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); + + if (!IsValid(InOutput)) + return false; + + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + if (!IsValid(HAC)) + return false; + + // Get the height map. + const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput, false, NAME_None); + if (!Heightfield) + return false; + + if (Heightfield->Type != EHoudiniPartType::Volume) + return false; + + const HAPI_NodeId GeoId = Heightfield->GeoId; + const HAPI_PartId PartId = Heightfield->PartId; + + TArray IntData; + TArray StrData; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_type + // --------------------------------------------- + int32 EditLayerType = HAPI_UNREAL_LANDSCAPE_EDITLAYER_TYPE_BASE; + IntData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TYPE, AttributeInfo, IntData, 1)) + { + if (IntData.Num() > 0) + { + EditLayerType = IntData[0]; + } + } + + const bool bLayerNoWeightBlend = EditLayerType == HAPI_UNREAL_LANDSCAPE_EDITLAYER_TYPE_BASE ? false : true; + + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_name + // --------------------------------------------- + StrData.Empty(); + FString EditableLayerName; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_NAME, + AttributeInfo, StrData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (StrData.Num() > 0) + { + EditableLayerName = StrData[0]; + } + } + if (EditableLayerName.IsEmpty()) + return false; + + // Construct the identifier of the Heightfield geo part. + FHoudiniOutputObjectIdentifier HeightfieldIdentifier(Heightfield->ObjectId, GeoId, PartId, "Heightfield"); + HeightfieldIdentifier.PartName = Heightfield->PartName; + + // Extract the float data from the Heightfield. + const FHoudiniVolumeInfo &VolumeInfo = Heightfield->VolumeInfo; + TArray FloatValues; + float FloatMin, FloatMax; + if (!GetHoudiniHeightfieldFloatData(Heightfield, FloatValues, FloatMin, FloatMax)) + return false; + + // Get the Unreal landscape size + const int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; + const int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; + + if (!LandscapeTileSizeInfo.bIsCached) + { + // Calculate a landscape size info from this heightfield to be + // used by subsequent tiles on the same landscape + if (FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( + HoudiniHeightfieldXSize, + HoudiniHeightfieldYSize, + LandscapeTileSizeInfo.UnrealSizeX, + LandscapeTileSizeInfo.UnrealSizeY, + LandscapeTileSizeInfo.NumSectionsPerComponent, + LandscapeTileSizeInfo.NumQuadsPerSection)) + { + LandscapeTileSizeInfo.bIsCached = true; + } + else + { + return false; + } + } + + TMap OutputTokens; + FHoudiniAttributeResolver Resolver; + // Update resolver attributes and tokens before we start resolving attributes. + InPackageParams.UpdateTokensFromParams(World, HAC, OutputTokens); + + // --------------------------------------------- + // Attribute: unreal_landscape_actor_name + // --------------------------------------------- + // Retrieve the name of the main Landscape actor to look for + FString TargetLandscapeName = "Input0"; + bool bHasEditLayerTarget = false; + StrData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TARGET, + AttributeInfo, StrData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (StrData.Num() > 0 && !StrData[0].IsEmpty()) + { + TargetLandscapeName = StrData[0]; + bHasEditLayerTarget = true; + } + } + + if (!bHasEditLayerTarget) + { + // The previous implementation of the "Edit Layer" mode used the Shared Landscape Actor name. If the (new) + // Edit Layer Target attribute wasn't specified, check whether the old attribute is present. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, + AttributeInfo, StrData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (StrData.Num() > 0 && !StrData[0].IsEmpty()) + { + TargetLandscapeName = StrData[0]; + } + } + } + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Landscape Name (Attrib): %s"), *(TargetLandscapeName)); + + Resolver.SetAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TARGET, TargetLandscapeName); + Resolver.SetTokensFromStringMap(OutputTokens); + TargetLandscapeName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TARGET, TargetLandscapeName); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Landscape Name (Resolved): %s"), *(TargetLandscapeName)); + + // --------------------------------------------- + // Find the landscape that we're targeting for output + // --------------------------------------------- + ALandscapeProxy* TargetLandscapeProxy = FindTargetLandscapeProxy(TargetLandscapeName, World, InAllInputLandscapes); + if (!IsValid(TargetLandscapeProxy)) + { + HOUDINI_LOG_WARNING(TEXT("Could not find landscape actor: %s"), *(TargetLandscapeName)); + return false; + } + ALandscape* TargetLandscape = TargetLandscapeProxy->GetLandscapeActor(); + if (!IsValid(TargetLandscape)) + { + HOUDINI_LOG_WARNING(TEXT("Could not retrieve the landscape actor for: %s"), *(TargetLandscapeName)); + return false; + } + + if (!TargetLandscape->bCanHaveLayersContent) + { + HOUDINI_LOG_WARNING(TEXT("Target landscape does not have edit layers enabled!: %s"), *(TargetLandscapeName)); + return false; + } + + ULandscapeInfo* TargetLandscapeInfo = TargetLandscapeProxy->GetLandscapeInfo(); + const FTransform TargetLandscapeTransform = TargetLandscape->GetActorTransform(); + + const float DestHeightScale = TargetLandscapeProxy->LandscapeActorToWorld().GetScale3D().Z; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Height Scale: %f"), DestHeightScale); + + // Create the layer if it doesn't exist + int32 EditLayerIndex = TargetLandscape->GetLayerIndex(FName(EditableLayerName)); + const FLandscapeLayer* TargetLayer = TargetLandscape->GetLayer(EditLayerIndex); + if (!TargetLayer) + { + // Create new edit layer + EditLayerIndex = TargetLandscape->CreateLayer(FName(EditableLayerName)); + TargetLayer = TargetLandscape->GetLayer(FName(EditableLayerName)); + } + + if (!TargetLayer) + { + HOUDINI_LOG_WARNING(TEXT("Could not find or create target layer: %s"), *(TargetLandscapeName)); + return false; + } + + { + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_after + // --------------------------------------------- + StrData.Empty(); + FString AfterLayerName; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_AFTER, + AttributeInfo, StrData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (StrData.Num() > 0) + { + AfterLayerName = StrData[0]; + } + } + if (!AfterLayerName.IsEmpty()) + { + // If we have an "after layer", move the output layer into position. + int32 NewLayerIndex = TargetLandscape->GetLayerIndex(FName(AfterLayerName)); + if (NewLayerIndex != INDEX_NONE && EditLayerIndex != NewLayerIndex) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Moving layer from %d to %d"), EditLayerIndex, NewLayerIndex); + if (NewLayerIndex < EditLayerIndex) + { + NewLayerIndex += 1; + } + TargetLandscape->ReorderLayer(EditLayerIndex, NewLayerIndex); + + // Ensure we have the correct layer/index + EditLayerIndex = TargetLandscape->GetLayerIndex(FName(EditableLayerName)); + TargetLayer = TargetLandscape->GetLayer(EditLayerIndex); + } + } + } + + // ---------------------------------------------------- + // Convert Heightfield data + // ---------------------------------------------------- + // Convert Houdini's heightfield data to Unreal's landscape data + TArray IntHeightData; + FTransform TileTransform; + if (!FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( + FloatValues, VolumeInfo, + LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY, + FloatMin, FloatMax, + IntHeightData, TileTransform, + false, true, DestHeightScale, + EditLayerType == HAPI_UNREAL_LANDSCAPE_EDITLAYER_TYPE_ADDITIVE)) + return false; + + + // ---------------------------------------------------- + // Calculate Tile location and landscape offset + // ---------------------------------------------------- + + int32 TargetMinX, TargetMaxX, TargetMinY, TargetMaxY; + TargetLandscapeInfo->GetLandscapeExtent(TargetMinX, TargetMinY, TargetMaxX, TargetMaxY); + + // ---------------------------------------------------- + // Calculate the draw location (in quad space) of + // where the Modify Layer should be drawn + // ---------------------------------------------------- + FVector LandscapeBaseLoc = TargetLandscape->GetTransform().InverseTransformPosition(TargetLandscape->GetTransform().GetLocation()); + FTransform HACTransform = HAC->GetComponentTransform(); + + // We need to unscale the TileTransform before concatenating it with the HAC transform. + FTransform UnscaledTileTransform = TileTransform; + UnscaledTileTransform.SetScale3D(FVector::OneVector); + + // Calculate the tile's transform in quad space on the target landscape. + const FTransform LocalTileTransform = HACTransform * UnscaledTileTransform * TargetLandscape->GetTransform().Inverse(); + + const FVector LocalPos = LocalTileTransform.GetLocation(); + TargetMinX += LocalPos.X; + TargetMinY += LocalPos.Y; + + // The layer location will be offset / drawn at the "min" location (on the target landscape) in quad space + const FVector LayerLoc = FVector(TargetMinX, TargetMinY, 0.f); + + FIntPoint TargetTileLoc; + + TargetTileLoc.X = FMath::RoundToInt(LayerLoc.X); + TargetTileLoc.Y = FMath::RoundToInt(LayerLoc.Y); + + FVector TileMin, TileMax; + TileMin.X = TargetTileLoc.X; + TileMin.Y = TargetTileLoc.Y; + TileMax.X = TargetTileLoc.X + LandscapeTileSizeInfo.UnrealSizeX - 1; + TileMax.Y = TargetTileLoc.Y + LandscapeTileSizeInfo.UnrealSizeY - 1; + TileMin.Z = TileMax.Z = 0.f; + + FTransform DestLandscapeTransform = TargetLandscapeProxy->LandscapeActorToWorld(); + + // NOTE: we don't manually inject a tile number in the object name. This should + // already be encoded in the TileName string. + FHoudiniPackageParams TilePackageParams = InPackageParams; + FHoudiniPackageParams LayerPackageParams = InPackageParams; + + TilePackageParams.ObjectName = TargetLandscapeName; + LayerPackageParams.ObjectName = TargetLandscapeName; + + // Look for all the layers/masks corresponding to the current heightfield. + TArray< const FHoudiniGeoPartObject* > FoundLayers; + FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, bHasEditLayers, EditLayerName, FoundLayers); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Found %d output layers."), FoundLayers.Num()); + + // If we are operating in EditLayer mode, then any layers we create or manage should + // have their bNoWeightBlend set to true otherwise all the paint layers will act as additive + // which, most of the time, is not what we expect. + const bool bDefaultNoWeightBlend = bHasEditLayers ? true : false; + + // Get the updated layers. + TArray LayerInfos; + if (!CreateOrUpdateLandscapeLayerData(FoundLayers, *Heightfield, LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY, + LayerMinimums, LayerMaximums, LayerInfos, false, bDefaultNoWeightBlend, + TilePackageParams, + LayerPackageParams, + OutCreatedPackages)) + return false; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Generated %d layer infos."), LayerInfos.Num()); + + // Update landscape edit layers to match layer infos + TMap ExistingLayers; + UpdateLandscapeMaterialLayers(TargetLandscape, LayerInfos, ExistingLayers, bLayerNoWeightBlend, bHasEditLayers, EditLayerName); + + // Clear layers + if (!ClearedLayers.Contains(EditableLayerName)) + { + bool bClearLayer = false; + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_clear + // --------------------------------------------- + // Check whether we should clear the target edit layer. + IntData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_CLEAR, + AttributeInfo, IntData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (IntData.Num() > 0) + { + bClearLayer = IntData[0] != 0; + } + } + + if (bClearLayer) + { + if (TargetLayer) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Clearing layer: %s"), *EditableLayerName); + ClearedLayers.Add(EditableLayerName); + + // Clear the heightmap + TargetLandscape->ClearLayer(TargetLayer->Guid, nullptr, ELandscapeClearMode::Clear_Heightmap); + + // Clear the paint layers, but only the ones that are being output from Houdini. + for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) + { + if (!ExistingLayers.Contains(InLayerInfo.LayerName)) + continue; + + TargetLandscape->ClearPaintLayer(TargetLayer->Guid, InLayerInfo.LayerInfo); + } + } + } + } + + + { + // Scope the Edit Layer before we start drawing on ANY of the layers + FScopedSetLandscapeEditingLayer Scope(TargetLandscape, TargetLayer->Guid, [=] { TargetLandscape->RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All); }); + FLandscapeEditDataInterface LandscapeEdit(TargetLandscapeInfo); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Drawing heightmap..")); + // Draw Heightmap + FHeightmapAccessor HeightmapAccessor(TargetLandscapeInfo); + HeightmapAccessor.SetData(TileMin.X, TileMin.Y, TileMax.X, TileMax.Y, IntHeightData.GetData()); + + // Draw material layers on the landscape + // Update the layers on the landscape. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target has layers content: %d"), TargetLandscape->HasLayersContent()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] IsEditingLayer? %d"), TargetLandscape->GetEditingLayer().IsValid()); + + for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Trying to draw on layer: %s"), *(InLayerInfo.LayerName.ToString())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); + + if (InLayerInfo.LayerInfo && InLayerInfo.LayerName.IsEqual(HAPI_UNREAL_VISIBILITY_LAYER_NAME)) + { + // NOTE: AProxyLandscape::VisibilityLayer is a STATIC property (Info objects is being shared by ALL landscapes). Don't try to update / replace it. + FAlphamapAccessor AlphaAccessor(TargetLandscapeInfo, ALandscapeProxy::VisibilityLayer); + AlphaAccessor.SetData(TileMin.X, TileMin.Y, TileMax.X, TileMax.Y, InLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + else + { + if (!ExistingLayers.Contains(InLayerInfo.LayerName)) + continue; + + int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); + if (TargetLandscape->EditorLayerSettings.IsValidIndex(LayerIndex)) + { + FLandscapeEditorLayerSettings& CurLayer = TargetLandscape->EditorLayerSettings[LayerIndex]; + // Draw on the current layer, if it is valid. + if (CurLayer.LayerInfoObj) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Drawing using Alpha accessor. Dest Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); + FAlphamapAccessor AlphaAccessor(TargetLandscapeInfo, CurLayer.LayerInfoObj); + AlphaAccessor.SetData(TileMin.X, TileMin.Y, TileMax.X, TileMax.Y, InLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + } + } + } + + } // Landscape layer drawing scope + + // Only keep output the output object that corresponds to this layer. Everything else should be removed. + TMap OutputObjects = InOutput->GetOutputObjects(); + TSet StaleOutputs; + OutputObjects.GetKeys(StaleOutputs); + bool bFoundOutputObject = false; + for(auto& Entry : OutputObjects) + { + if (bFoundOutputObject) + continue; // We already have a matching layer output object. Anything else is stale. + + FHoudiniOutputObjectIdentifier& OutputId = Entry.Key; + FHoudiniOutputObject& Object = Entry.Value; + UHoudiniLandscapeEditLayer* EditLayer = Cast(Object.OutputObject); + if (!IsValid(EditLayer)) + continue; + StaleOutputs.Remove(OutputId); + } + + // Clean up stale outputs + for(FHoudiniOutputObjectIdentifier& StaleId : StaleOutputs) + { + FHoudiniOutputObject& OutputObject = OutputObjects.FindChecked(StaleId); + if (UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject)) + { + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + + if (LandscapeProxy) + { + // We shouldn't destroy any input landscapes + if (!InAllInputLandscapes.Contains(LandscapeProxy)) + { + LandscapeProxy->Destroy(); + } + } + } + + OutputObjects.Remove(StaleId); + } + + // Update the output object + FHoudiniOutputObjectIdentifier OutputObjectIdentifier(Heightfield->ObjectId, GeoId, PartId, "EditableLayer"); + FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(OutputObjectIdentifier); + UHoudiniLandscapeEditLayer* LayerPtr = NewObject(InOutput); + LayerPtr->SetSoftPtr(TargetLandscape); + LayerPtr->LayerName = EditableLayerName; + OutputObj.OutputObject = LayerPtr; + // Editable layers doesn't currently require any attributes / tokens to be cached. + // OutputObj.CachedAttributes = OutputAttributes; + // OutputObj.CachedTokens = OutputTokens; + + return true; +} + + bool FHoudiniLandscapeTranslator::IsLandscapeInfoCompatible( const ULandscapeInfo* LandscapeInfo, @@ -986,6 +2055,13 @@ FHoudiniLandscapeTranslator::IsLandscapeInfoCompatible( if (LandscapeInfo->SubsectionSizeQuads != InNumQuadsPerSection) return false; + + int32 MinX, MinY, MaxX, MaxY; + if (LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + { + const int32 NumComponentsX = (MaxX - MinX) / (InNumQuadsPerSection*InNumSectionsPerComponent); + const int32 NumComponentsY = (MaxY - MinY) / (InNumQuadsPerSection*InNumSectionsPerComponent); + } return true; } @@ -1038,6 +2114,168 @@ bool FHoudiniLandscapeTranslator::IsLandscapeTypeCompatible(const AActor* Actor, return false; } +bool FHoudiniLandscapeTranslator::PopulateLandscapeExtents(FHoudiniLandscapeExtent& Extent, + const ULandscapeInfo* LandscapeInfo) +{ + if (LandscapeInfo->GetLandscapeExtent(Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY)) + { + Extent.ExtentsX = Extent.MaxX - Extent.MinX; + Extent.ExtentsY = Extent.MaxY - Extent.MinY; + Extent.bIsCached = true; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Cached extent: %d, %d -> %d, %d"), Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY); + + return true; + } + return false; +} + +bool FHoudiniLandscapeTranslator::UpdateLandscapeMaterialLayers(ALandscape* InLandscape, + const TArray& LayerInfos, + TMap& OutPaintLayers, + bool bNoWeightBlend, + bool bHasEditLayers, + const FName& EditLayerName) +{ + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::UpdateLandscapeEditLayers] Num Layer Infos: %d"), (LayerInfos.Num())); + + check(InLandscape); + + ULandscapeInfo* LandscapeInfo = InLandscape->GetLandscapeInfo(); + + FGuid LayerGuid; + if (bHasEditLayers) + { + const FLandscapeLayer* Layer = InLandscape->GetLayer(EditLayerName); + if (Layer) + { + LayerGuid = Layer->Guid; + } + } + + // If we are dealing with edit layers we need Lock the landscape edit layer, i.e., if the Guid is valid. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::UpdateLandscapeEditLayers] Scope locking edit layer: %s (%s)"), *(EditLayerName.ToString()), *(LayerGuid.ToString())); + FScopedSetLandscapeEditingLayer Scope(InLandscape, LayerGuid, [=] { InLandscape->RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All); }); + + OutPaintLayers.Empty(); + GetLandscapePaintLayers(InLandscape, OutPaintLayers); + + bool bLayerHasChanged = false; + for (const FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::UpdateLandscapeEditLayers] Processing Layer Info: %s, bNoWeightBlend: %d"), *(InLayerInfo.LayerName.ToString()), bNoWeightBlend); + + if (InLayerInfo.LayerName.IsEqual(HAPI_UNREAL_VISIBILITY_LAYER_NAME)) + { + // Visibility layers should always have this set to true. + InLayerInfo.LayerInfo->bNoWeightBlend = true; + continue; + } + else + { + // Any other layers use the incoming default. + // TODO: Override the bNoWeightBlend value from user attribute on the geo. + InLayerInfo.LayerInfo->bNoWeightBlend = bNoWeightBlend; + } + + // The following layer adding / replacing code have been referenced from: + // - ALandscapeProxy::CreateLayerInfo(...) + // - FLandscapeEditorCustomNodeBuilder_TargetLayers::OnTargetLayerSetObject(...) + + int32 LayerInfoIndex = LandscapeInfo->GetLayerInfoIndex(InLayerInfo.LayerName); + if (LayerInfoIndex == INDEX_NONE) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::UpdateLandscapeEditLayers] Creating new LandscapeLayerInfo: %s"), *(InLayerInfo.LayerName.ToString())); + // The material layer does not yet exist + LayerInfoIndex = LandscapeInfo->Layers.Add(FLandscapeInfoLayerSettings(InLayerInfo.LayerInfo, InLandscape)); + LandscapeInfo->CreateLayerEditorSettingsFor(InLayerInfo.LayerInfo); + bLayerHasChanged = true; + } + else + { + FLandscapeInfoLayerSettings& LayerSettings = LandscapeInfo->Layers[LayerInfoIndex]; + if (!LayerSettings.LayerInfoObj) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::UpdateLandscapeEditLayers] Setting new LandscapeLayerInfo on existing layer: %s"), *(InLayerInfo.LayerName.ToString())); + LayerSettings.LayerInfoObj = InLayerInfo.LayerInfo; + LandscapeInfo->CreateLayerEditorSettingsFor(InLayerInfo.LayerInfo); + bLayerHasChanged = true; + } + else if (LayerSettings.LayerInfoObj != InLayerInfo.LayerInfo) + { + // Existing material layer has mismatching Layer Info object. Update it. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::UpdateLandscapeEditLayers] Replacing existing LandscapeLayerInfo: %s"), *(InLayerInfo.LayerName.ToString())); + LandscapeInfo->ReplaceLayer(LayerSettings.LayerInfoObj, InLayerInfo.LayerInfo); + LayerSettings.LayerInfoObj = InLayerInfo.LayerInfo; + bLayerHasChanged = true; + } + } + + if (LayerInfoIndex != INDEX_NONE) + { + OutPaintLayers.Add(InLayerInfo.LayerName, LayerInfoIndex); + } + } + + if (bLayerHasChanged) + { + LandscapeInfo->UpdateLayerInfoMap(); + } + + return bLayerHasChanged; +} + +// bool +// FHoudiniLandscapeTranslator::ClearEditLayer( +// ALandscape* InLandscape, +// const TArray& LayerInfos, +// TSet& ClearedLayers, +// bool bHasEditLayer, +// const FString& EditLayerName) +// { +// TArray IntData; +// // Clear layers +// if (!ClearedLayers.Contains(EditLayerName)) +// { +// bool bClearLayer = false; +// // --------------------------------------------- +// // Attribute: unreal_landscape_editlayer_clear +// // --------------------------------------------- +// // Check whether we should clear the target edit layer. +// IntData.Empty(); +// if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, +// HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_CLEAR, AttributeInfo, IntData, 1)) +// { +// if (IntData.Num() > 0) +// { +// bClearLayer = IntData[0] != 0; +// } +// } +// +// if (bClearLayer) +// { +// if (TargetLayer) +// { +// HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Clearing layer heightmap: %s"), *EditableLayerName); +// ClearedLayers.Add(EditLayerName); +// +// // Clear the heightmap +// TargetLandscape->ClearLayer(TargetLayer->Guid, nullptr, ELandscapeClearMode::Clear_Heightmap); +// +// // Clear the paint layers, but only the ones that are being output from Houdini. +// for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) +// { +// if (!ExistingLayers.Contains(InLayerInfo.LayerName)) +// continue; +// +// int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); +// TargetLandscape->ClearPaintLayer(TargetLayer->Guid, InLayerInfo.LayerInfo); +// } +// } +// } +// } + ALandscapeProxy* FHoudiniLandscapeTranslator::FindExistingLandscapeActor( UWorld* InWorld, @@ -1118,6 +2356,96 @@ FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( return OutActor; } + +ALandscapeProxy* FHoudiniLandscapeTranslator::FindTargetLandscapeProxy(const FString& ActorName, UWorld* World, + const TArray& LandscapeInputs) +{ + int32 InputIndex = INDEX_NONE; + if (ActorName.StartsWith(TEXT("Input"))) + { + // Extract the numeric value after 'Input'. + FString IndexStr; + ActorName.Split(TEXT("Input"), nullptr, &IndexStr); + if (IndexStr.IsNumeric()) + { + InputIndex = FPlatformString::Atoi(*IndexStr); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FindTargetLandscapeProxy] Extract index %d from actor name: %s"), InputIndex, *ActorName); + } + } + + if (InputIndex != INDEX_NONE) + { + if (!LandscapeInputs.IsValidIndex(InputIndex)) + return nullptr; + return LandscapeInputs[InputIndex]; + } + + return FHoudiniEngineUtils::FindActorInWorldByLabel(World, ActorName); +} + +bool FHoudiniLandscapeTranslator::GetEditLayersFromOutput(UHoudiniOutput* InOutput, TArray& InEditLayers) +{ + if (!InOutput) + return false; + + InEditLayers.Empty(); + bool bFoundEditLayers = false; + const TArray& GeoObjects = InOutput->GetHoudiniGeoPartObjects(); + for (const FHoudiniGeoPartObject& PartObj : GeoObjects) + { + if (PartObj.Type != EHoudiniPartType::Volume) + continue; + + if (!PartObj.bHasEditLayers) + continue; + + if (PartObj.VolumeLayerName.IsEmpty()) + continue; + + if (!InEditLayers.Contains(PartObj.VolumeLayerName)) + { + InEditLayers.Add(PartObj.VolumeLayerName); + bFoundEditLayers = true; + } + } + + return bFoundEditLayers; +} + +void FHoudiniLandscapeTranslator::GetLandscapePaintLayers(ALandscape* Landscape, TMap& OutEditLayers) +{ + check(Landscape); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::GetLandscapePaintLayers] Collecting paint layers...")); + + if (Landscape->HasLayersContent()) + { + const int32 NumTargetLayers = Landscape->EditorLayerSettings.Num(); + for(int32 LayerIndex = 0; LayerIndex < NumTargetLayers; LayerIndex++) + { + FLandscapeEditorLayerSettings& Settings = Landscape->EditorLayerSettings[LayerIndex]; + if (!Settings.LayerInfoObj) + continue; + OutEditLayers.Add(Settings.LayerInfoObj->LayerName, LayerIndex); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::GetLandscapePaintLayers] Found existing landscape material layer: %s"), *(Settings.LayerInfoObj->LayerName.ToString())); + } + } + else + { + ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); + + const int32 NumLayers = LandscapeInfo->Layers.Num(); + for(int32 LayerIndex = 0; LayerIndex < NumLayers; LayerIndex++) + { + FLandscapeInfoLayerSettings& LayerSettings = LandscapeInfo->Layers[LayerIndex]; + // FLandscapeLayer* Layer = Landscape->GetLayer(LayerIndex); + OutEditLayers.Add(LayerSettings.LayerName, LayerIndex); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::GetLandscapePaintLayers] Found existing landscape material layer: %s"), *(LayerSettings.LayerName.ToString())); + } + } +} + + ALandscapeProxy* FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( UWorld* InWorld, @@ -1157,7 +2485,7 @@ FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( if (!OutActor) continue; - // If we were updating the input landscape before, but arent anymore, + // If we were updating the input landscape before, but aren't anymore, // we could still find it here in the output, ignore them now as we're only looking for previous output if (ValidLandscapes.Contains(OutActor)) continue; @@ -1166,7 +2494,7 @@ FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( // This is not the droid we're looking for continue; - if (OutActor->IsPendingKill()) + if (!IsValid(OutActor)) { FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + "_pendingkill"); continue; @@ -1204,9 +2532,12 @@ FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( void FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput( UHoudiniOutput* InOutput, + TArray &StaleOutputObjects, + TSet& OutActiveLandscapes, const TArray& InAllInputLandscapes, const TMap& OutputAttributes, const TMap& OutputTokens, + const FName& EditLayerName, ALandscape* SharedLandscapeActor, USceneComponent* SharedLandscapeActorParent, bool bCreatedMainLandscape, @@ -1215,9 +2546,9 @@ FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput( const EPackageMode InPackageMode) { if (InPackageMode == EPackageMode::Bake) - return SetLandscapeActorAsOutput_Bake(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); + return SetLandscapeActorAsOutput_Bake(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, EditLayerName, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); else - return SetLandscapeActorAsOutput_Temp(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); + return SetLandscapeActorAsOutput_Temp(InOutput, StaleOutputObjects, OutActiveLandscapes, InAllInputLandscapes, OutputAttributes, OutputTokens, EditLayerName, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); } void @@ -1226,6 +2557,7 @@ FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Bake( const TArray& InAllInputLandscapes, const TMap& OutputAttributes, const TMap& OutputTokens, + const FName& EditLayerName, ALandscape* SharedLandscapeActor, USceneComponent* SharedLandscapeActorParent, bool bCreatedMainLandscape, @@ -1239,9 +2571,12 @@ FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Bake( void FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp( UHoudiniOutput* InOutput, + TArray &StaleOutputObjects, + TSet& OutActiveLandscapes, const TArray& InAllInputLandscapes, const TMap& OutputAttributes, const TMap& OutputTokens, + const FName& EditLayerName, ALandscape* SharedLandscapeActor, USceneComponent* SharedLandscapeActorParent, bool bCreatedSharedLandscape, @@ -1256,64 +2591,76 @@ FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp( AttachActorToHAC(InOutput, SharedLandscapeActor); } - // TODO: The OutputObject cleanup being performed here should really be part of - // the output object itself (or at the very least be encapsulated in a reusable - // static function somewhere) so that individual output objects can be cleaned - // when necessary without having to duplicate code when its needed. - - // Cleanup any stale output objects + // Since this function will be called multiple times for the same Output (but different output objects) + // we can't perform any stale object cleanup here since we don't what the other calls are going to generate. + // Instead, stale output tracking and cleanup is by caller of the OutputLandscape_* functions. All we're doing + // here is removing objects from the StaleObjects array that we know for a fact is not stale. + TMap& Outputs = InOutput->GetOutputObjects(); - TArray StaleOutputs; + for (auto& Elem : Outputs) { UHoudiniLandscapePtr* LandscapePtr = nullptr; - bool bIsStale = false; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp] Checking output: %s"), *Elem.Key.PartName); if (!(Elem.Key == Identifier)) { - // Identifiers doesn't match so this is definitely a stale output. - StaleOutputs.Add(Elem.Key); - bIsStale = true; + // Output identifiers doesn't match so we can't remove this from the stale objects. + continue; } - LandscapePtr = Cast(Elem.Value.OutputObject); - if (LandscapePtr) - { - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp] Removing from stale outputs ... ")); + // Identifiers for output object match. Not stale. + StaleOutputObjects.Remove(Elem.Key); + } - if (LandscapeProxy) + // Send a landscape pointer back to the Output Object for this landscape tile. + FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(Identifier); + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObj.OutputObject); + + if (LandscapePtr) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp] LandscapePtr: %s"), *LandscapePtr->GetFullName()); + // If we have a Landscape ptr, ensure the landscape actors match. Mismatching actors should be destroyed. + ALandscapeProxy* Proxy = LandscapePtr->GetRawPtr(); + if (Proxy) + { + if (Proxy != LandscapeActor) { - // We shouldn't destroy any input landscape, - // or the landscape that we are currently trying to set as output.. - if (!InAllInputLandscapes.Contains(LandscapeProxy) && (LandscapeProxy != LandscapeActor)) - { - // This landscape proxy either doesn't match the landscape identifier - // or it doesn't match the actor we're about to output. Either way, - // get rid of it. - LandscapeProxy->Destroy(); - } + Proxy->Destroy(); + LandscapePtr->GetSoftPtr().Reset(); } } - - if (bIsStale) - { - Elem.Value.OutputObject = nullptr; - } } - - for (FHoudiniOutputObjectIdentifier& StaleOutput : StaleOutputs) + + if (!IsValid(LandscapePtr)) { - Outputs.Remove(StaleOutput); + // We don't have a valid landscape ptr. Create a new one. + LandscapePtr = NewObject(InOutput); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp] Creating new UHoudiniLandscapePtr for identifier: %s"), *Identifier.PartName); } - - - // Send a landscape pointer back to the Output Object for this landscape tile. - FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(Identifier); - UHoudiniLandscapePtr* LandscapePtr = NewObject(InOutput); + + check(LandscapePtr); + + // Update the Landscape Ptr. LandscapePtr->SetSoftPtr(LandscapeActor); + LandscapePtr->EditLayerName = EditLayerName; + OutputObj.OutputObject = LandscapePtr; OutputObj.CachedAttributes = OutputAttributes; OutputObj.CachedTokens = OutputTokens; + + // Be sure to track the landscapes that we are using for this tile/layer to ensure + // that they don't get deleted when stale outputs are being processed higher up the call stack. + if (LandscapeActor) + { + OutActiveLandscapes.Add(LandscapeActor); + } + + if (SharedLandscapeActor) + { + OutActiveLandscapes.Add(SharedLandscapeActor); + } } @@ -1323,12 +2670,14 @@ FHoudiniLandscapeTranslator::AttachActorToHAC(UHoudiniOutput* InOutput, AActor* UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); if (IsValid(HAC)) { - InActor->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); + InActor->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); + return true; } return false; } + FString FHoudiniLandscapeTranslator::GetActorNameSuffix(const EPackageMode& InPackageMode) { @@ -1359,7 +2708,10 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( float FloatMin, float FloatMax, TArray< uint16 >& IntHeightData, FTransform& LandscapeTransform, - const bool& NoResize) + const bool NoResize, + const bool bOverrideZScale, + const float CustomZScale, + const bool bIsAdditive) { IntHeightData.Empty(); LandscapeTransform.SetIdentity(); @@ -1386,6 +2738,9 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( FTransform CurrentVolumeTransform = HeightfieldVolumeInfo.Transform; + // Zero value for landscape texture data + const uint16 LandscapeZeroValue = LandscapeDataAccess::GetTexHeight(0.f); + // The ZRange in Houdini (in m) double MeterZRange = (double)(FloatMax - FloatMin); @@ -1396,9 +2751,14 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseFullResolution) DigitZRange = dUINT16_MAX - 1.0; - + // If we are not using the full range, we need to center the digit values so the terrain can be edited up and down - double DigitCenterOffset = FMath::FloorToDouble((dUINT16_MAX - DigitZRange) / 2.0); + double DigitCenterOffset = 0; + + if (!bIsAdditive) + { + DigitCenterOffset = FMath::FloorToDouble((dUINT16_MAX - DigitZRange) / 2.0); + } // The factor used to convert from Houdini's ZRange to the desired digit range double ZSpacing = (MeterZRange != 0.0) ? (DigitZRange / MeterZRange) : 0.0; @@ -1409,6 +2769,8 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; + bUseDefaultUE4Scaling |= bOverrideZScale; + if (bUseDefaultUE4Scaling) { //Check that our values are compatible with UE4's default scale values @@ -1424,20 +2786,34 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( DigitZRange = dUINT16_MAX - 1.0; DigitCenterOffset = 0; - // Default unreal landscape scaling is -256m:256m at Scale = 100 - // We need to apply the scale back to - FloatMin = -256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f; - FloatMax = 256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f; + // Default unreal landscape scaling is -256m:256m at Scale = 100, + // We need to apply the scale back, and swap Y/Z axis + FloatMin = -256.0f * CurrentVolumeTransform.GetScale3D().Y * 2.0f * CustomZScale/100.f; + FloatMax = 256.0f * CurrentVolumeTransform.GetScale3D().Y * 2.0f * CustomZScale/100.f; + MeterZRange = (double)(FloatMax - FloatMin); ZSpacing = ((double)DigitZRange) / MeterZRange; } + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] bIsAdditive: %d"), bIsAdditive); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] CustomZScale: %f"), CustomZScale); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] DigitCenterOffset: %f"), DigitZRange); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] DigitZRange: %f"), DigitZRange); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] MeterZRange: %f"), MeterZRange); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] ZSpacing: %f"), ZSpacing); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] Volume YScale: %f"), CurrentVolumeTransform.GetScale3D().Y); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] Float Range: %f - %f"), FloatMin, FloatMax); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] Zero Value: %d"), LandscapeZeroValue); + // Converting the data from Houdini to Unreal // For correct orientation in unreal, the point matrix has to be transposed. IntHeightData.SetNumUninitialized(SizeInPoints); - + int32 nUnreal = 0; + bool bValueClippedMin = false; + bool bValueClippedMax = false; + for (int32 nY = 0; nY < HoudiniYSize; nY++) { for (int32 nX = 0; nX < HoudiniXSize; nX++) @@ -1446,14 +2822,40 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( int32 nHoudini = nY + nX * HoudiniYSize; // Get the double values in [0 - ZRange] - double DoubleValue = (double)HeightfieldFloatValues[nHoudini] - (double)FloatMin; + double DoubleValue = (double)HeightfieldFloatValues[nHoudini]; + + // NOTE: Additive (edit) layers should not have their values offset, but they + // should be scaled using the same zspacing values as the base layer on the target landscape. + if (!bIsAdditive) + { + DoubleValue -= (double)FloatMin; + } // Then convert it to [0 - DesiredRange] and center it DoubleValue = DoubleValue * ZSpacing + DigitCenterOffset; - IntHeightData[nUnreal++] = FMath::RoundToInt(DoubleValue); + + if (bIsAdditive) + { + DoubleValue += LandscapeZeroValue; + } + bValueClippedMin = bValueClippedMin || (DoubleValue < 0); + bValueClippedMax = bValueClippedMax || (DoubleValue >= DigitZRange); + + const int32 IntValue = FMath::RoundToInt(DoubleValue); + IntHeightData[nUnreal++] = FMath::Clamp(IntValue, 0, FMath::RoundToInt(DigitZRange)); } } + if (bValueClippedMin) + { + HOUDINI_LOG_WARNING(TEXT("Heightfield values exceeded minimum range. Consider lowering `unreal_landscape_layer_min`.")); + } + + if (bValueClippedMax) + { + HOUDINI_LOG_WARNING(TEXT("Heightfield values exceeded range. Consider increasing `unreal_landscape_layer_max`.")); + } + //-------------------------------------------------------------------------------------------------- // 2. Resample / Pad the int data so that if fits unreal size requirements //-------------------------------------------------------------------------------------------------- @@ -1481,14 +2883,18 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( FVector LandscapeScale; // Unreal has a X/Y resolution of 1m per point while Houdini is dependant on the heighfield's grid spacing + // Swap Y/Z axis from H to UE LandscapeScale.X = CurrentVolumeTransform.GetScale3D().X * 2.0f; - LandscapeScale.Y = CurrentVolumeTransform.GetScale3D().Y * 2.0f; + LandscapeScale.Y = CurrentVolumeTransform.GetScale3D().Z * 2.0f; // Calculating the Z Scale so that the Z values in Unreal are the same as in Houdini // Unreal has a default Z range is 512m for a scale of a 100% LandscapeScale.Z = (float)((double)(dUINT16_MAX / DigitZRange) * MeterZRange / 512.0); if (bUseDefaultUE4Scaling) - LandscapeScale.Z = CurrentVolumeTransform.GetScale3D().Z * 2.0f; + { + // Swap Y/Z axis from H to UE + LandscapeScale.Z = CurrentVolumeTransform.GetScale3D().Y * 2.0f; + } LandscapeScale *= 100.f; // If the data was resized and not expanded, we need to modify the landscape's scale @@ -1512,9 +2918,14 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( // ( float and int32 are used for this because 0 might be out of the landscape Z range! // when using the full range, this would cause an overflow for a uint16!! ) float HoudiniZeroValueInDigit = (float)FMath::RoundToInt((0.0 - (double)FloatMin) * ZSpacing + DigitCenterOffset); - float ZOffset = -(HoudiniZeroValueInDigit - 32768.0f) / 128.0f * LandscapeScale.Z; + float ZOffset = -(HoudiniZeroValueInDigit - LandscapeZeroValue ) / 128.0f * LandscapeScale.Z; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] Houdini Zero Digit Value: %f"), HoudiniZeroValueInDigit); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] Z Offset: %f"), ZOffset); + LandscapePosition.Z += ZOffset; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] LandscapePosition.Z: %f"), LandscapePosition.Z); // If we have padded the data when resizing the landscape, we need to offset the position because of // the added values on the topLeft Corner of the Landscape @@ -1716,7 +3127,7 @@ FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( // Notify the user that the data was padded HOUDINI_LOG_WARNING( - TEXT("Landscape data was padded from ( %d x %d ) to ( %d x %d )."), + TEXT("Landscape data had to be padded from ( %d x %d ) to ( %d x %d )."), SizeX, SizeY, NewSizeX, NewSizeY); } else @@ -1732,7 +3143,7 @@ FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( // Notify the user if the heightfield data was resized HOUDINI_LOG_WARNING( - TEXT("Landscape data was resized from ( %d x %d ) to ( %d x %d )."), + TEXT("Landscape data had to be resized from ( %d x %d ) to ( %d x %d )."), SizeX, SizeY, NewSizeX, NewSizeY); } @@ -1865,9 +3276,12 @@ FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( } const FHoudiniGeoPartObject* -FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput(UHoudiniOutput* InOutput) +FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput( + UHoudiniOutput* InOutput, + const bool bMatchEditLayer, + const FName& EditLayerName) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return nullptr; if (InOutput->GetHoudiniGeoPartObjects().Num() < 1) @@ -1882,6 +3296,15 @@ FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput(UHoudiniOutput* InO if (!CurVolumeInfo.Name.Contains("height")) continue; + if (bMatchEditLayer) + { + if (!HGPO.bHasEditLayers) + continue; + FName LayerName(HGPO.VolumeLayerName); + if (!LayerName.IsEqual(EditLayerName)) + continue; + } + // We're only handling single values for now if (CurVolumeInfo.TupleSize != 1) { @@ -1910,7 +3333,12 @@ FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput(UHoudiniOutput* InO } void -FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& Heightfield, TArray< const FHoudiniGeoPartObject* >& FoundLayers) +FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput( + const UHoudiniOutput* InOutput, + const FHoudiniGeoPartObject& Heightfield, + const bool bMatchEditLayer, + const FName& EditLayerName, + TArray< const FHoudiniGeoPartObject* >& FoundLayers) { FoundLayers.Empty(); @@ -1926,7 +3354,8 @@ FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutpu TArray< int32 > TileValues; FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HeightFieldNodeId, Heightfield.PartId, "tile", AttribInfoTile, TileValues); + HeightFieldNodeId, Heightfield.PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, + AttribInfoTile, TileValues, 0, HAPI_ATTROWNER_INVALID, 0, 1); if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) { @@ -1943,6 +3372,16 @@ FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutpu if (NodeId == -1 || NodeId != HeightFieldNodeId) continue; + if (bMatchEditLayer) + { + // If this layer does not match the incoming edit layer, ignore it. + FString CurrentLayerName; + FHoudiniEngineUtils::GetEditLayerName(HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, CurrentLayerName); + const FName CurrentLayerFName(CurrentLayerName); + if (!CurrentLayerFName.IsEqual(EditLayerName)) + continue; + } + if (bParentHeightfieldHasTile) { int32 CurrentTile = -1; @@ -1952,8 +3391,8 @@ FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutpu TArray TileValues; FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "tile", AttribInfoTile, TileValues); - + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, + AttribInfoTile, TileValues, 0, HAPI_ATTROWNER_INVALID, 0, 1); if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) { @@ -1996,17 +3435,11 @@ FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutpu } } -bool -FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(const FHoudiniGeoPartObject* HGPO, TArray &OutFloatArr, float &OutFloatMin, float &OutFloatMax) +bool FHoudiniLandscapeTranslator::GetHoudiniHeightfieldVolumeInfo(const FHoudiniGeoPartObject* HGPO, HAPI_VolumeInfo& VolumeInfo) { - OutFloatArr.Empty(); - OutFloatMin = 0.f; - OutFloatMax = 0.f; - if (HGPO->Type != EHoudiniPartType::Volume) return false; - HAPI_VolumeInfo VolumeInfo; FHoudiniApi::VolumeInfo_Init(&VolumeInfo); HAPI_Result Result = FHoudiniApi::GetVolumeInfo( @@ -2027,6 +3460,20 @@ FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(const FHoudiniGeoPar if ((VolumeInfo.xLength < 2) || (VolumeInfo.yLength < 2)) return false; + + return true; +} + +bool +FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(const FHoudiniGeoPartObject* HGPO, TArray &OutFloatArr, float &OutFloatMin, float &OutFloatMax) +{ + OutFloatArr.Empty(); + OutFloatMin = 0.f; + OutFloatMax = 0.f; + + HAPI_VolumeInfo VolumeInfo; + if (!GetHoudiniHeightfieldVolumeInfo(HGPO, VolumeInfo)) + return false; const int32 SizeInPoints = VolumeInfo.xLength * VolumeInfo.yLength; @@ -2091,6 +3538,12 @@ FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(const FHoudiniGeoPart bool FHoudiniLandscapeTranslator::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& LayerGeoPartObject) { + if (LayerGeoPartObject.VolumeName.Equals(HAPI_UNREAL_VISIBILITY_LAYER_NAME, ESearchCase::IgnoreCase)) + { + // Visibility layers should always be treated as Unit layers. + return true; + } + // Check the attribute exists on primitive or detail HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_PRIM)) @@ -2105,7 +3558,8 @@ FHoudiniLandscapeTranslator::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& L FHoudiniApi::AttributeInfo_Init(&AttribInfoUnitLayer); TArray< int32 > AttribValues; FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, AttribInfoUnitLayer, AttribValues, 1, Owner); + LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, + AttribInfoUnitLayer, AttribValues, 1, Owner, 0, 1); if (AttribValues.Num() > 0 && AttribValues[0] == 1) return true; @@ -2114,7 +3568,7 @@ FHoudiniLandscapeTranslator::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& L } bool -FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( +FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayerData( const TArray& FoundLayers, const FHoudiniGeoPartObject& Heightfield, const int32& LandscapeXSize, const int32& LandscapeYSize, @@ -2122,8 +3576,9 @@ FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( const TMap& GlobalMaximums, TArray& OutLayerInfos, bool bIsUpdate, + bool bDefaultNoWeightBlending, const FHoudiniPackageParams& InTilePackageParams, - const FHoudiniPackageParams& InLayerPackageParams, + const FHoudiniPackageParams& InLayerPackageParams, TArray& OutCreatedPackages ) { @@ -2157,43 +3612,43 @@ FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( if (bIsUpdate && !LayerGeoPartObject->bHasGeoChanged) { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers]: Skipping geo part (no changes): %s, %s, %d, %d"), *(LayerGeoPartObject->VolumeName), *(LayerGeoPartObject->VolumeLayerName), LayerGeoPartObject->GeoId, LayerGeoPartObject->PartId); continue; } TArray FloatLayerData; float LayerMin = 0; float LayerMax = 0; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers]: Retrieving heightfield float data for geo part: %s, %s, %d, %d"), *(LayerGeoPartObject->VolumeName), *(LayerGeoPartObject->VolumeLayerName), LayerGeoPartObject->GeoId, LayerGeoPartObject->PartId); if (!FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(LayerGeoPartObject, FloatLayerData, LayerMin, LayerMax)) continue; - // No need to create flat layers as Unreal will remove them afterwards.. - if (LayerMin == LayerMax) - continue; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers]: Layer Min/Max: %f, %f"), LayerMin, LayerMax); + + // NOTE: We don't want to just skip the processing of flat layers here because it is completely valid for layers to be flat but have non-zero values. + // Even if UE culls a flat (zero) layer, it is still important for any existing layer content to be properly cleared -- if we just skip processing on this + // layer we could end up with tiles containing stale data. + // Also, we do not want to simply *delete* this layer because we may be drawing zero values to a subregion on a landscape (we may not be looking at the data + // for the whole landscape layer here!!) so we cannot make any assumptions at this. Process the layer normally even if it only contains zero values. + + // // No need to create flat layers as Unreal will remove them afterwards.. + // if (LayerMin == LayerMax) + // { + // HOUDINI_LOG_WARNING(TEXT("[FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers] Ignoring flat heightfield: %s, %s (Min: %f, Max: %f)"), *(LayerGeoPartObject->VolumeName), *(LayerGeoPartObject->VolumeLayerName), LayerMin, LayerMax); + // continue; + // } const FHoudiniVolumeInfo& LayerVolumeInfo = LayerGeoPartObject->VolumeInfo; // Get the layer's name FString LayerName = LayerVolumeInfo.Name; const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerName); + const FName SanitizedLayerFName = FName(SanitizedLayerName); TilePackageParams.ObjectName = InTilePackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; LayerPackageParams.ObjectName = InLayerPackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; - if (bExportTexture) - { - // Create a raw texture export of the layer on this tile - FString TextureName = TilePackageParams.ObjectName + "_raw"; - FHoudiniLandscapeTranslator::CreateUnrealTexture( - TilePackageParams, - TextureName, - LayerVolumeInfo.YLength, // Y and X inverted?? why? - LayerVolumeInfo.XLength, - FloatLayerData, - LayerMin, - LayerMax); - } - - // Check if that landscape layer has been marked as unit (range in [0-1] + // Check if that landscape layer has been marked as unit (range in [0-1]) if (IsUnitLandscapeLayer(*LayerGeoPartObject)) { LayerMin = 0.0f; @@ -2208,7 +3663,21 @@ FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( if (GlobalMinimums.Contains(LayerName)) LayerMin = GlobalMinimums[LayerName]; } - + + if (bExportTexture) + { + // Create a raw texture export of the layer on this tile + FString TextureName = TilePackageParams.ObjectName + "_raw"; + FHoudiniLandscapeTranslator::CreateUnrealTexture( + TilePackageParams, + TextureName, + LayerVolumeInfo.YLength, // Y and X inverted?? why? + LayerVolumeInfo.XLength, + FloatLayerData, + LayerMin, + LayerMax); + } + // Get the layer package path // FString LayerNameString = FString::Printf(TEXT("%s_%d"), LayerString.GetCharArray().GetData(), (int32)LayerGeoPartObject->PartId); // LayerNameString = UPackageTools::SanitizePackageName(LayerNameString); @@ -2219,20 +3688,34 @@ FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( // Creating the ImportLayerInfo and LayerInfo objects FLandscapeImportLayerInfo ImportLayerInfo(*LayerName); + // See if the user has assigned a layer info object via attribute UPackage * Package = nullptr; ULandscapeLayerInfoObject* LayerInfo = GetLandscapeLayerInfoForLayer(*LayerGeoPartObject, *LayerName); - if (!LayerInfo || LayerInfo->IsPendingKill()) + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[CreateOrUpdateLandscapeLayers] GetLandscapeLayerInfoForLayer. LayerName: %s."), *(LayerName)); + if (!IsValid(LayerInfo)) { // No assignment, try to find or create a landscape layer info object for that layer + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[CreateOrUpdateLandscapeLayers] No layer info. FindOrCreate layer info object...")); LayerInfo = FindOrCreateLandscapeLayerInfoObject(LayerName, LayerPackageParams.GetPackagePath(), LayerPackageParams.GetPackageName(), Package); } - if (!LayerInfo || LayerInfo->IsPendingKill()) + if (!IsValid(LayerInfo)) { continue; } + // Visibility are by default non weight blended + if (NonWeightBlendedLayerNames.Contains(LayerName) || SanitizedLayerFName.IsEqual(HAPI_UNREAL_VISIBILITY_LAYER_NAME)) + { + LayerInfo->bNoWeightBlend = true; + } + else + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[CreateOrUpdateLandscapeLayers] Setting bNoWeightBlend to %d on layer %s"), bDefaultNoWeightBlending, *(LayerInfo->LayerName.ToString())); + LayerInfo->bNoWeightBlend = bDefaultNoWeightBlending; + } + // Convert the float data to uint8 // HF masks need their X/Y sizes swapped if (!FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( @@ -2250,13 +3733,9 @@ FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( LayerInfo->LayerUsageDebugColor.B = (LayerMax - LayerMin) / 255.0f; LayerInfo->LayerUsageDebugColor.A = PI; - // Visibility are by default non weight blended - if (NonWeightBlendedLayerNames.Contains(LayerName) || LayerName.Equals(TEXT("visibility"), ESearchCase::IgnoreCase)) - LayerInfo->bNoWeightBlend = true; - else - LayerInfo->bNoWeightBlend = false; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers] Processing layer: %s (bDefaultNoWeightBlend: %d"), *(LayerName), bDefaultNoWeightBlending); - if (!bIsUpdate && Package && !Package->IsPendingKill()) + if (!bIsUpdate && IsValid(Package)) { // Mark the package dirty... Package->MarkPackageDirty(); @@ -2282,7 +3761,7 @@ FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( // See if there is a physical material assigned via attribute for that landscape layer UPhysicalMaterial* PhysMaterial = FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(*LayerGeoPartObject); - if (PhysMaterial && !PhysMaterial->IsPendingKill()) + if (IsValid(PhysMaterial)) { LayerInfo->PhysMaterial = PhysMaterial; } @@ -2350,8 +3829,9 @@ FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax( // If this volume has an attribute defining a minimum value use it as is. FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurrentHeightfield.GeoId, CurrentHeightfield.PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, + AttributeInfo, FloatData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) { if (FloatData.Num() > 0) { @@ -2362,8 +3842,9 @@ FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax( // If this volume has an attribute defining maximum value use it as is. FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurrentHeightfield.GeoId, CurrentHeightfield.PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, + AttributeInfo, FloatData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) { if (FloatData.Num() > 0) { @@ -2464,8 +3945,9 @@ FHoudiniLandscapeTranslator::GetLayersZMinZMax( { // Extract min value FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurrentHeightfield.GeoId, CurrentHeightfield.PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, + AttributeInfo, FloatData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) { if (FloatData.Num() > 0) { @@ -2491,8 +3973,9 @@ FHoudiniLandscapeTranslator::GetLayersZMinZMax( { // Extract max value FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurrentHeightfield.GeoId, CurrentHeightfield.PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, + AttributeInfo, FloatData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) { if (FloatData.Num() > 0) { @@ -2604,19 +4087,23 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( const TArray< uint16 >& IntHeightData, const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, const FTransform& TileTransform, + const FIntPoint& TileLocation, const int32& XSize, const int32& YSize, const int32& NumSectionPerLandscapeComponent, const int32& NumQuadsPerLandscapeSection, UMaterialInterface* LandscapeMaterial, UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* LandscapePhsyicalMaterial, + UPhysicalMaterial* LandscapePhysicalMaterial, const FString& LandscapeTileActorName, LandscapeActorType ActorType, - ALandscape* SharedLandscapeActor, + ALandscape* SharedLandscapeActor, UWorld* InWorld, ULevel* InLevel, - FHoudiniPackageParams InPackageParams) + FHoudiniPackageParams InPackageParams, + bool bHasEditLayers, + const FName& EditLayerName, + const FName& AfterLayerName) { if (!IsValid(InWorld)) return nullptr; @@ -2638,16 +4125,23 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; ALandscape* CachedLandscapeActor = nullptr; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Creating tile with layer: %s"), *(EditLayerName.ToString())); UWorld* NewWorld = nullptr; FString MapFileName; bool bBroadcastMaterialUpdate = false; + bool bRenameDefaultEditLayer = false; //... Create landscape tile ...// { // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos if (ActorType == LandscapeActorType::LandscapeStreamingProxy) { - CachedStreamingProxyActor = InWorld->SpawnActor(); + // Set the level to spawn in to InLevel + FActorSpawnParameters SpawnParameters; + if (IsValid(InLevel)) + SpawnParameters.OverrideLevel = InLevel; + CachedStreamingProxyActor = InWorld->SpawnActor(SpawnParameters); if (CachedStreamingProxyActor) { check(SharedLandscapeActor); @@ -2669,7 +4163,11 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( else { // Create a normal landscape actor - CachedLandscapeActor = InWorld->SpawnActor(); + // Set the level to spawn in to InLevel + FActorSpawnParameters SpawnParameters; + if (IsValid(InLevel)) + SpawnParameters.OverrideLevel = InLevel; + CachedLandscapeActor = InWorld->SpawnActor(SpawnParameters); if (CachedLandscapeActor) { CachedLandscapeActor->PreEditChange(nullptr); @@ -2677,8 +4175,10 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( CachedLandscapeActor->LandscapeMaterial = LandscapeMaterial; CachedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; CachedLandscapeActor->bCastStaticShadow = false; + CachedLandscapeActor->bCanHaveLayersContent = bHasEditLayers; bBroadcastMaterialUpdate = true; LandscapeTile = CachedLandscapeActor; + bRenameDefaultEditLayer = true; } } } @@ -2687,14 +4187,21 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( if (!LandscapeTile) return nullptr; - // At this point we either has a ALandscapeStreamingProxy or ALandscape actor for the landscape tile. + CachedLandscapeActor = LandscapeTile->GetLandscapeActor(); - // Autosaving the layers prevents them for being deleted with the Asset - // Save the packages created for the LayerInfos - //if ( CreatedLayerInfoPackage.Num() > 0 ) - // FEditorFileUtils::PromptForCheckoutAndSave( CreatedLayerInfoPackage, true, false ); + check(CachedLandscapeActor); + CachedLandscapeActor->bCanHaveLayersContent = bHasEditLayers; + + // Only import non-visibility layers. Visibility will be handled explicitly. + TArray CustomImportLayerInfos; + for (const FLandscapeImportLayerInfo& LayerInfo : ImportLayerInfos) + { + if (LayerInfo.LayerName.IsEqual(HAPI_UNREAL_VISIBILITY_LAYER_NAME)) + continue; + CustomImportLayerInfos.Add(LayerInfo); + } - // Import the landscape data + // At this point we either has a ALandscapeStreamingProxy or ALandscape actor for the landscape tile. // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue LandscapeTile->bCastStaticShadow = false; @@ -2709,21 +4216,14 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( TMap> HeightmapDataPerLayers; TMap> MaterialLayerDataPerLayer; HeightmapDataPerLayers.Add(FGuid(), IntHeightData); - MaterialLayerDataPerLayer.Add(FGuid(), ImportLayerInfos); - - FTransform NewTileTransform, LandscapeOffset; - FIntPoint TileLoc; - - // NOTE: The following Import call will reregister all components, causing the actor to lose its transform. - // So we'll be importing the tile data as if the actor was located at the origin and fix up transforms afterward. - CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeOffset, NewTileTransform, TileLoc); + TArray& MaterialImportLayerInfos = MaterialLayerDataPerLayer.Add(FGuid(), CustomImportLayerInfos); // Before we import new tile data, remove previous tile data that will be overlapped by the new tile. TSet OverlappingComponents; - const int32 DestMinX = TileLoc.X; - const int32 DestMinY = TileLoc.Y; - const int32 DestMaxX = TileLoc.X + XSize - 1; - const int32 DestMaxY = TileLoc.Y + YSize - 1; + const int32 DestMinX = TileLocation.X; + const int32 DestMinY = TileLocation.Y; + const int32 DestMaxX = TileLocation.X + XSize - 1; + const int32 DestMaxY = TileLocation.Y + YSize - 1; ULandscapeInfo* LandscapeInfo = LandscapeTile->GetLandscapeInfo(); @@ -2753,30 +4253,43 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); } + ALandscape* TargetLandscape = LandscapeTile->GetLandscapeActor(); + check(TargetLandscape); + + if (bHasEditLayers) + { + int32 CurrentLayerIndex = FindOrCreateEditLayer(TargetLandscape, EditLayerName, AfterLayerName); + SetActiveLandscapeLayer(TargetLandscape, CurrentLayerIndex); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Setting active landscape layer: %d"), CurrentLayerIndex); + } + // Import tile data // The Import function will correctly compute the tile section locations. No need to set it explicitly. // TODO: Verify this with world composition!! bool bIsStandalone = LandscapeTile->GetLandscapeActor() == LandscapeTile; - // We set the actor transform and absolute section base before importing heighfield data. This allows us to + // We set the actor transform and absolute section base before importing heightfield data. This allows us to // use the correct (quad-space) blitting region without causing overlaps during import. // NOTE: The following SetAbsoluteSectionBase() calls are very important. This tells the Landscape system - // where on the landscape, in quad space, a specific tile is located. This influences is used by various + // where on the landscape, in quad space, a specific tile is located. This is used by various // mechanisms and tools that tie into the Landscape system such as World Composition and Landscape editor tools. // If the Section Offset for a landscape tile is wrong, it will appear in the wrong place in the World Composition // viewer. Worse than that, none of Landscape Editor tools will work properly because it won't be able to - // locate the correct Landscape component when calculating the "Landscape Component Key" for the given word position. - // HeightmapAccessor and the AlphamapAccessor as well as the FLandscapeEditDataInterface blit functions use the + // locate the correct Landscape component when calculating the "Landscape Component Key" for the given world position. + // HeightmapAccessor and the AlphamapAccessor as well as the FLandscapeEditDataInterface blitting functions use the // same underlying Landscape Component Key calculation to find the correct landscape component to draw to. So if the // Section Offsets are wrong ... all manner of chaos will follow. // It is also super important to call ULandscapeInfo::RecreateLandscapeInfo() after change a LandscapeProxy's - // section offset in order to update the landscape's internal caches otherwise component key calculations - // won't work correctly. - - LandscapeTile->SetActorTransform(TileTransform); - LandscapeTile->SetAbsoluteSectionBase(TileLoc); + // section offset in order to update the landscape's internal caches (more specifically the component keys, which + // are based on the section offsets) otherwise component key calculations won't work correctly. + + LandscapeTile->SetActorRelativeTransform(TileTransform); + LandscapeTile->SetAbsoluteSectionBase(TileLocation); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Importing tile for actor: %s "), *(LandscapeTile->GetPathName())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Dest region: %d, %d -> %d, %d"), DestMinX, DestMinY, DestMaxX, DestMaxY); LandscapeTile->Import( LandscapeTile->GetLandscapeGuid(), @@ -2785,18 +4298,33 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( HeightmapDataPerLayers, NULL, MaterialLayerDataPerLayer, ImportLayerType); - LandscapeTile->RecreateComponentsState(); if (!LandscapeInfo) { LandscapeInfo = LandscapeTile->GetLandscapeInfo(); } + check(LandscapeInfo); + + if (bHasEditLayers && bRenameDefaultEditLayer) + { + // If this is a new landscape tile, rename the default edit layer. + // We have to do this after the Import() since the landscape layers + // only get initialized during that call. + FLandscapeLayer* DefaultEditLayer = TargetLandscape->GetLayer(0); + if (DefaultEditLayer) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Updating default layer name to: %s"), *(EditLayerName.ToString())); + DefaultEditLayer->Name = EditLayerName; + } + } + // We updated the landscape tile section offsets (SetAbsoluteSectionBase) so - // we need to recreate the landscape info for these changes reflect correctly in the ULandscapeInfo, - // and only then are we able to "blit" the new alpha data into the correct place on the landscape. + // we need to recreate the landscape info for these changes reflect correctly in the ULandscapeInfo component keys. + // Only then are we able to "blit" the new alpha data into the correct place on the landscape. ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); + LandscapeTile->RecreateComponentsState(); // TODO: Verify whether we still need to manually set the LandscapeLightingLOD setting or whether // calling PostEditChange() will properly fix the state. @@ -2806,6 +4334,49 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( // < 2048x2048 -> LOD0, >=2048x2048 -> LOD1, >= 4096x4096 -> LOD2, >= 8192x8192 -> LOD3 LandscapeTile->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); + // ---------------------------------------------------- + // Update visibility layer + // ---------------------------------------------------- + + // Update the visibility mask / layer if we have any (TileImport does not update the visibility layer). + { + FGuid LayerGuid; + ALandscape* LandscapeActor = LandscapeTile->GetLandscapeActor(); + + if (bHasEditLayers) + { + const FLandscapeLayer* Layer = LandscapeActor->GetLayer(EditLayerName); + LayerGuid = Layer->Guid; + } + + // If we are dealing with edit layers we need Lock the landscape edit layer, i.e., if the Guid is valid. + FScopedSetLandscapeEditingLayer Scope(LandscapeActor, LayerGuid, [=] { CachedLandscapeActor->RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All); }); + FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); + + for (auto CurLayerInfo : ImportLayerInfos) + { + if (CurLayerInfo.LayerInfo && CurLayerInfo.LayerName.IsEqual(HAPI_UNREAL_VISIBILITY_LAYER_NAME)) + { + FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); + AlphaAccessor.SetData(DestMinX, DestMinY, DestMaxX, DestMaxY, CurLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + else + { + ULandscapeLayerInfoObject* LayerInfo = LandscapeInfo->GetLayerInfoByName(CurLayerInfo.LayerName); + if (!IsValid(LayerInfo)) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("Missing layer with: %s"), *(CurLayerInfo.LayerName.ToString())); + } + else + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("Drawing layer with normalization: %s"), *(CurLayerInfo.LayerName.ToString())); + FAlphamapAccessor AlphaAccessor(LandscapeInfo, LayerInfo); + AlphaAccessor.SetData(DestMinX, DestMinY, DestMaxX, DestMaxY, CurLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + } + } + } + // ---------------------------------------------------- // Rename the actor // ---------------------------------------------------- @@ -2820,59 +4391,100 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( HOUDINI_LOG_WARNING(TEXT("Editor suppressed MarkPackageDirty() for landscape tile: %s"), *(LandscapeTile->GetPathName())); } +#if WITH_EDITOR + GEngine->BroadcastOnActorMoved(LandscapeTile); +#endif + return LandscapeTile; } +void +FHoudiniLandscapeTranslator::DestroyLandscape(ALandscape* Landscape) +{ + if (!IsValid(Landscape)) + return; + + ULandscapeInfo* Info = Landscape->GetLandscapeInfo(); + if (!IsValid(Info)) + return; + + TArray Proxies = Info->Proxies; + for(ALandscapeStreamingProxy* Proxy : Proxies) + { + Info->UnregisterActor(Proxy); + Proxy->Destroy(); + } + Landscape->Destroy(); +} + + void FHoudiniLandscapeTranslator::CalculateTileLocation( int32 NumSectionsPerComponent, int32 NumQuadsPerSection, - const FTransform& TileTransform, - FTransform& OutLandscapeOffset, - FTransform& OutTileTransform, + const FTransform& TileTransformWS, + FHoudiniLandscapeReferenceLocation& RefLoc, + FTransform& OutLandscapeTransform, FIntPoint& OutTileLocation) -{ +{ // IMPORTANT: Tile Location (Section Base) needs to be in multiples of the landscape component's quad size const int32 ComponentSize = NumSectionsPerComponent * NumQuadsPerSection; - OutLandscapeOffset = FTransform(FRotator::ZeroRotator, FVector::ZeroVector, TileTransform.GetScale3D()); - OutTileTransform = TileTransform; + OutLandscapeTransform = FTransform::Identity; + const FTransform TileSR = FTransform(TileTransformWS.GetRotation(), FVector::ZeroVector, TileTransformWS.GetScale3D()); - // Sometimes the calculated tile coordinate falls on a half-unit so we would need to remove that offset first - // before calculating integer (quad space) tile location. - // For example, 123.5, should become 123. -456.5 should become -456. - const FVector TileScale = TileTransform.GetScale3D(); - const float TileCoordX = TileTransform.GetLocation().X / TileScale.X; - const float TileCoordY = TileTransform.GetLocation().Y / TileScale.Y; + const FVector BaseLoc = TileSR.InverseTransformPosition(TileTransformWS.GetLocation()); - float NearestMultipleX = FMath::RoundHalfFromZero(TileCoordX / ComponentSize) * ComponentSize; - float NearestMultipleY = FMath::RoundHalfFromZero(TileCoordY / ComponentSize) * ComponentSize; + const FVector TileScale = TileTransformWS.GetScale3D(); + const float TileLocX = BaseLoc.X; // / TileScale.X; + const float TileLocY = BaseLoc.Y; // / TileScale.Y; - // If the multiples are too close to the middle, offset by 0.5 to avoid some tiles snapping up and others snapping down. - if (FMath::IsNearlyEqual(FMath::Frac(NearestMultipleX), 0.5f, 0.1f)) - { - NearestMultipleX += 0.5f; - } - if (FMath::IsNearlyEqual(FMath::Frac(NearestMultipleY), 0.5f, 0.1f)) + if (!RefLoc.bIsCached) { - NearestMultipleY += 0.5f; - } + // If there is no landscape reference location yet, calculate one now. - const float TileOffsetX = NearestMultipleX - TileCoordX; - const float TileOffsetY = NearestMultipleY - TileCoordY; + // We cache this tile as a reference point for the other landscape tiles so that they can calculate + // section base offsets in a consistent manner, relative to this tile. + const float NearestMultipleX = FMath::RoundHalfFromZero(TileLocX / ComponentSize) * ComponentSize; + const float NearestMultipleY = FMath::RoundHalfFromZero(TileLocY / ComponentSize) * ComponentSize; - OutTileLocation.X = FMath::RoundHalfFromZero(NearestMultipleX); - OutTileLocation.Y = FMath::RoundHalfFromZero(NearestMultipleY); + RefLoc.SectionCoordX = FMath::RoundHalfFromZero(NearestMultipleX); + RefLoc.SectionCoordY = FMath::RoundHalfFromZero(NearestMultipleY); + RefLoc.TileLocationX = TileLocX; + RefLoc.TileLocationY = TileLocY; + } + // Calculate the section coordinate for this tile + const float DeltaLocX = TileLocX - RefLoc.TileLocationX; + const float DeltaLocY = TileLocY - RefLoc.TileLocationY; + + const float DeltaCoordX = FMath::RoundHalfFromZero(DeltaLocX / ComponentSize) * ComponentSize; + const float DeltaCoordY = FMath::RoundHalfFromZero(DeltaLocY / ComponentSize) * ComponentSize; + + OutTileLocation.X = RefLoc.SectionCoordX + DeltaCoordX; + OutTileLocation.Y = RefLoc.SectionCoordY + DeltaCoordY; + // Adjust landscape offset to compensate for tile location / section base shifting. - OutLandscapeOffset.SetLocation( FVector(-TileOffsetX * TileScale.X, -TileOffsetY * TileScale.Y, 0.f) ); + if (!RefLoc.bIsCached) + { + FVector Offset((TileLocX - OutTileLocation.X), (TileLocY - OutTileLocation.Y), BaseLoc.Z); + Offset = TileSR.TransformPosition(Offset); + + RefLoc.MainTransform = TileTransformWS; + RefLoc.MainTransform.SetTranslation(Offset); + // Reference locations are now fully cached. + RefLoc.bIsCached = true; + } + + OutLandscapeTransform = RefLoc.MainTransform; } void FHoudiniLandscapeTranslator::GetLandscapeMaterials( const FHoudiniGeoPartObject& InHeightHGPO, + const FHoudiniPackageParams& InPackageParams, UMaterialInterface*& OutLandscapeMaterial, UMaterialInterface*& OutLandscapeHoleMaterial, UPhysicalMaterial*& OutLandscapePhysicalMaterial) @@ -2895,6 +4507,8 @@ FHoudiniLandscapeTranslator::GetLandscapeMaterials( HAPI_UNREAL_ATTRIB_MATERIAL, AttribMaterials, Materials); + bool bMaterialOverrideNeedsCreateInstance = false; + // If the material attribute was not found, check the material instance attribute. if (!AttribMaterials.exists) { @@ -2903,6 +4517,8 @@ FHoudiniLandscapeTranslator::GetLandscapeMaterials( InHeightHGPO.GeoId, InHeightHGPO.PartId, HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, AttribMaterials, Materials); + + bMaterialOverrideNeedsCreateInstance = true; } // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. @@ -2917,9 +4533,36 @@ FHoudiniLandscapeTranslator::GetLandscapeMaterials( if (AttribMaterials.exists && Materials.Num() > 0) { // Load the material - OutLandscapeMaterial = Cast(StaticLoadObject( - UMaterialInterface::StaticClass(), - nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); + + if (!bMaterialOverrideNeedsCreateInstance) + { + OutLandscapeMaterial = Cast(StaticLoadObject( + UMaterialInterface::StaticClass(), + nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); + } + else + { + TArray MaterialAndTexturePackages; + + // Purposefully empty material since it is satisfied by the override parameter + TMap InputAssignmentMaterials; + TMap OutputAssignmentMaterials; + + if (FHoudiniMaterialTranslator::SortUniqueFaceMaterialOverridesAndCreateMaterialInstances(Materials, InHeightHGPO, InPackageParams, + MaterialAndTexturePackages, + InputAssignmentMaterials, OutputAssignmentMaterials, + false)) + { + TArray Values; + OutputAssignmentMaterials.GenerateValueArray(Values); + if (Values.Num() > 0) + { + OutLandscapeMaterial = Values[0]; + } + } + + + } } } @@ -2980,12 +4623,12 @@ FHoudiniLandscapeTranslator::GetLandscapeComponentExtentAttributes( // Create an AttributeInfo HAPI_AttributeInfo AttributeInfo; FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfo); // Get MinX TArray IntData; if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", + AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) return false; if (IntData.Num() > 0) @@ -2993,7 +4636,8 @@ FHoudiniLandscapeTranslator::GetLandscapeComponentExtentAttributes( // Get MaxX if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_X", + AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) return false; if (IntData.Num() > 0) @@ -3001,7 +4645,8 @@ FHoudiniLandscapeTranslator::GetLandscapeComponentExtentAttributes( // Get MinY if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_Y", + AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) return false; if (IntData.Num() > 0) @@ -3009,7 +4654,8 @@ FHoudiniLandscapeTranslator::GetLandscapeComponentExtentAttributes( // Get MaxX if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_Y", + AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) return false; if (IntData.Num() > 0) @@ -3026,19 +4672,21 @@ FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject(const FString& // See if package exists, if it does, reuse it bool bCreatedPackage = false; OutPackage = FindPackage(nullptr, *PackageFullName); - if (!OutPackage || OutPackage->IsPendingKill()) + if (!IsValid(OutPackage)) { // We need to create a new package - OutPackage = CreatePackage(nullptr, *PackageFullName); + OutPackage = CreatePackage( *PackageFullName); bCreatedPackage = true; } - if (!OutPackage || OutPackage->IsPendingKill()) + if (!IsValid(OutPackage)) return nullptr; if (!OutPackage->IsFullyLoaded()) OutPackage->FullyLoad(); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject] Find LayerInfoObject package: %s"), *(InPackageName)); + ULandscapeLayerInfoObject* LayerInfo = nullptr; if (!bCreatedPackage) { @@ -3046,8 +4694,9 @@ FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject(const FString& LayerInfo = (ULandscapeLayerInfoObject*)StaticFindObjectFast(ULandscapeLayerInfoObject::StaticClass(), OutPackage, FName(*InPackageName)); } - if (!LayerInfo || LayerInfo->IsPendingKill()) + if (!IsValid(LayerInfo)) { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject] Could not find layer info. Creating new layer info package: %s"), *(InPackageName)); // Create a new LandscapeLayerInfoObject in the package LayerInfo = NewObject(OutPackage, FName(*InPackageName), RF_Public | RF_Standalone /*| RF_Transactional*/); @@ -3055,7 +4704,7 @@ FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject(const FString& FAssetRegistryModule::AssetCreated(LayerInfo); } - if (LayerInfo && !LayerInfo->IsPendingKill()) + if (IsValid(LayerInfo)) { LayerInfo->LayerName = FName(*InLayerName); @@ -3177,39 +4826,16 @@ FHoudiniLandscapeTranslator::EnableWorldComposition() return true; } - -bool -FHoudiniLandscapeTranslator::GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, - const int32& InPrimIndex, TArray& OutPropertyAttributes) -{ - // List all the generic property detail attributes ... - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then the primitive property attributes - // Volumes apparently dont have prim attributes because they're converted to pointmeshes somehow... - //FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - // InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InPrimIndex); - - // .. then the point property attributes - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InPrimIndex); - - return FoundCount > 0; -} - - bool FHoudiniLandscapeTranslator::UpdateGenericPropertiesAttributes( UObject* InObject, const TArray& InAllPropertyAttributes) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; // Iterate over the found Property attributes int32 NumSuccess = 0; - for (auto CurrentPropAttribute : InAllPropertyAttributes) + for (const auto& CurrentPropAttribute : InAllPropertyAttributes) { // Update the current Property Attribute if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) @@ -3249,7 +4875,7 @@ FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(const FString& BaseName FName CurrentLayerName = LandscapeInfo->Layers[LayerIndex].GetLayerName(); //ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->GetLayerInfoByName(CurrentLayerName, Landscape); ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; - if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) + if (!IsValid(CurrentLayerInfo)) continue; FString LayerSave = BaseName + CurrentLayerName.ToString() + TEXT(".png"); @@ -3283,7 +4909,7 @@ FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(ALandscapeProxy* Lan for (int LayerIndex = 0; LayerIndex < LandscapeProxy->EditorLayerSettings.Num(); LayerIndex++) { ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeProxy->EditorLayerSettings[LayerIndex].LayerInfoObj; - if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) + if (!IsValid(CurrentLayerInfo)) continue; FString CurrentLayerName = CurrentLayerInfo->LayerName.ToString(); @@ -3413,7 +5039,7 @@ FHoudiniLandscapeTranslator::ImportLandscapeData( else { // We're importing a Landscape layer - if (!LayerInfoObject || LayerInfoObject->IsPendingKill()) + if (!IsValid(LayerInfoObject)) return false; const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); @@ -3537,7 +5163,7 @@ FHoudiniLandscapeTranslator::CreateUnrealTexture( FString CreatedPackageName; UPackage* Package = MyPackageParams.CreatePackageForObject(CreatedPackageName); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return nullptr; // Create new texture object. @@ -3622,9 +5248,8 @@ FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(const FHoudiniGeoPartO TArray AttributeValues; if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InLayerHGPO.GeoId, InLayerHGPO.PartId, - HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL, - AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) + InLayerHGPO.GeoId, InLayerHGPO.PartId, HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL, + AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM, 0, 1)) return nullptr; if (AttributeValues.Num() > 0) @@ -3644,15 +5269,14 @@ FHoudiniLandscapeTranslator::GetLandscapeLayerInfoForLayer(const FHoudiniGeoPart TArray AttributeValues; if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InLayerHGPO.GeoId, InLayerHGPO.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO, - AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) + InLayerHGPO.GeoId, InLayerHGPO.PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO, + AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM, 0, 1)) return nullptr; if (AttributeValues.Num() > 0) { ULandscapeLayerInfoObject* FoundLayerInfo = LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); - if (!FoundLayerInfo || FoundLayerInfo->IsPendingKill()) + if (!IsValid(FoundLayerInfo)) return nullptr; // The layer info's name must match this layer's name or Unreal will not like this! @@ -3668,5 +5292,67 @@ FHoudiniLandscapeTranslator::GetLandscapeLayerInfoForLayer(const FHoudiniGeoPart return nullptr; } +int32 FHoudiniLandscapeTranslator::FindOrCreateEditLayer(ALandscape* Landscape, FName LayerName, FName AfterLayerName) +{ + check(Landscape); + check(Landscape->bCanHaveLayersContent); + + if (LayerName.IsNone()) + { + return INDEX_NONE; + } + + Landscape->SetEditingLayer(FGuid()); // Clear edit layer before adding a new one! + int32 LayerIndex = Landscape->GetLayerIndex(LayerName); + if (LayerIndex == INDEX_NONE) + { + // Create a new edit layer + LayerIndex = Landscape->CreateLayer(LayerName); + } + + if (LayerIndex == INDEX_NONE) + return INDEX_NONE; // Something went wrong. + + if (!AfterLayerName.IsNone()) + { + // If we have an "after layer", move the edit layer into position. + int32 NewLayerIndex = Landscape->GetLayerIndex(AfterLayerName); + if (NewLayerIndex != INDEX_NONE && LayerIndex != NewLayerIndex) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Moving layer from %d to %d"), LayerIndex, NewLayerIndex); + if (NewLayerIndex < LayerIndex) + { + NewLayerIndex += 1; + } + Landscape->ReorderLayer(LayerIndex, NewLayerIndex); + // Ensure we have the correct layer/index + LayerIndex = Landscape->GetLayerIndex(LayerName); + } + } + + return LayerIndex; +} + +bool FHoudiniLandscapeTranslator::SetActiveLandscapeLayer(ALandscape* Landscape, int32 LayerIndex) +{ + if (!Landscape->bCanHaveLayersContent) + return false; + + if (LayerIndex == INDEX_NONE) + { + // If the edit layer could not be found, or created, revert to the 'default' layer. + LayerIndex = 0; + } + + FLandscapeLayer* Layer = Landscape->GetLayer(LayerIndex); + if (Layer) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[SetActiveLandscapeLayer] Set Editing Layer from index: %d"), LayerIndex); + Landscape->SetEditingLayer(Layer->Guid); + return true; + } + + return false; +} diff --git a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h index a86f75859..50858ab2c 100644 --- a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,6 +34,7 @@ #include "EngineUtils.h" #include "HoudiniEngineOutputStats.h" #include "HoudiniPackageParams.h" +#include "HoudiniTranslatorTypes.h" class UHoudiniAssetComponent; class ULandscapeLayerInfoObject; @@ -48,7 +49,7 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator LandscapeActor = 0, LandscapeStreamingProxy = 1, }; - + static bool CreateLandscape( UHoudiniOutput* InOutput, TArray>& CreatedUntrackedActors, @@ -59,7 +60,73 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator UWorld* World, const TMap& LayerMinimums, const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + TSet& ClearedLayers, + TArray& OutCreatedPackages); + + static bool OutputLandscape_Generate( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedActors, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* World, + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + TSet& ClearedLayers, + FHoudiniPackageParams InPackageParams, + TArray& OutCreatedPackages); + + static bool OutputLandscape_GenerateTile( + UHoudiniOutput* InOutput, + TArray& StaleOutputObjects, + TArray>& CreatedUntrackedActors, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* World, + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, FHoudiniPackageParams InPackageParams, + bool bHasEditLayers, + const FString& InEditLayerName, + const FName& InAfterLayerName, + const TArray& AllLayerNames, + TSet& ClearedLayers, + TArray& OutCreatedPackages, + TSet& OutActiveLandscapes); + + // Outputting landscape as "editable layers" differs significantly from + // landscape outputs in "temp mode". To avoid a bigger spaghetti mess, we're + // dealing with modifying existing edit layers completely separately. + static bool OutputLandscape_ModifyLayer( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedActors, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* World, + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + const bool bHasEditLayers, + const FName& EditLayerName, + TSet& ClearedLayers, TArray& OutCreatedPackages); static ALandscapeProxy* FindExistingLandscapeActor_Bake( @@ -72,7 +139,19 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator const FString& InPackagePath, // Package path to search if not found in the world UWorld*& OutWorld, ULevel*& OutLevel, - bool& bCreatedPackage); + bool& bCreatedPackage); + + static ALandscapeProxy* FindTargetLandscapeProxy( + const FString& ActorName, + UWorld* World, + const TArray& LandscapeInputs + ); + + // Iterate through the HGPO's and collect the (non-empty) edit layer names retrieved by the Houdini Output Translator. + // Return false if there are no edit layers (InEditLayers will also be emptied). + static bool GetEditLayersFromOutput(UHoudiniOutput* InOutput, TArray& InEditLayers); + + static void GetLandscapePaintLayers(ALandscape* Landscape, TMap& EditLayers); protected: @@ -91,7 +170,28 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator static bool IsLandscapeTypeCompatible( const AActor* Actor, LandscapeActorType ActorType); - + + static bool PopulateLandscapeExtents( + FHoudiniLandscapeExtent& Extent, + const ULandscapeInfo* LandscapeInfo + ); + + static bool UpdateLandscapeMaterialLayers( + ALandscape* InLandscape, + const TArray& LayerInfos, + TMap& OutPaintLayers, + bool bNoWeightBlend, + bool bHasEditLayers, + const FName& LayerName + ); + + // static bool ClearLandscapeLayers( + // ALandscape* InLandscape, + // const TArray& LayerInfos, + // TSet& ClearedLayers, + // bool bHasEditLayer, + // const FString& EditLayerName + // ); /** * Find a ALandscapeProxy actor that can be reused. It is important @@ -132,9 +232,12 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator */ static void SetLandscapeActorAsOutput( UHoudiniOutput* InOutput, + TArray &StaleOutputObjects, + TSet& OutActiveLandscapes, const TArray& InAllInputLandscapes, const TMap& OutputAttributes, const TMap& OutputArguments, + const FName& EditLayerName, ALandscape* SharedLandscapeActor, USceneComponent* SharedLandscapeActorParent, bool bCreatedMainLandscape, @@ -147,6 +250,7 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator const TArray& InAllInputLandscapes, const TMap& OutputAttributes, const TMap& OutputArguments, + const FName& EditLayerName, ALandscape* SharedLandscapeActor, USceneComponent* SharedLandscapeActorParent, bool bCreatedMainLandscape, @@ -155,9 +259,12 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator static void SetLandscapeActorAsOutput_Temp( UHoudiniOutput* InOutput, + TArray &StaleOutputObjects, + TSet& OutActiveLandscapes, const TArray& InAllInputLandscapes, const TMap& OutputAttributes, const TMap& OutputArguments, + const FName& EditLayerName, ALandscape* SharedLandscapeActor, USceneComponent* SharedLandscapeActorParent, bool bCreatedMainLandscape, @@ -186,12 +293,16 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator static const FHoudiniGeoPartObject* GetHoudiniHeightFieldFromOutput( - UHoudiniOutput* InOutput); + UHoudiniOutput* InOutput, + const bool bMatchEditLayer, + const FName& EditLayerName); static void GetHeightfieldsLayersFromOutput( - const UHoudiniOutput* InOutput, - const FHoudiniGeoPartObject& Heightfield, - TArray< const FHoudiniGeoPartObject* >& FoundLayers); + const ::UHoudiniOutput* InOutput, + const ::FHoudiniGeoPartObject& Heightfield, + const bool bMatchEditLayer, const ::FName& EditLayerName, TArray& FoundLayers); + + static bool GetHoudiniHeightfieldVolumeInfo(const FHoudiniGeoPartObject* HGPO, HAPI_VolumeInfo& VolumeInfo); static bool GetHoudiniHeightfieldFloatData( const FHoudiniGeoPartObject* HGPO, @@ -216,7 +327,10 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator float FloatMax, TArray< uint16 >& IntHeightData, FTransform& LandscapeTransform, - const bool& NoResize = false); + const bool NoResize = false, + const bool bOverrideZScale = false, + const float CustomZScale = 100.f, + const bool bIsAdditive = false); static bool ResizeHeightDataForLandscape( TArray& HeightData, @@ -227,7 +341,7 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator FVector& LandscapeResizeFactor, FVector& LandscapePositionOffset); - static bool CreateOrUpdateLandscapeLayers( + static bool CreateOrUpdateLandscapeLayerData( const TArray& FoundLayers, const FHoudiniGeoPartObject& HeightField, const int32& LandscapeXSize, @@ -236,6 +350,7 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator const TMap &GlobalMaximums, TArray& OutLayerInfos, bool bIsUpdate, + bool bDefaultNoWeightBlending, const FHoudiniPackageParams& InTilePackageParams, const FHoudiniPackageParams& InLayerPackageParams, TArray& OutCreatedPackages); @@ -305,22 +420,28 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator const TArray< uint16 >& IntHeightData, const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, const FTransform& TileTransform, + const FIntPoint& TileLocation, const int32& XSize, const int32& YSize, const int32& NumSectionPerLandscapeComponent, const int32& NumQuadsPerLandscapeSection, UMaterialInterface* LandscapeMaterial, UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* LandscapePhsyicalMaterial, + UPhysicalMaterial* LandscapePhysicalMaterial, const FString& LandscapeTileActorName, LandscapeActorType ActorType, ALandscape* SharedLandscapeActor, // Landscape containing shared stated for streaming proxies UWorld* InWorld, // World in which to spawn ULevel* InLevel, // Level, contained in World, in which to spawn. - FHoudiniPackageParams InPackageParams); + FHoudiniPackageParams InPackageParams, + bool bHasEditLayers, + const FName& EditLayerName, + const FName& AfterLayerName); -protected: + // Destroy the given landscape and all its proxies + static void DestroyLandscape(ALandscape* Landscape); +protected: /** * Calculate the location of a landscape tile. * This location is typically used in conjunction with ALandscapeProxy::SetAbsoluteSectionBase(). @@ -329,14 +450,15 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator int32 NumSectionsPerComponent, int32 NumQuadsPerSection, const FTransform& InTileTransform, - FTransform& OutLandscapeOffset, - FTransform& OutTileTransform, + FHoudiniLandscapeReferenceLocation& RefLoc, + FTransform& OutLandscapeTransform, FIntPoint& OutTileLocation); public: static void GetLandscapeMaterials( const FHoudiniGeoPartObject& InHeightHGPO, + const FHoudiniPackageParams& InPackageParams, UMaterialInterface*& OutLandscapeMaterial, UMaterialInterface*& OutLandscapeHoleMaterial, UPhysicalMaterial*& OutLandscapePhysicalMaterial); @@ -375,6 +497,10 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator static ULandscapeLayerInfoObject* GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName); + // Find or create the given layer. Optionally position it after the 'AfterLayerName'. + static int32 FindOrCreateEditLayer(ALandscape* Landscape, FName LayerName, FName AfterLayerName); + static bool SetActiveLandscapeLayer(ALandscape* Landscape, int32 LayerIndex); + private: static bool ImportLandscapeData( diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp index 62dd57172..f4a1ad4b3 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,7 +24,6 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - #include "HoudiniMaterialTranslator.h" #include "HoudiniApi.h" @@ -64,12 +63,14 @@ const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeY = -150; const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepX = 220; const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepY = 220; -bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( +bool +FHoudiniMaterialTranslator::CreateHoudiniMaterials( const HAPI_NodeId& InAssetId, const FHoudiniPackageParams& InPackageParams, const TArray& InUniqueMaterialIds, const TArray& InUniqueMaterialInfos, const TMap& InMaterials, + const TMap& InAllOutputMaterials, TMap& OutMaterials, TArray& OutPackages, const bool& bForceRecookAll, @@ -99,9 +100,9 @@ bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( for (int32 MaterialIdx = 0; MaterialIdx < InUniqueMaterialIds.Num(); MaterialIdx++) { - HAPI_NodeId MaterialId = (HAPI_NodeId)InUniqueMaterialIds[MaterialIdx]; + HAPI_NodeId MaterialId = (HAPI_NodeId)InUniqueMaterialIds[MaterialIdx]; - HAPI_MaterialInfo MaterialInfo = InUniqueMaterialInfos[MaterialIdx]; + const HAPI_MaterialInfo& MaterialInfo = InUniqueMaterialInfos[MaterialIdx]; if (!MaterialInfo.exists) { // The material does not exist, @@ -129,28 +130,36 @@ bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( FString MaterialPathName = TEXT(""); if (!FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, MaterialInfo, MaterialPathName)) - continue; - - // TODO: GetAssetName! - FString AssetName = TEXT("HoudiniAsset"); - - bool bCreatedNewMaterial = false; + continue; - // TODO: Check existing material map!! - //UMaterial * Material = HoudiniCookParams.HoudiniCookManager ? Cast< UMaterial >(HoudiniCookParams.HoudiniCookManager->GetAssignmentMaterial(MaterialShopName)) : nullptr; + // Check first in the existing material map UMaterial * Material = nullptr; UMaterialInterface* const * FoundMaterial = InMaterials.Find(MaterialPathName); + bool bCanReuseExistingMaterial = false; if (FoundMaterial) { + bCanReuseExistingMaterial = (bInTreatExistingMaterialsAsUpToDate || !MaterialInfo.hasChanged) && !bForceRecookAll; Material = Cast(*FoundMaterial); } - if (Material && !Material->IsPendingKill()) + if(!Material || !bCanReuseExistingMaterial) + { + // Try to see if another output/part of this HDA has already recreated this material + // Since those materials have just been recreated, they are considered up to date and can always be reused. + FoundMaterial = InAllOutputMaterials.Find(MaterialPathName); + if (FoundMaterial) + { + Material = Cast(*FoundMaterial); + bCanReuseExistingMaterial = true; + } + } + + bool bCreatedNewMaterial = false; + if (IsValid(Material)) { - // If cached material exists and has not changed, we can reuse it. - if ((bInTreatExistingMaterialsAsUpToDate || !MaterialInfo.hasChanged) && !bForceRecookAll) + // If the cached material exists and is up to date, we can reuse it. + if (bCanReuseExistingMaterial) { - // We found cached material, we can reuse it. OutMaterials.Add(MaterialPathName, Material); continue; } @@ -158,8 +167,6 @@ bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( else { // Previous Material was not found, we need to create a new one. - // TODO: Handle this! - //EObjectFlags ObjFlags = (HoudiniCookParams.MaterialAndTextureBakeMode == EBakeMode::Intermediate) ? RF_Transactional : RF_Public | RF_Standalone; EObjectFlags ObjFlags = RF_Public | RF_Standalone; // Create material package and get material name. @@ -179,9 +186,12 @@ bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( bCreatedNewMaterial = true; } - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) continue; + // Get the asset name from the package params + FString AssetName = InPackageParams.HoudiniAssetName.IsEmpty() ? TEXT("HoudiniAsset") : InPackageParams.HoudiniAssetName; + // Get the package and add it to our list UPackage* Package = Material->GetOutermost(); OutPackages.AddUnique(Package); @@ -296,7 +306,7 @@ FHoudiniMaterialTranslator::CreateMaterialInstances( UMaterialInterface* CurrentSourceMaterialInterface = Cast( StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentSourceMaterial, nullptr, LOAD_NoWarn, nullptr)); - if (!CurrentSourceMaterialInterface || CurrentSourceMaterialInterface->IsPendingKill()) + if (!IsValid(CurrentSourceMaterialInterface)) { // Couldn't find the source material HOUDINI_LOG_WARNING(TEXT("Couldn't find the source material %s to create a material instance."), *CurrentSourceMaterial); @@ -428,6 +438,33 @@ FHoudiniMaterialTranslator::CreateMaterialInstances( return true; } +bool FHoudiniMaterialTranslator::SortUniqueFaceMaterialOverridesAndCreateMaterialInstances( + const TArray& Materials, + const FHoudiniGeoPartObject& InHGPO, const FHoudiniPackageParams& InPackageParams, + const TArray& InPackages, const TMap& InMaterials, + TMap& OutMaterials, const bool& bForceRecookAll) +{ + // Map containing unique face materials override attribute + // and their first valid prim index + // We create only one material instance per attribute + TMap UniqueFaceMaterialOverrides; + for (int FaceIdx = 0; FaceIdx < Materials.Num(); FaceIdx++) + { + FString MatOverrideAttr = Materials[FaceIdx]; + if (UniqueFaceMaterialOverrides.Contains(MatOverrideAttr)) + continue; + + // Add the material override and face index to the map + UniqueFaceMaterialOverrides.Add(MatOverrideAttr, FaceIdx); + } + + return FHoudiniMaterialTranslator::CreateMaterialInstances( + InHGPO, InPackageParams, + UniqueFaceMaterialOverrides, InPackages, + InMaterials, OutMaterials, + bForceRecookAll); +} + bool FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath) { @@ -550,7 +587,7 @@ FHoudiniMaterialTranslator::CreateUnrealTexture( const FString& TextureType, const FString& NodePath) { - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return nullptr; UTexture2D * Texture = nullptr; @@ -729,13 +766,8 @@ FHoudiniMaterialTranslator::HapiGetImagePlanes( HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlanes( FHoudiniEngine::Get().GetSession(), MaterialInfo.nodeId, &ImagePlaneStringHandles[0], ImagePlaneCount), false); - - for (int32 IdxPlane = 0; IdxPlane < ImagePlaneStringHandles.Num(); IdxPlane++) - { - FString ValueString; - if(FHoudiniEngineString::ToFString(ImagePlaneStringHandles[IdxPlane], ValueString)) - OutImagePlanes.Add(ValueString); - } + + FHoudiniEngineString::SHArrayToFStringArray(ImagePlaneStringHandles, OutImagePlanes); return true; } @@ -800,14 +832,12 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; HAPI_Result Result = HAPI_RESULT_SUCCESS; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Names of generating Houdini parameters. FString GeneratingParameterNameDiffuseTexture = TEXT(""); @@ -832,7 +862,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( // If texture sampling expression does exist, attempt to look up corresponding texture. UTexture2D * TextureDiffuse = nullptr; - if (ExpressionTextureSample && !ExpressionTextureSample->IsPendingKill()) + if (IsValid(ExpressionTextureSample)) TextureDiffuse = Cast< UTexture2D >(ExpressionTextureSample->Texture); // Locate uniform color expression. @@ -841,7 +871,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( MaterialExpression, UMaterialExpressionVectorParameter::StaticClass())); // If uniform color expression does not exist, create it. - if (!ExpressionConstant4Vector || ExpressionConstant4Vector->IsPendingKill()) + if (!IsValid(ExpressionConstant4Vector)) { ExpressionConstant4Vector = NewObject< UMaterialExpressionVectorParameter >( Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); @@ -857,7 +887,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( MaterialExpression, UMaterialExpressionVertexColor::StaticClass())); // If vertex color expression does not exist, create it. - if (!ExpressionVertexColor || ExpressionVertexColor->IsPendingKill()) + if (!IsValid(ExpressionVertexColor)) { ExpressionVertexColor = NewObject< UMaterialExpressionVertexColor >( Material, UMaterialExpressionVertexColor::StaticClass(), NAME_None, ObjectFlag); @@ -869,7 +899,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( // Material should have at least one multiply expression. UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast(MaterialExpression); - if (!MaterialExpressionMultiply || MaterialExpressionMultiply->IsPendingKill()) + if (!IsValid(MaterialExpressionMultiply)) MaterialExpressionMultiply = NewObject( Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); @@ -883,48 +913,43 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( Cast(MaterialExpressionMultiply->A.Expression); // See if a diffuse texture is available. - HAPI_ParmId ParmDiffuseTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); - - if (ParmDiffuseTextureId >= 0) - { - GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); + HAPI_ParmInfo ParmDiffuseTextureInfo; + HAPI_ParmId ParmDiffuseTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED, + true, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) + { + // Found via OGL tag + GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED, + false, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) + { + // Found via Parm name + GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE); } else { - ParmDiffuseTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); - - if (ParmDiffuseTextureId >= 0) - GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); - } - - // See if uniform color is available. - HAPI_ParmInfo ParmInfoDiffuseColor; - FHoudiniApi::ParmInfo_Init(&ParmInfoDiffuseColor); - HAPI_ParmId ParmDiffuseColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0, ParmInfoDiffuseColor); - - if (ParmDiffuseColorId >= 0) - { - GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0); - } - else - { - ParmDiffuseColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1, ParmInfoDiffuseColor); - - if (ParmDiffuseColorId >= 0) - GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1); + // failed to find the texture + ParmDiffuseTextureId = -1; } // If we have diffuse texture parameter. if (ParmDiffuseTextureId >= 0) { - TArray< char > ImageBuffer; + TArray ImageBuffer; // Get image planes of diffuse map. - TArray< FString > DiffuseImagePlanes; + TArray DiffuseImagePlanes; bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( ParmDiffuseTextureId, InMaterialInfo, DiffuseImagePlanes); @@ -961,7 +986,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) { UPackage * TextureDiffusePackage = nullptr; - if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) + if (IsValid(TextureDiffuse)) TextureDiffusePackage = Cast(TextureDiffuse->GetOuter()); HAPI_ImageInfo ImageInfo; @@ -985,7 +1010,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( InPackageParams, TextureDiffuseName); } - else if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) + else if (IsValid(TextureDiffuse)) { // Get the name of the texture if we are overwriting the exist asset TextureDiffuseName = TextureDiffuse->GetName(); @@ -996,7 +1021,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( } // Create diffuse texture, if we need to create one. - if (!TextureDiffuse || TextureDiffuse->IsPendingKill()) + if (!IsValid(TextureDiffuse)) bCreatedNewTextureDiffuse = true; FString NodePath; @@ -1020,7 +1045,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( // Create diffuse sampling expression, if needed. if (!ExpressionTextureSample) { - ExpressionTextureSample = NewObject< UMaterialExpressionTextureSampleParameter2D >( + ExpressionTextureSample = NewObject( Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); } @@ -1047,6 +1072,24 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( } } + // See if uniform color is available. + HAPI_ParmInfo ParmDiffuseColorInfo; + HAPI_ParmId ParmDiffuseColorId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL, ParmDiffuseColorInfo); + + if (ParmDiffuseColorId >= 0) + { + GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL); + } + else + { + ParmDiffuseColorId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE, ParmDiffuseColorInfo); + + if (ParmDiffuseColorId >= 0) + GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE); + } + // If we have uniform color parameter. if (ParmDiffuseColorId >= 0) { @@ -1054,9 +1097,9 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( if (FHoudiniApi::GetParmFloatValues( FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&Color.R, - ParmInfoDiffuseColor.floatValuesIndex, ParmInfoDiffuseColor.size) == HAPI_RESULT_SUCCESS) + ParmDiffuseColorInfo.floatValuesIndex, ParmDiffuseColorInfo.size) == HAPI_RESULT_SUCCESS) { - if (ParmInfoDiffuseColor.size == 3) + if (ParmDiffuseColorInfo.size == 3) Color.A = 1.0f; // Record generating parameter. @@ -1071,7 +1114,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( { if (!MaterialExpressionMultiplySecondary) { - MaterialExpressionMultiplySecondary = NewObject< UMaterialExpressionMultiply >( + MaterialExpressionMultiplySecondary = NewObject( Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); // Add expression. @@ -1119,10 +1162,14 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( MaterialExpressionMultiplySecondary->A.Expression = MaterialExpressionMultiply; MaterialExpressionMultiplySecondary->B.Expression = ExpressionTextureSample; - ExpressionTextureSample->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; - ExpressionTextureSample->MaterialExpressionEditorY = MaterialNodeY; + if (ExpressionTextureSample) + { + ExpressionTextureSample->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - + FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionTextureSample->MaterialExpressionEditorY = MaterialNodeY; + } + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; MaterialExpressionMultiplySecondary->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; @@ -1157,7 +1204,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; bool bExpressionCreated = false; @@ -1168,9 +1215,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( UMaterialExpression * MaterialExpression = Material->OpacityMask.Expression; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Opacity expressions. UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; @@ -1185,20 +1230,34 @@ FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( CreateTexture2DParameters.bSRGB = true; // See if opacity texture is available. - HAPI_ParmId ParmOpacityTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_OPACITY_0); - - if (ParmOpacityTextureId >= 0) - { - GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_0); + HAPI_ParmInfo ParmOpacityTextureInfo; + HAPI_ParmId ParmOpacityTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_OPACITY_OGL, + HAPI_UNREAL_PARAM_MAP_OPACITY_OGL_ENABLED, + true, + ParmOpacityTextureId, + ParmOpacityTextureInfo)) + { + // Found via OGL tag + GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_OPACITY, + HAPI_UNREAL_PARAM_MAP_OPACITY_ENABLED, + false, + ParmOpacityTextureId, + ParmOpacityTextureInfo)) + { + // Found via Parm name + GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY); } else { - ParmOpacityTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_OPACITY_1); - - if (ParmOpacityTextureId >= 0) - GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_1); + // failed to find the texture + ParmOpacityTextureId = -1; } // If we have opacity texture parameter. @@ -1264,7 +1323,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( InPackageParams, TextureOpacityName); } - else if (TextureOpacity && !TextureOpacity->IsPendingKill()) + else if (IsValid(TextureOpacity)) { // Get the name of the texture if we are overwriting the exist asset TextureOpacityName = TextureOpacity->GetName(); @@ -1360,7 +1419,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; bool bExpressionCreated = false; @@ -1368,9 +1427,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( float OpacityValue = 1.0f; bool bNeedsTranslucency = false; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Name of generating Houdini parameters. FString GeneratingParameterNameScalar = TEXT(""); @@ -1418,33 +1475,32 @@ FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( } } - // Retrieve opacity uniform parameter. - HAPI_ParmInfo ParmInfoOpacityValue; - FHoudiniApi::ParmInfo_Init(&ParmInfoOpacityValue); + // Retrieve opacity value + HAPI_ParmInfo ParmOpacityValueInfo; HAPI_ParmId ParmOpacityValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_0, ParmInfoOpacityValue); + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_OGL, ParmOpacityValueInfo); if (ParmOpacityValueId >= 0) { - GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_0); + GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_OGL); } else { ParmOpacityValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_1, ParmInfoOpacityValue); + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA, ParmOpacityValueInfo); if (ParmOpacityValueId >= 0) - GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_1); + GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA); } if (ParmOpacityValueId >= 0) { - if (ParmInfoOpacityValue.size > 0 && ParmInfoOpacityValue.floatValuesIndex >= 0) + if (ParmOpacityValueInfo.size > 0 && ParmOpacityValueInfo.floatValuesIndex >= 0) { float OpacityValueRetrieved = 1.0f; if (FHoudiniApi::GetParmFloatValues( FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, - (float *)&OpacityValue, ParmInfoOpacityValue.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + (float *)&OpacityValue, ParmOpacityValueInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) { if (!ExpressionScalarOpacity) { @@ -1551,16 +1607,14 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; bool bExpressionCreated = false; bool bTangentSpaceNormal = true; HAPI_Result Result = HAPI_RESULT_SUCCESS; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Name of generating Houdini parameter. FString GeneratingParameterName = TEXT(""); @@ -1574,36 +1628,47 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( CreateTexture2DParameters.bSRGB = false; // See if separate normal texture is available. - HAPI_ParmId ParmNameNormalId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_0); - - if (ParmNameNormalId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_0); + HAPI_ParmInfo ParmNormalTextureInfo; + HAPI_ParmId ParmNormalTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_NORMAL, + HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED, + false, + ParmNormalTextureId, + ParmNormalTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_NORMAL_OGL, + "", + true, + ParmNormalTextureId, + ParmNormalTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_OGL); } else { - ParmNameNormalId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_1); - - if (ParmNameNormalId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_1); + // failed to find the texture + ParmNormalTextureId = -1; } - if (ParmNameNormalId >= 0) + if (ParmNormalTextureId >= 0) { // Retrieve space for this normal texture. HAPI_ParmInfo ParmInfoNormalType; - FHoudiniApi::ParmInfo_Init(&ParmInfoNormalType); int32 ParmNormalTypeId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE, ParmInfoNormalType); + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE, ParmInfoNormalType); // Retrieve value for normal type choice list (if exists). - if (ParmNormalTypeId >= 0) { FString NormalType = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT); - if (ParmInfoNormalType.size > 0 && ParmInfoNormalType.stringValuesIndex >= 0) { HAPI_StringHandle StringHandle; @@ -1623,12 +1688,11 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( if (NormalType.Equals(TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD), ESearchCase::IgnoreCase)) bTangentSpaceNormal = false; } - - TArray< char > ImageBuffer; - + // Retrieve color plane. + TArray ImageBuffer; if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameNormalId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + ParmNormalTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) { UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = @@ -1674,7 +1738,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( InPackageParams, TextureNormalName); } - else if (TextureNormal && !TextureNormal->IsPendingKill()) + else if (IsValid(TextureNormal)) { // Get the name of the texture if we are overwriting the exist asset TextureNormalName = TextureNormal->GetName(); @@ -1750,37 +1814,50 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( if (!bExpressionCreated) { // See if diffuse texture is available. - HAPI_ParmId ParmNameBaseId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); - - if (ParmNameBaseId >= 0) + HAPI_ParmInfo ParmDiffuseTextureInfo; + HAPI_ParmId ParmDiffuseTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED, + true, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED, + false, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE); } else { - ParmNameBaseId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); - - if (ParmNameBaseId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); + // failed to find the texture + ParmDiffuseTextureId = -1; } - if (ParmNameBaseId >= 0) + if (ParmDiffuseTextureId >= 0) { // Normal plane is available in diffuse map. - - TArray< char > ImageBuffer; + TArray ImageBuffer; // Retrieve color plane - this will contain normal data. if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameBaseId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL, + ParmDiffuseTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGB, true, ImageBuffer)) { UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Normal.Expression); + Cast(Material->Normal.Expression); - UTexture2D * TextureNormal = nullptr; + UTexture2D* TextureNormal = nullptr; if (ExpressionNormal) { TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); @@ -1795,15 +1872,14 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( } } - UPackage * TextureNormalPackage = nullptr; + UPackage* TextureNormalPackage = nullptr; if (TextureNormal) - TextureNormalPackage = Cast< UPackage >(TextureNormal->GetOuter()); + TextureNormalPackage = Cast(TextureNormal->GetOuter()); HAPI_ImageInfo ImageInfo; FHoudiniApi::ImageInfo_Init(&ImageInfo); Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, &ImageInfo); if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) { @@ -1820,7 +1896,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( InPackageParams, TextureNormalName); } - else if (TextureNormal && !TextureNormal->IsPendingKill()) + else if (IsValid(TextureNormal)) { // Get the name of the texture if we are overwriting the exist asset TextureNormalName = TextureNormal->GetName(); @@ -1906,15 +1982,13 @@ FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; bool bExpressionCreated = false; HAPI_Result Result = HAPI_RESULT_SUCCESS; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Name of generating Houdini parameter. FString GeneratingParameterName = TEXT(""); @@ -1928,29 +2002,43 @@ FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( CreateTexture2DParameters.bSRGB = false; // See if specular texture is available. - HAPI_ParmId ParmNameSpecularId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_SPECULAR_0); - - if (ParmNameSpecularId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_0); + HAPI_ParmInfo ParmSpecularTextureInfo; + HAPI_ParmId ParmSpecularTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL, + HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL_ENABLED, + true, + ParmSpecularTextureId, + ParmSpecularTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_SPECULAR, + HAPI_UNREAL_PARAM_MAP_SPECULAR_ENABLED, + false, + ParmSpecularTextureId, + ParmSpecularTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR); } else { - ParmNameSpecularId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_SPECULAR_1); - - if (ParmNameSpecularId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_1); + // failed to find the texture + ParmSpecularTextureId = -1; } - if (ParmNameSpecularId >= 0) + if (ParmSpecularTextureId >= 0) { - TArray< char > ImageBuffer; + TArray ImageBuffer; // Retrieve color plane. if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameSpecularId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + ParmSpecularTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) { UMaterialExpressionTextureSampleParameter2D * ExpressionSpecular = @@ -1996,7 +2084,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( InPackageParams, TextureSpecularName); } - else if (TextureSpecular && !TextureSpecular->IsPendingKill()) + else if (IsValid(TextureSpecular)) { // Get the name of the texture if we are overwriting the exist asset TextureSpecularName = TextureSpecular->GetName(); @@ -2067,35 +2155,33 @@ FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( } } - HAPI_ParmInfo ParmInfoSpecularColor; - FHoudiniApi::ParmInfo_Init(&ParmInfoSpecularColor); - HAPI_ParmId ParmNameSpecularColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_0, ParmInfoSpecularColor); + // See if we have a specular color + HAPI_ParmInfo ParmSpecularColorInfo; + HAPI_ParmId ParmSpecularColorId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL, ParmSpecularColorInfo); - if (ParmNameSpecularColorId >= 0) + if (ParmSpecularColorId >= 0) { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_0); + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL); } else { - ParmNameSpecularColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_1, ParmInfoSpecularColor); + ParmSpecularColorId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR, ParmSpecularColorInfo); - if (ParmNameSpecularColorId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_1); + if (ParmSpecularColorId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR); } - if (!bExpressionCreated && ParmNameSpecularColorId >= 0) + if (!bExpressionCreated && ParmSpecularColorId >= 0) { // Specular color is available. - FLinearColor Color = FLinearColor::White; - if (FHoudiniApi::GetParmFloatValues( FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, - ParmInfoSpecularColor.floatValuesIndex, ParmInfoSpecularColor.size) == HAPI_RESULT_SUCCESS) + ParmSpecularColorInfo.floatValuesIndex, ParmSpecularColorInfo.size) == HAPI_RESULT_SUCCESS) { - if (ParmInfoSpecularColor.size == 3) + if (ParmSpecularColorInfo.size == 3) Color.A = 1.0f; UMaterialExpressionVectorParameter * ExpressionSpecularColor = @@ -2147,15 +2233,13 @@ FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; bool bExpressionCreated = false; HAPI_Result Result = HAPI_RESULT_SUCCESS; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Name of generating Houdini parameter. FString GeneratingParameterName = TEXT(""); @@ -2169,29 +2253,42 @@ FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( CreateTexture2DParameters.bSRGB = false; // See if roughness texture is available. - HAPI_ParmId ParmNameRoughnessId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0); - - if (ParmNameRoughnessId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0); + HAPI_ParmInfo ParmRoughnessTextureInfo; + HAPI_ParmId ParmRoughnessTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL_ENABLED, + true, + ParmRoughnessTextureId, + ParmRoughnessTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS_ENABLED, + false, + ParmRoughnessTextureId, + ParmRoughnessTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS); } else { - ParmNameRoughnessId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1); - - if (ParmNameRoughnessId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1); + // failed to find the texture + ParmRoughnessTextureId = -1; } - if (ParmNameRoughnessId >= 0) + if (ParmRoughnessTextureId >= 0) { - TArray< char > ImageBuffer; - + TArray ImageBuffer; // Retrieve color plane. if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameRoughnessId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + ParmRoughnessTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer ) ) { UMaterialExpressionTextureSampleParameter2D* ExpressionRoughness = @@ -2237,7 +2334,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( InPackageParams, TextureRoughnessName); } - else if (TextureRoughness && !TextureRoughness->IsPendingKill()) + else if (IsValid(TextureRoughness)) { // Get the name of the texture if we are overwriting the exist asset TextureRoughnessName = TextureRoughness->GetName(); @@ -2306,25 +2403,25 @@ FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( } } - HAPI_ParmInfo ParmInfoRoughnessValue; - FHoudiniApi::ParmInfo_Init(&ParmInfoRoughnessValue); - HAPI_ParmId ParmNameRoughnessValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0, ParmInfoRoughnessValue); + // See if we have a roughness value + HAPI_ParmInfo ParmRoughnessValueInfo; + HAPI_ParmId ParmRoughnessValueId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL, ParmRoughnessValueInfo); - if (ParmNameRoughnessValueId >= 0) + if (ParmRoughnessValueId >= 0) { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0); + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL); } else { - ParmNameRoughnessValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1, ParmInfoRoughnessValue); + ParmRoughnessValueId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS, ParmRoughnessValueInfo); - if (ParmNameRoughnessValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1); + if (ParmRoughnessValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS); } - if (!bExpressionCreated && ParmNameRoughnessValueId >= 0) + if (!bExpressionCreated && ParmRoughnessValueId >= 0) { // Roughness value is available. @@ -2332,7 +2429,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( if (FHoudiniApi::GetParmFloatValues( FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&RoughnessValue, - ParmInfoRoughnessValue.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + ParmRoughnessValueInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) { UMaterialExpressionScalarParameter * ExpressionRoughnessValue = Cast< UMaterialExpressionScalarParameter >(Material->Roughness.Expression); @@ -2388,15 +2485,13 @@ FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; bool bExpressionCreated = false; HAPI_Result Result = HAPI_RESULT_SUCCESS; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Name of generating Houdini parameter. FString GeneratingParameterName = TEXT(""); @@ -2410,21 +2505,43 @@ FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( CreateTexture2DParameters.bSRGB = false; // See if metallic texture is available. - HAPI_ParmId ParmNameMetallicId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_METALLIC); - - if (ParmNameMetallicId >= 0) - { + HAPI_ParmInfo ParmMetallicTextureInfo; + HAPI_ParmId ParmMetallicTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_METALLIC_OGL, + HAPI_UNREAL_PARAM_MAP_METALLIC_OGL_ENABLED, + true, + ParmMetallicTextureId, + ParmMetallicTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_METALLIC, + HAPI_UNREAL_PARAM_MAP_METALLIC_ENABLED, + false, + ParmMetallicTextureId, + ParmMetallicTextureInfo)) + { + // Found via Parm name GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC); } + else + { + // failed to find the texture + ParmMetallicTextureId = -1; + } - if (ParmNameMetallicId >= 0) + if (ParmMetallicTextureId >= 0) { - TArray< char > ImageBuffer; + TArray ImageBuffer; // Retrieve color plane. if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameMetallicId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + ParmMetallicTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) { UMaterialExpressionTextureSampleParameter2D * ExpressionMetallic = @@ -2433,7 +2550,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( UTexture2D * TextureMetallic = nullptr; if (ExpressionMetallic) { - TextureMetallic = Cast< UTexture2D >(ExpressionMetallic->Texture); + TextureMetallic = Cast(ExpressionMetallic->Texture); } else { @@ -2470,7 +2587,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( InPackageParams, TextureMetallicName); } - else if (TextureMetallic && !TextureMetallic->IsPendingKill()) + else if (IsValid(TextureMetallic)) { // Get the name of the texture if we are overwriting the exist asset TextureMetallicName = TextureMetallic->GetName(); @@ -2540,23 +2657,32 @@ FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( } } - HAPI_ParmInfo ParmInfoMetallic; - FHoudiniApi::ParmInfo_Init(&ParmInfoMetallic); - HAPI_ParmId ParmNameMetallicValueIdx = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC, ParmInfoMetallic); + // Get the metallic value + HAPI_ParmInfo ParmMetallicValueInfo; + HAPI_ParmId ParmMetallicValueId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL, ParmMetallicValueInfo); - if (ParmNameMetallicValueIdx >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC); + if (ParmMetallicValueId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL); + } + else + { + ParmMetallicValueId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC, ParmMetallicValueInfo); - if (!bExpressionCreated && ParmNameMetallicValueIdx >= 0) + if (ParmMetallicValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC); + } + + if (!bExpressionCreated && ParmMetallicValueId >= 0) { // Metallic value is available. - float MetallicValue = 0.0f; if (FHoudiniApi::GetParmFloatValues( FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&MetallicValue, - ParmInfoMetallic.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + ParmMetallicTextureInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) { UMaterialExpressionScalarParameter * ExpressionMetallicValue = Cast< UMaterialExpressionScalarParameter >(Material->Metallic.Expression); @@ -2612,15 +2738,13 @@ FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; bool bExpressionCreated = false; HAPI_Result Result = HAPI_RESULT_SUCCESS; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Name of generating Houdini parameter. FString GeneratingParameterName = TEXT(""); @@ -2634,21 +2758,43 @@ FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( CreateTexture2DParameters.bSRGB = false; // See if emissive texture is available. - HAPI_ParmId ParmNameEmissiveId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_EMISSIVE); - - if (ParmNameEmissiveId >= 0) - { + HAPI_ParmInfo ParmEmissiveTextureInfo; + HAPI_ParmId ParmEmissiveTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL, + HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL_ENABLED, + true, + ParmEmissiveTextureId, + ParmEmissiveTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_EMISSIVE, + HAPI_UNREAL_PARAM_MAP_EMISSIVE_ENABLED, + false, + ParmEmissiveTextureId, + ParmEmissiveTextureInfo)) + { + // Found via Parm name GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE); } + else + { + // failed to find the texture + ParmEmissiveTextureId = -1; + } - if (ParmNameEmissiveId >= 0) + if (ParmEmissiveTextureId >= 0) { TArray< char > ImageBuffer; // Retrieve color plane. if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameEmissiveId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + ParmEmissiveTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) { UMaterialExpressionTextureSampleParameter2D * ExpressionEmissive = @@ -2694,7 +2840,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( InPackageParams, TextureEmissiveName); } - else if (TextureEmissive && !TextureEmissive->IsPendingKill()) + else if (IsValid(TextureEmissive)) { // Get the name of the texture if we are overwriting the exist asset TextureEmissiveName = TextureEmissive->GetName(); @@ -2763,23 +2909,22 @@ FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( } } - HAPI_ParmInfo ParmInfoEmissive; - FHoudiniApi::ParmInfo_Init(&ParmInfoEmissive); - HAPI_ParmId ParmNameEmissiveValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0, ParmInfoEmissive); + HAPI_ParmInfo ParmEmissiveValueInfo; + HAPI_ParmId ParmEmissiveValueId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL, ParmEmissiveValueInfo); - if (ParmNameEmissiveValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0); + if (ParmEmissiveValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL); else { - ParmNameEmissiveValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1, ParmInfoEmissive); + ParmEmissiveValueId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE, ParmEmissiveValueInfo); - if (ParmNameEmissiveValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1); + if (ParmEmissiveValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE); } - if (!bExpressionCreated && ParmNameEmissiveValueId >= 0) + if (!bExpressionCreated && ParmEmissiveValueId >= 0) { // Emissive color is available. @@ -2787,9 +2932,9 @@ FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( if (FHoudiniApi::GetParmFloatValues( FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, - ParmInfoEmissive.floatValuesIndex, ParmInfoEmissive.size) == HAPI_RESULT_SUCCESS) + ParmEmissiveValueInfo.floatValuesIndex, ParmEmissiveValueInfo.size) == HAPI_RESULT_SUCCESS) { - if (ParmInfoEmissive.size == 3) + if (ParmEmissiveValueInfo.size == 3) Color.A = 1.0f; UMaterialExpressionConstant4Vector * ExpressionEmissiveColor = @@ -3142,96 +3287,169 @@ FHoudiniMaterialTranslator::FindGeneratedTexture(const FString& TextureString, c return nullptr; // Try to find the corresponding texture in the cooked temporary package generated by an HDA - UTexture* FoundTexture = nullptr; - for(const auto& CurrentPackage : InPackages) - { - // Iterate through the cooked packages - if (!CurrentPackage || CurrentPackage->IsPendingKill()) - continue; +UTexture* FoundTexture = nullptr; +for (const auto& CurrentPackage : InPackages) +{ + // Iterate through the cooked packages + if (!IsValid(CurrentPackage)) + continue; - // First, check if the package contains a texture - FString CurrentPackageName = CurrentPackage->GetName(); - UTexture* PackageTexture = LoadObject(CurrentPackage, *CurrentPackageName, nullptr, LOAD_None, nullptr); - if (!PackageTexture) - continue; + // First, check if the package contains a texture + FString CurrentPackageName = CurrentPackage->GetName(); + UTexture* PackageTexture = LoadObject(CurrentPackage, *CurrentPackageName, nullptr, LOAD_None, nullptr); + if (!PackageTexture) + continue; - // Then check if the package's metadata match what we're looking for - // Make sure this texture was generated by Houdini Engine - UMetaData* MetaData = CurrentPackage->GetMetaData(); - if (!MetaData || !MetaData->HasValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) - continue; + // Then check if the package's metadata match what we're looking for + // Make sure this texture was generated by Houdini Engine + UMetaData* MetaData = CurrentPackage->GetMetaData(); + if (!MetaData || !MetaData->HasValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) + continue; - // Get the texture type from the meta data - // Texture type store has meta data will be C_A, N, S, R etc.. - const FString TextureTypeString = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); - if (TextureTypeString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } + // Get the texture type from the meta data + // Texture type store has meta data will be C_A, N, S, R etc.. + const FString TextureTypeString = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); + if (TextureTypeString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } - // Convert the texture type to a "friendly" version - // C_A to diffuse, N to Normal, S to Specular etc... - FString TextureTypeFriendlyString = TextureTypeString; - FString TextureTypeFriendlyAlternateString = TEXT(""); - if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, ESearchCase::IgnoreCase) == 0) - { - TextureTypeFriendlyString = TEXT("diffuse"); - TextureTypeFriendlyAlternateString = TEXT("basecolor"); - } - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("normal"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("emissive"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("specular"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("roughness"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("metallic"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("opacity"); - - // See if we have a match between the texture string and the friendly name - if ((TextureTypeFriendlyString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - || (!TextureTypeFriendlyAlternateString.IsEmpty() && TextureTypeFriendlyAlternateString.Compare(TextureString, ESearchCase::IgnoreCase) == 0)) - { - FoundTexture = PackageTexture; - break; - } + // Convert the texture type to a "friendly" version + // C_A to diffuse, N to Normal, S to Specular etc... + FString TextureTypeFriendlyString = TextureTypeString; + FString TextureTypeFriendlyAlternateString = TEXT(""); + if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, ESearchCase::IgnoreCase) == 0) + { + TextureTypeFriendlyString = TEXT("diffuse"); + TextureTypeFriendlyAlternateString = TEXT("basecolor"); + } + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("normal"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("emissive"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("specular"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("roughness"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("metallic"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("opacity"); - // Get the node path from the meta data - const FString NodePath = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_NODE_PATH); - if (NodePath.IsEmpty()) - continue; + // See if we have a match between the texture string and the friendly name + if ((TextureTypeFriendlyString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + || (!TextureTypeFriendlyAlternateString.IsEmpty() && TextureTypeFriendlyAlternateString.Compare(TextureString, ESearchCase::IgnoreCase) == 0)) + { + FoundTexture = PackageTexture; + break; + } - // See if we have a match with the path and texture type - FString PathAndType = NodePath + TEXT("/") + TextureTypeString; - if (PathAndType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } + // Get the node path from the meta data + const FString NodePath = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_NODE_PATH); + if (NodePath.IsEmpty()) + continue; - // See if we have a match with the friendly path and texture type - FString PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyString; + // See if we have a match with the path and texture type + FString PathAndType = NodePath + TEXT("/") + TextureTypeString; + if (PathAndType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // See if we have a match with the friendly path and texture type + FString PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyString; + if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // Try the alternate friendly string + if (!TextureTypeFriendlyAlternateString.IsEmpty()) + { + PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyAlternateString; if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) { FoundTexture = PackageTexture; break; } + } +} + +return FoundTexture; +} + - // Try the alternate friendly string - if (!TextureTypeFriendlyAlternateString.IsEmpty()) +bool +FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + const HAPI_NodeId& InNodeId, + const std::string& InTextureParmName, + const std::string& InUseTextureParmName, + const bool& bFindByTag, + HAPI_ParmId& OutParmId, + HAPI_ParmInfo& OutParmInfo) +{ + OutParmId = -1; + + if(bFindByTag) + OutParmId = FHoudiniEngineUtils::HapiFindParameterByTag(InNodeId, InTextureParmName, OutParmInfo); + else + OutParmId = FHoudiniEngineUtils::HapiFindParameterByName(InNodeId, InTextureParmName, OutParmInfo); + + if (OutParmId < 0) + { + // Failed to find the texture + return false; + } + + // We found a valid parameter, check if the matching "use" parameter exists + HAPI_ParmInfo FoundUseParmInfo; + HAPI_ParmId FoundUseParmId = -1; + if(bFindByTag) + FoundUseParmId = FHoudiniEngineUtils::HapiFindParameterByTag(InNodeId, InUseTextureParmName, FoundUseParmInfo); + else + FoundUseParmId = FHoudiniEngineUtils::HapiFindParameterByName(InNodeId, InUseTextureParmName, FoundUseParmInfo); + + if (FoundUseParmId >= 0) + { + // We found a valid "use" parameter, check if it is disabled + // Get the param value + int32 UseValue = 0; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, &UseValue, FoundUseParmInfo.intValuesIndex, 1)) { - PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyAlternateString; - if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + if (UseValue == 0) { - FoundTexture = PackageTexture; - break; + // We found the texture parm, but the "use" param/tag is disabled, so don't use it! + // We still return true as we found the parameter, this will prevent looking for other parms + OutParmId = -1; + return true; } } } - return FoundTexture; + // Finally, make sure that the found texture Parm is not empty! + FString ParmValue = FString(); + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandle, OutParmInfo.stringValuesIndex, 1)) + { + // Convert the string handle to FString + FHoudiniEngineString::ToFString(StringHandle, ParmValue); + } + + if (ParmValue.IsEmpty()) + { + // We found the parm, but it's empty, don't use it! + // We still return true as we found the parameter, this will prevent looking for other parms + OutParmId = -1; + return true; + } + + return true; } + diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h index ed62575fb..a8127af06 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,7 +32,7 @@ #include "UObject/ObjectMacros.h" #include "Engine/TextureDefines.h" -//#include "HoudiniMaterialTranslator.generated.h" +#include class UMaterial; class UMaterialInterface; @@ -60,6 +60,7 @@ struct HOUDINIENGINE_API FHoudiniMaterialTranslator const TArray& InUniqueMaterialIds, const TArray& InUniqueMaterialInfos, const TMap& InMaterials, + const TMap& InAllOutputMaterials, TMap& OutMaterials, TArray& OutPackages, const bool& bForceRecookAll, @@ -75,6 +76,17 @@ struct HOUDINIENGINE_API FHoudiniMaterialTranslator TMap& OutMaterials, const bool& bForceRecookAll); + + // Helper for CreateMaterialInstances so you don't have to sort unique face materials overrides + static bool SortUniqueFaceMaterialOverridesAndCreateMaterialInstances( + const TArray& Materials, + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TArray& InPackages, + const TMap& InMaterials, + TMap& OutMaterials, + const bool& bForceRecookAll); + // static bool UpdateMaterialInstanceParameter( FHoudiniGenericAttribute MaterialParameter, @@ -131,6 +143,16 @@ struct HOUDINIENGINE_API FHoudiniMaterialTranslator const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialNodeInfo, FString& OutRelativePath); static bool GetMaterialRelativePath( const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath); + + // Returns true if a texture parameter was found + // Ensures that the texture is not disabled via the "UseTexture" Parm name/tag + static bool FindTextureParamByNameOrTag( + const HAPI_NodeId& InNodeId, + const std::string& InTextureParmName, + const std::string& InUseTextureParmName, + const bool& bFindByTag, + HAPI_ParmId& OutParmId, + HAPI_ParmInfo& OutParmInfo); protected: diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp index 4fe0b6568..7d0ec824c 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,7 +24,6 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - #include "HoudiniMeshTranslator.h" #include "HoudiniApi.h" @@ -76,23 +75,32 @@ #define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE +static TAutoConsoleVariable CVarHoudiniEngineMeshBuildTimer( + TEXT("HoudiniEngine.MeshBuildTimer"), + 0.0, + TEXT("When enabled, the plugin will output timings during the Mesh creation.\n") +); + // bool FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( UHoudiniOutput* InOutput, const FHoudiniPackageParams& InPackageParams, - EHoudiniStaticMeshMethod InStaticMeshMethod, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InMeshBuildSettings, + const TMap& InAllOutputMaterials, UObject* InOuterComponent, bool bInTreatExistingMaterialsAsUpToDate, bool bInDestroyProxies) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; - if (!InPackageParams.OuterPackage || InPackageParams.OuterPackage->IsPendingKill()) + if (!IsValid(InPackageParams.OuterPackage)) return false; - if (!InOuterComponent || InOuterComponent->IsPendingKill()) + if (!IsValid(InOuterComponent)) return false; TMap NewOutputObjects; @@ -114,6 +122,18 @@ FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( if (CurHGPO.Type != EHoudiniPartType::Mesh) continue; + // See if we have some uproperty attributes to update on + // the outer component (in most case, the HAC) + TArray PropertyAttributes; + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + CurHGPO.GeoId, CurHGPO.PartId, + true, 0, 0, 0, + PropertyAttributes)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( + InOuterComponent, PropertyAttributes); + } + CreateStaticMeshFromHoudiniGeoPartObject( CurHGPO, InPackageParams, @@ -121,8 +141,11 @@ FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( NewOutputObjects, AssignementMaterials, ReplacementMaterials, + InAllOutputMaterials, InForceRebuild, InStaticMeshMethod, + InSMGenerationProperties, + InMeshBuildSettings, bInTreatExistingMaterialsAsUpToDate); } @@ -141,10 +164,10 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( bool bInDestroyProxies, bool bInApplyGenericProperties) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; - if (!InOuterComponent || InOuterComponent->IsPendingKill()) + if (!IsValid(InOuterComponent)) return false; TMap OldOutputObjects = InOutput->GetOutputObjects(); @@ -164,7 +187,7 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( UObject* NewProxyMesh = NewOutputObj.Value.ProxyObject; UObject* OldStaticMesh = FoundOldOutputObj->OutputObject; - if (OldStaticMesh && !OldStaticMesh->IsPendingKill()) + if (IsValid(OldStaticMesh)) { // If a proxy was created for an existing static mesh, keep the existing static // mesh (will be hidden) @@ -181,7 +204,7 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( } UObject* OldProxyMesh = FoundOldOutputObj->ProxyObject; - if (OldProxyMesh && !OldProxyMesh->IsPendingKill()) + if (IsValid(OldProxyMesh)) { // If a new static mesh was created for a proxy, keep the proxy (will be hidden) // ... unless we want to explicitly destroy proxies @@ -212,12 +235,12 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( RemoveAndDestroyComponent(OldOutputObject.ProxyComponent); OldOutputObject.ProxyComponent = nullptr; - if (OldOutputObject.OutputObject && !OldOutputObject.OutputObject->IsPendingKill()) + if (IsValid(OldOutputObject.OutputObject)) { OldOutputObject.OutputObject->MarkPendingKill(); } - if (OldOutputObject.ProxyObject && !OldOutputObject.ProxyObject->IsPendingKill()) + if (IsValid(OldOutputObject.ProxyObject)) { OldOutputObject.ProxyObject->MarkPendingKill(); } @@ -268,11 +291,28 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( const FHoudiniOutputObjectIdentifier& OutputIdentifier = NewPair.Key; FHoudiniOutputObject& OutputObject = NewPair.Value; + if (OutputObject.bIsImplicit) + { + // This output is implicit and shouldn't have a representative component/proxy in the scene + // Remove the old component from the map + if (OutputObject.OutputComponent) + { + RemoveAndDestroyComponent(OutputObject.OutputComponent); + OutputObject.OutputComponent = nullptr; + } + + // Remove the old proxy component from the map + RemoveAndDestroyComponent(OutputObject.ProxyComponent); + OutputObject.ProxyComponent = nullptr; + + continue; // Skip any proxy / component creation below + } + // Check if we should create a Proxy/SMC if (OutputObject.bProxyIsCurrent) { UObject *Mesh = OutputObject.ProxyObject; - if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) + if (!IsValid(Mesh) || !Mesh->IsA()) { HOUDINI_LOG_ERROR(TEXT("Proxy Mesh is invalid (wrong type or pending kill)...")); continue; @@ -291,7 +331,7 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( { PostCreateHoudiniStaticMeshComponent(HSMC, Mesh); } - else if (HSMC && !HSMC->IsPendingKill() && HSMC->GetMesh() != Mesh) + else if (IsValid(HSMC) && HSMC->GetMesh() != Mesh) { // We need to reassign the HSM to the component UHoudiniStaticMesh* HSM = Cast(Mesh); @@ -332,7 +372,7 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( { // Create a new SMC if needed UObject* Mesh = OutputObject.OutputObject; - if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) + if (!IsValid(Mesh) || !Mesh->IsA()) { HOUDINI_LOG_ERROR(TEXT("Mesh is invalid (wrong type or pending kill)...")); continue; @@ -407,21 +447,21 @@ FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, con // Update navmesh? // Transform the component by transformation provided by HAPI. - InMeshComponent->SetRelativeTransform(InHGPO->TransformMatrix); + InMeshComponent->SetRelativeTransform(InHGPO ? InHGPO->TransformMatrix : FTransform::Identity); // If the static mesh had sockets, we can assign the desired actor to them now UStaticMeshComponent * StaticMeshComponent = Cast(InMeshComponent); UStaticMesh * StaticMesh = nullptr; - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + if (IsValid(StaticMeshComponent)) StaticMesh = StaticMeshComponent->GetStaticMesh(); - if (StaticMesh && !StaticMesh->IsPendingKill()) + if (IsValid(StaticMesh)) { int32 NumberOfSockets = StaticMesh == nullptr ? 0 : StaticMesh->Sockets.Num(); for (int32 nSocket = 0; nSocket < NumberOfSockets; nSocket++) { UStaticMeshSocket* MeshSocket = StaticMesh->Sockets[nSocket]; - if (MeshSocket && !MeshSocket->IsPendingKill() && (MeshSocket->Tag.IsEmpty())) + if (IsValid(MeshSocket) && (MeshSocket->Tag.IsEmpty())) continue; AddActorsToMeshSocket(StaticMesh->Sockets[nSocket], StaticMeshComponent, HoudiniCreatedSocketActors, HoudiniAttachedSocketActors); @@ -433,7 +473,7 @@ FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, con { AActor * CurActor = HoudiniCreatedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) + if (!IsValid(CurActor)) { HoudiniCreatedSocketActors.RemoveAt(Idx); continue; @@ -463,7 +503,7 @@ FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, con for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) { AActor* CurActor = HoudiniAttachedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) + if (!IsValid(CurActor)) { HoudiniAttachedSocketActors.RemoveAt(Idx); continue; @@ -496,12 +536,15 @@ FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, con InMeshComponent->ComponentTags.Empty(); // Update the property attributes on the component TArray PropertyAttributes; - if (GetGenericPropertiesAttributes( + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( InOutputIdentifier.GeoId, InOutputIdentifier.PartId, - InOutputIdentifier.PointIndex, InOutputIdentifier.PrimitiveIndex, + true, + InOutputIdentifier.PrimitiveIndex, + INDEX_NONE, + InOutputIdentifier.PointIndex, PropertyAttributes)) { - UpdateGenericPropertiesAttributes(InMeshComponent, PropertyAttributes); + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(InMeshComponent, PropertyAttributes); } } } @@ -514,13 +557,16 @@ FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( TMap& OutOutputObjects, TMap& AssignmentMaterialMap, TMap& ReplacementMaterialMap, + const TMap& InAllOutputMaterials, const bool& InForceRebuild, - EHoudiniStaticMeshMethod InStaticMeshMethod, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InSMBuildSettings, bool bInTreatExistingMaterialsAsUpToDate) { // If we're not forcing the rebuild // No need to recreate something that hasn't changed - if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged) && InOutputObjects.Num() > 0) + if (!InForceRebuild && !InHGPO.bHasGeoChanged && !InHGPO.bHasPartChanged && InOutputObjects.Num() > 0) { // Simply reuse the existing meshes OutOutputObjects = InOutputObjects; @@ -533,9 +579,12 @@ FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( CurrentTranslator.SetInputObjects(InOutputObjects); CurrentTranslator.SetOutputObjects(OutOutputObjects); CurrentTranslator.SetInputAssignmentMaterials(AssignmentMaterialMap); + CurrentTranslator.SetAllOutputMaterials(InAllOutputMaterials); CurrentTranslator.SetReplacementMaterials(ReplacementMaterialMap); CurrentTranslator.SetPackageParams(InPackageParams, true); CurrentTranslator.SetTreatExistingMaterialsAsUpToDate(bInTreatExistingMaterialsAsUpToDate); + CurrentTranslator.SetStaticMeshGenerationProperties(InSMGenerationProperties); + CurrentTranslator.SetStaticMeshBuildSettings(InSMBuildSettings); // TODO: Fetch from settings/HAC CurrentTranslator.DefaultMeshSmoothing = 1; @@ -718,8 +767,8 @@ FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices() AllSplitVertexLists.Add(GroupName, GroupVertexList); AllSplitVertexCounts.Add(GroupName, GroupVertexListCount); AllSplitFaceIndices.Add(GroupName, AllFaceList); - AllSplitFirstValidVertexIndex.Add(GroupName, FirstValidPrimIndex); - AllSplitFirstValidPrimIndex.Add(GroupName, FirstValidVertexIndex); + AllSplitFirstValidVertexIndex.Add(GroupName, FirstValidVertexIndex); + AllSplitFirstValidPrimIndex.Add(GroupName, FirstValidPrimIndex); } if (InvalidGroupNameIndices.Num() > 0) @@ -887,9 +936,8 @@ FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded() TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded")); // No need to read the normals if we want unreal to recompute them after - bool bReadNormals = true; - // TODO: Add runtime setting check! - //bool bReadNormals = HoudiniRuntimeSettings->RecomputeNormalsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadNormals = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeNormalsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; if (!bReadNormals) return true; @@ -1360,7 +1408,7 @@ FHoudiniMeshTranslator::CreateNewStaticMesh(const FString& InSplitIdentifier) PackageParams.SplitStr = InSplitIdentifier; UStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); - if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) + if (!IsValid(NewStaticMesh)) return nullptr; return NewStaticMesh; @@ -1378,7 +1426,7 @@ FHoudiniMeshTranslator::CreateNewHoudiniStaticMesh(const FString& InSplitIdentif PackageParams.SplitStr = InSplitIdentifier + "_HSM"; UHoudiniStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); - if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) + if (!IsValid(NewStaticMesh)) return nullptr; return NewStaticMesh; @@ -1387,6 +1435,9 @@ FHoudiniMeshTranslator::CreateNewHoudiniStaticMesh(const FString& InSplitIdentif bool FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() { + // Time limit for processing + bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; + double time_start = FPlatformTime::Seconds(); // Start by updating the vertex list @@ -1439,25 +1490,32 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // New mesh list TMap StaticMeshToBuild; - // Map of Houdini Material IDs to Unreal Material Indices - TMap MapHoudiniMatIdToUnrealIndex; - // Map of Houdini Material Attributes to Unreal Material Indices - TMap MapHoudiniMatAttributesToUnrealIndex; + // Map of Houdini Material IDs to Unreal Material Interface + TMap MapHoudiniMatIdToUnrealInterface; + // Map of Houdini Material Attributes to Unreal Material Interface + TMap MapHoudiniMatAttributesToUnrealInterface; + // Map of Unreal Material Interface to Unreal Material Index, per visible mesh + TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; - bool MeshMaterialsHaveBeenReset = false; + // bool MeshMaterialsHaveBeenReset = false; - // Mesh Socket array - TArray AllSockets; - FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); + double tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Pre Split-Loop in %f seconds."), tick - time_start); + } + + UStaticMesh* MainStaticMesh = nullptr; + bool bAssignedCustomCollisionMesh = false; + ECollisionTraceFlag MainStaticMeshCTF = StaticMeshGenerationProperties.GeneratedCollisionTraceFlag; // Iterate through all detected split groups we care about and split geometry. // The split are ordered in the following way: // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) { + double split_tick = FPlatformTime::Seconds(); + // Get split group name const FString& SplitGroupName = AllSplitGroups[SplitId]; @@ -1495,6 +1553,8 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() FHoudiniOutputObjectIdentifier OutputObjectIdentifier( HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); OutputObjectIdentifier.PartName = HGPO.PartName; + OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], + OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; // Get/Create the Aggregate Collisions for this mesh identifier FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); @@ -1502,6 +1562,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // Handle UCX / Convex Hull colliders if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; // Get the part position if needed UpdatePartPositionIfNeeded(); @@ -1520,6 +1581,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; // Get the part position if needed UpdatePartPositionIfNeeded(); @@ -1563,7 +1625,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() { // If we couldn't find a valid existing static mesh, create a new one FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + if (!IsValid(FoundStaticMesh)) continue; bNewStaticMeshCreated = true; @@ -1578,6 +1640,14 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); } + if (SplitType == EHoudiniSplitType::Normal && !MainStaticMesh) + { + MainStaticMesh = FoundStaticMesh; + MainStaticMesh->ComplexCollisionMesh = nullptr; + MainStaticMesh->bCustomizedCollision = false; + // NOTE: The main static mesh collision trace flag will be set after all splits have been processed. + } + if (!FoundOutputObject) { FHoudiniOutputObject NewOutputObject; @@ -1618,7 +1688,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() { FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); } - FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); + FoundStaticMesh->SetLightMapResolution(LODGroup.GetDefaultLightMapResolution()); } // By default, always work on the first source model, unless we're a LOD @@ -1654,6 +1724,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() continue; } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - PreRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + // Load existing raw model. This will be empty as we are constructing a new mesh. FRawMesh RawMesh; if (!bRebuildStaticMesh) @@ -1662,6 +1738,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // the geometry hasn't changed, but the materials have. // We can just load the old data into the Raw mesh and reuse it. SrcModel->LoadRawMesh(RawMesh); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - LoadRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } } else { @@ -1696,15 +1778,19 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() RawMesh.WedgeTangentZ[WedgeTangentZIdx].Z = SplitNormals[WedgeTangentZIdx * 3 + 1]; } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Normals in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } //--------------------------------------------------------------------------------------------------------------------- // TANGENTS //--------------------------------------------------------------------------------------------------------------------- - // No need to read the tangents if we want unreal to recompute them after - bool bReadTangents = true; - // TODO: Add runtime setting check! - //bool bReadTangents = HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; + // No need to read the tangents if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; if (bReadTangents) { // Extract this part's Tangents if needed @@ -1731,14 +1817,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() if (WedgeTangentUCount != WedgeNormalCount || WedgeTangentVCount != WedgeNormalCount) bGenerateTangents = true; - /* - // TODO: Add settings check! if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) { // No need to generate tangents if we want unreal to recompute them after bGenerateTangents = false; } - */ // Generate the tangents if needed if (bGenerateTangents) @@ -1777,6 +1860,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Tangents in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + //--------------------------------------------------------------------------------------------------------------------- // VERTEX COLORS AND ALPHAS //--------------------------------------------------------------------------------------------------------------------- @@ -1844,6 +1933,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() RawMesh.WedgeColors.Init(DefaultWedgeColor, WedgeColorsCount); } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Cd and Alpha in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + //--------------------------------------------------------------------------------------------------------------------- // FACE SMOOTHING //--------------------------------------------------------------------------------------------------------------------- @@ -1874,6 +1969,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() RawMesh.FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - FaceSmoothing in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + //--------------------------------------------------------------------------------------------------------------------- // UVS //--------------------------------------------------------------------------------------------------------------------- @@ -1925,7 +2026,13 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // Set the lightmap Coordinate Index // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention // If not, the first UV set will be used - FoundStaticMesh->LightMapCoordinateIndex = LightMapUVChannel; + FoundStaticMesh->SetLightMapCoordinateIndex(LightMapUVChannel); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - UVs in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } //--------------------------------------------------------------------------------------------------------------------- // LIGHTMAP RESOLUTION @@ -1935,7 +2042,13 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() UpdatePartLightmapResolutionsIfNeeded(); // make sure the mesh has a new lighting guid - FoundStaticMesh->LightingGuid = FGuid::NewGuid(); + FoundStaticMesh->SetLightingGuid(FGuid::NewGuid()); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Lightmap Resolutions in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } //--------------------------------------------------------------------------------------------------------------------- // INDICES @@ -1963,6 +2076,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() TArray< int32 > NeededVertices; RawMesh.WedgeIndices.SetNumZeroed(SplitVertexCount); + bool bHasInvalidFaceIndices = false; int32 ValidVertexId = 0; for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) { @@ -1983,9 +2097,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() || !IndicesMapper.IsValidIndex(WedgeIndices[2])) { // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + bHasInvalidFaceIndices = true; continue; } @@ -2039,6 +2151,19 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() ValidVertexId += 3; } + if (bHasInvalidFaceIndices) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + //--------------------------------------------------------------------------------------------------------------------- // POSITIONS //--------------------------------------------------------------------------------------------------------------------- @@ -2053,17 +2178,14 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // int32 VertexPositionsCount = NeededVertices.Num(); RawMesh.VertexPositions.SetNumZeroed(VertexPositionsCount); - + bool bHasInvalidPositionIndexData = false; for (int32 VertexPositionIdx = 0; VertexPositionIdx < VertexPositionsCount; ++VertexPositionIdx) { int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) { // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + bHasInvalidPositionIndexData = true; continue; } @@ -2074,6 +2196,13 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() RawMesh.VertexPositions[VertexPositionIdx].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; } + if (bHasInvalidPositionIndexData) + { + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } /* // TODO: // Check if this mesh contains only degenerate triangles. @@ -2086,6 +2215,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() continue; } */ + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } } //--------------------------------------------------------------------------------------------------------------------- @@ -2096,6 +2231,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // Need to update!! UpdatePartFaceMaterialOverridesIfNeeded(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Material Overrides in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + //--------------------------------------------------------------------------------------------------------------------- // FACE MATERIALS //--------------------------------------------------------------------------------------------------------------------- @@ -2106,21 +2247,34 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // Get face indices for this split. TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; - // We need to reset the Static Mesh's materials once per SM: - // so, for the first lod, or the main geo... - if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + // Fetch the FoundMesh's Static Materials array + TArray& FoundStaticMaterials = FoundStaticMesh->GetStaticMaterials(); + + // // We need to reset the Static Mesh's materials once per SM: + // // so, for the first lod, or the main geo... + // if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + // { + // FoundStaticMaterials.Empty(); + // MeshMaterialsHaveBeenReset = true; + // } + // + // // .. or for each visible complex collider + // if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + // FoundStaticMaterials.Empty(); + + // Clear the materials array of the mesh the first time we encounter it + if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) { - FoundStaticMesh->StaticMaterials.Empty(); - MeshMaterialsHaveBeenReset = true; + FoundStaticMaterials.Empty(); } - - // .. or for each visible complex collider - if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - FoundStaticMesh->StaticMaterials.Empty(); + TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); // Process material overrides first if (PartFaceMaterialOverrides.Num() > 0) { + // Array used to avoid constantly attempting to load invalid materials + TArray InvalidMaterials; + // If the part has material overrides RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) @@ -2129,30 +2283,31 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) continue; - const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); + UMaterialInterface * MaterialInterface = nullptr; int32 CurrentFaceMaterialIdx = 0; - if (FoundFaceMaterialIdx) - { - // We already know what material index to use for that override - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else + const FString& MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) { // Try to locate the corresponding material interface - UMaterialInterface * MaterialInterface = nullptr; // Start by looking in our assignment map - auto FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); if (FoundMaterialInterface) MaterialInterface = *FoundMaterialInterface; - if (!MaterialInterface && !MaterialName.IsEmpty()) + if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) { // Only try to load a material if has a chance to be valid! MaterialInterface = Cast( StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + + if (!MaterialInterface) + InvalidMaterials.Add(MaterialName); } if (MaterialInterface) @@ -2166,9 +2321,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() if (ReplacementMaterialInterface && *ReplacementMaterialInterface) MaterialInterface = *ReplacementMaterialInterface; - // Add this material to the map - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); + MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); } else { @@ -2179,13 +2332,10 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - CurrentFaceMaterialIdx = *FoundUnrealMatIndex; - } - else + FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + if (!MaterialInterface) { // If everything fails, we'll use the default material MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); @@ -2202,17 +2352,30 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() if (ReplacementMaterial && *ReplacementMaterial) MaterialInterface = *ReplacementMaterial; - // Add the material to the Static mesh - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); } } } - // Update the Face Material on the mesh - RawMesh.FaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; + if (MaterialInterface) + { + int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); + } + + // Update the Face Material on the mesh + RawMesh.FaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; + } } } else if (PartUniqueMaterialIds.Num() > 0) @@ -2238,8 +2401,8 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() if (ReplacementMaterial && *ReplacementMaterial) MaterialInterface = *ReplacementMaterial; - FoundStaticMesh->StaticMaterials.Empty(); - FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); } else { @@ -2259,35 +2422,50 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + UMaterialInterface* MaterialInterface = nullptr; + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (MaterialInterface) { - // This material has been mapped already, just assign the mat index - RawMesh.FaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; - continue; + int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + RawMesh.FaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; + continue; + } } + else + { + MaterialInterface = Cast(DefaultMaterial); - UMaterialInterface * MaterialInterface = Cast(DefaultMaterial); + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } - // Add the material to the Static mesh - int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + if (MaterialInterface) + { + // Add the material to the Static mesh + int32 UnrealMatIndex = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); - // Map the houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); + // Map the houdini ID to the unreal one + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); - // Update the face index - RawMesh.FaceMaterialIndices[FaceIdx] = UnrealMatIndex; + // Update the face index + RawMesh.FaceMaterialIndices[FaceIdx] = UnrealMatIndex; + } } } } @@ -2304,23 +2482,22 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() if (ReplacementMaterial && *ReplacementMaterial) MaterialInterface = *ReplacementMaterial; - FoundStaticMesh->StaticMaterials.Empty(); - FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); } - // TODO: - // BUILD SETTINGS - // (Using default for now) - SrcModel->BuildSettings.bRemoveDegenerates = true; - SrcModel->BuildSettings.bUseMikkTSpace = true; - SrcModel->BuildSettings.bBuildAdjacencyBuffer = false; - SrcModel->BuildSettings.MinLightmapResolution = 64; - SrcModel->BuildSettings.bUseFullPrecisionUVs = false; - SrcModel->BuildSettings.SrcLightmapIndex = 0; - SrcModel->BuildSettings.DstLightmapIndex = 1; - SrcModel->BuildSettings.bRecomputeNormals = (0 == RawMesh.WedgeTangentZ.Num()); - SrcModel->BuildSettings.bRecomputeTangents = (0 == RawMesh.WedgeTangentX.Num() || 0 == RawMesh.WedgeTangentY.Num()); - SrcModel->BuildSettings.bGenerateLightmapUVs = RawMesh.WedgeTexCoords->Num() <= 0; + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Face Materials in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // Update the Build Settings using the default setting values + UpdateMeshBuildSettings( + SrcModel->BuildSettings, + RawMesh.WedgeTangentZ.Num() > 0, + (RawMesh.WedgeTangentX.Num() > 0 && RawMesh.WedgeTangentY.Num() > 0), + RawMesh.WedgeTexCoords->Num() > 0); // Check for a lightmap resolution override int32 LightMapResolutionOverride = -1; @@ -2328,23 +2505,85 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() LightMapResolutionOverride = PartLightMapResolutions[0]; if (LightMapResolutionOverride > 0) - FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; + FoundStaticMesh->SetLightMapResolution(LightMapResolutionOverride); else - FoundStaticMesh->LightMapResolution = 64; + FoundStaticMesh->SetLightMapResolution(64); + + // TODO + //StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio; + //StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier; + //StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings; // TODO: // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? - // By default the distance field resolution should be set to 2.0 - // TODO should come from the HAC - //SrcModel->BuildSettings.DistanceFieldResolutionScale = HoudiniCookParams.GeneratedDistanceFieldResolutionScale; - SrcModel->BuildSettings.DistanceFieldResolutionScale = 2.0; - - // This is required due to the impeding deprecation of FRawMesh + // This was required due to the impeding deprecation of FRawMesh // If we dont update this UE4 will crash upon deleting an asset. - SrcModel->StaticMeshOwner = FoundStaticMesh; - // Store the new raw mesh. - SrcModel->SaveRawMesh(RawMesh); + //SrcModel->StaticMeshOwner = FoundStaticMesh; + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - PreSaveRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // Store the new raw mesh if it is valid + if (RawMesh.IsValid()) + { + SrcModel->SaveRawMesh(RawMesh); + } + else + { + HOUDINI_LOG_WARNING( + TEXT("[CreateStaticMesh_RawMesh]: Invalid StaticMesh data for %s LOD %i in cook output! Please check the log."), + *FoundStaticMesh->GetName(), LODIndex); + // Create an "empty" valid raw mesh (single zero-area triangle) + // TODO: is there a cleaner way to do this? Perhaps committing an empty mesh description? Empty RawMesh is + // a no-op on SrcModel->SaveRawMesh (leaves previous data in place). + // TODO: perhaps we can use an alternative "error" mesh? + RawMesh.Empty(); + RawMesh.VertexPositions.Add(FVector::ZeroVector); + RawMesh.WedgeIndices.SetNumZeroed(3); + RawMesh.WedgeTexCoords[0].Init(FVector2D::ZeroVector, RawMesh.WedgeIndices.Num()); + SrcModel->SaveRawMesh(RawMesh); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - SaveRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + { + // Patch the MeshDescription data structure that is being output from SaveRawMesh. SaveRawMesh leaves invalid entries + // in the PolyGroups / MaterialSlotNames arrays that causes issues later when the static mesh is built and LOD material assignments + // are being done (materials aren't correctly assigned to LODs if LODs use different materials). + + // Create a Polygon Group for each material slot + TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = + SrcModel->MeshDescription->PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); + + // We must use the number of assignment materials found to reserve the number of material slots + // Don't use the SM's StaticMaterials here as we may not reserve enough polygon groups when adding more materials + int32 NumberOfMaterials = FoundStaticMaterials.Num(); + if (NumberOfMaterials <= 0) + { + // No materials, create a polygon group for the default one + const FPolygonGroupID& PolygonGroupID = SrcModel->MeshDescription->CreatePolygonGroup(); + PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + } + else + { + FPolygonGroupArray& PolyGroups = SrcModel->MeshDescription->PolygonGroups(); + for (auto& CurrentMatAssignment : OutputAssignmentMaterials) + { + const FPolygonGroupID& PolygonGroupID = SrcModel->MeshDescription->CreatePolygonGroup(); + + PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = + FName(CurrentMatAssignment.Value ? *(CurrentMatAssignment.Value->GetName()) : *(CurrentMatAssignment.Key)); + } + } + } // LOD Screensize // default values has already been set, see if we have any attribute override for this @@ -2365,18 +2604,20 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // Update property attributes on the SM TArray PropertyAttributes; - if (GetGenericPropertiesAttributes( + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( HGPO.GeoId, HGPO.PartId, - AllSplitFirstValidVertexIndex[SplitGroupName], + true, AllSplitFirstValidPrimIndex[SplitGroupName], + INDEX_NONE, + AllSplitFirstValidVertexIndex[SplitGroupName], PropertyAttributes)) { - UpdateGenericPropertiesAttributes( + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( FoundStaticMesh, PropertyAttributes); } TArray LevelPaths; - if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) + if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths, HAPI_ATTROWNER_INVALID, 0, 1)) { if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) { @@ -2386,7 +2627,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } TArray OutputNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) + if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames, 0, 1)) { if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) { @@ -2395,8 +2636,18 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } } + TArray BakeNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeNameAttribute(HGPO.GeoId, HGPO.PartId, BakeNames, 0, 1)) + { + if (BakeNames.Num() > 0 && !BakeNames[0].IsEmpty()) + { + // cache the bake name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_NAME, BakeNames[0]); + } + } + TArray TileValues; - if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) + if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues, HAPI_ATTROWNER_INVALID, 0, 1)) { if (TileValues.Num() > 0 && TileValues[0] >= 0) { @@ -2406,7 +2657,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } TArray BakeOutputActorNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames, HAPI_ATTROWNER_INVALID, 0, 1)) { if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) { @@ -2415,8 +2666,18 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } } + TArray BakeFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, BakeFolders, HGPO.PartId, 0, 1)) + { + if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) + { + // cache the unreal_bake_folder attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); + } + } + TArray BakeOutlinerFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders, HAPI_ATTROWNER_INVALID, 0, 1)) { if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) { @@ -2425,6 +2686,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Attributes in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + // Notify that we created a new Static Mesh if needed if (bNewStaticMeshCreated) FAssetRegistryModule::AssetCreated(FoundStaticMesh); @@ -2438,6 +2705,13 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Finished Split in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Total Split time: %f seconds."), tick - split_tick); + } } // Look if we only have colliders @@ -2456,21 +2730,23 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() FHoudiniScopedGlobalSilence ScopedGlobalSilence; for (auto& Current : StaticMeshToBuild) { + tick = FPlatformTime::Seconds(); + UStaticMesh* SM = Current.Value; - if (!SM || SM->IsPendingKill()) + if (!IsValid(SM)) continue; - UBodySetup * BodySetup = SM->BodySetup; + UBodySetup * BodySetup = SM->GetBodySetup(); if (!BodySetup) { SM->CreateBodySetup(); - BodySetup = SM->BodySetup; + BodySetup = SM->GetBodySetup(); } EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); // Handle the Static Mesh's colliders - if (BodySetup && !BodySetup->IsPendingKill()) + if (IsValid(BodySetup)) { // Make sure rendering is done - so we are not changing data being used by collision drawing. FlushRenderingCommands(); @@ -2478,8 +2754,6 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // Clean up old colliders from a previous cook BodySetup->Modify(); BodySetup->RemoveSimpleCollision(); - // Create new GUID - BodySetup->InvalidatePhysicsData(); FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); @@ -2489,14 +2763,19 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; } - RefreshCollisionChange(*SM); - SM->bCustomizedCollision = true; - // See if we need to enable collisions on the whole mesh if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) { - // Complex collider, enable collisions for this static mesh. + // Complex collider, enable collisions for this (collider) static mesh. BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + + // Apply the collider to the Main static mesh, if relevant. + ApplyComplexColliderHelper( + MainStaticMesh, + SM, + SplitType, + bAssignedCustomCollisionMesh, + OutputObjects.Find(Current.Key)); } else { @@ -2516,72 +2795,81 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; if (bAddSocket) { - if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, AllSockets, true)) + if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, HGPO.AllMeshSockets, true)) { HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); } } + if (MainStaticMesh) + { + UBodySetup* MainBodySetup = MainStaticMesh->GetBodySetup(); + if (!IsValid(MainBodySetup)) + { + MainStaticMesh->CreateBodySetup(); + MainBodySetup = MainStaticMesh->GetBodySetup(); + } + + check(MainBodySetup); + // Set the main static mesh to whatever the final CTF should be. + MainBodySetup->CollisionTraceFlag = MainStaticMeshCTF; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Pre SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + // BUILD the Static Mesh // bSilent doesnt add the Build Errors... double build_start = FPlatformTime::Seconds(); TArray SMBuildErrors; SM->Build(true, &SMBuildErrors); - double build_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->Build() executed in %f seconds."), build_end - build_start); - - SM->GetOnMeshChanged().Broadcast(); - - /* - // Try to find the outer package so we can dirty it up - if (SM->GetOuter()) + if (bDoTiming) { - SM->GetOuter()->MarkPackageDirty(); + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - StaticMesh->Build() executed in %f seconds."), tick - build_start); } - else + + // This replaces the call to RefreshCollision below, but without CreateNavCollision + // as it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, + // and can be expensive depending on the vert/poly count of the mesh + // RefreshCollisionChange(*SM); { - SM->MarkPackageDirty(); + for (FThreadSafeObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) + { + UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); + if (StaticMeshComponent->GetStaticMesh() == SM) + { + // it needs to recreate IF it already has been created + if (StaticMeshComponent->IsPhysicsStateCreated()) + { + StaticMeshComponent->RecreatePhysicsState(); + } + } + } + + FEditorSupportDelegates::RedrawAllViewports.Broadcast(); } - */ - + + SM->GetOnMeshChanged().Broadcast(); UPackage* MeshPackage = SM->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) + if (IsValid(MeshPackage)) { MeshPackage->MarkPackageDirty(); - - /* - // DPT: deactivated auto saving mesh/material package - // only dirty for now, as we'll save them when saving the world. - TArray PackageToSave; - PackageToSave.Add(MeshPackage); - - // Save the created package - FEditorFileUtils::PromptForCheckoutAndSave(PackageToSave, false, false); - */ } - } - // TODO: Still necessary ? SM->Build should actually update the navmesh... - // Now that all the meshes are built and their collisions meshes and primitives updated, - // we need to update their pre-built navigation collision used by the navmesh - for (auto& Iter : OutputObjects) - { - UStaticMesh* StaticMesh = Cast(Iter.Value.OutputObject); - if (!StaticMesh || StaticMesh->IsPendingKill()) - continue; - - UBodySetup * BodySetup = StaticMesh->BodySetup; - if (BodySetup && !BodySetup->IsPendingKill() && StaticMesh->NavCollision) + if (bDoTiming) { - // Unreal caches the Navigation Collision and never updates it for StaticMeshes, - // so we need to manually flush and recreate the data to have proper navigation collision - BodySetup->InvalidatePhysicsData(); - BodySetup->CreatePhysicsMeshes(); - StaticMesh->NavCollision->Setup(BodySetup); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Post SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); } } + // !!! No need to call InvalidatePhysicsData / CreatePhysicsMeshes / GetNavCollision()->Setup + // Here as it has already been handled by the StaticMesh Build call + double time_end = FPlatformTime::Seconds(); HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() executed in %f seconds."), time_end - time_start); @@ -2591,6 +2879,9 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() bool FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() { + // Time limit for processing + bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; + double time_start = FPlatformTime::Seconds(); // Start by updating the vertex list @@ -2644,28 +2935,30 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // New mesh list TMap StaticMeshToBuild; - // Map of Houdini Material IDs to Unreal Material Indices - TMap< HAPI_NodeId, int32 > MapHoudiniMatIdToUnrealIndex; - // Map of Houdini Material Attributes to Unreal Material Indices - TMap< FString, int32 > MapHoudiniMatAttributesToUnrealIndex; + // Map of Houdini Material IDs to Unreal Material Interface + TMap MapHoudiniMatIdToUnrealInterface; + // Map of Houdini Material Attributes to Unreal Material Interface + TMap MapHoudiniMatAttributesToUnrealInterface; + // Map of Unreal Material Interface to Unreal Material Index, per visible mesh + TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; bool MeshMaterialsHaveBeenReset = false; - // Mesh Socket array - TArray AllSockets; - FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - double tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre Split-Loop in %f seconds."), tick - time_start); + if (bDoTiming) + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre Split-Loop in %f seconds."), tick - time_start); + + UStaticMesh* MainStaticMesh = nullptr; + bool bAssignedCustomCollisionMesh = false; + ECollisionTraceFlag MainStaticMeshCTF = StaticMeshGenerationProperties.GeneratedCollisionTraceFlag; // Iterate through all detected split groups we care about and split geometry. // The split are ordered in the following way: // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) { + double split_tick = FPlatformTime::Seconds(); + // Get split group name const FString& SplitGroupName = AllSplitGroups[SplitId]; @@ -2718,6 +3011,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // Create the convex hull colliders and add them to the Aggregate if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; // Failed to generate a convex collider HOUDINI_LOG_WARNING( TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), @@ -2730,6 +3024,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; // Get the part position if needed UpdatePartPositionIfNeeded(); @@ -2773,7 +3068,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() { // If we couldn't find a valid existing static mesh, create a new one FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + if (!IsValid(FoundStaticMesh)) continue; bNewStaticMeshCreated = true; @@ -2788,6 +3083,13 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); } + if (SplitType == EHoudiniSplitType::Normal) + { + MainStaticMesh = FoundStaticMesh; + MainStaticMesh->ComplexCollisionMesh = nullptr; + MainStaticMesh->bCustomizedCollision = false; + } + if (!FoundOutputObject) { FHoudiniOutputObject NewOutputObject; @@ -2827,7 +3129,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() { FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); } - FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); + FoundStaticMesh->SetLightMapResolution(LODGroup.GetDefaultLightMapResolution()); } // By default, always work on the first source model, unless we're a LOD @@ -2863,14 +3165,16 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() continue; } - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - PreMeshDescription in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - PreMeshDescription in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } - bool bRecomputeNormal = false; - bool bRecomputeTangent = false; + bool bHasNormal = false; + bool bHasTangents = false; - // Load the existing mesh description if we don't need to rebuild the mesh - //FRawMesh RawMesh; + // Load the existing mesh description if we don't need to rebuild the mesh FMeshDescription* MeshDescription; if (!bRebuildStaticMesh) { @@ -2922,6 +3226,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() int32 CurrentSplitIndex = 0; int32 ValidVertexId = 0; + bool bHasInvalidFaceIndices = false; for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) { int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; @@ -2941,9 +3246,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[2])) { // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + bHasInvalidFaceIndices = true; continue; } @@ -2973,9 +3276,19 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() ValidVertexId += 3; } + + if (bHasInvalidFaceIndices) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } //--------------------------------------------------------------------------------------------------------------------- // POSITIONS @@ -2992,7 +3305,8 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // TVertexAttributesRef VertexPositions = MeshDescription->VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); - + + bool bHasInvalidPositionIndexData = false; MeshDescription->ReserveNewVertices(SplitNeededVertices.Num()); for ( const int32& NeededVertexIndex : SplitNeededVertices) { @@ -3008,34 +3322,51 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() else { // Error when retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + bHasInvalidPositionIndexData = true; continue; } } - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bHasInvalidPositionIndexData) + { + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } //--------------------------------------------------------------------------------------------------------------------- // MATERIALS //--------------------------------------------------------------------------------------------------------------------- + + TArray& FoundStaticMaterials = FoundStaticMesh->GetStaticMaterials(); + + // // TODO: Check if still needed for MeshDescription + // // We need to reset the Static Mesh's materials once per SM: + // // so, for the first lod, or the main geo... + // if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + // { + // FoundStaticMaterials.Empty(); + // MeshMaterialsHaveBeenReset = true; + // } + // + // // .. or for each visible complex collider + // if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + // FoundStaticMaterials.Empty(); - // TODO: Check if still needed for MeshDescription - // We need to reset the Static Mesh's materials once per SM: - // so, for the first lod, or the main geo... - if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + // Clear the materials array of the mesh the first time we encounter it + if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) { - FoundStaticMesh->StaticMaterials.Empty(); - MeshMaterialsHaveBeenReset = true; + FoundStaticMaterials.Empty(); } - - // .. or for each visible complex collider - if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - FoundStaticMesh->StaticMaterials.Empty(); + TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); // Get this split's faces TArray& SplitGroupFaceIndices = AllSplitFaceIndices[SplitGroupName]; @@ -3058,7 +3389,8 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() if (ReplacementMaterial && *ReplacementMaterial) MaterialInterface = *ReplacementMaterial; - FoundStaticMesh->StaticMaterials.Add(MaterialInterface); + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(MaterialInterface); // TODO: ? Add default mat to the assignement map? } @@ -3083,7 +3415,8 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() if (ReplacementMaterial && *ReplacementMaterial) MaterialInterface = *ReplacementMaterial; - FoundStaticMesh->StaticMaterials.Add(MaterialInterface); + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(MaterialInterface); // TODO: ? Add the mat to the assignement map? } @@ -3104,65 +3437,91 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + UMaterialInterface* MaterialInterface = nullptr; + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (MaterialInterface) { - // This material has been mapped already, just use its material index - SplitFaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; - continue; + int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + SplitFaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; + continue; + } } + else + { + MaterialInterface = Cast(MaterialDefault); - UMaterialInterface * MaterialInterface = Cast(MaterialDefault); + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } - // Add the material to the Static mesh - //int32 UnrealMatIndex = SplitMaterials.Add(Material); - int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(MaterialInterface); + if (MaterialInterface) + { + // Add the material to the Static mesh + //int32 UnrealMatIndex = SplitMaterials.Add(Material); + int32 UnrealMatIndex = FoundStaticMaterials.Add(MaterialInterface); - // Map the houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); + // Map the houdini ID to the unreal one + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); - // Update the face index - SplitFaceMaterialIndices[FaceIdx] = UnrealMatIndex; + // Update the face index + SplitFaceMaterialIndices[FaceIdx] = UnrealMatIndex; + } } } } else { + // Array used to avoid constantly attempting to load invalid materials + TArray InvalidMaterials; + // If we have material overrides for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) { int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; + UMaterialInterface * MaterialInterface = nullptr; int32 CurrentFaceMaterialIdx = -1; if (PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) { const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); - if (FoundFaceMaterialIdx) - { - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) { // Try to locate the corresponding material interface - UMaterialInterface * MaterialInterface = nullptr; - if (!MaterialName.IsEmpty()) + + // Start by looking in our assignment map + FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) { // Only try to load a material if has a chance to be valid! MaterialInterface = Cast< UMaterialInterface >( StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + + if (!MaterialInterface) + InvalidMaterials.Add(MaterialName); } if (MaterialInterface) @@ -3177,12 +3536,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() MaterialInterface = *ReplacementMaterialInterface; // Add this material to the map - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); + MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); } } - if (CurrentFaceMaterialIdx < 0) + if (!MaterialInterface) { // The attribute Material or its replacement do not exist // See if we can fallback to the Houdini material assigned on the face @@ -3191,16 +3549,14 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - CurrentFaceMaterialIdx = *FoundUnrealMatIndex; - } - else + FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) { // If everything else fails, we'll use the default material - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); // We need to add this material to the map FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; @@ -3214,15 +3570,25 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() if (ReplacementMaterialInterface && *ReplacementMaterialInterface) MaterialInterface = *ReplacementMaterialInterface; - // Add the material to the Static mesh - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); } } } + int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); + } + // Update the Face Material on the mesh SplitFaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; } @@ -3253,8 +3619,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } } - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Materials in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Materials in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } // // VERTEX INSTANCE ATTRIBUTES @@ -3270,23 +3639,23 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() TVertexInstanceAttributesRef VertexInstanceNormals = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); - // Extract the tangents // No need to read the tangents if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; + + // Extract the tangents TArray SplitTangentU; TArray SplitTangentV; - bool bReadTangents = true; - // TODO: Add runtime setting check! - //bool bReadTangents = HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; if (bReadTangents) { // Extract this part's Tangents if needed UpdatePartTangentsIfNeeded(); - // Get the Tangents for this split + // Get the Tangents for this split FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); - // Get the binormals for this split + // Get the binormals for this split FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); @@ -3299,14 +3668,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() if (SplitTangentU.Num() != NormalCount || SplitTangentV.Num() != NormalCount) bGenerateTangents = true; - /* - // TODO: Add settings check! if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) { // No need to generate tangents if we want unreal to recompute them after bGenerateTangents = false; } - */ // Generate the tangents if needed if (bGenerateTangents) @@ -3365,8 +3731,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() TVertexInstanceAttributesRef VertexInstanceUVs = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); VertexInstanceUVs.SetNumIndices(UVSetCount); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr extracted in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr extracted in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } // Allocate space for the vertex instances and polygons MeshDescription->ReserveNewVertexInstances(SplitIndices.Num()); @@ -3374,15 +3743,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() //Approximately 2.5 edges per polygons MeshDescription->ReserveNewEdges(SplitIndices.Num() * 2.5f / 3); - bool bHasNormal = SplitNormals.Num() > 0; - bool bHasTangents = SplitTangentU.Num() > 0 && SplitTangentV.Num() > 0; + bHasNormal = SplitNormals.Num() > 0; + bHasTangents = SplitTangentU.Num() > 0 && SplitTangentV.Num() > 0; bool bHasRGB = SplitColors.Num() > 0; bool bHasRGBA = bHasRGB && AttribInfoColors.tupleSize == 4; bool bHasAlpha = SplitAlphas.Num() > 0; - bRecomputeNormal = !bHasNormal; - bRecomputeTangent = !bHasTangents; - TArray HasUVSets; HasUVSets.SetNumZeroed(PartUVSets.Num()); for (int32 Idx = 0; Idx < PartUVSets.Num(); Idx++) @@ -3491,8 +3857,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() MeshDescription->CreateTriangle(PolygonGroupID, FaceVertexInstanceIDs); } - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr filled in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr filled in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } //--------------------------------------------------------------------------------------------------------------------- // FACE SMOOTHING @@ -3531,8 +3900,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // Check FStaticMeshOperations::ConvertSmoothGroupToHardEdges(FaceSmoothingMasks, *MeshDescription); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - FaceSoothing filled in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - FaceSoothing filled in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } //--------------------------------------------------------------------------------------------------------------------- // LIGHTMAP RESOLUTION @@ -3541,26 +3913,19 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() UpdatePartLightmapResolutionsIfNeeded(); // make sure the mesh has a new lighting guid - FoundStaticMesh->LightingGuid = FGuid::NewGuid(); + FoundStaticMesh->SetLightingGuid(FGuid::NewGuid()); } - // TODO: - // BUILD SETTINGS - // (Using default for now) - SrcModel->BuildSettings.bRemoveDegenerates = true; - SrcModel->BuildSettings.bUseMikkTSpace = true; - SrcModel->BuildSettings.bBuildAdjacencyBuffer = false; - SrcModel->BuildSettings.MinLightmapResolution = 64; - SrcModel->BuildSettings.bUseFullPrecisionUVs = false; - SrcModel->BuildSettings.SrcLightmapIndex = 0; - SrcModel->BuildSettings.DstLightmapIndex = 1; - SrcModel->BuildSettings.bRecomputeNormals = bRecomputeNormal; - SrcModel->BuildSettings.bRecomputeTangents = bRecomputeNormal || bRecomputeTangent; - SrcModel->BuildSettings.bGenerateLightmapUVs = PartUVSets.Num() <= 0; + // Update the Build Settings using the default setting values + UpdateMeshBuildSettings( + SrcModel->BuildSettings, + bHasNormal, + bHasTangents, + PartUVSets.Num() > 0); // Set the lightmap Coordinate Index // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention - FoundStaticMesh->LightMapCoordinateIndex = PartUVSets.Num() > 1 ? 1 : 0; + FoundStaticMesh->SetLightMapCoordinateIndex(PartUVSets.Num() > 1 ? 1 : 0); // Check for a lightmapa resolution override int32 LightMapResolutionOverride = -1; @@ -3568,18 +3933,13 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() LightMapResolutionOverride = PartLightMapResolutions[0]; if (LightMapResolutionOverride > 0) - FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; + FoundStaticMesh->SetLightMapResolution(LightMapResolutionOverride); else - FoundStaticMesh->LightMapResolution = 64; + FoundStaticMesh->SetLightMapResolution(64); // TODO: // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? - // By default the distance field resolution should be set to 2.0 - // TODO should come from the HAC - //SrcModel->BuildSettings.DistanceFieldResolutionScale = HoudiniCookParams.GeneratedDistanceFieldResolutionScale; - SrcModel->BuildSettings.DistanceFieldResolutionScale = 2.0; - // RAW MESH CHECKS // TODO: Check not needed w/ FMeshDesc @@ -3587,6 +3947,14 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // If we dont update this UE4 will crash upon deleting an asset. //SrcModel->StaticMeshOwner = FoundStaticMesh; + // Check if the mesh has at least one triangle, if not, log a message + if (MeshDescription->Triangles().Num() == 0) + { + HOUDINI_LOG_WARNING( + TEXT("[CreateStaticMesh_MeshDescription]: 0 valid triangles in StaticMesh data for %s LOD %i! Please check the log."), + *FoundStaticMesh->GetName(), LODIndex); + } + // Store the new MeshDescription FoundStaticMesh->CommitMeshDescription(LODIndex); //Set the Imported version before calling the build @@ -3611,18 +3979,20 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // UPDATE UPROPERTY ATTRIBS // Update property attributes on the SM TArray PropertyAttributes; - if (GetGenericPropertiesAttributes( + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( HGPO.GeoId, HGPO.PartId, - AllSplitFirstValidVertexIndex[SplitGroupName], + true, AllSplitFirstValidPrimIndex[SplitGroupName], + INDEX_NONE, + AllSplitFirstValidVertexIndex[SplitGroupName], PropertyAttributes)) { - UpdateGenericPropertiesAttributes( + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( FoundStaticMesh, PropertyAttributes); } TArray LevelPaths; - if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) + if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths, HAPI_ATTROWNER_INVALID, 0, 1)) { if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) { @@ -3632,7 +4002,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } TArray OutputNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) + if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames, 0, 1)) { if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) { @@ -3641,8 +4011,18 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } } + TArray BakeNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeNameAttribute(HGPO.GeoId, HGPO.PartId, BakeNames, 0, 1)) + { + if (BakeNames.Num() > 0 && !BakeNames[0].IsEmpty()) + { + // cache the bake name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_NAME, BakeNames[0]); + } + } + TArray TileValues; - if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) + if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues, HAPI_ATTROWNER_INVALID, 0, 1)) { if (TileValues.Num() > 0 && TileValues[0] >= 0) { @@ -3652,7 +4032,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } TArray BakeOutputActorNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames, HAPI_ATTROWNER_INVALID, 0, 1)) { if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) { @@ -3661,8 +4041,18 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } } + TArray BakeFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, BakeFolders, HGPO.PartId, 0, 1)) + { + if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) + { + // cache the unreal_bake_folder attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); + } + } + TArray BakeOutlinerFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders, HAPI_ATTROWNER_INVALID, 0, 1)) { if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) { @@ -3680,13 +4070,18 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() { FoundOutputObject->OutputObject = FoundStaticMesh; FoundOutputObject->bProxyIsCurrent = false; + FoundOutputObject->bIsImplicit = false; OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); } StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Finished MD in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Finished Split in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Total Split time: %f seconds."), tick - split_tick); + } } // Look if we only have colliders @@ -3704,22 +4099,24 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() FHoudiniScopedGlobalSilence ScopedGlobalSilence; for (auto& Current : StaticMeshToBuild) - { + { + tick = FPlatformTime::Seconds(); + UStaticMesh* SM = Current.Value; - if (!SM || SM->IsPendingKill()) + if (!IsValid(SM)) continue; - UBodySetup * BodySetup = SM->BodySetup; + UBodySetup * BodySetup = SM->GetBodySetup(); if (!BodySetup) { SM->CreateBodySetup(); - BodySetup = SM->BodySetup; + BodySetup = SM->GetBodySetup(); } EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); // Handle the Static Mesh's colliders - if (BodySetup && !BodySetup->IsPendingKill()) + if (IsValid(BodySetup)) { // Make sure rendering is done - so we are not changing data being used by collision drawing. FlushRenderingCommands(); @@ -3740,13 +4137,19 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // Moved RefreshCollisionChange to after the SM->Build call // RefreshCollisionChange(*SM); - SM->bCustomizedCollision = true; + // SM->bCustomizedCollision = true; // See if we need to enable collisions on the whole mesh if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) { // Complex collider, enable collisions for this static mesh. BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + ApplyComplexColliderHelper( + MainStaticMesh, + SM, + SplitType, + bAssignedCustomCollisionMesh, + OutputObjects.Find(Current.Key)); } else { @@ -3766,32 +4169,51 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; if (bAddSocket) { - if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, AllSockets, true)) + if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, HGPO.AllMeshSockets, true)) { HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); } } + if (MainStaticMesh) + { + UBodySetup* MainBodySetup = MainStaticMesh->GetBodySetup(); + if (!IsValid(MainBodySetup)) + { + MainStaticMesh->CreateBodySetup(); + MainBodySetup = MainStaticMesh->GetBodySetup(); + } + + check(MainBodySetup); + // Set the main static mesh to whatever the final CTF should be. + MainBodySetup->CollisionTraceFlag = MainStaticMeshCTF; + } + + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + // BUILD the Static Mesh // bSilent doesnt add the Build Errors... double build_start = FPlatformTime::Seconds(); TArray SMBuildErrors; - SM->Build(true, &SMBuildErrors); - double build_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->Build() executed in %f seconds."), build_end - build_start); + SM->Build(true, &SMBuildErrors); - // TODO: copied the content of RefreshCollision below and commented out CreateNavCollision - // it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, + if (bDoTiming) + { + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - StaticMesh->Build() executed in %f seconds."), tick - build_start); + } + + // This replaces the call to RefreshCollision below, but without CreateNavCollision + // as it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, // and can be expensive depending on the vert/poly count of the mesh - // TODO: also moved this to after the call to Build, since Build updates the mesh's - // physics state (calling this before Build when rebuilding an existing high poly mesh as - // low poly mesh, incurs quite a performance hit. This is likely due to processing of physics - // meshes with high vert/poly count before the Build // RefreshCollisionChange(*SM); { - // SM->CreateNavCollision(/*bIsUpdate=*/true); - - for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) + for (FThreadSafeObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) { UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); if (StaticMeshComponent->GetStaticMesh() == SM) @@ -3808,62 +4230,22 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } SM->GetOnMeshChanged().Broadcast(); - /* - // Try to find the outer package so we can dirty it up - if (SM->GetOuter()) - { - SM->GetOuter()->MarkPackageDirty(); - } - else - { - SM->MarkPackageDirty(); - } - */ UPackage* MeshPackage = SM->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) + if (IsValid(MeshPackage)) { MeshPackage->MarkPackageDirty(); - /* - // DPT: deactivated auto saving mesh/material package - // only dirty for now, as we'll save them when saving the world. - TArray PackageToSave; - PackageToSave.Add(MeshPackage); + } - // Save the created package - FEditorFileUtils::PromptForCheckoutAndSave(PackageToSave, false, false); - */ + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Post SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); } } - // TODO: Still necessary ? SM->Build should actually update the navmesh... - // TODO: Commented out for now, since it appears that the content of the loop is - // already called in UStaticMesh::BuildInternal and UStaticMesh::PostBuildInternal - //// Now that all the meshes are built and their collisions meshes and primitives updated, - //// we need to update their pre-built navigation collision used by the navmesh - //for (auto& Iter : OutputObjects) - //{ - // UStaticMesh* StaticMesh = Cast(Iter.Value.OutputObject); - // if (!StaticMesh || StaticMesh->IsPendingKill()) - // continue; - - // UBodySetup * BodySetup = StaticMesh->BodySetup; - // if (BodySetup && !BodySetup->IsPendingKill() && StaticMesh->NavCollision) - // { - // // Unreal caches the Navigation Collision and never updates it for StaticMeshes, - // // so we need to manually flush and recreate the data to have proper navigation collision - // // TODO: Is this still required? These two functions are called by - // // UStaticMesh::BuildInternal, which is called by UStaticMesh::Build/BatchBuild - // // BodySetup->InvalidatePhysicsData(); - // // BodySetup->CreatePhysicsMeshes(); - - // // TODO: Is this still required? This function is called by UStaticMesh::CreateNavCollision - // // which is called by the UStaticMesh::PostBuildInternal function, which is called at the - // // end of the build. - // // StaticMesh->NavCollision->Setup(BodySetup); - // } - //} - + // !!! No need to call InvalidatePhysicsData / CreatePhysicsMeshes / GetNavCollision()->Setup + // Here as it has already been handled by the StaticMesh Build call + double time_end = FPlatformTime::Seconds(); HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() executed in %f seconds."), time_end - time_start); @@ -3873,6 +4255,9 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() bool FHoudiniMeshTranslator::CreateHoudiniStaticMesh() { + // Time limit for processing + bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh")); const double time_start = FPlatformTime::Seconds(); @@ -3919,15 +4304,18 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() } } - // Map of Houdini Material IDs to Unreal Material Indices - TMap< HAPI_NodeId, int32 > MapHoudiniMatIdToUnrealIndex; - // Map of Houdini Material Attributes to Unreal Material Indices - TMap< FString, int32 > MapHoudiniMatAttributesToUnrealIndex; + // Map of Houdini Material IDs to Unreal Material Interface + TMap MapHoudiniMatIdToUnrealInterface; + // Map of Houdini Material Attributes to Unreal Material Interface + TMap MapHoudiniMatAttributesToUnrealInterface; + // Map of Unreal Material Interface to Unreal Material Index, per visible mesh + TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; - bool MeshMaterialsHaveBeenReset = false; + // bool MeshMaterialsHaveBeenReset = false; double tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - Pre Split-Loop in %f seconds."), tick - time_start); + if(bDoTiming) + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - Pre Split-Loop in %f seconds."), tick - time_start); // Iterate through all detected split groups we care about and split geometry. bool bMainGeoOrFirstLODFound = false; @@ -4007,7 +4395,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() FHoudiniOutputObjectIdentifier OutputObjectIdentifier( HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); OutputObjectIdentifier.PartName = HGPO.PartName; - OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], + OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName]; OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; // Try to find existing properties for this identifier @@ -4033,7 +4421,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() { // If we couldn't find a valid existing dynamic mesh, create a new one FoundStaticMesh = CreateNewHoudiniStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + if (!IsValid(FoundStaticMesh)) continue; bNewStaticMeshCreated = true; @@ -4047,8 +4435,11 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() } FoundOutputObject->bProxyIsCurrent = true; - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - PreBuildMesh in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - PreBuildMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } if (bRebuildStaticMesh) { @@ -4085,6 +4476,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() { TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build IndicesMapper and NeededVertices")); + bool bHasInvalidFaceIndices = false; int32 ValidVertexId = 0; for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) { @@ -4104,10 +4496,8 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() || !IndicesMapper.IsValidIndex(WedgeIndices[1]) || !IndicesMapper.IsValidIndex(WedgeIndices[2])) { - // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Dynamic Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + // Invalid face index. Don't log in the loop. + bHasInvalidFaceIndices = true; continue; } @@ -4133,6 +4523,13 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() ValidVertexId += 3; } + + if (bHasInvalidFaceIndices) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Dynamic Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } } //--------------------------------------------------------------------------------------------------------------------- @@ -4160,15 +4557,15 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() // TANGENTS //--------------------------------------------------------------------------------------------------------------------- - TArray< float > SplitTangentU; - TArray< float > SplitTangentV; + TArray SplitTangentU; + TArray SplitTangentV; int32 TangentUCount = 0; int32 TangentVCount = 0; - // No need to read the tangents if we want unreal to recompute them after - // TODO: Add runtime setting check! - //bool bReadTangents = HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; - bool bReadTangents = true; - bool bGenerateTangents = false; + // No need to read the tangents if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; + + bool bGenerateTangentsFromNormalAttribute = false; if (bReadTangents) { // Extract this part's Tangents if needed @@ -4182,28 +4579,33 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); + if ((SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0)) + bReadTangents = false; + // We need to manually generate tangents if: // - we have normals but dont have tangentu or tangentv attributes // - we have not specified that we wanted unreal to generate them - bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); + bGenerateTangentsFromNormalAttribute = (NormalCount > 0) && !bReadTangents; // Check that the number of tangents read matches the number of normals TangentUCount = SplitTangentU.Num() / 3; TangentVCount = SplitTangentV.Num() / 3; - if (TangentUCount != NormalCount || TangentVCount != NormalCount) + if (NormalCount > 0 && (TangentUCount != NormalCount || TangentVCount != NormalCount)) { HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh: Generate tangents due to count mismatch (# U Tangents = %d; # V Tangents = %d; # Normals = %d)"), TangentUCount, TangentVCount, NormalCount); - bGenerateTangents = true; + bGenerateTangentsFromNormalAttribute = true; + bReadTangents = false; } - /* - // TODO: Add settings check! - if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) + if (bGenerateTangentsFromNormalAttribute && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) { // No need to generate tangents if we want unreal to recompute them after - bGenerateTangents = false; + bGenerateTangentsFromNormalAttribute = false; } - */ + } + else + { + bGenerateTangentsFromNormalAttribute = (NormalCount > 0); } //--------------------------------------------------------------------------------------------------------------------- @@ -4269,12 +4671,12 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() FoundStaticMesh->Initialize( NumVertexPositions, NumTriangles, - NumUVLayers, // NumUVLayers - 0, // InitialNumStaticMaterials - NormalCount > 0, // HasNormals - NormalCount > 0 && bReadTangents, // HasTangents - bSplitColorValid, // HasColors - bHasPerFaceMaterials // HasPerFaceMaterials + NumUVLayers, // NumUVLayers + 0, // InitialNumStaticMaterials + NormalCount > 0, // HasNormals + bReadTangents || bGenerateTangentsFromNormalAttribute, // HasTangents + bSplitColorValid, // HasColors + bHasPerFaceMaterials // HasPerFaceMaterials ); //--------------------------------------------------------------------------------------------------------------------- @@ -4292,6 +4694,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() { TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Vertex Positions")); + bool bHasInvalidPositionIndexData = false; for (int32 VertexPositionIdx = 0; VertexPositionIdx < NumVertexPositions; ++VertexPositionIdx) //ParallelFor(NumVertexPositions, [&](uint32 VertexPositionIdx) { @@ -4299,10 +4702,8 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) { // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Dynamic Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + bHasInvalidPositionIndexData = true; + continue; } // We need to swap Z and Y coordinate here, and convert from m to cm. @@ -4312,6 +4713,14 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION )); }//); + + if (bHasInvalidPositionIndexData) + { + HOUDINI_LOG_WARNING( + TEXT("Creating Dynamic Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } } //--------------------------------------------------------------------------------------------------------------------- @@ -4326,7 +4735,8 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() for (int32 TriangleIdx = 0; TriangleIdx < NumTriangles; ++TriangleIdx) // ParallelFor(NumTriangles, [&](uint32 TriangleIdx) { - + // TODO: add some additional intermediate consts for index calculations to make the indexing + // TODO: code a bit more readable const int32 TriVertIdx0 = TriangleIdx * 3; FoundStaticMesh->SetTriangleVertexIndices(TriangleIdx, FIntVector( TriangleIndices[TriVertIdx0 + 0], @@ -4335,26 +4745,39 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() )); const int32 TriWindingIndex[3] = { 0, 2, 1 }; - if (NormalCount > 0 && SplitNormals.IsValidIndex(TriVertIdx0 * 3 + 3 * 3 - 1)) + // Normals and tangents (either getting tangents from attributes or generating tangents from the + // normals + if (NormalCount > 0 || bReadTangents) { - // Flip Z and Y coordinate for normal, but don't scale for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) { - const FVector Normal( - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 0], - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 2], - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 1] - ); - - FoundStaticMesh->SetTriangleVertexNormal(TriangleIdx, TriWindingIndex[ElementIdx], Normal); + const bool bHasNormal = (NormalCount > 0 && SplitNormals.IsValidIndex(TriVertIdx0 * 3 + 3 * 3 - 1)); + FVector Normal = FVector::ZeroVector; + if (bHasNormal) + { + // Flip Z and Y coordinate for normal, but don't scale + Normal.Set( + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 0], + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 2], + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 1] + ); + + FoundStaticMesh->SetTriangleVertexNormal(TriangleIdx, TriWindingIndex[ElementIdx], Normal); + } - if (bReadTangents) + if (bReadTangents || bGenerateTangentsFromNormalAttribute) { FVector TangentU, TangentV; - if (bGenerateTangents) + if (bGenerateTangentsFromNormalAttribute) { - // Generate the tangents if needed - Normal.FindBestAxisVectors(TangentU, TangentV); + if (bHasNormal) + { + // Generate the tangents if needed + Normal.FindBestAxisVectors(TangentU, TangentV); + + FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); + FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); + } } else { @@ -4366,14 +4789,15 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() TangentU.X = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; TangentU.Y = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; TangentU.Z = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; - } - FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); - FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); + FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); + FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); + } } } } + // Vertex Colors if (bSplitColorValid && SplitColors.IsValidIndex(TriVertIdx0 * AttribInfoColors.tupleSize + 3 * AttribInfoColors.tupleSize - 1)) { FLinearColor VertexLinearColor; @@ -4404,6 +4828,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() } } + // UVs if (NumUVLayers > 0) { // Dynamic mesh supports only 1 UV layer on the mesh it self. So we set the first layer @@ -4424,7 +4849,25 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() } } } - }//); + } + } + + FMeshBuildSettings BuildSettings; + UpdateMeshBuildSettings( + BuildSettings, + FoundStaticMesh->HasNormals(), + FoundStaticMesh->HasTangents(), + false); + // Compute normals if requested or needed/missing + if (BuildSettings.bRecomputeNormals) + { + FoundStaticMesh->CalculateNormals(BuildSettings.bComputeWeightedNormals); + } + + // Compute tangents if requested or needed/missing + if (BuildSettings.bRecomputeTangents) + { + FoundStaticMesh->CalculateTangents(BuildSettings.bComputeWeightedNormals); } } @@ -4435,41 +4878,55 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() // Get face indices for this split. TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; + // Fetch the FoundMesh's Static Materials array + TArray& FoundStaticMaterials = FoundStaticMesh->GetStaticMaterials(); + + // Clear the materials array of the mesh the first time we encounter it + if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) + { + FoundStaticMaterials.Empty(); + } + TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); + // Process material overrides first if (PartFaceMaterialOverrides.Num() > 0) { TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Material Overrides")); + // Array used to avoid constantly attempting to load invalid materials + TArray InvalidMaterials; + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) { int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) continue; - const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); + UMaterialInterface * MaterialInterface = nullptr; int32 CurrentFaceMaterialIdx = 0; - if (FoundFaceMaterialIdx) - { - // We already know what material index to use for that override - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else + const FString& MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) { // Try to locate the corresponding material interface - UMaterialInterface * MaterialInterface = nullptr; // Start by looking in our assignment map - auto FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); if (FoundMaterialInterface) MaterialInterface = *FoundMaterialInterface; - if (!MaterialInterface && !MaterialName.IsEmpty()) - { - // Only try to load a material if has a chance to be valid! + // Only try to load a material if it has a chance to be valid! + if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) + { MaterialInterface = Cast( StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + + if (!MaterialInterface) + InvalidMaterials.Add(MaterialName); } if (MaterialInterface) @@ -4484,8 +4941,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() MaterialInterface = *ReplacementMaterialInterface; // Add this material to the map - CurrentFaceMaterialIdx = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); - MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); + MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); } else { @@ -4496,13 +4952,10 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - CurrentFaceMaterialIdx = *FoundUnrealMatIndex; - } - else + FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + if (!MaterialInterface) { // If everything fails, we'll use the default material MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); @@ -4519,17 +4972,29 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() if (ReplacementMaterial && *ReplacementMaterial) MaterialInterface = *ReplacementMaterial; - // Add the material to the mesh - CurrentFaceMaterialIdx = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); } } } - // Update the Face Material on the mesh - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, CurrentFaceMaterialIdx); + if (MaterialInterface) + { + int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); + } + // Update the Face Material on the mesh + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, CurrentFaceMaterialIdx); + } } } else if (PartUniqueMaterialIds.Num() > 0) @@ -4554,7 +5019,8 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() if (ReplacementMaterial && *ReplacementMaterial) MaterialInterface = *ReplacementMaterial; - FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); } else { @@ -4574,35 +5040,51 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + UMaterialInterface* MaterialInterface = nullptr; + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (MaterialInterface) { - // This material has been mapped already, just assign the mat index - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, *FoundUnrealMatIndex); - continue; + int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, *FoundUnrealMatIndex); + continue; + } } + else + { + MaterialInterface = Cast(DefaultMaterial); - UMaterialInterface * MaterialInterface = Cast(DefaultMaterial); - - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; - // Add the material to the mesh - int32 UnrealMatIndex = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + // Map the houdini ID to the unreal one + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } - // Map the houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); + if (MaterialInterface) + { + // Add the material to the Static mesh + int32 UnrealMatIndex = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); - // Update the face index - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, UnrealMatIndex); + // Map the houdini ID to the unreal one + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); + + // Update the face index + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, UnrealMatIndex); + } } } } @@ -4611,8 +5093,6 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Default Material")); // No materials were found, we need to use default Houdini material. - int32 SplitFaceCount = SplitFaceIndices.Num(); - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); // See if we have a replacement material and use it on the mesh instead @@ -4620,7 +5100,8 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() if (ReplacementMaterial && *ReplacementMaterial) MaterialInterface = *ReplacementMaterial; - FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); } //// Update property attributes on the mesh @@ -4637,6 +5118,16 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() FoundStaticMesh->Optimize(); + // Check if the mesh is valid (check all the counts (vertex, triangles, vertex instances, UVs etc) but skip + // looping over each individual triangle vertex index to check if the value is valid). + const bool bSkipVertexIndicesCheck = true; + if (!FoundStaticMesh->IsValid(bSkipVertexIndicesCheck)) + { + HOUDINI_LOG_WARNING( + TEXT("[CreateHoudiniStaticMesh]: Invalid StaticMesh data for %s in cook output! Please check the log."), + *FoundStaticMesh->GetName()); + } + //// Try to find the outer package so we can dirty it up //if (FoundStaticMesh->GetOuter()) //{ @@ -4647,7 +5138,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() // FoundStaticMesh->MarkPackageDirty(); //} UPackage *MeshPackage = FoundStaticMesh->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) + if (IsValid(MeshPackage)) { MeshPackage->MarkPackageDirty(); @@ -4674,6 +5165,36 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() return true; } +void +FHoudiniMeshTranslator::ApplyComplexColliderHelper( + UStaticMesh* TargetStaticMesh, + UStaticMesh* ComplexStaticMesh, + const EHoudiniSplitType SplitType, + bool& bAssignedCustomCollisionMesh, + FHoudiniOutputObject* OutputObject) +{ + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider && TargetStaticMesh) + { + if (!bAssignedCustomCollisionMesh) + { + bAssignedCustomCollisionMesh = true; + TargetStaticMesh->ComplexCollisionMesh = ComplexStaticMesh; + TargetStaticMesh->bCustomizedCollision = true; + bAssignedCustomCollisionMesh = true; + // We don't want an actor/component for this object in the scene, so flag it as an implicit output. + if (OutputObject) + { + OutputObject->bIsImplicit = true; + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("More than one (invisible) complex collision mesh found. Static Mesh assets only support a single complex collision mesh. Creating additional collision geo as Static Mesh Components.")); + } + } +} + + bool FHoudiniMeshTranslator::CreateNeededMaterials() { @@ -4683,10 +5204,16 @@ FHoudiniMeshTranslator::CreateNeededMaterials() TArray MaterialAndTexturePackages; FHoudiniMaterialTranslator::CreateHoudiniMaterials( - HGPO.AssetId, PackageParams, - PartUniqueMaterialIds, PartUniqueMaterialInfos, - InputAssignmentMaterials, OutputAssignmentMaterials, - MaterialAndTexturePackages, false, bTreatExistingMaterialsAsUpToDate); + HGPO.AssetId, + PackageParams, + PartUniqueMaterialIds, + PartUniqueMaterialInfos, + InputAssignmentMaterials, + AllOutputMaterials, + OutputAssignmentMaterials, + MaterialAndTexturePackages, + false, + bTreatExistingMaterialsAsUpToDate); /* // Save the created packages if needed @@ -4700,20 +5227,8 @@ FHoudiniMeshTranslator::CreateNeededMaterials() // Map containing unique face materials override attribute // and their first valid prim index // We create only one material instance per attribute - TMap UniqueFaceMaterialOverrides; - for (int FaceIdx = 0; FaceIdx < PartFaceMaterialOverrides.Num(); FaceIdx++) - { - FString MatOverrideAttr = PartFaceMaterialOverrides[FaceIdx]; - if (UniqueFaceMaterialOverrides.Contains(MatOverrideAttr)) - continue; - - // Add the material override and face index to the map - UniqueFaceMaterialOverrides.Add(MatOverrideAttr, FaceIdx); - } - FHoudiniMaterialTranslator::CreateMaterialInstances( - HGPO, PackageParams, - UniqueFaceMaterialOverrides, MaterialAndTexturePackages, + FHoudiniMaterialTranslator::SortUniqueFaceMaterialOverridesAndCreateMaterialInstances(PartFaceMaterialOverrides, HGPO, PackageParams, MaterialAndTexturePackages, InputAssignmentMaterials, OutputAssignmentMaterials, false); } @@ -4760,7 +5275,7 @@ FHoudiniMeshTranslator::FindExistingStaticMesh(const FHoudiniOutputObjectIdentif { // Make sure it's a valid static mesh FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + if (!IsValid(FoundStaticMesh)) FoundStaticMesh = nullptr; } @@ -4773,11 +5288,10 @@ FHoudiniMeshTranslator::FindExistingStaticMesh(const FHoudiniOutputObjectIdentif // Make sure it's a valid static mesh FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + if (!IsValid(FoundStaticMesh)) return nullptr; } - /* if (FoundStaticMesh) { UObject* OuterMost = FoundStaticMesh->GetOutermostObject(); @@ -4790,7 +5304,6 @@ FHoudiniMeshTranslator::FindExistingStaticMesh(const FHoudiniOutputObjectIdentif FoundStaticMesh = nullptr; } } - */ return FoundStaticMesh; } @@ -4805,7 +5318,7 @@ FHoudiniMeshTranslator::FindExistingHoudiniStaticMesh(const FHoudiniOutputObject { // Make sure it's a valid static mesh FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + if (!IsValid(FoundStaticMesh)) FoundStaticMesh = nullptr; } @@ -4818,7 +5331,7 @@ FHoudiniMeshTranslator::FindExistingHoudiniStaticMesh(const FHoudiniOutputObject // Make sure it's a valid static mesh FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + if (!IsValid(FoundStaticMesh)) return nullptr; } @@ -5367,7 +5880,7 @@ FHoudiniMeshTranslator::GetLODSCreensizeForSplit(const FString& SplitGroupName) screensize = PartLODScreensize[FirstValidPrimIndex]; } - if (screensize >= 0.0f) + if (screensize < 0.0f) { // We couldn't find the primitive attribute, look for a "lodX_screensize" detail attribute FString LODAttributeName = SplitGroupName + HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX; @@ -5378,7 +5891,7 @@ FHoudiniMeshTranslator::GetLODSCreensizeForSplit(const FString& SplitGroupName) FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*LODAttributeName), - AttribInfoScreenSize, LODScreenSizes, 0, HAPI_ATTROWNER_DETAIL); + AttribInfoScreenSize, LODScreenSizes, 0, HAPI_ATTROWNER_DETAIL, 0, 1); if (AttribInfoScreenSize.exists && LODScreenSizes.Num() > 0) { @@ -5386,7 +5899,7 @@ FHoudiniMeshTranslator::GetLODSCreensizeForSplit(const FString& SplitGroupName) } } - if (screensize >= 0.0f) + if (screensize < 0.0f) { // finally, look for a potential uproperty style attribute // aka, "unreal_uproperty_screensize" @@ -5396,7 +5909,7 @@ FHoudiniMeshTranslator::GetLODSCreensizeForSplit(const FString& SplitGroupName) FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( HGPO.GeoId, HGPO.PartId, "unreal_uproperty_screensize", - AttribInfoScreenSize, LODScreenSizes); + AttribInfoScreenSize, LODScreenSizes, 0, HAPI_ATTROWNER_INVALID, 0, 1); if (AttribInfoScreenSize.exists) { @@ -5877,54 +6390,6 @@ FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(const TArray& InP } -bool -FHoudiniMeshTranslator::GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, - const int32& InFirstValidVertexIndex, const int32& InFirstValidPrimIndex, - TArray& OutPropertyAttributes) -{ - // List all the generic property detail attributes ... - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then the primitive property attributes for the given prim - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InFirstValidPrimIndex); - - // .. then finally, point uprop attributes for the given vert - // TODO: !! get the correct Index here? - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InFirstValidVertexIndex); - - return FoundCount > 0; -} - -bool -FHoudiniMeshTranslator::UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Iterate over the found Property attributes - int32 NumSuccess = 0; - for (auto CurrentPropAttribute : InAllPropertyAttributes) - { - // Update the current Property Attribute - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) - continue; - - // Success! - NumSuccess++; - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); - } - - return (NumSuccess > 0); -} - - void FHoudiniMeshTranslator::SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO) { @@ -5941,11 +6406,11 @@ FHoudiniMeshTranslator::SetPackageParams(const FHoudiniPackageParams& InPackageP bool FHoudiniMeshTranslator::RemoveAndDestroyComponent(UObject* InComponent) { - if (!InComponent || InComponent->IsPendingKill()) + if (!IsValid(InComponent)) return false; USceneComponent* SceneComponent = Cast(InComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) + if (IsValid(SceneComponent)) { // Remove from the HoudiniAssetActor if (SceneComponent->GetOwner()) @@ -5967,7 +6432,7 @@ FHoudiniMeshTranslator::CreateMeshComponent(UObject *InOuterComponent, const TSu // Create a new SMC as we couldn't find an existing one USceneComponent* OuterSceneComponent = Cast(InOuterComponent); UObject * Outer = nullptr; - if (OuterSceneComponent && !OuterSceneComponent->IsPendingKill()) + if (IsValid(OuterSceneComponent)) Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); UMeshComponent *MeshComponent = NewObject(Outer, InComponentType, NAME_None, RF_Transactional); @@ -6064,7 +6529,7 @@ FHoudiniMeshTranslator::CreateOrUpdateMeshComponent( // If there is an existing component, but it is pending kill, then it was likely // deleted by some other process, such as by the user in the editor, so don't use it - if (!MeshComponent || MeshComponent->IsPendingKill() || !MeshComponent->IsA(InComponentType)) + if (!IsValid(MeshComponent) || !MeshComponent->IsA(InComponentType)) { // If the component is not of type InComponentType, or the found component is pending kill, destroy // the existing component (a new one is then created below) @@ -6096,7 +6561,7 @@ bool FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors) { - if (!Socket || Socket->IsPendingKill() || !StaticMeshComponent || StaticMeshComponent->IsPendingKill()) + if (!IsValid(Socket) || !IsValid(StaticMeshComponent)) return false; // The actor to assign is stored is the socket's tag @@ -6128,15 +6593,15 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); #if WITH_EDITOR UWorld* EditorWorld = StaticMeshComponent->GetOwner() ? StaticMeshComponent->GetOwner()->GetWorld() : nullptr; - if (!EditorWorld || EditorWorld->IsPendingKill()) + if (!IsValid(EditorWorld)) return false; - // Remove the previous created actors which were attached to this socket + // Remove the previously created actors which were attached to this socket { for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) { AActor * CurActor = HoudiniCreatedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) + if (!IsValid(CurActor)) { HoudiniCreatedSocketActors.RemoveAt(Idx); continue; @@ -6155,7 +6620,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) { AActor * CurActor = HoudiniAttachedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) + if (!IsValid(CurActor)) { HoudiniAttachedSocketActors.RemoveAt(Idx); continue; @@ -6174,12 +6639,12 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati AActor * CreatedDefaultActor = nullptr; UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) + if (IsValid(DefaultReferenceSM)) { TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( EditorWorld->GetCurrentLevel(), DefaultReferenceSM, false, RF_Transactional, nullptr); - if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) + if (NewActors.Num() <= 0 || !IsValid(NewActors[0])) { HOUDINI_LOG_WARNING( TEXT("Failed to load default mesh.")); @@ -6192,7 +6657,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati for (auto & CurComp : NewActors[0]->GetComponents()) { UStaticMeshComponent * CurSMC = Cast(CurComp); - if (CurSMC && !CurSMC->IsPendingKill()) + if (IsValid(CurSMC)) CurSMC->SetMobility(OutputSMCMobility); } @@ -6213,6 +6678,10 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati return CreatedDefaultActor; }; + // If nothing was specified, we're done + if (ActorStringArray.Num() <= 0) + return true; + bool bUseDefaultActor = true; // Get from the Houdini runtime setting if use default object when the reference is invalid // true by default if fail to access HoudiniRuntimeSettings @@ -6222,6 +6691,9 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati bUseDefaultActor = HoudiniRuntimeSettings->bShowDefaultMesh; } + /* + // !! Only use the default mesh if we failed to find/spawn the actor to attach + // not if we didn't specify any actor to attach! if (ActorStringArray.Num() <= 0) { if (!bUseDefaultActor) @@ -6231,18 +6703,19 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati TEXT("Output static mesh: Socket '%s' actor is not specified. Spawn a default mesh (hidden in game)."), *(Socket->GetName())); AActor * DefaultActor = CreateDefaultActor(); - if (DefaultActor && !DefaultActor->IsPendingKill()) + if (IsValid(DefaultActor)) HoudiniCreatedSocketActors.Add(DefaultActor); return true; } + */ // try to find the actor in level first for (TActorIterator ActorItr(EditorWorld); ActorItr; ++ActorItr) { // Same as with the Object Iterator, access the subclass instance with the * or -> operators. AActor *Actor = *ActorItr; - if (!Actor || Actor->IsPendingKillOrUnreachable()) + if (!IsValid(Actor) || Actor->IsPendingKillOrUnreachable()) continue; for (int32 StringIdx = 0; StringIdx < ActorStringArray.Num(); StringIdx++) @@ -6256,7 +6729,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati for (auto & CurComp : Actor->GetComponents()) { UStaticMeshComponent * SMC = Cast(CurComp); - if (SMC && !SMC->IsPendingKill()) + if (IsValid(SMC)) SMC->SetMobility(OutputSMCMobility); } @@ -6274,7 +6747,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati for (int32 Idx = ActorStringArray.Num() - 1; Idx>= 0; --Idx) { UObject * Obj = StaticLoadObject(UObject::StaticClass(), nullptr, *ActorStringArray[Idx]); - if (!Obj || Obj->IsPendingKill()) + if (!IsValid(Obj)) { bSuccess = false; continue; @@ -6284,7 +6757,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( EditorWorld->GetCurrentLevel(), Obj, false, RF_Transactional, nullptr); - if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) + if (NewActors.Num() <= 0 || !IsValid(NewActors[0])) { bSuccess = false; continue; @@ -6295,7 +6768,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati for (auto & CurComp : NewActors[0]->GetComponents()) { UStaticMeshComponent * CurSMC = Cast(CurComp); - if (CurSMC && !CurSMC->IsPendingKill()) + if (IsValid(CurSMC)) CurSMC->SetMobility(OutputSMCMobility); } @@ -6316,7 +6789,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati // If failed to load this object, spawn a default mesh AActor * CurDefaultActor = CreateDefaultActor(); - if (CurDefaultActor && !CurDefaultActor->IsPendingKill()) + if (IsValid(CurDefaultActor)) HoudiniCreatedSocketActors.Add(CurDefaultActor); } } @@ -6328,4 +6801,32 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati return bSuccess; } +void +FHoudiniMeshTranslator::UpdateMeshBuildSettings( + FMeshBuildSettings& OutMeshBuildSettings, + const bool& bHasNormals, + const bool& bHasTangents, + const bool& bHasLightmapUVSet) +{ + // Use the values provided to the translator + OutMeshBuildSettings = StaticMeshBuildSettings; + + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + + // Recomputing normals. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeNormalFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeNormalsFlag : HRSRF_OnlyIfMissing; + if(RecomputeNormalFlag == HRSRF_OnlyIfMissing) + OutMeshBuildSettings.bRecomputeNormals = !bHasNormals; + + // Recomputing tangents. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeTangentFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag : HRSRF_OnlyIfMissing; + if (RecomputeTangentFlag == HRSRF_OnlyIfMissing) + OutMeshBuildSettings.bRecomputeTangents = !bHasTangents; + + // Lightmap UV generation. + EHoudiniRuntimeSettingsRecomputeFlag GenerateLightmapUVFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag : HRSRF_OnlyIfMissing; + if (GenerateLightmapUVFlag == HRSRF_OnlyIfMissing) + OutMeshBuildSettings.bGenerateLightmapUVs = !bHasLightmapUVSet; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h index b9a6da6aa..365a5f841 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -81,7 +81,10 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator static bool CreateAllMeshesAndComponentsFromHoudiniOutput( UHoudiniOutput* InOutput, const FHoudiniPackageParams& InPackageParams, - EHoudiniStaticMeshMethod InStaticMeshMethod, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InMeshBuildSettings, + const TMap& InAllOutputMaterials, UObject* InOuterComponent, bool bInTreatExistingMaterialsAsUpToDate=false, bool bInDestroyProxies=false); @@ -93,8 +96,11 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator TMap& OutOutputObjects, TMap& InAssignmentMaterialMap, TMap& InReplacementMaterialMap, + const TMap& InAllOutputMaterials, const bool& InForceRebuild, - EHoudiniStaticMeshMethod InStaticMeshMethod, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InMeshBuildSettings, bool bInTreatExistingMaterialsAsUpToDate = false); static bool CreateOrUpdateAllComponents( @@ -127,6 +133,13 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator const TArray& InData, TArray& OutSplitData); + // Update the MeshBuild Settings using the values from the runtime settings/overrides on the HAC + void UpdateMeshBuildSettings( + FMeshBuildSettings& OutMeshBuildSettings, + const bool& bHasNormals, + const bool& bHasTangents, + const bool& bHasLightmapUVSet); + //----------------------------------------------------------------------------------------------------------------------------- // ACCESSORS @@ -144,24 +157,16 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator void SetInputAssignmentMaterials(const TMap& InInputMaterials) { InputAssignmentMaterials = InInputMaterials; }; void SetReplacementMaterials(const TMap& InReplacementMaterials) { ReplacementMaterials = InReplacementMaterials; }; + void SetAllOutputMaterials(const TMap& InAllOutputMaterials) { AllOutputMaterials = InAllOutputMaterials; }; //void SetInputObjectProperties(const TMap& InInputObjectProperties) { InputObjectProperties = InInputObjectProperties; }; //void SetOutputObjectProperties(TMap& InOutputObjectProperties) { OutputObjectProperties = InOutputObjectProperties; }; void SetTreatExistingMaterialsAsUpToDate(bool bInTreatExistingMaterialsAsUpToDate) { bTreatExistingMaterialsAsUpToDate = bInTreatExistingMaterialsAsUpToDate; } - //----------------------------------------------------------------------------------------------------------------------------- - // Helpers - //----------------------------------------------------------------------------------------------------------------------------- - - // Helper functions for generic property attributes - static bool GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, - const int32& InFirstValidVertexIndex, const int32& InFirstValidPrimIndex, - TArray& OutPropertyAttributes); + void SetStaticMeshGenerationProperties(const FHoudiniStaticMeshGenerationProperties& InStaticMeshGenerationProperties) { StaticMeshGenerationProperties = InStaticMeshGenerationProperties; }; - static bool UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes); + void SetStaticMeshBuildSettings(const FMeshBuildSettings& InMBS) { StaticMeshBuildSettings = InMBS; }; protected: @@ -174,6 +179,13 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator // Create a UHoudiniStaticMesh bool CreateHoudiniStaticMesh(); + static void ApplyComplexColliderHelper( + UStaticMesh* TargetStaticMesh, + UStaticMesh* ComplexStaticMesh, + const EHoudiniSplitType SplitType, + bool& AssignedCustomCollisionMesh, + FHoudiniOutputObject* OutputObject); + void ResetPartCache(); bool UpdatePartVertexList(); @@ -305,6 +317,9 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator TMap OutputAssignmentMaterials; // Input Replacement Materials maps TMap ReplacementMaterials; + // All the materials that have been generated by this Houdini Asset + // Used to avoid generating the same houdini material over and over again + TMap AllOutputMaterials; // Input mesh properties //TMap InputObjectProperties; @@ -401,4 +416,10 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator // When building a mesh, if an associated material already exists, treat // it as up to date, regardless of the MaterialInfo.bHasChanged flag bool bTreatExistingMaterialsAsUpToDate; + + // Default properties to be used when generating Static Meshes + FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; + + // Default Mesh Build settings to be used when generating Static Meshes + FMeshBuildSettings StaticMeshBuildSettings; }; diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp index 299a2af4d..9b8e93072 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,17 +63,22 @@ // bool -FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& bInForceUpdate, bool& bOutHasHoudiniStaticMeshOutput) +FHoudiniOutputTranslator::UpdateOutputs( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate, + bool& bOutHasHoudiniStaticMeshOutput) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; - // Get the bake folder override - FHoudiniOutputTranslator::GetBakeFolderFromAttribute(HAC); - // Get the temp folder override FHoudiniOutputTranslator::GetTempFolderFromAttribute(HAC); + // Outputs that should be cleared, but only AFTER new output processing have taken place. + // This is needed for landscape resizing where the new landscape needs to copy data from the original landscape + // before the original landscape gets destroyed. + TArray DeferredClearOutputs; + // Check if the HDA has been marked as not producing outputs if (!HAC->bOutputless) { @@ -90,9 +95,19 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& } TArray NewOutputs; - if (FHoudiniOutputTranslator::BuildAllOutputs(HAC->GetAssetId(), HAC, HAC->Outputs, NewOutputs, HAC->bOutputTemplateGeos)) + TArray OutputNodes = HAC->GetOutputNodeIds(); + TMap OutputNodeCookCounts = HAC->GetOutputNodeCookCounts(); + if (FHoudiniOutputTranslator::BuildAllOutputs( + HAC->GetAssetId(), HAC, OutputNodes, OutputNodeCookCounts, + HAC->Outputs, NewOutputs, HAC->bOutputTemplateGeos, HAC->bUseOutputNodes)) { - ClearAndRemoveOutputs(HAC); + // NOTE: For now we are currently forcing all outputs to be cleared here. There is still an issue where, in some + // circumstances, landscape tiles disappear when clearing outputs after processing. + // The reason we may need to defer landscape clearing is to allow the landscape creation code to + // capture the extent of the landscape. The extent of the landscape can only be calculated if all landscape + // tiles are still present in the map. If we find that we don't need this for updating of Input landscapes, + // we can safely remove this feature. + ClearAndRemoveOutputs(HAC, DeferredClearOutputs, true); // Replace with the new parameters HAC->Outputs = NewOutputs; } @@ -100,7 +115,40 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& else { // This HDA is marked as not supposed to produce any output - ClearAndRemoveOutputs(HAC); + ClearAndRemoveOutputs(HAC, DeferredClearOutputs, true); + } + + // Look for details generic property attributes on the outputs, + // and try to apply them to the HAC. + // This can be used to preset some of the HDA's uproperty via attribute + TArray GenericAttributes; + for (auto& CurrentOutput : HAC->Outputs) + { + const TArray& CurrentOutputHGPO = CurrentOutput->GetHoudiniGeoPartObjects(); + for (auto& CurrentHGPO : CurrentOutputHGPO) + { + FHoudiniEngineUtils::GetGenericAttributeList( + CurrentHGPO.GeoId, + CurrentHGPO.PartId, + HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, + GenericAttributes, + HAPI_ATTROWNER_DETAIL); + } + } + + // Attempt to apply the attributes to the HAC if we have any + for (const auto& CurrentPropAttribute : GenericAttributes) + { + // Get the current Property Attribute + const FString& CurrentPropertyName = CurrentPropAttribute.AttributeName; + if (CurrentPropertyName.IsEmpty()) + continue; + + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(HAC, CurrentPropAttribute)) + continue; + + // Success! + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on Houdini Asset Component named %s"), *CurrentPropertyName, *HAC->GetName()); } // NOTE: PersistentWorld can be NULL when, for example, working with @@ -115,7 +163,7 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& if (IsValid(WorldComposition)) { // We don't want the origin to shift as we're potentially updating levels. - WorldComposition->bTemporallyDisableOriginTracking = true; + WorldComposition->bTemporarilyDisableOriginTracking = true; } // "Process" the mesh. @@ -139,6 +187,10 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); PackageParams.ComponentGUID = HAC->GetComponentGUID(); PackageParams.ObjectName = FString(); + + // ---------------------------------------------------- + // Outputs prepass + // ---------------------------------------------------- TArray CreatedWorldCompositionPackages; bool bCreatedNewMaps = false; @@ -194,34 +246,33 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& // Before processing all the outputs, // See if we have any landscape input that have "Update Input Landscape" enabled - // And make an array of all our input landscapes + // And make an array of all our input landscapes as well. TArray AllInputLandscapes; TArray InputLandscapesToUpdate; - for (auto CurrentInput : HAC->Inputs) - { - if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) - continue; - - // Get the landscape input's landscape - ALandscapeProxy* InputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); - if (!InputLandscape) - continue; - - AllInputLandscapes.Add(InputLandscape); - - if (CurrentInput->GetUpdateInputLandscape()) - InputLandscapesToUpdate.Add(InputLandscape); - } + FHoudiniEngineUtils::GatherLandscapeInputs(HAC, AllInputLandscapes, InputLandscapesToUpdate); // ---------------------------------------------------- // Process outputs // ---------------------------------------------------- + // Landscape creation will cache the first tile as a reference location + // in this struct to be used by during construction of subsequent tiles. + FHoudiniLandscapeReferenceLocation LandscapeReferenceLocation; + // Landscape Size info will be cached by the first tile, similar to LandscapeReferenceLocation + FHoudiniLandscapeTileSizeInfo LandscapeSizeInfo; + FHoudiniLandscapeExtent LandscapeExtent; + TSet ClearedLandscapeLayers; + + // The houdini materials that have been generated by this HDA. + // We track them to prevent recreate the same houdini material over and over if it is assigned to multiple parts. + // (this can easily happen when using packed prims) + TMap AllOutputMaterials; + TArray CreatedPackages; for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) { UHoudiniOutput* CurOutput = HAC->GetOutputAt(OutputIdx); - if (!CurOutput || CurOutput->IsPendingKill()) + if (!IsValid(CurOutput)) continue; FString Notification = FString::Format(TEXT("Processing output {0} / {1}..."), {FString::FromInt(OutputIdx + 1), FString::FromInt(NumOutputs)}); @@ -264,6 +315,9 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& CurOutput, PackageParams, bIsProxyStaticMeshEnabled ? EHoudiniStaticMeshMethod::UHoudiniStaticMesh : HAC->StaticMeshMethod, + HAC->StaticMeshGenerationProperties, + HAC->StaticMeshBuildSettings, + AllOutputMaterials, OuterComponent); NumVisibleOutputs++; @@ -347,7 +401,11 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& PersistentWorld, LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums, + LandscapeExtent, + LandscapeSizeInfo, + LandscapeReferenceLocation, PackageParams, + ClearedLandscapeLayers, CreatedPackages); bHasLandscape = true; @@ -357,7 +415,10 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& for (auto& Pair : CurOutput->GetOutputObjects()) { UHoudiniLandscapePtr* LandscapePtr = Cast(Pair.Value.OutputObject); - OutputLandscape = LandscapePtr->GetRawPtr(); + if (IsValid(LandscapePtr)) + { + OutputLandscape = LandscapePtr->GetRawPtr(); + } break; } @@ -366,27 +427,38 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& // Attach the created landscapes to HAC // Output Transforms are always relative to the HDA HAC->SetMobility(EComponentMobility::Static); - OutputLandscape->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); + OutputLandscape->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); // Note that the above attach will cause the collision components to crap out. This manifests // itself via the Landscape editor tools not being able to trace Landscape collision components. - // By recreating collision components here, it appears to put things back into working order. + // By recreating collision components here, it appears to put things back into working order. + OutputLandscape->GetLandscapeInfo()->FixupProxiesTransform(); + OutputLandscape->GetLandscapeInfo()->RecreateLandscapeInfo(PersistentWorld, true); OutputLandscape->RecreateCollisionComponents(); + FEditorDelegates::PostLandscapeLayerUpdated.Broadcast(); } bCreatedNewMaps |= bNewMapCreated; - break; } default: // Do Nothing for now break; } + + for (auto& CurMat : CurOutput->AssignementMaterials) + { + // Add the newly generated materials if any + if (!AllOutputMaterials.Contains(CurMat.Key)) + AllOutputMaterials.Add(CurMat); + } } // Now that all meshes have been created, process the instancers for (auto& CurOutput : InstancerOutputs) { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); + if (!FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent, PackageParams)) + continue; + NumVisibleOutputs++; } @@ -401,6 +473,22 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& FHoudiniEngineUtils::AddHoudiniLogoToComponent(HAC); } + // Clear any old outputs that was marked as "Should Defer Clear". + // This should happen before SharedLandscapeActor cleanup + // since this needs to remove old landscape proxies so that empty SharedLandscapeActors + // can be removed afterward. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniOutputTranslator::UpdateOutputs] Clearing old outputs: %d"), DeferredClearOutputs.Num()); + for(UHoudiniOutput* OldOutput : DeferredClearOutputs) + { + ClearOutput(OldOutput); + } + + // if (IsValid(LandscapeExtents.IntermediateResizeLandscape)) + // { + // LandscapeExtents.IntermediateResizeLandscape->Destroy(); + // LandscapeExtents.IntermediateResizeLandscape = nullptr; + // } + if (bHasLandscape) { // ---------------------------------------------------- @@ -449,22 +537,45 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& if (TrackedLandscapes.Contains(Landscape)) continue; - ULandscapeInfo* Info = Landscape->GetLandscapeInfo(); - if (!Info || Info->Proxies.Num() == 0) + if (Landscape->GetLandscapeInfo()->Proxies.Num() == 0) + if (!IsValid(Landscape)) + continue; + + ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); + if (!LandscapeInfo) { Landscape->Destroy(); + continue; } + + if (LandscapeInfo->Proxies.Num() == 0) + Landscape->Destroy(); } } // Recreate Landscape Info calls WorldChange, so no need to do it manually. ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); + +#if WITH_EDITOR + if (GEditor) + { + // We force a viewport refresh since some actions, such as updating landscape + // edit layers will not reflect until the user moves the viewport camera. + GEditor->RedrawLevelEditingViewports(true); + } +#endif } + // Destroy the intermediate resize landscape, if there is one. + // if (IsValid(LandscapeExtents.IntermediateResizeLandscape)) + // { + // FHoudiniLandscapeTranslator::DestroyLandscape(LandscapeExtents.IntermediateResizeLandscape); + // } + if (IsValid(WorldComposition)) { // Disable the flag that we set before starting the import process. - WorldComposition->bTemporallyDisableOriginTracking = false; + WorldComposition->bTemporarilyDisableOriginTracking = false; } // If the owner component was marked as loaded, unmark all outputs @@ -508,7 +619,7 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& bool FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; UObject* OuterComponent = HAC; @@ -526,6 +637,9 @@ FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAss PackageParams.ComponentGUID = HAC->GetComponentGUID(); PackageParams.ObjectName = FString(); + // Keep track of all generated houdini materials to avoid recreating them over and over + TMap AllOutputMaterials; + bool bFoundProxies = false; TArray InstancerOutputs; for (auto& CurOutput : HAC->Outputs) @@ -540,6 +654,9 @@ FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAss CurOutput, PackageParams, HAC->StaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh ? HAC->StaticMeshMethod : EHoudiniStaticMeshMethod::RawMesh, + HAC->StaticMeshGenerationProperties, + HAC->StaticMeshBuildSettings, + AllOutputMaterials, OuterComponent, true, // bInTreatExistingMaterialsAsUpToDate bInDestroyProxies @@ -550,6 +667,13 @@ FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAss { InstancerOutputs.Add(CurOutput); } + + for (auto& CurMat : CurOutput->AssignementMaterials) + { + //Adds the generated materials if any + if (!AllOutputMaterials.Contains(CurMat.Key)) + AllOutputMaterials.Add(CurMat); + } } // Rebuild instancers if we built any static meshes from proxies @@ -557,7 +681,7 @@ FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAss { for (auto& CurOutput : InstancerOutputs) { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent, PackageParams); } } @@ -576,8 +700,9 @@ FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); // Retrieve information about each object contained within our asset. - TArray< HAPI_ObjectInfo > ObjectInfos; - if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos)) + TArray ObjectInfos; + TArray ObjectTransforms; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos, ObjectTransforms)) return false; TArray EditableCurveObjIds; @@ -624,13 +749,25 @@ FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) continue; // We only handle editable curves for now - if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) - continue; - - // Only catch editable curves if (CurrentEditableGeoInfo.type != HAPI_GeoType::HAPI_GEOTYPE_CURVE) continue; + // Check if the curve is closed (-1 unknown, could not find parameter on node). A closed curve will + // be returned as a mesh by HAPI instead of a curve + int32 CurveClosed = -1; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + EditableNodeIds[nEditable], HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed)) + { + CurveClosed = -1; + } + else + { + if (CurveClosed) + CurveClosed = 1; + else + CurveClosed = 0; + } + // Cook the editable node to get its parts if (CurrentEditableGeoInfo.partCount <= 0) { @@ -655,7 +792,9 @@ FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) continue; - if (CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_CURVE) + // A closed curve will be returned as a mesh in HAPI + if (CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_CURVE && + (CurveClosed <= 0 || CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_MESH)) continue; // Get the editable curve's part name @@ -666,11 +805,10 @@ FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) EditableCurveObjIds.Add(CurrentHapiObjectInfo.nodeId); EditableCurveGeoIds.Add(CurrentEditableGeoInfo.nodeId); EditableCurvePartIds.Add(CurrentHapiPartInfo.id); - EditableCurvePartNames.Add(PartName); + EditableCurvePartNames.Add(PartName); } } } - } int32 Idx = 0; @@ -688,7 +826,7 @@ FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) break; UHoudiniSplineComponent * HoudiniSplineComponent = Cast(Pair.Value.OutputComponent); - if (HoudiniSplineComponent && !HoudiniSplineComponent->IsPendingKill()) + if (IsValid(HoudiniSplineComponent)) { HoudiniSplineComponent->SetNodeId(EditableCurveGeoIds[Idx]); @@ -707,7 +845,7 @@ FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) const TArray &Children = HAC->GetAttachChildren(); for (auto & CurAttachedComp : Children) { - if (!CurAttachedComp || CurAttachedComp->IsPendingKill()) + if (!IsValid(CurAttachedComp)) continue; if (!CurAttachedComp->IsA()) @@ -739,7 +877,10 @@ FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) NewOutputObject.OutputComponent = CurAttachedSplineComp; CurrentOutput->SetHasEditableNodeBuilt(true); - FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(CurAttachedSplineComp); + + // Never add additional rot/scale attributes on editable curves as this crashes HAPI + FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( + CurAttachedSplineComp, false); Idx += 1; break; @@ -767,7 +908,7 @@ FHoudiniOutputTranslator::UploadChangedEditableOutput( UHoudiniAssetComponent* HAC, const bool& bInForceUpdate) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; TArray &Outputs = HAC->Outputs; @@ -785,13 +926,15 @@ FHoudiniOutputTranslator::UploadChangedEditableOutput( for (auto& CurrentOutputObj : CurrentOutput->GetOutputObjects()) { UHoudiniSplineComponent* HoudiniSplineComponent = Cast(CurrentOutputObj.Value.OutputComponent); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; if (!HoudiniSplineComponent->HasChanged()) continue; - if (FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(HoudiniSplineComponent)) + // Dont add rot/scale on editable curves as this crashes HAPI + if (FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( + HoudiniSplineComponent, false)) HoudiniSplineComponent->MarkChanged(false); else HoudiniSplineComponent->SetNeedsToTriggerUpdate(false); @@ -806,10 +949,18 @@ bool FHoudiniOutputTranslator::BuildAllOutputs( const HAPI_NodeId& AssetId, UObject* InOuterObject, + const TArray& OutputNodes, + const TMap& OutputNodeCookCounts, TArray& InOldOutputs, TArray& OutNewOutputs, - const bool& InOutputTemplatedGeos) + const bool& InOutputTemplatedGeos, + const bool& InUseOutputNodes) { + // NOTE: This function still gathers output nodes from the asset id. This is old behaviour. + // Output nodes are now being gathered before cooking starts and is passed in through + // the OutputNodes array. Clean up this function by only using output nodes from the + // aforementioned array. + // Ensure the asset has a valid node ID if (AssetId < 0) { @@ -822,28 +973,26 @@ FHoudiniOutputTranslator::BuildAllOutputs( HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + // Get the Asset NodeInfo + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetNodeInfo), false); + FString CurrentAssetName; { FHoudiniEngineString hapiSTR(AssetInfo.nameSH); hapiSTR.ToFString(CurrentAssetName); } - // Retrieve the asset's transform. - // TODO: Unused?! - //FTransform AssetUnrealTransform; - //if (!FHoudiniEngineUtils::HapiGetAssetTransform(AssetId, AssetUnrealTransform)) - // return false; + // In certain cases, such as PDG output processing we might end up with a SOP node instead of a + // container. In that case, don't try to run child queries on this node. They will fail. + const bool bAssetHasChildren = !(AssetNodeInfo.type == HAPI_NODETYPE_SOP && AssetNodeInfo.childNodeCount == 0); // Retrieve information about each object contained within our asset. TArray ObjectInfos; - if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos)) - return false; - - //const int32 ObjectCount = ObjectInfos.Num(); - - // Retrieve transforms for each object in this asset. TArray ObjectTransforms; - if (!FHoudiniEngineUtils::HapiGetObjectTransforms(AssetId, ObjectTransforms)) + if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos, ObjectTransforms)) return false; // Mark all the previous HGPOs on the outputs as stale @@ -859,145 +1008,263 @@ FHoudiniOutputTranslator::BuildAllOutputs( // match them with theit corresponding height volume after TArray UnassignedVolumeParts; - TArray AllSockets; + // When receiving landscape edit layers, we are no longer to split + // outputs based on 'height' volumes + TSet TileIds; - // Iterate through all objects. - int32 OutputIdx = 1; - for (int32 ObjectId = 0; ObjectId < ObjectInfos.Num(); ++ObjectId) + // VA: Editable nodes fetching have been moved here to fetch them for the whole asset, only once. + // It seemed unnecessary to have to fetch these for every Object node. Instead, + // we'll collect all the editable nodes for the HDA and process them only on the first loop. + // This also allows us to use more 'strict' Object node retrieval for output processing since + // we don't have to worry that we might miss any editable nodes. + + // Start by getting the number of editable nodes + TArray EditableGeoInfos; + int32 EditableNodeCount = 0; + if (bAssetHasChildren) { - // Retrieve the object info - const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectId]; + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, + true, &EditableNodeCount)); + } - // Cache/convert them - FHoudiniObjectInfo CurrentObjectInfo; - CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); + // All editable nodes will be output, regardless + // of whether the subnet is considered visible or not. + if (EditableNodeCount > 0) + { + TArray EditableNodeIds; + EditableNodeIds.SetNumUninitialized(EditableNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, EditableNodeIds.GetData(), EditableNodeCount)); - // Retrieve object name. - FString CurrentObjectName = CurrentObjectInfo.Name; + for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) + { + HAPI_GeoInfo CurrentEditableGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); + + // TODO: Check whether this display geo is actually being output + // Just because this is a display node doesn't mean that it will be output (it + // might be in a hidden subnet) + + // Do not process the main display geo twice! + if (CurrentEditableGeoInfo.isDisplayGeo) + continue; - // Get transformation for this object. - const HAPI_Transform & ObjectTransform = ObjectTransforms[ObjectId]; - FTransform TransformMatrix; - FHoudiniEngineUtils::TranslateHapiTransform(ObjectTransform, TransformMatrix); + // We only handle editable curves for now + if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) + continue; - // TODO: Check transforms?? + // Add this geo to the geo info array + EditableGeoInfos.Add(CurrentEditableGeoInfo); + } + } - // Build an array of the geos we'll need to process - // In most case, it will only be the display geo, - // but we may also want to process editable geos as well - TArray GeoInfos; + - // Get the Display Geo's info - HAPI_GeoInfo DisplayHapiGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) + const bool bIsSopAsset = AssetInfo.nodeId != AssetInfo.objectNodeId; + bool bUseOutputFromSubnets = true; + if (bAssetHasChildren) + { + if (FHoudiniEngineUtils::ContainsSopNodes(AssetInfo.nodeId)) { - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s] unable to retrieve GeoInfo, - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName); + // This HDA contains immediate SOP nodes. Don't look for subnets to output. + bUseOutputFromSubnets = false; } else { - // Add the display geo info to the array - GeoInfos.Add(DisplayHapiGeoInfo); + // Assume we're using a subnet-based HDA + bUseOutputFromSubnets = true; } + } + else + { + // This asset doesn't have any children. Don't try to find subnets. + bUseOutputFromSubnets = false; + } - // Handle the editable nodes for this geo - // Start by getting the number of editable nodes - int32 EditableNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, - true, &EditableNodeCount)); + // Before we can perform visibility checks on the Object nodes, we have + // to build a set of all the Object node ids. The 'AllObjectIds' act + // as a visibility filter. If an Object node is not present in this + // list, the content of that node will not be displayed (display / output / templated nodes). + // NOTE that if the HDA contains immediate SOP nodes we will ignore + // all subnets and only use the data outputs directly from the HDA. - if (EditableNodeCount > 0) - { - TArray< HAPI_NodeId > EditableNodeIds; - EditableNodeIds.SetNumUninitialized(EditableNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - AssetId, EditableNodeIds.GetData(), EditableNodeCount)); + TSet AllObjectIds; + if (bUseOutputFromSubnets) + { + int NumObjSubnets; + TArray ObjectIds; + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, + HAPI_NODETYPE_OBJ, + HAPI_NODEFLAGS_OBJ_SUBNET, + true, + &NumObjSubnets + ), + false); + + ObjectIds.SetNumUninitialized(NumObjSubnets); + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, + ObjectIds.GetData(), + NumObjSubnets + ), + false); + AllObjectIds.Append(ObjectIds); + } + else + { + AllObjectIds.Add(AssetInfo.objectNodeId); + } - for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) - { - HAPI_GeoInfo CurrentEditableGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); + TMap CurrentCookCounts; + + // Iterate through all objects. + int32 OutputIdx = 1; + for (int32 ObjectIdx = 0; ObjectIdx < ObjectInfos.Num(); ObjectIdx++) + { + // Retrieve the object info + const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectIdx]; - // Do not process the main display geo twice! - if (CurrentEditableGeoInfo.isDisplayGeo) - continue; + // Determine whether this object node is fully visible. + bool bObjectIsVisible = false; + HAPI_NodeId GatherOutputsNodeId = -1; // Outputs will be gathered from this node. + if (!bAssetHasChildren) + { + // If the asset doesn't have children, we have to gather outputs from the asset's parent in order to output + // this asset node + bObjectIsVisible = true; + GatherOutputsNodeId = AssetNodeInfo.parentId; + } + else if (bIsSopAsset && CurrentHapiObjectInfo.nodeId == AssetInfo.objectNodeId) + { + // When dealing with a SOP asset, be sure to gather outputs from the SOP node, not the + // outer object node. + bObjectIsVisible = true; + GatherOutputsNodeId = AssetInfo.nodeId; + } + else + { + bObjectIsVisible = FHoudiniEngineUtils::IsObjNodeFullyVisible(AllObjectIds, AssetId, CurrentHapiObjectInfo.nodeId); + GatherOutputsNodeId = CurrentHapiObjectInfo.nodeId; + } - // We only handle editable curves for now - if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) - continue; + // Cache/convert them + FHoudiniObjectInfo CurrentObjectInfo; + CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); - // Add this geo to the geo info array - GeoInfos.Add(CurrentEditableGeoInfo); - } - } + // Retrieve object name. + FString CurrentObjectName = CurrentObjectInfo.Name; - // Handle the templated nodes if desired - if (InOutputTemplatedGeos) + // Get transformation for this object. + FTransform TransformMatrix = FTransform::Identity; + if (ObjectTransforms.IsValidIndex(ObjectIdx)) { - // Start by getting the number of templated nodes - int32 TemplatedNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, - HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_TEMPLATED, - true, &TemplatedNodeCount)); + const HAPI_Transform & ObjectTransform = ObjectTransforms[ObjectIdx]; + FHoudiniEngineUtils::TranslateHapiTransform(ObjectTransform, TransformMatrix); + } + else + { + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: No HAPI transform for Object [%d %s] - using identity."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName); + } - if (TemplatedNodeCount > 0) - { - TArray TemplatedNodeIds; - TemplatedNodeIds.SetNumUninitialized(TemplatedNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, TemplatedNodeIds.GetData(), TemplatedNodeCount)); + // Build an array of the geos we'll need to process + // In most case, it will only be the display geo, + // but we may also want to process editable geos as well + TArray GeoInfos; - for (int32 nTemplated = 0; nTemplated < TemplatedNodeCount; nTemplated++) - { - HAPI_GeoInfo CurrentTemplatedGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentTemplatedGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - TemplatedNodeIds[nTemplated], &CurrentTemplatedGeoInfo)); + // These node ids may need to be cooked in order to extract part counts. + TSet ForceNodesToCook; - // Do not process the main display geo twice! - if (CurrentTemplatedGeoInfo.isDisplayGeo) - continue; + // Track (heightfield) tile ids in order to determine + // when to create new tiles (used when outputting landscape edit layers). + TSet FoundTileIndices; - // We don't want all the nested template node IDs, - // as our HDA could potentially be using other HDAs with nested template flags - // Make sure the parent of the templated node is either the HDA, the current OBJ or the Display SOP - HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CurrentTemplatedGeoInfo.nodeId); - if (ParentId != CurrentHapiObjectInfo.nodeId - && ParentId != DisplayHapiGeoInfo.nodeId - && ParentId != AssetId) - { - continue; - } + // Append the initial set of editable geo infos here + // then clear the editable geo infos array since we + // only want to process them once. + GeoInfos.Append(EditableGeoInfos); + EditableGeoInfos.Empty(); - // Add this geo to the geo info array - GeoInfos.Add(CurrentTemplatedGeoInfo); - } - } - } + if (bObjectIsVisible) + { + // NOTE: The HAPI_GetDisplayGeoInfo will not always return the expected Geometry subnet's + // Display flag geometry. If the Geometry subnet contains an Object subnet somewhere, the + // GetDisplayGeoInfo will sometimes fetch the display SOP from within the subnet which is + // not what we want. + + // Resolve and gather outputs (display / output / template nodes) from the GatherOutputsNodeId. + FHoudiniEngineUtils::GatherImmediateOutputGeoInfos(GatherOutputsNodeId, + InUseOutputNodes, + InOutputTemplatedGeos, + GeoInfos, + ForceNodesToCook); + + } // if (bObjectIsVisible) // Iterates through the geos we want to process for (int32 GeoIdx = 0; GeoIdx < GeoInfos.Num(); GeoIdx++) { - // Cook editable/templated nodes to get their parts. + // Cache the geo nodes ids for this asset const HAPI_GeoInfo& CurrentHapiGeoInfo = GeoInfos[GeoIdx]; - if ((CurrentHapiGeoInfo.isEditable && CurrentHapiGeoInfo.partCount <= 0) - || (CurrentHapiGeoInfo.isTemplated && CurrentHapiGeoInfo.partCount <= 0)) + // We shouldn't add display nodes for cooking since the + // if (!CurrentHapiGeoInfo.isDisplayGeo) + // { + // OutNodeIdsToCook.Add(CurrentHapiGeoInfo.nodeId); + // } + + // We cannot rely on the bGeoHasChanged flag when dealing with session sync. Since the + // property will be set to false for any node that has cooked twice. Instead, we compare + // current cook counts against the last cached count that we have in order to determine + // whether geo has changed. + bool bHasChanged = false; + + if (!CurrentCookCounts.Contains(CurrentHapiGeoInfo.nodeId)) + { + CurrentCookCounts.Add(CurrentHapiGeoInfo.nodeId, FHoudiniEngineUtils::HapiGetCookCount(CurrentHapiGeoInfo.nodeId)); + } + + if (OutputNodeCookCounts.Contains(CurrentHapiGeoInfo.nodeId)) + { + // If the cook counts changed, we assume the geo has changed. + bHasChanged = OutputNodeCookCounts[CurrentHapiGeoInfo.nodeId] != CurrentCookCounts[CurrentHapiGeoInfo.nodeId]; + } + else + { + // Something is new! We don't have a cook count for this node. + bHasChanged = true; + } + + // Left in here for debugging convenience. + // if (bHasChanged) + // { + // FString NodePath; + // FHoudiniEngineUtils::HapiGetAbsNodePath(CurrentHapiGeoInfo.nodeId, NodePath); + // HOUDINI_LOG_MESSAGE(TEXT("[TaskCookAsset] We say Geo Has Changed!: %d, %s"), CurrentHapiGeoInfo.nodeId, *NodePath); + // } + + // HERE BE FUDGING! + // Change the hasGeoChanged flag on the GeoInfo to match our expectation + // of whether geo has changed. + GeoInfos[GeoIdx].hasGeoChanged = CurrentHapiGeoInfo.hasGeoChanged || bHasChanged; + + // Cook editable/templated nodes to get their parts. + if ((ForceNodesToCook.Contains(CurrentHapiGeoInfo.nodeId) && CurrentHapiGeoInfo.partCount <= 0) + || (CurrentHapiGeoInfo.isEditable && CurrentHapiGeoInfo.partCount <= 0) + || (CurrentHapiGeoInfo.isTemplated && CurrentHapiGeoInfo.partCount <= 0) + || (!CurrentHapiGeoInfo.isDisplayGeo && CurrentHapiGeoInfo.partCount <= 0)) { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( @@ -1013,7 +1280,9 @@ FHoudiniOutputTranslator::BuildAllOutputs( // Simply create an empty array for this geo's group names // We might need it later for splitting TArray GeoGroupNames; - bool HasSocketGroups = false; + + // Store all the sockets found for this geo's part + TArray GeoMeshSockets; // Iterate on this geo's parts for (int32 PartId = 0; PartId < CurrentGeoInfo.PartCount; ++PartId) @@ -1195,10 +1464,29 @@ FHoudiniOutputTranslator::BuildAllOutputs( CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); continue; } + + // Extract Mesh sockets + // Do this before ignoring invalid parts, as socket groups/attributes could be set on parts + // that don't have any mesh, just points! Those would be be considered "invalid" parts but + // could still have valid sockets! + TArray PartMeshSockets; + FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, PartMeshSockets, CurrentHapiPartInfo.isInstanced); + FHoudiniEngineUtils::AddMeshSocketsToArray_Group( + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, PartMeshSockets, CurrentHapiPartInfo.isInstanced); // Ignore invalid parts if (CurrentPartType == EHoudiniPartType::Invalid) + { + if(PartMeshSockets.Num() > 0) + { + // Store these Part sockets for the Geo + // We'll copy them to the outputs produced by this Geo later + GeoMeshSockets.Append(PartMeshSockets); + } + continue; + } // Build the HGPO corresponding to this part FHoudiniGeoPartObject currentHGPO; @@ -1235,6 +1523,14 @@ FHoudiniOutputTranslator::BuildAllOutputs( currentHGPO.GeoInfo = CurrentGeoInfo; currentHGPO.PartInfo = CurrentPartInfo; + currentHGPO.AllMeshSockets = PartMeshSockets; + + // If the mesh is NOT visible and is NOT instanced, skip it. + if (!currentHGPO.bIsVisible && !currentHGPO.bIsInstanced) + { + continue; + } + // We only support meshes for templated geos if (currentHGPO.bIsTemplated && (CurrentPartType != EHoudiniPartType::Mesh)) continue; @@ -1254,7 +1550,7 @@ FHoudiniOutputTranslator::BuildAllOutputs( // // Extract the group names used by this part to see if it will require splitting // Only meshes can be split, via their primitive groups - TArray< FString > SplitGroupNames; + TArray SplitGroupNames; if (CurrentPartType == EHoudiniPartType::Mesh) { if (!CurrentHapiPartInfo.isInstanced && GeoGroupNames.Num() > 0) @@ -1349,20 +1645,24 @@ FHoudiniOutputTranslator::BuildAllOutputs( // Now see if this volume has a tile attribute TArray TileValues; - if (FHoudiniEngineUtils::GetTileAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, TileValues, HAPI_ATTROWNER_PRIM)) + if (FHoudiniEngineUtils::GetTileAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, TileValues, HAPI_ATTROWNER_PRIM, 0, 1)) { if (TileValues.Num() > 0 && TileValues[0] >= 0) currentHGPO.VolumeTileIndex = TileValues[0]; else currentHGPO.VolumeTileIndex = -1; } + + currentHGPO.bHasEditLayers = FHoudiniEngineUtils::GetEditLayerName(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, currentHGPO.VolumeLayerName, HAPI_ATTROWNER_PRIM); } } currentHGPO.VolumeInfo = CurrentVolumeInfo; // Cache the curve info as well + // !!! Only call GetCurveInfo if the PartType is Curve + // !!! Closed curves are actually Meshes, and calling GetCurveInfo on a Mesh will crash HAPI! FHoudiniCurveInfo CurrentCurveInfo; - if (CurrentPartType == EHoudiniPartType::Curve) + if (CurrentPartType == EHoudiniPartType::Curve && CurrentPartInfo.Type == EHoudiniPartType::Curve) { HAPI_CurveInfo CurrentHapiCurveInfo; FHoudiniApi::CurveInfo_Init(&CurrentHapiCurveInfo); @@ -1383,12 +1683,6 @@ FHoudiniOutputTranslator::BuildAllOutputs( // See if a custom bake folder override for the mesh was assigned via the "unreal_bake_folder" attribute //TArray BakeFolderOverrides; - // Extract socket points - FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - currentHGPO.GeoId, currentHGPO.PartId, AllSockets, CurrentHapiPartInfo.isInstanced); - FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - currentHGPO.GeoId, currentHGPO.PartId, AllSockets, CurrentHapiPartInfo.isInstanced); - // See if we have an existing output that matches this HGPO or if we need to create a new one bool IsFoundOutputValid = false; UHoudiniOutput ** FoundHoudiniOutput = nullptr; @@ -1399,9 +1693,20 @@ FHoudiniOutputTranslator::BuildAllOutputs( FoundHoudiniOutput = InOldOutputs.FindByPredicate( [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(currentHGPO) : false; }); - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) - IsFoundOutputValid = true; + if (FoundHoudiniOutput && *FoundHoudiniOutput && currentHGPO.Type == EHoudiniPartType::Curve) + { + // Curve hacks!! + // If we're dealing with a curve, editable and non-editable curves are interpreted very + // differently so we have to apply an IsEditable comparison as well. + if ((*FoundHoudiniOutput)->IsEditableNode() != currentHGPO.bIsEditable) + { + // The IsEditable property is different. We can't reuse this output! + FoundHoudiniOutput = nullptr; + } + } + if (FoundHoudiniOutput && IsValid(*FoundHoudiniOutput)) + IsFoundOutputValid = true; } else { @@ -1409,7 +1714,7 @@ FHoudiniOutputTranslator::BuildAllOutputs( FoundHoudiniOutput = InOldOutputs.FindByPredicate( [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, true) : false; }); - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + if (FoundHoudiniOutput && IsValid(*FoundHoudiniOutput)) IsFoundOutputValid = true; // If we dont have a match in the old maps, also look in the newly created outputs @@ -1418,7 +1723,7 @@ FHoudiniOutputTranslator::BuildAllOutputs( FoundHoudiniOutput = OutNewOutputs.FindByPredicate( [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, false) : false; }); - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + if (FoundHoudiniOutput && IsValid(*FoundHoudiniOutput)) IsFoundOutputValid = true; } } @@ -1435,14 +1740,32 @@ FHoudiniOutputTranslator::BuildAllOutputs( else { // We couldn't find a valid output object, so create a new one - - // If the current part is a volume, only create a new output object - // if the volume's name is "height", if not store the HGPO aside - if (currentHGPO.Type == EHoudiniPartType::Volume - && !currentHGPO.VolumeName.Equals(HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME, ESearchCase::IgnoreCase)) + if (currentHGPO.Type == EHoudiniPartType::Volume) { - UnassignedVolumeParts.Add(currentHGPO); - continue; + bool bBatchHGPO = false; + if(!currentHGPO.VolumeName.Equals(HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME, ESearchCase::IgnoreCase)) + { + // This volume is not a height volume, so it will be batched into a single HGPO. + bBatchHGPO = true; + } + else if (currentHGPO.bHasEditLayers) + { + if (FoundTileIndices.Contains(currentHGPO.VolumeTileIndex)) + { + // If this volume name is height, AND we have edit layers enabled, check to see whether + // this is a new tile. If this is NOT a new tile, we assume that this is simply content + // for a new edit layer on the current tile. Batch it! + bBatchHGPO = true; + } + } + // Ensure this tile is tracked + FoundTileIndices.Add(currentHGPO.VolumeTileIndex); + if (bBatchHGPO) + { + // We want to batch this HGPO with the output object. Process it later. + UnassignedVolumeParts.Add(currentHGPO); + continue; + } } // Create a new output object @@ -1454,29 +1777,61 @@ FHoudiniOutputTranslator::BuildAllOutputs( RF_NoFlags); // Make sure the created object is valid - if (!HoudiniOutput || HoudiniOutput->IsPendingKill()) + if (!IsValid(HoudiniOutput)) { //HOUDINI_LOG_WARNING("Failed to create asset output"); continue; } // Mark if the HoudiniOutput is editable - HoudiniOutput->SetIsEditableNode(currentHGPO.bIsEditable); } + // Ensure that we always update the 'Editable' state of the output since this + // may very well change between cooks (for example, the User is editina the HDA is session sync). + HoudiniOutput->SetIsEditableNode(currentHGPO.bIsEditable); // Add the HGPO to the output HoudiniOutput->AddNewHGPO(currentHGPO); // Add this output object to the new ouput array OutNewOutputs.AddUnique(HoudiniOutput); + } + // END: for Part + + if (GeoMeshSockets.Num() > 0) + { + // If we have any mesh socket, assign them to the HGPO for this geo + for (auto& CurNewOutput : OutNewOutputs) + { + if (!IsValid(CurNewOutput)) + continue; + + int32 FirstValidIdx = CurNewOutput->StaleCount; + if (!CurNewOutput->HoudiniGeoPartObjects.IsValidIndex(FirstValidIdx)) + FirstValidIdx = 0; + + for (int32 Idx = FirstValidIdx; Idx < CurNewOutput->HoudiniGeoPartObjects.Num(); Idx++) + { + // Only add sockets to valid/non stale HGPOs + FHoudiniGeoPartObject& CurHGPO = CurNewOutput->HoudiniGeoPartObjects[Idx]; + if (CurHGPO.ObjectId != CurrentHapiObjectInfo.nodeId) + continue; + + if (CurHGPO.GeoId != CurrentHapiGeoInfo.nodeId) + continue; + + CurHGPO.AllMeshSockets.Append(GeoMeshSockets); + } + } } } + // END: for GEO } + // END: for OBJ // Update the output/HGPO associations from the map // Clear the old HGPO since we don't need them anymore for (auto& CurrentOuput : OutNewOutputs) { - if (!CurrentOuput || CurrentOuput->IsPendingKill()) + if (!IsValid(CurrentOuput)) continue; CurrentOuput->DeleteAllStaleHGPOs(); @@ -1494,7 +1849,7 @@ FHoudiniOutputTranslator::BuildAllOutputs( return Output ? Output->HeightfieldMatch(currentVolumeHGPO, false) : false; }); - if (!FoundHoudiniOutput || !(*FoundHoudiniOutput) || (*FoundHoudiniOutput)->IsPendingKill()) + if (!FoundHoudiniOutput || !IsValid(*FoundHoudiniOutput)) { // Skip - consider this volume as invalid continue; @@ -1518,9 +1873,25 @@ FHoudiniOutputTranslator::BuildAllOutputs( bool FHoudiniOutputTranslator::UpdateChangedOutputs(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; + + UObject* OuterComponent = HAC; + + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); + PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); + + PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + PackageParams.OuterPackage = HAC->GetComponentLevel(); + PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); + PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); + PackageParams.ComponentGUID = HAC->GetComponentGUID(); + PackageParams.ObjectName = FString(); + TArray& Outputs = HAC->Outputs; // Iterate through the outputs array of HAC. @@ -1565,11 +1936,11 @@ FHoudiniOutputTranslator::UpdateChangedOutputs(UHoudiniAssetComponent* HAC) // Instantiate the HDA if it's not been // This is because CreateAllInstancersFromHoudiniOutput() actually reads the transform from HAPI // Calling it on a HDA not yet instantiated causes a crash... - HAC->AssetState = EHoudiniAssetState::PreInstantiation; + HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); } else { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurrentOutput, Outputs, HAC); + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurrentOutput, Outputs, HAC, PackageParams); } } } @@ -1796,7 +2167,7 @@ FHoudiniOutputTranslator::CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHou void -FHoudiniOutputTranslator::ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC) +FHoudiniOutputTranslator::ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC, TArray& OutputsPendingClear, bool bForceClearAll) { if (!IsValid(InHAC)) return; @@ -1806,7 +2177,14 @@ FHoudiniOutputTranslator::ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC) // Simply clearing the array is enough for (auto& OldOutput : InHAC->Outputs) { - ClearOutput(OldOutput); + if (OldOutput->ShouldDeferClear() && !bForceClearAll) + { + OutputsPendingClear.Add(OldOutput); + } + else + { + ClearOutput(OldOutput); + } } InHAC->Outputs.Empty(); @@ -1877,32 +2255,27 @@ FHoudiniOutputTranslator::ClearOutput(UHoudiniOutput* Output) UHierarchicalInstancedStaticMeshComponent* const FoliageHISMC = Cast(Component); if (IsValid(FoliageHISMC)) { - // Find the parent component: the foliage component outer, otherwise, if a houdini asset actor, the - // houdini asset component, otherwise for a normal actor its root component, finally try and see - // if the outer itself is a component. - USceneComponent* ParentComponent = Cast(FoliageHISMC->GetOuter()); - if (!IsValid(ParentComponent)) + // Find the parent component: the output is typically owned by an HAC. + USceneComponent* ParentComponent = nullptr; + UObject* const OutputOuter = Output->GetOuter(); + if (IsValid(OutputOuter)) { - UObject* const OutputOuter = Output->GetOuter(); - if (IsValid(OutputOuter)) + if (OutputOuter->IsA()) { - if (OutputOuter->IsA()) - { - ParentComponent = Cast(OutputOuter)->GetHoudiniAssetComponent(); - } - else if (OutputOuter->IsA()) - { - ParentComponent = Cast(OutputOuter)->GetRootComponent(); - } - else - { - ParentComponent = Cast(OutputOuter); - } + ParentComponent = Cast(OutputOuter); } + // other possibilities? } + + // fallback to trying the owner of the HISMC + if (!ParentComponent) + { + ParentComponent = Cast(FoliageHISMC); + } + if (IsValid(ParentComponent)) { - FHoudiniInstanceTranslator::CleanupFoliageInstances(FoliageHISMC, ParentComponent); + FHoudiniInstanceTranslator::CleanupFoliageInstances(FoliageHISMC, OutputObject.Value.OutputObject, ParentComponent); FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); } } @@ -1953,33 +2326,10 @@ FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(const HAPI_NodeId & Nod return true; } -void -FHoudiniOutputTranslator::GetBakeFolderFromAttribute(UHoudiniAssetComponent * HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &DisplayGeoInfo)) - return; - - FString BakeFolderOverride = FString(); - - FHoudiniEngineUtils::GetBakeFolderOverridePath(DisplayGeoInfo.nodeId, BakeFolderOverride); - - // If the TempCookFolder of the HAC is non-empty and is different from the override path. - // do not override it if the current temp cook path is valid. (it was user specified) - if (!HAC->BakeFolder.Path.IsEmpty() && !HAC->BakeFolder.Path.Equals(BakeFolderOverride)) - return; - - HAC->BakeFolder.Path = BakeFolderOverride; -} - void FHoudiniOutputTranslator::GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; HAPI_GeoInfo DisplayGeoInfo; diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h index 76b45c06e..db076c535 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,6 +28,8 @@ #include "HAPI/HAPI_Common.h" +#include "CoreMinimal.h" + class UHoudiniOutput; class UHoudiniAssetComponent; @@ -64,9 +66,12 @@ struct HOUDINIENGINE_API FHoudiniOutputTranslator static bool BuildAllOutputs( const HAPI_NodeId& AssetId, UObject* InOuterObject, + const TArray& OutputNodes, + const TMap& OutputNodeCookCounts, TArray& InOldOutputs, TArray& OutNewOutputs, - const bool& InOutputTemplatedGeos); + const bool& InOutputTemplatedGeos, + const bool& InUseOutputNodes); static bool UpdateChangedOutputs( UHoudiniAssetComponent* HAC); @@ -83,12 +88,20 @@ struct HOUDINIENGINE_API FHoudiniOutputTranslator static void CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache); static void CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache); - // Helper to clear the outputs of the houdini asset component - static void ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC); + /** + * Helper to clear the outputs of the houdini asset component + * + * Some outputs (such as landscapes) need "deferred clearing". This means that + * these outputs should only be destroyed AFTER the new outputs have been processed. + * + * @param InHAC All outputs for this Houdini Asset Component will be cleared. + * @param OutputsPendingClear Any outputs that is "pending" clear. These outputs should typically be cleared AFTER the new outputs have been fully processed. + * @param bForceClearAll Setting this flag will force outputs to be cleared here and not take into account outputs requested a deferred clear. + */ + static void ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC, TArray& OutputsPendingClear, bool bForceClearAll = false); // Helper to clear an individual UHoudiniOutput static void ClearOutput(UHoudiniOutput* Output); static bool GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName); - static void GetBakeFolderFromAttribute(UHoudiniAssetComponent * HAC); static void GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp index 73fe067d3..1ae043f6c 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp @@ -1,5 +1,30 @@ -#include "HoudiniPDGImporterMessages.h" +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include "HoudiniPDGImporterMessages.h" FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage() : FilePath() @@ -79,3 +104,4 @@ FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage(const { } + diff --git a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h index c09b330a4..d9dc0b1b1 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h @@ -1,3 +1,29 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + #pragma once #include "CoreMinimal.h" @@ -159,3 +185,4 @@ struct HOUDINIENGINE_API FHoudiniPDGImportBGEOResultMessage : public FHoudiniPDG TArray Outputs; }; + diff --git a/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp b/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp index 3a2186537..da5754b05 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,7 @@ #include "HAL/FileManager.h" #include "HoudiniApi.h" +#include "HoudiniAsset.h" #include "HoudiniEngine.h" #include "HoudiniEngineUtils.h" #include "HoudiniEngineString.h" @@ -62,7 +63,7 @@ FHoudiniPDGManager::~FHoudiniPDGManager() bool FHoudiniPDGManager::InitializePDGAssetLink(UHoudiniAssetComponent* InHAC) { - if (!InHAC || InHAC->IsPendingKill()) + if (!IsValid(InHAC)) return false; int32 AssetId = InHAC->GetAssetId(); @@ -75,13 +76,13 @@ FHoudiniPDGManager::InitializePDGAssetLink(UHoudiniAssetComponent* InHAC) // Create a new PDG Asset Link Object bool bRegisterPDGAssetLink = false; UHoudiniPDGAssetLink* PDGAssetLink = InHAC->GetPDGAssetLink(); - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + if (!IsValid(PDGAssetLink)) { PDGAssetLink = NewObject(InHAC, UHoudiniPDGAssetLink::StaticClass(), NAME_None, RF_Transactional); bRegisterPDGAssetLink = true; } - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + if (!IsValid(PDGAssetLink)) return false; PDGAssetLink->AssetID = AssetId; @@ -155,7 +156,7 @@ FHoudiniPDGManager::InitializePDGAssetLink(UHoudiniAssetComponent* InHAC) bool FHoudiniPDGManager::UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink) { - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + if (!IsValid(PDGAssetLink)) return false; // If the PDG Asset link is inactive, indicate that our HDA must be instantiated @@ -171,7 +172,7 @@ FHoudiniPDGManager::UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink) else if (ParentHAC && ParentHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) { PDGAssetLink->LinkState = EPDGLinkState::Linking; - ParentHAC->AssetState = EHoudiniAssetState::PreInstantiation; + ParentHAC->SetAssetState(EHoudiniAssetState::PreInstantiation); } else { @@ -213,7 +214,7 @@ bool FHoudiniPDGManager::PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys) { // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + if (!IsValid(PDGAssetLink)) return false; // Get all the network nodes within the asset, recursively. @@ -448,7 +449,7 @@ bool FHoudiniPDGManager::PopulateTOPNodes( const TArray& InTopNodeIDs, UTOPNetwork* InTOPNetwork, UHoudiniPDGAssetLink* InPDGAssetLink, bool bInZeroWorkItemTallys) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return false; if (!IsValid(InTOPNetwork)) @@ -492,7 +493,7 @@ FHoudiniPDGManager::PopulateTOPNodes( if (bInZeroWorkItemTallys) { - CurrentTOPNode->WorkItemTally.ZeroAll(); + CurrentTOPNode->ZeroWorkItemTally(); CurrentTOPNode->NodeState = EPDGNodeState::None; } } @@ -540,7 +541,7 @@ FHoudiniPDGManager::PopulateTOPNodes( if (!IsValid(CurTOPNode)) continue; - InPDGAssetLink->ClearTOPNodeWorkItemResults(CurTOPNode); + UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(CurTOPNode); } InTOPNetwork->AllTOPNodes = AllTOPNodes; @@ -622,6 +623,8 @@ FHoudiniPDGManager::DirtyAll(UTOPNetwork* InTOPNet) void FHoudiniPDGManager::CookOutput(UTOPNetwork* InTOPNet) { + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGManager::CookOutput); + // Cook the output TOP node of the currently selected TOP network. //WorkItemTally.ZeroAll(); //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); @@ -743,7 +746,7 @@ FHoudiniPDGManager::Update() } UHoudiniPDGAssetLink* CurPDGAssetLink = PDGAssetLinks[Idx].Get(); - if (!CurPDGAssetLink || CurPDGAssetLink->IsPendingKill()) + if (!IsValid(CurPDGAssetLink)) { PDGAssetLinks.RemoveAt(Idx); continue; @@ -871,6 +874,7 @@ void FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo) { UHoudiniPDGAssetLink* PDGAssetLink = nullptr; + UTOPNetwork* TOPNetwork = nullptr; UTOPNode* TOPNode = nullptr; HAPI_PDG_EventType EventType = (HAPI_PDG_EventType)EventInfo.eventType; @@ -882,12 +886,9 @@ FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, const FString CurrentWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(CurrentWorkItemState); const FString LastWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(LastWorkItemState); - if(!GetTOPAssetLinkAndNode(EventInfo.nodeId, PDGAssetLink, TOPNode) - || PDGAssetLink == nullptr || PDGAssetLink->IsPendingKill() - || TOPNode == nullptr || TOPNode->IsPendingKill() - || TOPNode->NodeId != EventInfo.nodeId) - { - + if(!GetTOPAssetLinkNetworkAndNode(EventInfo.nodeId, PDGAssetLink, TOPNetwork, TOPNode) + || !IsValid(PDGAssetLink) || !IsValid(TOPNetwork) || !IsValid(TOPNode) || !IsValid(TOPNode)) + { HOUDINI_LOG_WARNING(TEXT("[ProcessPDGEvent]: Could not find matching TOPNode for event %s, workitem id %d, node id %d"), *EventName, EventInfo.workitemId, EventInfo.nodeId); return; } @@ -910,14 +911,15 @@ FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, break; case HAPI_PDG_EVENT_WORKITEM_ADD: + CreateOrRelinkWorkItem(TOPNode, InContextID, EventInfo.workitemId); bUpdatePDGNodeState = true; - NotifyTOPNodeTotalWorkItem(PDGAssetLink, TOPNode, 1); + NotifyTOPNodeCreatedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); break; case HAPI_PDG_EVENT_WORKITEM_REMOVE: RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, TOPNode); bUpdatePDGNodeState = true; - NotifyTOPNodeTotalWorkItem(PDGAssetLink, TOPNode, -1); + NotifyTOPNodeRemovedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); break; case HAPI_PDG_EVENT_COOK_WARNING: @@ -930,6 +932,8 @@ FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, case HAPI_PDG_EVENT_COOK_COMPLETE: SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); + TOPNode->HandleOnPDGEventCookComplete(); + TOPNetwork->HandleOnPDGEventCookCompleteReceivedByChildNode(PDGAssetLink, TOPNode); break; case HAPI_PDG_EVENT_DIRTY_START: @@ -946,44 +950,39 @@ FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, bUpdatePDGNodeState = true; if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) { - NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, -1); } else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) { - NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, -1); } else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) { - NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, -1); } else if ( (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE || LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) { // Handled previously cooked WI - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, -1); } else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) { - NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, -1); } else { // TODO: // unhandled state change - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 0); + HOUDINI_PDG_WARNING(TEXT("Unhandled PDG state change! Node: %s, WorkItemID %d"), IsValid(TOPNode) ? *TOPNode->NodePath : TEXT(""), EventInfo.workitemId); } if (LastWorkItemState == CurrentWorkItemState) { // TODO: // Not a change!! shouldnt happen! - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 0); + HOUDINI_PDG_WARNING(TEXT("Last state == current state! Node: %s, WorkItemID %d, state %d"), IsValid(TOPNode) ? *TOPNode->NodePath : TEXT(""), EventInfo.workitemId, EventInfo.lastState); } // New states if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) { - NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, 1); + NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); } else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNCOOKED) { @@ -997,36 +996,38 @@ FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, } else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) { - NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, 1); + NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); } else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) { - NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, 1); + NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); } else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS || CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE) { - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 1); + NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); // On cook success, handle results - CreateWorkItemResult(TOPNode, InContextID, EventInfo.workitemId, TOPNode->bAutoLoad); + CreateOrRelinkWorkItemResult(TOPNode, InContextID, EventInfo.workitemId, TOPNode->bAutoLoad); } else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) { // TODO: on cook failure, get log path? - NotifyTOPNodeErrorWorkItem(PDGAssetLink, TOPNode, 1); + NotifyTOPNodeErrorWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); MsgColor = FLinearColor::Red; } else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CANCEL) { - // Ignore it because in-progress cooks can be cancelled when automatically recooking graph + NotifyTOPNodeCookCancelledWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); } } break; + case HAPI_PDG_EVENT_COOK_START: + TOPNode->HandleOnPDGEventCookStart(); + break; // Unhandled events case HAPI_PDG_EVENT_DIRTY_ALL: - case HAPI_PDG_EVENT_COOK_START: case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: @@ -1037,14 +1038,14 @@ FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, case HAPI_PDG_EVENT_NODE_RENAME: case HAPI_PDG_EVENT_NODE_CONNECT: case HAPI_PDG_EVENT_NODE_DISCONNECT: - case HAPI_PDG_EVENT_WORKITEM_SET_INT: - case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: - case HAPI_PDG_EVENT_WORKITEM_SET_STRING: - case HAPI_PDG_EVENT_WORKITEM_SET_FILE: - case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: - case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: + case HAPI_PDG_EVENT_WORKITEM_SET_INT: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_STRING: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_FILE: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: // DEPRECATED case HAPI_PDG_EVENT_WORKITEM_RESULT: - case HAPI_PDG_EVENT_WORKITEM_PRIORITY: + case HAPI_PDG_EVENT_WORKITEM_PRIORITY: // DEPRECATED case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: @@ -1062,13 +1063,20 @@ FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, { if (TOPNode->AreAllWorkItemsComplete()) { - if (TOPNode->AnyWorkItemsFailed()) + // At the end of a node/net cook, ensure that the work items are in sync with HAPI and remove any + // work items with invalid ids or that don't exist on the HAPI side anymore. + SyncAndPruneWorkItems(TOPNode); + // Check that all work items are still complete after the sync + if (TOPNode->AreAllWorkItemsComplete()) { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Failed); - } - else - { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); + if (TOPNode->AnyWorkItemsFailed()) + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Failed); + } + else + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); + } } } } @@ -1116,11 +1124,12 @@ FHoudiniPDGManager::ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo) bool -FHoudiniPDGManager::GetTOPAssetLinkAndNode( - const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNode*& OutTOPNode) +FHoudiniPDGManager::GetTOPAssetLinkNetworkAndNode( + const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNetwork*& OutTOPNetwork, UTOPNode*& OutTOPNode) { // Returns the PDGAssetLink and FTOPNode data associated with this TOP node ID OutAssetLink = nullptr; + OutTOPNetwork = nullptr; OutTOPNode = nullptr; for (TWeakObjectPtr& CurAssetLinkPtr : PDGAssetLinks) { @@ -1128,18 +1137,23 @@ FHoudiniPDGManager::GetTOPAssetLinkAndNode( continue; UHoudiniPDGAssetLink* CurAssetLink = CurAssetLinkPtr.Get(); - if (!CurAssetLink || CurAssetLink->IsPendingKill()) + if (!IsValid(CurAssetLink)) continue; - OutTOPNode = CurAssetLink->GetTOPNode((int32)InNodeID); - - if (OutTOPNode != nullptr) + if (CurAssetLink->GetTOPNodeAndNetworkByNodeId((int32)InNodeID, OutTOPNetwork, OutTOPNode)) { - OutAssetLink = CurAssetLink; - return true; + if (OutTOPNetwork != nullptr && OutTOPNode != nullptr) + { + OutAssetLink = CurAssetLink; + return true; + } } } + OutAssetLink = nullptr; + OutTOPNetwork = nullptr; + OutTOPNode = nullptr; + return false; } @@ -1163,7 +1177,8 @@ FHoudiniPDGManager::NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetL //Debug.LogFormat("NotifyTOPNodePDGStateClear:: {0}", topNode._nodeName); InTOPNode->NodeState = EPDGNodeState::None; - InTOPNode->WorkItemTally.ZeroAll(); + InTOPNode->ZeroWorkItemTally(); + InTOPNode->OnDirtyNode(); HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ZeroAll"), *(InTOPNode->NodePath)); @@ -1173,93 +1188,118 @@ FHoudiniPDGManager::NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetL } void -FHoudiniPDGManager::NotifyTOPNodeTotalWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +FHoudiniPDGManager::NotifyTOPNodeCreatedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) { if (!IsValid(InTOPNode)) return; - InTOPNode->WorkItemTally.TotalWorkItems = FMath::Max(InTOPNode->WorkItemTally.TotalWorkItems + Increment, 0); + InTOPNode->OnWorkItemCreated(InWorkItemID); - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally TotalWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally Created WorkItem, Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumWorkItems()); // InPDGAssetLink->bNeedsUIRefresh = true; //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); } void -FHoudiniPDGManager::NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +FHoudiniPDGManager::NotifyTOPNodeRemovedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) { if (!IsValid(InTOPNode)) return; - InTOPNode->WorkItemTally.CookedWorkItems = FMath::Max(InTOPNode->WorkItemTally.CookedWorkItems + Increment, 0); + InTOPNode->OnWorkItemRemoved(InWorkItemID); - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookedWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally Removed WorkItem, Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumWorkItems()); // InPDGAssetLink->bNeedsUIRefresh = true; //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); } void -FHoudiniPDGManager::NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +FHoudiniPDGManager::NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) { if (!IsValid(InTOPNode)) return; - - InTOPNode->WorkItemTally.ErroredWorkItems = FMath::Max(InTOPNode->WorkItemTally.ErroredWorkItems + Increment, 0); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ErroredWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + + InTOPNode->OnWorkItemCooked(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookedWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookedWorkItems()); // InPDGAssetLink->bNeedsUIRefresh = true; //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); } void -FHoudiniPDGManager::NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +FHoudiniPDGManager::NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) { if (!IsValid(InTOPNode)) return; + + InTOPNode->OnWorkItemErrored(InWorkItemID); - InTOPNode->WorkItemTally.WaitingWorkItems = FMath::Max(InTOPNode->WorkItemTally.WaitingWorkItems + Increment, 0); + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ErroredWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumErroredWorkItems()); - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally WaitingWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemWaiting(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally WaitingWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumErroredWorkItems()); // InPDGAssetLink->bNeedsUIRefresh = true; //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); } void -FHoudiniPDGManager::NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +FHoudiniPDGManager::NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) { if (!IsValid(InTOPNode)) return; - - InTOPNode->WorkItemTally.ScheduledWorkItems = FMath::Max(InTOPNode->WorkItemTally.ScheduledWorkItems + Increment, 0); - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ScheduledWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + InTOPNode->OnWorkItemScheduled(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ScheduledWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumScheduledWorkItems()); // InPDGAssetLink->bNeedsUIRefresh = true; //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); } void -FHoudiniPDGManager::NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +FHoudiniPDGManager::NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) { if (!IsValid(InTOPNode)) return; - - InTOPNode->WorkItemTally.CookingWorkItems = FMath::Max(InTOPNode->WorkItemTally.CookingWorkItems + Increment, 0); - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookingWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + InTOPNode->OnWorkItemCooking(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookingWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookingWorkItems()); // InPDGAssetLink->bNeedsUIRefresh = true; //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); } +void +FHoudiniPDGManager::NotifyTOPNodeCookCancelledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemCookCancelled(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookCancelledWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookCancelledWorkItems()); +} + void FHoudiniPDGManager::ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) { - if (!InAssetLink || InAssetLink->IsPendingKill()) + if (!IsValid(InAssetLink)) return; // TODO!!! @@ -1273,7 +1313,7 @@ FHoudiniPDGManager::ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const void FHoudiniPDGManager::RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) { - if (!InAssetLink || InAssetLink->IsPendingKill()) + if (!IsValid(InAssetLink)) return; // Clear all of the work item's results for the specified TOP node and also remove the work item itself from @@ -1284,7 +1324,7 @@ FHoudiniPDGManager::RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI void FHoudiniPDGManager::RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink) { - if (!InAssetLink || InAssetLink->IsPendingKill()) + if (!IsValid(InAssetLink)) return; // Only update the editor properties if the PDG asset link's Actor is selected @@ -1292,7 +1332,7 @@ FHoudiniPDGManager::RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink) InAssetLink->UpdateWorkItemTally(); UHoudiniAssetComponent* HAC = Cast(InAssetLink->GetOuter()); - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; AActor* ActorOwner = HAC->GetOwner(); @@ -1305,7 +1345,7 @@ FHoudiniPDGManager::RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink) void FHoudiniPDGManager::NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess) { - if (!InAssetLink || InAssetLink->IsPendingKill()) + if (!IsValid(InAssetLink)) return; if (bSuccess) @@ -1328,8 +1368,59 @@ FHoudiniPDGManager::NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const b } } +int32 +FHoudiniPDGManager::CreateOrRelinkWorkItem( + UTOPNode* InTOPNode, + const HAPI_PDG_GraphContextId& InContextID, + HAPI_PDG_WorkitemId InWorkItemID) +{ + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to get work %d info: InTOPNode is null."), InWorkItemID); + return INDEX_NONE; + } + + HAPI_PDG_WorkitemInfo WorkItemInfo; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemInfo( + FHoudiniEngine::Get().GetSession(), InContextID, InWorkItemID, &WorkItemInfo)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d info for %s"), InWorkItemID, *(InTOPNode->NodeName)); + // TODO? continue? + return INDEX_NONE; + } + + // Try to find the existing WorkItem by ID. + int32 Index = InTOPNode->ArrayIndexOfWorkResultByID(InWorkItemID); + if (Index == INDEX_NONE) + { + // Try to find the first entry with WorkItemID == INDEX_NONE. The WorkItemIDs are + // transient, so not saved when the map / asset link is saved. So when loading a map containing the asset + // link all the IDs are INDEX_NONE and so we re-use any stale entries in array index order (should be reliable + // if work items generate in the same order. In the future we might have to consider adding support for a + // custom ID attribute for more stable re-linking of work items). + Index = InTOPNode->ArrayIndexOfFirstInvalidWorkResult(); + if (Index == INDEX_NONE) + { + // If we couldn't find a stale entry to re-use, create a new one + FTOPWorkResult LocalWorkResult; + LocalWorkResult.WorkItemID = InWorkItemID; + LocalWorkResult.WorkItemIndex = WorkItemInfo.index; + Index = InTOPNode->WorkResult.Add(LocalWorkResult); + } + else + { + // We found a stale entry, re-use it + FTOPWorkResult& ReUsedWorkResult = InTOPNode->WorkResult[Index]; + ReUsedWorkResult.WorkItemID = InWorkItemID; + ReUsedWorkResult.WorkItemIndex = WorkItemInfo.index; + } + } + + return Index; +} + bool -FHoudiniPDGManager::CreateWorkItemResult( +FHoudiniPDGManager::CreateOrRelinkWorkItemResult( UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID, @@ -1350,6 +1441,30 @@ FHoudiniPDGManager::CreateWorkItemResult( return false; } + // Try to find the existing WorkResult by ID. + int32 WorkResultArrayIndex = InTOPNode->ArrayIndexOfWorkResultByID(InWorkItemID); + FTOPWorkResult* WorkResult = nullptr; + if (WorkResultArrayIndex != INDEX_NONE) + WorkResult = InTOPNode->GetWorkResultByArrayIndex(WorkResultArrayIndex); + if (!WorkResult) + { + // TODO: This shouldn't really happen, it means a work item finished cooking and generated a result before + // we received an event that the work item was added/generated. + WorkResultArrayIndex = CreateOrRelinkWorkItem(InTOPNode, InContextID, InWorkItemID); + if (WorkResultArrayIndex != INDEX_NONE) + { + WorkResult = InTOPNode->GetWorkResultByArrayIndex(WorkResultArrayIndex); + } + } + + if (!WorkResult) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get or add a FTOPWorkResult for WorkItemID %d for %s"), InWorkItemID, *(InTOPNode->NodeName)); + return false; + } + + TArray NewResultObjects; + TSet ResultIndicesThatWereReused; if (WorkItemInfo.numResults > 0) { TArray ResultInfos; @@ -1364,16 +1479,6 @@ FHoudiniPDGManager::CreateWorkItemResult( return false; } - FTOPWorkResult* WorkResult = UHoudiniPDGAssetLink::GetWorkResultByID(InWorkItemID, InTOPNode); - if (!WorkResult) - { - FTOPWorkResult LocalWorkResult; - LocalWorkResult.WorkItemID = InWorkItemID; - LocalWorkResult.WorkItemIndex = WorkItemInfo.index; - const int32 Idx = InTOPNode->WorkResult.Add(LocalWorkResult); - WorkResult = &(InTOPNode->WorkResult[Idx]); - } - FString WorkItemName; FHoudiniEngineString::ToFString(WorkItemInfo.nameSH, WorkItemName); @@ -1395,57 +1500,158 @@ FHoudiniPDGManager::CreateWorkItemResult( // Construct the name and look for an existing work result, re-use it if found, otherwise create a new one const FString WorkResultName = FString::Printf( - TEXT("%s_%s_%d"), + TEXT("%s_%s_%d_%d"), *(InTOPNode->ParentName), *WorkItemName, - WorkItemInfo.index); + WorkResultArrayIndex, + Idx); - FTOPWorkResultObject* ExistingResultObject = WorkResult->ResultObjects.FindByPredicate([WorkResultName](const FTOPWorkResultObject& InResultObject) + // int32 ExistingObjectIndex = WorkResult->ResultObjects.IndexOfByPredicate([WorkResultName](const FTOPWorkResultObject& InResultObject) + // { + // return InResultObject.Name == WorkResultName; + // }); + int32 ExistingObjectIndex = WorkResult->ResultObjects.IndexOfByPredicate([Idx](const FTOPWorkResultObject& InResultObject) { - return InResultObject.Name == WorkResultName; + return InResultObject.WorkItemResultInfoIndex == Idx; }); - if (ExistingResultObject) + if (WorkResult->ResultObjects.IsValidIndex(ExistingObjectIndex)) { - ExistingResultObject->FilePath = CurrentPath; - if (ExistingResultObject->State == EPDGWorkResultState::Loaded && !bInLoadResultObjects) + FTOPWorkResultObject& ExistingResultObject = WorkResult->ResultObjects[ExistingObjectIndex]; + + ExistingResultObject.Name = WorkResultName; + ExistingResultObject.FilePath = CurrentPath; + ExistingResultObject.SetAutoBakedSinceLastLoad(false); + if (ExistingResultObject.State == EPDGWorkResultState::Loaded && !bInLoadResultObjects) { - ExistingResultObject->State = EPDGWorkResultState::ToDelete; + ExistingResultObject.State = EPDGWorkResultState::ToDelete; } - else if ((ExistingResultObject->State == EPDGWorkResultState::Loaded || - ExistingResultObject->State == EPDGWorkResultState::ToDelete || - ExistingResultObject->State == EPDGWorkResultState::Deleting) && bInLoadResultObjects) + else { // When the commandlet is not being used, we could leave the outputs in place and have // translators try to re-use objects/components. When the commandlet is being used, the packages // are always saved and standalone, so if we want to automatically clean up old results then we // need to destroy the existing outputs if (BGEOCommandletStatus == EHoudiniBGEOCommandletStatus::Connected) - ExistingResultObject->DestroyResultOutputs(); - ExistingResultObject->State = EPDGWorkResultState::ToLoad; - } - else - { - ExistingResultObject->State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + { + constexpr bool bDeleteOutputActors = false; + InTOPNode->DeleteWorkResultObjectOutputs(WorkResultArrayIndex, ExistingObjectIndex, bDeleteOutputActors); + } + + if ((ExistingResultObject.State == EPDGWorkResultState::Loaded || + ExistingResultObject.State == EPDGWorkResultState::ToDelete || + ExistingResultObject.State == EPDGWorkResultState::Deleting) && bInLoadResultObjects) + { + ExistingResultObject.State = EPDGWorkResultState::ToLoad; + } + else + { + ExistingResultObject.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + } } + + NewResultObjects.Add(ExistingResultObject); + ResultIndicesThatWereReused.Add(ExistingObjectIndex); } else { FTOPWorkResultObject ResultObj; ResultObj.Name = WorkResultName; ResultObj.FilePath = CurrentPath; - ResultObj.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + ResultObj.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + ResultObj.WorkItemResultInfoIndex = Idx; + ResultObj.SetAutoBakedSinceLastLoad(false); - WorkResult->ResultObjects.Add(ResultObj); + NewResultObjects.Add(ResultObj); } } } + // Destroy any old ResultObjects that were not re-used + const int32 NumPrevResultObjects = WorkResult->ResultObjects.Num(); + for (int32 ResultObjectIndex = 0; ResultObjectIndex < NumPrevResultObjects; ++ResultObjectIndex) + { + if (ResultIndicesThatWereReused.Contains(ResultObjectIndex)) + continue; + InTOPNode->DeleteWorkResultObjectOutputs(WorkResultArrayIndex, ResultObjectIndex); + } + WorkResult->ResultObjects = NewResultObjects; return true; } +int32 +FHoudiniPDGManager::SyncAndPruneWorkItems(UTOPNode* InTOPNode) +{ + TSet WorkItemIDSet; + TArray WorkItemIDs; + int NumWorkItems = -1; + + if (!IsValid(InTOPNode)) + return -1; + + HAPI_Session const * const HAPISession = FHoudiniEngine::Get().GetSession(); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNumWorkitems(HAPISession, InTOPNode->NodeId, &NumWorkItems)) + { + HOUDINI_LOG_WARNING(TEXT("GetNumWorkitems call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); + return -1; + } + + WorkItemIDs.SetNum(NumWorkItems); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitems(HAPISession, InTOPNode->NodeId, WorkItemIDs.GetData(), NumWorkItems)) + { + HOUDINI_LOG_WARNING(TEXT("GetWorkitems call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); + return -1; + } + + HAPI_PDG_GraphContextId ContextId; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId(HAPISession, InTOPNode->NodeId, &ContextId)) + { + HOUDINI_LOG_WARNING(TEXT("GetPDGGraphContextId call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); + return -1; + } + + for (const HAPI_PDG_WorkitemId& WorkItemID : WorkItemIDs) + { + WorkItemIDSet.Add(static_cast(WorkItemID)); + + // If the WorkItemID is not present in FTOPWorkResult array, sync from HAPI + if (InTOPNode->GetWorkResultByID(WorkItemID) == nullptr) + { + CreateOrRelinkWorkItemResult(InTOPNode, ContextId, WorkItemID); + InTOPNode->OnWorkItemCreated(WorkItemID); + } + } + + // TODO: refactor functions that access the TOPNode's array and properties directly to rather be functions on the + // UTOPNode and make access to these arrays protected/private + + // Remove any work result entries with invalid IDs or where the WorkItemID is not in the set of ids returned by + // HAPI (only if we could get the IDs from HAPI). + const FGuid HoudiniComponentGuid(InTOPNode->GetHoudiniComponentGuid()); + int32 NumRemoved = 0; + const int32 NumWorkItemsInArray = InTOPNode->WorkResult.Num(); + for (int32 Index = NumWorkItemsInArray - 1; Index >= 0; --Index) + { + FTOPWorkResult& WorkResult = InTOPNode->WorkResult[Index]; + if (WorkResult.WorkItemID == INDEX_NONE || !WorkItemIDSet.Contains(WorkResult.WorkItemID)) + { + HOUDINI_PDG_WARNING( + TEXT("Pruning a FTOPWorkResult entry from TOP Node %d, WorkItemID %d, WorkItemIndex %d, Array Index %d"), + InTOPNode->NodeId, WorkResult.WorkItemID, WorkResult.WorkItemIndex, Index); + WorkResult.ClearAndDestroyResultObjects(HoudiniComponentGuid); + InTOPNode->WorkResult.RemoveAt(Index); + InTOPNode->OnWorkItemRemoved(WorkResult.WorkItemID); + NumRemoved++; + } + } + + return NumRemoved; +} + void FHoudiniPDGManager::ProcessWorkItemResults() { + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGManager::ProcessWorkItemResults); + const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); for (auto& CurrentPDGAssetLink : PDGAssetLinks) { @@ -1482,7 +1688,7 @@ FHoudiniPDGManager::ProcessWorkItemResults() } else { - PackageParams.OuterPackage = AssetLinkParent->GetOutermost(); + PackageParams.OuterPackage = AssetLinkParent ? AssetLinkParent->GetOutermost() : nullptr; PackageParams.HoudiniAssetName = FString(); PackageParams.HoudiniAssetActorName = FString(); // PackageParams.ComponentGUID = HAC->GetComponentGUID(); @@ -1516,11 +1722,18 @@ FHoudiniPDGManager::ProcessWorkItemResults() // ... All WorkResult CurrentTOPNode->bCachedHaveNotLoadedWorkResults = false; CurrentTOPNode->bCachedHaveLoadedWorkResults = false; - for (FTOPWorkResult& CurrentWorkResult : CurrentTOPNode->WorkResult) + + const int32 NumWorkResults = CurrentTOPNode->WorkResult.Num(); + for (int32 WorkResultArrayIndex = 0; WorkResultArrayIndex < NumWorkResults; ++WorkResultArrayIndex) + // for (FTOPWorkResult& CurrentWorkResult : CurrentTOPNode->WorkResult) { + FTOPWorkResult& CurrentWorkResult = CurrentTOPNode->WorkResult[WorkResultArrayIndex]; // ... All WorkResultObjects - for (FTOPWorkResultObject& CurrentWorkResultObj : CurrentWorkResult.ResultObjects) + const int32 NumWorkResultObjects = CurrentWorkResult.ResultObjects.Num(); + for (int32 WorkResultObjectArrayIndex = 0; WorkResultObjectArrayIndex < NumWorkResultObjects; ++WorkResultObjectArrayIndex) + // for (FTOPWorkResultObject& CurrentWorkResultObj : CurrentWorkResult.ResultObjects) { + FTOPWorkResultObject& CurrentWorkResultObj = CurrentWorkResult.ResultObjects[WorkResultObjectArrayIndex]; if (CurrentWorkResultObj.State == EPDGWorkResultState::ToLoad) { CurrentWorkResultObj.State = EPDGWorkResultState::Loading; @@ -1529,6 +1742,9 @@ FHoudiniPDGManager::ProcessWorkItemResults() PackageParams.PDGTOPNetworkName = CurrentTOPNet->NodeName; PackageParams.PDGTOPNodeName = CurrentTOPNode->NodeName; PackageParams.PDGWorkItemIndex = CurrentWorkResult.WorkItemIndex; + // Use the array index to ensure uniqueness among the work items of the node ( + // CurrentWorkResult.WorkItemIndex is not necessarily unique) + PackageParams.PDGWorkResultArrayIndex = WorkResultArrayIndex; if (CommandletStatus == EHoudiniBGEOCommandletStatus::Connected) { @@ -1549,11 +1765,13 @@ FHoudiniPDGManager::ProcessWorkItemResults() PackageParams)) { CurrentWorkResultObj.State = EPDGWorkResultState::Loaded; + CurrentWorkResultObj.SetAutoBakedSinceLastLoad(false); CurrentTOPNode->bCachedHaveLoadedWorkResults = true; // Broadcast that we have loaded the work result object to those interested AssetLink->OnWorkResultObjectLoaded.Broadcast( - AssetLink, CurrentTOPNode, CurrentWorkResult.WorkItemID, CurrentWorkResultObj.Name); + AssetLink, CurrentTOPNode, WorkResultArrayIndex, + CurrentWorkResultObj.WorkItemResultInfoIndex); } else { @@ -1581,9 +1799,7 @@ FHoudiniPDGManager::ProcessWorkItemResults() CurrentWorkResultObj.State = EPDGWorkResultState::Deleting; // Delete and clean up that WRObj - CurrentWorkResultObj.DestroyResultOutputs(); - CurrentWorkResultObj.GetOutputActorOwner().DestroyOutputActor(); - CurrentWorkResultObj.State = EPDGWorkResultState::Deleted; + CurrentTOPNode->DeleteWorkResultObjectOutputs(WorkResultArrayIndex, WorkResultObjectArrayIndex); CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; } else if (CurrentWorkResultObj.State == EPDGWorkResultState::Deleted) @@ -1629,15 +1845,19 @@ void FHoudiniPDGManager::HandleImportBGEOResultMessage( // Find asset link and work result object UHoudiniPDGAssetLink *AssetLink = nullptr; + UTOPNetwork *TOPNetwork = nullptr; UTOPNode *TOPNode = nullptr; - if (!GetTOPAssetLinkAndNode(InMessage.TOPNodeId, AssetLink, TOPNode) || + if (!GetTOPAssetLinkNetworkAndNode(InMessage.TOPNodeId, AssetLink, TOPNetwork, TOPNode) || !IsValid(AssetLink) || !IsValid(TOPNode)) { HOUDINI_LOG_WARNING(TEXT("Failed to find TOP node with id %d, aborting output object creation."), InMessage.TOPNodeId); return; } - FTOPWorkResult* WorkResult = AssetLink->GetWorkResultByID(InMessage.WorkItemId, TOPNode); + FTOPWorkResult* WorkResult = nullptr; + const int32 WorkResultArrayIndex = TOPNode->ArrayIndexOfWorkResultByID(InMessage.WorkItemId); + if (WorkResultArrayIndex != INDEX_NONE) + WorkResult = TOPNode->GetWorkResultByArrayIndex(WorkResultArrayIndex); if (WorkResult == nullptr) { HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result with id %d, aborting output object creation."), InMessage.WorkItemId); @@ -1812,12 +2032,15 @@ void FHoudiniPDGManager::HandleImportBGEOResultMessage( const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; FHoudiniOutputObject *OutputObject = NewOutput->GetOutputObjects().Find(Identifier); - if (OutputObject && IsValid(OutputObject->OutputComponent)) + if (OutputObject) { - // Update generic property attributes - FHoudiniMeshTranslator::UpdateGenericPropertiesAttributes( - OutputObject->OutputComponent, - ImportOutputObject.GenericAttributes.PropertyAttributes); + if (IsValid(OutputObject->OutputComponent)) + { + // Update generic property attributes + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( + OutputObject->OutputComponent, + ImportOutputObject.GenericAttributes.PropertyAttributes); + } // Copy cached attributes OutputObject->CachedAttributes.Append(ImportOutputObject.CachedAttributes); @@ -1833,9 +2056,11 @@ void FHoudiniPDGManager::HandleImportBGEOResultMessage( if (bSuccess) { WorkResultObject->State = EPDGWorkResultState::Loaded; + WorkResultObject->SetAutoBakedSinceLastLoad(false); HOUDINI_LOG_MESSAGE(TEXT("Loaded geo for %s"), *InMessage.Name); // Broadcast that we have loaded the work result object to those interested - AssetLink->OnWorkResultObjectLoaded.Broadcast(AssetLink, TOPNode, WorkResult->WorkItemID, WorkResultObject->Name); + AssetLink->OnWorkResultObjectLoaded.Broadcast( + AssetLink, TOPNode, WorkResultArrayIndex, WorkResultObject->WorkItemResultInfoIndex); } else { @@ -1970,12 +2195,26 @@ FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) if (InAssetId < 0) return false; + // Get the list of all non bypassed TOP nodes within the current network (ignoring schedulers) + int32 TOPNodeCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER | HAPI_NODEFLAGS_NON_BYPASS, true, &TOPNodeCount)) + { + return false; + } + + // We found valid TOP Nodes, this is a PDG HDA + if (TOPNodeCount > 0) + return true; + + /* // Get all the network nodes within the asset, recursively. // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type int32 NetworkNodeCount = 0; HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( FHoudiniEngine::Get().GetSession(), InAssetId, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); + HAPI_NODETYPE_SOP | HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_NETWORK, true, & NetworkNodeCount), false); if (NetworkNodeCount <= 0) return false; @@ -2014,6 +2253,8 @@ FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) // For each Network we found earlier, only consider those with TOP child nodes // If we find TOP nodes in a valid network, then consider this HDA a PDG HDA + HAPI_NodeInfo CurrentNodeInfo; + FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) { if (CurrentNodeId < 0) @@ -2021,8 +2262,6 @@ FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) continue; } - HAPI_NodeInfo CurrentNodeInfo; - FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) { @@ -2049,6 +2288,7 @@ FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) if (TOPNodeCount > 0) return true; } + */ // No valid TOP node found in SOP/TOP Networks, this is not a PDG HDA return false; diff --git a/Source/HoudiniEngine/Private/HoudiniPDGManager.h b/Source/HoudiniEngine/Private/HoudiniPDGManager.h index de2e6fe3a..10564ec1e 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGManager.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGManager.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -125,11 +125,23 @@ struct HOUDINIENGINE_API FHoudiniPDGManager // node. This destroys any loaded results (geometry etc), and the work item struct. void RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); - // Create FTOPWorkResult for a given TOP node, and optionally (via bInLoadResultObjects) create its FTOPWorkResultObjects. + // Create a (or re-use an existing) FTOPWorkResult for a given TOPNode and the specified work item ID, without + // creating its FTOPWorkResultObjects. + // Returns INDEX_NONE if an entry could not be created or data could not be retrieved from HAPI. + int32 CreateOrRelinkWorkItem(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID); + + // Ensure that FTOPWorkResult exists, and create its FTOPWorkResultObjects for a given TOP node and work item id, + // and optionally (via bInLoadResultObjects) create its FTOPWorkResultObjects. // Geometry is not directly loaded by this function, the FTOPWorkResultObjects' states will be set to ToLoad and // the ProcessWorkItemResults function will take care of loading the geo. // Results must be tagged with 'file', and must have a file path, otherwise will not included. - bool CreateWorkItemResult(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID, bool bInLoadResultObjects=false); + bool CreateOrRelinkWorkItemResult(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID, bool bInLoadResultObjects=false); + + // First Create or re-link FTOPWorkResults based on the work items that exist on InTOPNode in HAPI. + // Then remove any FTOPWorkResults (and clean up their output) from the WorkResults of InTOPNode if: + // WorkResult.WorkItemID is INDEX_NONE + // WorkResult.WorkItemID is not in the list of work item ids that HAPI returns for this node + int32 SyncAndPruneWorkItems(UTOPNode* InTOPNode); // Handles replies from commandlets in response to a FHoudiniPDGImportBGEODiscoverMessage void HandleImportBGEODiscoverMessage( @@ -160,23 +172,27 @@ struct HOUDINIENGINE_API FHoudiniPDGManager static void ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo); // Returns the PDGAssetLink and FTOPNode associated with this TOP node ID - bool GetTOPAssetLinkAndNode(const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNode*& OutTOPNode); + bool GetTOPAssetLinkNetworkAndNode(const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNetwork*& OutTOPNetwork, UTOPNode*& OutTOPNode); void SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState); void NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode); - void NotifyTOPNodeTotalWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + void NotifyTOPNodeCreatedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeRemovedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - void NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + void NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - void NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + void NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - void NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + void NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - void NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + void NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - void NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + void NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeCookCancelledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); private: @@ -196,5 +212,4 @@ struct HOUDINIENGINE_API FHoudiniPDGManager uint32 BGEOCommandletProcessId; // Keep track of the BGEO commandlet status EHoudiniBGEOCommandletStatus BGEOCommandletStatus; - }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp index 53a9ed520..deb0de777 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp @@ -1,490 +1,518 @@ - -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGTranslator.h" - - -#include "Editor.h" -#include "Containers/Array.h" -#include "FileHelpers.h" -#include "LandscapeInfo.h" -// #include "Engine/WorldComposition.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniGeoImporter.h" -#include "HoudiniPackageParams.h" -#include "HoudiniOutput.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniSplineTranslator.h" - -#define LOCTEXT_NAMESPACE "HoudiniEngine" - -bool -FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray InOutputTypesToProcess, - bool bInTreatExistingMaterialsAsUpToDate) -{ - if (!IsValid(InAssetLink)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InAssetLink is null.")); - return false; - } - - if (!IsValid(InTOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InTOPNode is null.")); - return false; - } - - TArray OldTOPOutputs = InWorkResultObject.GetResultOutputs(); - TArray NewTOPOutputs; - - FHoudiniEngine::Get().CreateTaskSlateNotification(LOCTEXT("LoadPDGBGEO", "Loading PDG Output BGEO File...")); - - bool bResult = false; - // Create a new file node in HAPI for the bgeo and cook it - HAPI_NodeId FileNodeId = -1; - bResult = UHoudiniGeoImporter::OpenBGEOFile(InWorkResultObject.FilePath, FileNodeId); - if (bResult) - bResult = UHoudiniGeoImporter::CookFileNode(FileNodeId); - - // If the cook was successful, build outputs - if (bResult) - { - FHoudiniEngine::Get().UpdateTaskSlateNotification( - LOCTEXT("BuildPDGBGEOOutputs", "Building Ouputs from BGEO File...")); - - const bool bAddOutputsToRootSet = false; - bResult = UHoudiniGeoImporter::BuildAllOutputsForNode( - FileNodeId, - InAssetLink, - OldTOPOutputs, - NewTOPOutputs, - bAddOutputsToRootSet); - } - - if (bResult) - { - FHoudiniEngine::Get().UpdateTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); - - // If we successfully received outputs from the BGEO file, process the outputs - FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); - AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - if (!IsValid(WorkItemOutputActor)) - { - UWorld* World = InAssetLink->GetWorld(); - FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); - AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - if (!IsValid(TOPNodeOutputActor)) - { - if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) - TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - } - if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) - WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - } - - for (auto& OldOutput : OldTOPOutputs) - { - FHoudiniOutputTranslator::ClearOutput(OldOutput); - } - OldTOPOutputs.Empty(); - InWorkResultObject.GetResultOutputs().Empty(); - InWorkResultObject.SetResultOutputs(NewTOPOutputs); - - bResult = CreateAllResultObjectsFromPDGOutputs( - NewTOPOutputs, - InPackageParams, - WorkItemOutputActor->GetRootComponent(), - InOutputTypesToProcess, - bInTreatExistingMaterialsAsUpToDate); - - if (!bResult) - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); - else - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); - - InTOPNode->UpdateOutputVisibilityInLevel(); - } - else - { - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("BuildPDGBGEOOutputsFail", "Failed building outputs from BGEO file...")); - } - - // Delete the file node used to load the BGEO via HAPI - if (FileNodeId >= 0) - { - UHoudiniGeoImporter::CloseBGEOFile(FileNodeId); - FileNodeId = -1; - } - - return bResult; -} - -bool -FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray& InOutputs, - TArray InOutputTypesToProcess, - const TMap* InPreBuiltInstancedOutputPartData) -{ - if (!IsValid(InAssetLink)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InAssetLink is null.")); - return false; - } - - if (!IsValid(InTOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InTOPNode is null.")); - return false; - } - - FHoudiniEngine::Get().CreateTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); - - // If we successfully received outputs from the BGEO file, process the outputs - FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); - AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - if (!IsValid(WorkItemOutputActor)) - { - UWorld* World = InAssetLink->GetWorld(); - FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); - AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - if (!IsValid(TOPNodeOutputActor)) - { - if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) - TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - } - if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) - WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - } - - InWorkResultObject.SetResultOutputs(InOutputs); - - const bool bInTreatExistingMaterialsAsUpToDate = true; - const bool bOnlyUseExistingAssets = true; - bool bResult = CreateAllResultObjectsFromPDGOutputs( - InOutputs, - InPackageParams, - WorkItemOutputActor->GetRootComponent(), - InOutputTypesToProcess, - bInTreatExistingMaterialsAsUpToDate, - bOnlyUseExistingAssets, - InPreBuiltInstancedOutputPartData); - - if (!bResult) - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); - else - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); - - InTOPNode->UpdateOutputVisibilityInLevel(); - - return bResult; -} - -bool -FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( - TArray& InOutputs, - const FHoudiniPackageParams& InPackageParams, - UObject* InOuterComponent, - TArray InOutputTypesToProcess, - bool bInTreatExistingMaterialsAsUpToDate, - bool bInOnlyUseExistingAssets, - const TMap* InPreBuiltInstancedOutputPartData) -{ - // Process the new/updated outputs via the various translators - // We try to maintain as much parity with the existing HoudiniAssetComponent workflow - // as possible. - - // // For world composition landscapes - // FString WorldCompositionPath = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - // if (bInUseWorldComposition) - // { - // FHoudiniLandscapeTranslator::EnableWorldComposition(); - // - // // Save the current map as well if world composition is enabled. - // FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext(); - // UWorld* CurrentWorld = EditorWorldContext.World(); - // - // if (CurrentWorld) - // { - // // Save the current map - // FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false); - // UPackage* CurrentWorldPackage = CreatePackage(nullptr, *CurrentWorldPath); - // if (CurrentWorldPackage) - // { - // CurrentWorldPackage->MarkPackageDirty(); - // - // TArray CurrentWorldToSave; - // CurrentWorldToSave.Add(CurrentWorldPackage); - // - // FEditorFileUtils::PromptForCheckoutAndSave(CurrentWorldToSave, true, false); - // - // WorldCompositionPath = FPackageName::GetLongPackagePath(CurrentWorldPackage->GetName()); - // } - // } - // } - - // // Before processing any of the output, - // // we need to get the min/max value for all Height volumes in this output (if any) - TMap LandscapeLayerGlobalMinimums; - TMap LandscapeLayerGlobalMaximums; - - for (UHoudiniOutput* CurOutput : InOutputs) - { - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (OutputType == EHoudiniOutputType::Landscape) - { - // Populate all layer minimums/maximums with default values since, in PDG mode, - // we can't collect the values across all tiles. The user would have to do this - // manually in the Topnet. - FHoudiniLandscapeTranslator::GetLayersZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums); - } - } - - TArray InstancerOutputs; - TArray LandscapeOutputs; - TArray CreatedPackages; - - //bool bCreatedNewMaps = false; - UWorld* PersistentWorld = InOuterComponent->GetTypedOuter(); - check(PersistentWorld); - - for (UHoudiniOutput* CurOutput : InOutputs) - { - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (InOutputTypesToProcess.Num() > 0 && !InOutputTypesToProcess.Contains(OutputType)) - { - continue; - } - switch (OutputType) - { - case EHoudiniOutputType::Mesh: - { - const bool bInDestroyProxies = false; - if (bInOnlyUseExistingAssets) - { - const bool bInApplyGenericProperties = false; - TMap NewOutputObjects(CurOutput->GetOutputObjects()); - FHoudiniMeshTranslator::CreateOrUpdateAllComponents( - CurOutput, - InOuterComponent, - NewOutputObjects, - bInDestroyProxies, - bInApplyGenericProperties - ); - } - else - { - FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - CurOutput, - InPackageParams, - EHoudiniStaticMeshMethod::RawMesh, - InOuterComponent, - bInTreatExistingMaterialsAsUpToDate, - bInDestroyProxies - ); - } - } - break; - - case EHoudiniOutputType::Curve: - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, InOuterComponent); - break; - } - - case EHoudiniOutputType::Instancer: - { - InstancerOutputs.Add(CurOutput); - } - break; - - case EHoudiniOutputType::Landscape: - { - TArray EmptyInputLandscapes; - // Retrieve the topnet parent to which Sharedlandscapes will be attached. - AActor* WorkItemActor = InOuterComponent->GetTypedOuter(); - USceneComponent* TopnetParent = nullptr; - if (WorkItemActor) - { - AActor* TopnetParentActor = WorkItemActor->GetAttachParentActor(); - if (TopnetParentActor) - { - TopnetParent = TopnetParentActor->GetRootComponent(); - } - } - TArray> CreatedUntrackedOutputs; - - FHoudiniLandscapeTranslator::CreateLandscape( - CurOutput, - CreatedUntrackedOutputs, - EmptyInputLandscapes, - EmptyInputLandscapes, - TopnetParent, - TEXT("{hda_actor_name}_{pdg_topnet_name}_"), - PersistentWorld, - LandscapeLayerGlobalMinimums, - LandscapeLayerGlobalMaximums, - InPackageParams, - //bCreatedNewMaps, - CreatedPackages); - // Attach any landscape actors to InOuterComponent - LandscapeOutputs.Add(CurOutput); - } - break; - - default: - { - HOUDINI_LOG_WARNING(TEXT("[FTOPWorkResultObject::UpdateResultOutputs]: Unsupported output type: %s"), *UHoudiniOutput::OutputTypeToString(OutputType)); - } - break; - } - } - - // Process instancer outputs after all other outputs have been processed, since it - // might depend on meshes etc from other outputs - if (InstancerOutputs.Num() > 0) - { - for (UHoudiniOutput* CurOutput : InstancerOutputs) - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( - CurOutput, - InOutputs, - InOuterComponent, - InPreBuiltInstancedOutputPartData - ); - } - } - - - USceneComponent* ParentComponent = Cast(InOuterComponent); - - if (ParentComponent) - { - AActor* ParentActor = ParentComponent->GetOwner(); - for (UHoudiniOutput* LandscapeOutput : LandscapeOutputs) - { - for (auto& Pair : LandscapeOutput->GetOutputObjects()) - { - FHoudiniOutputObject &OutputObject = Pair.Value; - - // If the OutputObject has an OutputComponent, try to attach it to ParentComponent - if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) - { - USceneComponent* Component = Cast(OutputObject.OutputComponent); - if (IsValid(Component) && !Component->IsAttachedTo(ParentComponent)) - { - Component->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepWorldTransform); - continue; - } - } - - // If OutputObject does not have an OutputComponent, check if OutputObject is an AActor and attach - // it to ParentComponent - if (IsValid(OutputObject.OutputObject)) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); - if (IsValid(LandscapePtr)) - { - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); - if (IsValid(LandscapeProxy)) - { - if (!LandscapeProxy->IsAttachedTo(ParentActor)) - { - LandscapeProxy->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform); - LandscapeProxy->RecreateCollisionComponents(); - } - - if (LandscapeProxy) - { - // We need to recreate component states for landscapes if a tile was created, moved, or resized - // otherwise the landscape will exhibit render artifacts (such as only rendering every other - // component.) - LandscapeProxy->RecreateComponentsState(); - } - } - - } - } - } - } - } - - /* - if (bCreatedNewMaps && PersistentWorld) - { - // Force the asset registry to update its cache of packages paths - // recursively for this world, otherwise world composition won't - // pick them up during the WorldComposition::Rescan(). - FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); - - ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); - - if (IsValid(PersistentWorld->WorldComposition)) - { - UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); - } - - FEditorDelegates::RefreshLevelBrowser.Broadcast(); - FEditorDelegates::RefreshAllBrowsers.Broadcast(); - } - */ - - if (CreatedPackages.Num() > 0) - { - // Save created packages. For example, we don't want landscape layers deleted - // along with the HDA. - FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); - } - - return true; -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGTranslator.h" + + +#include "Editor.h" +#include "Containers/Array.h" +#include "FileHelpers.h" +#include "LandscapeInfo.h" +// #include "Engine/WorldComposition.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniGeoImporter.h" +#include "HoudiniPackageParams.h" +#include "HoudiniOutput.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniTranslatorTypes.h" + +#define LOCTEXT_NAMESPACE "HoudiniEngine" + +bool +FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray InOutputTypesToProcess, + bool bInTreatExistingMaterialsAsUpToDate) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem); + + if (!IsValid(InAssetLink)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InAssetLink is null.")); + return false; + } + + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InTOPNode is null.")); + return false; + } + + TArray OldTOPOutputs = InWorkResultObject.GetResultOutputs(); + TArray NewTOPOutputs; + + FHoudiniEngine::Get().CreateTaskSlateNotification(LOCTEXT("LoadPDGBGEO", "Loading PDG Output BGEO File...")); + + bool bResult = false; + // Create a new file node in HAPI for the bgeo and cook it + HAPI_NodeId FileNodeId = -1; + bResult = UHoudiniGeoImporter::OpenBGEOFile(InWorkResultObject.FilePath, FileNodeId); + if (bResult) + bResult = UHoudiniGeoImporter::CookFileNode(FileNodeId); + + // If the cook was successful, build outputs + if (bResult) + { + FHoudiniEngine::Get().UpdateTaskSlateNotification( + LOCTEXT("BuildPDGBGEOOutputs", "Building Ouputs from BGEO File...")); + + const bool bAddOutputsToRootSet = false; + bResult = UHoudiniGeoImporter::BuildAllOutputsForNode( + FileNodeId, + InAssetLink, + OldTOPOutputs, + NewTOPOutputs, + bAddOutputsToRootSet); + } + + if (bResult) + { + FHoudiniEngine::Get().UpdateTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); + + // If we successfully received outputs from the BGEO file, process the outputs + FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); + AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + if (!IsValid(WorkItemOutputActor)) + { + UWorld* World = InAssetLink->GetWorld(); + FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); + AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + if (!IsValid(TOPNodeOutputActor)) + { + if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) + TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + } + if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) + WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + } + + for (auto& OldOutput : OldTOPOutputs) + { + FHoudiniOutputTranslator::ClearOutput(OldOutput); + } + OldTOPOutputs.Empty(); + InWorkResultObject.GetResultOutputs().Empty(); + InWorkResultObject.SetResultOutputs(NewTOPOutputs); + + // Gather landscape actors from inputs. + // NOTE: If performance becomes a problem, cache these on the TOPNode along with all the other cached landscape + // data. + TArray AllInputLandscapes; + TArray InputLandscapesToUpdate; + UHoudiniAssetComponent* HAC = InAssetLink->GetOuterHoudiniAssetComponent(); + FHoudiniEngineUtils::GatherLandscapeInputs(HAC, AllInputLandscapes, InputLandscapesToUpdate); + + bResult = CreateAllResultObjectsFromPDGOutputs( + NewTOPOutputs, + InPackageParams, + WorkItemOutputActor->GetRootComponent(), + InTOPNode->GetLandscapeExtent(), + InTOPNode->GetLandscapeReferenceLocation(), + InTOPNode->GetLandscapeSizeInfo(), + InTOPNode->ClearedLandscapeLayers, + AllInputLandscapes, + InputLandscapesToUpdate, + InOutputTypesToProcess, + bInTreatExistingMaterialsAsUpToDate); + + if (!bResult) + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); + else + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); + + InTOPNode->UpdateOutputVisibilityInLevel(); + } + else + { + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("BuildPDGBGEOOutputsFail", "Failed building outputs from BGEO file...")); + } + + // Delete the file node used to load the BGEO via HAPI + if (FileNodeId >= 0) + { + UHoudiniGeoImporter::CloseBGEOFile(FileNodeId); + FileNodeId = -1; + } + + return bResult; +} + +bool +FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray& InOutputs, + TArray InOutputTypesToProcess, + const TMap* InPreBuiltInstancedOutputPartData) +{ + if (!IsValid(InAssetLink)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InAssetLink is null.")); + return false; + } + + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InTOPNode is null.")); + return false; + } + + FHoudiniEngine::Get().CreateTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); + + // If we successfully received outputs from the BGEO file, process the outputs + FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); + AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + if (!IsValid(WorkItemOutputActor)) + { + UWorld* World = InAssetLink->GetWorld(); + FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); + AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + if (!IsValid(TOPNodeOutputActor)) + { + if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) + TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + } + if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) + WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + } + + InWorkResultObject.SetResultOutputs(InOutputs); + + // Gather landscape actors from inputs. + // NOTE: If performance becomes a problem, cache these on the TOPNode along with all the other cached landscape + // data. + TArray AllInputLandscapes; + TArray InputLandscapesToUpdate; + UHoudiniAssetComponent* HAC = InAssetLink->GetOuterHoudiniAssetComponent(); + FHoudiniEngineUtils::GatherLandscapeInputs(HAC, AllInputLandscapes, InputLandscapesToUpdate); + + const bool bInTreatExistingMaterialsAsUpToDate = true; + const bool bOnlyUseExistingAssets = true; + const bool bResult = CreateAllResultObjectsFromPDGOutputs( + InOutputs, + InPackageParams, + WorkItemOutputActor->GetRootComponent(), + InTOPNode->GetLandscapeExtent(), + InTOPNode->GetLandscapeReferenceLocation(), + InTOPNode->GetLandscapeSizeInfo(), + InTOPNode->ClearedLandscapeLayers, + AllInputLandscapes, + InputLandscapesToUpdate, + InOutputTypesToProcess, + bInTreatExistingMaterialsAsUpToDate, + bOnlyUseExistingAssets, + InPreBuiltInstancedOutputPartData); + + if (!bResult) + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); + else + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); + + InTOPNode->UpdateOutputVisibilityInLevel(); + + return bResult; +} + +bool +FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( + TArray& InOutputs, + const FHoudiniPackageParams& InPackageParams, + UObject* InOuterComponent, + FHoudiniLandscapeExtent& CachedLandscapeExtent, + FHoudiniLandscapeReferenceLocation& CachedLandscapeRefLoc, + FHoudiniLandscapeTileSizeInfo& CachedLandscapeSizeInfo, + TSet& ClearedLandscapeLayers, + TArray AllInputLandscapes, + TArray InputLandscapesToUpdate, + TArray InOutputTypesToProcess, + bool bInTreatExistingMaterialsAsUpToDate, + bool bInOnlyUseExistingAssets, + const TMap* InPreBuiltInstancedOutputPartData + ) +{ + // Process the new/updated outputs via the various translators + // We try to maintain as much parity with the existing HoudiniAssetComponent workflow + // as possible. + + + // // Before processing any of the output, + // // we need to get the min/max value for all Height volumes in this output (if any) + TMap LandscapeLayerGlobalMinimums; + TMap LandscapeLayerGlobalMaximums; + + for (UHoudiniOutput* CurOutput : InOutputs) + { + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (OutputType == EHoudiniOutputType::Landscape) + { + // Populate all layer minimums/maximums with default values since, in PDG mode, + // we can't collect the values across all tiles. The user would have to do this + // manually in the Topnet. + FHoudiniLandscapeTranslator::GetLayersZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums); + } + } + + TArray InstancerOutputs; + TArray LandscapeOutputs; + TArray CreatedPackages; + + //bool bCreatedNewMaps = false; + UWorld* PersistentWorld = InOuterComponent->GetTypedOuter(); + check(PersistentWorld); + + // Keep track of all generated houdini materials to avoid recreating them over and over + TMap AllOutputMaterials; + + for (UHoudiniOutput* CurOutput : InOutputs) + { + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (InOutputTypesToProcess.Num() > 0 && !InOutputTypesToProcess.Contains(OutputType)) + { + continue; + } + switch (OutputType) + { + case EHoudiniOutputType::Mesh: + { + const bool bInDestroyProxies = false; + if (bInOnlyUseExistingAssets) + { + const bool bInApplyGenericProperties = false; + TMap NewOutputObjects(CurOutput->GetOutputObjects()); + FHoudiniMeshTranslator::CreateOrUpdateAllComponents( + CurOutput, + InOuterComponent, + NewOutputObjects, + bInDestroyProxies, + bInApplyGenericProperties + ); + } + else + { + // TODO: If Outer is an HAC, get SMGP/MBS from it ?? + FHoudiniStaticMeshGenerationProperties SMGP = FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties(); + FMeshBuildSettings MBS = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); + FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + CurOutput, + InPackageParams, + EHoudiniStaticMeshMethod::RawMesh, + SMGP, + MBS, + AllOutputMaterials, + InOuterComponent, + bInTreatExistingMaterialsAsUpToDate, + bInDestroyProxies + ); + } + } + break; + + case EHoudiniOutputType::Curve: + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, InOuterComponent); + break; + } + + case EHoudiniOutputType::Instancer: + { + InstancerOutputs.Add(CurOutput); + } + break; + + case EHoudiniOutputType::Landscape: + { + // Retrieve the topnet parent to which Sharedlandscapes will be attached. + AActor* WorkItemActor = InOuterComponent->GetTypedOuter(); + USceneComponent* TopnetParent = nullptr; + if (WorkItemActor) + { + AActor* TopnetParentActor = WorkItemActor->GetAttachParentActor(); + if (TopnetParentActor) + { + TopnetParent = TopnetParentActor->GetRootComponent(); + } + } + TArray> CreatedUntrackedOutputs; + + FHoudiniLandscapeTranslator::CreateLandscape( + CurOutput, + CreatedUntrackedOutputs, + InputLandscapesToUpdate, + AllInputLandscapes, + TopnetParent, + TEXT("{hda_actor_name}_{pdg_topnet_name}_"), + PersistentWorld, + LandscapeLayerGlobalMinimums, + LandscapeLayerGlobalMaximums, + CachedLandscapeExtent, + CachedLandscapeSizeInfo, + CachedLandscapeRefLoc, + InPackageParams, + //bCreatedNewMaps, + ClearedLandscapeLayers, + CreatedPackages); + // Attach any landscape actors to InOuterComponent + LandscapeOutputs.Add(CurOutput); + } + break; + + default: + { + HOUDINI_LOG_WARNING(TEXT("[FTOPWorkResultObject::UpdateResultOutputs]: Unsupported output type: %s"), *UHoudiniOutput::OutputTypeToString(OutputType)); + } + break; + } + + for (auto& CurMat : CurOutput->GetAssignementMaterials()) + { + //Adds the generated materials if any + if (!AllOutputMaterials.Contains(CurMat.Key)) + AllOutputMaterials.Add(CurMat); + } + } + + // Process instancer outputs after all other outputs have been processed, since it + // might depend on meshes etc from other outputs + if (InstancerOutputs.Num() > 0) + { + for (UHoudiniOutput* CurOutput : InstancerOutputs) + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( + CurOutput, + InOutputs, + InOuterComponent, + InPackageParams, + InPreBuiltInstancedOutputPartData); + } + } + + USceneComponent* ParentComponent = Cast(InOuterComponent); + if (IsValid(ParentComponent)) + { + AActor* ParentActor = ParentComponent->GetOwner(); + for (UHoudiniOutput* LandscapeOutput : LandscapeOutputs) + { + if(!IsValid(LandscapeOutput)) + continue; + + for (auto& Pair : LandscapeOutput->GetOutputObjects()) + { + FHoudiniOutputObject& OutputObject = Pair.Value; + + // If the OutputObject has an OutputComponent, try to attach it to ParentComponent + if (IsValid(OutputObject.OutputComponent)) + { + USceneComponent* Component = Cast(OutputObject.OutputComponent); + if (IsValid(Component) && !Component->IsAttachedTo(ParentComponent)) + { + Component->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepWorldTransform); + continue; + } + } + + // If OutputObject does not have an OutputComponent, check if OutputObject is an AActor and attach + // it to ParentComponent + if (IsValid(OutputObject.OutputObject)) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); + if (IsValid(LandscapePtr)) + { + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + if (IsValid(LandscapeProxy)) + { + if (!LandscapeProxy->IsAttachedTo(ParentActor)) + { + LandscapeProxy->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform); + LandscapeProxy->RecreateCollisionComponents(); + } + + if (LandscapeProxy) + { + // We need to recreate component states for landscapes if a tile was created, moved, or resized + // otherwise the landscape will exhibit render artifacts (such as only rendering every other + // component.) + LandscapeProxy->RecreateComponentsState(); + } + } + } + } + } + } + } + + /* + if (bCreatedNewMaps && PersistentWorld) + { + // Force the asset registry to update its cache of packages paths + // recursively for this world, otherwise world composition won't + // pick them up during the WorldComposition::Rescan(). + FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); + + ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); + + if (IsValid(PersistentWorld->WorldComposition)) + { + UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); + } + + FEditorDelegates::RefreshLevelBrowser.Broadcast(); + FEditorDelegates::RefreshAllBrowsers.Broadcast(); + } + */ + + if (CreatedPackages.Num() > 0) + { + // Save created packages. For example, we don't want landscape layers deleted + // along with the HDA. + FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); + } + + return true; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h index dd52428ed..8d68e31d9 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,6 +32,7 @@ class UHoudiniPDGAssetLink; class UHoudiniOutput; class AActor; class UTOPNode; +class ALandscapeProxy; enum class EHoudiniOutputType : uint8; @@ -39,6 +40,9 @@ struct FHoudiniPackageParams; struct FTOPWorkResultObject; struct FHoudiniOutputObjectIdentifier; struct FHoudiniInstancedOutputPartData; +struct FHoudiniLandscapeExtent; +struct FHoudiniLandscapeReferenceLocation; +struct FHoudiniLandscapeTileSizeInfo; struct HOUDINIENGINE_API FHoudiniPDGTranslator { @@ -68,8 +72,16 @@ struct HOUDINIENGINE_API FHoudiniPDGTranslator TArray& InOutputs, const FHoudiniPackageParams& InPackageParams, UObject* InOuterComponent, + FHoudiniLandscapeExtent& CachedLandscapeExtent, + FHoudiniLandscapeReferenceLocation& CachedLandscapeRefLoc, + FHoudiniLandscapeTileSizeInfo& CachedLandscapeSizeInfo, + TSet& ClearedLandscapeLayers, + TArray AllInputLandscapes, + TArray InputLandscapesToUpdate, TArray InOutputTypesToProcess={}, bool bInTreatExistingMaterialsAsUpToDate=false, bool bInOnlyUseExistingAssets=false, - const TMap* InPreBuiltInstancedOutputPartData=nullptr); + const TMap* InPreBuiltInstancedOutputPartData=nullptr + ); }; + diff --git a/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp b/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp index ccb218754..a0bd6a923 100644 --- a/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -61,8 +61,7 @@ FHoudiniPackageParams::FHoudiniPackageParams() PDGTOPNetworkName.Empty(); PDGTOPNodeName.Empty(); PDGWorkItemIndex = INDEX_NONE; - - bAttemptToLoadMissingPackages = false; + PDGWorkResultArrayIndex = INDEX_NONE; } @@ -93,11 +92,11 @@ FHoudiniPackageParams::GetPackageName() const return ObjectName; // If we have PDG infos, generate a name including them - if (!PDGTOPNetworkName.IsEmpty() && !PDGTOPNodeName.IsEmpty() && PDGWorkItemIndex >= 0) + if (!PDGTOPNetworkName.IsEmpty() && !PDGTOPNodeName.IsEmpty() && PDGWorkResultArrayIndex >= 0) { return FString::Printf( TEXT("%s_%s_%s_%d_%d_%s"), - *HoudiniAssetName, *PDGTOPNetworkName, *PDGTOPNodeName, PDGWorkItemIndex, PartId, *SplitStr); + *HoudiniAssetName, *PDGTOPNetworkName, *PDGTOPNodeName, PDGWorkResultArrayIndex, PartId, *SplitStr); } else { @@ -166,7 +165,7 @@ FHoudiniPackageParams::GetBakeCounterFromBakedAsset(const UObject* InAsset, int3 if (!IsValid(InAsset)) return false; - UPackage* Package = InAsset->GetOutermost();// GetPackage(); + UPackage* Package = InAsset->GetPackage(); // const FString PackagePathName = Package->GetPathName(); // FString PackagePathNamePrefix; // FString BakeCountOrGUID; @@ -210,7 +209,7 @@ FHoudiniPackageParams::GetGUIDFromTempAsset(const UObject* InAsset, FString& Out if (!InAsset) return false; - UPackage* Package = InAsset->GetOutermost();//GetPackage(); + UPackage* Package = InAsset->GetPackage(); if (!IsValid(Package)) return false; @@ -235,12 +234,11 @@ FHoudiniPackageParams::GetPackageNameExcludingBakeCounter(const UObject* InAsset if (!IsValid(InAsset)) return FString(); - UPackage* Package = InAsset->GetOutermost(); + UPackage* Package = InAsset->GetPackage(); if (!IsValid(Package)) return FString(); FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); - int32 BakeCounter = 0; if (GetBakeCounterFromBakedAsset(InAsset, BakeCounter)) { @@ -258,7 +256,7 @@ FHoudiniPackageParams::MatchesPackagePathNameExcludingBakeCounter(const UObject* if (!IsValid(InAsset)) return false; - UPackage* Package = InAsset->GetOutermost();//GetPackage(); + UPackage* Package = InAsset->GetPackage(); if (!IsValid(Package)) return false; @@ -273,12 +271,11 @@ FHoudiniPackageParams::GetPackageNameExcludingGUID(const UObject* InAsset) if (!IsValid(InAsset)) return FString(); - UPackage* Package = InAsset->GetOutermost(); + UPackage* Package = InAsset->GetPackage(); if (!IsValid(Package)) return FString(); FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); - FString GUIDStr; if (GetGUIDFromTempAsset(InAsset, GUIDStr)) { @@ -319,31 +316,27 @@ FHoudiniPackageParams::CreatePackageForObject(FString& OutPackageName, int32 InB // Sanitize package name. FinalPackageName = UPackageTools::SanitizePackageName(FinalPackageName); - UObject * PackageOuter = nullptr; - if (PackageMode == EPackageMode::CookToLevel) - { - // If we are not baking, then use outermost package, since objects within our package - // need to be visible to external operations, such as copy paste. - PackageOuter = OuterPackage; - } - - // See if a package named similarly already exists - UPackage* FoundPackage = FindPackage(PackageOuter, *FinalPackageName); - if (FoundPackage == nullptr && bAttemptToLoadMissingPackages) + // If we are set to create new assets, check if a package named similarly already exists + if (ReplaceMode == EPackageReplaceMode::CreateNewAssets) { - FoundPackage = LoadPackage(Cast(PackageOuter), *FinalPackageName, LOAD_NoWarn); - } - if (ReplaceMode == EPackageReplaceMode::CreateNewAssets - && FoundPackage && !FoundPackage->IsPendingKill()) - { - // we need to generate a new name for it - CurrentGuid = FGuid::NewGuid(); - BakeCounter++; - continue; + UPackage* FoundPackage = FindPackage(nullptr, *FinalPackageName); + if (FoundPackage == nullptr) + { + // Package might not be in memory, check if it exists on disk + FoundPackage = LoadPackage(nullptr, *FinalPackageName, LOAD_Verify | LOAD_NoWarn); + } + + if (IsValid(FoundPackage)) + { + // we need to generate a new name for it + CurrentGuid = FGuid::NewGuid(); + BakeCounter++; + continue; + } } // Create actual package. - NewPackage = CreatePackage(PackageOuter, *FinalPackageName); + NewPackage = CreatePackage(*FinalPackageName); if (IsValid(NewPackage)) { // Record bake counter / temp GUID in package metadata @@ -362,6 +355,16 @@ FHoudiniPackageParams::CreatePackageForObject(FString& OutPackageName, int32 InB // HOUDINI_LOG_MESSAGE(TEXT("Recording temp guid in package metadata: %s"), *GuidStr); MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_TEMP_GUID, GuidStr); } + + // Store the Component GUID that generated the package, as well as the package name and meta key + // indicating that the package was generated by Houdini + { + const FString ComponentGuidStr = ComponentGUID.ToString(); + MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_COMPONENT_GUID, ComponentGuidStr); + + MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *OutPackageName); + } } } @@ -388,7 +391,7 @@ T* FHoudiniPackageParams::CreateObjectAndPackage() // Create the package for the object FString NewObjectName; UPackage* Package = CreatePackageForObject(NewObjectName); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return nullptr; const FString SanitizedObjectName = ObjectTools::SanitizeObjectName(NewObjectName); @@ -396,7 +399,7 @@ T* FHoudiniPackageParams::CreateObjectAndPackage() T* ExistingTypedObject = FindObject(Package, *NewObjectName); UObject* ExistingObject = FindObject(Package, *NewObjectName); - if (ExistingTypedObject != nullptr && !ExistingTypedObject->IsPendingKill()) + if (IsValid(ExistingTypedObject)) { // An object of the appropriate type already exists, update it! ExistingTypedObject->PreEditChange(nullptr); @@ -412,7 +415,7 @@ T* FHoudiniPackageParams::CreateObjectAndPackage() // Create a package for each mesh Package = CreatePackageForObject(NewObjectName); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return nullptr; } else @@ -422,12 +425,21 @@ T* FHoudiniPackageParams::CreateObjectAndPackage() } } - // Add meta information to this package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *NewObjectName); + T* const CreatedObject = NewObject(Package, FName(*SanitizedObjectName), GetObjectFlags()); + + // Add meta information to the new object in the package. + if (IsValid(CreatedObject)) + { + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, CreatedObject, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, CreatedObject, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *NewObjectName); + + const FString ComponentGuidStr = ComponentGUID.ToString(); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, CreatedObject, HAPI_UNREAL_PACKAGE_META_COMPONENT_GUID, ComponentGuidStr); + } - return NewObject(Package, FName(*SanitizedObjectName), GetObjectFlags()); + return CreatedObject; } diff --git a/Source/HoudiniEngine/Private/HoudiniPackageParams.h b/Source/HoudiniEngine/Private/HoudiniPackageParams.h index 8580b3376..379d9b0d7 100644 --- a/Source/HoudiniEngine/Private/HoudiniPackageParams.h +++ b/Source/HoudiniEngine/Private/HoudiniPackageParams.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,6 +26,7 @@ #pragma once +#include "HoudiniAssetComponent.h" #include "UObject/ObjectMacros.h" #include "Engine/World.h" #include "Misc/Paths.h" @@ -158,12 +159,9 @@ struct HOUDINIENGINE_API FHoudiniPackageParams // For PDG temporary outputs: the work item index of the TOP node UPROPERTY() int32 PDGWorkItemIndex; - - // If FindPackage returns null, if this flag is true then a LoadPackage will also be attempted - // This is for use cases, such as commandlets, that might unload packages once done with them, but that must - // reliably be able to determine if a package exists later + // For PDG temporary outputs: the work item array index in the WorkResult array of the TOP node UPROPERTY() - bool bAttemptToLoadMissingPackages; + int32 PDGWorkResultArrayIndex; ////TODO: We don't have access to Houdini attributes in HoudiniEngine/HoudiniEnginePrivatePCH. //FString GetTempFolderArgument(ERuntimePackageMode PackageMode) const; @@ -191,11 +189,17 @@ struct HOUDINIENGINE_API FHoudiniPackageParams template void UpdateTokensFromParams( const UWorld* WorldContext, + const UHoudiniAssetComponent* HAC, TMap& OutTokens) const { UpdateOutputPathTokens(PackageMode, OutTokens); - OutTokens.Add("world", ValueT( FPaths::GetPath(WorldContext->GetPathName()) )); + if(IsValid(WorldContext)) + OutTokens.Add("world", ValueT( FPaths::GetPath(WorldContext->GetPathName()) )); + + if(HAC && HAC->GetOutermost()) + OutTokens.Add("hda_level", ValueT( HAC->GetOutermost()->GetPathName() )); + OutTokens.Add("object_name", ValueT( ObjectName )); OutTokens.Add("object_id", ValueT( FString::FromInt(ObjectId) )); OutTokens.Add("geo_id", ValueT( FString::FromInt(GeoId) )); @@ -206,6 +210,7 @@ struct HOUDINIENGINE_API FHoudiniPackageParams OutTokens.Add("pdg_topnet_name", ValueT( PDGTOPNetworkName )); OutTokens.Add("pdg_topnode_name", ValueT( PDGTOPNodeName )); OutTokens.Add("pdg_workitem_index", ValueT( FString::FromInt(PDGWorkItemIndex) )); + OutTokens.Add("pdg_workresult_array_index", ValueT( FString::FromInt(PDGWorkResultArrayIndex) )); OutTokens.Add("guid", ValueT( ComponentGUID.ToString() )); } @@ -214,8 +219,8 @@ struct HOUDINIENGINE_API FHoudiniPackageParams { const FString PackagePath = GetPackagePath(); - OutTokens.Add("temp", ValueT(FPaths::GetPath(TempCookFolder))); - OutTokens.Add("bake", ValueT(FPaths::GetPath(BakeFolder))); + OutTokens.Add("temp", ValueT(TempCookFolder)); + OutTokens.Add("bake", ValueT(BakeFolder)); // `out_basepath` is useful if users want to organize their cook/bake assets // different to the convention defined by GetPackagePath(). This would typically @@ -224,14 +229,14 @@ struct HOUDINIENGINE_API FHoudiniPackageParams { case EPackageMode::CookToTemp: case EPackageMode::CookToLevel: - OutTokens.Add("out_basepath", ValueT(FPaths::GetPath(TempCookFolder))); + OutTokens.Add("out_basepath", ValueT(TempCookFolder)); break; case EPackageMode::Bake: - OutTokens.Add("out_basepath", ValueT(FPaths::GetPath(BakeFolder))); + OutTokens.Add("out_basepath", ValueT(BakeFolder)); break; } - OutTokens.Add("out", ValueT( FPaths::GetPath(PackagePath) )); + OutTokens.Add("out", ValueT(PackagePath)); } }; diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp index d01233694..b6df76444 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,6 +29,7 @@ #include "HoudiniApi.h" #include "HoudiniEnginePrivatePCH.h" +#include "HoudiniAsset.h" #include "HoudiniParameter.h" #include "HoudiniParameterButton.h" #include "HoudiniParameterButtonStrip.h" @@ -57,12 +58,6 @@ #include "HoudiniAssetComponent.h" -// Used parameter tags -#define HAPI_PARAM_TAG_NOSWAP "hengine_noswap" -#define HAPI_PARAM_TAG_FILE_READONLY "filechooser_mode" -#define HAPI_PARAM_TAG_UNITS "units" -#define HAPI_PARAM_TAG_ASSET_REF "asset_ref" - // Default values for certain UI min and max parameter values #define HAPI_UNREAL_PARAM_INT_UI_MIN 0 #define HAPI_UNREAL_PARAM_INT_UI_MAX 10 @@ -80,14 +75,14 @@ bool FHoudiniParameterTranslator::UpdateParameters(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // When recooking/rebuilding the HDA, force a full update of all params - bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); + const bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested() || HAC->IsParameterDefinitionUpdateNeeded(); TArray NewParameters; - if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, true, bForceFullUpdate)) + if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, true, bForceFullUpdate, HAC->GetHoudiniAsset(), HAC->GetHapiAssetName())) { /* // DO NOT MANUALLY DESTROY THE OLD/DANGLING PARAMETERS! @@ -96,7 +91,7 @@ FHoudiniParameterTranslator::UpdateParameters(UHoudiniAssetComponent* HAC) // Destroy old/dangling parameters for (auto& OldParm : HAC->Parameters) { - if (!OldParm || OldParm->IsPendingKill()) + if (!IsValid(OldParm)) continue; OldParm->ConditionalBeginDestroy(); @@ -106,6 +101,9 @@ FHoudiniParameterTranslator::UpdateParameters(UHoudiniAssetComponent* HAC) // Replace with the new parameters HAC->Parameters = NewParameters; + + // Update the details panel after the parameter changes/updates + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); } @@ -120,7 +118,7 @@ FHoudiniParameterTranslator::OnPreCookParameters(UHoudiniAssetComponent* HAC) // synced before the cook starts (Looking at you, ramp parameters!) for (UHoudiniParameter* Param : HAC->Parameters) { - if (!Param || Param->IsPendingKill()) + if (!IsValid(Param)) continue; Param->OnPreCook(); @@ -133,21 +131,23 @@ FHoudiniParameterTranslator::OnPreCookParameters(UHoudiniAssetComponent* HAC) bool FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // Update all the parameters using the loaded parameter object - // We set "UpdateValues" to false because we do not want to "read" the parameter value from Houdini - // but keep the loaded value + // We set "UpdateValues" to false because we do not want to "read" the parameter value + // from Houdini but keep the loaded value + + // Share AssetInfo if needed + bool bNeedToFetchAssetInfo = true; + HAPI_AssetInfo AssetInfo; - // This is the first cook on loading after a save or duplication, - // We need to sync the Ramp parameters first, so that their child parameters can be kept - // TODO: Simplify this, should be handled in BuildAllParameters, + // This is the first cook on loading after a save or duplication for (int32 Idx = 0; Idx < HAC->Parameters.Num(); ++Idx) { UHoudiniParameter* Param = HAC->Parameters[Idx]; - if (!Param || Param->IsPendingKill()) + if (!IsValid(Param)) continue; switch(Param->GetParameterType()) @@ -156,7 +156,23 @@ FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) case EHoudiniParameterType::FloatRamp: case EHoudiniParameterType::MultiParm: { - SyncMultiParmValuesAtLoad(Param, HAC->Parameters, HAC->AssetId, Idx); + // We need to sync the Ramp parameters first, so that their child parameters can be kept + if (bNeedToFetchAssetInfo) + { + FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &AssetInfo); + bNeedToFetchAssetInfo = false; + } + + // TODO: Simplify this, should be handled in BuildAllParameters + SyncMultiParmValuesAtLoad(Param, HAC->Parameters, HAC->AssetId, AssetInfo); + } + break; + + case EHoudiniParameterType::Button: + case EHoudiniParameterType::ButtonStrip: + { + // Do not trigger buttons upon loading + Param->MarkChanged(false); } break; @@ -166,12 +182,15 @@ FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) } // When recooking/rebuilding the HDA, force a full update of all params - bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); + const bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested() || HAC->IsParameterDefinitionUpdateNeeded(); // This call to BuildAllParameters will keep all the loaded parameters (in the HAC's Parameters array) // that are still present in the HDA, and keep their loaded value. TArray NewParameters; - if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, false, bForceFullUpdate)) + // We don't need to fetch defaults from the asset definition for a loaded HAC + const UHoudiniAsset* const HoudiniAsset = nullptr; + const FString HoudiniAssetName = FString(); + if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, false, bForceFullUpdate, HoudiniAsset, HoudiniAssetName)) { /* // DO NOT DESTROY OLD PARAMS MANUALLY HERE @@ -180,7 +199,7 @@ FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) // Destroy old/dangling parameters for (auto& OldParm : HAC->Parameters) { - if (!OldParm || OldParm->IsPendingKill()) + if (!IsValid(OldParm)) continue; OldParm->ConditionalBeginDestroy(); @@ -190,6 +209,9 @@ FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) // Simply replace with the new parameters HAC->Parameters = NewParameters; + + // Update the details panel after the parameter changes/updates + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); } return true; @@ -202,59 +224,203 @@ FHoudiniParameterTranslator::BuildAllParameters( TArray& CurrentParameters, TArray& NewParameters, const bool& bUpdateValues, - const bool& InForceFullUpdate) + const bool& InForceFullUpdate, + const UHoudiniAsset* InHoudiniAsset, + const FString& InHoudiniAssetName) { + const bool bIsAssetValid = IsValid(InHoudiniAsset); + // Ensure the asset has a valid node ID - if (AssetId < 0) + if (AssetId < 0 && !bIsAssetValid) { return false; } - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + int32 ParmCount = 0; + + // Default value counts and arrays for if we need to fetch those from Houdini. + int DefaultIntValueCount = 0; + int DefaultFloatValueCount = 0; + int DefaultStringValueCount = 0; + int DefaultChoiceValueCount = 0; + TArray DefaultIntValues; + TArray DefaultFloatValues; + TArray DefaultStringValues; + TArray DefaultChoiceValues; + + HAPI_NodeId NodeId = -1; + HAPI_AssetLibraryId AssetLibraryId = -1; + FString HoudiniAssetName; + + if (AssetId >= 0) + { + // Get the asset's info + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HAPI_Result Result = FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo); + + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Hapi failed: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } - // .. the asset's node info - HAPI_NodeInfo NodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); + NodeId = AssetInfo.nodeId; + + // .. the asset's node info + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); + + ParmCount = NodeInfo.parmCount; + } + else + { + if (!FHoudiniEngineUtils::LoadHoudiniAsset(InHoudiniAsset, AssetLibraryId) ) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParameters - could not load Houdini Asset.")); + return false; + } + + // Handle hda files that contain multiple assets + TArray AssetNames; + if (!FHoudiniEngineUtils::GetSubAssetNames(AssetLibraryId, AssetNames)) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParameters - unable to retrieve asset names.")); + return false; + } + + if (AssetNames.Num() == 0) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParameters - unable to retrieve asset names.")); + return false; + } + + // If no InHoudiniAssetName was specified, pick the first asset from the library + if (InHoudiniAssetName.IsEmpty()) + { + const FHoudiniEngineString HoudiniEngineString(AssetNames[0]); + HoudiniEngineString.ToFString(HoudiniAssetName); + } + else + { + // Ensure that the specified asset name is in the library + for (const HAPI_StringHandle& Handle : AssetNames) + { + const FHoudiniEngineString HoudiniEngineString(Handle); + FString AssetNameStr; + HoudiniEngineString.ToFString(AssetNameStr); + if (AssetNameStr == InHoudiniAssetName) + { + HoudiniAssetName = AssetNameStr; + break; + } + } + } + + if (HoudiniAssetName.IsEmpty()) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParametersFromAssetDefinition - could not find asset in library.")); + return false; + } + + HAPI_Result Result = FHoudiniApi::GetAssetDefinitionParmCounts( + FHoudiniEngine::Get().GetSession(), AssetLibraryId, TCHAR_TO_UTF8(*HoudiniAssetName), &ParmCount, + &DefaultIntValueCount, &DefaultFloatValueCount, &DefaultStringValueCount, &DefaultChoiceValueCount); + + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Hapi failed: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } - NewParameters.Empty(); - if (NodeInfo.parmCount == 0) + if (ParmCount > 0) + { + // Allocate space in the default value arrays + // Fetch default values from HAPI + DefaultIntValues.SetNumZeroed(DefaultIntValueCount); + DefaultFloatValues.SetNumZeroed(DefaultFloatValueCount); + DefaultStringValues.SetNumZeroed(DefaultStringValueCount); + DefaultChoiceValues.SetNumZeroed(DefaultChoiceValueCount); + + Result = FHoudiniApi::GetAssetDefinitionParmValues( + FHoudiniEngine::Get().GetSession(), AssetLibraryId, TCHAR_TO_UTF8(*HoudiniAssetName), + DefaultIntValues.GetData(), 0, DefaultIntValueCount, + DefaultFloatValues.GetData(), 0, DefaultFloatValueCount, + false, DefaultStringValues.GetData(), 0, DefaultStringValueCount, + DefaultChoiceValues.GetData(), 0, DefaultChoiceValueCount); + + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Hapi failed: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + } + } + + NewParameters.Empty(); + if (ParmCount == 0) { // The asset doesnt have any parameter, we're done. return true; } - else if (NodeInfo.parmCount < 0) + else if (ParmCount < 0) { // Invalid parm count return false; } - TArray AllMultiParams; + // Retrieve all the parameter infos either from instantiated node or from asset definition. + TArray ParmInfos; + ParmInfos.SetNumUninitialized(ParmCount); - // Retrieve all the parameter infos. - TArray< HAPI_ParmInfo > ParmInfos; - ParmInfos.SetNumUninitialized(NodeInfo.parmCount); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParameters( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); + if (AssetId >= 0) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), NodeId, &ParmInfos[0], 0, ParmCount), false); + } + else + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAssetDefinitionParmInfos( + FHoudiniEngine::Get().GetSession(), AssetLibraryId, TCHAR_TO_UTF8(*HoudiniAssetName), &ParmInfos[0], 0, ParmCount), false); + } // Create a name lookup cache for the current parameters - TMap CurrentParametersByName; + // Use an array has in some cases, multiple parameters can have the same name! + TMap> CurrentParametersByName; CurrentParametersByName.Reserve(CurrentParameters.Num()); - for (auto& Parm : CurrentParameters) + for (const auto& Parm : CurrentParameters) { - if (!Parm) + if (!IsValid(Parm)) continue; - CurrentParametersByName.Add(Parm->GetParameterName(), Parm); + + FString ParmName = Parm->GetParameterName(); + TArray* FoundParmArray = CurrentParametersByName.Find(ParmName); + if (!FoundParmArray) + { + // Create a new array + TArray ParmArray; + ParmArray.Add(Parm); + + // add the new array to the map + CurrentParametersByName.Add(ParmName, ParmArray); + } + else + { + // add this parameter to the existing array + FoundParmArray->Add(Parm); + } } // Create properties for parameters. + TMap FloatRampsToIndex; + TMap ColorRampsToIndex; TArray NewParmIds; - for (int32 ParamIdx = 0; ParamIdx < NodeInfo.parmCount; ++ParamIdx) + TArray AllMultiParams; + for (int32 ParamIdx = 0; ParamIdx < ParmCount; ++ParamIdx) { - // Retrieve param info at this index. const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; @@ -271,6 +437,7 @@ FHoudiniParameterTranslator::BuildAllParameters( // Check if any parent folder of this parameter is invisible bool SkipParm = false; + bool ParentFolderVisible = true; HAPI_ParmId ParentId = ParmInfo.parentId; while (ParentId > 0 && !SkipParm) { @@ -278,9 +445,15 @@ FHoudiniParameterTranslator::BuildAllParameters( return Info.id == ParentId; })) { + // We now keep invisible parameters but show/hid them in UpdateParameterFromInfo(). if (ParentInfoPtr->invisible && ParentInfoPtr->type == HAPI_PARMTYPE_FOLDER) - SkipParm = true; - ParentId = ParentInfoPtr->parentId; + ParentFolderVisible = false; + + // Prevent endless loops! + if (ParentId != ParentInfoPtr->parentId) + ParentId = ParentInfoPtr->parentId; + else + ParentId = -1; } else { @@ -301,51 +474,66 @@ FHoudiniParameterTranslator::BuildAllParameters( EHoudiniParameterType ParmType = EHoudiniParameterType::Invalid; FHoudiniParameterTranslator::GetParmTypeFromParmInfo(ParmInfo, ParmType); - UHoudiniParameter ** FoundHoudiniParameter = CurrentParametersByName.Find(NewParmName); - - // If that parameter exists, we might be able to simply reuse it. - bool IsFoundParameterValid = false; - if (FoundHoudiniParameter && *FoundHoudiniParameter && !(*FoundHoudiniParameter)->IsPendingKill()) + // Not using the name lookup map! + UHoudiniParameter* FoundHoudiniParameter = nullptr; + TArray* MatchingParameters = CurrentParametersByName.Find(NewParmName); + if ((ParmType != EHoudiniParameterType::Invalid) && MatchingParameters) { - // First, we can simply check that the tuple size hasn't changed - if ((*FoundHoudiniParameter)->GetTupleSize() != ParmInfo.size) - { - IsFoundParameterValid = false; - } - else if (ParmType == EHoudiniParameterType::Invalid ) - { - IsFoundParameterValid = false; - } - else if (ParmType != (*FoundHoudiniParameter)->GetParameterType() ) - { - // Types do not match - IsFoundParameterValid = false; - } - else if ( !CheckParameterTypeAndClassMatch( *FoundHoudiniParameter, ParmType) ) - { - // Found parameter class does not match - IsFoundParameterValid = false; - } - else + //for (auto& CurrentParm : *MatchingParameters) + for(int32 Idx = MatchingParameters->Num() - 1; Idx >= 0; Idx--) { - // We can reuse the parameter - IsFoundParameterValid = true; + UHoudiniParameter* CurrentParm = (*MatchingParameters)[Idx]; + if (!CurrentParm) + continue; + + // First Check the parameter types match + if (ParmType != CurrentParm->GetParameterType()) + { + // Types do not match + continue; + } + + // Then, make sure the tuple size hasn't changed + if (CurrentParm->GetTupleSize() != ParmInfo.size) + { + // Tuple do not match + continue; + } + + if (!CheckParameterTypeAndClassMatch(CurrentParm, ParmType)) + { + // Wrong class + continue; + } + + // We can reuse this parameter + FoundHoudiniParameter = CurrentParm; + + // Remove it from the array/map + MatchingParameters->RemoveAt(Idx); + if (MatchingParameters->Num() <= 0) + CurrentParametersByName.Remove(NewParmName); + + break; } } - + UHoudiniParameter * HoudiniAssetParameter = nullptr; - - if (IsFoundParameterValid) + if (FoundHoudiniParameter) { // We can reuse the parameter we found - HoudiniAssetParameter = *FoundHoudiniParameter; + HoudiniAssetParameter = FoundHoudiniParameter; // Transfer param object from current map to new map CurrentParameters.Remove(HoudiniAssetParameter); - CurrentParametersByName.Remove(NewParmName); // Do a fast update of this parameter - if (!FHoudiniParameterTranslator::UpdateParameterFromInfo(HoudiniAssetParameter, AssetInfo.nodeId, ParmInfo, InForceFullUpdate, bUpdateValues)) + if (!FHoudiniParameterTranslator::UpdateParameterFromInfo( + HoudiniAssetParameter, NodeId, ParmInfo, InForceFullUpdate, bUpdateValues, + AssetId >= 0 ? nullptr : &DefaultIntValues, + AssetId >= 0 ? nullptr : &DefaultFloatValues, + AssetId >= 0 ? nullptr : &DefaultStringValues, + AssetId >= 0 ? nullptr : &DefaultChoiceValues)) continue; // Reset the states of ramp parameters. @@ -357,6 +545,8 @@ FHoudiniParameterTranslator::BuildAllParameters( UHoudiniParameterRampFloat* FloatRampParam = Cast(HoudiniAssetParameter); if (FloatRampParam) { + // Record float and color ramps for further processing (creating their Points arrays) + FloatRampsToIndex.Add(FloatRampParam, NewParameters.Num()); UHoudiniAssetComponent* ParentHAC = Cast(FloatRampParam->GetOuter()); if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) FloatRampParam->bCaching = false; @@ -370,6 +560,8 @@ FHoudiniParameterTranslator::BuildAllParameters( UHoudiniParameterRampColor* ColorRampParam = Cast(HoudiniAssetParameter); if (ColorRampParam) { + // Record float and color ramps for further processing (creating their Points arrays) + ColorRampsToIndex.Add(ColorRampParam, NewParameters.Num()); UHoudiniAssetComponent* ParentHAC = Cast(ColorRampParam->GetOuter()); if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) ColorRampParam->bCaching = false; @@ -385,16 +577,36 @@ FHoudiniParameterTranslator::BuildAllParameters( // Create a new parameter object of the appropriate type HoudiniAssetParameter = CreateTypedParameter(Outer, ParmType, NewParmName); // Fully update this parameter - if (!FHoudiniParameterTranslator::UpdateParameterFromInfo(HoudiniAssetParameter, AssetInfo.nodeId, ParmInfo, true, true)) + if (!FHoudiniParameterTranslator::UpdateParameterFromInfo( + HoudiniAssetParameter, NodeId, ParmInfo, true, true, + AssetId >= 0 ? nullptr : &DefaultIntValues, + AssetId >= 0 ? nullptr : &DefaultFloatValues, + AssetId >= 0 ? nullptr : &DefaultStringValues, + AssetId >= 0 ? nullptr : &DefaultChoiceValues)) continue; + // Record float and color ramps for further processing (creating their Points arrays) + const EHoudiniParameterType NewParamType = HoudiniAssetParameter->GetParameterType(); + if (NewParamType == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat* FloatRampParam = Cast(HoudiniAssetParameter); + if (FloatRampParam) + FloatRampsToIndex.Add(FloatRampParam, NewParameters.Num()); + } + else if (NewParamType == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor* ColorRampParam = Cast(HoudiniAssetParameter); + if (ColorRampParam) + ColorRampsToIndex.Add(ColorRampParam, NewParameters.Num()); + } } + + HoudiniAssetParameter->SetVisibleParent(ParentFolderVisible); // Add the new parameters NewParameters.Add(HoudiniAssetParameter); NewParmIds.Add(ParmInfo.id); - // Check if the parameter is a direct child of a multiparam. if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::MultiParm) AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); @@ -407,7 +619,6 @@ FHoudiniParameterTranslator::BuildAllParameters( if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::FolderList) AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); } - } // Assign folder type to all folderlists, @@ -415,13 +626,13 @@ FHoudiniParameterTranslator::BuildAllParameters( for (int32 Idx = 0; Idx < NewParameters.Num(); ++Idx) { UHoudiniParameter * CurParam = NewParameters[Idx]; - if (!CurParam || CurParam->IsPendingKill()) + if (!IsValid(CurParam)) continue; if (CurParam->GetParameterType() == EHoudiniParameterType::FolderList) { UHoudiniParameterFolderList* CurFolderList = Cast(CurParam); - if (!CurFolderList || CurFolderList->IsPendingKill()) + if (!IsValid(CurFolderList)) continue; int32 FirstChildIdx = Idx + 1; @@ -429,7 +640,7 @@ FHoudiniParameterTranslator::BuildAllParameters( continue; UHoudiniParameterFolder* FirstChildFolder = Cast(NewParameters[FirstChildIdx]); - if (!FirstChildFolder || FirstChildFolder->IsPendingKill()) + if (!IsValid(FirstChildFolder)) continue; if (FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Radio || @@ -449,8 +660,32 @@ FHoudiniParameterTranslator::BuildAllParameters( } } - FHoudiniEngineUtils::UpdateEditorProperties(Outer, true); + // Create / update the Points arrays for the ramp parameters + if (FloatRampsToIndex.Num() > 0) + { + for (TPair const& Entry : FloatRampsToIndex) + { + UHoudiniParameterRampFloat* const RampFloatParam = Entry.Key; + const int32 ParamIndex = Entry.Value; + if (!IsValid(RampFloatParam)) + continue; + RampFloatParam->UpdatePointsArray(NewParameters, ParamIndex + 1); + } + } + if (ColorRampsToIndex.Num() > 0) + { + for (TPair const& Entry : ColorRampsToIndex) + { + UHoudiniParameterRampColor* const RampColorParam = Entry.Key; + const int32 ParamIndex = Entry.Value; + if (!IsValid(RampColorParam)) + continue; + + RampColorParam->UpdatePointsArray(NewParameters, ParamIndex + 1); + } + } + return true; } @@ -862,7 +1097,7 @@ FHoudiniParameterTranslator::CheckParameterTypeAndClassMatch(UHoudiniParameter* bool FHoudiniParameterTranslator::CheckParameterClassAndInfoMatch(UHoudiniParameter* Parameter, const HAPI_ParmInfo& ParmInfo) { - if (!Parameter || Parameter->IsPendingKill()) + if (!IsValid(Parameter)) return false; UClass* FoundClass = Parameter->GetClass(); @@ -1056,13 +1291,19 @@ FHoudiniParameterTranslator::CreateTypedParameter(UObject * Outer, const EHoudin bool FHoudiniParameterTranslator::UpdateParameterFromInfo( UHoudiniParameter * HoudiniParameter, const HAPI_NodeId& InNodeId, const HAPI_ParmInfo& ParmInfo, - const bool& bFullUpdate, const bool& bUpdateValue) + const bool& bFullUpdate, const bool& bUpdateValue, + const TArray* DefaultIntValues, + const TArray* DefaultFloatValues, + const TArray* DefaultStringValues, + const TArray* DefaultChoiceValues) { - if (!HoudiniParameter || HoudiniParameter->IsPendingKill()) + if (!IsValid(HoudiniParameter)) return false; // Copy values from the ParmInfos - HoudiniParameter->SetNodeId(InNodeId); + const bool bHasValidNodeId = InNodeId >= 0; + if (bHasValidNodeId) + HoudiniParameter->SetNodeId(InNodeId); HoudiniParameter->SetParmId(ParmInfo.id); HoudiniParameter->SetParentParmId(ParmInfo.parentId); @@ -1114,11 +1355,12 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( HoudiniParameter->SetParameterHelp(Help); } - if (ParmType == EHoudiniParameterType::String + if (bHasValidNodeId && + (ParmType == EHoudiniParameterType::String || ParmType == EHoudiniParameterType::Int || ParmType == EHoudiniParameterType::Float || ParmType == EHoudiniParameterType::Toggle - || ParmType == EHoudiniParameterType::Color) + || ParmType == EHoudiniParameterType::Color)) { // See if the parm has an expression int32 TupleIdx = ParmInfo.intValuesIndex; @@ -1159,38 +1401,41 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( } // Get parameter tags. - int32 TagCount = HoudiniParameter->GetTagCount(); - for (int32 Idx = 0; Idx < TagCount; ++Idx) + if (bHasValidNodeId) { - HAPI_StringHandle TagNameSH; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagName( - FHoudiniEngine::Get().GetSession(), - InNodeId, ParmInfo.id, Idx, &TagNameSH)) + int32 TagCount = HoudiniParameter->GetTagCount(); + for (int32 Idx = 0; Idx < TagCount; ++Idx) { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); - continue; - } + HAPI_StringHandle TagNameSH; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagName( + FHoudiniEngine::Get().GetSession(), + InNodeId, ParmInfo.id, Idx, &TagNameSH)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); + continue; + } - FString NameString = TEXT(""); - FHoudiniEngineString::ToFString(TagNameSH, NameString); - if (NameString.IsEmpty()) - { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); - continue; - } + FString NameString = TEXT(""); + FHoudiniEngineString::ToFString(TagNameSH, NameString); + if (NameString.IsEmpty()) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); + continue; + } - HAPI_StringHandle TagValueSH; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagValue( - FHoudiniEngine::Get().GetSession(), - InNodeId, ParmInfo.id, TCHAR_TO_ANSI(*NameString), &TagValueSH)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag value: parmId: %d, tag: %s"), ParmInfo.id, *NameString); - } + HAPI_StringHandle TagValueSH; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagValue( + FHoudiniEngine::Get().GetSession(), + InNodeId, ParmInfo.id, TCHAR_TO_ANSI(*NameString), &TagValueSH)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag value: parmId: %d, tag: %s"), ParmInfo.id, *NameString); + } - FString ValueString = TEXT(""); - FHoudiniEngineString::ToFString(TagValueSH, ValueString); + FString ValueString = TEXT(""); + FHoudiniEngineString::ToFString(TagValueSH, ValueString); - HoudiniParameter->GetTags().Add(NameString, ValueString); + HoudiniParameter->GetTags().Add(NameString, ValueString); + } } } @@ -1201,7 +1446,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Button: { UHoudiniParameterButton* HoudiniParameterButton = Cast(HoudiniParameter); - if (HoudiniParameterButton && !HoudiniParameterButton->IsPendingKill()) + if (IsValid(HoudiniParameterButton)) { HoudiniParameterButton->SetValueIndex(ParmInfo.intValuesIndex); } @@ -1211,7 +1456,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::ButtonStrip: { UHoudiniParameterButtonStrip* HoudiniParameterButtonStrip = Cast(HoudiniParameter); - if (HoudiniParameterButtonStrip && !HoudiniParameterButtonStrip->IsPendingKill()) + if (IsValid(HoudiniParameterButtonStrip)) { HoudiniParameterButtonStrip->SetValueIndex(ParmInfo.intValuesIndex); HoudiniParameterButtonStrip->Count = ParmInfo.choiceCount; @@ -1221,15 +1466,31 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( { // Get the choice descriptors. TArray< HAPI_ParmChoiceInfo > ParmChoices; + ParmChoices.SetNum(ParmInfo.choiceCount); for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); - + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + } + else if (DefaultChoiceValues && DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex) && + DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex + ParmInfo.choiceCount - 1)) + { + FPlatformMemory::Memcpy( + ParmChoices.GetData(), + DefaultChoiceValues->GetData() + ParmInfo.choiceIndex, + sizeof(HAPI_ParmChoiceInfo) * ParmInfo.choiceCount); + } + else + { + return false; + } + HoudiniParameterButtonStrip->InitializeLabels(ParmInfo.choiceCount); for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) @@ -1243,10 +1504,26 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( } } - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterButtonStrip->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.choiceCount) != HAPI_RESULT_SUCCESS) + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterButtonStrip->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.choiceCount) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex) && + DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex + ParmInfo.choiceCount - 1)) + { + for (int32 Index = 0; Index < ParmInfo.choiceCount; ++Index) + { + HoudiniParameterButtonStrip->SetValueAt( + Index, (*DefaultIntValues)[ParmInfo.intValuesIndex + Index]); + } + } + else { return false; } @@ -1257,7 +1534,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Color: { UHoudiniParameterColor* HoudiniParameterColor = Cast(HoudiniParameter); - if (HoudiniParameterColor && !HoudiniParameterColor->IsPendingKill()) + if (IsValid(HoudiniParameterColor)) { // Set the valueIndex HoudiniParameterColor->SetValueIndex(ParmInfo.floatValuesIndex); @@ -1267,9 +1544,24 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( { // Get the actual value for this property. FLinearColor Color = FLinearColor::White; - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - (float *)&Color.R, ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + (float *)&Color.R, ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultFloatValues && DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex) && + DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex + ParmInfo.size - 1)) + { + FPlatformMemory::Memcpy( + &Color.R, + DefaultFloatValues->GetData() + ParmInfo.floatValuesIndex, + sizeof(float) * ParmInfo.size); + } + else { return false; } @@ -1289,7 +1581,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::ColorRamp: { UHoudiniParameterRampColor* HoudiniParameterRampColor = Cast(HoudiniParameter); - if (HoudiniParameterRampColor && !HoudiniParameterRampColor->IsPendingKill()) + if (IsValid(HoudiniParameterRampColor)) { HoudiniParameterRampColor->SetInstanceCount(ParmInfo.instanceCount); HoudiniParameterRampColor->MultiParmInstanceLength = ParmInfo.instanceLength; @@ -1299,7 +1591,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::FloatRamp: { UHoudiniParameterRampFloat* HoudiniParameterRampFloat = Cast(HoudiniParameter); - if (HoudiniParameterRampFloat && !HoudiniParameterRampFloat->IsPendingKill()) + if (IsValid(HoudiniParameterRampFloat)) { HoudiniParameterRampFloat->SetInstanceCount(ParmInfo.instanceCount); HoudiniParameterRampFloat->MultiParmInstanceLength = ParmInfo.instanceLength; @@ -1313,7 +1605,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::FileImage: { UHoudiniParameterFile* HoudiniParameterFile = Cast(HoudiniParameter); - if (HoudiniParameterFile && !HoudiniParameterFile->IsPendingKill()) + if (IsValid(HoudiniParameterFile)) { // Set the valueIndex HoudiniParameterFile->SetValueIndex(ParmInfo.stringValuesIndex); @@ -1324,7 +1616,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( // Check if we are read-only bool bIsReadOnly = false; FString FileChooserTag; - if (FHoudiniParameterTranslator::HapiGetParameterTagValue(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_FILE_READONLY, FileChooserTag)) + if (bHasValidNodeId && FHoudiniParameterTranslator::HapiGetParameterTagValue(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_FILE_READONLY, FileChooserTag)) { if (FileChooserTag.Equals(TEXT("read"), ESearchCase::IgnoreCase)) bIsReadOnly = true; @@ -1348,10 +1640,27 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( { // Get the actual values for this property. TArray< HAPI_StringHandle > StringHandles; - StringHandles.SetNumZeroed(ParmInfo.size); - if (FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), InNodeId, false, - &StringHandles[0], ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + + if (bHasValidNodeId) + { + StringHandles.SetNumZeroed(ParmInfo.size); + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), InNodeId, false, + &StringHandles[0], ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex) && + DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex + ParmInfo.size - 1)) + { + StringHandles.SetNumZeroed(ParmInfo.size); + FPlatformMemory::Memcpy( + &StringHandles[0], + DefaultStringValues->GetData() + ParmInfo.stringValuesIndex, + sizeof(HAPI_StringHandle) * ParmInfo.size); + } + else { return false; } @@ -1380,7 +1689,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Float: { UHoudiniParameterFloat* HoudiniParameterFloat = Cast(HoudiniParameter); - if (HoudiniParameterFloat && !HoudiniParameterFloat->IsPendingKill()) + if (IsValid(HoudiniParameterFloat)) { // Set the valueIndex HoudiniParameterFloat->SetValueIndex(ParmInfo.floatValuesIndex); @@ -1389,10 +1698,26 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( { // Update the parameter's value HoudiniParameterFloat->SetNumberOfValues(ParmInfo.size); - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InNodeId, + + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterFloat->GetValuesPtr(), + ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultFloatValues && DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex) && + DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex + ParmInfo.size - 1)) + { + FPlatformMemory::Memcpy( HoudiniParameterFloat->GetValuesPtr(), - ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + DefaultFloatValues->GetData() + ParmInfo.floatValuesIndex, + sizeof(float) * ParmInfo.size); + } + else { return false; } @@ -1412,11 +1737,13 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( // Get the parameter's unit from the "unit" tag FString ParamUnit; - FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); - HoudiniParameterFloat->SetUnit(ParamUnit); - - // Get the parameter's no swap tag (hengine_noswap) - HoudiniParameterFloat->SetNoSwap(HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_NOSWAP)); + if (bHasValidNodeId) + { + FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); + HoudiniParameterFloat->SetUnit(ParamUnit); + // Get the parameter's no swap tag (hengine_noswap) + HoudiniParameterFloat->SetNoSwap(HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_NOSWAP)); + } // Set the min and max for this parameter if (ParmInfo.hasMin) @@ -1526,7 +1853,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Folder: { UHoudiniParameterFolder* HoudiniParameterFolder = Cast(HoudiniParameter); - if (HoudiniParameterFolder && !HoudiniParameterFolder->IsPendingKill()) + if (IsValid(HoudiniParameterFolder)) { // Set the valueIndex HoudiniParameterFolder->SetValueIndex(ParmInfo.intValuesIndex); @@ -1538,7 +1865,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::FolderList: { UHoudiniParameterFolderList* HoudiniParameterFolderList = Cast(HoudiniParameter); - if (HoudiniParameterFolderList && !HoudiniParameterFolderList->IsPendingKill()) + if (IsValid(HoudiniParameterFolderList)) { // Set the valueIndex HoudiniParameterFolderList->SetValueIndex(ParmInfo.intValuesIndex); @@ -1550,7 +1877,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( { // Inputs parameters are just stored, and handled separately by UHoudiniInputs UHoudiniParameterOperatorPath* HoudiniParameterOperatorPath = Cast(HoudiniParameter); - if (HoudiniParameterOperatorPath && !HoudiniParameterOperatorPath->IsPendingKill()) + if (IsValid(HoudiniParameterOperatorPath)) { /* // DO NOT CREATE A DUPLICATE INPUT HERE! @@ -1564,7 +1891,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( if (!ParentHAC) return false; - if (!NewInput || NewInput->IsPendingKill()) + if (!IsValid(NewInput)) return false; // Set the nodeId @@ -1581,7 +1908,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Int: { UHoudiniParameterInt* HoudiniParameterInt = Cast(HoudiniParameter); - if (HoudiniParameterInt && !HoudiniParameterInt->IsPendingKill()) + if (IsValid(HoudiniParameterInt)) { // Set the valueIndex HoudiniParameterInt->SetValueIndex(ParmInfo.intValuesIndex); @@ -1590,10 +1917,30 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( { // Get the actual values for this property. HoudiniParameterInt->SetNumberOfValues(ParmInfo.size); - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterInt->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterInt->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex) && + DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex + ParmInfo.size - 1)) + { + for (int32 Index = 0; Index < ParmInfo.size; ++Index) + { + // TODO: cannot use SetValueAt: Min/Max has not yet been configured and defaults to 0,0 + // so the value is clamped to 0 + // HoudiniParameterInt->SetValueAt( + // (*DefaultIntValues)[ParmInfo.intValuesIndex + Index], Index); + *(HoudiniParameterInt->GetValuesPtr() + Index) = (*DefaultIntValues)[ParmInfo.intValuesIndex + Index]; + } + } + else { return false; } @@ -1612,8 +1959,11 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( // Get the parameter's unit from the "unit" tag FString ParamUnit; - FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); - HoudiniParameterInt->SetUnit(ParamUnit); + if (bHasValidNodeId) + { + FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); + HoudiniParameterInt->SetUnit(ParamUnit); + } // Set the min and max for this parameter if (ParmInfo.hasMin) @@ -1678,7 +2028,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::IntChoice: { UHoudiniParameterChoice* HoudiniParameterIntChoice = Cast(HoudiniParameter); - if (HoudiniParameterIntChoice && !HoudiniParameterIntChoice->IsPendingKill()) + if (IsValid(HoudiniParameterIntChoice)) { // Set the valueIndex HoudiniParameterIntChoice->SetValueIndex(ParmInfo.intValuesIndex); @@ -1687,10 +2037,26 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( { // Get the actual values for this property. int32 CurrentIntValue = 0; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, &CurrentIntValue, - ParmInfo.intValuesIndex, ParmInfo.size), false); + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, &CurrentIntValue, + ParmInfo.intValuesIndex, 1/*ParmInfo.size*/), false); + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex)) + { + CurrentIntValue = (*DefaultIntValues)[ParmInfo.intValuesIndex]; + } + else + { + return false; + } + + // Get the value from the index array, if applicable. + if (CurrentIntValue < HoudiniParameterIntChoice->GetNumChoices()) + CurrentIntValue = HoudiniParameterIntChoice->GetIndexFromValueArray(CurrentIntValue); // Check the value is valid if (CurrentIntValue >= ParmInfo.choiceCount) @@ -1710,20 +2076,36 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( HoudiniParameterIntChoice->SetDefaultIntValue(); // Get the choice descriptors. TArray< HAPI_ParmChoiceInfo > ParmChoices; - ParmChoices.SetNumUninitialized(ParmInfo.choiceCount); + + ParmChoices.SetNum(ParmInfo.choiceCount); for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + } + else if (DefaultChoiceValues && DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex) && + DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex + ParmInfo.choiceCount - 1)) + { + FPlatformMemory::Memcpy( + ParmChoices.GetData(), + DefaultChoiceValues->GetData() + ParmInfo.choiceIndex, + sizeof(HAPI_ParmChoiceInfo) * ParmInfo.choiceCount); + } + else + { + return false; + } // Set the array sizes HoudiniParameterIntChoice->SetNumChoices(ParmInfo.choiceCount); bool bMatchedSelectionLabel = false; - int32 CurrentIntValue = HoudiniParameterIntChoice->GetIntValue(); + int32 CurrentIntValue = HoudiniParameterIntChoice->GetIntValueIndex(); for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) { FString * ChoiceLabel = HoudiniParameterIntChoice->GetStringChoiceLabelAt(ChoiceIdx); @@ -1740,6 +2122,26 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( { HoudiniParameterIntChoice->SetStringValue(*ChoiceLabel); } + + int32 IntValue = ChoiceIdx; + + // If useMenuItemTokenAsValue is set, then the value is not the index. Find the value using the token, if possible. + if (ParmInfo.useMenuItemTokenAsValue) + { + if (ChoiceIdx < ParmChoices.Num()) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + FString Token; + + if (HoudiniEngineString.ToFString(Token)) + { + int32 Value = FCString::Atoi(*Token); + IntValue = Value; + } + } + } + + HoudiniParameterIntChoice->SetIntValueArray(ChoiceIdx, IntValue); } } else if (bUpdateValue) @@ -1754,7 +2156,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::StringChoice: { UHoudiniParameterChoice* HoudiniParameterStringChoice = Cast(HoudiniParameter); - if (HoudiniParameterStringChoice && !HoudiniParameterStringChoice->IsPendingKill()) + if (IsValid(HoudiniParameterStringChoice)) { // Set the valueIndex HoudiniParameterStringChoice->SetValueIndex(ParmInfo.stringValuesIndex); @@ -1763,10 +2165,22 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( { // Get the actual values for this property. HAPI_StringHandle StringHandle; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandle, - ParmInfo.stringValuesIndex, ParmInfo.size), false); + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandle, + ParmInfo.stringValuesIndex, 1/*ParmInfo.size*/), false); + } + else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex)) + { + StringHandle = (*DefaultStringValues)[ParmInfo.stringValuesIndex]; + } + else + { + return false; + } // Get the string value FString StringValue; @@ -1783,14 +2197,30 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( HoudiniParameterStringChoice->SetDefaultStringValue(); // Get the choice descriptors. TArray< HAPI_ParmChoiceInfo > ParmChoices; - ParmChoices.SetNumUninitialized(ParmInfo.choiceCount); + + ParmChoices.SetNum(ParmInfo.choiceCount); for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + } + else if (DefaultChoiceValues && DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex) && + DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex + ParmInfo.choiceCount - 1)) + { + FPlatformMemory::Memcpy( + ParmChoices.GetData(), + DefaultChoiceValues->GetData() + ParmInfo.choiceIndex, + sizeof(HAPI_ParmChoiceInfo) * ParmInfo.choiceCount); + } + else + { + return false; + } // Set the array sizes HoudiniParameterStringChoice->SetNumChoices(ParmInfo.choiceCount); @@ -1837,7 +2267,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Label: { UHoudiniParameterLabel* HoudiniParameterLabel = Cast(HoudiniParameter); - if (HoudiniParameterLabel && !HoudiniParameterLabel->IsPendingKill()) + if (IsValid(HoudiniParameterLabel)) { if (ParmInfo.type != HAPI_PARMTYPE_LABEL) return false; @@ -1847,11 +2277,28 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( // Get the actual value for this property. TArray StringHandles; - StringHandles.SetNumZeroed(ParmInfo.size); - FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandles[0], - ParmInfo.stringValuesIndex, ParmInfo.size); + + if (bHasValidNodeId) + { + StringHandles.SetNumZeroed(ParmInfo.size); + FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandles[0], + ParmInfo.stringValuesIndex, ParmInfo.size); + } + else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex) && + DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex + ParmInfo.size - 1)) + { + StringHandles.SetNumZeroed(ParmInfo.size); + FPlatformMemory::Memcpy( + StringHandles.GetData(), + DefaultStringValues->GetData() + ParmInfo.stringValuesIndex, + sizeof(HAPI_StringHandle) * ParmInfo.size); + } + else + { + return false; + } HoudiniParameterLabel->EmptyLabelString(); @@ -1870,7 +2317,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::MultiParm: { UHoudiniParameterMultiParm* HoudiniParameterMulti = Cast(HoudiniParameter); - if (HoudiniParameterMulti && !HoudiniParameterMulti->IsPendingKill()) + if (IsValid(HoudiniParameterMulti)) { if (ParmInfo.type != HAPI_PARMTYPE_MULTIPARMLIST) return false; @@ -1880,9 +2327,21 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( // Set the multiparm value int32 MultiParmValue = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, &MultiParmValue, ParmInfo.intValuesIndex, 1), false); + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, &MultiParmValue, ParmInfo.intValuesIndex, 1), false); + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex)) + { + MultiParmValue = (*DefaultIntValues)[ParmInfo.intValuesIndex]; + } + else + { + return false; + } HoudiniParameterMulti->SetValue(MultiParmValue); HoudiniParameterMulti->MultiParmInstanceCount = ParmInfo.instanceCount; @@ -1900,7 +2359,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Separator: { UHoudiniParameterSeparator* HoudiniParameterSeparator = Cast(HoudiniParameter); - if (HoudiniParameterSeparator && !HoudiniParameterSeparator->IsPendingKill()) + if (IsValid(HoudiniParameterSeparator)) { // We can only handle separator type. if (ParmInfo.type != HAPI_PARMTYPE_SEPARATOR) @@ -1916,7 +2375,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::StringAssetRef: { UHoudiniParameterString* HoudiniParameterString = Cast(HoudiniParameter); - if (HoudiniParameterString && !HoudiniParameterString->IsPendingKill()) + if (IsValid(HoudiniParameterString)) { // We can only handle string type. if (ParmInfo.type != HAPI_PARMTYPE_STRING && ParmInfo.type != HAPI_PARMTYPE_NODE) @@ -1930,11 +2389,28 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( { // Get the actual value for this property. TArray< HAPI_StringHandle > StringHandles; - StringHandles.SetNumZeroed(ParmInfo.size); - if (FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandles[0], - ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + + if (bHasValidNodeId) + { + StringHandles.SetNumZeroed(ParmInfo.size); + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandles[0], + ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex) && + DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex + ParmInfo.size - 1)) + { + StringHandles.SetNumZeroed(ParmInfo.size); + FPlatformMemory::Memcpy( + StringHandles.GetData(), + DefaultStringValues->GetData() + ParmInfo.stringValuesIndex, + sizeof(HAPI_StringHandle) * ParmInfo.size); + } + else { return false; } @@ -1955,8 +2431,11 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( // Set default string values on created HoudiniParameterString->SetDefaultValues(); // Check if the parameter has the "asset_ref" tag - HoudiniParameterString->SetIsAssetRef( - FHoudiniParameterTranslator::HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_ASSET_REF)); + if (bHasValidNodeId) + { + HoudiniParameterString->SetIsAssetRef( + FHoudiniParameterTranslator::HapiGetParameterHasTag(InNodeId, ParmInfo.id, HOUDINI_PARAMETER_STRING_REF_TAG)); + } } } } @@ -1965,7 +2444,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Toggle: { UHoudiniParameterToggle* HoudiniParameterToggle = Cast(HoudiniParameter); - if (HoudiniParameterToggle && !HoudiniParameterToggle->IsPendingKill()) + if (IsValid(HoudiniParameterToggle)) { if (ParmInfo.type != HAPI_PARMTYPE_TOGGLE) return false; @@ -1978,10 +2457,27 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( { // Get the actual values for this property. HoudiniParameterToggle->SetNumberOfValues(ParmInfo.size); - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterToggle->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterToggle->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex) && + DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex + ParmInfo.size - 1)) + { + for (int32 Index = 0; Index < ParmInfo.size; ++Index) + { + HoudiniParameterToggle->SetValueAt( + (*DefaultIntValues)[ParmInfo.intValuesIndex + Index] != 0, Index); + } + } + else { return false; } @@ -2084,32 +2580,49 @@ FHoudiniParameterTranslator::HapiGetParameterHasTag(const HAPI_NodeId& NodeId, c bool FHoudiniParameterTranslator::UploadChangedParameters( UHoudiniAssetComponent * HAC ) { - if (!HAC || HAC->IsPendingKill()) + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniParameterTranslator::UploadChangedParameters); + + if (!IsValid(HAC)) return false; TMap RampsToRevert; + // First upload all parameters, including the current child parameters/points of ramps, and then process + // the ramp parameters themselves (delete and insert operations of ramp points) + // This is so that the initial upload of parameter values use the correct parameter value/tuple array indices + // (which will change after potential insert/delete operations). Insert operations will upload their new + // parameter values after the insert. + TArray RampsToUpload; for (int32 ParmIdx = 0; ParmIdx < HAC->GetNumParameters(); ParmIdx++) { UHoudiniParameter*& CurrentParm = HAC->Parameters[ParmIdx]; - if (!CurrentParm || CurrentParm->IsPendingKill() || !CurrentParm->HasChanged()) + if (!IsValid(CurrentParm) || !CurrentParm->HasChanged()) continue; bool bSuccess = false; + const EHoudiniParameterType CurrentParmType = CurrentParm->GetParameterType(); if (CurrentParm->IsPendingRevertToDefault()) { bSuccess = RevertParameterToDefault(CurrentParm); - if (CurrentParm->GetParameterType() == EHoudiniParameterType::FloatRamp || - CurrentParm->GetParameterType() == EHoudiniParameterType::ColorRamp) + if (CurrentParmType == EHoudiniParameterType::FloatRamp || + CurrentParmType == EHoudiniParameterType::ColorRamp) { RampsToRevert.Add(CurrentParm->GetParameterName(), CurrentParm); } } else { - bSuccess = UploadParameterValue(CurrentParm); + if (CurrentParmType == EHoudiniParameterType::FloatRamp || + CurrentParmType == EHoudiniParameterType::ColorRamp) + { + RampsToUpload.Add(CurrentParm); + } + else + { + bSuccess = UploadParameterValue(CurrentParm); + } } @@ -2127,13 +2640,22 @@ FHoudiniParameterTranslator::UploadChangedParameters( UHoudiniAssetComponent * H FHoudiniParameterTranslator::RevertRampParameters(RampsToRevert, HAC->GetAssetId()); + for (UHoudiniParameter* const RampParam : RampsToUpload) + { + if (!IsValid(RampParam)) + continue; + + if (UploadParameterValue(RampParam)) + RampParam->MarkChanged(false); + } + return true; } bool FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return false; switch (InParam->GetParameterType()) @@ -2141,7 +2663,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) case EHoudiniParameterType::Float: { UHoudiniParameterFloat* FloatParam = Cast(InParam); - if (!FloatParam || FloatParam->IsPendingKill()) + if (!IsValid(FloatParam)) { return false; } @@ -2161,7 +2683,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) case EHoudiniParameterType::Int: { UHoudiniParameterInt* IntParam = Cast(InParam); - if (!IntParam || IntParam->IsPendingKill()) + if (!IsValid(IntParam)) { return false; } @@ -2181,7 +2703,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) case EHoudiniParameterType::String: { UHoudiniParameterString* StringParam = Cast(InParam); - if (!StringParam || StringParam->IsPendingKill()) + if (!IsValid(StringParam)) { return false; } @@ -2205,11 +2727,13 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) case EHoudiniParameterType::IntChoice: { UHoudiniParameterChoice* ChoiceParam = Cast(InParam); - if (!ChoiceParam || ChoiceParam->IsPendingKill()) + if (!IsValid(ChoiceParam)) return false; // Set the parameter's int value. - int32 IntValue = ChoiceParam->GetIntValue(); + const int32 IntValueIndex = ChoiceParam->GetIntValueIndex(); + const int32 IntValue = ChoiceParam->GetIntValue(IntValueIndex); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( FHoudiniEngine::Get().GetSession(), ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); @@ -2218,7 +2742,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) case EHoudiniParameterType::StringChoice: { UHoudiniParameterChoice* ChoiceParam = Cast(InParam); - if (!ChoiceParam || ChoiceParam->IsPendingKill()) + if (!IsValid(ChoiceParam)) { return false; } @@ -2234,7 +2758,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) else { // Set the parameter's int value. - int32 IntValue = ChoiceParam->GetIntValue(); + int32 IntValue = ChoiceParam->GetIntValueIndex(); HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmIntValues( FHoudiniEngine::Get().GetSession(), ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); @@ -2245,7 +2769,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) case EHoudiniParameterType::Color: { UHoudiniParameterColor* ColorParam = Cast(InParam); - if (!ColorParam || ColorParam->IsPendingKill()) + if (!IsValid(ColorParam)) return false; bool bHasAlpha = ColorParam->GetTupleSize() == 4 ? true : false; @@ -2358,7 +2882,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) bool FHoudiniParameterTranslator::RevertParameterToDefault(UHoudiniParameter* InParam) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return false; if (!InParam->IsPendingRevertToDefault()) @@ -2442,12 +2966,11 @@ FHoudiniParameterTranslator::GetFolderTypeFromParamInfo(const HAPI_ParmInfo* Par } bool -FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InParam, TArray &OldParams, const int32& InAssetId, const int32 CurrentIndex) +FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad( + UHoudiniParameter* InParam, TArray& OldParams, const int32& InAssetId, const HAPI_AssetInfo& AssetInfo) { - UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - - if (!MultiParam || MultiParam->IsPendingKill()) + if (!IsValid(MultiParam)) return false; UHoudiniParameterRampFloat* FloatRampParameter = nullptr; @@ -2455,15 +2978,9 @@ FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InPara if (MultiParam->GetParameterType() == EHoudiniParameterType::FloatRamp) FloatRampParameter = Cast(MultiParam); - else if (MultiParam->GetParameterType() == EHoudiniParameterType::ColorRamp) ColorRampParameter = Cast(MultiParam); - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), InAssetId, &AssetInfo), false); - HAPI_NodeId NodeId = AssetInfo.nodeId; int32 Idx = 0; @@ -2473,36 +2990,85 @@ FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InPara if (!GetMultiParmInstanceStartIdx(AssetInfo, MultiParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) return false; - - for (int n = 0; n < InstanceCount - MultiParam->GetInstanceCount(); ++n) + const int32 InstanceCountInUnreal = FMath::Max(MultiParam->GetInstanceCount(), 0); + if (InstanceCount > InstanceCountInUnreal) { - FHoudiniApi::RemoveMultiparmInstance( - FHoudiniEngine::Get().GetSession(), NodeId, - ParmId, MultiParam->InstanceStartOffset); + // The multiparm has more instances on the Houdini side, remove instances from the end until it has the same + // number as in Unreal. + // NOTE: Initially this code always removed the first instance. But that causes an issue if HAPI/Houdini does + // not immediately update the parameter names (param1, param2, param3, when param1 is removed, 2 -> 1, + // 3 -> 2) so that could result in GetParameters returning parameters with unique IDs, but where the names + // are not up to date, so in the above example, the last param could still be named param3 when it should + // be named param2. + const int32 Delta = InstanceCount - InstanceCountInUnreal; + for (int32 n = 0; n < Delta; ++n) + { + FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, + ParmId, MultiParam->InstanceStartOffset + InstanceCount - 1 - n); + } + } + else if (InstanceCountInUnreal > InstanceCount) + { + // The multiparm has fewer instances on the Houdini side, add instances at the end until it has the same + // number as in Unreal. + // NOTE: Initially this code always inserted before the first instance. But that causes an issue if HAPI/Houdini + // does not immediately update the parameter names (param1, param2, param3, when a param is inserted + // before 1, then 1->2, 2->3, 3->4 so that could result in GetParameters returning parameters with unique + // IDs, but where the names are not up to date, so in the above example, the now second param could still + // be named param1 when it should be named param2. + const int32 Delta = InstanceCountInUnreal - InstanceCount; + for (int32 n = 0; n < Delta; ++n) + { + FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, + ParmId, MultiParam->InstanceStartOffset + InstanceCount + n); + } } - for (int n = 0; n < MultiParam->GetInstanceCount() - InstanceCount; ++n) + // We are going to Sync nested multi-params recursively + int32 MyParmId = InParam->GetParmId(); + // First, we need to manually look for our index in the old map + // Since there is a possibility that the parameter interface has changed since our previous cook + int32 MyIndex = -1; + for (int32 ParamIdx = 0; ParamIdx < OldParams.Num(); ParamIdx++) { - FHoudiniApi::InsertMultiparmInstance( - FHoudiniEngine::Get().GetSession(), NodeId, - ParmId, MultiParam->InstanceStartOffset); + UHoudiniParameter* CurrentOldParm = OldParams[ParamIdx]; + if (!IsValid(CurrentOldParm)) + continue; + + if (CurrentOldParm->GetParmId() != MyParmId) + continue; + + // We found ourself, exit now + MyIndex = ParamIdx; + break; } - - // Sync nested multi-params recursively - for (int32 ParamIdx = CurrentIndex; ParamIdx < OldParams.Num(); ++ParamIdx) + if (MyIndex >= 0) { - UHoudiniParameter* NextParm = OldParams[ParamIdx]; - if (NextParm->GetParentParmId() == ParmId) + // Now Sync nested multi-params recursively + for (int32 ParamIdx = MyIndex + 1; ParamIdx < OldParams.Num(); ParamIdx++) { - if (NextParm->GetParameterType() == EHoudiniParameterType::MultiParm) - { - SyncMultiParmValuesAtLoad(NextParm, OldParams, InAssetId, ParamIdx); - } + UHoudiniParameter* NextParm = OldParams[ParamIdx]; + if (!IsValid(NextParm)) + continue; + + if (NextParm->GetParentParmId() != MyParmId) + continue; + + if (NextParm->GetParameterType() != EHoudiniParameterType::MultiParm) + continue; + + // Always make sure to NOT recurse on ourselves! + // This could happen if parms have been deleted... + if (NextParm->GetParmId() == MyParmId) + continue; + + SyncMultiParmValuesAtLoad(NextParm, OldParams, InAssetId, AssetInfo); } } - // The multiparm is a ramp, Get the param infos again, since the number of param instances is changed if (!GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) return false; @@ -2557,7 +3123,6 @@ FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InPara } } - return true; } @@ -2565,7 +3130,7 @@ FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InPara bool FHoudiniParameterTranslator::UploadRampParameter(UHoudiniParameter* InParam) { UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - if (!MultiParam || MultiParam->IsPendingKill()) + if (!IsValid(MultiParam)) return false; UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InParam->GetOuter()); @@ -2645,8 +3210,7 @@ bool FHoudiniParameterTranslator::UploadRampParameter(UHoudiniParameter* InParam int32 Idx = 0; int32 InstanceCount = -1; HAPI_ParmId ParmId = -1; - TArray< HAPI_ParmInfo > ParmInfos; - + TArray ParmInfos; if (!FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) return false; @@ -2658,10 +3222,11 @@ bool FHoudiniParameterTranslator::UploadRampParameter(UHoudiniParameter* InParam if (InsertIndex != InstanceCount) return false; - - // Starting index of parameters which just inserted + // Starting index of parameters which we just inserted Idx += 3 * InsertIndexStart; - + + if (!ParmInfos.IsValidIndex(Idx + 2)) + return false; for (auto & Event : *Events) { @@ -2780,44 +3345,56 @@ FHoudiniParameterTranslator::UploadDirectoryPath(UHoudiniParameterFile* InParam) } bool -FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(HAPI_AssetInfo& InAssetInfo, const FString InParmName, +FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(const HAPI_AssetInfo& InAssetInfo, const FString InParmName, int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos) { + // TODO: FIX/IMPROVE THIS! + // This is bad, that function can be called recursively, fetches all parameters, + // iterates on them, and fetches their name!! WTF! + // TODO: Slightly better now, at least we dont fetch every parameter's name! + // Reset outputs - OutStartIdx = 0; + OutStartIdx = -1; OutInstanceCount = -1; OutParmId = -1; OutParmInfos.Empty(); - // .. the asset's node info + // Try to find the parameter by its name + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, TCHAR_TO_UTF8(*InParmName), &OutParmId), false); + + if (OutParmId < 0) + return false; + + // Get the asset's node info HAPI_NodeInfo NodeInfo; HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &NodeInfo), false); + // Get all parameters OutParmInfos.SetNumUninitialized(NodeInfo.parmCount); HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &OutParmInfos[0], 0, NodeInfo.parmCount), false); - - while (OutStartIdx < OutParmInfos.Num()) + OutStartIdx = 0; + for (const auto& CurrentParmInfo : OutParmInfos) { - FString ParmNameBuffer; - FHoudiniEngineString(OutParmInfos[OutStartIdx].nameSH).ToFString(ParmNameBuffer); - - if (ParmNameBuffer == InParmName) + if (OutParmId == CurrentParmInfo.id) { - OutParmId = OutParmInfos[OutStartIdx].id; OutInstanceCount = OutParmInfos[OutStartIdx].instanceCount; - break; + + // Increment, to get the Start index of the ramp children parameters + OutStartIdx++; + return true; } - OutStartIdx += 1; + OutStartIdx++; } - // Start index of the ramp children parameters - OutStartIdx += 1; + // We failed to find the parm + OutStartIdx = -1; - return true; + return false; } bool diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h index 203372a51..539ce7021 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,6 +28,9 @@ #include "HAPI/HAPI_Common.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +class UHoudiniAsset; class UHoudiniAssetComponent; class UHoudiniParameter; class UHoudiniParameterFile; @@ -64,10 +67,11 @@ struct HOUDINIENGINE_API FHoudiniParameterTranslator static bool RevertParameterToDefault(UHoudiniParameter* InParam); // - static bool SyncMultiParmValuesAtLoad(UHoudiniParameter* MultiParam, TArray &OldParams, const int32& InAssetId, const int32 Idx); + static bool SyncMultiParmValuesAtLoad( + UHoudiniParameter* MultiParam, TArray &OldParams, const int32& InAssetId, const HAPI_AssetInfo& AssetInfo); // - static bool GetMultiParmInstanceStartIdx(HAPI_AssetInfo& InAssetInfo, const FString InParmName, + static bool GetMultiParmInstanceStartIdx(const HAPI_AssetInfo& InAssetInfo, const FString InParmName, int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos); /** Update parameters from the asset, re-uses parameters passed into CurrentParameters. @@ -85,7 +89,9 @@ struct HOUDINIENGINE_API FHoudiniParameterTranslator TArray& CurrentParameters, TArray& NewParameters, const bool& bUpdateValues, - const bool& InForceFullUpdate); + const bool& InForceFullUpdate, + const UHoudiniAsset* InHoudiniAsset, + const FString& InHoudiniAssetName); // Parameter creation static UHoudiniParameter * CreateTypedParameter( @@ -103,7 +109,11 @@ struct HOUDINIENGINE_API FHoudiniParameterTranslator const HAPI_NodeId& InNodeId, const HAPI_ParmInfo& ParmInfo, const bool& bFullUpdate = true, - const bool& bUpdateValue = true); + const bool& bUpdateValue = true, + const TArray* DefaultIntValues = nullptr, + const TArray* DefaultFloatValues = nullptr, + const TArray* DefaultStringValues = nullptr, + const TArray* DefaultChoiceValues = nullptr); static UClass* GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo); diff --git a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp index 11c1b8b61..4f329835b 100644 --- a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,7 +34,7 @@ #include "HoudiniSplineComponent.h" #include "HoudiniEngineUtils.h" #include "HoudiniEngineString.h" - +#include "HoudiniGenericAttribute.h" #include "HoudiniGeoPartObject.h" #include "Components/SplineComponent.h" @@ -141,41 +141,52 @@ FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniInput* Input) } bool -FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSplineComponent) +FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent* HoudiniSplineComponent) { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return false; int32 CurveNode_id = HoudiniSplineComponent->GetNodeId(); if (CurveNode_id < 0) return false; - - bool Success = true; - FString CurvePointsString = FString(); - int32 CurveTypeValue = (int32)EHoudiniCurveType::Bezier; - int32 CurveMethodValue = (int32)EHoudiniCurveMethod::CVs; - int32 CurveClosed = 0; - int32 CurveReversed = 0; - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsString( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT(""), CurvePointsString); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); + FString CurvePointsString = FString(); + if (!FHoudiniEngineUtils::HapiGetParameterDataAsString( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT(""), CurvePointsString)) + { + return false; + } + int32 CurveTypeValue = (int32)EHoudiniCurveType::Bezier; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue)) + { + return false; + } HoudiniSplineComponent->SetCurveType((EHoudiniCurveType)CurveTypeValue); + + int32 CurveMethodValue = (int32)EHoudiniCurveMethod::CVs; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue)) + { + return false; + } HoudiniSplineComponent->SetCurveMethod((EHoudiniCurveMethod)CurveMethodValue); + + int32 CurveClosed = 0; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed)) + { + return false; + } HoudiniSplineComponent->SetClosedCurve(CurveClosed == 1); + + int32 CurveReversed = 0; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed)) + { + return false; + } HoudiniSplineComponent->SetReversed(CurveReversed == 1); // We need to get the NodeInfo to get the parent id @@ -184,11 +195,14 @@ FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSp HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( FHoudiniEngine::Get().GetSession(), CurveNode_id, &NodeInfo), false); - TArray< float > RefinedCurvePositions; + TArray RefinedCurvePositions; HAPI_AttributeInfo AttributeRefinedCurvePositions; FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); - Success &= FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNode_id, 0, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions); + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNode_id, 0, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions)) + { + return false; + } // Process coords string and extract positions. TArray CurvePoints; @@ -197,15 +211,15 @@ FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSp TArray CurveDisplayPoints; FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurveDisplayPoints); - // build curve points for editable curves. - if (HoudiniSplineComponent->CurvePoints.Num() < CurvePoints.Num()) + // Build curve points for editable curves. + if (HoudiniSplineComponent->CurvePoints.Num() != CurvePoints.Num()) { - HoudiniSplineComponent->CurvePoints.Empty(); - for (FVector NextPos : CurvePoints) + HoudiniSplineComponent->CurvePoints.SetNum(CurvePoints.Num()); + for(int32 Idx = 0; Idx < CurvePoints.Num(); Idx++) { - FTransform NextTrans = FTransform::Identity; - NextTrans.SetLocation(NextPos); - HoudiniSplineComponent->CurvePoints.Add(NextTrans); + FTransform Transform = FTransform::Identity; + Transform.SetLocation(CurvePoints[Idx]); + HoudiniSplineComponent->CurvePoints[Idx] = Transform; } } @@ -214,26 +228,29 @@ FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSp HoudiniSplineComponent->MarkChanged(false); - return Success; + return true; } bool -FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent) +FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( + UHoudiniSplineComponent* HoudiniSplineComponent, + bool bInAddRotAndScaleAttributes) { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return true; TArray PositionArray; TArray RotationArray; TArray Scales3dArray; - TArray UniformScaleArray; - for (FTransform & NextTransform : HoudiniSplineComponent->CurvePoints) + for (FTransform& CurrentTransform : HoudiniSplineComponent->CurvePoints) { - PositionArray.Add(NextTransform.GetLocation()); - RotationArray.Add(NextTransform.GetRotation()); - Scales3dArray.Add(NextTransform.GetScale3D()); - UniformScaleArray.Add(1.f); + PositionArray.Add(CurrentTransform.GetLocation()); + if (bInAddRotAndScaleAttributes) + { + RotationArray.Add(CurrentTransform.GetRotation()); + Scales3dArray.Add(CurrentTransform.GetScale3D()); + } } HAPI_NodeId CurveNode_id = HoudiniSplineComponent->GetNodeId(); @@ -255,8 +272,8 @@ FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSpline CurveNode_id, InputNodeNameString, &PositionArray, - &RotationArray, - &Scales3dArray, + bInAddRotAndScaleAttributes ? &RotationArray : nullptr, + bInAddRotAndScaleAttributes ? &Scales3dArray : nullptr, HoudiniSplineComponent->GetCurveType(), HoudiniSplineComponent->GetCurveMethod(), HoudiniSplineComponent->IsClosedCurve(), @@ -270,18 +287,6 @@ FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSpline return Success; } -bool -FHoudiniSplineTranslator::HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, UHoudiniSplineComponent* SplineComponent) -{ - if (!SplineComponent || SplineComponent->IsPendingKill()) - return true; - - bool Success = HapiUpdateNodeForHoudiniSplineComponent(SplineComponent); - - return Success; -} - bool FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( HAPI_NodeId& CurveNodeId, @@ -674,6 +679,35 @@ FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( } break; + case HAPI_STORAGETYPE_INT64: + { + // Storing IntData + TArray Int64Data; + Int64Data.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, -1, + Int64Data.GetData(), 0, attr_info.count), false); + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info, Int64Data.GetData(), + 0, attr_info.count), false); + } + break; + case HAPI_STORAGETYPE_FLOAT: { // Storing Float Data @@ -749,7 +783,14 @@ FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( break; default: + { + // Unhandled attribute type - warn + HOUDINI_LOG_WARNING( + TEXT("HapiCreateCurveInputNodeForData() - Unhandled attribute type - skipping") + TEXT("- consider disabling additionnal rot/scale if having issues with the HDA")); continue; + } + } } } @@ -961,8 +1002,6 @@ FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( // And cook it with refinement enabled CookOptions.refineCurveToLinear = true; - //HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - // FHoudiniEngine::Get().GetSession(), CurveNodeId, &CookOptions), false); if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, false)) return false; #endif @@ -1020,7 +1059,7 @@ FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode(co if (GeoId < 0) return nullptr; - if (!OuterComponent || OuterComponent->IsPendingKill()) + if (!IsValid(OuterComponent)) return nullptr; USceneComponent* const SceneComponent = Cast(OuterComponent); @@ -1038,6 +1077,9 @@ FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode(co HoudiniSplineComponent->RegisterComponent(); HoudiniSplineComponent->AttachToComponent(SceneComponent, FAttachmentTransformRules::KeepRelativeTransform); + // Delete the curve points so that UpdateHoudiniCurves initializes them from HAPI + HoudiniSplineComponent->CurvePoints.Empty(); + HoudiniSplineComponent->DisplayPoints.Empty(); UpdateHoudiniCurve(HoudiniSplineComponent); ReselectSelectedActors(); @@ -1049,11 +1091,11 @@ FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode(co UHoudiniSplineComponent* FHoudiniSplineTranslator::CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC) { - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return nullptr; UObject* Outer = nullptr; - if (OuterHAC && !OuterHAC->IsPendingKill()) + if (IsValid(OuterHAC)) Outer = OuterHAC->GetOwner() ? OuterHAC->GetOwner() : OuterHAC->GetOuter(); UHoudiniSplineComponent *NewHoudiniSplineComponent = NewObject(Outer, UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Transactional); @@ -1096,7 +1138,7 @@ USplineComponent* FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(const TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed) { - if (!OuterComponent || OuterComponent->IsPendingKill()) + if (!IsValid(OuterComponent)) return nullptr; USceneComponent* OuterSceneComponent = Cast(OuterComponent); @@ -1175,7 +1217,7 @@ FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed) { - if (!EditedSplineComponent || EditedSplineComponent->IsPendingKill()) + if (!IsValid(EditedSplineComponent)) return false; if (CurvePoints.Num() < 2) @@ -1206,7 +1248,7 @@ FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent) { - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return false; if (CurvePoints.Num() < 2) @@ -1267,7 +1309,7 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( return true; } - if (!InOuterComponent || InOuterComponent->IsPendingKill()) + if (!IsValid(InOuterComponent)) return false; int32 CurveNodeId = InHGPO.GeoId; @@ -1340,7 +1382,7 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( bNeedToRebuildSpline = true; USceneComponent* FoundComponent = Cast(FoundOutputObject ? FoundOutputObject->OutputComponent : nullptr); - if (FoundComponent && !FoundComponent->IsPendingKill()) + if (IsValid(FoundComponent)) { // Only support output to Unreal Spline for now... //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::UnrealSpline) @@ -1392,9 +1434,17 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( // Fill in the rest of output curve properties OutSplines.Add(CurveIdentifier, NewOutputObject); + + // Update FOundOutputObject so we can cache attributes after + FoundOutputObject = OutSplines.Find(CurveIdentifier); } else { + // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before + // setting the new values (so that we do not re-use any values from the previous cook) + FoundOutputObject->CachedAttributes.Empty(); + FoundOutputObject->CachedTokens.Empty(); + // if (FoundOutputObject->CurveOutputProperty.CurveOutputType == EHoudiniCurveOutputType::UnrealSpline) { @@ -1412,7 +1462,7 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( if (!FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(CurvesDisplayPoints[n], FoundUnrealSpline, FoundOutputObject->CurveOutputProperty.CurveType, FoundOutputObject->CurveOutputProperty.bClosed)) continue; - OutSplines.Add(CurveIdentifier, *FoundOutputObject); + FoundOutputObject = &OutSplines.Add(CurveIdentifier, *FoundOutputObject); } else { @@ -1430,7 +1480,7 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( FoundOutputObject->OutputComponent = NewUnrealSpline; - OutSplines.Add(CurveIdentifier, *FoundOutputObject); + FoundOutputObject = &OutSplines.Add(CurveIdentifier, *FoundOutputObject); } } // We current support Unreal Spline output only... @@ -1475,6 +1525,81 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( */ } + // Cache commonly supported Houdini attributes on the OutputAttributes + TArray LevelPaths; + if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute( + InHGPO.GeoId, InHGPO.PartId, LevelPaths, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + { + // cache the level path attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); + } + } + + TArray OutputNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute( + InHGPO.GeoId, InHGPO.PartId, OutputNames, 0, 1)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + // cache the output name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); + } + } + + TArray BakeNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeNameAttribute( + InHGPO.GeoId, InHGPO.PartId, BakeNames, 0, 1)) + { + if (BakeNames.Num() > 0 && !BakeNames[0].IsEmpty()) + { + // cache the output name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_NAME, BakeNames[0]); + } + } + + TArray BakeOutputActorNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute( + InHGPO.GeoId, InHGPO.PartId, BakeOutputActorNames, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); + } + } + + TArray BakeFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute( + InHGPO.GeoId, BakeFolders, InHGPO.PartId, 0, 1)) + { + if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) + { + // cache the unreal_bake_folder attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); + } + } + + TArray BakeOutlinerFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( + InHGPO.GeoId, InHGPO.PartId, BakeOutlinerFolders, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); + } + } + + // Update generic properties attributes on the spline component + TArray GenericAttributes; + if (FoundOutputObject && FHoudiniEngineUtils::GetGenericPropertiesAttributes( + InHGPO.GeoId, InHGPO.PartId, true, 0, 0, 0, GenericAttributes)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(FoundOutputObject->OutputComponent, GenericAttributes); + } + if (bReusedPreviousOutput) { // Remove the reused output unreal spline from the old map to avoid its deletion @@ -1493,10 +1618,10 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( bool FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; - if (!InOuterComponent || InOuterComponent->IsPendingKill()) + if (!IsValid(InOuterComponent)) return false; // ONLY DO THIS ON CURVES!!!! @@ -1505,7 +1630,7 @@ FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOu // UHoudiniAssetComponent* OuterHAC = Cast(InOuterComponent); // - // if (!OuterHAC || OuterHAC->IsPendingKill()) + // if (!IsValid(OuterHAC)) // return false; TMap NewOutputObjects; @@ -1525,16 +1650,15 @@ FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOu IntData.Empty(); if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE, CurveOutputAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE, + CurveOutputAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) continue; if (IntData.Num() <= 0) continue; - else - { - if (IntData[0] == 0) - continue; - } + + if (IntData[0] == 0) + continue; HAPI_AttributeInfo LinearAttriInfo; FHoudiniApi::AttributeInfo_Init(&LinearAttriInfo); @@ -1542,10 +1666,11 @@ FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOu bool bIsLinear = false; if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR, LinearAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR, + LinearAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) { if (IntData.Num() > 0) - bIsLinear = IntData[0] == 1; + bIsLinear = IntData[0] != 0; } HAPI_AttributeInfo ClosedAttriInfo; @@ -1554,10 +1679,11 @@ FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOu bool bIsClosed = false; if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED, ClosedAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED, + ClosedAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) { if (IntData.Num() > 0) - bIsClosed = IntData[0] == 1; + bIsClosed = IntData[0] != 0; } // We output curve to Unreal Spline only for now @@ -1572,7 +1698,7 @@ FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOu { USceneComponent* OldSplineSceneComponent = Cast(OldPair.Value.OutputComponent); - if (!OldSplineSceneComponent || OldSplineSceneComponent->IsPendingKill()) + if (!IsValid(OldSplineSceneComponent)) continue; // The output object is supposed to be a spline diff --git a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h index 7a32561a7..2c90a3ffe 100644 --- a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,11 +57,7 @@ struct HOUDINIENGINE_API FHoudiniSplineTranslator static void UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC); // Upload Houdini spline component data to the curve node, and then sync the Houdini Spline Component with the curve node. - static bool HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent); - - // Create a new curve node. - static bool HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, UHoudiniSplineComponent* SplineComponent); + static bool HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent, bool bInSetRotAndScaleAttributes); // Update the curve node data, or create a new curve node if the CurveNodeId is valid. static bool HapiCreateCurveInputNodeForData( diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp index bc6c64da3..5f9e0cce5 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp @@ -1,6 +1,35 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ #include "HoudiniStringResolver.h" #include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniEngineRuntime.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HAL/FileManager.h" void FHoudiniStringResolver::GetTokensAsStringMap(TMap& OutTokens) const { @@ -10,9 +39,19 @@ void FHoudiniStringResolver::GetTokensAsStringMap(TMap& OutToke } } +FString FHoudiniStringResolver::SanitizeTokenValue(const FString& InValue) +{ + // Replace {} characters with __ + FString OutString = InValue; + OutString.ReplaceInline(ANSI_TO_TCHAR("{"), ANSI_TO_TCHAR("__")); + OutString.ReplaceInline(ANSI_TO_TCHAR("}"), ANSI_TO_TCHAR("__")); + + return OutString; +} + void FHoudiniStringResolver::SetToken(const FString& InName, const FString& InValue) { - CachedTokens.Add(InName, InValue); + CachedTokens.Add(InName, SanitizeTokenValue(InValue)); } void FHoudiniStringResolver::SetTokensFromStringMap(const TMap& InTokens, bool bClearTokens) @@ -24,7 +63,7 @@ void FHoudiniStringResolver::SetTokensFromStringMap(const TMap for (auto& Elem : InTokens) { - CachedTokens.Add(Elem.Key, Elem.Value); + CachedTokens.Add(Elem.Key, SanitizeTokenValue(Elem.Value)); } } @@ -111,15 +150,79 @@ FString FHoudiniAttributeResolver::ResolveFullLevelPath() const return FHoudiniEngineRuntimeUtils::JoinPaths(OutputFolder, LevelPathAttr); } -FString FHoudiniAttributeResolver::ResolveOutputName() const +FString FHoudiniAttributeResolver::ResolveOutputName(const bool bInForBake) const { FString OutputAttribName; if (CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2)) + { OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2; + } + else if (bInForBake && CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_BAKE_NAME)) + { + OutputAttribName = HAPI_UNREAL_ATTRIB_BAKE_NAME; + } else + { OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1; + } return ResolveAttribute(OutputAttribName, TEXT("{object_name}")); } +FString FHoudiniAttributeResolver::ResolveBakeFolder() const +{ + const FString DefaultBakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + FString BakeFolder = ResolveAttribute(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, TEXT("{bake}")); + if (BakeFolder.IsEmpty()) + return DefaultBakeFolder; + + //if (BakeFolder.StartsWith("Game/")) + //{ + // BakeFolder = "/" + BakeFolder; + //} + + //FString AbsoluteOverridePath; + //if (BakeFolder.StartsWith("/Game/")) + //{ + // const FString RelativePath = FPaths::ProjectContentDir() + BakeFolder.Mid(6, BakeFolder.Len() - 6); + // AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + //} + //else + //{ + // if (!BakeFolder.IsEmpty()) + // AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*BakeFolder); + //} + + //// Check Validity of the path + //if (AbsoluteOverridePath.IsEmpty()) + //{ + // return DefaultBakeFolder; + //} + + return BakeFolder; +} + +void FHoudiniAttributeResolver::LogCachedAttributesAndTokens() const +{ + TArray Lines; + Lines.Add(TEXT("==================")); + Lines.Add(TEXT("Cached Attributes:")); + Lines.Add(TEXT("==================")); + for (const auto& Entry : CachedAttributes) + { + Lines.Add(FString::Printf(TEXT("%s: %s"), *(Entry.Key), *(Entry.Value))); + } + + Lines.Add(TEXT("==============")); + Lines.Add(TEXT("Cached Tokens:")); + Lines.Add(TEXT("==============")); + for (const auto& Entry : CachedTokens) + { + Lines.Add(FString::Printf(TEXT("%s: %s"), *(Entry.Key), *(Entry.Value.StringValue))); + } + + HOUDINI_LOG_DISPLAY(TEXT("%s"), *FString::Join(Lines, TEXT("\n"))); +} + diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.h b/Source/HoudiniEngine/Private/HoudiniStringResolver.h index 152b1ad0f..c0ea28b4b 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.h +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.h @@ -1,3 +1,28 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ #pragma once @@ -27,7 +52,10 @@ struct HOUDINIENGINE_API FHoudiniStringResolver // Set a named argument that will be used for argument replacement during GetAttribute calls. void SetToken(const FString& InName, const FString& InValue); - + + // Sanitize a token value. Currently only replaces { and } with __. + static FString SanitizeTokenValue(const FString& InValue); + void GetTokensAsStringMap(TMap& OutTokens) const; void SetTokensFromStringMap(const TMap& InValue, bool bClearTokens=true); @@ -71,6 +99,14 @@ struct HOUDINIENGINE_API FHoudiniAttributeResolver : public FHoudiniStringResolv FString ResolveFullLevelPath() const; // Helper for resolver custom output name attributes. - FString ResolveOutputName() const; + FString ResolveOutputName(bool bInForBake=false) const; + + // Helper for resolving the unreal_bake_folder attribute. Converts to an absolute path. + FString ResolveBakeFolder() const; + // ---------------------------------- + // Debug + // ---------------------------------- + // Logs the resolver's cached attributes and tokens + void LogCachedAttributesAndTokens() const; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp b/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp index 63ad13f59..1fd5162eb 100644 --- a/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp +++ b/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngine/Private/SAssetSelectionWidget.h b/Source/HoudiniEngine/Private/SAssetSelectionWidget.h index 7fb29c056..66f2c39e6 100644 --- a/Source/HoudiniEngine/Private/SAssetSelectionWidget.h +++ b/Source/HoudiniEngine/Private/SAssetSelectionWidget.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngine/Private/Tests/HoudiniCoreTests.cpp b/Source/HoudiniEngine/Private/Tests/HoudiniCoreTests.cpp new file mode 100644 index 000000000..251e169c4 --- /dev/null +++ b/Source/HoudiniEngine/Private/Tests/HoudiniCoreTests.cpp @@ -0,0 +1,13 @@ +#include "../HoudiniEngine.h" +#include "Misc/AutomationTest.h" + +#if WITH_DEV_AUTOMATION_TESTS + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(HoudiniCoreTest, "Houdini.Core.TestAutomation", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter) + +bool HoudiniCoreTest::RunTest(const FString & Parameters) +{ + return true; +} + +#endif \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/Tests/HoudiniCoreTests.h b/Source/HoudiniEngine/Private/Tests/HoudiniCoreTests.h new file mode 100644 index 000000000..a9d659de4 --- /dev/null +++ b/Source/HoudiniEngine/Private/Tests/HoudiniCoreTests.h @@ -0,0 +1,7 @@ +#pragma once +#if WITH_DEV_AUTOMATION_TESTS + +#include "CoreMinimal.h" + +#endif + diff --git a/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp b/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp index d2214b9b6..84c891640 100644 --- a/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,6 +23,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + #include "UnrealBrushTranslator.h" #include "HoudiniEngine.h" @@ -248,7 +249,7 @@ bool FUnrealBrushTranslator::CreateInputNodeForBrush( for (int32 PosIndex = 0; PosIndex < NumPoints; ++PosIndex) { FVector Point = BrushModel->Points[PosIndex]; - Point = ActorTransformInverse.TransformPosition(Point); + Point = ActorTransform.InverseTransformPosition(Point); FVector Pos(Point.X, Point.Z, Point.Y); OutPosition[PosIndex] = Pos/HAPI_UNREAL_SCALE_FACTOR_POSITION; } @@ -375,26 +376,52 @@ bool FUnrealBrushTranslator::CreateInputNodeForBrush( } } - // Create list of materials, one for each face. - TArray< char * > OutMaterials; + // List of materials, one for each face. + TArray OutMaterials; + + //Lists of material parameters TMap> ScalarMaterialParameters; TMap> VectorMaterialParameters; TMap> TextureMaterialParameters; - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - Materials, MaterialIndices, OutMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - CreatedNodeId, - 0, - NumNodes, - OutMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + Materials, MaterialIndices, OutMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + CreatedNodeId, + 0, + NumNodes, + OutMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + Materials, MaterialIndices, OutMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + CreatedNodeId, + 0, + NumNodes, + OutMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } // Delete material names. FUnrealMeshTranslator::DeleteFaceMaterialArray(OutMaterials); @@ -410,7 +437,6 @@ bool FUnrealBrushTranslator::CreateInputNodeForBrush( check(0); return false; } - } // Commit the geo. diff --git a/Source/HoudiniEngine/Private/UnrealBrushTranslator.h b/Source/HoudiniEngine/Private/UnrealBrushTranslator.h index f03075a2c..1d285570f 100644 --- a/Source/HoudiniEngine/Private/UnrealBrushTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealBrushTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp new file mode 100644 index 000000000..fda662186 --- /dev/null +++ b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp @@ -0,0 +1,290 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealFoliageTypeTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "FoliageType_InstancedStaticMesh.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInputTranslator.h" + +#include "Engine/StaticMesh.h" +#include "Components/StaticMeshComponent.h" + + +bool +FUnrealFoliageTypeTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + UFoliageType_InstancedStaticMesh* InFoliageType, + HAPI_NodeId& InputObjectNodeId, + const FString& InputNodeName, + const bool& ExportAllLODs, + const bool& ExportSockets, + const bool& ExportColliders) +{ + if (!IsValid(InFoliageType)) + return false; + + UStaticMesh* const InputSM = InFoliageType->GetStaticMesh(); + if (!IsValid(InputSM)) + return false; + + UStaticMeshComponent* const StaticMeshComponent = nullptr; + bool bSuccess = HapiCreateInputNodeForStaticMesh( + InputSM, + InputObjectNodeId, + InputNodeName, + StaticMeshComponent, + ExportAllLODs, + ExportSockets, + ExportColliders); + + if (bSuccess) + { + const int32 PartId = 0; + CreateHoudiniFoliageTypeAttributes(InFoliageType, InputObjectNodeId, PartId, HAPI_ATTROWNER_DETAIL); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InputObjectNodeId), false); + } + + return bSuccess; +} + +bool FUnrealFoliageTypeTranslator::CreateInputNodeForReference( + UFoliageType* InFoliageType, + HAPI_NodeId& InInputNodeId, + const FString& InRef, + const FString& InInputNodeName, + const FTransform& InTransform, + const bool& bImportAsReferenceRotScaleEnabled) +{ + bool bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InInputNodeId, InRef, InInputNodeName, InTransform, bImportAsReferenceRotScaleEnabled); + if (!bSuccess) + return false; + + const int32 PartId = 0; + if (CreateHoudiniFoliageTypeAttributes(InFoliageType, InInputNodeId, PartId, HAPI_ATTROWNER_POINT)) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InInputNodeId), false); + return true; + } + + return false; +} + +bool +FUnrealFoliageTypeTranslator::CreateHoudiniFoliageTypeAttributes(UFoliageType* InFoliageType, const int32& InNodeId, const int32& InPartId, HAPI_AttributeOwner InAttributeOwner) +{ + if (InNodeId < 0) + return false; + + bool bSuccess = true; + + // Create attribute for unreal_foliage + HAPI_AttributeInfo AttributeInfoUnrealFoliage; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoUnrealFoliage); + AttributeInfoUnrealFoliage.tupleSize = 1; + AttributeInfoUnrealFoliage.count = 1; + AttributeInfoUnrealFoliage.exists = true; + AttributeInfoUnrealFoliage.owner = InAttributeOwner; + AttributeInfoUnrealFoliage.storage = HAPI_STORAGETYPE_INT; + AttributeInfoUnrealFoliage.originalOwner = HAPI_ATTROWNER_INVALID; + + // Create the new attribute + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, &AttributeInfoUnrealFoliage)) + { + // The New attribute has been successfully created, set its value + int UnrealFoliage = 1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, &AttributeInfoUnrealFoliage, + &UnrealFoliage, 0, 1)) + { + bSuccess = false; + } + } + + if (!bSuccess) + return false; + + // Foliage type properties that should be sent to Houdini as unreal_uproperty_ attributes. + static TArray FoliageTypePropertyNames({ + // float + // FName("Density"), + // FName("DensityAdjustmentFactor"), + // FName("Radius"), + // FName("SingleInstanceModeRadius"), + FName("AlignMaxAngle"), + FName("RandomPitchAngle"), + FName("MinimumLayerWeight"), + FName("MinimumExclusionLayerWeight"), + FName("CollisionRadius"), + FName("ShadeRadius"), + FName("InitialSeedDensity"), + FName("AverageSpreadDistance"), + FName("SpreadVariance"), + FName("MaxInitialSeedOffset"), + FName("MaxInitialAge"), + FName("MaxAge"), + FName("OverlapPriority"), + + // bool + // FName("bSingleInstanceModeOverrideRadius"), + FName("bCanGrowInShade"), + FName("bSpawnsInShade"), + + // int32 + FName("OverriddenLightMapRes"), + FName("CustomDepthStencilValue"), + FName("TranslucencySortPriority"), + FName("NumSteps"), + FName("SeedsPerStep"), + FName("DistributionSeed"), + FName("ChangeCount"), + FName("VirtualTextureCullMips"), + + // uint32 + FName("AlignToNormal"), + FName("RandomYaw"), + FName("CollisionWithWorld"), + FName("CastShadow"), + FName("bAffectDynamicIndirectLighting"), + FName("bAffectDistanceFieldLighting"), + FName("bCastDynamicShadow"), + FName("bCastStaticShadow"), + FName("bCastShadowAsTwoSided"), + FName("bReceivesDecals"), + FName("bOverrideLightMapRes"), + FName("bUseAsOccluder"), + FName("bRenderCustomDepth"), + FName("ReapplyDensity"), + FName("ReapplyRadius"), + FName("ReapplyAlignToNormal"), + FName("ReapplyRandomYaw"), + FName("ReapplyScaling"), + FName("ReapplyScaleX"), + FName("ReapplyScaleY"), + FName("ReapplyScaleZ"), + FName("ReapplyRandomPitchAngle"), + FName("ReapplyGroundSlope"), + FName("ReapplyHeight"), + FName("ReapplyLandscapeLayers"), + FName("ReapplyZOffset"), + FName("ReapplyCollisionWithWorld"), + FName("ReapplyVertexColorMask"), + FName("bEnableDensityScaling"), + FName("bEnableDiscardOnLoad"), + + // enums + // FName("Scaling"), + FName("LightmapType"), + + // FFloatInterval + // FName("ScaleX"), + // FName("ScaleY"), + // FName("ScaleZ"), + FName("ZOffset"), + FName("GroundSlopeAngle"), + FName("Height"), + FName("ProceduralScale"), + + // FVector + FName("CollisionScale"), + FName("LowBoundOriginRadius")}); + + EAttribOwner AttribOwner; + switch (InAttributeOwner) + { + case HAPI_ATTROWNER_POINT: + AttribOwner = EAttribOwner::Point; + break; + case HAPI_ATTROWNER_VERTEX: + AttribOwner = EAttribOwner::Vertex; + break; + case HAPI_ATTROWNER_PRIM: + AttribOwner = EAttribOwner::Prim; + break; + case HAPI_ATTROWNER_DETAIL: + AttribOwner = EAttribOwner::Detail; + break; + case HAPI_ATTROWNER_INVALID: + case HAPI_ATTROWNER_MAX: + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Owner: %d"), InAttributeOwner); + return false; + } + FHoudiniGenericAttribute GenericAttribute; + GenericAttribute.AttributeCount = 1; + GenericAttribute.AttributeOwner = AttribOwner; + + // Reserve enough space in the arrays (we only have a single point (or all are detail attributes), so attribute + // count is 1, but the tuple size could be up to 10 for transforms + GenericAttribute.DoubleValues.Reserve(10); + GenericAttribute.IntValues.Reserve(10); + GenericAttribute.StringValues.Reserve(10); + + for (const FName& PropertyName : FoliageTypePropertyNames) + { + const FString PropertyNameStr = PropertyName.ToString(); + GenericAttribute.AttributeName = FString::Printf(TEXT("unreal_uproperty_%s"), *PropertyNameStr); + // Find the property on the foliage type instance + FProperty* FoundProperty = nullptr; + UObject* FoundPropertyObject = nullptr; + void* Container = nullptr; + if (!FHoudiniGenericAttribute::FindPropertyOnObject( + InFoliageType, + PropertyNameStr, + FoundProperty, + FoundPropertyObject, + Container)) + continue; + + if (!FHoudiniGenericAttribute::GetAttributeTupleSizeAndStorageFromProperty( + InFoliageType, + FoundProperty, + Container, + GenericAttribute.AttributeTupleSize, + GenericAttribute.AttributeType)) + continue; + + const int32 AtIndex = 0; + if (!FHoudiniGenericAttribute::GetPropertyValueFromObject( + InFoliageType, + FoundProperty, + Container, + GenericAttribute, + AtIndex)) + continue; + + FHoudiniEngineUtils::SetGenericPropertyAttribute(InNodeId, InPartId, GenericAttribute); + } + + return bSuccess; +} diff --git a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h new file mode 100644 index 000000000..ae84271a0 --- /dev/null +++ b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h @@ -0,0 +1,67 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "UnrealMeshTranslator.h" + +class UFoliageType; +class UFoliageType_InstancedStaticMesh; +class UStaticMeshComponent; + +struct HOUDINIENGINE_API FUnrealFoliageTypeTranslator : public FUnrealMeshTranslator +{ +public: + // HAPI : Marshaling, extract geometry and create input asset for it - return true on success + static bool HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + UFoliageType_InstancedStaticMesh* InFoliageType, + HAPI_NodeId& InputObjectNodeId, + const FString& InputNodeName, + const bool& ExportAllLODs = false, + const bool& ExportSockets = false, + const bool& ExportColliders = false); + + // Create an input node that references the asset via InRef (unreal_instance). + // Also calls CreateHoudiniFoliageTypeAttributes, to create the unreal_foliage attribute, as well as + // unreal_uproperty_ attributes for the foliage type settings. + static bool CreateInputNodeForReference( + UFoliageType* InFoliageType, + HAPI_NodeId& InInputNodeId, + const FString& InRef, + const FString& InInputNodeName, + const FTransform& InTransform, + const bool& bImportAsReferenceRotScaleEnabled); + +protected: + // Creates the unreal_foliage and unreal_uproperty_ attributes for the foliage type. + static bool CreateHoudiniFoliageTypeAttributes( + UFoliageType* InFoliageType, const int32& InNodeId, const int32& InPartId, HAPI_AttributeOwner InAttributeOwner); +}; diff --git a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp index b7d13e751..6a298e3bb 100644 --- a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,7 +50,7 @@ FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( // Get the Static Mesh instanced by the component UStaticMesh* SM = ISMC->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) + if (!IsValid(SM)) return true; // Marshall the Static Mesh to Houdini diff --git a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h index 904cf8ec6..c7a6a5ef9 100644 --- a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp index b36ba936e..88b4b8f19 100644 --- a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,6 +23,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + #include "HoudiniApi.h" #include "HoudiniEngineRuntimePrivatePCH.h" #include "HoudiniEnginePrivatePCH.h" @@ -279,6 +280,7 @@ FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( return false; // Export the whole landscape and its layer as a single heightfield. + FString NodeName = InputNodeNameStr + TEXT("_") + LandscapeProxy->GetName(); //-------------------------------------------------------------------------------------------------- // 1. Extracting the height data @@ -295,7 +297,13 @@ FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( TArray HeightfieldFloatValues; HAPI_VolumeInfo HeightfieldVolumeInfo; FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); - FTransform LandscapeTransform = LandscapeProxy->ActorToWorld(); + //FTransform LandscapeTransform = LandscapeProxy->LandscapeActorToWorld();// LandscapeProxy->ActorToWorld(); + + // Get the actual transform of this proxy, not the landscape actor's transform! + FTransform LandscapeTM = LandscapeProxy->LandscapeActorToWorld(); + FTransform ProxyRelativeTM(FVector(LandscapeProxy->LandscapeSectionOffset)); + FTransform LandscapeTransform = ProxyRelativeTM * LandscapeTM; + FVector CenterOffset = FVector::ZeroVector; if (!ConvertLandscapeDataToHeightfieldData( HeightData, XSize, YSize, Min, Max, LandscapeTransform, @@ -309,7 +317,7 @@ FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( HAPI_NodeId HeightId = -1; HAPI_NodeId MaskId = -1; HAPI_NodeId MergeId = -1; - if (!CreateHeightfieldInputNode(InputNodeNameStr, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) + if (!CreateHeightfieldInputNode(NodeName, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) return false; //-------------------------------------------------------------------------------------------------- @@ -317,171 +325,158 @@ FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( //-------------------------------------------------------------------------------------------------- // Set the Height volume's data HAPI_PartId PartId = 0; - if (!SetHeighfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height"))) + if (!SetHeightfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height"))) return false; - // Add the materials used - UMaterialInterface* LandscapeMat = LandscapeProxy->GetLandscapeMaterial(); - UMaterialInterface* LandscapeHoleMat = LandscapeProxy->GetLandscapeHoleMaterial(); - UPhysicalMaterial* LandscapePhysMat = LandscapeProxy->DefaultPhysMaterial; - AddLandscapeMaterialAttributesToVolume(HeightId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); - - // Add the landscape's actor tags as prim attributes if we have any - FHoudiniEngineUtils::CreateAttributesFromTags(HeightId, PartId, LandscapeProxy->Tags); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(HeightId, PartId, LandscapeProxy, 1); - - // Add the unreal_level_path attribute - ULevel* Level = LandscapeProxy->GetLevel(); - if (Level) - { - FHoudiniEngineUtils::AddLevelPathAttribute(HeightId, PartId, Level, 1); - /* - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToVolume(HeightId, PartId, LevelPath); - */ - } + // Apply attributes to the heightfield + ApplyAttributesToHeightfieldNode(HeightId, PartId, LandscapeProxy); // Commit the height volume HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( FHoudiniEngine::Get().GetSession(), HeightId), false); //-------------------------------------------------------------------------------------------------- - // 5. Extract and convert all the layers - //-------------------------------------------------------------------------------------------------- + // 5. Extract and convert all the layers + //-------------------------------------------------------------------------------------------------- ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); if (!LandscapeInfo) return false; - bool MaskInitialized = false; int32 MergeInputIndex = 2; - int32 NumLayers = LandscapeInfo->Layers.Num(); - for (int32 n = 0; n < NumLayers; n++) - { - // 1. Extract the uint8 values from the layer - TArray CurrentLayerIntData; - FLinearColor LayerUsageDebugColor; - FString LayerName; - if (!GetLandscapeLayerData(LandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) - continue; - - // 2. Convert unreal uint8 values to floats - // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float - HAPI_VolumeInfo CurrentLayerVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); - TArray CurrentLayerFloatData; - if (!ConvertLandscapeLayerDataToHeightfieldData( - CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, - CurrentLayerFloatData, CurrentLayerVolumeInfo)) - continue; - - // We reuse the height layer's transform - CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform; - - // 3. See if we need to create an input volume, or can reuse the HF's default mask volume - bool IsMask = false; - if (LayerName.Equals(TEXT("mask"), ESearchCase::IgnoreCase)) - IsMask = true; + if (!ExtractAndConvertAllLandscapeLayers(LandscapeProxy, HeightFieldId, PartId, MergeId, MaskId, HeightfieldVolumeInfo, XSize, YSize, MergeInputIndex)) + return false; - HAPI_NodeId LayerVolumeNodeId = -1; - if (!IsMask) - { - // Current layer is not mask, so we need to create a new input volume - std::string LayerNameStr; - FHoudiniEngineUtils::ConvertUnrealString(LayerName, LayerNameStr); + auto MergeInputFn = [&MergeInputIndex] (const HAPI_NodeId MergeId, const HAPI_NodeId NodeId) -> HAPI_Result + { + // We had to create a new volume for this layer, so we need to connect it to the HF's merge node + HAPI_Result Result = FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, MergeInputIndex, NodeId, 0); - FHoudiniApi::CreateHeightfieldInputVolumeNode( - FHoudiniEngine::Get().GetSession(), - HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f); - } - else + if (Result == HAPI_RESULT_SUCCESS) { - // Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node - LayerVolumeNodeId = MaskId; + MergeInputIndex++; } + return Result; + }; - // Check if we have a valid id for the input volume. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerVolumeNodeId)) - continue; - - // 4. Set the layer/mask heighfield data in Houdini - HAPI_PartId CurrentPartId = 0; - if (!SetHeighfieldData(LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName)) - continue; - - // Get the physical material used by that layer - UPhysicalMaterial* LayerPhysicalMat = LandscapePhysMat; + // We need a valid landscape actor to get the edit layers + ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); + if(IsValid(Landscape)) + { + //-------------------------------------------------------------------------------------------------- + // Create heightfield input for each editable landscape layer + //-------------------------------------------------------------------------------------------------- + HAPI_VolumeInfo LayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); + + for(FLandscapeLayer& Layer : Landscape->LandscapeLayers) { - FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[n]; - ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; - if (LayerInfo) - LayerPhysicalMat = LayerInfo->PhysMaterial; - } - - // Also add the material attributes to the layer volumes - AddLandscapeMaterialAttributesToVolume(LayerVolumeNodeId, PartId, LandscapeMat, LandscapeHoleMat, LayerPhysicalMat); - - // Add the landscape's actor tags as prim attributes if we have any - FHoudiniEngineUtils::CreateAttributesFromTags(LayerVolumeNodeId, PartId, LandscapeProxy->Tags); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(LayerVolumeNodeId, PartId, LandscapeProxy, 1); + const FString LayerVolumeName = FString::Format(TEXT("landscapelayer_{0}"), {Layer.Name.ToString()}); + + HAPI_NodeId LandscapeLayerNodeId = -1; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape] Creating input node for editable landscape layer: %s"), *LayerVolumeName); - // Also add the level path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(LayerVolumeNodeId, PartId, Level, 1); - //AddLevelPathAttributeToVolume(LayerVolumeNodeId, PartId, LevelPath); - - // Commit the volume's geo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId), false); - - if (!IsMask) - { - // We had to create a new volume for this layer, so we need to connect it to the HF's merge node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, + &LandscapeLayerNodeId, + TCHAR_TO_UTF8(*LayerVolumeName), + XSize, YSize, + 1.f + ), false); + + // Create a volume visualization node + const FString VisualizationName = FString::Format(TEXT("visualization_{0}"), {Layer.Name.ToString()}); + HAPI_NodeId VisualizationNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, + "volumevisualization", + TCHAR_TO_UTF8(*VisualizationName), + false, + &VisualizationNodeId + ), false); + + // Set Visualization Mode to Height Field + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + VisualizationNodeId, + "vismode", + 0, 2 + ), false); + + // Set Density Field to '*'. + HAPI_ParmId DensityFieldParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + VisualizationNodeId, + "densityfield", + &DensityFieldParmId + ), false); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + VisualizationNodeId, + "*", + DensityFieldParmId, 0 + ), false); + + // Create a visibility node + const FString VisibilityName = FString::Format(TEXT("visibility_{0}"), {Layer.Name.ToString()}); + HAPI_NodeId VisibilityNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, + "visibility", + TCHAR_TO_UTF8(*VisibilityName), + false, + &VisibilityNodeId + ), false); + + // Connect landscape layer to visualization HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( FHoudiniEngine::Get().GetSession(), - MergeId, MergeInputIndex, LayerVolumeNodeId, 0), false); + VisualizationNodeId, 0, LandscapeLayerNodeId, 0), false); - MergeInputIndex++; - } - else - { - MaskInitialized = true; + // Connect visualization to visibility + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + VisibilityNodeId, 0, VisualizationNodeId, 0), false); + + // Connect the visibility node to the merge input + HOUDINI_CHECK_ERROR_RETURN(MergeInputFn(MergeId, VisibilityNodeId), false); + + FScopedSetLandscapeEditingLayer Scope(Landscape, Layer.Guid ); // Scope landscape access to the current layer + + TArray LayerHeightData; + TArray LayerHeightFloatData; + //-------------------------------------------------------------------------------------------------- + // Extracting height data + //-------------------------------------------------------------------------------------------------- + if (!GetLandscapeData(LandscapeProxy, LayerHeightData, XSize, YSize, Min, Max)) + return false; + + //-------------------------------------------------------------------------------------------------- + // Convert the height uint16 data to float + //-------------------------------------------------------------------------------------------------- + if (!ConvertLandscapeDataToHeightfieldData( + LayerHeightData, XSize, YSize, Min, Max, LandscapeTransform, + LayerHeightFloatData, LayerVolumeInfo, CenterOffset)) + return false; + + HAPI_PartId LayerPartId = 0; + SetHeightfieldData(LandscapeLayerNodeId, LayerPartId, LayerHeightFloatData, LayerVolumeInfo, LayerVolumeName); + + // Apply attributes to the heightfield input node + ApplyAttributesToHeightfieldNode(LandscapeLayerNodeId, 0, LandscapeProxy); + + // Commit the volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), LandscapeLayerNodeId), false); } } - // We need to have a mask layer as it is required for proper heightfield functionalities - // Setting the volume info on the mask is needed for the HF to have proper transform in H! - // If we didn't create a mask volume before, send a default one now - if (!MaskInitialized) - { - MaskInitialized = InitDefaultHeightfieldMask(HeightfieldVolumeInfo, MaskId); - - // Add the materials used - AddLandscapeMaterialAttributesToVolume(MaskId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); - - // Add the landscape's actor tags as prim attributes if we have any - FHoudiniEngineUtils::CreateAttributesFromTags(MaskId, PartId, LandscapeProxy->Tags); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(MaskId, PartId, LandscapeProxy, 1); - - // Also add the level path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(MaskId, PartId, Level, 1); - //AddLevelPathAttributeToVolume(MaskId, PartId, LevelPath); - - // Commit the mask volume's geo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), MaskId), false); - } - HAPI_TransformEuler HAPIObjectTransform; FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform); //FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform ); @@ -511,6 +506,261 @@ FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( return true; } +bool FUnrealLandscapeTranslator::CreateHeightfieldFromLandscapeComponentArray(ALandscapeProxy* LandscapeProxy, + const TSet& SelectedComponents, HAPI_NodeId& CreatedHeightfieldNodeId, + const FString& InputNodeNameStr) +{ + if ( SelectedComponents.Num() <= 0 ) + return false; + + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!IsValid(LandscapeInfo)) + return false; + + //-------------------------------------------------------------------------------------------------- + // Each selected component will be exported as tiled volumes in a single heightfield + //-------------------------------------------------------------------------------------------------- + //FTransform LandscapeTransform = FTransform::Identity; // The offset will be done in the component side + FTransform LandscapeTM = LandscapeProxy->LandscapeActorToWorld(); + FTransform ProxyRelativeTM(FVector(LandscapeProxy->LandscapeSectionOffset)); + FTransform LandscapeTransform = ProxyRelativeTM * LandscapeTM; + + HAPI_NodeId HeightfieldNodeId = -1; + HAPI_NodeId HeightfieldeMergeId = -1; + + int32 MergeInputIndex = 0; + bool bAllComponentCreated = true; + + int32 ComponentIdx = 0; + + LandscapeInfo->ForAllLandscapeComponents([&](ULandscapeComponent* CurrentComponent) + { + if ( !CurrentComponent ) + return; + + if ( !SelectedComponents.Contains( CurrentComponent ) ) + return; + + if ( !CreateHeightfieldFromLandscapeComponent( LandscapeProxy, CurrentComponent, ComponentIdx, HeightfieldNodeId, HeightfieldeMergeId, MergeInputIndex, InputNodeNameStr, LandscapeTransform ) ) + bAllComponentCreated = false; + + ComponentIdx++; + }); + + // Check that we have a valid id for the input Heightfield. + if ( FHoudiniEngineUtils::IsHoudiniNodeValid( HeightfieldNodeId ) ) + CreatedHeightfieldNodeId = HeightfieldNodeId; + + // Set the HF's parent OBJ's tranform to the Landscape's transform + HAPI_TransformEuler HAPIObjectTransform; + FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform); + //FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform ); + LandscapeTransform.SetScale3D( FVector::OneVector ); + FHoudiniEngineUtils::TranslateUnrealTransform( LandscapeTransform, HAPIObjectTransform ); + HAPIObjectTransform.position[ 1 ] = 0.0f; + + HAPI_NodeId ParentObjNodeId = FHoudiniEngineUtils::HapiGetParentNodeId( HeightfieldNodeId ); + FHoudiniApi::SetObjectTransform( FHoudiniEngine::Get().GetSession(), ParentObjNodeId, &HAPIObjectTransform ); + + return bAllComponentCreated; +} + +bool FUnrealLandscapeTranslator::CreateHeightfieldFromLandscapeComponent(ALandscapeProxy* LandscapeProxy, ULandscapeComponent* LandscapeComponent, + const int32& ComponentIndex, HAPI_NodeId& HeightFieldId, HAPI_NodeId& MergeId, int32& MergeInputIndex, const FString& InputNodeNameStr, const FTransform & ParentTransform) +{ + if ( !LandscapeComponent ) + return false; + + FString NodeName = InputNodeNameStr + TEXT("_") + LandscapeProxy->GetName(); + + //-------------------------------------------------------------------------------------------------- + // 1. Extract the height data + //-------------------------------------------------------------------------------------------------- + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + LandscapeComponent->GetComponentExtent( MinX, MinY, MaxX, MaxY ); + + ULandscapeInfo* LandscapeInfo = LandscapeComponent->GetLandscapeInfo(); + if ( !LandscapeInfo ) + return false; + + TArray HeightData; + int32 XSize, YSize; + if ( !GetLandscapeData( LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize ) ) + return false; + + FVector Origin = LandscapeComponent->Bounds.Origin; + FVector Extents = LandscapeComponent->Bounds.BoxExtent; + FVector Min = Origin - Extents; + FVector Max = Origin + Extents; + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the landscape's height uint16 data to float + //-------------------------------------------------------------------------------------------------- + TArray HeightfieldFloatValues; + HAPI_VolumeInfo HeightfieldVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); + FTransform LandscapeComponentTransform = LandscapeComponent->GetComponentTransform(); + + FVector CenterOffset = FVector::ZeroVector; + if ( !ConvertLandscapeDataToHeightfieldData( + HeightData, XSize, YSize, Min, Max, LandscapeComponentTransform, + HeightfieldFloatValues, HeightfieldVolumeInfo, + CenterOffset ) ) + return false; + + // We need to modify the Volume's position to the Component's position relative to the Landscape's position + FVector RelativePosition = LandscapeComponent->GetRelativeTransform().GetLocation(); + HeightfieldVolumeInfo.transform.position[1] = RelativePosition.X; + HeightfieldVolumeInfo.transform.position[0] = RelativePosition.Y; + HeightfieldVolumeInfo.transform.position[2] = 0.0f; + + ALandscapeProxy * Proxy = LandscapeComponent->GetLandscapeProxy(); + if (Proxy) + { + FTransform LandscapeTM = Proxy->LandscapeActorToWorld(); + FTransform ProxyRelativeTM(FVector(Proxy->LandscapeSectionOffset)); + + // For landscapes that live in streaming proxies, need to account for both the parent transform and + // the current transform. + // For single actor landscapes, the parent proxy transform is equal to this proxy transform + // Either way, we want to multiply by the inverse of the parent transform to get the relative + FTransform LandscapeTransform = ParentTransform.Inverse() * ProxyRelativeTM * LandscapeTM; + FVector Location = LandscapeTransform.GetLocation(); + + HeightfieldVolumeInfo.transform.position[1] += Location.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + HeightfieldVolumeInfo.transform.position[0] += Location.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + //-------------------------------------------------------------------------------------------------- + // 3. Create the Heightfield Input Node + //-------------------------------------------------------------------------------------------------- + HAPI_NodeId HeightId = -1; + HAPI_NodeId MaskId = -1; + bool CreatedHeightfieldNode = false; + if ( HeightFieldId < 0 || MergeId < 0 ) + { + if (!CreateHeightfieldInputNode(NodeName, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) + return false; + + MergeInputIndex = 2; + CreatedHeightfieldNode = true; + } + else + { + // Heightfield node was previously created, create additionnal height and a mask volumes for it + FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, &HeightId, "height", XSize, YSize, 1.0f ); + + FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, &MaskId, "mask", XSize, YSize, 1.0f ); + + // Connect the two newly created volumes to the HF's merge node + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, MergeInputIndex++, HeightId, 0 ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, MergeInputIndex++, MaskId, 0 ), false ); + } + + //-------------------------------------------------------------------------------------------------- + // 4. Set the HeightfieldData in Houdini + //-------------------------------------------------------------------------------------------------- + // Set the Height volume's data + HAPI_PartId PartId = 0; + if (!SetHeightfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height"))) + return false; + + // Apply tile attribute + AddLandscapeTileAttribute(HeightId, PartId, ComponentIndex); + + // Apply attributes to the heightfield + ApplyAttributesToHeightfieldNode(HeightId, PartId, LandscapeProxy); + + // Commit the height volume + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), HeightId), false); + + //-------------------------------------------------------------------------------------------------- + // 5. Extract and convert all the layers to HF masks + //-------------------------------------------------------------------------------------------------- + if (!ExtractAndConvertAllLandscapeLayers(LandscapeProxy, HeightFieldId, PartId, MergeId, MaskId, HeightfieldVolumeInfo, XSize, YSize, MergeInputIndex)) + return false; + + if ( CreatedHeightfieldNode ) + { + // Since HF are centered but landscape arent, we need to set the HF's center parameter + // Do it only once after creating the Heightfield node + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 0, CenterOffset.X); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 1, 0.0); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 2, CenterOffset.Y); + } + + // Finally, cook the Heightfield node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), HeightFieldId, nullptr), false); + return true; +} + + +bool +FUnrealLandscapeTranslator::CreateInputNodeForLandscape( + ALandscapeProxy* LandscapeProxy, + const FString& InputNodeNameStr, + const FString& HeightFieldName, + const FTransform& LandscapeTransform, + FVector& CenterOffset, + HAPI_NodeId& HeightId, + HAPI_PartId& PartId, + HAPI_NodeId& HeightFieldId, + HAPI_NodeId& MaskId, + HAPI_NodeId& MergeId, + TArray& HeightData, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + int32& XSize, int32& YSize + ) +{ + //-------------------------------------------------------------------------------------------------- + // 1. Extracting the height data + //-------------------------------------------------------------------------------------------------- + + FVector Min, Max; + + if (!GetLandscapeData(LandscapeProxy, HeightData, XSize, YSize, Min, Max)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the height uint16 data to float + //-------------------------------------------------------------------------------------------------- + TArray HeightfieldFloatValues; + + if (!ConvertLandscapeDataToHeightfieldData( + HeightData, XSize, YSize, Min, Max, LandscapeTransform, + HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 3. Create the Heightfield Input Node + //-------------------------------------------------------------------------------------------------- + if (!CreateHeightfieldInputNode(InputNodeNameStr, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 4. Set the HeightfieldData in Houdini + //-------------------------------------------------------------------------------------------------- + // Set the Height volume's data + if (!SetHeightfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, HeightFieldName)) + return false; + + return true; +} + // Converts Unreal uint16 values to Houdini Float bool FUnrealLandscapeTranslator::ConvertLandscapeLayerDataToHeightfieldData( @@ -645,12 +895,23 @@ FUnrealLandscapeTranslator::GetLandscapeData( int32 MinY = MAX_int32; int32 MaxX = -MAX_int32; int32 MaxY = -MAX_int32; - - // To handle streaming proxies correctly, get the extents via all the components, - // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. - for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) + + ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); + if (LandscapeProxy == Landscape) { - Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); + // The proxy is a landscape actor, so we have to use the landscape extent (landscape components + // may have been moved to proxies and may not be present on this actor). + LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); + } + else + { + // We only want to get the data for this landscape proxy. + // To handle streaming proxies correctly, get the extents via all the components, + // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. + for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) + { + Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); + } } if (!GetLandscapeData(LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize)) @@ -686,8 +947,8 @@ FUnrealLandscapeTranslator::GetLandscapeData( if ((XSize < 2) || (YSize < 2)) return false; - - // Extracting the uint16 values from the landscape + + // Extracting the uint16 values from the landscape FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); HeightData.AddZeroed(XSize * YSize); LandscapeEdit.GetHeightDataFast(MinX, MinY, MaxX, MaxY, HeightData.GetData(), 0); @@ -713,6 +974,43 @@ FUnrealLandscapeTranslator::GetLandscapeProxyBounds( Bounds.GetCenterAndExtents(Origin, Extents); } +void +FUnrealLandscapeTranslator::ApplyAttributesToHeightfieldNode( + const HAPI_NodeId HeightId, + const HAPI_PartId PartId, + ALandscapeProxy* LandscapeProxy) +{ + UMaterialInterface* LandscapeMat = LandscapeProxy->GetLandscapeMaterial(); + UMaterialInterface* LandscapeHoleMat = LandscapeProxy->GetLandscapeHoleMaterial(); + UPhysicalMaterial* LandscapePhysMat = LandscapeProxy->DefaultPhysMaterial; + + AddLandscapeMaterialAttributesToVolume(HeightId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); + + // Add the landscape's actor tags as prim attributes if we have any + FHoudiniEngineUtils::CreateAttributesFromTags(HeightId, PartId, LandscapeProxy->Tags); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(HeightId, PartId, LandscapeProxy, 1); + + // Add the unreal_level_path attribute + ULevel* Level = LandscapeProxy->GetLevel(); + if (Level) + { + FHoudiniEngineUtils::AddLevelPathAttribute(HeightId, PartId, Level, 1); + /* + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToVolume(HeightId, PartId, LevelPath); + */ + } +} + + bool FUnrealLandscapeTranslator::ConvertLandscapeDataToHeightfieldData( const TArray& IntHeightData, @@ -723,7 +1021,7 @@ FUnrealLandscapeTranslator::ConvertLandscapeDataToHeightfieldData( HAPI_VolumeInfo& HeightfieldVolumeInfo, FVector& CenterOffset) { - HeightfieldFloatValues.Empty(); + HeightfieldFloatValues.Empty(); int32 HoudiniXSize = YSize; int32 HoudiniYSize = XSize; @@ -885,7 +1183,7 @@ FUnrealLandscapeTranslator::CreateHeightfieldInputNode( } bool -FUnrealLandscapeTranslator::SetHeighfieldData( +FUnrealLandscapeTranslator::SetHeightfieldData( const HAPI_NodeId& VolumeNodeId, const HAPI_PartId& PartId, TArray& FloatValues, @@ -945,7 +1243,7 @@ bool FUnrealLandscapeTranslator::AddLandscapeMaterialAttributesToVolume( return false; // LANDSCAPE MATERIAL - if (InLandscapeMaterial && !InLandscapeMaterial->IsPendingKill()) + if (IsValid(InLandscapeMaterial)) { // Extract the path name from the material interface FString InLandscapeMaterialString = InLandscapeMaterial->GetPathName(); @@ -993,7 +1291,7 @@ bool FUnrealLandscapeTranslator::AddLandscapeMaterialAttributesToVolume( } // HOLE MATERIAL - if (InLandscapeHoleMaterial && !InLandscapeHoleMaterial->IsPendingKill()) + if (IsValid(InLandscapeHoleMaterial)) { // Extract the path name from the material interface FString InLandscapeMaterialString = InLandscapeHoleMaterial->GetPathName(); @@ -1041,7 +1339,7 @@ bool FUnrealLandscapeTranslator::AddLandscapeMaterialAttributesToVolume( } // PHYSICAL MATERIAL - if (InPhysicalMaterial && !InPhysicalMaterial->IsPendingKill()) + if (IsValid(InPhysicalMaterial)) { // Extract the path name from the material interface FString InPhysMatlString = InPhysicalMaterial->GetPathName(); @@ -1151,11 +1449,14 @@ FUnrealLandscapeTranslator::AddLevelPathAttributeToVolume( bool FUnrealLandscapeTranslator::GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, const int32& LayerIndex, - TArray& LayerData, FLinearColor& LayerUsageDebugColor, + ALandscapeProxy* LandscapeProxy, + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, FString& LayerName) { - if (!LandscapeInfo) + if (!IsValid(LandscapeInfo) || !IsValid(LandscapeProxy)) return false; // Get the landscape X/Y Size @@ -1163,7 +1464,26 @@ FUnrealLandscapeTranslator::GetLandscapeLayerData( int32 MinY = MAX_int32; int32 MaxX = -MAX_int32; int32 MaxY = -MAX_int32; - if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + + ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); + if (LandscapeProxy == Landscape) + { + // The proxy is a landscape actor, so we have to use the landscape extent (landscape components + // may have been moved to proxies and may not be present on this actor). + LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); + } + else + { + // We only want to get the data for this landscape proxy. + // To handle streaming proxies correctly, get the extents via all the components, + // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. + for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) + { + Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); + } + } + + if(MinX == MAX_int32 || MinY == MAX_int32 || MaxX == -MAX_int32 || MaxY == -MAX_int32) return false; if (!GetLandscapeLayerData( @@ -1172,6 +1492,13 @@ FUnrealLandscapeTranslator::GetLandscapeLayerData( LayerData, LayerUsageDebugColor, LayerName)) return false; + if (FName(LayerName).Compare(ALandscape::VisibilityLayer->LayerName) ==0) + { + // If we encounter the visibility layer, make sure we name + // it according to the plugin's expectations instead of using the internal `DataLayer__` name. + LayerName = HAPI_UNREAL_VISIBILITY_LAYER_NAME; + } + return true; } @@ -1231,7 +1558,7 @@ FUnrealLandscapeTranslator::InitDefaultHeightfieldMask( // Set the heighfield data in Houdini FString MaskName = TEXT("mask"); HAPI_PartId PartId = 0; - if (!SetHeighfieldData(MaskVolumeNodeId, PartId, MaskFloatData, MaskVolumeInfo, MaskName)) + if (!SetHeightfieldData(MaskVolumeNodeId, PartId, MaskFloatData, MaskVolumeInfo, MaskName)) return false; return true; @@ -1696,6 +2023,31 @@ FUnrealLandscapeTranslator::AddLandscapeComponentNameAttribute(const HAPI_NodeId return true; } +bool FUnrealLandscapeTranslator::AddLandscapeTileAttribute( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const int32& TileIdx ) +{ + HAPI_AttributeInfo AttributeInfoTileIndex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoTileIndex); + + AttributeInfoTileIndex.count = 1; + AttributeInfoTileIndex.tupleSize = 1; + AttributeInfoTileIndex.exists = true; + AttributeInfoTileIndex.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoTileIndex.storage = HAPI_STORAGETYPE_INT; + AttributeInfoTileIndex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, &AttributeInfoTileIndex ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, &AttributeInfoTileIndex, + (const int *)&TileIdx, 0, AttributeInfoTileIndex.count ), false ); + + return true; +} + bool FUnrealLandscapeTranslator::AddLandscapeLightmapColorAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeLightmapValues) { @@ -1987,4 +2339,134 @@ FUnrealLandscapeTranslator::AddLandscapeLayerAttribute( 0, AttributeInfoLayer.count), false); return true; -} \ No newline at end of file +} + +bool FUnrealLandscapeTranslator::ExtractAndConvertAllLandscapeLayers( + ALandscapeProxy* LandscapeProxy, + const HAPI_NodeId & HeightFieldId, + const HAPI_PartId & PartId, + const HAPI_NodeId &MergeId, + const HAPI_NodeId &MaskId, + const HAPI_VolumeInfo& HeightfieldVolumeInfo, + const int32 & XSize, + const int32 & YSize, + int32 & OutMergeInputIndex) +{ + + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + bool MaskInitialized = false; + + auto MergeInputFn = [&OutMergeInputIndex] (const HAPI_NodeId MergeId, const HAPI_NodeId NodeId) -> HAPI_Result + { + // We had to create a new volume for this layer, so we need to connect it to the HF's merge node + HAPI_Result Result = FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, OutMergeInputIndex, NodeId, 0); + + if (Result == HAPI_RESULT_SUCCESS) + { + OutMergeInputIndex++; + } + return Result; + }; + + int32 NumLayers = LandscapeInfo->Layers.Num(); + for (int32 n = 0; n < NumLayers; n++) + { + // 1. Extract the uint8 values from the layer + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if (!GetLandscapeLayerData(LandscapeProxy, LandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + continue; + + // 2. Convert unreal uint8 values to floats + // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float + HAPI_VolumeInfo CurrentLayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); + TArray CurrentLayerFloatData; + if (!ConvertLandscapeLayerDataToHeightfieldData( + CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, + CurrentLayerFloatData, CurrentLayerVolumeInfo)) + continue; + + // We reuse the height layer's transform + CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform; + + // 3. See if we need to create an input volume, or can reuse the HF's default mask volume + bool IsMask = false; + if (LayerName.Equals(TEXT("mask"), ESearchCase::IgnoreCase)) + IsMask = true; + + HAPI_NodeId LayerVolumeNodeId = -1; + if (!IsMask) + { + // Current layer is not mask, so we need to create a new input volume + std::string LayerNameStr; + FHoudiniEngineUtils::ConvertUnrealString(LayerName, LayerNameStr); + + FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f); + } + else + { + // Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node + LayerVolumeNodeId = MaskId; + } + + // Check if we have a valid id for the input volume. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerVolumeNodeId)) + continue; + + // 4. Set the layer/mask heighfield data in Houdini + HAPI_PartId CurrentPartId = 0; + if (!SetHeightfieldData(LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName)) + continue; + + // Get the physical material used by that layer + UPhysicalMaterial* LayerPhysicalMat = LandscapeProxy->DefaultPhysMaterial; + { + FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[n]; + ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; + if (LayerInfo) + LayerPhysicalMat = LayerInfo->PhysMaterial; + } + + // Apply attributes to the heightfield input node + ApplyAttributesToHeightfieldNode(LayerVolumeNodeId, PartId, LandscapeProxy); + + // Commit the volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId), false); + + if (!IsMask) + { + // We had to create a new volume for this layer, so we need to connect it to the HF's merge node + HOUDINI_CHECK_ERROR_RETURN(MergeInputFn(MergeId, LayerVolumeNodeId), false); + } + else + { + MaskInitialized = true; + } + } + + // We need to have a mask layer as it is required for proper heightfield functionalities + // Setting the volume info on the mask is needed for the HF to have proper transform in H! + // If we didn't create a mask volume before, send a default one now + if (!MaskInitialized) + { + MaskInitialized = InitDefaultHeightfieldMask(HeightfieldVolumeInfo, MaskId); + + ApplyAttributesToHeightfieldNode(MaskId, PartId, LandscapeProxy); + + // Commit the mask volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), MaskId), false); + } + + return true; +} diff --git a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h index ecc94a963..8f0c2735f 100644 --- a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,6 +44,46 @@ struct HOUDINIENGINE_API FUnrealLandscapeTranslator HAPI_NodeId& CreatedHeightfieldNodeId, const FString &InputNodeNameStr); + static bool CreateHeightfieldFromLandscapeComponentArray( + ALandscapeProxy* LandscapeProxy, + const TSet< ULandscapeComponent * > & SelectedComponents, + HAPI_NodeId& CreatedHeightfieldNodeId, + const FString &InputNodeNameStr + ); + + static bool CreateHeightfieldFromLandscapeComponent( + ALandscapeProxy* LandscapeProxy, + ULandscapeComponent * LandscapeComponent, + const int32& ComponentIndex, + HAPI_NodeId& HeightFieldId, + HAPI_NodeId& MergeId, + int32& MergeInputIndex, + const FString& InputNodeNameStr, + const FTransform & ParentTransform); + + static bool CreateInputNodeForLandscape( + ALandscapeProxy* LandscapeProxy, + const FString& InputNodeNameStr, + const FString& HeightFieldName, + const FTransform& LandscapeTransform, + FVector& CenterOffset, + HAPI_NodeId& HeightId, + HAPI_PartId& PartId, + HAPI_NodeId& HeightFieldId, + HAPI_NodeId& MaskId, + HAPI_NodeId& MergeId, + TArray& HeightData, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + int32& XSize, int32& YSize + + ); + + static void ApplyAttributesToHeightfieldNode( + const HAPI_NodeId HeightId, + const HAPI_PartId PartId, + ALandscapeProxy* LandscapeProxy + ); + // Extracts the uint16 values of a given landscape static bool GetLandscapeData( ALandscapeProxy* LandscapeProxy, @@ -95,7 +135,7 @@ struct HOUDINIENGINE_API FUnrealLandscapeTranslator HAPI_NodeId& MergeNodeId ); // Set the volume float value for a heightfield - static bool SetHeighfieldData( + static bool SetHeightfieldData( const HAPI_NodeId& AssetId, const HAPI_PartId& PartId, TArray< float >& FloatValues, @@ -118,6 +158,7 @@ struct HOUDINIENGINE_API FUnrealLandscapeTranslator // Extracts the uint8 values of a given landscape static bool GetLandscapeLayerData( + ALandscapeProxy* LandscapeProxy, ULandscapeInfo* LandscapeInfo, const int32& LayerIndex, TArray& LayerData, @@ -206,6 +247,9 @@ struct HOUDINIENGINE_API FUnrealLandscapeTranslator const HAPI_NodeId& NodeId, const TArray& LandscapeComponentNameArray); + static bool AddLandscapeTileAttribute( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const int32& TileIdx ); + // Add the lightmap color attribute extracted from a landscape static bool AddLandscapeLightmapColorAttribute( const HAPI_NodeId& NodeId, @@ -231,4 +275,15 @@ struct HOUDINIENGINE_API FUnrealLandscapeTranslator const TArray& LandscapeLayerArray, const FString& LayerName); + static bool ExtractAndConvertAllLandscapeLayers( + ALandscapeProxy* LandscapeProxy, + const HAPI_NodeId & HeightFieldId, + const HAPI_PartId & PartId, + const HAPI_NodeId &MergeId, + const HAPI_NodeId &MaskId, + const HAPI_VolumeInfo& HeightfieldVolumeInfo, + const int32 & XSize, + const int32 & YSize, + int32 & OutMergeInputIndex); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp b/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp index 49d233faf..aac6e7905 100644 --- a/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,7 +57,7 @@ FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( const bool& ExportColliders /* = false */) { // If we don't have a static mesh there's nothing to do. - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return false; // Node ID for the newly created node @@ -70,16 +70,16 @@ FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( bool DoExportLODs = ExportAllLODs && (StaticMesh->GetNumLODs() > 1); // Export colliders if there are some - bool DoExportColliders = ExportColliders && StaticMesh->BodySetup != nullptr; + bool DoExportColliders = ExportColliders && StaticMesh->GetBodySetup() != nullptr; if (DoExportColliders) { - if (!StaticMesh->BodySetup) + if (!StaticMesh->GetBodySetup()) { DoExportColliders = false; } else { - if (StaticMesh->BodySetup->AggGeom.GetElementCount() <= 0) + if (StaticMesh->GetBodySetup()->AggGeom.GetElementCount() <= 0) DoExportColliders = false; } } @@ -254,7 +254,7 @@ FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( int32 NextMergeIndex = NumLODsToExport; if (DoExportColliders) { - FKAggregateGeom SimpleColliders = StaticMesh->BodySetup->AggGeom; + FKAggregateGeom SimpleColliders = StaticMesh->GetBodySetup()->AggGeom; // Export BOX colliders for (auto& CurBox : SimpleColliders.BoxElems) @@ -488,7 +488,7 @@ FUnrealMeshTranslator::CreateInputNodeForMeshSockets( for (int32 Idx = 0; Idx < NumSockets; ++Idx) { UStaticMeshSocket* CurrentSocket = InMeshSocket[Idx]; - if (!CurrentSocket || CurrentSocket->IsPendingKill()) + if (!IsValid(CurrentSocket)) continue; // Get the socket's transform and convert it to HapiTransform @@ -946,15 +946,16 @@ FUnrealMeshTranslator::CreateInputNodeForRawMesh( // If we have instance override vertex colors on the StaticMeshComponent, // we first need to propagate them to our copy of the RawMesh Vert Colors TArray ChangedColors; + FStaticMeshRenderData* SMRenderData = StaticMesh->GetRenderData(); + if (StaticMeshComponent && StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && - StaticMesh->RenderData && - StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) + SMRenderData && + SMRenderData->LODResources.IsValidIndex(InLODIndex)) { FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; - FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; + FStaticMeshLODResources& RenderModel = SMRenderData->LODResources[InLODIndex]; FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) @@ -1094,7 +1095,7 @@ FUnrealMeshTranslator::CreateInputNodeForRawMesh( { // Create an array of Material Interfaces TArray MaterialInterfaces; - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()) + if (IsValid(StaticMeshComponent) && StaticMeshComponent->IsValidLowLevel()) { // StaticMeshComponent->GetUsedMaterials(MaterialInterfaces, false); @@ -1116,7 +1117,7 @@ FUnrealMeshTranslator::CreateInputNodeForRawMesh( else { // Query the Static mesh's materials - for (int32 MatIdx = 0; MatIdx < StaticMesh->StaticMaterials.Num(); MatIdx++) + for (int32 MatIdx = 0; MatIdx < StaticMesh->GetStaticMaterials().Num(); MatIdx++) { MaterialInterfaces.Add(StaticMesh->GetMaterial(MatIdx)); } @@ -1126,10 +1127,11 @@ FUnrealMeshTranslator::CreateInputNodeForRawMesh( // TODO: Fix me properly! // Proper fix would be to export the meshes via the FStaticMeshLODResources obtained // by GetLODForExport(), and then export the mesh by sections. - if (StaticMesh->RenderData && StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) + FStaticMeshRenderData* SMRenderData = StaticMesh->GetRenderData(); + if (SMRenderData && SMRenderData->LODResources.IsValidIndex(InLODIndex)) { TMap MapOfMaterials; - FStaticMeshLODResources& LODResources = StaticMesh->RenderData->LODResources[InLODIndex]; + FStaticMeshLODResources& LODResources = SMRenderData->LODResources[InLODIndex]; for (int32 SectionIndex = 0; SectionIndex < LODResources.Sections.Num(); SectionIndex++) { // Get the material for each element at the current lod index @@ -1159,32 +1161,58 @@ FUnrealMeshTranslator::CreateInputNodeForRawMesh( } } - // Create list of materials, one for each face. + // List of materials, one for each face. TArray StaticMeshFaceMaterials; + + //Lists of material parameters TMap> ScalarMaterialParameters; TMap> VectorMaterialParameters; TMap> TextureMaterialParameters; - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - RawMesh.FaceMaterialIndices.Num(), - StaticMeshFaceMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + RawMesh.FaceMaterialIndices.Num(), + StaticMeshFaceMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + RawMesh.FaceMaterialIndices.Num(), + StaticMeshFaceMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } // Delete material names. FUnrealMeshTranslator::DeleteFaceMaterialArray(StaticMeshFaceMaterials); // Delete texture material parameter names - for (auto & Pair : TextureMaterialParameters) + for (auto & Pair : TextureMaterialParameters) { FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); } @@ -1228,10 +1256,10 @@ FUnrealMeshTranslator::CreateInputNodeForRawMesh( // TODO: // Fetch default lightmap res from settings... int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) + if (StaticMesh->GetLightMapResolution() != GeneratedLightMapResolution) { TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); + LightMapResolutions.Add(StaticMesh->GetLightMapResolution()); HAPI_AttributeInfo AttributeInfoLightMapResolution; FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); @@ -1405,14 +1433,14 @@ FUnrealMeshTranslator::CreateInputNodeForRawMesh( //--------------------------------------------------------------------------------------------------------------------- // COMPONENT AND ACTOR TAGS //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + if (IsValid(StaticMeshComponent)) { TArray AllTags; for (auto& ComponentTag : StaticMeshComponent->ComponentTags) AllTags.AddUnique(ComponentTag); AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) + if (IsValid(ParentActor)) { for (auto& ActorTag : ParentActor->Tags) AllTags.AddUnique(ActorTag); @@ -1422,7 +1450,7 @@ FUnrealMeshTranslator::CreateInputNodeForRawMesh( if (!FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, AllTags)) HOUDINI_LOG_WARNING(TEXT("Could not create groups for the Static Mesh Component and Actor tags!")); - if (ParentActor && !ParentActor->IsPendingKill()) + if (IsValid(ParentActor)) { // Add the unreal_actor_path attribute FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); @@ -1594,9 +1622,11 @@ FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( TArray MaterialInterfaces; TArray TriangleMaterialIndices; + const TArray& StaticMaterials = StaticMesh->GetStaticMaterials(); + // If the static mesh component is valid, get the materials via the component to account for overrides - const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); - const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); + const bool bIsStaticMeshComponentValid = (IsValid(StaticMeshComponent) && StaticMeshComponent->IsValidLowLevel()); + const int32 NumStaticMaterials = StaticMaterials.Num(); // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, // then we will set UEDefaultMaterial at the invalid index int32 UEDefaultMaterialIndex = INDEX_NONE; @@ -1606,7 +1636,7 @@ FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( MaterialInterfaces.Reserve(NumStaticMaterials); for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) { - const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; + const FStaticMaterial &MaterialInfo = StaticMaterials[MaterialIndex]; UMaterialInterface *Material = nullptr; if (bIsStaticMeshComponentValid) { @@ -1617,7 +1647,7 @@ FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( Material = MaterialInfo.MaterialInterface; } // If the Material is NULL or invalid, fallback to the default material - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) { if (!UEDefaultMaterial) { @@ -2007,33 +2037,58 @@ FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( // Send material assignments to Houdini if (NumMaterials > 0) { - // Create list of materials, one for each face. + // List of materials, one for each face. TArray TriangleMaterials; + + //Lists of material parameters TMap> ScalarMaterialParameters; TMap> VectorMaterialParameters; TMap> TextureMaterialParameters; - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - TriangleMaterials.Num(), - TriangleMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } // Delete material names. FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); - // Delete texture parameter attribute names. - for (auto & Pair : TextureMaterialParameters) + // Delete texture material parameter names + for (auto & Pair : TextureMaterialParameters) { FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); } @@ -2043,7 +2098,6 @@ FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( check(0); return false; } - } // TODO: The render mesh (LODResources) does not have face smoothing information, and the raw mesh triangle order is @@ -2084,10 +2138,10 @@ FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( // TODO: // Fetch default lightmap res from settings... int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) + if (StaticMesh->GetLightMapResolution() != GeneratedLightMapResolution) { TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); + LightMapResolutions.Add(StaticMesh->GetLightMapResolution()); HAPI_AttributeInfo AttributeInfoLightMapResolution; FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); @@ -2261,7 +2315,7 @@ FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( //--------------------------------------------------------------------------------------------------------------------- // COMPONENT AND ACTOR TAGS //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + if (IsValid(StaticMeshComponent)) { // Try to create groups for the static mesh component's tags if (StaticMeshComponent->ComponentTags.Num() > 0 @@ -2269,7 +2323,7 @@ FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) + if (IsValid(ParentActor)) { // Try to create groups for the parent Actor's tags if (ParentActor->Tags.Num() > 0 @@ -2429,16 +2483,17 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( } bool bUseComponentOverrideColors = false; + FStaticMeshRenderData* SMRenderData = StaticMesh->GetRenderData(); + // Determine if have override colors on the static mesh component, if so prefer to use those if (StaticMeshComponent && StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && - StaticMesh->RenderData && - StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) + SMRenderData && + SMRenderData->LODResources.IsValidIndex(InLODIndex)) { FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; - FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; + FStaticMeshLODResources& RenderModel = SMRenderData->LODResources[InLODIndex]; FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) @@ -2468,9 +2523,11 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( TArray MaterialInterfaces; TArray TriangleMaterialIndices; + const TArray& StaticMaterials = StaticMesh->GetStaticMaterials(); + // If the static mesh component is valid, get the materials via the component to account for overrides - const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); - const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); + const bool bIsStaticMeshComponentValid = (IsValid(StaticMeshComponent) && StaticMeshComponent->IsValidLowLevel()); + const int32 NumStaticMaterials = StaticMaterials.Num(); // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, // then we will set UEDefaultMaterial at the invalid index int32 UEDefaultMaterialIndex = INDEX_NONE; @@ -2480,7 +2537,7 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( MaterialInterfaces.Reserve(NumStaticMaterials); for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) { - const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; + const FStaticMaterial &MaterialInfo = StaticMaterials[MaterialIndex]; UMaterialInterface *Material = nullptr; if (bIsStaticMeshComponentValid) { @@ -2491,7 +2548,7 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( Material = MaterialInfo.MaterialInterface; } // If the Material is NULL or invalid, fallback to the default material - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) { if (!UEDefaultMaterial) { @@ -2703,10 +2760,10 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) { FVector4 Color = FLinearColor::White; - if (bUseComponentOverrideColors) + if (bUseComponentOverrideColors && SMRenderData) { FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshLODResources& RenderModel = StaticMesh->RenderData->LODResources[InLODIndex]; + FStaticMeshLODResources& RenderModel = SMRenderData->LODResources[InLODIndex]; FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; int32 Index = RenderModel.WedgeMap[VertexInstanceIdx]; @@ -2926,32 +2983,58 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( // Send material assignments to Houdini if (NumMaterials > 0) { - // Create list of materials, one for each face. + // List of materials, one for each face. TArray TriangleMaterials; + + //Lists of material parameters TMap> ScalarMaterialParameters; TMap> VectorMaterialParameters; TMap> TextureMaterialParameters; - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; - // Create attribute for materials and all attributes for material parameters - bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - TriangleMaterialIndices.Num(), - TriangleMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } // Delete material names. FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); - // Delete texture material parameter names. - for (auto & Pair : TextureMaterialParameters) + // Delete texture material parameter names + for (auto & Pair : TextureMaterialParameters) { FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); } @@ -2999,10 +3082,10 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( // TODO: // Fetch default lightmap res from settings... int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) + if (StaticMesh->GetLightMapResolution() != GeneratedLightMapResolution) { - TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); + TArray LightMapResolutions; + LightMapResolutions.Add(StaticMesh->GetLightMapResolution()); HAPI_AttributeInfo AttributeInfoLightMapResolution; FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); @@ -3176,7 +3259,7 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( //--------------------------------------------------------------------------------------------------------------------- // COMPONENT AND ACTOR TAGS //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + if (IsValid(StaticMeshComponent)) { // Try to create groups for the static mesh component's tags if (StaticMeshComponent->ComponentTags.Num() > 0 @@ -3184,7 +3267,7 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) + if (IsValid(ParentActor)) { // Try to create groups for the parent Actor's tags if (ParentActor->Tags.Num() > 0 @@ -3199,7 +3282,7 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( /* FString LevelPath = FString(); - if (ParentActor && !ParentActor->IsPendingKill()) + if (IsValid(ParentActor)) { if (ULevel* Level = ParentActor->GetLevel()) { @@ -3225,6 +3308,65 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( } +void +FUnrealMeshTranslator::CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray& OutStaticMeshFaceMaterials) +{ + // We need to create list of unique materials. + TArray UniqueMaterialList; + + UMaterialInterface * MaterialInterface = nullptr; + char* UniqueName = nullptr; + + UMaterialInterface * DefaultMaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + char* DefaultMaterialName = FHoudiniEngineUtils::ExtractRawString(DefaultMaterialInterface->GetPathName()); + + if (Materials.Num()) + { + // We have materials. + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); MaterialIdx++) + { + UniqueName = nullptr; + MaterialInterface = Materials[MaterialIdx]; + if (!MaterialInterface) + { + // Null material interface found, add default instead. + UniqueMaterialList.Add(DefaultMaterialName); + + // No need to collect material parameters on the default material + continue; + } + + // We found a material, get its name and material parameters + FString FullMaterialName = MaterialInterface->GetPathName(); + UniqueName = FHoudiniEngineUtils::ExtractRawString(FullMaterialName); + UniqueMaterialList.Add(UniqueName); + } + } + else + { + // We do not have any materials, add default. + UniqueMaterialList.Add(DefaultMaterialName); + } + + // TODO: Needs to be improved! + // We shouldnt be testing for each face, but only for each unique facematerial value... + for (int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx) + { + int32 FaceMaterialIdx = FaceMaterialIndices[FaceIdx]; + if (UniqueMaterialList.IsValidIndex(FaceMaterialIdx)) + { + OutStaticMeshFaceMaterials.Add(UniqueMaterialList[FaceMaterialIdx]); + } + else + { + OutStaticMeshFaceMaterials.Add(DefaultMaterialName); + } + } +} + void FUnrealMeshTranslator::CreateFaceMaterialArray( @@ -3236,7 +3378,7 @@ FUnrealMeshTranslator::CreateFaceMaterialArray( TMap> & OutTextureMaterialParameters) { // We need to create list of unique materials. - TArray< char * > UniqueMaterialList; + TArray UniqueMaterialList; UMaterialInterface * MaterialInterface = nullptr; char* UniqueName = nullptr; @@ -3336,7 +3478,7 @@ FUnrealMeshTranslator::CreateFaceMaterialArray( UTexture * CurTexture = nullptr; MaterialInterface->GetTextureParameterValue(CurTextureParam, CurTexture); - if (!CurTexture || CurTexture->IsPendingKill()) + if (!IsValid(CurTexture)) continue; FString TexturePath = CurTexture->GetPathName(); @@ -3465,7 +3607,8 @@ FUnrealMeshTranslator::CreateInputNodeForBox( InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); // Set its group name param to collision_geo_simple_box - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); const char * GroupNameStr = ""; { FString LODGroup = TEXT("collision_geo_simple_box") + FString::FromInt(ColliderIndex); @@ -3539,7 +3682,8 @@ FUnrealMeshTranslator::CreateInputNodeForSphere( InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); // Set its group name param to collision_geo_simple_box - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); const char * GroupNameStr = ""; { FString LODGroup = TEXT("collision_geo_simple_sphere") + FString::FromInt(ColliderIndex); @@ -3609,7 +3753,6 @@ FUnrealMeshTranslator::CreateInputNodeForSphyl( } // Get the transform matrix for the rotation - FRotationMatrix SphylRotMatrix(SphylRotation); // Get the Sphyl's vertices by rotating the arc NumSides+1 times. TArray Vertices; @@ -3624,8 +3767,10 @@ FUnrealMeshTranslator::CreateInputNodeForSphyl( { int32 VIx = (NumRings + 1)*SideIdx + VertIdx; - FVector CurPosition = SphylCenter + ArcRot.TransformPosition(ArcVertices[VertIdx]); - CurPosition = SphylRotMatrix.TransformPosition(CurPosition); + FVector ArcVertex = ArcRot.TransformPosition(ArcVertices[VertIdx]); + ArcVertex = SphylRotation.Quaternion() * ArcVertex; + + FVector CurPosition = SphylCenter + ArcVertex; // Convert the UE4 position to Houdini Vertices[VIx * 3 + 0] = CurPosition.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; @@ -3674,7 +3819,8 @@ FUnrealMeshTranslator::CreateInputNodeForSphyl( InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); // Set its group name param to collision_geo_simple_box - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); const char * GroupNameStr = ""; { FString LODGroup = TEXT("collision_geo_simple_capsule") + FString::FromInt(ColliderIndex); @@ -3702,7 +3848,12 @@ FUnrealMeshTranslator::CreateInputNodeForConvex( TArray Vertices; TArray Indices; -/* + FTransform ConvexTransform = ConvexCollider.GetTransform(); + + FVector TransformOffset = ConvexTransform.GetLocation(); + FVector ScaleOffset = ConvexTransform.GetScale3D(); + FQuat RotationOffset = ConvexTransform.GetRotation(); + #if PHYSICS_INTERFACE_PHYSX if (ConvexCollider.GetConvexMesh() || ConvexCollider.GetMirroredConvexMesh()) #elif WITH_CHAOS @@ -3711,14 +3862,17 @@ FUnrealMeshTranslator::CreateInputNodeForConvex( #else if(false) #endif -*/ - if (ConvexCollider.GetConvexMesh() || ConvexCollider.GetMirroredConvexMesh()) { // Get the convex colliders vertices and indices from the mesh TArray VertexBuffer; TArray IndexBuffer; ConvexCollider.AddCachedSolidConvexGeom(VertexBuffer, IndexBuffer, FColor::White); + for (int32 i = 0; i < VertexBuffer.Num(); i++) + { + VertexBuffer[i].Position = TransformOffset + (RotationOffset * (ScaleOffset * VertexBuffer[i].Position)); + } + Vertices.SetNum(VertexBuffer.Num() * 3); int32 CurIndex = 0; for (auto& CurVert : VertexBuffer) @@ -3728,7 +3882,7 @@ FUnrealMeshTranslator::CreateInputNodeForConvex( Vertices[CurIndex * 3 + 2] = CurVert.Position.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; CurIndex++; } - + Indices.SetNum(IndexBuffer.Num()); for (int Idx = 0; (Idx + 2) < IndexBuffer.Num(); Idx += 3) { @@ -3740,11 +3894,20 @@ FUnrealMeshTranslator::CreateInputNodeForConvex( } else { + // Need to copy vertices because we plan on modifying it by Quaternion/Vector multiplication + TArray VertexBuffer; + VertexBuffer.SetNum(ConvexCollider.VertexData.Num()); + + for (int32 Idx = 0; Idx < ConvexCollider.VertexData.Num(); Idx++) + { + VertexBuffer[Idx] = TransformOffset + (RotationOffset * (ScaleOffset * ConvexCollider.VertexData[Idx])); + } + int32 NumVert = ConvexCollider.VertexData.Num(); Vertices.SetNum(NumVert * 3); //Indices.SetNum(NumVert); int32 CurIndex = 0; - for (auto& CurVert : ConvexCollider.VertexData) + for (auto& CurVert : VertexBuffer) { Vertices[CurIndex * 3 + 0] = CurVert.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; Vertices[CurIndex * 3 + 1] = CurVert.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; @@ -3797,7 +3960,8 @@ FUnrealMeshTranslator::CreateInputNodeForConvex( InParentNodeID, "groupcreate", GroupNodeName, false, &GroupNodeId), false); // Set its group name param to collision_geo_simple_ucx - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); const char * GroupNameStr = ""; { FString LODGroup = TEXT("collision_geo_simple_ucx") + FString::FromInt(ColliderIndex); diff --git a/Source/HoudiniEngine/Private/UnrealMeshTranslator.h b/Source/HoudiniEngine/Private/UnrealMeshTranslator.h index e39998d1e..b524ea308 100644 --- a/Source/HoudiniEngine/Private/UnrealMeshTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealMeshTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -126,8 +126,20 @@ struct HOUDINIENGINE_API FUnrealMeshTranslator const HAPI_NodeId& InParentNodeId, HAPI_NodeId& OutSocketsNodeId); - // Create helper array of material names, used for marshalling static mesh's materials. + + // Helper function to extract the array of material names used by a given mesh + // This is used for marshalling static mesh's materials. + // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() + static void CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray& OutStaticMeshFaceMaterials); + + // Helper function to extract the array of material names used by a given mesh + // Also extracts all scalar/vector/texture parameter in the materials + // This is used for marshalling static mesh's materials. // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() + // The texture parameter array also needs to be cleared. static void CreateFaceMaterialArray( const TArray& Materials, const TArray& FaceMaterialIndices, diff --git a/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp b/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp index cc6f857bb..4a2e2a0d8 100644 --- a/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,6 +23,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + #include "UnrealSplineTranslator.h" #include "HoudiniEngine.h" @@ -37,7 +38,7 @@ bool FUnrealSplineTranslator::CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId& CreatedInputNodeId, const FString& NodeName) { - if (!SplineComponent || SplineComponent->IsPendingKill()) + if (!IsValid(SplineComponent)) return false; int32 NumberOfControlPoints = SplineComponent->GetNumberOfSplinePoints(); @@ -93,7 +94,7 @@ FUnrealSplineTranslator::CreateInputNodeForSplineComponent(USplineComponent* Spl // Add the parent actor's tag if it has any AActor* ParentActor = SplineComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) + if (IsValid(ParentActor)) { if (FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, ParentActor->Tags)) NeedToCommit = true; diff --git a/Source/HoudiniEngine/Private/UnrealSplineTranslator.h b/Source/HoudiniEngine/Private/UnrealSplineTranslator.h index 826bc9ba2..e6d302233 100644 --- a/Source/HoudiniEngine/Private/UnrealSplineTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealSplineTranslator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI.h b/Source/HoudiniEngine/Public/HAPI/HAPI.h index 7391fae93..69b56c9a6 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI.h @@ -389,6 +389,11 @@ HAPI_DECL HAPI_GetServerEnvString( const HAPI_Session * session, /// @brief Provides the number of environment variables that are in /// the server environment's process /// +/// Note that ::HAPI_GetServerEnvVarList() should be called directly after +/// this method, otherwise there is the possibility that the environment +/// variable count of the server will have changed by the time that +/// ::HAPI_GetServerEnvVarList() is called. +/// /// @ingroup Environment /// /// @param[in] session @@ -418,7 +423,8 @@ HAPI_DECL HAPI_GetServerEnvVarCount( const HAPI_Session * session, /// /// @param[in] start /// First index of range. Must be at least @c 0 and at most -/// @c env_count returned by ::HAPI_GetServerEnvVarCount() +/// @c env_count - 1 where @c env_count is the count returned by +/// ::HAPI_GetServerEnvVarCount() /// /// /// @@ -1019,7 +1025,7 @@ HAPI_DECL HAPI_GetString( const HAPI_Session * session, /// HAPI_DECL HAPI_SetCustomString( const HAPI_Session * session, const char * string_value, - int * handle_value ); + HAPI_StringHandle * handle_value ); /// @brief Removes the specified string from the server /// and invalidates the handle @@ -1036,7 +1042,7 @@ HAPI_DECL HAPI_SetCustomString( const HAPI_Session * session, /// Handle of the string that was added /// HAPI_DECL HAPI_RemoveCustomString( const HAPI_Session * session, - const int string_handle ); + const HAPI_StringHandle string_handle ); /// @brief Gives back the length of the buffer needed to hold /// all the values null-separated for the given string @@ -1205,6 +1211,36 @@ HAPI_DECL HAPI_SetTimelineOptions( const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options ); +/// @brief Gets the global compositor options. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] compositor_options +/// The compositor options struct. +/// +HAPI_DECL HAPI_GetCompositorOptions( + const HAPI_Session * session, + HAPI_CompositorOptions * compositor_options); + +/// @brief Sets the global compositor options. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] compositor_options +/// The compositor options. +/// +HAPI_DECL HAPI_SetCompositorOptions( + const HAPI_Session * session, + const HAPI_CompositorOptions * compositor_options); + /// @defgroup Assets /// Functions for managing asset libraries @@ -4250,6 +4286,63 @@ HAPI_DECL HAPI_GetDisplayGeoInfo( const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info ); +/// @brief A helper method that gets the number of main geometry outputs inside +/// an Object node or SOP node. If the node is an Object node, this +/// method will return the cumulative number of geometry outputs for all +/// geometry nodes that it contains. When searching for output geometry, +/// this method will only consider subnetworks that have their display +/// flag enabled. +/// +/// This method must be called before HAPI_GetOutputGeoInfos() is +/// called. +/// +/// @ingroup GeometryGetters +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id of the Object or SOP node to get the geometry +/// output count of. +/// +/// @param[out] count +/// The number of geometry (SOP) outputs. +HAPI_DECL HAPI_GetOutputGeoCount( const HAPI_Session* session, + HAPI_NodeId node_id, + int* count); + +/// @brief Gets the geometry info structs (::HAPI_GeoInfo) for a node's +/// main geometry outputs. This method can only be called after +/// HAPI_GetOutputGeoCount() has been called with the same node id. +/// +/// @ingroup GeometryGetters +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id of the Object or SOP node to get the output +/// geometry info structs (::HAPI_GeoInfo) for. +/// +/// @param[out] geo_infos_array +/// Output array where the output geometry info structs will be +/// stored. The size of the array must match the count argument +/// returned by the HAPI_GetOutputGeoCount() method. +/// +/// @param[in] count +/// Sanity check count. The count must be equal to the count +/// returned by the HAPI_GetOutputGeoCount() method. +HAPI_DECL HAPI_GetOutputGeoInfos( const HAPI_Session* session, + HAPI_NodeId node_id, + HAPI_GeoInfo* geo_infos_array, + int count ); + /// @brief Get the geometry info struct (::HAPI_GeoInfo) on a SOP node. /// /// @ingroup GeometryGetters @@ -4294,6 +4387,36 @@ HAPI_DECL HAPI_GetPartInfo( const HAPI_Session * session, HAPI_PartId part_id, HAPI_PartInfo * part_info ); + +/// @brief Gets the number of edges that belong to an edge group on a geometry +/// part. +/// +/// @ingroup GeometryGetters +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] group_name +/// The name of the edge group. +/// +/// @param[out] edge_count +/// The number of edges that belong to the group. +/// +HAPI_DECL HAPI_GetEdgeCountOfEdgeGroup( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * group_name, + int * edge_count ); + /// @brief Get the array of faces where the nth integer in the array is /// the number of vertices the nth face has. /// @@ -4372,7 +4495,7 @@ HAPI_DECL HAPI_GetVertexList( const HAPI_Session * session, /// @brief Get the attribute info struct for the attribute specified by name. /// -/// @ingroup GeometryGetters +/// @ingroup Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4408,7 +4531,7 @@ HAPI_DECL HAPI_GetAttributeInfo( const HAPI_Session * session, /// name string handles are only valid until the next time this /// function is called. /// -/// @ingroup GeometryGetters +/// @ingroup Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4445,7 +4568,7 @@ HAPI_DECL HAPI_GetAttributeNames( const HAPI_Session * session, /// @brief Get attribute integer data. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4505,7 +4628,7 @@ HAPI_DECL HAPI_GetAttributeIntData( const HAPI_Session * session, /// Therefore the array values are returned as a flat array, with /// another sizes array containing the lengths of each array entry. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4563,9 +4686,9 @@ HAPI_DECL HAPI_GetAttributeIntArrayData( const HAPI_Session * session, int * sizes_fixed_array, int start, int sizes_fixed_length ); -/// @brief Get attribute 64-bit integer data. +/// @brief Get attribute unsigned 8-bit integer data. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4596,7 +4719,7 @@ HAPI_DECL HAPI_GetAttributeIntArrayData( const HAPI_Session * session, /// @c stride. /// /// @param[out] data_array -/// An 64-bit integer array at least the size of +/// An unsigned 8-bit integer array at least the size of /// length * ::HAPI_AttributeInfo::tupleSize. /// /// @param[in] start @@ -4611,21 +4734,21 @@ HAPI_DECL HAPI_GetAttributeIntArrayData( const HAPI_Session * session, /// do nothing and return ::HAPI_RESULT_SUCCESS. /// /// -HAPI_DECL HAPI_GetAttributeInt64Data( const HAPI_Session * session, +HAPI_DECL HAPI_GetAttributeUInt8Data( const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, - HAPI_Int64 * data_array, + HAPI_UInt8 * data_array, int start, int length ); -/// @brief Get array attribute 64-bit integer data. +/// @brief Get array attribute unsigned 8-bit integer data. /// Each entry in an array attribute can have varying array lengths. /// Therefore the array values are returned as a flat array, with /// another sizes array containing the lengths of each array entry. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4649,7 +4772,7 @@ HAPI_DECL HAPI_GetAttributeInt64Data( const HAPI_Session * session, /// returned by ::HAPI_GetAttributeInfo(). /// /// @param[out] data_fixed_array -/// An 64-bit integer array at least the size of +/// An unsigned 8-bit integer array at least the size of /// ::HAPI_AttributeInfo::totalArrayElements. /// /// @param[in] data_fixed_length @@ -4672,19 +4795,19 @@ HAPI_DECL HAPI_GetAttributeInt64Data( const HAPI_Session * session, /// do nothing and return ::HAPI_RESULT_SUCCESS. /// /// -HAPI_DECL HAPI_GetAttributeInt64ArrayData( const HAPI_Session * session, +HAPI_DECL HAPI_GetAttributeUInt8ArrayData( const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, - HAPI_Int64 * data_fixed_array, + HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length ); -/// @brief Get attribute float data. +/// @brief Get attribute 8-bit integer data. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4715,7 +4838,7 @@ HAPI_DECL HAPI_GetAttributeInt64ArrayData( const HAPI_Session * session, /// @c stride. /// /// @param[out] data_array -/// An float array at least the size of +/// An 8-bit integer array at least the size of /// length * ::HAPI_AttributeInfo::tupleSize. /// /// @param[in] start @@ -4730,21 +4853,21 @@ HAPI_DECL HAPI_GetAttributeInt64ArrayData( const HAPI_Session * session, /// do nothing and return ::HAPI_RESULT_SUCCESS. /// /// -HAPI_DECL HAPI_GetAttributeFloatData( const HAPI_Session * session, +HAPI_DECL HAPI_GetAttributeInt8Data( const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, - float * data_array, + HAPI_Int8 * data_array, int start, int length ); -/// @brief Get array attribute float data. +/// @brief Get array attribute 8-bit integer data. /// Each entry in an array attribute can have varying array lengths. /// Therefore the array values are returned as a flat array, with /// another sizes array containing the lengths of each array entry. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4768,7 +4891,7 @@ HAPI_DECL HAPI_GetAttributeFloatData( const HAPI_Session * session, /// returned by ::HAPI_GetAttributeInfo(). /// /// @param[out] data_fixed_array -/// An float array at least the size of +/// An 8-bit integer array at least the size of /// ::HAPI_AttributeInfo::totalArrayElements. /// /// @param[in] data_fixed_length @@ -4777,7 +4900,7 @@ HAPI_DECL HAPI_GetAttributeFloatData( const HAPI_Session * session, /// /// @param[out] sizes_fixed_array /// An integer array at least the size of -/// sizes_fixed_length to hold the size of each entry. +/// sizes_fixed_length to hold the size of each entry. /// /// @param[in] start /// First index of range. Must be at least 0 and at @@ -4791,19 +4914,19 @@ HAPI_DECL HAPI_GetAttributeFloatData( const HAPI_Session * session, /// do nothing and return ::HAPI_RESULT_SUCCESS. /// /// -HAPI_DECL HAPI_GetAttributeFloatArrayData( const HAPI_Session * session, +HAPI_DECL HAPI_GetAttributeInt8ArrayData( const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, - float * data_fixed_array, + HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length ); -/// @brief Get 64-bit attribute float data. +/// @brief Get attribute 16-bit integer data. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4834,7 +4957,7 @@ HAPI_DECL HAPI_GetAttributeFloatArrayData( const HAPI_Session * session, /// @c stride. /// /// @param[out] data_array -/// An 64-bit float array at least the size of +/// An 16-bit integer array at least the size of /// length * ::HAPI_AttributeInfo::tupleSize. /// /// @param[in] start @@ -4849,21 +4972,21 @@ HAPI_DECL HAPI_GetAttributeFloatArrayData( const HAPI_Session * session, /// do nothing and return ::HAPI_RESULT_SUCCESS. /// /// -HAPI_DECL HAPI_GetAttributeFloat64Data( const HAPI_Session * session, - HAPI_NodeId node_id, - HAPI_PartId part_id, - const char * name, - HAPI_AttributeInfo * attr_info, - int stride, - double * data_array, - int start, int length ); +HAPI_DECL HAPI_GetAttributeInt16Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + int stride, + HAPI_Int16 * data_array, + int start, int length ); -/// @brief Get array attribute 64-bit float data. +/// @brief Get array attribute 16-bit integer data. /// Each entry in an array attribute can have varying array lengths. /// Therefore the array values are returned as a flat array, with /// another sizes array containing the lengths of each array entry. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4881,13 +5004,13 @@ HAPI_DECL HAPI_GetAttributeFloat64Data( const HAPI_Session * session, /// Attribute name. /// /// @param[in] attr_info -/// ::HAPI_AttributeInfo used as input for the. -/// totalArrayElements. Also contains some sanity checks like +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like /// data type. Generally should be the same struct /// returned by ::HAPI_GetAttributeInfo(). /// /// @param[out] data_fixed_array -/// An 64-bit float array at least the size of +/// An 16-bit integer array at least the size of /// ::HAPI_AttributeInfo::totalArrayElements. /// /// @param[in] data_fixed_length @@ -4910,21 +5033,19 @@ HAPI_DECL HAPI_GetAttributeFloat64Data( const HAPI_Session * session, /// do nothing and return ::HAPI_RESULT_SUCCESS. /// /// -HAPI_DECL HAPI_GetAttributeFloat64ArrayData( const HAPI_Session * session, - HAPI_NodeId node_id, - HAPI_PartId part_id, - const char * name, - HAPI_AttributeInfo * attr_info, - double * data_fixed_array, - int data_fixed_length, - int * sizes_fixed_array, - int start, int sizes_fixed_length ); +HAPI_DECL HAPI_GetAttributeInt16ArrayData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + HAPI_Int16 * data_fixed_array, + int data_fixed_length, + int * sizes_fixed_array, + int start, int sizes_fixed_length ); -/// @brief Get attribute string data. Note that the string handles -/// returned are only valid until the next time this function -/// is called. +/// @brief Get attribute 64-bit integer data. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4947,8 +5068,15 @@ HAPI_DECL HAPI_GetAttributeFloat64ArrayData( const HAPI_Session * session, /// data type. Generally should be the same struct /// returned by ::HAPI_GetAttributeInfo(). /// +/// @param[in] stride +/// Specifies how many items to skip over for each element. +/// With a stride of -1, the stride will be set to +/// @c attr_info->tuple_size. Otherwise, the stride will be +/// set to the maximum of @c attr_info->tuple_size and +/// @c stride. +/// /// @param[out] data_array -/// An ::HAPI_StringHandle array at least the size of +/// An 64-bit integer array at least the size of /// length * ::HAPI_AttributeInfo::tupleSize. /// /// @param[in] start @@ -4963,22 +5091,21 @@ HAPI_DECL HAPI_GetAttributeFloat64ArrayData( const HAPI_Session * session, /// do nothing and return ::HAPI_RESULT_SUCCESS. /// /// -HAPI_DECL HAPI_GetAttributeStringData( const HAPI_Session * session, - HAPI_NodeId node_id, - HAPI_PartId part_id, - const char * name, - HAPI_AttributeInfo * attr_info, - HAPI_StringHandle * data_array, - int start, int length ); +HAPI_DECL HAPI_GetAttributeInt64Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + int stride, + HAPI_Int64 * data_array, + int start, int length ); -/// @brief Get array attribute string data. +/// @brief Get array attribute 64-bit integer data. /// Each entry in an array attribute can have varying array lengths. /// Therefore the array values are returned as a flat array, with -/// another sizes array containing the lengths of each array entry. -/// Note that the string handles returned are only valid until -/// the next time this function is called. +/// another sizes array containing the lengths of each array entry. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4996,13 +5123,13 @@ HAPI_DECL HAPI_GetAttributeStringData( const HAPI_Session * session, /// Attribute name. /// /// @param[in] attr_info -/// ::HAPI_AttributeInfo used as input for the. -/// totalArrayElements. Also contains some sanity checks like +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like /// data type. Generally should be the same struct /// returned by ::HAPI_GetAttributeInfo(). /// /// @param[out] data_fixed_array -/// An ::HAPI_StringHandle array at least the size of +/// An 64-bit integer array at least the size of /// ::HAPI_AttributeInfo::totalArrayElements. /// /// @param[in] data_fixed_length @@ -5025,23 +5152,19 @@ HAPI_DECL HAPI_GetAttributeStringData( const HAPI_Session * session, /// do nothing and return ::HAPI_RESULT_SUCCESS. /// /// -HAPI_DECL HAPI_GetAttributeStringArrayData( const HAPI_Session * session, - HAPI_NodeId node_id, - HAPI_PartId part_id, - const char * name, - HAPI_AttributeInfo * attr_info, - HAPI_StringHandle * data_fixed_array, - int data_fixed_length, - int * sizes_fixed_array, - int start, int sizes_fixed_length ); +HAPI_DECL HAPI_GetAttributeInt64ArrayData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + HAPI_Int64 * data_fixed_array, + int data_fixed_length, + int * sizes_fixed_array, + int start, int sizes_fixed_length ); -/// @brief Get group names for an entire geo. Please note that this -/// function is NOT per-part, but it is per-geo. The companion -/// function ::HAPI_GetGroupMembership() IS per-part. Also keep -/// in mind that the name string handles are only -/// valid until the next time this function is called. +/// @brief Get attribute float data. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5052,38 +5175,395 @@ HAPI_DECL HAPI_GetAttributeStringArrayData( const HAPI_Session * session, /// @param[in] node_id /// The node id. /// -/// @param[in] group_type -/// The group type. -/// -/// @param[out] group_names_array -/// The array of names to be filled. Should be the size -/// given by ::HAPI_GeoInfo_GetGroupCountByType() with -/// @p group_type and the ::HAPI_GeoInfo of @p geo_id. -/// @note These string handles are only valid until the -/// next call to ::HAPI_GetGroupNames(). +/// @param[in] part_id +/// The part id. /// -/// @param[in] group_count -/// Sanity check. Should be less than or equal to the size -/// of @p group_names. +/// @param[in] name +/// Attribute name. /// -HAPI_DECL HAPI_GetGroupNames( const HAPI_Session * session, - HAPI_NodeId node_id, - HAPI_GroupType group_type, - HAPI_StringHandle * group_names_array, - int group_count ); - -/// @brief Get group membership. +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). /// -/// @ingroup GeometryGetters +/// @param[in] stride +/// Specifies how many items to skip over for each element. +/// With a stride of -1, the stride will be set to +/// @c attr_info->tuple_size. Otherwise, the stride will be +/// set to the maximum of @c attr_info->tuple_size and +/// @c stride. /// -/// @param[in] session -/// The session of Houdini you are interacting with. -/// See @ref HAPI_Sessions for more on sessions. -/// Pass NULL to just use the default in-process session. -/// +/// @param[out] data_array +/// An float array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. /// -/// @param[in] node_id -/// The node id. +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeFloatData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + int stride, + float * data_array, + int start, int length ); + +/// @brief Get array attribute float data. +/// Each entry in an array attribute can have varying array lengths. +/// Therefore the array values are returned as a flat array, with +/// another sizes array containing the lengths of each array entry. +/// +/// @ingroup GeometryGetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[out] data_fixed_array +/// An float array at least the size of +/// ::HAPI_AttributeInfo::totalArrayElements. +/// +/// @param[in] data_fixed_length +/// Must be ::HAPI_AttributeInfo::totalArrayElements. +/// +/// +/// @param[out] sizes_fixed_array +/// An integer array at least the size of +/// sizes_fixed_length to hold the size of each entry. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] sizes_fixed_length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeFloatArrayData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + float * data_fixed_array, + int data_fixed_length, + int * sizes_fixed_array, + int start, int sizes_fixed_length ); + +/// @brief Get 64-bit attribute float data. +/// +/// @ingroup GeometryGetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] stride +/// Specifies how many items to skip over for each element. +/// With a stride of -1, the stride will be set to +/// @c attr_info->tuple_size. Otherwise, the stride will be +/// set to the maximum of @c attr_info->tuple_size and +/// @c stride. +/// +/// @param[out] data_array +/// An 64-bit float array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeFloat64Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + int stride, + double * data_array, + int start, int length ); + +/// @brief Get array attribute 64-bit float data. +/// Each entry in an array attribute can have varying array lengths. +/// Therefore the array values are returned as a flat array, with +/// another sizes array containing the lengths of each array entry. +/// +/// @ingroup GeometryGetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for the. +/// totalArrayElements. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[out] data_fixed_array +/// An 64-bit float array at least the size of +/// ::HAPI_AttributeInfo::totalArrayElements. +/// +/// @param[in] data_fixed_length +/// Must be ::HAPI_AttributeInfo::totalArrayElements. +/// +/// +/// @param[out] sizes_fixed_array +/// An integer array at least the size of +/// sizes_fixed_length to hold the size of each entry. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] sizes_fixed_length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeFloat64ArrayData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + double * data_fixed_array, + int data_fixed_length, + int * sizes_fixed_array, + int start, int sizes_fixed_length ); + +/// @brief Get attribute string data. Note that the string handles +/// returned are only valid until the next time this function +/// is called. +/// +/// @ingroup GeometryGetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[out] data_array +/// An ::HAPI_StringHandle array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeStringData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + HAPI_StringHandle * data_array, + int start, int length ); + +/// @brief Get array attribute string data. +/// Each entry in an array attribute can have varying array lengths. +/// Therefore the array values are returned as a flat array, with +/// another sizes array containing the lengths of each array entry. +/// Note that the string handles returned are only valid until +/// the next time this function is called. +/// +/// @ingroup GeometryGetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for the. +/// totalArrayElements. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[out] data_fixed_array +/// An ::HAPI_StringHandle array at least the size of +/// ::HAPI_AttributeInfo::totalArrayElements. +/// +/// @param[in] data_fixed_length +/// Must be ::HAPI_AttributeInfo::totalArrayElements. +/// +/// +/// @param[out] sizes_fixed_array +/// An integer array at least the size of +/// sizes_fixed_length to hold the size of each entry. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] sizes_fixed_length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeStringArrayData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + HAPI_StringHandle * data_fixed_array, + int data_fixed_length, + int * sizes_fixed_array, + int start, int sizes_fixed_length ); + +/// @brief Get group names for an entire geo. Please note that this +/// function is NOT per-part, but it is per-geo. The companion +/// function ::HAPI_GetGroupMembership() IS per-part. Also keep +/// in mind that the name string handles are only +/// valid until the next time this function is called. +/// +/// @ingroup GeometryGetters +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] group_type +/// The group type. +/// +/// @param[out] group_names_array +/// The array of names to be filled. Should be the size +/// given by ::HAPI_GeoInfo_GetGroupCountByType() with +/// @p group_type and the ::HAPI_GeoInfo of @p geo_id. +/// @note These string handles are only valid until the +/// next call to ::HAPI_GetGroupNames(). +/// +/// @param[in] group_count +/// Sanity check. Should be less than or equal to the size +/// of @p group_names. +/// +HAPI_DECL HAPI_GetGroupNames( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_GroupType group_type, + HAPI_StringHandle * group_names_array, + int group_count ); + +/// @brief Get group membership. +/// +/// @ingroup GeometryGetters +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. /// /// @param[in] part_id /// The part id. @@ -5097,23 +5577,33 @@ HAPI_DECL HAPI_GetGroupNames( const HAPI_Session * session, /// @param[out] membership_array_all_equal /// (optional) Quick way to determine if all items are in /// the given group or all items our not in the group. -/// You can just pass NULL here if not interested. +/// If you do not need this information or you are getting edge +/// group information, you can just pass NULL for this argument. /// /// @param[out] membership_array /// Array of ints that represent the membership of this -/// group. Should be the size given by -/// ::HAPI_PartInfo_GetElementCountByGroupType() with -/// @p group_type and the ::HAPI_PartInfo of @p part_id. +/// group. When getting membership information for a point or +/// primitive group, the size of the array should be the size +/// given by ::HAPI_PartInfo_GetElementCountByGroupType() with +/// @p group_type and the ::HAPI_PartInfo of @p part_id. When +/// retrieving the edges belonging to an edge group, the +/// membership array will be filled with point numbers that +/// comprise the edges of the edge group. Each edge is specified +/// by two points, which means that the size of the array should +/// be the size given by ::HAPI_GetEdgeCountOfEdgeGroup() * 2. /// /// @param[in] start /// Start offset into the membership array. Must be -/// less than ::HAPI_PartInfo_GetElementCountByGroupType(). +/// less than ::HAPI_PartInfo_GetElementCountByGroupType() when +/// it is a point or primitive group. When getting the +/// membership information for an edge group, this argument must +/// be less than the size returned by +/// ::HAPI_GetEdgeCountOfEdgeGroup() * 2 - 1. /// /// /// @param[in] length /// Should be less than or equal to the size /// of @p membership_array. -/// /// HAPI_DECL HAPI_GetGroupMembership( const HAPI_Session * session, HAPI_NodeId node_id, @@ -5433,7 +5923,7 @@ HAPI_DECL HAPI_SetVertexList( const HAPI_Session * session, /// @brief Add an attribute. /// -/// @ingroup GeometrySetters +/// @ingroup GeometrySetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5458,9 +5948,10 @@ HAPI_DECL HAPI_AddAttribute( const HAPI_Session * session, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info ); + /// @brief Delete an attribute from an input geo /// -/// @ingroup GeometrySetters +/// @ingroup GeometrySetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5488,7 +5979,7 @@ HAPI_DECL HAPI_DeleteAttribute( const HAPI_Session * session, /// @brief Set attribute integer data. /// -/// @ingroup GeometrySetters +/// @ingroup GeometrySetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5533,9 +6024,150 @@ HAPI_DECL HAPI_SetAttributeIntData( const HAPI_Session * session, const int * data_array, int start, int length ); +/// @brief Set unsigned 8-bit attribute integer data. +/// +/// @ingroup GeometrySetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] data_array +/// An unsigned 8-bit integer array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// +/// +HAPI_DECL HAPI_SetAttributeUInt8Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + const HAPI_AttributeInfo * attr_info, + const HAPI_UInt8 * data_array, + int start, int length ); + +/// @brief Set 8-bit attribute integer data. +/// +/// @ingroup GeometrySetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] data_array +/// An 8-bit integer array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// +/// +HAPI_DECL HAPI_SetAttributeInt8Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + const HAPI_AttributeInfo * attr_info, + const HAPI_Int8 * data_array, + int start, int length ); + +/// @brief Set 16-bit attribute integer data. +/// +/// @ingroup GeometrySetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] data_array +/// An 16-bit integer array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// +/// +HAPI_DECL HAPI_SetAttributeInt16Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + const HAPI_AttributeInfo * attr_info, + const HAPI_Int16 * data_array, + int start, int length ); + /// @brief Set 64-bit attribute integer data. /// -/// @ingroup GeometrySetters +/// @ingroup GeometrySetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5582,7 +6214,7 @@ HAPI_DECL HAPI_SetAttributeInt64Data( const HAPI_Session * session, /// @brief Set attribute float data. /// -/// @ingroup GeometrySetters +/// @ingroup GeometrySetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5629,7 +6261,7 @@ HAPI_DECL HAPI_SetAttributeFloatData( const HAPI_Session * session, /// @brief Set 64-bit attribute float data. /// -/// @ingroup GeometrySetters +/// @ingroup GeometrySetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5676,7 +6308,7 @@ HAPI_DECL HAPI_SetAttributeFloat64Data( const HAPI_Session * session, /// @brief Set attribute string data. /// -/// @ingroup GeometrySetters +/// @ingroup GeometrySetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5813,7 +6445,10 @@ HAPI_DECL HAPI_DeleteGroup( const HAPI_Session * session, /// /// @param[in] length /// Should be less than or equal to the size -/// of @p membership_array. +/// of @p membership_array. When setting edge group membership, +/// this parameter should be set to the number of points (which +/// are used to implictly define the edges), not to the number +/// edges in the group. /// /// HAPI_DECL HAPI_SetGroupMembership( const HAPI_Session * session, diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h index 4869521bf..bafb29ed2 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h @@ -1,4 +1,4 @@ -/* +/* * PROPRIETARY INFORMATION. This software is proprietary to * Side Effects Software Inc., and is not to be reproduced, * transmitted, or disclosed in any way without written permission. @@ -110,9 +110,31 @@ typedef char HAPI_Bool; #endif // __cplusplus -// 64-bit Integers -typedef long long HAPI_Int64; -HAPI_STATIC_ASSERT( sizeof( HAPI_Int64 ) == 8, unsupported_size_of_long ); +// x-bit Integers +// Thrift doesn't support unsigned integers, so we cast it as a 16-bit int, but only +// for automated code generation +#ifdef HAPI_AUTOGEN + typedef signed char int8_t; + typedef short int16_t; + typedef long long int64_t; + typedef short HAPI_UInt8; // 16-bit type for thrift +#else + #include + #ifdef HAPI_THRIFT_ABI + typedef int16_t HAPI_UInt8; + #else + typedef uint8_t HAPI_UInt8; + HAPI_STATIC_ASSERT(sizeof(HAPI_UInt8) == 1, unsupported_size_of_uint8); + #endif +#endif + +typedef int8_t HAPI_Int8; +HAPI_STATIC_ASSERT(sizeof(HAPI_Int8) == 1, unsupported_size_of_int8); +typedef int16_t HAPI_Int16; +HAPI_STATIC_ASSERT(sizeof(HAPI_Int16) == 2, unsupported_size_of_int16); +typedef int64_t HAPI_Int64; +HAPI_STATIC_ASSERT(sizeof(HAPI_Int64) == 8, unsupported_size_of_long); + // The process id has to be uint on Windows and int on any other platform. #if ( defined _WIN32 || defined WIN32 ) @@ -162,6 +184,7 @@ enum HAPI_License HAPI_LICENSE_HOUDINI_FX, HAPI_LICENSE_HOUDINI_ENGINE_INDIE, HAPI_LICENSE_HOUDINI_INDIE, + HAPI_LICENSE_HOUDINI_ENGINE_UNITY_UNREAL, HAPI_LICENSE_MAX }; HAPI_C_ENUM_TYPEDEF( HAPI_License ) @@ -500,8 +523,9 @@ enum HAPI_NodeFlags /// TOP Node Specific Flags /// All TOP nodes except schedulers - HAPI_NODEFLAGS_TOP_NONSCHEDULER = 1 << 13 + HAPI_NODEFLAGS_TOP_NONSCHEDULER = 1 << 13, + HAPI_NODEFLAGS_NON_BYPASS = 1 << 14 /// Nodes that are not bypassed }; HAPI_C_ENUM_TYPEDEF( HAPI_NodeFlags ) typedef int HAPI_NodeFlagsBits; @@ -511,6 +535,7 @@ enum HAPI_GroupType HAPI_GROUPTYPE_INVALID = -1, HAPI_GROUPTYPE_POINT, HAPI_GROUPTYPE_PRIM, + HAPI_GROUPTYPE_EDGE, HAPI_GROUPTYPE_MAX }; HAPI_C_ENUM_TYPEDEF( HAPI_GroupType ) @@ -565,6 +590,9 @@ enum HAPI_StorageType HAPI_STORAGETYPE_FLOAT, HAPI_STORAGETYPE_FLOAT64, HAPI_STORAGETYPE_STRING, + HAPI_STORAGETYPE_UINT8, + HAPI_STORAGETYPE_INT8, + HAPI_STORAGETYPE_INT16, HAPI_STORAGETYPE_MAX }; HAPI_C_ENUM_TYPEDEF( HAPI_StorageType ) @@ -839,65 +867,101 @@ HAPI_C_ENUM_TYPEDEF( HAPI_PDG_State ) /// Used with PDG functions enum HAPI_PDG_EventType { + /// An empty, undefined event. Should be ignored. HAPI_PDG_EVENT_NULL, + /// Sent when a new work item is added by a node HAPI_PDG_EVENT_WORKITEM_ADD, + /// Sent when a work item is deleted from a node HAPI_PDG_EVENT_WORKITEM_REMOVE, + /// Sent when a work item's state changes HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE, + /// Sent when a work item has a dependency added HAPI_PDG_EVENT_WORKITEM_ADD_DEP, + /// Sent when a dependency is removed from a work item HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP, + /// Sent from dynamic work items that generate from a cooked item HAPI_PDG_EVENT_WORKITEM_ADD_PARENT, + /// Sent when the parent item for a work item is deleted HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT, + /// A node event that indicates that node is about to have all its work items cleared HAPI_PDG_EVENT_NODE_CLEAR, + /// Sent when an error is issued by the node HAPI_PDG_EVENT_COOK_ERROR, + /// Sent when an warning is issued by the node HAPI_PDG_EVENT_COOK_WARNING, + /// Sent for each node in the graph, when a cook completes HAPI_PDG_EVENT_COOK_COMPLETE, + /// A node event indicating that one more items in the node will be dirtied HAPI_PDG_EVENT_DIRTY_START, + /// A node event indicating that the node has finished dirtying items HAPI_PDG_EVENT_DIRTY_STOP, + /// A event indicating that the entire graph is about to be dirtied HAPI_PDG_EVENT_DIRTY_ALL, + /// A work item event that indicates the item has been selected in the TOPs UI HAPI_PDG_EVENT_UI_SELECT, + /// Sent when a new node is created HAPI_PDG_EVENT_NODE_CREATE, + /// Sent when a node was removed from the graph HAPI_PDG_EVENT_NODE_REMOVE, + /// Sent when a node was renamed HAPI_PDG_EVENT_NODE_RENAME, + /// Sent when a node was connected to another node HAPI_PDG_EVENT_NODE_CONNECT, + /// Sent when a node is disconnected from another node HAPI_PDG_EVENT_NODE_DISCONNECT, + /// Sent when an int attribute value is modified on a work item HAPI_PDG_EVENT_WORKITEM_SET_INT, + /// Sent when a float attribute value is modified on a work item HAPI_PDG_EVENT_WORKITEM_SET_FLOAT, + /// Sent when a string attribute value is modified on a work item HAPI_PDG_EVENT_WORKITEM_SET_STRING, + /// Sent when a file attribute value is modified on a work item HAPI_PDG_EVENT_WORKITEM_SET_FILE, + /// Sent when a Python object attribute value is modified on a work item HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT, + /// Sent when a geometry attribute value is modified on a work item HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY, + /// Deprecated HAPI_PDG_EVENT_WORKITEM_MERGE, + /// Sent when an output file is added to a work item HAPI_PDG_EVENT_WORKITEM_RESULT, + /// Sent when a work items priority is changed HAPI_PDG_EVENT_WORKITEM_PRIORITY, - + /// Sent for each node in the graph, when a cook starts HAPI_PDG_EVENT_COOK_START, - + /// Deprecated HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR, + /// Deprecated HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR, + /// Deprecated HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE, - + /// Deprecated HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED, - + /// A special enum that represents the OR of all event types HAPI_PDG_EVENT_ALL, + /// A special enum that represents the OR of both the `CookError` and `CookWarning` events HAPI_PDG_EVENT_LOG, + /// Sent when a new scheduler is added to the graph HAPI_PDG_EVENT_SCHEDULER_ADDED, + /// Sent when a scheduler is removed from the graph HAPI_PDG_EVENT_SCHEDULER_REMOVED, + /// Sent when the scheduler assigned to a node is changed HAPI_PDG_EVENT_SET_SCHEDULER, - + /// Deprecated HAPI_PDG_EVENT_SERVICE_MANAGER_ALL, HAPI_PDG_CONTEXT_EVENTS @@ -1371,6 +1435,9 @@ struct HAPI_API HAPI_ParmInfo /// Provides the raw condition string which is used to evalute whether /// a parm is enabled or disabled HAPI_StringHandle disabledConditionSH; + + /// Whether or not the "Use Menu Item Token As Value" checkbox was checked in a integer menu item. + HAPI_Bool useMenuItemTokenAsValue; }; HAPI_C_STRUCT_TYPEDEF( HAPI_ParmInfo ) @@ -1503,6 +1570,7 @@ struct HAPI_API HAPI_GeoInfo /// @{ int pointGroupCount; int primitiveGroupCount; + int edgeGroupCount; /// @} /// Total number of parts this geometry contains. @@ -1761,17 +1829,27 @@ HAPI_C_STRUCT_TYPEDEF( HAPI_VolumeVisualInfo ) struct HAPI_API HAPI_CurveInfo { HAPI_CurveType curveType; - int curveCount; /// The number of curves contained in this curve mesh. - int vertexCount; /// The number of control vertices (CVs) for all curves. - int knotCount; /// The number of knots for all curves. + /// The number of curves contained in this curve mesh. + int curveCount; + + /// The number of control vertices (CVs) for all curves. + int vertexCount; + + /// The number of knots for all curves. + int knotCount; + + /// Whether the curves in this curve mesh are periodic. HAPI_Bool isPeriodic; - /// Whether the curves in this curve mesh are periodic. + + /// Whether the curves in this curve mesh are rational. HAPI_Bool isRational; - /// Whether the curves in this curve mesh are rational. - int order; /// Order of 1 is invalid. 0 means there is a varying order. - HAPI_Bool hasKnots; /// Whether the curve has knots. + /// Order of 1 is invalid. 0 means there is a varying order. + int order; + + /// Whether the curve has knots. + HAPI_Bool hasKnots; }; HAPI_C_STRUCT_TYPEDEF( HAPI_CurveInfo ) @@ -1873,4 +1951,15 @@ struct HAPI_API HAPI_SessionSyncInfo }; HAPI_C_STRUCT_TYPEDEF( HAPI_SessionSyncInfo ) +/// Configuration options for Houdini's compositing context +struct HAPI_API HAPI_CompositorOptions +{ + /// Specifies the maximum allowed width of an image in the compositor + int maximumResolutionX; + + /// Specifies the maximum allowed height of an image in the compositor + int maximumResolutionY; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_CompositorOptions ) + #endif // __HAPI_COMMON_h__ diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Helpers.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Helpers.h index f9dcd46ea..d30e978de 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Helpers.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Helpers.h @@ -26,6 +26,13 @@ HAPI_DECL_RETURN( void ) HAPI_DECL_RETURN( HAPI_TimelineOptions ) HAPI_TimelineOptions_Create(); +// COMPOSITOR SETTINGS ------------------------------------------------------ + +HAPI_DECL_RETURN( void) + HAPI_CompositorOptions_Init( HAPI_CompositorOptions * in ); +HAPI_DECL_RETURN( HAPI_CompositorOptions ) + HAPI_CompositorOptions_Create(); + // ASSETS ------------------------------------------------------------------- HAPI_DECL_RETURN( void ) diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h index 36f73c1cd..0a7ba1383 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2020> Side Effects Software Inc.* +* Copyright (c) <2021> Side Effects Software Inc.* * Permission is hereby granted, free of charge, to any person obtaining a copy* of this software and associated documentation files (the "Software"), to deal* in the Software without restriction, including without limitation the rights* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell* copies of the Software, and to permit persons to whom the Software is* furnished to do so, subject to the following conditions:** The above copyright notice and this permission notice shall be included in all* copies or substantial portions of the Software.** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE* SOFTWARE. * * Produced by: * Side Effects Software Inc @@ -27,12 +27,12 @@ // expecting to compile against. #define HAPI_VERSION_HOUDINI_MAJOR 18 #define HAPI_VERSION_HOUDINI_MINOR 5 -#define HAPI_VERSION_HOUDINI_BUILD 408 +#define HAPI_VERSION_HOUDINI_BUILD 759 #define HAPI_VERSION_HOUDINI_PATCH 0 // The two components of the Houdini Engine (marketed) version. #define HAPI_VERSION_HOUDINI_ENGINE_MAJOR 3 -#define HAPI_VERSION_HOUDINI_ENGINE_MINOR 5 +#define HAPI_VERSION_HOUDINI_ENGINE_MINOR 7 // This is a monotonously increasing API version number that can be used // to lock against a certain API for compatibility purposes. Basically, @@ -40,6 +40,6 @@ // might no longer compile. Semantic changes to the methods will also // cause this version to increase. This number will be reset to 0 // every time the Houdini Engine version is bumped. -#define HAPI_VERSION_HOUDINI_ENGINE_API 1 +#define HAPI_VERSION_HOUDINI_ENGINE_API 3 #endif // __HAPI_VERSION_h__ diff --git a/Source/HoudiniEngine/Public/HoudiniApi.h b/Source/HoudiniEngine/Public/HoudiniApi.h index c8e782c63..f2cc2a25a 100644 --- a/Source/HoudiniEngine/Public/HoudiniApi.h +++ b/Source/HoudiniEngine/Public/HoudiniApi.h @@ -1,5 +1,5 @@ /* - * Copyright (c) <2020> Side Effects Software Inc. * + * Copyright (c) <2021> Side Effects Software Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -55,6 +55,8 @@ struct HOUDINIENGINE_API FHoudiniApi typedef HAPI_Result (*ComposeChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); typedef HAPI_Result (*ComposeNodeCookResultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); typedef HAPI_Result (*ComposeObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); + typedef HAPI_CompositorOptions (*CompositorOptions_CreateFuncPtr)(); + typedef void (*CompositorOptions_InitFuncPtr)(HAPI_CompositorOptions * in); typedef HAPI_Result (*ConnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); typedef HAPI_Result (*ConvertMatrixToEulerFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); typedef HAPI_Result (*ConvertMatrixToQuatFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); @@ -99,13 +101,19 @@ struct HOUDINIENGINE_API FHoudiniApi typedef HAPI_Result (*GetAttributeFloatArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); typedef HAPI_Result (*GetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); typedef HAPI_Result (*GetAttributeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*GetAttributeInt16ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeInt16DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length); typedef HAPI_Result (*GetAttributeInt64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); typedef HAPI_Result (*GetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeInt8ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length); typedef HAPI_Result (*GetAttributeIntArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); typedef HAPI_Result (*GetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); typedef HAPI_Result (*GetAttributeNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); typedef HAPI_Result (*GetAttributeStringArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); typedef HAPI_Result (*GetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeUInt8ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeUInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length); typedef HAPI_Result (*GetAvailableAssetCountFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); typedef HAPI_Result (*GetAvailableAssetsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); typedef HAPI_Result (*GetBoxInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); @@ -114,6 +122,7 @@ struct HOUDINIENGINE_API FHoudiniApi typedef HAPI_Result (*GetComposedNodeCookResultFuncPtr)(const HAPI_Session * session, char * string_value, int length); typedef HAPI_Result (*GetComposedObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); typedef HAPI_Result (*GetComposedObjectTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); + typedef HAPI_Result (*GetCompositorOptionsFuncPtr)(const HAPI_Session * session, HAPI_CompositorOptions * compositor_options); typedef HAPI_Result (*GetConnectionErrorFuncPtr)(char * string_value, int length, HAPI_Bool clear); typedef HAPI_Result (*GetConnectionErrorLengthFuncPtr)(int * buffer_length); typedef HAPI_Result (*GetCookingCurrentCountFuncPtr)(const HAPI_Session * session, int * count); @@ -123,6 +132,7 @@ struct HOUDINIENGINE_API FHoudiniApi typedef HAPI_Result (*GetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); typedef HAPI_Result (*GetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); typedef HAPI_Result (*GetDisplayGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); + typedef HAPI_Result (*GetEdgeCountOfEdgeGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * group_name, int * edge_count); typedef HAPI_Result (*GetEnvIntFuncPtr)(HAPI_EnvIntType int_type, int * value); typedef HAPI_Result (*GetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); typedef HAPI_Result (*GetFirstVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); @@ -158,6 +168,8 @@ struct HOUDINIENGINE_API FHoudiniApi typedef HAPI_Result (*GetNumWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * num); typedef HAPI_Result (*GetObjectInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); typedef HAPI_Result (*GetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); + typedef HAPI_Result (*GetOutputGeoCountFuncPtr)(const HAPI_Session* session, HAPI_NodeId node_id, int* count); + typedef HAPI_Result (*GetOutputGeoInfosFuncPtr)(const HAPI_Session* session, HAPI_NodeId node_id, HAPI_GeoInfo* geo_infos_array, int count); typedef HAPI_Result (*GetOutputNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); typedef HAPI_Result (*GetPDGEventsFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); typedef HAPI_Result (*GetPDGGraphContextIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); @@ -273,7 +285,7 @@ struct HOUDINIENGINE_API FHoudiniApi typedef HAPI_Result (*QueryNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); typedef HAPI_Result (*QueryNodeOutputConnectedCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); typedef HAPI_Result (*QueryNodeOutputConnectedNodesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); - typedef HAPI_Result (*RemoveCustomStringFuncPtr)(const HAPI_Session * session, const int string_handle); + typedef HAPI_Result (*RemoveCustomStringFuncPtr)(const HAPI_Session * session, const HAPI_StringHandle string_handle); typedef HAPI_Result (*RemoveMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); typedef HAPI_Result (*RemoveParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); typedef HAPI_Result (*RenameNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); @@ -291,15 +303,19 @@ struct HOUDINIENGINE_API FHoudiniApi typedef HAPI_Result (*SetAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); typedef HAPI_Result (*SetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); typedef HAPI_Result (*SetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeInt16DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length); typedef HAPI_Result (*SetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length); typedef HAPI_Result (*SetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); typedef HAPI_Result (*SetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); + typedef HAPI_Result (*SetAttributeUInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length); typedef HAPI_Result (*SetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); + typedef HAPI_Result (*SetCompositorOptionsFuncPtr)(const HAPI_Session * session, const HAPI_CompositorOptions * compositor_options); typedef HAPI_Result (*SetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); typedef HAPI_Result (*SetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); typedef HAPI_Result (*SetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); typedef HAPI_Result (*SetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); - typedef HAPI_Result (*SetCustomStringFuncPtr)(const HAPI_Session * session, const char * string_value, int * handle_value); + typedef HAPI_Result (*SetCustomStringFuncPtr)(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value); typedef HAPI_Result (*SetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); typedef HAPI_Result (*SetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); typedef HAPI_Result (*SetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); @@ -368,6 +384,8 @@ struct HOUDINIENGINE_API FHoudiniApi static ComposeChildNodeListFuncPtr ComposeChildNodeList; static ComposeNodeCookResultFuncPtr ComposeNodeCookResult; static ComposeObjectListFuncPtr ComposeObjectList; + static CompositorOptions_CreateFuncPtr CompositorOptions_Create; + static CompositorOptions_InitFuncPtr CompositorOptions_Init; static ConnectNodeInputFuncPtr ConnectNodeInput; static ConvertMatrixToEulerFuncPtr ConvertMatrixToEuler; static ConvertMatrixToQuatFuncPtr ConvertMatrixToQuat; @@ -412,13 +430,19 @@ struct HOUDINIENGINE_API FHoudiniApi static GetAttributeFloatArrayDataFuncPtr GetAttributeFloatArrayData; static GetAttributeFloatDataFuncPtr GetAttributeFloatData; static GetAttributeInfoFuncPtr GetAttributeInfo; + static GetAttributeInt16ArrayDataFuncPtr GetAttributeInt16ArrayData; + static GetAttributeInt16DataFuncPtr GetAttributeInt16Data; static GetAttributeInt64ArrayDataFuncPtr GetAttributeInt64ArrayData; static GetAttributeInt64DataFuncPtr GetAttributeInt64Data; + static GetAttributeInt8ArrayDataFuncPtr GetAttributeInt8ArrayData; + static GetAttributeInt8DataFuncPtr GetAttributeInt8Data; static GetAttributeIntArrayDataFuncPtr GetAttributeIntArrayData; static GetAttributeIntDataFuncPtr GetAttributeIntData; static GetAttributeNamesFuncPtr GetAttributeNames; static GetAttributeStringArrayDataFuncPtr GetAttributeStringArrayData; static GetAttributeStringDataFuncPtr GetAttributeStringData; + static GetAttributeUInt8ArrayDataFuncPtr GetAttributeUInt8ArrayData; + static GetAttributeUInt8DataFuncPtr GetAttributeUInt8Data; static GetAvailableAssetCountFuncPtr GetAvailableAssetCount; static GetAvailableAssetsFuncPtr GetAvailableAssets; static GetBoxInfoFuncPtr GetBoxInfo; @@ -427,6 +451,7 @@ struct HOUDINIENGINE_API FHoudiniApi static GetComposedNodeCookResultFuncPtr GetComposedNodeCookResult; static GetComposedObjectListFuncPtr GetComposedObjectList; static GetComposedObjectTransformsFuncPtr GetComposedObjectTransforms; + static GetCompositorOptionsFuncPtr GetCompositorOptions; static GetConnectionErrorFuncPtr GetConnectionError; static GetConnectionErrorLengthFuncPtr GetConnectionErrorLength; static GetCookingCurrentCountFuncPtr GetCookingCurrentCount; @@ -436,6 +461,7 @@ struct HOUDINIENGINE_API FHoudiniApi static GetCurveKnotsFuncPtr GetCurveKnots; static GetCurveOrdersFuncPtr GetCurveOrders; static GetDisplayGeoInfoFuncPtr GetDisplayGeoInfo; + static GetEdgeCountOfEdgeGroupFuncPtr GetEdgeCountOfEdgeGroup; static GetEnvIntFuncPtr GetEnvInt; static GetFaceCountsFuncPtr GetFaceCounts; static GetFirstVolumeTileFuncPtr GetFirstVolumeTile; @@ -471,6 +497,8 @@ struct HOUDINIENGINE_API FHoudiniApi static GetNumWorkitemsFuncPtr GetNumWorkitems; static GetObjectInfoFuncPtr GetObjectInfo; static GetObjectTransformFuncPtr GetObjectTransform; + static GetOutputGeoCountFuncPtr GetOutputGeoCount; + static GetOutputGeoInfosFuncPtr GetOutputGeoInfos; static GetOutputNodeIdFuncPtr GetOutputNodeId; static GetPDGEventsFuncPtr GetPDGEvents; static GetPDGGraphContextIdFuncPtr GetPDGGraphContextId; @@ -604,10 +632,14 @@ struct HOUDINIENGINE_API FHoudiniApi static SetAnimCurveFuncPtr SetAnimCurve; static SetAttributeFloat64DataFuncPtr SetAttributeFloat64Data; static SetAttributeFloatDataFuncPtr SetAttributeFloatData; + static SetAttributeInt16DataFuncPtr SetAttributeInt16Data; static SetAttributeInt64DataFuncPtr SetAttributeInt64Data; + static SetAttributeInt8DataFuncPtr SetAttributeInt8Data; static SetAttributeIntDataFuncPtr SetAttributeIntData; static SetAttributeStringDataFuncPtr SetAttributeStringData; + static SetAttributeUInt8DataFuncPtr SetAttributeUInt8Data; static SetCachePropertyFuncPtr SetCacheProperty; + static SetCompositorOptionsFuncPtr SetCompositorOptions; static SetCurveCountsFuncPtr SetCurveCounts; static SetCurveInfoFuncPtr SetCurveInfo; static SetCurveKnotsFuncPtr SetCurveKnots; @@ -681,6 +713,8 @@ struct HOUDINIENGINE_API FHoudiniApi static HAPI_Result ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); static HAPI_Result ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); static HAPI_Result ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); + static HAPI_CompositorOptions CompositorOptions_CreateEmptyStub(); + static void CompositorOptions_InitEmptyStub(HAPI_CompositorOptions * in); static HAPI_Result ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); static HAPI_Result ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); static HAPI_Result ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); @@ -725,13 +759,19 @@ struct HOUDINIENGINE_API FHoudiniApi static HAPI_Result GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); static HAPI_Result GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); static HAPI_Result GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); + static HAPI_Result GetAttributeInt16ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length); static HAPI_Result GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); static HAPI_Result GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); + static HAPI_Result GetAttributeInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length); static HAPI_Result GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); static HAPI_Result GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); static HAPI_Result GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); static HAPI_Result GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); static HAPI_Result GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); + static HAPI_Result GetAttributeUInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length); static HAPI_Result GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); static HAPI_Result GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); static HAPI_Result GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); @@ -740,6 +780,7 @@ struct HOUDINIENGINE_API FHoudiniApi static HAPI_Result GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length); static HAPI_Result GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); static HAPI_Result GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); + static HAPI_Result GetCompositorOptionsEmptyStub(const HAPI_Session * session, HAPI_CompositorOptions * compositor_options); static HAPI_Result GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear); static HAPI_Result GetConnectionErrorLengthEmptyStub(int * buffer_length); static HAPI_Result GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count); @@ -749,6 +790,7 @@ struct HOUDINIENGINE_API FHoudiniApi static HAPI_Result GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); static HAPI_Result GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); static HAPI_Result GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); + static HAPI_Result GetEdgeCountOfEdgeGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * group_name, int * edge_count); static HAPI_Result GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value); static HAPI_Result GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); static HAPI_Result GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); @@ -784,6 +826,8 @@ struct HOUDINIENGINE_API FHoudiniApi static HAPI_Result GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num); static HAPI_Result GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); static HAPI_Result GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); + static HAPI_Result GetOutputGeoCountEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, int* count); + static HAPI_Result GetOutputGeoInfosEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, HAPI_GeoInfo* geo_infos_array, int count); static HAPI_Result GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); static HAPI_Result GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); static HAPI_Result GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); @@ -899,7 +943,7 @@ struct HOUDINIENGINE_API FHoudiniApi static HAPI_Result QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); static HAPI_Result QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); static HAPI_Result QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); - static HAPI_Result RemoveCustomStringEmptyStub(const HAPI_Session * session, const int string_handle); + static HAPI_Result RemoveCustomStringEmptyStub(const HAPI_Session * session, const HAPI_StringHandle string_handle); static HAPI_Result RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); static HAPI_Result RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); static HAPI_Result RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); @@ -917,15 +961,19 @@ struct HOUDINIENGINE_API FHoudiniApi static HAPI_Result SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); static HAPI_Result SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); static HAPI_Result SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); + static HAPI_Result SetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length); static HAPI_Result SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); + static HAPI_Result SetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length); static HAPI_Result SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); static HAPI_Result SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); + static HAPI_Result SetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length); static HAPI_Result SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); + static HAPI_Result SetCompositorOptionsEmptyStub(const HAPI_Session * session, const HAPI_CompositorOptions * compositor_options); static HAPI_Result SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); static HAPI_Result SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); static HAPI_Result SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); static HAPI_Result SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); - static HAPI_Result SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, int * handle_value); + static HAPI_Result SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value); static HAPI_Result SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); static HAPI_Result SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); static HAPI_Result SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); diff --git a/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs b/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs index 9951bbe2a..91c671201 100644 --- a/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs +++ b/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs @@ -1,32 +1,28 @@ /* - * Copyright (c) <2020> Side Effects Software Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * Produced by: - * Side Effects Software Inc - * 123 Front Street West, Suite 1401 - * Toronto, Ontario - * Canada M5J 2M2 - * 416-504-9876 - * - */ +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ using UnrealBuildTool; using System; diff --git a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp index 68599e511..165891548 100644 --- a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp +++ b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -74,7 +74,7 @@ FAssetTypeActions_HoudiniAsset::GetCategories() UThumbnailInfo * FAssetTypeActions_HoudiniAsset::GetThumbnailInfo(UObject * Asset) const { - if (!Asset || Asset->IsPendingKill()) + if (!IsValid(Asset)) return nullptr; UHoudiniAsset * HoudiniAsset = CastChecked< UHoudiniAsset >(Asset); diff --git a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h index ac8806ebf..fe798135a 100644 --- a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h +++ b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp index 8991f9796..8f7e86ba7 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h index 6b22120f9..286b7586a 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp index 165addd61..895b17c06 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h index 982113c99..3357d841f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp index 50f4f5cc9..6d4d6805c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -74,22 +74,40 @@ FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() { FHoudiniParameterDetails* ParamDetailsPtr = ParameterDetails.Get(); + for (auto& CurFloatRampCurveEditor : ParamDetailsPtr->CreatedFloatCurveEditors) + { + if (CurFloatRampCurveEditor.IsValid()) + { + CurFloatRampCurveEditor->HoudiniFloatRampCurve = nullptr; + CurFloatRampCurveEditor->SetCurveOwner(nullptr); + } + } for (auto& CurFloatRampCurve : ParamDetailsPtr->CreatedFloatRampCurves) { - if (!CurFloatRampCurve || CurFloatRampCurve->IsPendingKill()) + if (!IsValid(CurFloatRampCurve)) continue; CurFloatRampCurve->RemoveFromRoot(); } + for (auto& CurColorRampCurveEditor : ParamDetailsPtr->CreatedColorGradientEditors) + { + if (CurColorRampCurveEditor.IsValid()) + { + CurColorRampCurveEditor->HoudiniColorRampCurve = nullptr; + CurColorRampCurveEditor->SetCurveOwner(nullptr); + } + } for (auto& CurColorRampCurve : ParamDetailsPtr->CreatedColorRampCurves) { - if (!CurColorRampCurve || CurColorRampCurve->IsPendingKill()) + if (!IsValid(CurColorRampCurve)) continue; CurColorRampCurve->RemoveFromRoot(); } - + + ParamDetailsPtr->CreatedFloatCurveEditors.Empty(); + ParamDetailsPtr->CreatedColorGradientEditors.Empty(); ParamDetailsPtr->CreatedFloatRampCurves.Empty(); ParamDetailsPtr->CreatedColorRampCurves.Empty(); } @@ -128,6 +146,115 @@ FHoudiniAssetComponentDetails::AddIndieLicenseRow(IDetailCategoryBuilder& InCate ]; } + +void +FHoudiniAssetComponentDetails::AddSessionStatusRow(IDetailCategoryBuilder& InCategory) +{ + FDetailWidgetRow& PDGStatusRow = InCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetSessionStatusAndColor(StatusString, StatusColor); + return FText::FromString(StatusString); + }) + .ColorAndOpacity_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetSessionStatusAndColor(StatusString, StatusColor); + return FSlateColor(StatusColor); + }) + ] + ]; +} + +bool +FHoudiniAssetComponentDetails::GetSessionStatusAndColor( + FString& OutStatusString, FLinearColor& OutStatusColor) +{ + OutStatusString = FString(); + OutStatusColor = FLinearColor::White; + + const EHoudiniSessionStatus& SessionStatus = FHoudiniEngine::Get().GetSessionStatus(); + + switch (SessionStatus) + { + case EHoudiniSessionStatus::NotStarted: + // Session not initialized yet + OutStatusString = TEXT("Houdini Engine Session - Not Started"); + OutStatusColor = FLinearColor::White; + break; + + case EHoudiniSessionStatus::Connected: + // Session successfully started + OutStatusString = TEXT("Houdini Engine Session READY"); + OutStatusColor = FLinearColor::Green; + break; + case EHoudiniSessionStatus::Stopped: + // Session stopped + OutStatusString = TEXT("Houdini Engine Session STOPPED"); + OutStatusColor = FLinearColor(1.0f, 0.5f, 0.0f); + break; + case EHoudiniSessionStatus::Failed: + // Session failed to be created/connected + OutStatusString = TEXT("Houdini Engine Session FAILED"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniSessionStatus::Lost: + // Session Lost (HARS/Houdini Crash?) + OutStatusString = TEXT("Houdini Engine Session LOST"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniSessionStatus::NoLicense: + // Failed to acquire a license + OutStatusString = TEXT("Houdini Engine Session FAILED - No License"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniSessionStatus::None: + // Session type set to None + OutStatusString = TEXT("Houdini Engine Session DISABLED"); + OutStatusColor = FLinearColor::White; + break; + default: + case EHoudiniSessionStatus::Invalid: + OutStatusString = TEXT("Houdini Engine Session INVALID"); + OutStatusColor = FLinearColor::Red; + break; + } + + // Handle a few specific case for active session + if (SessionStatus == EHoudiniSessionStatus::Connected) + { + bool bPaused = !FHoudiniEngine::Get().IsCookingEnabled(); + bool bSSync = FHoudiniEngine::Get().IsSessionSyncEnabled(); + if (bPaused) + { + OutStatusString = TEXT("Houdini Engine Session PAUSED"); + OutStatusColor = FLinearColor::Yellow; + } + /* + else if (bSSync) + { + OutStatusString = TEXT("Houdini Engine Session Sync READY"); + OutStatusColor = FLinearColor::Blue; + } + */ + } + + return true; +} + void FHoudiniAssetComponentDetails::AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC) { @@ -152,7 +279,7 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil if (Object) { UHoudiniAssetComponent * HAC = Cast< UHoudiniAssetComponent >(Object); - if (HAC && !HAC->IsPendingKill()) + if (IsValid(HAC)) HoudiniAssetComponents.Add(HAC); } } @@ -274,7 +401,7 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil { // We only want to create root parameters here, they will recursively create child parameters. UHoudiniParameter* CurrentParam = MainComponent->GetParameterAt(ParamIdx); - if (!CurrentParam || CurrentParam->IsPendingKill()) + if (!IsValid(CurrentParam)) continue; // TODO: remove ? unneeded? @@ -291,14 +418,14 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) { UHoudiniParameter* LinkedParam = HACs[LinkedIdx]->GetParameterAt(ParamIdx); - if (!LinkedParam || LinkedParam->IsPendingKill()) + if (!IsValid(LinkedParam)) continue; // Linked params should match the main param! If not try to find one that matches if ( !LinkedParam->Matches(*CurrentParam) ) { LinkedParam = MainComponent->FindMatchingParameter(CurrentParam); - if (!LinkedParam || LinkedParam->IsPendingKill() || LinkedParam->IsChildParameter()) + if (!IsValid(LinkedParam) || LinkedParam->IsChildParameter()) continue; } @@ -328,7 +455,7 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil { UHoudiniHandleComponent* CurrentHandleComponent = MainComponent->GetHandleComponentAt(HandleIdx); - if (!CurrentHandleComponent || CurrentHandleComponent->IsPendingKill()) + if (!IsValid(CurrentHandleComponent)) continue; TArray EditedHandles; @@ -338,14 +465,14 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); ++LinkedIdx) { UHoudiniHandleComponent* LinkedHandle = HACs[LinkedIdx]->GetHandleComponentAt(HandleIdx); - if (!LinkedHandle || LinkedHandle->IsPendingKill()) + if (!IsValid(LinkedHandle)) continue; // Linked handles should match the main param, if not try to find one that matches if (!LinkedHandle->Matches(*CurrentHandleComponent)) { LinkedHandle = MainComponent->FindMatchingHandle(CurrentHandleComponent); - if (!LinkedHandle || LinkedHandle->IsPendingKill()) + if (!IsValid(LinkedHandle)) continue; } @@ -377,7 +504,7 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil for (int32 InputIdx = 0; InputIdx < MainComponent->GetNumInputs(); InputIdx++) { UHoudiniInput* CurrentInput = MainComponent->GetInputAt(InputIdx); - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (!MainComponent->IsInputTypeSupported(CurrentInput->GetInputType())) @@ -395,14 +522,14 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) { UHoudiniInput* LinkedInput = HACs[LinkedIdx]->GetInputAt(InputIdx); - if (!LinkedInput || LinkedInput->IsPendingKill()) + if (!IsValid(LinkedInput)) continue; // Linked params should match the main param! If not try to find one that matches if (!LinkedInput->Matches(*CurrentInput)) { LinkedInput = MainComponent->FindMatchingInput(CurrentInput); - if (!LinkedInput || LinkedInput->IsPendingKill()) + if (!IsValid(LinkedInput)) continue; } @@ -429,7 +556,7 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil for (int32 OutputIdx = 0; OutputIdx < MainComponent->GetNumOutputs(); OutputIdx++) { UHoudiniOutput* CurrentOutput = MainComponent->GetOutputAt(OutputIdx); - if (!CurrentOutput || CurrentOutput->IsPendingKill()) + if (!IsValid(CurrentOutput)) continue; // Build an array of edited inpoutputs for multi edit @@ -440,7 +567,7 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) { UHoudiniOutput* LinkedOutput = HACs[LinkedIdx]->GetOutputAt(OutputIdx); - if (!LinkedOutput || LinkedOutput->IsPendingKill()) + if (!IsValid(LinkedOutput)) continue; /* @@ -448,7 +575,7 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil if (!LinkedOutput->Matches(*CurrentOutput)) { LinkedOutput = MainComponent->FindMatchingInput(CurrentOutput); - if (!LinkedOutput || LinkedOutput->IsPendingKill()) + if (!IsValid(LinkedOutput)) continue; } */ diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h index 702bc8f80..f640fb21b 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,6 +52,11 @@ class FHoudiniAssetComponentDetails : public IDetailCustomization // Create an instance of this detail layout class. static TSharedRef MakeInstance(); + // Adds a text row that indicate the status of the Houdini Session + static void AddSessionStatusRow(IDetailCategoryBuilder& InCategory); + + static bool GetSessionStatusAndColor(FString& OutStatusString, FLinearColor& OutStatusColor); + private: // Adds a text row indicate we're using a Houdini indie license diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp index 63378d038..4b1961337 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h index ca8ba14cf..c0c1cf333 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp index f3910a62d..bc74754c9 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,6 +46,7 @@ #include "HoudiniPDGAssetLink.h" #include "HoudiniStringResolver.h" #include "HoudiniEngineCommands.h" +#include "HoudiniEngineRuntimeUtils.h" #include "Engine/StaticMesh.h" #include "Engine/World.h" @@ -88,9 +89,14 @@ #include "Kismet2/BlueprintEditorUtils.h" #include "MaterialEditor/Public/MaterialEditingLibrary.h" #include "MaterialGraph/MaterialGraph.h" +#include "Materials/MaterialInstance.h" #include "Particles/ParticleSystemComponent.h" #include "Sound/SoundBase.h" #include "UObject/UnrealType.h" +#include "Math/Box.h" +#include "Misc/ScopedSlowTask.h" + +HOUDINI_BAKING_DEFINE_LOG_CATEGORY(); #define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE @@ -101,6 +107,9 @@ FHoudiniEngineBakedActor::FHoudiniEngineBakedActor() , ActorBakeName(NAME_None) , BakedObject(nullptr) , SourceObject(nullptr) + , BakeFolderPath() + , bInstancerOutput(false) + , bPostBakeProcessPostponed(false) { } @@ -111,7 +120,10 @@ FHoudiniEngineBakedActor::FHoudiniEngineBakedActor( int32 InOutputIndex, const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, UObject* InBakedObject, - UObject* InSourceObject) + UObject* InSourceObject, + UObject* InBakedComponent, + const FString& InBakeFolderPath, + const FHoudiniPackageParams& InBakedObjectPackageParams) : Actor(InActor) , OutputIndex(InOutputIndex) , OutputObjectIdentifier(InOutputObjectIdentifier) @@ -119,6 +131,11 @@ FHoudiniEngineBakedActor::FHoudiniEngineBakedActor( , WorldOutlinerFolder(InWorldOutlinerFolder) , BakedObject(InBakedObject) , SourceObject(InSourceObject) + , BakedComponent(InBakedComponent) + , BakeFolderPath(InBakeFolderPath) + , BakedObjectPackageParams(InBakedObjectPackageParams) + , bInstancerOutput(false) + , bPostBakeProcessPostponed(false) { } @@ -127,14 +144,15 @@ FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( UHoudiniAssetComponent* InHACToBake, bool bInReplacePreviousBake, EHoudiniEngineBakeOption InBakeOption, - bool bInRemoveHACOutputOnSuccess) + bool bInRemoveHACOutputOnSuccess, + bool bInRecenterBakedActors) { if (!IsValid(InHACToBake)) return false; // Handle proxies: if the output has any current proxies, first refine them bool bHACNeedsToReCook; - if (!CheckForAndRefineHoudiniProxyMesh(InHACToBake, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bHACNeedsToReCook)) + if (!CheckForAndRefineHoudiniProxyMesh(InHACToBake, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors, bHACNeedsToReCook)) { // Either the component is invalid, or needs a recook to refine a proxy mesh return false; @@ -145,13 +163,13 @@ FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( { case EHoudiniEngineBakeOption::ToActor: { - bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(InHACToBake, bInReplacePreviousBake, bInReplacePreviousBake); + bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(InHACToBake, bInReplacePreviousBake, bInReplacePreviousBake, bInRecenterBakedActors); } break; case EHoudiniEngineBakeOption::ToBlueprint: { - bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(InHACToBake, bInReplacePreviousBake); + bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(InHACToBake, bInReplacePreviousBake, bInRecenterBakedActors); } break; @@ -171,24 +189,28 @@ FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( } if (bSuccess && bInRemoveHACOutputOnSuccess) - FHoudiniOutputTranslator::ClearAndRemoveOutputs(InHACToBake); + { + TArray DeferredClearOutputs; + FHoudiniOutputTranslator::ClearAndRemoveOutputs(InHACToBake, DeferredClearOutputs, true); + } return bSuccess; } bool FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets) + UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets, bool bInRecenterBakedActors) { - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return false; TArray NewActors; TArray PackagesToSave; FHoudiniEngineOutputStats BakeStats; - if (!FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( - HoudiniAssetComponent, bInReplaceActors, bInReplaceAssets, NewActors, PackagesToSave, BakeStats)) + const bool bBakedWithErrors = !FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( + HoudiniAssetComponent, bInReplaceActors, bInReplaceAssets, NewActors, PackagesToSave, BakeStats); + if (bBakedWithErrors) { // TODO ? HOUDINI_LOG_WARNING(TEXT("Errors when baking")); @@ -206,7 +228,7 @@ FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( if (!IsValid(Entry.Actor)) continue; - if (HoudiniAssetComponent->bRecenterBakedActors) + if (bInRecenterBakedActors) CenterActorToBoundingBoxCenter(Entry.Actor); if (GEditor) @@ -222,6 +244,9 @@ FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); } + // Broadcast that the bake is complete + HoudiniAssetComponent->HandleOnPostBake(!bBakedWithErrors); + return true; } @@ -238,15 +263,9 @@ FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return false; - AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); - if (!IsValid(OwnerActor)) - return false; - - const FString HoudiniAssetName = OwnerActor->GetName(); - // Get an array of the outputs const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); TArray Outputs; @@ -262,15 +281,17 @@ FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( if (BakedOutputs.Num() != NumOutputs) BakedOutputs.SetNum(NumOutputs); + const TArray AllBakedActors; return BakeHoudiniOutputsToActors( + HoudiniAssetComponent, Outputs, BakedOutputs, - HoudiniAssetName, HoudiniAssetComponent->GetComponentTransform(), HoudiniAssetComponent->BakeFolder, HoudiniAssetComponent->TemporaryCookFolder, bInReplaceActors, bInReplaceAssets, + AllBakedActors, OutNewActors, OutPackagesToSave, OutBakeStats, @@ -282,14 +303,15 @@ FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( bool FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, const TArray& InOutputs, TArray& InBakedOutputs, - const FString& InHoudiniAssetName, const FTransform& InParentTransform, const FDirectoryPath& InBakeFolder, const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutNewActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats, @@ -304,16 +326,24 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( FString Msg = FString::Format(*MsgTemplate, { 0, NumOutputs }); FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Msg)); - TArray BakedActors; + TArray AllBakedActors = InBakedActors; + TArray NewBakedActors; + // Ensure that InBakedOutputs is the same size as InOutputs + if (InBakedOutputs.Num() != NumOutputs) + InBakedOutputs.SetNum(NumOutputs); + // First bake everything except instancers, then bake instancers. Since instancers might use meshes in // from the other outputs. bool bHasAnyInstancers = false; int32 NumProcessedOutputs = 0; + + TMap AlreadyBakedMaterialsMap; + TArray OutputBakedActors; for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) { UHoudiniOutput* Output = InOutputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) { NumProcessedOutputs++; continue; @@ -330,21 +360,25 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( continue; } + OutputBakedActors.Reset(); switch (OutputType) { case EHoudiniOutputType::Mesh: { FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( + HoudiniAssetComponent, OutputIdx, InOutputs, InBakedOutputs, - InHoudiniAssetName, InBakeFolder, InTempCookFolder, bInReplaceActors, bInReplaceAssets, - BakedActors, + AllBakedActors, + OutputBakedActors, OutPackagesToSave, + AlreadyBakedMaterialsMap, + OutBakeStats, InFallbackActor, InFallbackWorldOutlinerFolder); } @@ -360,20 +394,15 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( case EHoudiniOutputType::Landscape: { - UHoudiniAssetComponent* HAC = Cast(Output->GetOuter()); - if (IsValid(HAC)) - { - // UWorld* WorldContext = Output->GetWorld(); - const bool bResult = BakeLandscape( - OutputIdx, - Output, - InBakedOutputs[OutputIdx].BakedOutputObjects, - bInReplaceActors, - bInReplaceAssets, - InBakeFolder.Path, - InHoudiniAssetName, - OutBakeStats); - } + const bool bResult = BakeLandscape( + HoudiniAssetComponent, + OutputIdx, + InOutputs, + InBakedOutputs, + bInReplaceActors, + bInReplaceAssets, + InBakeFolder.Path, + OutBakeStats); } break; @@ -383,14 +412,16 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( case EHoudiniOutputType::Curve: { FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( - Output, - InBakedOutputs[OutputIdx].BakedOutputObjects, + HoudiniAssetComponent, + OutputIdx, + InOutputs, InBakedOutputs, - InHoudiniAssetName, InBakeFolder, bInReplaceActors, bInReplaceAssets, - BakedActors, + AllBakedActors, + OutputBakedActors, + OutBakeStats, InFallbackActor, InFallbackWorldOutlinerFolder); } @@ -400,6 +431,9 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( break; } + AllBakedActors.Append(OutputBakedActors); + NewBakedActors.Append(OutputBakedActors); + NumProcessedOutputs++; } @@ -408,18 +442,20 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) { UHoudiniOutput* Output = InOutputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) { - NumProcessedOutputs++; continue; } - Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); - if (Output->GetType() == EHoudiniOutputType::Instancer) { + OutputBakedActors.Reset(); + + Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); + FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( + HoudiniAssetComponent, OutputIdx, InOutputs, InBakedOutputs, @@ -428,33 +464,299 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( InTempCookFolder, bInReplaceActors, bInReplaceAssets, - BakedActors, + AllBakedActors, + OutputBakedActors, OutPackagesToSave, + AlreadyBakedMaterialsMap, + OutBakeStats, InInstancerComponentTypesToBake, InFallbackActor, InFallbackWorldOutlinerFolder); + + AllBakedActors.Append(OutputBakedActors); + NewBakedActors.Append(OutputBakedActors); + + NumProcessedOutputs++; } + } + } - NumProcessedOutputs++; + // Only do the post bake post-process once per Actor + TSet UniqueActors; + for (FHoudiniEngineBakedActor& BakedActor : NewBakedActors) + { + if (BakedActor.bPostBakeProcessPostponed && BakedActor.Actor) + { + BakedActor.bPostBakeProcessPostponed = false; + AActor* Actor = BakedActor.Actor; + bool bIsAlreadyInSet = false; + UniqueActors.Add(Actor, &bIsAlreadyInSet); + if (!bIsAlreadyInSet) + { + Actor->InvalidateLightingCache(); + Actor->PostEditMove(true); + Actor->MarkPackageDirty(); + } } } - OutNewActors.Append(BakedActors); - + OutNewActors = MoveTemp(NewBakedActors); + return true; } +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats) +{ + UHoudiniOutput* Output = InAllOutputs[InOutputIndex]; + if (!IsValid(Output)) + return false; + + if (Output->GetType() != EHoudiniOutputType::Instancer) + return false; + + if (!IsValid(InOutputObject.OutputComponent)) + return false; + + UStaticMeshComponent* SMC = Cast(InOutputObject.OutputComponent); + if (!IsValid(SMC)) + { + HOUDINI_LOG_WARNING( + TEXT("Unsupported component for foliage: %s"),*(InOutputObject.OutputComponent->GetClass()->GetName())); + return false; + } + + UStaticMesh* InstancedStaticMesh = SMC->GetStaticMesh(); + if (!IsValid(InstancedStaticMesh)) + { + // No mesh, skip this instancer + return false; + } + + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets + ? EPackageReplaceMode::ReplaceExistingAssets + : EPackageReplaceMode::CreateNewAssets; + UWorld* DesiredWorld = Output ? Output->GetWorld() : GWorld; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InstancedStaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + const bool bFoundMeshOutput = FindOutputObject(InstancedStaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + MeshPackageParams, MeshResolver, InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + InstancedStaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, InBakedActors, InTempCookFolder.Path, + OutPackagesToSave, InOutAlreadyBakedMaterialsMap, OutBakeStats); + } + else + { + BakedStaticMesh = InstancedStaticMesh; + } + + // Update the baked object + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // const FString InstancerName = FString::Printf(TEXT("%s_foliage_%s"), *ObjectName, *(InOutputObjectIdentifier.SplitIdentifier)); + // Construct PackageParams for the instancer itself. When baking to actor we technically won't create a stand-alone + // disk package for the instancer, but certain attributes (such as level path) use tokens populated from the + // package params. + FHoudiniPackageParams InstancerPackageParams; + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + InstancerPackageParams, InstancerResolver, InBakeFolder.Path, AssetPackageReplaceMode); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_path attribute + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + HOUDINI_LOG_ERROR(TEXT("Could not find or create a level: %s"), *LevelPackagePath); + return false; + } + + // If we have created a new level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + OutBakeStats.NotifyPackageCreated(1); + OutBakeStats.NotifyObjectsCreated(DesiredLevel->GetClass()->GetName(), 1); + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // Get foliage actor for the level + const bool bCreateIfNone = true; + AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, bCreateIfNone); + if (!IsValid(InstancedFoliageActor)) + { + HOUDINI_LOG_ERROR(TEXT("Could not find or create an instanced foliage actor for level %s"), *(DesiredLevel->GetPathName())); + return false; + } + + // Get the previous bake data for this instancer + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // Foliage type is replaced in replacement mode if: + // the previous baked object is this foliage type + // and we haven't bake this foliage type during this bake (BakeResults) + // NOTE: foliage type is only recorded as the previous bake object if we created the foliage type + // TODO: replacement mode should probably only affect the instances themselves and not the foliage type + // since the foliage type is already linked to whatever mesh we are using (which will be replaced + // incremented already). To track instances it looks like we would have to use the locations of the + // baked instances (likely cannot use the indices, since the user might modify/add/remove instances + // after the bake). + + // See if we already have a FoliageType for that static mesh + UFoliageType* FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(BakedStaticMesh); + if (!IsValid(FoliageType)) + { + // We need to create a new FoliageType for this Static Mesh + // TODO: Add foliage default settings + InstancedFoliageActor->AddMesh(BakedStaticMesh, &FoliageType); + // Update the previous bake results with the foliage type we created + InBakedOutputObject.BakedComponent = FSoftObjectPath(FoliageType).ToString(); + } + else + { + const FString FoliageTypePath = FSoftObjectPath(FoliageType).ToString(); + if (bInReplaceAssets && InBakedOutputObject.BakedComponent == FoliageTypePath && + !InBakedActors.FindByPredicate([FoliageType](const FHoudiniEngineBakedActor& Entry) { return Entry.BakedComponent == FoliageType; })) + { + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + // Update the previous bake results with the foliage type + InBakedOutputObject.BakedComponent = FoliageTypePath; + } + else + { + // If we didn't create the foliage type, don't set the baked component + InBakedOutputObject.BakedComponent.Empty(); + } + } + + // Record the foliage bake in the current results + FHoudiniEngineBakedActor NewResult; + NewResult.OutputIndex = InOutputIndex; + NewResult.OutputObjectIdentifier = InOutputObjectIdentifier; + NewResult.SourceObject = InstancedStaticMesh; + NewResult.BakedObject = BakedStaticMesh; + NewResult.BakedComponent = FoliageType; + + OutBakedActorEntry = NewResult; + + // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it + FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); + if (!FoliageInfo) + return false; + + int32 CurrentInstanceCount = 0; + if (SMC->IsA()) + { + UInstancedStaticMeshComponent* ISMC = Cast(SMC); + const int32 NumInstances = ISMC->GetInstanceCount(); + for (int32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex) + { + FTransform InstanceTransform; + const bool bWorldSpace = true; + if (ISMC->GetInstanceTransform(InstanceIndex, InstanceTransform, bWorldSpace)) + { + FFoliageInstance FoliageInstance; + FoliageInstance.Location = InstanceTransform.GetLocation(); + FoliageInstance.Rotation = InstanceTransform.GetRotation().Rotator(); + FoliageInstance.DrawScale3D = InstanceTransform.GetScale3D(); + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + + CurrentInstanceCount++; + } + } + } + else + { + const FTransform ComponentToWorldTransform = SMC->GetComponentToWorld(); + FFoliageInstance FoliageInstance; + FoliageInstance.Location = ComponentToWorldTransform.GetLocation(); + FoliageInstance.Rotation = ComponentToWorldTransform.GetRotation().Rotator(); + FoliageInstance.DrawScale3D = ComponentToWorldTransform.GetScale3D(); + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + + CurrentInstanceCount++; + } + + // TODO: This was due to a bug in UE4.22-20, check if still needed! + if (FoliageInfo->GetComponent()) + FoliageInfo->GetComponent()->BuildTreeIfOutdated(true, true); + + // Notify the user that we succesfully bake the instances to foliage + FString Notification = TEXT("Successfully baked ") + FString::FromInt(CurrentInstanceCount) + TEXT(" instances of ") + BakedStaticMesh->GetName() + TEXT(" to Foliage"); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + InstancedFoliageActor->RegisterAllComponents(); + + // Update / repopulate the foliage editor mode's mesh list + if (CurrentInstanceCount > 0) + FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); + + return true; +} bool FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent) { - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return false; for (int32 n = 0; n < HoudiniAssetComponent->GetNumOutputs(); ++n) { UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(n); - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) continue; if (Output->GetType() != EHoudiniOutputType::Instancer) @@ -478,215 +780,103 @@ FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComp bool FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) { - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor * OwnerActor = HoudiniAssetComponent->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return false; - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return false; - - int32 BakedCount = 0; + TMap AlreadyBakedMaterialsMap; + FHoudiniEngineOutputStats BakeStats; TArray PackagesToSave; FTransform HoudiniAssetTransform = HoudiniAssetComponent->GetComponentTransform(); // Build an array of the outputs so that we can search for meshes/previous baked meshes - const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); TArray Outputs; - Outputs.Reserve(NumOutputs); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) - { - UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(OutputIdx); - if (!Output || Output->IsPendingKill()) - continue; - - Outputs.Add(Output); - } - + HoudiniAssetComponent->GetOutputs(Outputs); + const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); + // Get the previous bake outputs and match the output array size TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); if (BakedOutputs.Num() != NumOutputs) BakedOutputs.SetNum(NumOutputs); + + TArray AllBakedActors; + bool bSuccess = true; // Map storing original and baked Static Meshes - TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) { - UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(OutputIdx); - if (!Output || Output->IsPendingKill()) + UHoudiniOutput* Output = Outputs[OutputIdx]; + if (!IsValid(Output)) continue; if (Output->GetType() != EHoudiniOutputType::Instancer) continue; - // TODO: No need to use the instanced outputs for this - // We should simply iterate on the Output Objects instead! TMap& OutputObjects = Output->GetOutputObjects(); - TMap& InstancedOutputs = Output->GetInstancedOutputs(); - for (auto & Pair : InstancedOutputs) + const TMap& OldBakedOutputObjects = BakedOutputs[OutputIdx].BakedOutputObjects; + TMap NewBakedOutputObjects; + + for (auto & Pair : OutputObjects) { - FString InstanceName = OwnerActor->GetName(); - - // // See if we have a bake name for that output - // FHoudiniOutputObject* OutputObj = OutputObjects.Find(Pair.Key); - // if (OutputObj && OutputObj->BakeName.IsEmpty()) - // InstanceName = OutputObj->BakeName; - - FHoudiniInstancedOutput& InstancedOutput = Pair.Value; - for (int32 VariarionIdx = 0; VariarionIdx < InstancedOutput.VariationObjects.Num(); ++VariarionIdx) + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + FHoudiniOutputObject& OutputObject = Pair.Value; + + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); + + FHoudiniEngineBakedActor OutputBakedActorEntry; + const bool bInReplaceActors = false; + if (BakeInstancerOutputToFoliage( + HoudiniAssetComponent, + OutputIdx, + Outputs, + Identifier, + OutputObject, + BakedOutputObject, + HoudiniAssetComponent->BakeFolder, + HoudiniAssetComponent->TemporaryCookFolder, + bInReplaceActors, + bInReplaceAssets, + AllBakedActors, + OutputBakedActorEntry, + PackagesToSave, + AlreadyBakedMaterialsMap, + BakeStats)) { - // TODO: !!! what if the instanced object/var is not a static mesh!!!!!! - UObject* CurrentVariationObject = InstancedOutput.VariationObjects[VariarionIdx].Get(); - UStaticMesh* InstancedStaticMesh = Cast(CurrentVariationObject); - if (!InstancedStaticMesh) - { - if (CurrentVariationObject) - { - HOUDINI_LOG_ERROR(TEXT("Failed to bake the instances of %s to Foliage"), *CurrentVariationObject->GetName()); - } - continue; - } - - // Check if we have already handled this mesh (already baked it from a previous variation), if so, - // use that - UStaticMesh* OutStaticMesh = nullptr; - bool bCreateNewType = true; - if (OriginalToBakedMesh.Contains(InstancedStaticMesh)) - { - OutStaticMesh = OriginalToBakedMesh.FindChecked(InstancedStaticMesh); - bCreateNewType = false; - } - - if (!IsValid(OutStaticMesh)) - { - // Find the output object and identifier for the mesh and previous bake of the mesh (if it exists) - FString ObjectName; - int32 MeshOutputIdx = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshOutputIdentifier; - UStaticMesh* PreviousBakeMesh = nullptr; - FHoudiniBakedOutputObject* BakedOutputObject = nullptr; - if (FindOutputObject(InstancedStaticMesh, Outputs, MeshOutputIdx, MeshOutputIdentifier)) - { - GetTemporaryOutputObjectBakeName(InstancedStaticMesh, Outputs, ObjectName); - - BakedOutputObject = &BakedOutputs[MeshOutputIdx].BakedOutputObjects.FindOrAdd(MeshOutputIdentifier); - if (BakedOutputObject) - { - PreviousBakeMesh = Cast(BakedOutputObject->GetBakedObjectIfValid()); - } - } - else - { - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InstancedStaticMesh); - } - - // If the instanced static mesh is still a temporary Houdini created Static Mesh - // we will duplicate/bake it first before baking to foliage - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - MeshOutputIdentifier, - HoudiniAssetComponent->BakeFolder.Path, - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); - - // DuplicateStaticMeshAndCreatePackageIfNeeded uses baked results to find a baked version of - // InstancedStaticMesh in the current bake results, but since we are already using - // OriginalToBakedMesh we don't have to populate BakedResults - const TArray BakedResults; - OutStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - InstancedStaticMesh, - PreviousBakeMesh, - PackageParams, - Outputs, - BakedResults, - HoudiniAssetComponent->TemporaryCookFolder.Path, - PackagesToSave); - OriginalToBakedMesh.Add(InstancedStaticMesh, OutStaticMesh); - - // Update our tracked baked output - if (BakedOutputObject) - BakedOutputObject->BakedObject = FSoftObjectPath(OutStaticMesh).ToString(); - - bCreateNewType = true; - } - - // See if we already have a FoliageType for that static mesh - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(OutStaticMesh); - if (!FoliageType || FoliageType->IsPendingKill()) - { - // We need to create a new FoliageType for this Static Mesh - // TODO: Add foliage default settings - InstancedFoliageActor->AddMesh(OutStaticMesh, &FoliageType); - bCreateNewType = true; - } - - // If we are baking in replace mode, remove the foliage type if it already exists - // and a create a new one - if (bInReplaceAssets && bCreateNewType && IsValid(FoliageType)) - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - - // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it - FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); - if (!FoliageInfo) - continue; - - // Apply the transform offset on the transforms for this variation - TArray ProcessedTransforms; - FHoudiniInstanceTranslator::ProcessInstanceTransforms(InstancedOutput, VariarionIdx, ProcessedTransforms); - - FFoliageInstance FoliageInstance; - int32 CurrentInstanceCount = 0; - for (auto CurrentTransform : ProcessedTransforms) - { - FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); - FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); - FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); - - FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); - CurrentInstanceCount++; - } - - // TODO: This was due to a bug in UE4.22-20, check if still needed! - if (FoliageInfo->GetComponent()) - FoliageInfo->GetComponent()->BuildTreeIfOutdated(true, true); - - // Notify the user that we succesfully bake the instances to foliage - FString Notification = TEXT("Successfully baked ") + FString::FromInt(CurrentInstanceCount) + TEXT(" instances of ") + OutStaticMesh->GetName() + TEXT(" to Foliage"); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - BakedCount += ProcessedTransforms.Num(); + AllBakedActors.Add(OutputBakedActorEntry); + } + else + { + bSuccess = false; } } - } - InstancedFoliageActor->RegisterAllComponents(); + // Update the cached baked output data + BakedOutputs[OutputIdx].BakedOutputObjects = NewBakedOutputObjects; + } - // Update / repopulate the foliage editor mode's mesh list - FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); - - if (BakedCount > 0) + if (PackagesToSave.Num() > 0) { FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - return true; } - return false; + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + // Broadcast that the bake is complete + HoudiniAssetComponent->HandleOnPostBake(bSuccess); + + return bSuccess; } bool FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, TArray& InBakedOutputs, @@ -695,8 +885,11 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, TArray const* InInstancerComponentTypesToBake, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) @@ -705,20 +898,30 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( return false; UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; - TMap& OutputObjects = InOutput->GetOutputObjects(); - // Ensure we have the same number of baked outputs and asset outputs if (InBakedOutputs.Num() != InAllOutputs.Num()) InBakedOutputs.SetNum(InAllOutputs.Num()); + + TMap& OutputObjects = InOutput->GetOutputObjects(); + const TMap& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; + TMap NewBakedOutputObjects; + + TArray AllBakedActors = InBakedActors; + TArray NewBakedActors; + TArray OutputBakedActors; // Iterate on the output objects, baking their object/component as we go for (auto& Pair : OutputObjects) { + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; FHoudiniOutputObject& CurrentOutputObject = Pair.Value; - FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputs[InOutputIndex].BakedOutputObjects.FindOrAdd(Pair.Key); + + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); if (CurrentOutputObject.bProxyIsCurrent) { @@ -726,17 +929,69 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( // ?? } - if (!CurrentOutputObject.OutputComponent || CurrentOutputObject.OutputComponent->IsPendingKill()) + if (!IsValid(CurrentOutputObject.OutputComponent)) continue; + OutputBakedActors.Reset(); + if (CurrentOutputObject.OutputComponent->IsA()) { - // TODO: Baking foliage instancer to actors it not supported currently + // Bake foliage as foliage + if (!InInstancerComponentTypesToBake || + InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageInstancedStaticMeshComponent)) + { + FHoudiniEngineBakedActor BakedActorEntry; + if (BakeInstancerOutputToFoliage( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + AllBakedActors, + BakedActorEntry, + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, + OutBakeStats)) + { + OutputBakedActors.Add(BakedActorEntry); + } + } + else if (!InInstancerComponentTypesToBake || + InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent)) + { + BakeInstancerOutputToActors_ISMC( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + AllBakedActors, + OutputBakedActors, + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, + OutBakeStats, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } } else if (CurrentOutputObject.OutputComponent->IsA() && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedStaticMeshComponent))) { BakeInstancerOutputToActors_ISMC( + HoudiniAssetComponent, InOutputIndex, InAllOutputs, // InBakedOutputs, @@ -748,8 +1003,11 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( InTempCookFolder, bInReplaceActors, bInReplaceAssets, - OutActors, + AllBakedActors, + OutputBakedActors, OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, + OutBakeStats, InFallbackActor, InFallbackWorldOutlinerFolder); } @@ -757,6 +1015,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedActorComponent))) { BakeInstancerOutputToActors_IAC( + HoudiniAssetComponent, InOutputIndex, Pair.Key, CurrentOutputObject, @@ -764,60 +1023,87 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( InBakeFolder, bInReplaceActors, bInReplaceAssets, - OutActors, - OutPackagesToSave); + AllBakedActors, + OutputBakedActors, + OutPackagesToSave, + OutBakeStats); } else if (CurrentOutputObject.OutputComponent->IsA() && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::MeshSplitInstancerComponent))) { - BakeInstancerOutputToActors_MSIC( - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InTransform, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); + FHoudiniEngineBakedActor BakedActorEntry; + if (BakeInstancerOutputToActors_MSIC( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + AllBakedActors, + BakedActorEntry, + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, + OutBakeStats, + InFallbackActor, + InFallbackWorldOutlinerFolder)) + { + OutputBakedActors.Add(BakedActorEntry); + } } else if (CurrentOutputObject.OutputComponent->IsA() && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::StaticMeshComponent))) { - BakeInstancerOutputToActors_SMC( - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); + FHoudiniEngineBakedActor BakedActorEntry; + if (BakeInstancerOutputToActors_SMC( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + AllBakedActors, + BakedActorEntry, + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, + OutBakeStats, + InFallbackActor, + InFallbackWorldOutlinerFolder)) + { + OutputBakedActors.Add(BakedActorEntry); + } + } else { // Unsupported component! } + AllBakedActors.Append(OutputBakedActors); + NewBakedActors.Append(OutputBakedActors); } + // Update the cached baked output data + InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects; + + OutActors = MoveTemp(NewBakedActors); + return true; } bool FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, // const TArray& InAllBakedOutputs, @@ -829,78 +1115,108 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { UInstancedStaticMeshComponent * InISMC = Cast(InOutputObject.OutputComponent); - if (!InISMC || InISMC->IsPendingKill()) + if (!IsValid(InISMC)) return false; AActor * OwnerActor = InISMC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) return false; UStaticMesh * StaticMesh = InISMC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return false; - // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its - // name from its package. - FString ObjectName; - if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) - { - // Not found in HDA/temp outputs, use its package name - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - } - - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - - // See if the instanced static mesh is still a temporary Houdini created Static Mesh - // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams PackageParams; + // Certain SMC materials may need to be duplicated if we didn't generate the mesh object. + TArray DuplicatedISMCOverrideMaterials; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - // ObjectName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - // This will bake/duplicate the mesh if temporary, or return the input one if it is not + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, PackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + + // Construct PackageParams for the instancer itself. When baking to actor we technically won't create a stand-alone + // disk package for the instancer, but certain attributes (such as level path) use tokens populated from the + // package params. + FHoudiniPackageParams InstancerPackageParams; + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + InstancerPackageParams, InstancerResolver, InBakeFolder.Path, AssetPackageReplaceMode); + + const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + MeshPackageParams, MeshResolver, InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, InBakedActors, InTempCookFolder.Path, + OutPackagesToSave, InOutAlreadyBakedMaterialsMap, OutBakeStats); + } + else + { + BakedStaticMesh = StaticMesh; + + + // We still need to duplicate materials, if they are temporary. + TArray Materials = InISMC->GetMaterials(); + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) + { + UMaterialInterface* MaterialInterface = Materials[MaterialIdx]; + if (!IsValid(MaterialInterface)) + continue; + + // Only duplicate the material if it is temporary + if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InAllOutputs, InTempCookFolder.Path)) + { + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage( + MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap, OutBakeStats); + DuplicatedISMCOverrideMaterials.Add(DuplicatedMaterial); + } + } + } // Update the baked object InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - + + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath( + InOutputObject, + FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InstancerPackageParams.HoudiniAssetActorName : InFallbackWorldOutlinerFolder)); + // By default spawn in the current level unless specified via the unreal_level_path attribute ULevel* DesiredLevel = GWorld->GetCurrentLevel(); bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); if (bHasLevelPathAttribute) { - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); bool bCreatedPackage = false; if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( @@ -917,6 +1233,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( // TODO: ? always add? if (bCreatedPackage && DesiredLevel) { + OutBakeStats.NotifyPackageCreated(1); + OutBakeStats.NotifyObjectsCreated(DesiredLevel->GetClass()->GetName(), 1); // We can now save the package again, and unload it. OutPackagesToSave.Add(DesiredLevel->GetOutermost()); } @@ -929,7 +1247,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( FName BakeActorName; AActor* FoundActor = nullptr; bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, InBakedActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) return false; /* @@ -968,34 +1286,37 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( if (!FoundActor) { - FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InstanceTransform, RF_Transactional); - if (!FoundActor || FoundActor->IsPendingKill()) + FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InstanceTransform); + if (!IsValid(FoundActor)) continue; } - FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, SMFactory->NewActorClass, BakeActorName, FoundActor); - // FoundActor->Rename(*NewName.ToString()); - // FoundActor->SetActorLabel(NewName.ToString()); - RenameAndRelabelActor(FoundActor, NewName.ToString(), false); + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, SMFactory->NewActorClass, BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, NewNameStr, false); // The folder is named after the original actor and contains all generated actors SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) + if (!IsValid(SMActor)) continue; // Copy properties from the existing component CopyPropertyToNewActorAndComponent(FoundActor, SMActor->GetStaticMeshComponent(), InISMC); - OutActors.Add(FHoudiniEngineBakedActor( + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, InOutputObjectIdentifier, BakedStaticMesh, - StaticMesh)); + StaticMesh, + SMActor->GetStaticMeshComponent(), + bFoundMeshOutput ? MeshPackageParams.BakeFolder : FString(), + MeshPackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; } } else @@ -1007,16 +1328,18 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( FActorSpawnParameters SpawnInfo; SpawnInfo.OverrideLevel = DesiredLevel; SpawnInfo.ObjectFlags = RF_Transactional; - SpawnInfo.Name = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName); + SpawnInfo.Name = FName(MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString())); SpawnInfo.bDeferConstruction = true; // Spawn the new Actor FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); - if (!FoundActor || FoundActor->IsPendingKill()) + if (!IsValid(FoundActor)) return false; bSpawnedActor = true; - FoundActor->SetActorLabel(FoundActor->GetName()); + OutBakeStats.NotifyObjectsCreated(FoundActor->GetClass()->GetName(), 1); + + FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName()); FoundActor->SetActorHiddenInGame(InISMC->bHiddenInGame); } else @@ -1029,8 +1352,10 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( RemovePreviouslyBakedComponent(InPrevComponent); } - const FName UniqueActorName = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName, FoundActor); - RenameAndRelabelActor(FoundActor, UniqueActorName.ToString(), false); + const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false); + + OutBakeStats.NotifyObjectsUpdated(FoundActor->GetClass()->GetName(), 1); } // The folder is named after the original actor and contains all generated actors @@ -1047,17 +1372,35 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( UHierarchicalInstancedStaticMeshComponent* InHISMC = Cast(InISMC); if (InHISMC) { - NewISMC = DuplicateObject( - InHISMC, - FoundActor, - MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetFName())); + // Handle foliage: don't duplicate foliage component, create a new hierarchical one and copy what we can + // from the foliage component + if (InHISMC->IsA()) + { + NewISMC = NewObject( + FoundActor, + FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName()))); + CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); + + OutBakeStats.NotifyObjectsCreated(UHierarchicalInstancedStaticMeshComponent::StaticClass()->GetName(), 1); + } + else + { + NewISMC = DuplicateObject( + InHISMC, + FoundActor, + FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName()))); + + OutBakeStats.NotifyObjectsCreated(InHISMC->GetClass()->GetName(), 1); + } } else { NewISMC = DuplicateObject( InISMC, FoundActor, - MakeUniqueObjectNameIfNeeded(FoundActor, InISMC->GetClass(), InISMC->GetFName())); + FName(MakeUniqueObjectNameIfNeeded(FoundActor, InISMC->GetClass(), InISMC->GetName()))); + + OutBakeStats.NotifyObjectsCreated(InISMC->GetClass()->GetName(), 1); } if (!NewISMC) @@ -1072,6 +1415,19 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( // NewISMC->SetupAttachment(nullptr); NewISMC->SetStaticMesh(BakedStaticMesh); FoundActor->AddInstanceComponent(NewISMC); + + if (DuplicatedISMCOverrideMaterials.Num() > 0) + { + UMaterialInterface * InstancerMaterial = DuplicatedISMCOverrideMaterials[0]; + if (InstancerMaterial) + { + NewISMC->OverrideMaterials.Empty(); + int32 MeshMaterialCount = BakedStaticMesh->GetStaticMaterials().Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + NewISMC->SetMaterial(Idx, InstancerMaterial); + } + } + // NewActor->SetRootComponent(NewISMC); if (IsValid(RootComponent)) NewISMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); @@ -1085,18 +1441,22 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( FoundActor->FinishSpawning(InTransform); InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, InOutputObjectIdentifier, BakedStaticMesh, - StaticMesh)); + StaticMesh, + NewISMC, + bFoundMeshOutput ? MeshPackageParams.BakeFolder : FString(), + MeshPackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; - FoundActor->InvalidateLightingCache(); - FoundActor->PostEditMove(true); - FoundActor->MarkPackageDirty(); + // Postpone post-bake calls to do them once per actor + OutActors.Last().bPostBakeProcessPostponed = true; } // If we are baking in replace mode, remove previously baked components/instancers @@ -1114,6 +1474,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( bool FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, // const TArray& InAllBakedOutputs, @@ -1124,79 +1485,111 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, - TArray& OutActors, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { UStaticMeshComponent* InSMC = Cast(InOutputObject.OutputComponent); - if (!InSMC || InSMC->IsPendingKill()) + if (!IsValid(InSMC)) return false; AActor* OwnerActor = InSMC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) return false; UStaticMesh* StaticMesh = InSMC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return false; - // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its - // name from its package. - FString ObjectName; - if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) - { - // Not found in HDA/temp outputs, use its package name - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - } + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - // BaseName holds the Actor / HDA name - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + // Certain SMC materials may need to be duplicated if we didn't generate the mesh object. + TArray DuplicatedSMCOverrideMaterials; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + // Package params for the instancer // See if the instanced static mesh is still a temporary Houdini created Static Mesh // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - // BaseName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); + FHoudiniPackageParams InstancerPackageParams; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + InstancerPackageParams, InstancerResolver, InBakeFolder.Path, AssetPackageReplaceMode); + + const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + MeshPackageParams, MeshResolver, InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, InBakedActors, InTempCookFolder.Path, + OutPackagesToSave, InOutAlreadyBakedMaterialsMap, OutBakeStats); + } + else + { + BakedStaticMesh = StaticMesh; - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, Cast(InBakedOutputObject.GetBakedObjectIfValid()), PackageParams, InAllOutputs, - OutActors, InTempCookFolder.Path, OutPackagesToSave); + // We still need to duplicate materials, if they are temporary. + TArray Materials = InSMC->GetMaterials(); + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) + { + UMaterialInterface* MaterialInterface = Materials[MaterialIdx]; + if (!IsValid(MaterialInterface)) + continue; + + // Only duplicate the material if it is temporary + if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InAllOutputs, InTempCookFolder.Path)) + { + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage( + MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap, OutBakeStats); + DuplicatedSMCOverrideMaterials.Add(DuplicatedMaterial); + } + } + } // Update the previous baked object InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath( + InOutputObject, + FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InstancerPackageParams.HoudiniAssetActorName : InFallbackWorldOutlinerFolder)); + + + // By default spawn in the current level unless specified via the unreal_level_path attribute ULevel* DesiredLevel = GWorld->GetCurrentLevel(); bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); if (bHasLevelPathAttribute) { - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); bool bCreatedPackage = false; if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( @@ -1213,6 +1606,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( // TODO: ? always add? if (bCreatedPackage && DesiredLevel) { + OutBakeStats.NotifyPackageCreated(1); + OutBakeStats.NotifyObjectsCreated(DesiredLevel->GetClass()->GetName(), 1); // We can now save the package again, and unload it. OutPackagesToSave.Add(DesiredLevel->GetOutermost()); } @@ -1225,7 +1620,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( FName BakeActorName; AActor* FoundActor = nullptr; bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, InBakedActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) return false; UStaticMeshComponent* StaticMeshComponent = nullptr; @@ -1237,12 +1632,14 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( if (!SMFactory) return false; - FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); - if (!FoundActor || FoundActor->IsPendingKill()) + FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InSMC->GetComponentTransform()); + if (!IsValid(FoundActor)) return false; + OutBakeStats.NotifyObjectsCreated(FoundActor->GetClass()->GetName(), 1); + AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) + if (!IsValid(SMActor)) return false; StaticMeshComponent = SMActor->GetStaticMeshComponent(); @@ -1271,13 +1668,13 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( FoundActor->AddInstanceComponent(StaticMeshComponent); StaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); StaticMeshComponent->RegisterComponent(); + + OutBakeStats.NotifyObjectsCreated(StaticMeshComponent->GetClass()->GetName(), 1); } } - FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, FoundActor->GetClass(), BakeActorName, FoundActor); - // FoundActor->Rename(*NewName.ToString()); - // FoundActor->SetActorLabel(NewName.ToString()); - RenameAndRelabelActor(FoundActor, NewName.ToString(), false); + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, FoundActor->GetClass(), BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, NewNameStr, false); // The folder is named after the original actor and contains all generated actors SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); @@ -1289,19 +1686,39 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( return false; // Copy properties from the existing component - CopyPropertyToNewActorAndComponent(FoundActor, StaticMeshComponent, InSMC); + const bool bCopyWorldTransform = true; + CopyPropertyToNewActorAndComponent(FoundActor, StaticMeshComponent, InSMC, bCopyWorldTransform); StaticMeshComponent->SetStaticMesh(BakedStaticMesh); + + if (DuplicatedSMCOverrideMaterials.Num() > 0) + { + UMaterialInterface * InstancerMaterial = DuplicatedSMCOverrideMaterials[0]; + if (InstancerMaterial) + { + StaticMeshComponent->OverrideMaterials.Empty(); + int32 MeshMaterialCount = BakedStaticMesh->GetStaticMaterials().Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + StaticMeshComponent->SetMaterial(Idx, InstancerMaterial); + } + } InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( + FHoudiniEngineBakedActor OutputEntry( FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, InOutputObjectIdentifier, BakedStaticMesh, - StaticMesh)); - + StaticMesh, + StaticMeshComponent, + MeshPackageParams.BakeFolder, + MeshPackageParams); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; + + OutBakedActorEntry = OutputEntry; + // If we are baking in replace mode, remove previously baked components/instancers if (bInReplaceActors && bInReplaceAssets) { @@ -1317,6 +1734,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( bool FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, const FHoudiniOutputObject& InOutputObject, @@ -1324,53 +1742,44 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( const FDirectoryPath& InBakeFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, - TArray& OutPackagesToSave) + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) { UHoudiniInstancedActorComponent* InIAC = Cast(InOutputObject.OutputComponent); - if (!InIAC || InIAC->IsPendingKill()) + if (!IsValid(InIAC)) return false; - AActor * OwnerActor = InIAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + AActor* OwnerActor = InIAC->GetOwner(); + if (!IsValid(OwnerActor)) return false; - // BaseName holds the Actor / HDA name - const FName BaseName = FName(OwnerActor->GetName()); - // Get the object instanced by this IAC UObject* InstancedObject = InIAC->GetInstancedObject(); - if (!InstancedObject || InstancedObject->IsPendingKill()) + if (!IsValid(InstancedObject)) return false; + // Set the default object name to the + const FString DefaultObjectName = InstancedObject->GetName(); + FHoudiniPackageParams PackageParams; const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - BaseName.ToString(), - OwnerActor->GetName(), - AssetPackageReplaceMode); + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, DefaultObjectName, + PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); // By default spawn in the current level unless specified via the unreal_level_path attribute - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; ULevel* DesiredLevel = GWorld->GetCurrentLevel(); bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); if (bHasLevelPathAttribute) { - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - // Get the package path from the unreal_level_apth attribute FString LevelPackagePath = Resolver.ResolveFullLevelPath(); @@ -1389,6 +1798,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( // TODO: ? always add? if (bCreatedPackage && DesiredLevel) { + OutBakeStats.NotifyPackageCreated(1); + OutBakeStats.NotifyObjectsCreated(DesiredLevel->GetClass()->GetName(), 1); // We can now save the package again, and unload it. OutPackagesToSave.Add(DesiredLevel->GetOutermost()); } @@ -1397,6 +1808,69 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( if (!DesiredLevel) return false; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *PackageParams.HoudiniAssetActorName); + + // Try to find the unreal_bake_actor, if specified. If we found the actor, we will attach the instanced actors + // to it. If we did not find an actor, but unreal_bake_actor was set, then we create a new actor with that name + // and parent the instanced actors to it. Otherwise, we don't attach the instanced actors to anything. + FName DesiredParentBakeActorName; + FName ParentBakeActorName; + AActor* ParentToActor = nullptr; + bool bHasBakeActorName = false; + constexpr AActor* FallbackActor = nullptr; + const FName DefaultBakeActorName = NAME_None; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, InBakedActors, DesiredLevel, DefaultBakeActorName, bInReplaceActors, FallbackActor, ParentToActor, bHasBakeActorName, DesiredParentBakeActorName)) + { + HOUDINI_LOG_ERROR(TEXT("Finding / processing unreal_bake_actor unexpectedly failed during bake.")); + return false; + } + + OutActors.Reset(); + + if (!ParentToActor && bHasBakeActorName) + { + // We need to create an actor to attached the instanced actors to + UActorFactory* const ActorFactory = GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()); + if (!ActorFactory) + { + HOUDINI_LOG_ERROR(TEXT("Could not find actor factory %s."), IsValid(UActorFactoryEmptyActor::StaticClass()) ? *(UActorFactoryEmptyActor::StaticClass()->GetName()) : TEXT("null")); + return false; + } + + constexpr UObject* AssetToSpawn = nullptr; + constexpr EObjectFlags ObjectFlags = RF_Transactional; + ParentBakeActorName = *MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), DesiredParentBakeActorName.ToString()); + ParentToActor = ActorFactory->CreateActor(AssetToSpawn, DesiredLevel, InIAC->GetComponentTransform(), ObjectFlags, ParentBakeActorName); + + if (!IsValid(ParentToActor)) + { + ParentToActor = nullptr; + } + else + { + OutBakeStats.NotifyObjectsCreated(ParentToActor->GetClass()->GetName(), 1); + + ParentToActor->SetActorLabel(ParentBakeActorName.ToString()); + OutActors.Emplace(FHoudiniEngineBakedActor( + ParentToActor, + DesiredParentBakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + nullptr, // InBakedObject + nullptr, // InSourceObject + nullptr, // InBakedComponent + PackageParams.BakeFolder, + PackageParams)); + } + } + + if (ParentToActor) + { + InBakedOutputObject.ActorBakeName = DesiredParentBakeActorName; + InBakedOutputObject.Actor = FSoftObjectPath(ParentToActor).ToString(); + } + // If we are baking in actor replacement mode, remove any previously baked instanced actors for this output if (bInReplaceActors && InBakedOutputObject.InstancedActors.Num() > 0) { @@ -1414,6 +1888,9 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( // Destroy Actor if it is valid and part of DesiredLevel if (IsValid(Actor) && Actor->GetLevel() == DesiredLevel) { + // Just before we destroy the actor, rename it with a _DELETE suffix, so that we can re-use + // its original name before garbage collection + FHoudiniEngineUtils::SafeRenameActor(Actor, Actor->GetName() + TEXT("_DELETE")); #if WITH_EDITOR LevelWorld->EditorDestroyActor(Actor, true); #else @@ -1430,33 +1907,43 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( // Iterates on all the instances of the IAC for (AActor* CurrentInstancedActor : InIAC->GetInstancedActors()) { - if (!CurrentInstancedActor || CurrentInstancedActor->IsPendingKill()) + if (!IsValid(CurrentInstancedActor)) continue; - FName NewInstanceName = MakeUniqueObjectNameIfNeeded(DesiredLevel, InstancedObject->StaticClass(), BaseName); - FString NewNameStr = NewInstanceName.ToString(); + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, CurrentInstancedActor->GetClass(), PackageParams.ObjectName); FTransform CurrentTransform = CurrentInstancedActor->GetTransform(); AActor* NewActor = FHoudiniInstanceTranslator::SpawnInstanceActor(CurrentTransform, DesiredLevel, InIAC); - if (!NewActor || NewActor->IsPendingKill()) + if (!IsValid(NewActor)) continue; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, BaseName); + OutBakeStats.NotifyObjectsCreated(NewActor->GetClass()->GetName(), 1); + + EditorUtilities::CopyActorProperties(CurrentInstancedActor, NewActor); + + FHoudiniEngineUtils::SafeRenameActor(NewActor, NewNameStr); - NewActor->SetActorLabel(NewNameStr); SetOutlinerFolderPath(NewActor, InOutputObject, WorldOutlinerFolderPath); NewActor->SetActorTransform(CurrentTransform); + if (ParentToActor) + NewActor->AttachToActor(ParentToActor, FAttachmentTransformRules::KeepWorldTransform); + InBakedOutputObject.InstancedActors.Add(FSoftObjectPath(NewActor).ToString()); - OutActors.Add(FHoudiniEngineBakedActor( + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( NewActor, - BaseName, + *PackageParams.ObjectName, WorldOutlinerFolderPath, InOutputIndex, InOutputObjectIdentifier, nullptr, - InstancedObject)); + InstancedObject, + nullptr, + PackageParams.BakeFolder, + PackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = PackageParams; } // TODO: @@ -1477,6 +1964,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( bool FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, // const TArray& InAllBakedOutputs, @@ -1488,80 +1976,113 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, - TArray& OutActors, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { UHoudiniMeshSplitInstancerComponent * InMSIC = Cast(InOutputObject.OutputComponent); - if (!InMSIC || InMSIC->IsPendingKill()) + if (!IsValid(InMSIC)) return false; AActor * OwnerActor = InMSIC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) return false; UStaticMesh * StaticMesh = InMSIC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return false; - // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its - // name from its package. - FString ObjectName; - if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) - { - // Not found in HDA/temp outputs, use its package name - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - } + // Certain SMC materials may need to be duplicated if we didn't generate the mesh object. + TArray DuplicatedMSICOverrideMaterials; - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; // See if the instanced static mesh is still a temporary Houdini created Static Mesh // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - // BaseName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); + FHoudiniPackageParams InstancerPackageParams; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + InstancerPackageParams, InstancerResolver, InBakeFolder.Path, AssetPackageReplaceMode); + + const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + MeshPackageParams, MeshResolver, InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, InBakedActors, InTempCookFolder.Path, + OutPackagesToSave, InOutAlreadyBakedMaterialsMap, OutBakeStats); + } + else + { + BakedStaticMesh = StaticMesh; - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, Cast(InBakedOutputObject.GetBakedObjectIfValid()), PackageParams, InAllOutputs, - OutActors, InTempCookFolder.Path, OutPackagesToSave); + + // We still need to duplicate materials, if they are temporary. + TArray Materials = InMSIC->GetOverrideMaterials(); + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) + { + UMaterialInterface* MaterialInterface = Materials[MaterialIdx]; + if (!IsValid(MaterialInterface)) + continue; + + // Only duplicate the material if it is temporary + if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InAllOutputs, InTempCookFolder.Path)) + { + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage( + MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap, OutBakeStats); + DuplicatedMSICOverrideMaterials.Add(DuplicatedMaterial); + } + } + } // Update the baked output InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - + + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath( + InOutputObject, + FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InstancerPackageParams.HoudiniAssetActorName : InFallbackWorldOutlinerFolder)); + + + // By default spawn in the current level unless specified via the unreal_level_path attribute ULevel* DesiredLevel = GWorld->GetCurrentLevel(); bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); if (bHasLevelPathAttribute) { - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - - // Get the level specified by attribute - // Access some of the attributes that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - // Get the package path from the unreal_level_path attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); bool bCreatedPackage = false; if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( @@ -1578,6 +2099,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( // TODO: ? always add? if (bCreatedPackage && DesiredLevel) { + OutBakeStats.NotifyPackageCreated(1); + OutBakeStats.NotifyObjectsCreated(DesiredLevel->GetClass()->GetName(), 1); // We can now save the package again, and unload it. OutPackagesToSave.Add(DesiredLevel->GetOutermost()); } @@ -1591,7 +2114,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( AActor* FoundActor = nullptr; bool bHasBakeActorName = false; bool bSpawnedActor = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, InBakedActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) return false; if (!FoundActor) @@ -1600,16 +2123,19 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( FActorSpawnParameters SpawnInfo; SpawnInfo.OverrideLevel = DesiredLevel; SpawnInfo.ObjectFlags = RF_Transactional; - SpawnInfo.Name = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName); + SpawnInfo.Name = FName(MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString())); SpawnInfo.bDeferConstruction = true; // Spawn the new Actor FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); - if (!FoundActor || FoundActor->IsPendingKill()) + if (!IsValid(FoundActor)) return false; bSpawnedActor = true; - FoundActor->SetActorLabel(FoundActor->GetName()); + OutBakeStats.NotifyObjectsCreated(FoundActor->GetClass()->GetName(), 1); + + FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName()); + FoundActor->SetActorHiddenInGame(InMSIC->bHiddenInGame); } else @@ -1629,8 +2155,10 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( RemovePreviouslyBakedComponent(PrevComponent); } - const FName UniqueActorName = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName, FoundActor); - RenameAndRelabelActor(FoundActor, UniqueActorName.ToString(), false); + const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false); + + OutBakeStats.NotifyObjectsUpdated(FoundActor->GetClass()->GetName(), 1); } // The folder is named after the original actor and contains all generated actors SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); @@ -1647,16 +2175,18 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( // Now add s SMC component for each of the SMC's instance for (UStaticMeshComponent* CurrentSMC : InMSIC->GetInstances()) { - if (!CurrentSMC || CurrentSMC->IsPendingKill()) + if (!IsValid(CurrentSMC)) continue; UStaticMeshComponent* NewSMC = DuplicateObject( CurrentSMC, FoundActor, - MakeUniqueObjectNameIfNeeded(FoundActor, CurrentSMC->GetClass(), CurrentSMC->GetFName())); - if (!NewSMC || NewSMC->IsPendingKill()) + FName(MakeUniqueObjectNameIfNeeded(FoundActor, CurrentSMC->GetClass(), CurrentSMC->GetName()))); + if (!IsValid(NewSMC)) continue; + OutBakeStats.NotifyObjectsCreated(NewSMC->GetClass()->GetName(), 1); + InBakedOutputObject.InstancedComponents.Add(FSoftObjectPath(NewSMC).ToString()); NewSMC->RegisterComponent(); @@ -1664,6 +2194,19 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( NewSMC->SetStaticMesh(BakedStaticMesh); FoundActor->AddInstanceComponent(NewSMC); NewSMC->SetWorldTransform(CurrentSMC->GetComponentTransform()); + + if (DuplicatedMSICOverrideMaterials.Num() > 0) + { + UMaterialInterface * InstancerMaterial = DuplicatedMSICOverrideMaterials[0]; + if (InstancerMaterial) + { + NewSMC->OverrideMaterials.Empty(); + int32 MeshMaterialCount = BakedStaticMesh->GetStaticMaterials().Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + NewSMC->SetMaterial(Idx, InstancerMaterial); + } + } + if (IsValid(RootComponent)) NewSMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform); @@ -1676,18 +2219,24 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( FoundActor->FinishSpawning(InTransform); InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( + FHoudiniEngineBakedActor OutputEntry( FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, InOutputObjectIdentifier, BakedStaticMesh, - StaticMesh)); + StaticMesh, + nullptr, + MeshPackageParams.BakeFolder, + MeshPackageParams); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; + + // Postpone these calls to do them once per actor + OutputEntry.bPostBakeProcessPostponed = true; - FoundActor->InvalidateLightingCache(); - FoundActor->PostEditMove(true); - FoundActor->MarkPackageDirty(); + OutBakedActorEntry = OutputEntry; // If we are baking in replace mode, remove previously baked components/instancers if (bInReplaceActors && bInReplaceAssets) @@ -1758,6 +2307,7 @@ FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( bool FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( const UObject* InObject, + EHoudiniOutputType InOutputType, const TArray& InAllOutputs, FString& OutBakeName) { @@ -1768,7 +2318,7 @@ FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( int32 MeshOutputIdx = INDEX_NONE; FHoudiniOutputObjectIdentifier MeshIdentifier; - if (FindOutputObject(InObject, InAllOutputs, MeshOutputIdx, MeshIdentifier)) + if (FindOutputObject(InObject, InOutputType, InAllOutputs, MeshOutputIdx, MeshIdentifier)) { // Found the mesh, get its name const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIdx]->GetOutputObjects().FindChecked(MeshIdentifier); @@ -1782,24 +2332,31 @@ FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( bool FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, TArray& InBakedOutputs, - const FString& InHoudiniAssetName, const FDirectoryPath& InBakeFolder, const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { + // Check that index is not negative + if (InOutputIndex < 0) + return false; + if (!InAllOutputs.IsValidIndex(InOutputIndex)) return false; UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; @@ -1807,27 +2364,34 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( return false; TMap& OutputObjects = InOutput->GetOutputObjects(); -const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); + const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); // Get the previous bake objects - if (InOutputIndex >= 0 && !InBakedOutputs.IsValidIndex(InOutputIndex)) + if (!InBakedOutputs.IsValidIndex(InOutputIndex)) InBakedOutputs.SetNum(InOutputIndex + 1); - TMap& BakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; + + const TMap& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; + TMap NewBakedOutputObjects; + + TArray AllBakedActors = InBakedActors; + TArray NewBakedActors; for (auto& Pair : OutputObjects) { const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; const FHoudiniOutputObject& OutputObject = Pair.Value; - // Fetch previous bake output - FHoudiniBakedOutputObject& BakedOutputObject = BakedOutputObjects.FindOrAdd(Identifier); + // Add a new baked output object entry and update it with the previous bake's data, if available + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); UStaticMesh* StaticMesh = Cast(OutputObject.OutputObject); - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) continue; UStaticMeshComponent* InSMC = Cast(OutputObject.OutputComponent); - if (!InSMC || InSMC->IsPendingKill()) + if (!IsValid(InSMC)) continue; // Find the HGPO that matches this output identifier @@ -1838,76 +2402,30 @@ const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects( if (FoundHGPO && FoundHGPO->bIsTemplated) continue; - FHoudiniAttributeResolver Resolver; - Resolver.SetCachedAttributes(OutputObject.CachedAttributes); - Resolver.SetTokensFromStringMap(OutputObject.CachedTokens); const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - // The default output name (if not set via attributes) is {object_name}, which look for an object_name - // key-value token - if (!Resolver.GetCachedTokens().Contains(TEXT("object_name"))) - Resolver.SetToken(TEXT("object_name"), DefaultObjectName); - - // The bake name override has priority - FString SMName = OutputObject.BakeName; - if (SMName.IsEmpty()) - { - // // ... finally the part name - // if (FoundHGPO && FoundHGPO->bHasCustomPartName) - // SMName = FoundHGPO->PartName; - // else - SMName = Resolver.ResolveOutputName(); - if (SMName.IsEmpty()) - SMName = DefaultObjectName; - } - - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, Identifier, InBakeFolder.Path, SMName, - InHoudiniAssetName, AssetPackageReplaceMode); - - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(OutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InHoudiniAssetName : InFallbackWorldOutlinerFolder)); UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - // See if this output object has an unreal_level_path attribute specified - // In which case, we need to create/find the desired level for baking instead of using the current one - bool bHasLevelPathAttribute = OutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Access some of the attribute that were cached on the output object - // FHoudiniAttributeResolver Resolver; - // const TMap& CachedAttributes = OutputObject.CachedAttributes; - TMap Tokens = OutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - // Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - continue; - } + FHoudiniPackageParams PackageParams; - // If we have created a level, add it to the packages to save - // TODO: ? always add the level to the packages to save? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } + if (!ResolvePackageParams( + HoudiniAssetComponent, + InOutput, + Identifier, + OutputObject, + DefaultObjectName, + InBakeFolder, + bInReplaceAssets, + PackageParams, + OutPackagesToSave)) + { + continue; } + + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath( + OutputObject, + FName(InFallbackWorldOutlinerFolder.IsEmpty() ? PackageParams.HoudiniAssetActorName : InFallbackWorldOutlinerFolder)); // Bake the static mesh if it is still temporary UStaticMesh* BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( @@ -1915,38 +2433,40 @@ const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects( Cast(BakedOutputObject.GetBakedObjectIfValid()), PackageParams, InAllOutputs, - OutActors, + AllBakedActors, InTempCookFolder.Path, - OutPackagesToSave); + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, + OutBakeStats); - if (!BakedSM || BakedSM->IsPendingKill()) + if (!IsValid(BakedSM)) continue; // Record the baked object BakedOutputObject.BakedObject = FSoftObjectPath(BakedSM).ToString(); // Make sure we have a level to spawn to - if (!DesiredLevel || DesiredLevel->IsPendingKill()) + if (!IsValid(DesiredLevel)) continue; // Try to find the unreal_bake_actor, if specified FName BakeActorName; AActor* FoundActor = nullptr; bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(OutputObject, BakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + if (!FindUnrealBakeActor(OutputObject, BakedOutputObject, AllBakedActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) return false; UStaticMeshComponent* SMC = nullptr; if (!FoundActor) { // Spawn the new actor - FoundActor = Factory->CreateActor(BakedSM, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); - if (!FoundActor || FoundActor->IsPendingKill()) + FoundActor = Factory->CreateActor(BakedSM, DesiredLevel, InSMC->GetComponentTransform()); + if (!IsValid(FoundActor)) continue; // Copy properties to new actor AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) + if (!IsValid(SMActor)) continue; SMC = SMActor->GetStaticMeshComponent(); @@ -1981,23 +2501,23 @@ const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects( } // We need to make a unique name for the actor, renaming an object on top of another is a fatal error - const FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, BakeActorName, FoundActor); - const FString NewNameStr = NewName.ToString(); - // FoundActor->Rename(*NewNameStr); - // FoundActor->SetActorLabel(NewNameStr); + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, BakeActorName.ToString(), FoundActor); RenameAndRelabelActor(FoundActor, NewNameStr, false); SetOutlinerFolderPath(FoundActor, OutputObject, WorldOutlinerFolderPath); if (IsValid(SMC)) { - CopyPropertyToNewActorAndComponent(FoundActor, SMC, InSMC); + const bool bCopyWorldTransform = true; + CopyPropertyToNewActorAndComponent(FoundActor, SMC, InSMC, bCopyWorldTransform); SMC->SetStaticMesh(BakedSM); BakedOutputObject.BakedComponent = FSoftObjectPath(SMC).ToString(); } BakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( - FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, Identifier, BakedSM, StaticMesh)); + const FHoudiniEngineBakedActor& BakedActorEntry = NewBakedActors.Add_GetRef(FHoudiniEngineBakedActor( + FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, Identifier, BakedSM, StaticMesh, SMC, + PackageParams.BakeFolder, PackageParams)); + AllBakedActors.Add(BakedActorEntry); // If we are baking in replace mode, remove previously baked components/instancers if (bInReplaceActors && bInReplaceAssets) @@ -2010,39 +2530,134 @@ const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects( } } + // Update the cached baked output data + InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects; + + OutActors = MoveTemp(NewBakedActors); + + return true; +} + +bool FHoudiniEngineBakeUtils::ResolvePackageParams( + const UHoudiniAssetComponent* HoudiniAssetComponent, + UHoudiniOutput* InOutput, + const FHoudiniOutputObjectIdentifier& Identifier, + const FHoudiniOutputObject& InOutputObject, + const FString& DefaultObjectName, + const FDirectoryPath& InBakeFolder, + const bool bInReplaceAssets, + FHoudiniPackageParams& OutPackageParams, + TArray& OutPackagesToSave, + const FString& InHoudiniAssetName, + const FString& InHoudiniAssetActorName) +{ + FHoudiniAttributeResolver Resolver; + + UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, Identifier, InOutputObject, DefaultObjectName, + OutPackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode, + InHoudiniAssetName, InHoudiniAssetActorName); + + // See if this output object has an unreal_level_path attribute specified + // In which case, we need to create/find the desired level for baking instead of using the current one + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_path attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add the level to the packages to save? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + return true; } bool FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( - UHoudiniOutput* Output, - TMap& InBakedOutputObjects, - const TArray& InAllBakedOutputs, - const FString& InHoudiniAssetName, + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, const FDirectoryPath& InBakeFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { - if (!Output || Output->IsPendingKill()) + // Check that index is not negative + if (InOutputIndex < 0) return false; + + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* const Output = InAllOutputs[InOutputIndex]; + if (!IsValid(Output)) + return false; + + if (!IsValid(HoudiniAssetComponent)) + return false; + + AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); + const FString HoudiniAssetActorName = IsValid(OwnerActor) ? OwnerActor->GetName() : FString(); TArray PackagesToSave; + // Find the previous baked output data for this output index. If an entry + // does not exist, create entries up to and including this output index + if (!InBakedOutputs.IsValidIndex(InOutputIndex)) + InBakedOutputs.SetNum(InOutputIndex + 1); + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniBakedOutput& BakedOutput = InBakedOutputs[InOutputIndex]; + const TMap& OldBakedOutputObjects = BakedOutput.BakedOutputObjects; + TMap NewBakedOutputObjects; + const TArray & HGPOs = Output->GetHoudiniGeoPartObjects(); + TArray AllBakedActors = InBakedActors; + TArray NewBakedActors; + for (auto & Pair : OutputObjects) { + FHoudiniOutputObject& OutputObject = Pair.Value; USplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); - if (!SplineComponent || SplineComponent->IsPendingKill()) + if (!IsValid(SplineComponent)) continue; - FHoudiniOutputObjectIdentifier & Identifier = Pair.Key; - FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputObjects.FindOrAdd(Identifier); + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); // TODO: FIX ME!! May not work 100% const FHoudiniGeoPartObject* FoundHGPO = nullptr; @@ -2060,27 +2675,38 @@ FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( if (!FoundHGPO) continue; - FString CurveName = Pair.Value.BakeName; - if (CurveName.IsEmpty()) - { - if (FoundHGPO->bHasCustomPartName) - CurveName = FoundHGPO->PartName; - else - CurveName = InHoudiniAssetName + "_" + SplineComponent->GetName(); - } + const FString DefaultObjectName = HoudiniAssetActorName + "_" + SplineComponent->GetName(); FHoudiniPackageParams PackageParams; // Set the replace mode based on if we are doing a replacement or incremental asset bake const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, Identifier, InBakeFolder.Path, CurveName, - InHoudiniAssetName, AssetPackageReplaceMode); - BakeCurve(OutputObject, BakedOutputObject, PackageParams, bInReplaceActors, bInReplaceAssets, OutActors, - PackagesToSave, InFallbackActor, InFallbackWorldOutlinerFolder); + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + UWorld* const DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, Identifier, OutputObject, DefaultObjectName, + PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); + + FHoudiniEngineBakedActor OutputBakedActor; + BakeCurve( + OutputObject, BakedOutputObject, PackageParams, Resolver, bInReplaceActors, bInReplaceAssets, + AllBakedActors, OutputBakedActor, PackagesToSave, OutBakeStats, InFallbackActor, InFallbackWorldOutlinerFolder); + + OutputBakedActor.OutputIndex = InOutputIndex; + OutputBakedActor.OutputObjectIdentifier = Identifier; + + AllBakedActors.Add(OutputBakedActor); + NewBakedActors.Add(OutputBakedActor); } + // Update the cached bake output results + BakedOutput.BakedOutputObjects = NewBakedOutputObjects; + + OutActors = MoveTemp(NewBakedActors); + SaveBakedPackages(PackagesToSave); return true; @@ -2089,10 +2715,10 @@ FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( bool FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint) { - if (!InActor || InActor->IsPendingKill()) + if (!IsValid(InActor)) return false; - if (!OutBlueprint || OutBlueprint->IsPendingKill()) + if (!IsValid(OutBlueprint)) return false; if (InActor->GetInstanceComponents().Num() > 0) @@ -2103,7 +2729,7 @@ FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBluepri if (OutBlueprint->GeneratedClass) { AActor * CDO = Cast< AActor >(OutBlueprint->GeneratedClass->GetDefaultObject()); - if (!CDO || CDO->IsPendingKill()) + if (!IsValid(CDO)) return false; const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) @@ -2113,7 +2739,7 @@ FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBluepri EditorUtilities::CopyActorProperties(InActor, CDO, CopyOptions); USceneComponent * Scene = CDO->GetRootComponent(); - if (Scene && !Scene->IsPendingKill()) + if (IsValid(Scene)) { Scene->SetRelativeLocation(FVector::ZeroVector); Scene->SetRelativeRotation(FRotator::ZeroRotator); @@ -2127,7 +2753,7 @@ FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBluepri break; USceneComponent * Component = Scene->GetAttachChildren()[ChildCount - 1]; - if (Component && !Component->IsPendingKill()) + if (IsValid(Component)) Component->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); } check(Scene->GetAttachChildren().Num() == 0); @@ -2151,12 +2777,12 @@ FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBluepri } bool -FHoudiniEngineBakeUtils::BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) +FHoudiniEngineBakeUtils::BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, bool bInRecenterBakedActors) { FHoudiniEngineOutputStats BakeStats; TArray PackagesToSave; TArray Blueprints; - const bool bSuccess = BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, BakeStats, Blueprints, PackagesToSave); + const bool bSuccess = BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, bInRecenterBakedActors, BakeStats, Blueprints, PackagesToSave); if (!bSuccess) { // TODO: ? @@ -2185,6 +2811,9 @@ FHoudiniEngineBakeUtils::BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComp TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + // Broadcast that the bake is complete + HoudiniAssetComponent->HandleOnPostBake(bSuccess); + return bSuccess; } @@ -2192,11 +2821,12 @@ bool FHoudiniEngineBakeUtils::BakeBlueprints( UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, + bool bInRecenterBakedActors, FHoudiniEngineOutputStats& InBakeStats, TArray& OutBlueprints, TArray& OutPackagesToSave) { - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return false; AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); @@ -2214,6 +2844,7 @@ FHoudiniEngineBakeUtils::BakeBlueprints( EHoudiniInstancerComponentType::StaticMeshComponent, EHoudiniInstancerComponentType::InstancedStaticMeshComponent, EHoudiniInstancerComponentType::MeshSplitInstancerComponent, + EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent }; // When baking blueprints we always create new actors since they are deleted from the world once copied into the // blueprint @@ -2238,14 +2869,16 @@ FHoudiniEngineBakeUtils::BakeBlueprints( bBakeSuccess = BakeBlueprintsFromBakedActors( Actors, - HoudiniAssetComponent->bRecenterBakedActors, + bInRecenterBakedActors, bInReplaceAssets, + IsValid(HoudiniAssetComponent->GetHoudiniAsset()) ? HoudiniAssetComponent->GetHoudiniAsset()->GetName() : FString(), bIsOwnerActorValid ? OwnerActor->GetName() : FString(), HoudiniAssetComponent->BakeFolder, &BakedOutputs, nullptr, OutBlueprints, - OutPackagesToSave); + OutPackagesToSave, + InBakeStats); return bBakeSuccess; } @@ -2255,16 +2888,19 @@ FHoudiniEngineBakeUtils::BakeStaticMesh( UStaticMesh * StaticMesh, const FHoudiniPackageParams& PackageParams, const TArray& InAllOutputs, - const FDirectoryPath& InTempCookFolder) + const FDirectoryPath& InTempCookFolder, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats) { - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return nullptr; TArray PackagesToSave; TArray Outputs; const TArray BakedResults; UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave); + StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave, + InOutAlreadyBakedMaterialsMap, OutBakeStats); if (BakedStaticMesh) { @@ -2284,20 +2920,36 @@ FHoudiniEngineBakeUtils::BakeStaticMesh( bool FHoudiniEngineBakeUtils::BakeLandscape( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, - UHoudiniOutput* InOutput, - TMap& InBakedOutputObjects, + const TArray& InAllOutputs, + TArray& InBakedOutputs, bool bInReplaceActors, bool bInReplaceAssets, - FString BakePath, - FString HoudiniAssetName, + const FString& BakePath, FHoudiniEngineOutputStats& BakeStats ) { - if (!IsValid(InOutput)) + // Check that index is not negative + if (InOutputIndex < 0) return false; - TMap& OutputObjects = InOutput->GetOutputObjects(); + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* const Output = InAllOutputs[InOutputIndex]; + if (!IsValid(Output)) + return false; + + // Find the previous baked output data for this output index. If an entry + // does not exist, create entries up to and including this output index + if (!InBakedOutputs.IsValidIndex(InOutputIndex)) + InBakedOutputs.SetNum(InOutputIndex + 1); + + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniBakedOutput& BakedOutput = InBakedOutputs[InOutputIndex]; + const TMap& OldBakedOutputObjects = BakedOutput.BakedOutputObjects; + TMap NewBakedOutputObjects; TArray PackagesToSave; TArray LandscapeWorldsToUpdate; @@ -2307,7 +2959,9 @@ FHoudiniEngineBakeUtils::BakeLandscape( { const FHoudiniOutputObjectIdentifier& ObjectIdentifier = Elem.Key; FHoudiniOutputObject& OutputObject = Elem.Value; - FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputObjects.FindOrAdd(ObjectIdentifier); + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(ObjectIdentifier); + if (OldBakedOutputObjects.Contains(ObjectIdentifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(ObjectIdentifier); // Populate the package params for baking this output object. if (!IsValid(OutputObject.OutputObject)) @@ -2326,19 +2980,22 @@ FHoudiniEngineBakeUtils::BakeLandscape( // Set the replace mode based on if we are doing a replacement or incremental asset bake const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - ObjectIdentifier, - BakePath, - ObjectName, - HoudiniAssetName, - AssetPackageReplaceMode - ); + + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + UWorld* const DesiredWorld = Landscape ? Landscape->GetWorld() : GWorld; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, ObjectIdentifier, OutputObject, ObjectName, + PackageParams, Resolver, BakePath, AssetPackageReplaceMode); BakeLandscapeObject(OutputObject, BakedOutputObject, bInReplaceActors, bInReplaceAssets, - PackageParams, LandscapeWorldsToUpdate, PackagesToSave, BakeStats); + PackageParams, Resolver, LandscapeWorldsToUpdate, PackagesToSave, BakeStats); } + // Update the cached baked output data + BakedOutput.BakedOutputObjects = NewBakedOutputObjects; + if (PackagesToSave.Num() > 0) { FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); @@ -2385,6 +3042,7 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( bool bInReplaceActors, bool bInReplaceAssets, FHoudiniPackageParams& PackageParams, + FHoudiniAttributeResolver& InResolver, TArray& WorldsToUpdate, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& BakeStats) @@ -2405,17 +3063,6 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( ULandscapeInfo::RecreateLandscapeInfo(TileWorld, true); - // At this point we reconstruct the resolver using cached attributes and tokens - // and just update certain tokens (output paths) for bake mode. - FHoudiniAttributeResolver Resolver; - { - TMap Tokens = InOutputObject.CachedTokens; - // PackageParams.UpdateOutputPathTokens(EPackageMode::Bake, Tokens); - PackageParams.UpdateTokensFromParams(TileWorld, Tokens); - Resolver.SetCachedAttributes(InOutputObject.CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - // If this actor has a shared landscape, ensure the shared landscape gets detached from the HAC // and has the appropriate name. ALandscape* SharedLandscapeActor = TileActor->GetLandscapeActor(); @@ -2427,67 +3074,84 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( PreviousSharedLandscapeActor = PreviousTileActor->GetLandscapeActor(); const bool bHasSharedLandscape = SharedLandscapeActor != TileActor; - const bool bHasPreviousSharedLandscape = PreviousSharedLandscapeActor && PreviousSharedLandscapeActor != PreviousTileActor; + bool bHasPreviousSharedLandscape = PreviousSharedLandscapeActor && PreviousSharedLandscapeActor != PreviousTileActor; + if (bHasPreviousSharedLandscape) + { + // Ignore the previous shared landscape if the world's are different + // Typically in baking we treat completely different asset/output names in a bake as detached from the "previous" bake + if (PreviousSharedLandscapeActor->GetWorld() != SharedLandscapeActor->GetWorld()) + bHasPreviousSharedLandscape = false; + } + bool bLandscapeReplaced = false; if (bHasSharedLandscape) { // If we are baking in replace mode and we have a previous shared landscape actor, use the name of that // actor - const FString DesiredSharedLandscapeName = bHasPreviousSharedLandscape && bInReplaceActors - ? PreviousSharedLandscapeActor->GetName() - : Resolver.ResolveAttribute( - HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, - SharedLandscapeActor->GetName()); - - // If we are not baking in replacement mode, create a unique name if the name is already in use - const FString SharedLandscapeName = !bInReplaceActors - ? MakeUniqueObjectNameIfNeeded(SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *DesiredSharedLandscapeName).ToString() - : DesiredSharedLandscapeName; - - if (SharedLandscapeActor->GetName() != SharedLandscapeName) + FString SharedLandscapeName = InResolver.ResolveAttribute( + HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, + SharedLandscapeActor->GetName()); + + // If the shared landscape is still attached, or it's base name does not match the desired name, "bake" it + AActor* const AttachedParent = SharedLandscapeActor->GetAttachParentActor(); + if (AttachedParent || SharedLandscapeActor->GetFName().GetPlainNameString() != SharedLandscapeName) { - AActor* FoundActor = nullptr; - ALandscape* ExistingLandscape = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, SharedLandscapeName, FoundActor); - if (ExistingLandscape && bInReplaceActors) + if (bHasPreviousSharedLandscape && bInReplaceActors && + PreviousSharedLandscapeActor->GetFName().GetPlainNameString() == SharedLandscapeName) + { + SharedLandscapeName = PreviousSharedLandscapeActor->GetName(); + } + else if (!bInReplaceActors) { - // Even though we found an existing landscape with the desired type, we're just going to destroy/replace - // it for now. - FHoudiniEngineUtils::RenameToUniqueActor(ExistingLandscape, SharedLandscapeName+"_0"); - ExistingLandscape->Destroy(); - bLandscapeReplaced = true; + // If we are not baking in replacement mode, create a unique name if the name is already in use + SharedLandscapeName = MakeUniqueObjectNameIfNeeded( + SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *SharedLandscapeName, SharedLandscapeActor); } + + if (SharedLandscapeActor->GetName() != SharedLandscapeName) + { + AActor* FoundActor = nullptr; + ALandscape* ExistingLandscape = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, SharedLandscapeName, FoundActor); + if (ExistingLandscape && bInReplaceActors) + { + // Even though we found an existing landscape with the desired type, we're just going to destroy/replace + // it for now. + FHoudiniEngineUtils::RenameToUniqueActor(ExistingLandscape, SharedLandscapeName+"_0"); + ExistingLandscape->Destroy(); + bLandscapeReplaced = true; + } - // Fix name of shared landscape - FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, *SharedLandscapeName); + // Fix name of shared landscape + FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, *SharedLandscapeName); + } + + SharedLandscapeActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + WorldsToUpdate.AddUnique(SharedLandscapeActor->GetWorld()); } - - SharedLandscapeActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - WorldsToUpdate.AddUnique(SharedLandscapeActor->GetWorld()); } // Find the world where the landscape tile should be placed. TArray ValidLandscapes; - FString ActorName = Resolver.ResolveOutputName(); + FString ActorName = InResolver.ResolveOutputName(); // If the unreal_level_path was not specified, then fallback to the tile world's package - FString PackagePath = TileWorld->GetOutermost() ? TileWorld->GetOutermost()->GetPathName() : FString(); + FString PackagePath = TileWorld->GetPackage() ? TileWorld->GetPackage()->GetPathName() : FString(); bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); if (bHasLevelPathAttribute) - PackagePath = Resolver.ResolveFullLevelPath(); + PackagePath = InResolver.ResolveFullLevelPath(); - if (bInReplaceActors) - { - // If we are baking in replace mode: get the previous baked actor (if available) name, but only if it is in the - // same target level - if (IsValid(PreviousTileActor)) + // Get the previous baked actor (if available) name, but only if it is in the + // same target level, and it's plain name (no numeric suffix) matches ActorName + // In replacement mode we'll then replace the previous tile actor. + if (bInReplaceActors && IsValid(PreviousTileActor)) + { + UPackage* PreviousPackage = PreviousTileActor->GetPackage(); + if (IsValid(PreviousPackage) && PreviousPackage->GetPathName() == PackagePath && + PreviousTileActor->GetFName().GetPlainNameString() == ActorName) { - UPackage* PreviousPackage = PreviousTileActor->GetOutermost();//GetPackage(); - if (IsValid(PreviousPackage) && PreviousPackage->GetPathName() == PackagePath) - { - ActorName = PreviousTileActor->GetName(); - } + ActorName = PreviousTileActor->GetName(); } } @@ -2523,7 +3187,7 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( else { // incremental, keep existing actor and create a unique name for the new one - ActorName = MakeUniqueObjectName(TargetActor->GetOuter(), TargetActor->GetClass(), *ActorName).ToString(); + ActorName = MakeUniqueObjectNameIfNeeded(TargetActor->GetOuter(), TargetActor->GetClass(), ActorName, TileActor); } TargetActor = nullptr; } @@ -2557,6 +3221,24 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( // Ensure the landscape actor is detached. TileActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); } + + UPackage * CreatedPackage = TargetLevel->GetOutermost(); + TMap AlreadyBakedMaterialsMap; + + // Replace materials + if (TileActor->LandscapeMaterial) + { + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage( + TileActor->LandscapeMaterial, PackageParams, OutPackagesToSave, AlreadyBakedMaterialsMap, BakeStats); + TileActor->LandscapeMaterial = DuplicatedMaterial; + } + + if (TileActor->LandscapeHoleMaterial) + { + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage( + TileActor->LandscapeHoleMaterial, PackageParams, OutPackagesToSave, AlreadyBakedMaterialsMap, BakeStats); + TileActor->LandscapeHoleMaterial = DuplicatedMaterial; + } // Ensure the tile actor has the desired name. FHoudiniEngineUtils::SafeRenameActor(TileActor, ActorName); @@ -2586,6 +3268,100 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( // InBakedOutputObject.BakedObject = nullptr; // } + // Bake the landscape layer uassets + ULandscapeInfo* const LandscapeInfo = TileActor->GetLandscapeInfo(); + if (IsValid(LandscapeInfo) && LandscapeInfo->Layers.Num() > 0) + { + TSet TempLayers; + const int32 NumLayers = LandscapeInfo->Layers.Num(); + TempLayers.Reserve(NumLayers); + for (int32 LayerIndex = 0; LayerIndex < NumLayers; ++LayerIndex) + { + const FLandscapeInfoLayerSettings& Layer = LandscapeInfo->Layers[LayerIndex]; + if (!IsValid(Layer.LayerInfoObj)) + continue; + + if (!IsObjectInTempFolder(Layer.LayerInfoObj, PackageParams.TempCookFolder)) + continue; + + if (!TempLayers.Contains(Layer.LayerInfoObj)) + TempLayers.Add(Layer.LayerInfoObj); + } + + // Setup package params to duplicate each layer + FHoudiniPackageParams LayerPackageParams = PackageParams; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + LayerPackageParams.ReplaceMode = AssetPackageReplaceMode; + + // Determine the final bake name of the "owning" landscape (shared landscape in tiled mode, or just the + // landscape actor itself in non-tiled mode + FString OwningLandscapeActorBakeName; + if (bHasSharedLandscape && IsValid(SharedLandscapeActor)) + { + SharedLandscapeActor->GetName(OwningLandscapeActorBakeName); + } + else + { + TileActor->GetName(OwningLandscapeActorBakeName); + } + + // Keep track of the landscape layers we are baking this time around, and replace in the baked output object + // at the end. + TMap ThisBakedLandscapeLayers; + + // Bake/duplicate temp layers and replace temp layers via LandscapeInfo + for (ULandscapeLayerInfoObject* const LayerInfo : TempLayers) + { + const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerInfo->LayerName.ToString()); + LayerPackageParams.SplitStr = SanitizedLayerName; + LayerPackageParams.ObjectName = OwningLandscapeActorBakeName + TEXT("_layer_") + SanitizedLayerName; + + // Get the previously baked layer info for this layer, if any + ULandscapeLayerInfoObject* const PreviousBakedLayerInfo = InBakedOutputObject.GetLandscapeLayerInfoIfValid( + LayerInfo->LayerName); + + // If our name is the base name (no number) of the previous, then we can fetch the bake counter for + // replacement / incrementing from it + int32 BakeCounter = 0; + if (IsValid(PreviousBakedLayerInfo) && LayerPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakedLayerInfo)) + { + // Get the bake counter from the previous bake + FHoudiniPackageParams::GetBakeCounterFromBakedAsset(PreviousBakedLayerInfo, BakeCounter); + } + + FString LayerPackageName; + UPackage* const LayerPackage = LayerPackageParams.CreatePackageForObject(LayerPackageName, BakeCounter); + if (IsValid(LayerPackage)) + { + BakeStats.NotifyPackageCreated(1); + ULandscapeLayerInfoObject* BakedLayer = DuplicateObject( + LayerInfo, LayerPackage, *LayerPackageName); + if (IsValid(BakedLayer)) + { + BakeStats.NotifyObjectsCreated(BakedLayer->GetClass()->GetName(), 1); + OutPackagesToSave.Add(LayerPackage); + + // Trigger update of the Layer Info + BakedLayer->PreEditChange(nullptr); + BakedLayer->PostEditChange(); + BakedLayer->MarkPackageDirty(); + + // Mark the package dirty... + LayerPackage->MarkPackageDirty(); + + LandscapeInfo->ReplaceLayer(LayerInfo, BakedLayer); + + // Record as the new baked result for the LayerName + ThisBakedLandscapeLayers.Add(LayerInfo->LayerName, FSoftObjectPath(BakedLayer).ToString()); + } + } + } + + // Update the baked landscape layers in InBakedOutputObject + InBakedOutputObject.LandscapeLayers = ThisBakedLandscapeLayers; + } + // Remove the landscape from the InOutputObject since it should no longer be used/reused/updated by temp cooks InOutputObject.OutputObject = nullptr; @@ -2616,12 +3392,14 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( const TArray& InParentOutputs, const TArray& InCurrentBakedActors, const FString& InTemporaryCookFolder, - TArray & OutCreatedPackages) + TArray & OutCreatedPackages, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats) { - if (!InStaticMesh || InStaticMesh->IsPendingKill()) + if (!IsValid(InStaticMesh)) return nullptr; - bool bIsTemporaryStaticMesh = IsObjectTemporary(InStaticMesh, InParentOutputs, InTemporaryCookFolder); + const bool bIsTemporaryStaticMesh = IsObjectTemporary(InStaticMesh, EHoudiniOutputType::Mesh, InParentOutputs, InTemporaryCookFolder); if (!bIsTemporaryStaticMesh) { // The Static Mesh is not a temporary one/already baked, we can simply reuse it @@ -2655,14 +3433,14 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( if (bPreviousBakeStaticMeshValid) { PackageParams.GetBakeCounterFromBakedAsset(InPreviousBakeStaticMesh, BakeCounter); - PreviousBakeMaterials = InPreviousBakeStaticMesh->StaticMaterials;//GetStaticMaterials(); + PreviousBakeMaterials = InPreviousBakeStaticMesh->GetStaticMaterials(); } } FString CreatedPackageName; UPackage* MeshPackage = PackageParams.CreatePackageForObject(CreatedPackageName, BakeCounter); - if (!MeshPackage || MeshPackage->IsPendingKill()) + if (!IsValid(MeshPackage)) return nullptr; - + OutBakeStats.NotifyPackageCreated(1); OutCreatedPackages.Add(MeshPackage); // We need to be sure the package has been fully loaded before calling DuplicateObject @@ -2683,17 +3461,21 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( // it so that its render resources can be safely replaced/updated, and then reattach it UStaticMesh * DuplicatedStaticMesh = nullptr; UStaticMesh* ExistingMesh = FindObject(MeshPackage, *CreatedPackageName); + bool bFoundExistingMesh = false; if (IsValid(ExistingMesh)) { FStaticMeshComponentRecreateRenderStateContext SMRecreateContext(ExistingMesh); DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); + bFoundExistingMesh = true; + OutBakeStats.NotifyObjectsReplaced(UStaticMesh::StaticClass()->GetName(), 1); } else { DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); + OutBakeStats.NotifyObjectsUpdated(UStaticMesh::StaticClass()->GetName(), 1); } - if (!DuplicatedStaticMesh || DuplicatedStaticMesh->IsPendingKill()) + if (!IsValid(DuplicatedStaticMesh)) return nullptr; // Add meta information. @@ -2706,18 +3488,18 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( // See if we need to duplicate materials and textures. TArrayDuplicatedMaterials; - TArray& Materials = DuplicatedStaticMesh->StaticMaterials; + TArray& Materials = DuplicatedStaticMesh->GetStaticMaterials(); for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) { UMaterialInterface* MaterialInterface = Materials[MaterialIdx].MaterialInterface; - if (!MaterialInterface || MaterialInterface->IsPendingKill()) + if (!IsValid(MaterialInterface)) continue; // Only duplicate the material if it is temporary - if (IsObjectTemporary(MaterialInterface, InParentOutputs, InTemporaryCookFolder)) + if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InParentOutputs, InTemporaryCookFolder)) { UPackage * MaterialPackage = Cast(MaterialInterface->GetOuter()); - if (MaterialPackage && !MaterialPackage->IsPendingKill()) + if (IsValid(MaterialPackage)) { FString MaterialName; if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( @@ -2726,20 +3508,27 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( MaterialName = MaterialName + "_Material" + FString::FromInt(MaterialIdx + 1); // We only deal with materials. - UMaterial * Material = Cast< UMaterial >(MaterialInterface); - if (Material && !Material->IsPendingKill()) + if (!MaterialInterface->IsA(UMaterial::StaticClass()) && !MaterialInterface->IsA(UMaterialInstance::StaticClass())) + { + continue; + } + + UMaterialInterface * Material = MaterialInterface; + + if (IsValid(Material)) { // Look for a previous bake material at this index - UMaterial* PreviousBakeMaterial = nullptr; + UMaterialInterface* PreviousBakeMaterial = nullptr; if (bPreviousBakeStaticMeshValid && PreviousBakeMaterials.IsValidIndex(MaterialIdx)) { - PreviousBakeMaterial = Cast(PreviousBakeMaterials[MaterialIdx].MaterialInterface); + PreviousBakeMaterial = Cast(PreviousBakeMaterials[MaterialIdx].MaterialInterface); } // Duplicate material resource. - UMaterial * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( - Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages); + UMaterialInterface * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( + Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages, InOutAlreadyBakedMaterialsMap, + OutBakeStats); - if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + if (!IsValid(DuplicatedMaterial)) continue; // Store duplicated material. @@ -2757,10 +3546,11 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( } // Assign duplicated materials. - DuplicatedStaticMesh->StaticMaterials = DuplicatedMaterials; + DuplicatedStaticMesh->SetStaticMaterials(DuplicatedMaterials); // Notify registry that we have created a new duplicate mesh. - FAssetRegistryModule::AssetCreated(DuplicatedStaticMesh); + if (!bFoundExistingMesh) + FAssetRegistryModule::AssetCreated(DuplicatedStaticMesh); // Dirty the static mesh package. DuplicatedStaticMesh->MarkPackageDirty(); @@ -2772,13 +3562,15 @@ ALandscapeProxy* FHoudiniEngineBakeUtils::BakeHeightfield( ALandscapeProxy * InLandscapeProxy, const FHoudiniPackageParams & PackageParams, - const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType) + const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType, + FHoudiniEngineOutputStats& OutBakeStats) { - if (!InLandscapeProxy || InLandscapeProxy->IsPendingKill()) + if (!IsValid(InLandscapeProxy)) return nullptr; const FString & BakeFolder = PackageParams.BakeFolder; const FString & AssetName = PackageParams.HoudiniAssetName; + TArray PackagesToSave; switch (LandscapeOutputBakeType) { @@ -2792,7 +3584,7 @@ FHoudiniEngineBakeUtils::BakeHeightfield( { // Create heightmap image to the bake folder ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); - if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) + if (!IsValid(InLandscapeInfo)) return nullptr; // bake to image must use absoluate path, @@ -2820,7 +3612,7 @@ FHoudiniEngineBakeUtils::BakeHeightfield( case EHoudiniLandscapeOutputBakeType::BakeToWorld: { ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); - if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) + if (!IsValid(InLandscapeInfo)) return nullptr; // 0. Get Landscape Data // @@ -2839,7 +3631,7 @@ FHoudiniEngineBakeUtils::BakeHeightfield( TArray CurrentLayerIntData; FLinearColor LayerUsageDebugColor; FString LayerName; - if (!FUnrealLandscapeTranslator::GetLandscapeLayerData(InLandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + if (!FUnrealLandscapeTranslator::GetLandscapeLayerData(InLandscapeProxy, InLandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) continue; FLandscapeImportLayerInfo CurrentLayerInfo; @@ -2865,6 +3657,8 @@ FHoudiniEngineBakeUtils::BakeHeightfield( if (!CreatedPackage) return nullptr; + OutBakeStats.NotifyPackageCreated(1); + // 2. Create a new world asset with dialog // UWorldFactory* Factory = NewObject(); FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); @@ -2878,6 +3672,7 @@ FHoudiniEngineBakeUtils::BakeHeightfield( if (!NewWorld) return nullptr; + OutBakeStats.NotifyObjectsCreated(NewWorld->GetClass()->GetName(), 1); NewWorld->SetCurrentLevel(NewWorld->PersistentLevel); // 4. Spawn a landscape proxy actor in the created world @@ -2885,6 +3680,7 @@ FHoudiniEngineBakeUtils::BakeHeightfield( if (!BakedLandscapeProxy) return nullptr; + OutBakeStats.NotifyObjectsCreated(BakedLandscapeProxy->GetClass()->GetName(), 1); // Create a new GUID FGuid currentGUID = FGuid::NewGuid(); BakedLandscapeProxy->SetLandscapeGuid(currentGUID); @@ -2911,19 +3707,28 @@ FHoudiniEngineBakeUtils::BakeHeightfield( BakedLandscapeProxy->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); + TMap AlreadyBakedMaterialsMap; + if (BakedLandscapeProxy->LandscapeMaterial) - BakedLandscapeProxy->LandscapeMaterial = InLandscapeProxy->LandscapeMaterial; + { + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage( + BakedLandscapeProxy->LandscapeMaterial, PackageParams, PackagesToSave, AlreadyBakedMaterialsMap, OutBakeStats); + BakedLandscapeProxy->LandscapeMaterial = DuplicatedMaterial; + } if (BakedLandscapeProxy->LandscapeHoleMaterial) - BakedLandscapeProxy->LandscapeHoleMaterial = InLandscapeProxy->LandscapeHoleMaterial; + { + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage( + BakedLandscapeProxy->LandscapeHoleMaterial, PackageParams, PackagesToSave, AlreadyBakedMaterialsMap, OutBakeStats); + BakedLandscapeProxy->LandscapeHoleMaterial = DuplicatedMaterial; + } // 6. Register all the landscape components, and set landscape actor transform BakedLandscapeProxy->RegisterAllComponents(); BakedLandscapeProxy->SetActorTransform(InLandscapeProxy->GetTransform()); // 7. Save Package - TArray PackagesToSave; PackagesToSave.Add(CreatedPackage); FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); @@ -2946,8 +3751,10 @@ FHoudiniEngineBakeUtils::BakeCurve( USplineComponent* InSplineComponent, ULevel* InLevel, const FHoudiniPackageParams &PackageParams, + const FName& InActorName, AActor*& OutActor, USplineComponent*& OutSplineComponent, + FHoudiniEngineOutputStats& OutBakeStats, FName InOverrideFolderPath, AActor* InActor) { @@ -2957,30 +3764,39 @@ FHoudiniEngineBakeUtils::BakeCurve( if (!Factory) return false; - OutActor = Factory->CreateActor(nullptr, InLevel, InSplineComponent->GetComponentTransform(), RF_Transactional); + OutActor = Factory->CreateActor(nullptr, InLevel, InSplineComponent->GetComponentTransform()); + if (IsValid(OutActor)) + OutBakeStats.NotifyObjectsCreated(OutActor->GetClass()->GetName(), 1); } else { OutActor = InActor; + if (IsValid(OutActor)) + OutBakeStats.NotifyObjectsUpdated(OutActor->GetClass()->GetName(), 1); } - // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset - const FName BaseActorName(PackageParams.ObjectName); - const FName NewName = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), BaseActorName, OutActor); - const FString NewNameStr = NewName.ToString(); - // OutActor->Rename(*NewNameStr); - // OutActor->SetActorLabel(NewNameStr); + // Fallback to ObjectName from package params if InActorName is not set + const FName ActorName = InActorName.IsNone() ? FName(PackageParams.ObjectName) : InActorName; + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), InActorName.ToString(), OutActor); RenameAndRelabelActor(OutActor, NewNameStr, false); - OutActor->SetFolderPath(InOverrideFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetName) : InOverrideFolderPath); + OutActor->SetFolderPath(InOverrideFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetActorName) : InOverrideFolderPath); USplineComponent* DuplicatedSplineComponent = DuplicateObject( InSplineComponent, OutActor, - MakeUniqueObjectNameIfNeeded(OutActor, InSplineComponent->GetClass(), FName(PackageParams.ObjectName))); + FName(MakeUniqueObjectNameIfNeeded(OutActor, InSplineComponent->GetClass(), PackageParams.ObjectName))); + + if (IsValid(DuplicatedSplineComponent)) + OutBakeStats.NotifyObjectsCreated(DuplicatedSplineComponent->GetClass()->GetName(), 1); + OutActor->AddInstanceComponent(DuplicatedSplineComponent); const bool bCreateIfMissing = true; USceneComponent* RootComponent = GetActorRootComponent(OutActor, bCreateIfMissing); DuplicatedSplineComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + + // We duplicated the InSplineComponent, so we don't have to copy all of its properties, but we must set the + // world transform + DuplicatedSplineComponent->SetWorldTransform(InSplineComponent->GetComponentTransform()); FAssetRegistryModule::AssetCreated(DuplicatedSplineComponent); DuplicatedSplineComponent->RegisterComponent(); @@ -2995,10 +3811,13 @@ FHoudiniEngineBakeUtils::BakeCurve( FHoudiniBakedOutputObject& InBakedOutputObject, // const TArray& InAllBakedOutputs, const FHoudiniPackageParams &PackageParams, + FHoudiniAttributeResolver& InResolver, bool bInReplaceActors, bool bInReplaceAssets, - TArray& OutActors, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { @@ -3013,18 +3832,8 @@ FHoudiniEngineBakeUtils::BakeCurve( { UWorld* DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + FString LevelPackagePath = InResolver.ResolveFullLevelPath(); bool bCreatedPackage = false; if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( @@ -3041,6 +3850,8 @@ FHoudiniEngineBakeUtils::BakeCurve( // TODO: ? always add? if (bCreatedPackage && DesiredLevel) { + OutBakeStats.NotifyPackageCreated(1); + OutBakeStats.NotifyObjectsCreated(DesiredLevel->GetClass()->GetName(), 1); // We can now save the package again, and unload it. OutPackagesToSave.Add(DesiredLevel->GetOutermost()); } @@ -3053,7 +3864,7 @@ FHoudiniEngineBakeUtils::BakeCurve( FName BakeActorName; AActor* FoundActor = nullptr; bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, InBakedActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) return false; // If we are baking in replace mode, remove the previous bake component @@ -3066,11 +3877,9 @@ FHoudiniEngineBakeUtils::BakeCurve( } } - FHoudiniPackageParams CurvePackageParams = PackageParams; - CurvePackageParams.ObjectName = BakeActorName.ToString(); USplineComponent* NewSplineComponent = nullptr; - const FName OutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *(CurvePackageParams.HoudiniAssetName)); - if (!BakeCurve(SplineComponent, DesiredLevel, CurvePackageParams, FoundActor, NewSplineComponent, OutlinerFolderPath, FoundActor)) + const FName OutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *(PackageParams.HoudiniAssetActorName)); + if (!BakeCurve(SplineComponent, DesiredLevel, PackageParams, BakeActorName, FoundActor, NewSplineComponent, OutBakeStats, OutlinerFolderPath, FoundActor)) return false; InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); @@ -3086,10 +3895,19 @@ FHoudiniEngineBakeUtils::BakeCurve( InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); } - FHoudiniEngineBakedActor Result; - Result.Actor = FoundActor; - Result.ActorBakeName = BakeActorName; - OutActors.Add(Result); + FHoudiniEngineBakedActor Result( + FoundActor, + BakeActorName, + OutlinerFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetActorName) : OutlinerFolderPath, + INDEX_NONE, // Output index + FHoudiniOutputObjectIdentifier(), + nullptr, // InBakedObject + nullptr, // InSourceObject + NewSplineComponent, + PackageParams.BakeFolder, + PackageParams); + + OutBakedActorEntry = Result; return true; } @@ -3101,7 +3919,7 @@ FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( UWorld* WorldToSpawn, const FTransform & SpawnTransform) { - if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(InHoudiniSplineComponent)) return nullptr; TArray & DisplayPoints = InHoudiniSplineComponent->DisplayPoints; @@ -3134,7 +3952,7 @@ FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( } } - AActor* NewActor = Factory->CreateActor(nullptr, DesiredLevel, InHoudiniSplineComponent->GetComponentTransform(), RF_Transactional); + AActor* NewActor = Factory->CreateActor(nullptr, DesiredLevel, InHoudiniSplineComponent->GetComponentTransform()); USplineComponent* BakedUnrealSplineComponent = NewObject(NewActor); if (!BakedUnrealSplineComponent) @@ -3157,10 +3975,7 @@ FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( BakedUnrealSplineComponent->RegisterComponent(); // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset - const FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, *(PackageParams.ObjectName), NewActor); - const FString NewNameStr = NewName.ToString(); - // NewActor->Rename(*NewNameStr); - // NewActor->SetActorLabel(NewNameStr); + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, *(PackageParams.ObjectName), NewActor); RenameAndRelabelActor(NewActor, NewNameStr, false); NewActor->SetFolderPath(FName(PackageParams.HoudiniAssetName)); @@ -3174,7 +3989,7 @@ FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( UWorld* WorldToSpawn, const FTransform & SpawnTransform) { - if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(InHoudiniSplineComponent)) return nullptr; FGuid BakeGUID = FGuid::NewGuid(); @@ -3195,7 +4010,7 @@ FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( // See if package exists, if it does, we need to regenerate the name. UPackage * Package = FindPackage(nullptr, *PackageName); - if (Package && !Package->IsPendingKill()) + if (IsValid(Package)) { // Package does exist, there's a collision, we need to generate a new name. BakeGUID.Invalidate(); @@ -3203,7 +4018,7 @@ FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( else { // Create actual package. - Package = CreatePackage(nullptr, *PackageName); + Package = CreatePackage(*PackageName); } AActor * CreatedHoudiniSplineActor = FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( @@ -3212,7 +4027,7 @@ FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( TArray PackagesToSave; UBlueprint * Blueprint = nullptr; - if (CreatedHoudiniSplineActor && !CreatedHoudiniSplineActor->IsPendingKill()) + if (IsValid(CreatedHoudiniSplineActor)) { UObject* Asset = nullptr; @@ -3268,11 +4083,11 @@ FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( UPackage * Package, UObject * Object, const TCHAR * Key, const TCHAR * Value) { - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return; UMetaData * MetaData = Package->GetMetaData(); - if (MetaData && !MetaData->IsPendingKill()) + if (IsValid(MetaData)) MetaData->SetValue(Object, Key, Value); } @@ -3282,11 +4097,11 @@ FHoudiniEngineBakeUtils:: GetHoudiniGeneratedNameFromMetaInformation( UPackage * Package, UObject * Object, FString & HoudiniName) { - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return false; UMetaData * MetaData = Package->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) + if (!IsValid(MetaData)) return false; if (MetaData->HasValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) @@ -3302,12 +4117,19 @@ GetHoudiniGeneratedNameFromMetaInformation( return false; } -UMaterial * +UMaterialInterface * FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( - UMaterial * Material, UMaterial* PreviousBakeMaterial, const FString & MaterialName, const FHoudiniPackageParams& ObjectPackageParams, - TArray & OutGeneratedPackages) + UMaterialInterface * Material, UMaterialInterface* PreviousBakeMaterial, const FString & MaterialName, const FHoudiniPackageParams& ObjectPackageParams, + TArray & OutGeneratedPackages, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats) { - UMaterial * DuplicatedMaterial = nullptr; + if (InOutAlreadyBakedMaterialsMap.Contains(Material)) + { + return InOutAlreadyBakedMaterialsMap[Material]; + } + + UMaterialInterface * DuplicatedMaterial = nullptr; FString CreatedMaterialName; // Create material package. Use the same package params as static mesh, but with the material's name @@ -3319,26 +4141,35 @@ FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( bool bIsPreviousBakeMaterialValid = IsValid(PreviousBakeMaterial); int32 BakeCounter = 0; TArray PreviousBakeMaterialExpressions; - if (bIsPreviousBakeMaterialValid) + + + if (bIsPreviousBakeMaterialValid && PreviousBakeMaterial->IsA(UMaterial::StaticClass())) { + UMaterial * PreviousMaterialCast = Cast(PreviousBakeMaterial); bIsPreviousBakeMaterialValid = MaterialPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeMaterial); - if (bIsPreviousBakeMaterialValid) + + if (bIsPreviousBakeMaterialValid && PreviousMaterialCast) { MaterialPackageParams.GetBakeCounterFromBakedAsset(PreviousBakeMaterial, BakeCounter); - PreviousBakeMaterialExpressions = PreviousBakeMaterial->Expressions; + + PreviousBakeMaterialExpressions = PreviousMaterialCast->Expressions; } } UPackage * MaterialPackage = MaterialPackageParams.CreatePackageForObject(CreatedMaterialName, BakeCounter); - if (!MaterialPackage || MaterialPackage->IsPendingKill()) + if (!IsValid(MaterialPackage)) return nullptr; + OutBakeStats.NotifyPackageCreated(1); + // Clone material. - DuplicatedMaterial = DuplicateObject< UMaterial >(Material, MaterialPackage, *CreatedMaterialName); - if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + DuplicatedMaterial = DuplicateObject< UMaterialInterface >(Material, MaterialPackage, *CreatedMaterialName); + if (!IsValid(DuplicatedMaterial)) return nullptr; + OutBakeStats.NotifyObjectsCreated(DuplicatedMaterial->GetClass()->GetName(), 1); + // Add meta information. FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( MaterialPackage, DuplicatedMaterial, @@ -3348,17 +4179,21 @@ FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedMaterialName); // Retrieve and check various sampling expressions. If they contain textures, duplicate (and bake) them. - const int32 NumExpressions = DuplicatedMaterial->Expressions.Num(); - for (int32 ExpressionIdx = 0; ExpressionIdx < NumExpressions; ++ExpressionIdx) + UMaterial * DuplicatedMaterialCast = Cast(DuplicatedMaterial); + if (DuplicatedMaterialCast) { - UMaterialExpression* Expression = DuplicatedMaterial->Expressions[ExpressionIdx]; - UMaterialExpression* PreviousBakeExpression = nullptr; - if (bIsPreviousBakeMaterialValid && PreviousBakeMaterialExpressions.IsValidIndex(ExpressionIdx)) + const int32 NumExpressions = DuplicatedMaterialCast->Expressions.Num(); + for (int32 ExpressionIdx = 0; ExpressionIdx < NumExpressions; ++ExpressionIdx) { - PreviousBakeExpression = PreviousBakeMaterialExpressions[ExpressionIdx]; + UMaterialExpression* Expression = DuplicatedMaterialCast->Expressions[ExpressionIdx]; + UMaterialExpression* PreviousBakeExpression = nullptr; + if (bIsPreviousBakeMaterialValid && PreviousBakeMaterialExpressions.IsValidIndex(ExpressionIdx)) + { + PreviousBakeExpression = PreviousBakeMaterialExpressions[ExpressionIdx]; + } + FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( + Expression, PreviousBakeExpression, MaterialPackageParams, OutGeneratedPackages, OutBakeStats); } - FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( - Expression, PreviousBakeExpression, MaterialPackageParams, OutGeneratedPackages); } // Notify registry that we have created a new duplicate material. @@ -3371,28 +4206,33 @@ FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( // DuplicatedMaterial->ForceRecompileForRendering(); // Use UMaterialEditingLibrary::RecompileMaterial since it correctly updates texture references in the material // which ForceRecompileForRendering does not do - UMaterialEditingLibrary::RecompileMaterial(DuplicatedMaterial); + if (DuplicatedMaterialCast) + { + UMaterialEditingLibrary::RecompileMaterial(DuplicatedMaterialCast); + } OutGeneratedPackages.Add(MaterialPackage); + InOutAlreadyBakedMaterialsMap.Add(Material, DuplicatedMaterial); + return DuplicatedMaterial; } void FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( UMaterialExpression * MaterialExpression, UMaterialExpression* PreviousBakeMaterialExpression, - const FHoudiniPackageParams& PackageParams, TArray & OutCreatedPackages) + const FHoudiniPackageParams& PackageParams, TArray & OutCreatedPackages, FHoudiniEngineOutputStats& OutBakeStats) { UMaterialExpressionTextureSample * TextureSample = Cast< UMaterialExpressionTextureSample >(MaterialExpression); - if (!TextureSample || TextureSample->IsPendingKill()) + if (!IsValid(TextureSample)) return; UTexture2D * Texture = Cast< UTexture2D >(TextureSample->Texture); - if (!Texture || Texture->IsPendingKill()) + if (!IsValid(Texture)) return; UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); - if (!TexturePackage || TexturePackage->IsPendingKill()) + if (!IsValid(TexturePackage)) return; // Try to get the previous bake's texture @@ -3410,7 +4250,7 @@ FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( { // Duplicate texture. UTexture2D * DuplicatedTexture = FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( - Texture, PreviousBakeTexture, GeneratedTextureName, PackageParams, OutCreatedPackages); + Texture, PreviousBakeTexture, GeneratedTextureName, PackageParams, OutCreatedPackages, OutBakeStats); // Re-assign generated texture. TextureSample->Texture = DuplicatedTexture; @@ -3420,20 +4260,20 @@ FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( UTexture2D * FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( UTexture2D * Texture, UTexture2D* PreviousBakeTexture, const FString & SubTextureName, const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages) + TArray & OutCreatedPackages, FHoudiniEngineOutputStats& OutBakeStats) { UTexture2D* DuplicatedTexture = nullptr; #if WITH_EDITOR // Retrieve original package of this texture. UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); - if (!TexturePackage || TexturePackage->IsPendingKill()) + if (!IsValid(TexturePackage)) return nullptr; FString GeneratedTextureName; if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation(TexturePackage, Texture, GeneratedTextureName)) { UMetaData * MetaData = TexturePackage->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) + if (!IsValid(MetaData)) return nullptr; // Retrieve texture type. @@ -3461,14 +4301,18 @@ FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( UPackage * NewTexturePackage = TexturePackageParams.CreatePackageForObject(CreatedTextureName, BakeCounter); - if (!NewTexturePackage || NewTexturePackage->IsPendingKill()) + if (!IsValid(NewTexturePackage)) return nullptr; + + OutBakeStats.NotifyPackageCreated(1); // Clone texture. DuplicatedTexture = DuplicateObject< UTexture2D >(Texture, NewTexturePackage, *CreatedTextureName); - if (!DuplicatedTexture || DuplicatedTexture->IsPendingKill()) + if (!IsValid(DuplicatedTexture)) return nullptr; + OutBakeStats.NotifyObjectsCreated(DuplicatedTexture->GetClass()->GetName(), 1); + // Add meta information. FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( NewTexturePackage, DuplicatedTexture, @@ -3496,12 +4340,12 @@ FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( bool FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent) { - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return false; AActor * ActorOwner = HoudiniAssetComponent->GetOwner(); - if (!ActorOwner || ActorOwner->IsPendingKill()) + if (!IsValid(ActorOwner)) return false; UWorld* World = ActorOwner->GetWorld(); @@ -3525,7 +4369,7 @@ FHoudiniEngineBakeUtils::SaveBakedPackages(TArray & PackagesToSave, b { // Save the current map FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false); - UPackage* CurrentWorldPackage = CreatePackage(nullptr, *CurrentWorldPath); + UPackage* CurrentWorldPackage = CreatePackage(*CurrentWorldPath); if (CurrentWorldPackage) { @@ -3538,10 +4382,9 @@ FHoudiniEngineBakeUtils::SaveBakedPackages(TArray & PackagesToSave, b } bool -FHoudiniEngineBakeUtils::FindOutputObject( - const UObject* InObjectToFind, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier) +FHoudiniEngineBakeUtils::FindOutputObject(const UObject* InObjectToFind, EHoudiniOutputType InOutputType, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier) { - if (!InObjectToFind || InObjectToFind->IsPendingKill()) + if (!IsValid(InObjectToFind)) return false; const int32 NumOutputs = InOutputs.Num(); @@ -3550,8 +4393,11 @@ FHoudiniEngineBakeUtils::FindOutputObject( const UHoudiniOutput* CurOutput = InOutputs[OutputIdx]; if (!IsValid(CurOutput)) continue; + + if (CurOutput->GetType() != InOutputType) + continue; - for (const auto& CurOutputObject : CurOutput->GetOutputObjects()) + for (auto& CurOutputObject : CurOutput->GetOutputObjects()) { if (CurOutputObject.Value.OutputObject == InObjectToFind || CurOutputObject.Value.OutputComponent == InObjectToFind @@ -3569,16 +4415,16 @@ FHoudiniEngineBakeUtils::FindOutputObject( } bool -FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, UHoudiniAssetComponent* InHAC) +FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; FString TempPath = FString(); // TODO: Get the HAC outputs in a better way? TArray Outputs; - if (InHAC && !InHAC->IsPendingKill()) + if (IsValid(InHAC)) { const int32 NumOutputs = InHAC->GetNumOutputs(); Outputs.Reserve(NumOutputs); @@ -3590,24 +4436,18 @@ FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, UHoudiniAssetCompo TempPath = InHAC->TemporaryCookFolder.Path; } - return IsObjectTemporary(InObject, Outputs, TempPath); + return IsObjectTemporary(InObject, InOutputType, Outputs, TempPath); } -bool FHoudiniEngineBakeUtils::IsObjectTemporary( - UObject* InObject, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) +bool FHoudiniEngineBakeUtils::IsObjectInTempFolder(UObject* const InObject, const FString& InTemporaryCookFolder) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; - int32 ParentOutputIndex = -1; - FHoudiniOutputObjectIdentifier Identifier; - if (FindOutputObject(InObject, InParentOutputs, ParentOutputIndex, Identifier)) - return true; - // Check the package path for this object // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated UPackage* ObjectPackage = InObject->GetOutermost(); - if (ObjectPackage && !ObjectPackage->IsPendingKill()) + if (IsValid(ObjectPackage)) { const FString PathName = ObjectPackage->GetPathName(); if (PathName.StartsWith(InTemporaryCookFolder)) @@ -3617,31 +4457,58 @@ bool FHoudiniEngineBakeUtils::IsObjectTemporary( const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); if (PathName.StartsWith(HoudiniRuntimeSettings->DefaultTemporaryCookFolder)) return true; - - /* + } + + return false; +} + +bool FHoudiniEngineBakeUtils::IsObjectTemporary( + UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) +{ + if (!IsValid(InObject)) + return false; + + int32 ParentOutputIndex = -1; + FHoudiniOutputObjectIdentifier Identifier; + if (FindOutputObject(InObject, InOutputType, InParentOutputs, ParentOutputIndex, Identifier)) + return true; + + // Check the package path for this object + // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated + if (IsObjectInTempFolder(InObject, InTemporaryCookFolder)) + return true; + + /* + UPackage* ObjectPackage = InObject->GetOutermost(); + if (IsValid(ObjectPackage)) + { // TODO: this just indicates that the object was generated by H // it could as well have been baked before... // we should probably add a "temp" metadata // Look in the meta info as well?? UMetaData * MetaData = ObjectPackage->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) + if (!IsValid(MetaData)) return false; if (MetaData->HasValue(InObject, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) return true; - */ } + */ return false; } void -FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent(AActor* NewActor, UStaticMeshComponent* NewSMC, UStaticMeshComponent* InSMC) +FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent( + AActor* NewActor, + UStaticMeshComponent* NewSMC, + UStaticMeshComponent* InSMC, + bool bInCopyWorldTransform) { - if (!NewSMC || NewSMC->IsPendingKill()) + if (!IsValid(NewSMC)) return; - if (!InSMC || InSMC->IsPendingKill()) + if (!IsValid(InSMC)) return; // Copy properties to new actor @@ -3669,7 +4536,7 @@ FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent(AActor* NewActor, US NewSMC->SetPhysMaterialOverride(InBodySetup->GetPhysMaterial()); } - if (NewActor && !NewActor->IsPendingKill()) + if (IsValid(NewActor)) NewActor->SetActorHiddenInGame(InSMC->bHiddenInGame); NewSMC->SetVisibility(InSMC->IsVisible()); @@ -3679,12 +4546,20 @@ FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent(AActor* NewActor, US // FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject(InSMC, InHGPO); // The below code is from EditorUtilities::CopyActorProperties and modified to only copy from one component to another - UClass* ComponentClass = InSMC->GetClass(); - if (ComponentClass != NewSMC->GetClass()) + UClass* ComponentClass = nullptr; + if (InSMC->GetClass()->IsChildOf(NewSMC->GetClass())) + { + ComponentClass = NewSMC->GetClass(); + } + else if (NewSMC->GetClass()->IsChildOf(InSMC->GetClass())) + { + ComponentClass = InSMC->GetClass(); + } + else { HOUDINI_LOG_WARNING( TEXT("Incompatible component classes in CopyPropertyToNewActorAndComponent: %s vs %s"), - *(ComponentClass->GetName()), + *(InSMC->GetName()), *(NewSMC->GetClass()->GetName())); NewSMC->PostEditChange(); @@ -3763,6 +4638,11 @@ FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent(AActor* NewActor, US } } + if (bInCopyWorldTransform) + { + NewSMC->SetWorldTransform(InSMC->GetComponentTransform()); + } + NewSMC->PostEditChange(); }; @@ -3847,12 +4727,12 @@ FHoudiniEngineBakeUtils::RenameAsset(UObject* InAsset, const FString& InNewName, FString NewName; if (bMakeUniqueIfNotUnique) - NewName = MakeUniqueObjectNameIfNeeded(InAsset->GetOutermost()/*GetPackage()*/, InAsset->GetClass(), FName(InNewName), InAsset).ToString(); + NewName = MakeUniqueObjectNameIfNeeded(InAsset->GetPackage(), InAsset->GetClass(), InNewName, InAsset); else NewName = InNewName; - InAsset->Rename(*NewName); - + FHoudiniEngineUtils::RenameObject(InAsset, *NewName); + const FSoftObjectPath NewPath = FSoftObjectPath(InAsset); if (OldPath != NewPath) { @@ -3874,12 +4754,12 @@ FHoudiniEngineBakeUtils::RenameAndRelabelActor(AActor* InActor, const FString& I FString NewName; if (bMakeUniqueIfNotUnique) - NewName = MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), FName(InNewName), InActor).ToString(); + NewName = MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), InNewName, InActor); else NewName = InNewName; - InActor->Rename(*NewName); - InActor->SetActorLabel(NewName); + FHoudiniEngineUtils::RenameObject(InActor, *NewName); + FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, NewName); const FSoftObjectPath NewPath = FSoftObjectPath(InActor); if (OldPath != NewPath) @@ -3911,8 +4791,6 @@ FHoudiniEngineBakeUtils::DetachAndRenameBakedPDGOutputActor( // Detach from parent InActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); // Rename - // InActor->Rename(*MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), FName(InNewName)).ToString()); - // InActor->SetActorLabel(InNewName); const bool bMakeUniqueIfNotUnique = true; RenameAndRelabelActor(InActor, InNewName, bMakeUniqueIfNotUnique); @@ -3925,11 +4803,13 @@ bool FHoudiniEngineBakeUtils::BakePDGWorkResultObject( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, - int32 InWorkResultIndex, - int32 InWorkResultObjectIndex, + int32 InWorkResultArrayIndex, + int32 InWorkResultObjectArrayIndex, bool bInReplaceActors, bool bInReplaceAssets, bool bInBakeToWorkResultActor, + bool bInIsAutoBake, + const TArray& InBakedActors, TArray& OutBakedActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats, @@ -3943,18 +4823,30 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( if (!IsValid(InNode)) return false; - if (!InNode->WorkResult.IsValidIndex(InWorkResultIndex)) + if (!InNode->WorkResult.IsValidIndex(InWorkResultArrayIndex)) return false; - FTOPWorkResult& WorkResult = InNode->WorkResult[InWorkResultIndex]; - if (!WorkResult.ResultObjects.IsValidIndex(InWorkResultObjectIndex)) + FTOPWorkResult& WorkResult = InNode->WorkResult[InWorkResultArrayIndex]; + if (!WorkResult.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) return false; - FTOPWorkResultObject& WorkResultObject = WorkResult.ResultObjects[InWorkResultObjectIndex]; + FTOPWorkResultObject& WorkResultObject = WorkResult.ResultObjects[InWorkResultObjectArrayIndex]; TArray& Outputs = WorkResultObject.GetResultOutputs(); if (Outputs.Num() == 0) return true; + if (WorkResultObject.State != EPDGWorkResultState::Loaded) + { + if (bInIsAutoBake && WorkResultObject.AutoBakedSinceLastLoad()) + { + HOUDINI_LOG_MESSAGE(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObject (%s) is not loaded but was auto-baked since its last load."), *WorkResultObject.Name); + return true; + } + + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObject (%s) is not loaded, cannot bake it."), *WorkResultObject.Name); + return false; + } + AActor* WorkResultObjectActor = WorkResultObject.GetOutputActorOwner().GetOutputActor(); if (!IsValid(WorkResultObjectActor)) { @@ -3962,27 +4854,26 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( return false; } - // BakedActorsForWorkResultObject contains each actor that contains baked PDG results. Actors may - // appear in the array more than once if they have more than one baked result/component associated with - // them - TArray BakedActorsForWorkResultObject; - const FString HoudiniAssetName(WorkResultObject.Name); - // Find the previous bake output for this work result object FString Key; - InNode->GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key); + InNode->GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key); FHoudiniPDGWorkResultObjectBakedOutput& BakedOutputContainer = InNode->GetBakedWorkResultObjectsOutputs().FindOrAdd(Key); - + + const UHoudiniAssetComponent* HoudiniAssetComponent = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InPDGAssetLink); + check(IsValid(HoudiniAssetComponent)); + + TArray WROBakedActors; BakeHoudiniOutputsToActors( + HoudiniAssetComponent, Outputs, BakedOutputContainer.BakedOutputs, - HoudiniAssetName, WorkResultObjectActor->GetActorTransform(), InPDGAssetLink->BakeFolder, InPDGAssetLink->GetTemporaryCookFolder(), bInReplaceActors, bInReplaceAssets, - BakedActorsForWorkResultObject, + InBakedActors, + WROBakedActors, OutPackagesToSave, OutBakeStats, InOutputTypesToBake, @@ -3991,34 +4882,38 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( InFallbackWorldOutlinerFolder); // Set the PDG indices on the output baked actor entries - if (BakedActorsForWorkResultObject.Num() > 0) + FOutputActorOwner& OutputActorOwner = WorkResultObject.GetOutputActorOwner(); + AActor* const WROActor = OutputActorOwner.GetOutputActor(); + FHoudiniEngineBakedActor const * BakedWROActorEntry = nullptr; + if (WROBakedActors.Num() > 0) { - for (FHoudiniEngineBakedActor& BakedActorEntry : BakedActorsForWorkResultObject) + for (FHoudiniEngineBakedActor& BakedActorEntry : WROBakedActors) { - BakedActorEntry.PDGWorkResultIndex = InWorkResultIndex; - BakedActorEntry.PDGWorkResultObjectIndex = InWorkResultObjectIndex; + BakedActorEntry.PDGWorkResultArrayIndex = InWorkResultArrayIndex; + BakedActorEntry.PDGWorkItemIndex = WorkResult.WorkItemIndex; + BakedActorEntry.PDGWorkResultObjectArrayIndex = InWorkResultObjectArrayIndex; + + if (WROActor && BakedActorEntry.Actor == WROActor) + { + BakedWROActorEntry = &BakedActorEntry; + } } } // If anything was baked to WorkResultObjectActor, detach it from its parent if (bInBakeToWorkResultActor) { - FOutputActorOwner& OutputActorOwner = WorkResultObject.GetOutputActorOwner(); // if we re-used the temp actor as a bake actor, then remove its temp outputs - WorkResultObject.DestroyResultOutputs(); - AActor* WROActor = OutputActorOwner.GetOutputActor(); + constexpr bool bDeleteOutputActor = false; + InNode->DeleteWorkResultObjectOutputs(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, bDeleteOutputActor); if (WROActor) { - const FHoudiniEngineBakedActor* BakedActorEntry = BakedActorsForWorkResultObject.FindByPredicate([WROActor](const FHoudiniEngineBakedActor& Entry) - { - return Entry.Actor == WROActor; - }); - if (BakedActorEntry) + if (BakedWROActorEntry) { OutputActorOwner.SetOutputActor(nullptr); const FString OldActorPath = FSoftObjectPath(WROActor).ToString(); DetachAndRenameBakedPDGOutputActor( - WROActor, BakedActorEntry->ActorBakeName.ToString(), BakedActorEntry->WorldOutlinerFolder); + WROActor, BakedWROActorEntry->ActorBakeName.ToString(), BakedWROActorEntry->WorldOutlinerFolder); const FString NewActorPath = FSoftObjectPath(WROActor).ToString(); if (OldActorPath != NewActorPath) { @@ -4039,160 +4934,114 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( } } } - OutBakedActors.Append(BakedActorsForWorkResultObject); + + if (bInIsAutoBake) + WorkResultObject.SetAutoBakedSinceLastLoad(true); + + OutBakedActors = MoveTemp(WROBakedActors); + return true; } - -bool -FHoudiniEngineBakeUtils::BakePDGWorkResultObject( +void +FHoudiniEngineBakeUtils::CheckPDGAutoBakeAfterResultObjectLoaded( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName) + int32 InWorkItemHAPIIndex, + int32 InWorkItemResultInfoIndex) { if (!IsValid(InPDGAssetLink)) - return false; - - if (!IsValid(InNode)) - return false; - - // Find the work result index and work result object index - const int32 WorkResultIndex = InNode->WorkResult.IndexOfByPredicate([InWorkResultId](const FTOPWorkResult& Entry) - { - return Entry.WorkItemID == InWorkResultId; - }); - if (!InNode->WorkResult.IsValidIndex(WorkResultIndex)) - return false; - FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultIndex]; - const int32 WorkResultObjectIndex = WorkResult.ResultObjects.IndexOfByPredicate([InWorkResultObjectName](const FTOPWorkResultObject& Entry) - { - return Entry.Name.Equals(InWorkResultObjectName); - }); - if (!WorkResult.ResultObjects.IsValidIndex(WorkResultObjectIndex)) - return false; - - // Determine the output world outliner folder path via the PDG asset link's - // owner's folder path and name - UObject* PDGOwner = InPDGAssetLink->GetOwnerActor(); - if (!PDGOwner) - PDGOwner = InPDGAssetLink->GetOuter(); - const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); - - // Determine the actor/package replacement settings - const bool bBakeBlueprints = InPDGAssetLink->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToBlueprint; - const bool bReplaceActors = !bBakeBlueprints && InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - // Determine the output types to bake: don't bake landscapes in blueprint baking mode - TArray OutputTypesToBake; - TArray InstancerComponentTypesToBake; - if (bBakeBlueprints) - { - OutputTypesToBake.Add(EHoudiniOutputType::Mesh); - OutputTypesToBake.Add(EHoudiniOutputType::Instancer); - OutputTypesToBake.Add(EHoudiniOutputType::Curve); - - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); - } - - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - TArray BakedActors; - - bool bSuccess = BakePDGWorkResultObject( - InPDGAssetLink, - InNode, - WorkResultIndex, - WorkResultObjectIndex, - bReplaceActors, - bReplaceAssets, - !bBakeBlueprints, - BakedActors, - PackagesToSave, - BakeStats, - OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, - InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, - FallbackWorldOutlinerFolderPath.ToString() - ); + return; - // Recenter and select the baked actors - if (GEditor && BakedActors.Num() > 0) - GEditor->SelectNone(false, true); - - for (const FHoudiniEngineBakedActor& Entry : BakedActors) - { - if (!IsValid(Entry.Actor)) - continue; - - if (InPDGAssetLink->bRecenterBakedActors) - CenterActorToBoundingBoxCenter(Entry.Actor); + if (!InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded) + return; - if (GEditor) - GEditor->SelectActor(Entry.Actor, true, false); - } - - if (GEditor && BakedActors.Num() > 0) - GEditor->NoteSelectionChange(); + if (!IsValid(InNode)) + return; - if (bBakeBlueprints && bSuccess) - { - TArray Blueprints; - bSuccess = BakeBlueprintsFromBakedActors( - BakedActors, - InPDGAssetLink->bRecenterBakedActors, - bReplaceAssets, - InPDGAssetLink->AssetName, - InPDGAssetLink->BakeFolder, - nullptr, - &InNode->GetBakedWorkResultObjectsOutputs(), - Blueprints, - PackagesToSave); + // Check if the node is ready for baking: all work items must be complete + bool bDoNotBake = false; + bool bPendingBakeItems = false; + if (!InNode->AreAllWorkItemsComplete() || InNode->AnyWorkItemsFailed()) + bDoNotBake = true; - // Sync the CB to the baked objects - if(GEditor && Blueprints.Num() > 0) + // Check if the node is ready for baking: all work items must be loaded + if (!bDoNotBake) + { + for (const FTOPWorkResult& WorkResult : InNode->WorkResult) { - TArray Assets; - Assets.Reserve(Blueprints.Num()); - for (UBlueprint* Blueprint : Blueprints) + for (const FTOPWorkResultObject& WRO : WorkResult.ResultObjects) { - Assets.Add(Blueprint); + if (WRO.State != EPDGWorkResultState::Loaded && !WRO.AutoBakedSinceLastLoad()) + { + bDoNotBake = true; + break; + } } - GEditor->SyncBrowserToObjects(Assets); + if (bDoNotBake) + break; } } - SaveBakedPackages(PackagesToSave); - + if (!bDoNotBake) { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + // Check which outputs are selected for baking: selected node, selected network or all + // And only bake if the node falls within the criteria + UTOPNetwork const * const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + UTOPNode const * const SelectedTOPNode = InPDGAssetLink->GetSelectedTOPNode(); + switch (InPDGAssetLink->PDGBakeSelectionOption) + { + case EPDGBakeSelectionOption::SelectedNetwork: + if (!IsValid(SelectedTOPNetwork) || !InNode->IsParentTOPNetwork(SelectedTOPNetwork)) + { + HOUDINI_LOG_WARNING( + TEXT("Not baking Node %s (Net %s): not in selected network"), + InNode ? *InNode->GetName() : TEXT(""), + SelectedTOPNetwork ? *SelectedTOPNetwork->GetName() : TEXT("")); + bDoNotBake = true; + } + break; + case EPDGBakeSelectionOption::SelectedNode: + if (InNode != SelectedTOPNode) + { + HOUDINI_LOG_WARNING( + TEXT("Not baking Node %s (Net %s): not the selected node"), + InNode ? *InNode->GetName() : TEXT(""), + SelectedTOPNetwork ? *SelectedTOPNetwork->GetName() : TEXT("")); + bDoNotBake = true; + } + break; + case EPDGBakeSelectionOption::All: + default: + break; + } } - return bSuccess; -} + // If there are no nodes left to auto-bake, broadcast the onpostbake delegate + if (bDoNotBake && !InPDGAssetLink->AnyRemainingAutoBakeNodes()) + InPDGAssetLink->HandleOnPostBake(true); -void -FHoudiniEngineBakeUtils::AutoBakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName) -{ - if (!IsValid(InPDGAssetLink)) + if (bDoNotBake) return; - if (!InPDGAssetLink->bBakeAfterWorkResultObjectLoaded) - return; + bool bSuccess = false; + const bool bIsAutoBake = true; + switch (InPDGAssetLink->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + bSuccess = FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InNode, bIsAutoBake, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); + break; - BakePDGWorkResultObject( - InPDGAssetLink, - InNode, - InWorkResultId, - InWorkResultObjectName); + case EHoudiniEngineBakeOption::ToBlueprint: + bSuccess = FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(InPDGAssetLink, InNode, bIsAutoBake, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); + break; + + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported HoudiniEngineBakeOption %i"), InPDGAssetLink->HoudiniEngineBakeOption); + } + + if (!InPDGAssetLink->AnyRemainingAutoBakeNodes()) + InPDGAssetLink->HandleOnPostBake(bSuccess); } bool @@ -4200,11 +5049,13 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, bool bInBakeForBlueprint, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, TArray& OutBakedActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return false; if (!IsValid(InNode)) @@ -4218,8 +5069,8 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); // Determine the actor/package replacement settings - const bool bReplaceActors = !bInBakeForBlueprint && InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + const bool bReplaceActors = !bInBakeForBlueprint && InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + const bool bReplaceAssets = InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; // Determine the output types to bake: don't bake landscapes in blueprint baking mode TArray OutputTypesToBake; @@ -4233,46 +5084,104 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent); } const int32 NumWorkResults = InNode->WorkResult.Num(); - for (int32 WorkResultIdx = 0; WorkResultIdx < NumWorkResults; ++WorkResultIdx) + FScopedSlowTask Progress(NumWorkResults, FText::FromString(FString::Printf(TEXT("Baking PDG Node Output %s ..."), *InNode->GetName()))); + Progress.MakeDialog(); + TArray OurBakedActors; + TArray WorkResultObjectBakedActors; + for (int32 WorkResultArrayIdx = 0; WorkResultArrayIdx < NumWorkResults; ++WorkResultArrayIdx) { - FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultIdx]; + FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultArrayIdx]; const int32 NumWorkResultObjects = WorkResult.ResultObjects.Num(); - for (int32 WorkResultObjectIdx = 0; WorkResultObjectIdx < NumWorkResultObjects; ++WorkResultObjectIdx) + for (int32 WorkResultObjectArrayIdx = 0; WorkResultObjectArrayIdx < NumWorkResultObjects; ++WorkResultObjectArrayIdx) { + WorkResultObjectBakedActors.Reset(); + Progress.EnterProgressFrame(1.0f); + BakePDGWorkResultObject( InPDGAssetLink, InNode, - WorkResultIdx, - WorkResultObjectIdx, + WorkResultArrayIdx, + WorkResultObjectArrayIdx, bReplaceActors, bReplaceAssets, !bInBakeForBlueprint, - OutBakedActors, + bInIsAutoBake, + OurBakedActors, + WorkResultObjectBakedActors, OutPackagesToSave, OutBakeStats, OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, FallbackWorldOutlinerFolderPath.ToString() ); + + OurBakedActors.Append(WorkResultObjectBakedActors); } } + OutBakedActors = MoveTemp(OurBakedActors); + return true; } +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) +{ + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + TArray BakedActors; + + const bool bBakeBlueprints = false; + + bool bSuccess = BakePDGTOPNodeOutputsKeepActors( + InPDGAssetLink, InTOPNode, bBakeBlueprints, bInIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); + + SaveBakedPackages(PackagesToSave); + + // Recenter and select the baked actors + if (GEditor && BakedActors.Num() > 0) + GEditor->SelectNone(false, true); + + for (const FHoudiniEngineBakedActor& Entry : BakedActors) + { + if (!IsValid(Entry.Actor)) + continue; + + if (bInRecenterBakedActors) + CenterActorToBoundingBoxCenter(Entry.Actor); + + if (GEditor) + GEditor->SelectActor(Entry.Actor, true, false); + } + + if (GEditor && BakedActors.Num() > 0) + GEditor->NoteSelectionChange(); + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + return bSuccess; +} + bool FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNetwork* InNetwork, bool bInBakeForBlueprint, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, TArray& BakedActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return false; if (!IsValid(InNetwork)) @@ -4284,16 +5193,16 @@ FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors( if (!IsValid(Node)) continue; - bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bInBakeForBlueprint, BakedActors, OutPackagesToSave, OutBakeStats); + bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bInBakeForBlueprint, bInIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, OutPackagesToSave, OutBakeStats); } return bSuccess; } bool -FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink) +FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return false; TArray PackagesToSave; @@ -4301,9 +5210,10 @@ FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* TArray BakedActors; const bool bBakeBlueprints = false; + const bool bIsAutoBake = false; bool bSuccess = true; - switch(InPDGAssetLink->PDGBakeSelectionOption) + switch(InBakeSelectionOption) { case EPDGBakeSelectionOption::All: for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) @@ -4316,14 +5226,14 @@ FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* if (!IsValid(Node)) continue; - bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); + bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); } } break; case EPDGBakeSelectionOption::SelectedNetwork: - bSuccess = BakePDGTOPNetworkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNetwork(), bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); + bSuccess = BakePDGTOPNetworkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNetwork(), bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); case EPDGBakeSelectionOption::SelectedNode: - bSuccess = BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); + bSuccess = BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); } SaveBakedPackages(PackagesToSave); @@ -4337,7 +5247,7 @@ FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* if (!IsValid(Entry.Actor)) continue; - if (InPDGAssetLink->bRecenterBakedActors) + if (bInRecenterBakedActors) CenterActorToBoundingBoxCenter(Entry.Actor); if (GEditor) @@ -4353,6 +5263,9 @@ FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); } + // Broadcast that the bake is complete + InPDGAssetLink->HandleOnPostBake(bSuccess); + return bSuccess; } @@ -4361,12 +5274,14 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( const TArray& InBakedActors, bool bInRecenterBakedActors, bool bInReplaceAssets, - const FString& InAssetName, + const FString& InHoudiniAssetName, + const FString& InHoudiniAssetActorName, const FDirectoryPath& InBakeFolder, - TArray* const InNonPDGBakedOuputs, + TArray* const InNonPDGBakedOutputs, TMap* const InPDGBakedOutputs, TArray& OutBlueprints, - TArray& OutPackagesToSave) + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) { // // Clear selection // if (GEditor) @@ -4380,19 +5295,61 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); const bool bIsAssetEditorSubsystemValid = IsValid(AssetEditorSubsystem); TArray AssetsToReOpenEditors; - TSet BakedActorSet; + TMap BakedActorMap; for (const FHoudiniEngineBakedActor& Entry : InBakedActors) { AActor *Actor = Entry.Actor; - if (!Actor || Actor->IsPendingKill()) + if (!IsValid(Actor)) continue; - if (BakedActorSet.Contains(Actor)) + // If we have a previously baked a blueprint, get the bake counter from it so that both replace and increment + // is consistent with the bake counter + int32 BakeCounter = 0; + FHoudiniBakedOutputObject* BakedOutputObject = nullptr; + // Get the baked output object + if (Entry.PDGWorkResultArrayIndex >= 0 && Entry.PDGWorkItemIndex >= 0 && Entry.PDGWorkResultObjectArrayIndex >= 0 && InPDGBakedOutputs) + { + const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkResultArrayIndex, Entry.PDGWorkResultObjectArrayIndex); + FHoudiniPDGWorkResultObjectBakedOutput* WorkResultObjectBakedOutput = InPDGBakedOutputs->Find(Key); + if (WorkResultObjectBakedOutput) + { + if (Entry.OutputIndex >= 0 && WorkResultObjectBakedOutput->BakedOutputs.IsValidIndex(Entry.OutputIndex)) + { + BakedOutputObject = WorkResultObjectBakedOutput->BakedOutputs[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); + } + } + } + else if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs) + { + if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs->IsValidIndex(Entry.OutputIndex)) + { + BakedOutputObject = (*InNonPDGBakedOutputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); + } + } + + if (BakedActorMap.Contains(Actor)) + { + // Record the blueprint as the previous bake blueprint and clear the info of the temp bake actor/component + if (BakedOutputObject) + { + UBlueprint* const BakedBlueprint = BakedActorMap[Actor]; + if (BakedBlueprint) + BakedOutputObject->Blueprint = FSoftObjectPath(BakedBlueprint).ToString(); + else + BakedOutputObject->Blueprint.Empty(); + BakedOutputObject->Actor.Empty(); + // TODO: Set the baked component to the corresponding component in the blueprint? + BakedOutputObject->BakedComponent.Empty(); + } continue; + } - BakedActorSet.Add(Actor); + // Add a placeholder entry since we've started processing the actor, we'll replace the null with the blueprint + // if successful and leave it as null if the bake fails (the actor will then be skipped if it appears in the + // array again). + BakedActorMap.Add(Actor, nullptr); UObject* Asset = nullptr; @@ -4403,6 +5360,17 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( // Create package for out Blueprint FString BlueprintName; + // For instancers we determine the bake folder from the instancer, + // for everything else we use the baked object's bake folder + // If all of that is blank, we fall back to InBakeFolder. + FString BakeFolderPath; + if (Entry.bInstancerOutput) + BakeFolderPath = Entry.InstancerPackageParams.BakeFolder; + else + BakeFolderPath = Entry.BakeFolderPath; + if (BakeFolderPath.IsEmpty()) + BakeFolderPath = InBakeFolder.Path; + FHoudiniPackageParams PackageParams; // Set the replace mode based on if we are doing a replacement or incremental asset bake const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? @@ -4410,62 +5378,39 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( PackageParams, FHoudiniOutputObjectIdentifier(), - InBakeFolder.Path, + BakeFolderPath, Entry.ActorBakeName.ToString() + "_BP", - InAssetName, + InHoudiniAssetName, + InHoudiniAssetActorName, AssetPackageReplaceMode); - // If we have a previously baked a blueprint, get the bake counter from it so that both replace and increment - // is consistent with the bake counter - int32 BakeCounter = 0; - UBlueprint* InPreviousBlueprint = nullptr; - FHoudiniBakedOutputObject* BakedOutputObject = nullptr; - FHoudiniPDGWorkResultObjectBakedOutput* WorkResultObjectBakedOutput = nullptr; - // Get the baked output object - if (Entry.PDGWorkResultIndex >= 0 && Entry.PDGWorkResultObjectIndex >= 0 && InPDGBakedOutputs) - { - const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkResultIndex, Entry.PDGWorkResultObjectIndex); - WorkResultObjectBakedOutput = InPDGBakedOutputs->Find(Key); - if (WorkResultObjectBakedOutput) - { - if (Entry.OutputIndex >= 0 && WorkResultObjectBakedOutput->BakedOutputs.IsValidIndex(Entry.OutputIndex)) - { - BakedOutputObject = WorkResultObjectBakedOutput->BakedOutputs[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); - } - } - } - else if (Entry.OutputIndex >= 0 && InNonPDGBakedOuputs) - { - if (Entry.OutputIndex >= 0 && InNonPDGBakedOuputs->IsValidIndex(Entry.OutputIndex)) - { - BakedOutputObject = (*InNonPDGBakedOuputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); - } - } if (BakedOutputObject) { - InPreviousBlueprint = BakedOutputObject->GetBlueprintIfValid(); - if (IsValid(InPreviousBlueprint)) + UBlueprint* const PreviousBlueprint = BakedOutputObject->GetBlueprintIfValid(); + if (IsValid(PreviousBlueprint)) { - if (PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBlueprint)) + if (PackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBlueprint)) { - PackageParams.GetBakeCounterFromBakedAsset(InPreviousBlueprint, BakeCounter); + PackageParams.GetBakeCounterFromBakedAsset(PreviousBlueprint, BakeCounter); } } } UPackage* Package = PackageParams.CreatePackageForObject(BlueprintName, BakeCounter); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) { HOUDINI_LOG_WARNING(TEXT("Could not find or create a package for the blueprint of %s"), *(Actor->GetPathName())); continue; } + OutBakeStats.NotifyPackageCreated(1); + if (!Package->IsFullyLoaded()) Package->FullyLoad(); //Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor(*BlueprintName, Package, Actor, false); - // Find existing asset first first (only relevant if we are in replacement mode). If the existing asset has a + // Find existing asset first (only relevant if we are in replacement mode). If the existing asset has a // different base class than the incoming actor, we reparent the blueprint to the new base class before // clearing the SCS graph and repopulating it from the temp actor. Asset = StaticFindObjectFast(UBlueprint::StaticClass(), Package, FName(*BlueprintName)); @@ -4491,6 +5436,7 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( } } } + // TODO: PENDINGKILL replacement ? else if (Asset && Asset->IsPendingKill()) { // Rename to pending kill so that we can use the desired name @@ -4500,6 +5446,7 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( Asset = nullptr; } + bool bCreatedNewBlueprint = false; if (!Asset) { UBlueprintFactory* Factory = NewObject(); @@ -4508,13 +5455,16 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); Asset = AssetToolsModule.Get().CreateAsset( - BlueprintName, InBakeFolder.Path, - UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + BlueprintName, PackageParams.GetPackagePath(), + UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + + if (Asset) + bCreatedNewBlueprint = true; } UBlueprint* Blueprint = Cast(Asset); - if (!Blueprint || Blueprint->IsPendingKill()) + if (!IsValid(Blueprint)) { HOUDINI_LOG_WARNING( TEXT("Found an asset at %s/%s, but it was not a blueprint or was pending kill."), @@ -4523,6 +5473,15 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( continue; } + if (bCreatedNewBlueprint) + { + OutBakeStats.NotifyObjectsCreated(Blueprint->GetClass()->GetName(), 1); + } + else + { + OutBakeStats.NotifyObjectsUpdated(Blueprint->GetClass()->GetName(), 1); + } + // Close editors opened on existing asset if applicable if (Blueprint && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Blueprint, false) != nullptr) { @@ -4530,11 +5489,17 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( AssetsToReOpenEditors.Add(Blueprint); } - // Record the blueprint as the previous bake blueprint + // Record the blueprint as the previous bake blueprint and clear the info of the temp bake actor/component if (BakedOutputObject) + { BakedOutputObject->Blueprint = FSoftObjectPath(Blueprint).ToString(); + BakedOutputObject->Actor.Empty(); + // TODO: Set the baked component to the corresponding component in the blueprint? + BakedOutputObject->BakedComponent.Empty(); + } OutBlueprints.Add(Blueprint); + BakedActorMap[Actor] = Blueprint; // Clear old Blueprint Node tree { @@ -4547,17 +5512,26 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(Actor, Blueprint); - UWorld* World = Actor->GetWorld(); - if (!World) - World = GWorld; - - World->EditorDestroyActor(Actor, true); - // Save the created BP package. Package->MarkPackageDirty(); OutPackagesToSave.Add(Package); } + // Destroy the actors that were baked + for (const auto& BakedActorEntry : BakedActorMap) + { + AActor* const Actor = BakedActorEntry.Key; + if (!IsValid(Actor)) + continue; + + UWorld* World = Actor->GetWorld(); + if (!World) + World = GWorld; + + if (World) + World->EditorDestroyActor(Actor, true); + } + // Re-open asset editors for updated blueprints that were open in editors if (bIsAssetEditorSubsystemValid && AssetsToReOpenEditors.Num() > 0) { @@ -4577,6 +5551,9 @@ bool FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + bool bInRecenterBakedActors, TArray& OutBlueprints, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats) @@ -4595,7 +5572,7 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( return false; } - const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + const bool bReplaceAssets = InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; // Bake PDG output to new actors // bInBakeForBlueprint == true will skip landscapes and instanced actor components @@ -4605,6 +5582,8 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( InPDGAssetLink, InNode, bInBakeForBlueprint, + bInIsAutoBake, + InPDGBakePackageReplaceMode, BakedActors, OutPackagesToSave, OutBakeStats @@ -4612,18 +5591,66 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( if (bSuccess) { + AActor* OwnerActor = InPDGAssetLink->GetOwnerActor(); bSuccess = BakeBlueprintsFromBakedActors( BakedActors, - InPDGAssetLink->bRecenterBakedActors, + bInRecenterBakedActors, bReplaceAssets, InPDGAssetLink->AssetName, + IsValid(OwnerActor) ? OwnerActor->GetName() : FString(), InPDGAssetLink->BakeFolder, nullptr, &InNode->GetBakedWorkResultObjectsOutputs(), OutBlueprints, - OutPackagesToSave); + OutPackagesToSave, + OutBakeStats); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) +{ + TArray Blueprints; + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + + if (!IsValid(InPDGAssetLink)) + return false; + + const bool bSuccess = BakePDGTOPNodeBlueprints( + InPDGAssetLink, + InTOPNode, + bInIsAutoBake, + InPDGBakePackageReplaceMode, + bInRecenterBakedActors, + Blueprints, + PackagesToSave, + BakeStats); + + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor && Blueprints.Num() > 0) + { + TArray Assets; + Assets.Reserve(Blueprints.Num()); + for (UBlueprint* Blueprint : Blueprints) + { + Assets.Add(Blueprint); + } + GEditor->SyncBrowserToObjects(Assets); + } + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); } + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + return bSuccess; } @@ -4631,40 +5658,44 @@ bool FHoudiniEngineBakeUtils::BakePDGTOPNetworkBlueprints( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNetwork* InNetwork, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + bool bInRecenterBakedActors, TArray& OutBlueprints, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return false; if (!IsValid(InNetwork)) return false; + const bool bIsAutoBake = false; bool bSuccess = true; for (UTOPNode* Node : InNetwork->AllTOPNodes) { if (!IsValid(Node)) continue; - bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, OutBlueprints, OutPackagesToSave, OutBakeStats); + bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, bIsAutoBake, InPDGBakePackageReplaceMode, bInRecenterBakedActors, OutBlueprints, OutPackagesToSave, OutBakeStats); } return bSuccess; } bool -FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink) +FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) { TArray Blueprints; TArray PackagesToSave; FHoudiniEngineOutputStats BakeStats; - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return false; + const bool bIsAutoBake = false; bool bSuccess = true; - switch(InPDGAssetLink->PDGBakeSelectionOption) + switch(InBakeSelectionOption) { case EPDGBakeSelectionOption::All: for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) @@ -4677,7 +5708,7 @@ FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGA if (!IsValid(Node)) continue; - bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, Blueprints, PackagesToSave, BakeStats); + bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, bIsAutoBake, InPDGBakePackageReplaceMode, bInRecenterBakedActors, Blueprints, PackagesToSave, BakeStats); } } break; @@ -4685,13 +5716,18 @@ FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGA bSuccess &= BakePDGTOPNetworkBlueprints( InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNetwork(), - Blueprints, - PackagesToSave, - BakeStats); + InPDGBakePackageReplaceMode, + bInRecenterBakedActors, + Blueprints, + PackagesToSave, + BakeStats); case EPDGBakeSelectionOption::SelectedNode: bSuccess &= BakePDGTOPNodeBlueprints( InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), + bIsAutoBake, + InPDGBakePackageReplaceMode, + bInRecenterBakedActors, Blueprints, PackagesToSave, BakeStats); @@ -4719,6 +5755,9 @@ FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGA TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + // Broadcast that the bake is complete + InPDGAssetLink->HandleOnPostBake(bSuccess); + return bSuccess; } @@ -4795,6 +5834,7 @@ FHoudiniEngineBakeUtils::FindDesiredBakeActorFromBakeActorName( // If we found an actor and it is pending kill, rename it and don't use it if (FoundActor) { + // TODO: PENDINGKILL replacement ? if (FoundActor->IsPendingKill()) { if (bRenamePendingKillActor) @@ -4809,8 +5849,8 @@ FHoudiniEngineBakeUtils::FindDesiredBakeActorFromBakeActorName( *MakeUniqueObjectNameIfNeeded( FoundActor->GetOuter(), FoundActor->GetClass(), - FName(FoundActor->GetName() + "_Pending_Kill"), - FoundActor).ToString(), + FoundActor->GetName() + "_Pending_Kill", + FoundActor), false); } if (bInNoPendingKillActors) @@ -4853,7 +5893,7 @@ bool FHoudiniEngineBakeUtils::FindUnrealBakeActor( } else { - OutBakeActorName = *BakeActorNameStr; + OutBakeActorName = FName(BakeActorNameStr, NAME_NO_NUMBER_INTERNAL); // We have a bake actor name, look for the actor AActor* BakeNameActor = nullptr; if (FindDesiredBakeActorFromBakeActorName(BakeActorNameStr, InLevel, BakeNameActor)) @@ -4967,6 +6007,7 @@ FHoudiniEngineBakeUtils::CheckForAndRefineHoudiniProxyMesh( bool bInReplacePreviousBake, EHoudiniEngineBakeOption InBakeOption, bool bInRemoveHACOutputOnSuccess, + bool bInRecenterBakedActors, bool& bOutNeedsReCook) { if (!IsValid(InHoudiniAssetComponent)) @@ -4998,8 +6039,8 @@ FHoudiniEngineBakeUtils::CheckForAndRefineHoudiniProxyMesh( // Only if (!InHoudiniAssetComponent->IsBakeAfterNextCookEnabled() || !InHoudiniAssetComponent->GetOnPostCookBakeDelegate().IsBound()) { - InHoudiniAssetComponent->GetOnPostCookBakeDelegate().BindLambda([bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess](UHoudiniAssetComponent* InHAC) { - return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(InHAC, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess); + InHoudiniAssetComponent->GetOnPostCookBakeDelegate().BindLambda([bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors](UHoudiniAssetComponent* InHAC) { + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(InHAC, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors); }); } InHoudiniAssetComponent->MarkAsNeedCook(); @@ -5040,7 +6081,19 @@ FHoudiniEngineBakeUtils::CenterActorToBoundingBoxCenter(AActor* InActor) const bool bIncludeFromChildActors = true; FVector Origin; FVector BoxExtent; - InActor->GetActorBounds(bOnlyCollidingComponents, Origin, BoxExtent, bIncludeFromChildActors); + // InActor->GetActorBounds(bOnlyCollidingComponents, Origin, BoxExtent, bIncludeFromChildActors); + FBox Box(ForceInit); + + InActor->ForEachComponent(bIncludeFromChildActors, [&](const UPrimitiveComponent* InPrimComp) + { + // Only use non-editor-only components for the bounds calculation (to exclude things like editor only sprite/billboard components) + if (InPrimComp->IsRegistered() && !InPrimComp->IsEditorOnly() && + (!bOnlyCollidingComponents || InPrimComp->IsCollisionEnabled())) + { + Box += InPrimComp->Bounds.GetBox(); + } + }); + Box.GetCenterAndExtents(Origin, BoxExtent); const FVector Delta = Origin - RootComponent->GetComponentLocation(); // Actor->SetActorLocation(Origin); @@ -5085,34 +6138,56 @@ FHoudiniEngineBakeUtils::GetActorRootComponent(AActor* InActor, bool bCreateIfMi return RootComponent; } -FName -FHoudiniEngineBakeUtils::MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, FName InName, UObject* InObjectThatWouldBeRenamed) +FString +FHoudiniEngineBakeUtils::MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, const FString& InName, UObject* InObjectThatWouldBeRenamed) { if (IsValid(InObjectThatWouldBeRenamed)) { const FName CurrentName = InObjectThatWouldBeRenamed->GetFName(); - if (CurrentName == InName) + if (CurrentName.ToString() == InName) return InName; // Check if the prefix matches (without counter suffix) the new name - const FString CurrentNamePlainStr = CurrentName.GetPlainNameString(); - if (CurrentNamePlainStr == InName.ToString()) - return CurrentName; + // In other words, if InName is 'my_actor' and the object is already an increment of it, 'my_actor_5' then + // don't we can just keep the current name + if (CurrentName.GetPlainNameString() == InName) + return CurrentName.ToString(); } UObject* ExistingObject = nullptr; - if (InOuter == ANY_PACKAGE) - { - ExistingObject = StaticFindObject(nullptr, ANY_PACKAGE, *InName.ToString()); - } - else - { - ExistingObject = StaticFindObjectFast(nullptr, InOuter, InName); - } + FName CandidateName(InName); + bool bAppendedNumber = false; + // Do our own loop for generate suffixes as sequentially as possible. If this turns out to be expensive we can + // revert to MakeUniqueObjectName. + // return MakeUniqueObjectName(InOuter, InClass, CandidateName).ToString(); + do + { + if (InOuter == ANY_PACKAGE) + { + ExistingObject = StaticFindObject(nullptr, ANY_PACKAGE, *(CandidateName.ToString())); + } + else + { + ExistingObject = StaticFindObjectFast(nullptr, InOuter, CandidateName); + } + + if (ExistingObject) + { + if (!bAppendedNumber) + { + const bool bSplitName = false; + CandidateName = FName(*InName, NAME_EXTERNAL_TO_INTERNAL(1), FNAME_Add, bSplitName); + bAppendedNumber = true; + } + else + { + CandidateName.SetNumber(CandidateName.GetNumber() + 1); + } + // CandidateName = FString::Printf(TEXT("%s_%d"), *InName, ++Counter); + } + } while (ExistingObject); - if (ExistingObject) - return MakeUniqueObjectName(InOuter, InClass, InName); - return InName; + return CandidateName.ToString(); } FName @@ -5206,4 +6281,34 @@ FHoudiniEngineBakeUtils::DestroyPreviousBakeOutput( return NumDeleted; } -#undef LOCTEXT_NAMESPACE \ No newline at end of file +UMaterialInterface* FHoudiniEngineBakeUtils::BakeSingleMaterialToPackage(UMaterialInterface* InOriginalMaterial, + const FHoudiniPackageParams & InPackageParams, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats) +{ + if (!IsValid(InOriginalMaterial)) + { + return nullptr; + } + + // We only deal with materials. + if (!InOriginalMaterial->IsA(UMaterial::StaticClass()) && !InOriginalMaterial->IsA(UMaterialInstance::StaticClass())) + { + return nullptr; + } + + FString MaterialName = InOriginalMaterial->GetName(); + + // Duplicate material resource. + UMaterialInterface * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( + InOriginalMaterial, nullptr, MaterialName, InPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap, + OutBakeStats); + + if (!IsValid(DuplicatedMaterial)) + return nullptr; + + return DuplicatedMaterial; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h index 421ab59c7..84aa0e730 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,10 +23,12 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + #pragma once #include "HoudiniPDGAssetLink.h" #include "HoudiniOutput.h" +#include "HoudiniPackageParams.h" class UHoudiniAssetComponent; class UHoudiniOutput; @@ -48,6 +50,7 @@ struct FHoudiniOutputObject; struct FHoudiniOutputObjectIdentifier; struct FHoudiniEngineOutputStats; struct FHoudiniBakedOutputObject; +struct FHoudiniAttributeResolver; enum class EHoudiniLandscapeOutputBakeType : uint8; @@ -55,10 +58,16 @@ enum class EHoudiniLandscapeOutputBakeType : uint8; UENUM() enum class EHoudiniInstancerComponentType : uint8 { + // Single static mesh component StaticMeshComponent, + // (Hierarichal)InstancedStaticMeshComponent InstancedStaticMeshComponent, MeshSplitInstancerComponent, - InstancedActorComponent + InstancedActorComponent, + // For baking foliage as foliage + FoliageInstancedStaticMeshComponent, + // Baking foliage as HISMC + FoliageAsHierarchicalInstancedStaticMeshComponent }; // Helper struct to track actors created/used when baking, with @@ -75,7 +84,10 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakedActor int32 InOutputIndex, const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, UObject* InBakedObject, - UObject* InSourceObject); + UObject* InSourceObject, + UObject* InBakedComponent, + const FString& InBakeFolderPath, + const FHoudiniPackageParams& InBakedObjectPackageParams); // The actor that the baked output was associated with AActor* Actor = nullptr; @@ -92,17 +104,41 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakedActor // The world outliner folder the actor is placed in FName WorldOutlinerFolder = NAME_None; - // The index of the work item when baking PDG - int32 PDGWorkResultIndex = INDEX_NONE; + // The array index of the work result when baking PDG + int32 PDGWorkResultArrayIndex = INDEX_NONE; + + // The work item index (as returned by HAPI) for the work item/work result, used when baking PDG + int32 PDGWorkItemIndex = INDEX_NONE; - // The index of the work result object of the work item when baking PDG - int32 PDGWorkResultObjectIndex = INDEX_NONE; + // The array index of the work result object of the work result when baking PDG + int32 PDGWorkResultObjectArrayIndex = INDEX_NONE; // The baked primary asset (such as static mesh) UObject* BakedObject = nullptr; // The temp asset that was baked to BakedObject UObject* SourceObject = nullptr; + + // The baked component or foliage type in the case of foliage + UObject* BakedComponent = nullptr; + + // The bake folder path to where BakedObject was baked + FString BakeFolderPath; + + // The package params for the BakedObject + FHoudiniPackageParams BakedObjectPackageParams; + + // True if this entry was created by an instancer output. + bool bInstancerOutput; + + // The package params built for the instancer part of the output, if this was an instancer. + // This would mostly be useful in situations for we later need the resolver and/or cached attributes and + // tokens, such as for blueprint baking. + FHoudiniPackageParams InstancerPackageParams; + + // Used to delay all post bake calls so they are done only once per baked actor + bool bPostBakeProcessPostponed = false; + }; struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils @@ -119,14 +155,17 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils static ALandscapeProxy* BakeHeightfield( ALandscapeProxy * InLandscapeProxy, const FHoudiniPackageParams &PackageParams, - const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType); + const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType, + FHoudiniEngineOutputStats& OutBakeStats); static bool BakeCurve( USplineComponent* InSplineComponent, ULevel* InLevel, const FHoudiniPackageParams &PackageParams, + const FName& InActorName, AActor*& OutActor, USplineComponent*& OutSplineComponent, + FHoudiniEngineOutputStats& OutBakeStats, FName InOverrideFolderPath=NAME_None, AActor* InActor=nullptr); @@ -135,10 +174,13 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils FHoudiniBakedOutputObject& InBakedOutputObject, // const TArray& InAllBakedOutputs, const FHoudiniPackageParams &PackageParams, + FHoudiniAttributeResolver& InResolver, bool bInReplaceActors, bool bInReplaceAssets, - TArray& OutActors, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor=nullptr, const FString& InFallbackWorldOutlinerFolder=""); @@ -158,16 +200,18 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils UStaticMesh * StaticMesh, const FHoudiniPackageParams & PackageParams, const TArray& InAllOutputs, - const FDirectoryPath& InTempCookFolder); + const FDirectoryPath& InTempCookFolder, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats); static bool BakeLandscape( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, - UHoudiniOutput* InOutput, - TMap& InBakedOutputObjects, + const TArray& InAllOutputs, + TArray& InBakedOutputs, bool bInReplaceActors, bool bInReplaceAssets, - FString BakePath, - FString HoudiniAssetName, + const FString& BakePath, FHoudiniEngineOutputStats& BakeStats); static bool BakeLandscapeObject( @@ -176,11 +220,13 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils bool bInReplaceActors, bool bInReplaceAssets, FHoudiniPackageParams& PackageParams, + FHoudiniAttributeResolver& InResolver, TArray& WorldsToUpdate, TArray& OutPackagesToUnload, FHoudiniEngineOutputStats& BakeStats); static bool BakeInstancerOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, TArray& InBakedOutputs, @@ -189,13 +235,17 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, TArray const* InInstancerComponentTypesToBake=nullptr, AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); + const FString& InFallbackWorldOutlinerFolder=TEXT("")); static bool BakeInstancerOutputToActors_ISMC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, // const TArray& InAllBakedOutputs, @@ -207,12 +257,16 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); + const FString& InFallbackWorldOutlinerFolder=TEXT("")); static bool BakeInstancerOutputToActors_IAC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, const FHoudiniOutputObject& InOutputObject, @@ -220,10 +274,13 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FDirectoryPath& InBakeFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, - TArray& OutPackagesToSave); + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); static bool BakeInstancerOutputToActors_MSIC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, // const TArray& InAllBakedOutputs, @@ -235,12 +292,16 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, - TArray& OutActors, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor=nullptr, const FString& InFallbackWorldOutlinerFolder=""); static bool BakeInstancerOutputToActors_SMC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, // const TArray& InAllBakedOutputs, @@ -251,8 +312,11 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, - TArray& OutActors, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor=nullptr, const FString& InFallbackWorldOutlinerFolder=""); @@ -263,27 +327,33 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const TArray& InParentOutputs, const TArray& InCurrentBakedActors, const FString& InTemporaryCookFolder, - TArray & OutCreatedPackages); + TArray & OutCreatedPackages, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats); - static UMaterial * DuplicateMaterialAndCreatePackage( - UMaterial * Material, - UMaterial* PreviousBakeMaterial, + static UMaterialInterface * DuplicateMaterialAndCreatePackage( + UMaterialInterface * Material, + UMaterialInterface * PreviousBakeMaterial, const FString & SubMaterialName, const FHoudiniPackageParams& ObjectPackageParams, - TArray & OutCreatedPackages); + TArray & OutCreatedPackages, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats); static void ReplaceDuplicatedMaterialTextureSample( UMaterialExpression * MaterialExpression, UMaterialExpression* PreviousBakeMaterialExpression, const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages); + TArray & OutCreatedPackages, + FHoudiniEngineOutputStats& OutBakeStats); static UTexture2D * DuplicateTextureAndCreatePackage( UTexture2D * Texture, UTexture2D* PreviousBakeTexture, const FString & SubTextureName, const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages); + TArray & OutCreatedPackages, + FHoudiniEngineOutputStats& OutBakeStats); // Bake a Houdini asset component (InHACToBake) based on the bInReplace and BakeOption arguments. // Returns true if the underlying bake function (for example, BakeHoudiniActorToActors, returns true (or a valid UObject*)) @@ -291,10 +361,11 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils UHoudiniAssetComponent* InHACToBake, bool bInReplacePreviousBake, EHoudiniEngineBakeOption InBakeOption, - bool bInRemoveHACOutputOnSuccess); + bool bInRemoveHACOutputOnSuccess, + bool bInRecenterBakedActors); static bool BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets); + UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets, bool bInRecenterBakedActor); static bool BakeHoudiniActorToActors( UHoudiniAssetComponent* HoudiniAssetComponent, @@ -309,66 +380,111 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FString& InFallbackWorldOutlinerFolder=""); static bool BakeHoudiniOutputsToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, const TArray& InOutputs, TArray& InBakedOutputs, - const FString& InHoudiniAssetName, const FTransform& InParentTransform, const FDirectoryPath& InBakeFolder, const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutNewActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats, TArray const* InOutputTypesToBake=nullptr, TArray const* InInstancerComponentTypesToBake=nullptr, AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); + const FString& InFallbackWorldOutlinerFolder=TEXT("")); + + static bool BakeInstancerOutputToFoliage( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats); static bool CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent); static bool BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); static bool BakeStaticMeshOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, TArray& InBakedOutputs, - const FString& InHoudiniAssetName, const FDirectoryPath& InBakeFolder, const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor=nullptr, const FString& InFallbackWorldOutlinerFolder=""); + static bool ResolvePackageParams( + const UHoudiniAssetComponent* HoudiniAssetComponent, + UHoudiniOutput* InOutput, + const FHoudiniOutputObjectIdentifier& Identifier, + const FHoudiniOutputObject& InOutputObject, + const FString& DefaultObjectName, + const FDirectoryPath& InBakeFolder, + const bool bInReplaceAssets, + FHoudiniPackageParams& OutPackageParams, + TArray& OutPackagesToSave, + const FString& InHoudiniAssetName=TEXT(""), + const FString& InHoudiniAssetActorName=TEXT("")); + static bool BakeHoudiniCurveOutputToActors( - UHoudiniOutput* Output, - TMap& InBakedOutputObjects, - const TArray& InAllBakedOutputs, - const FString& InHoudiniAssetName, + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, const FDirectoryPath& InBakeFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); + const FString& InFallbackWorldOutlinerFolder=TEXT("")); static bool BakeBlueprintsFromBakedActors( const TArray& InBakedActors, bool bInRecenterBakedActors, bool bInReplaceAssets, - const FString& InAssetName, + const FString& InHoudiniAssetName, + const FString& InHoudiniAssetActorName, const FDirectoryPath& InBakeFolder, - TArray* const InNonPDGBakedOuputs, + TArray* const InNonPDGBakedOutputs, TMap* const InPDGBakedOutputs, TArray& OutBlueprints, - TArray& OutPackagesToSave); + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); - static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); + static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, bool bInRecenterBakedActors); - static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, FHoudiniEngineOutputStats& InBakeStats, TArray& OutBlueprints, TArray& OutPackagesToSave); + static bool BakeBlueprints( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceAssets, + bool bInRecenterBakedActors, + FHoudiniEngineOutputStats& InBakeStats, + TArray& OutBlueprints, + TArray& OutPackagesToSave); static bool CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint); @@ -385,15 +501,23 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils // Look for InObjectToFind among InOutputs. Return true if found and set OutOutputIndex and OutIdentifier. static bool FindOutputObject( - const UObject* InObjectToFind, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier); + const UObject* InObjectToFind, EHoudiniOutputType InOutputType, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier); + + static bool IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC); - static bool IsObjectTemporary(UObject* InObject, UHoudiniAssetComponent* InHAC); + // Returns true if InObject is in InTemporaryCookFolder, or in the default Temporary cook folder from the runtime + // settings. + static bool IsObjectInTempFolder(UObject* const InObject, const FString& InTemporaryCookFolder); static bool IsObjectTemporary( - UObject* InObject, const TArray& InParentOutputs, const FString& InTemporaryCookFolder); + UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder); // Function used to copy properties from the source Static Mesh Component to the new (baked) one - static void CopyPropertyToNewActorAndComponent(AActor* NewActor, UStaticMeshComponent* NewSMC, UStaticMeshComponent* InSMC); + static void CopyPropertyToNewActorAndComponent( + AActor* NewActor, + UStaticMeshComponent* NewSMC, + UStaticMeshComponent* InSMC, + bool bInCopyWorldTransform=false); // Finds the world/level indicated by the package path. // If the level doesn't exists, it will be created. @@ -476,11 +600,13 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils static bool BakePDGWorkResultObject( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, - int32 InWorkResultIndex, - int32 InWorkResultObjectIndex, + int32 InWorkResultArrayIndex, + int32 InWorkResultObjectArrayIndex, bool bInReplaceActors, bool bInReplaceAssets, bool bInBakeToWorkResultActor, + bool bInIsAutoBake, + const TArray& InBakedActors, TArray& OutBakedActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats, @@ -488,18 +614,12 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils TArray const* InInstancerComponentTypesToBake=nullptr, const FString& InFallbackWorldOutlinerFolder=""); - static bool BakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName); - // Checks if auto-bake is enabled on InPDGAssetLink, and if it is, calls BakePDGWorkResultObject. - static void AutoBakePDGWorkResultObject( + static void CheckPDGAutoBakeAfterResultObjectLoaded( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName); + int32 InWorkItemHAPIIndex, + int32 InWorkItemResultInfoIndex); // Bake PDG output. This bakes all assets from all work items in the specified InNode (FTOPNode). // It uses the existing output actors in the level, but breaks any links from these actors to the PDG link and @@ -508,10 +628,15 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, bool bInBakeForBlueprint, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, TArray& OutBakedActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats); + // Helper function to bake only a specific PDG TOP node's outputs to actors. + static bool BakePDGTOPNodeOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); + // Bake PDG output. This bakes all assets from all work items in the specified TOP network. // It uses the existing output actors in the level, but breaks any links // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent @@ -520,6 +645,8 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNetwork* InNetwork, bool bInBakeForBlueprint, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, TArray& OutBakedActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats); @@ -528,7 +655,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils // InPDGAssetLink->PDGBakeSelectionOption. It uses the existing output actors in the level, but breaks any links // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent // PDG output actor. - static bool BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink); + static bool BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); // Bake PDG output. This bakes all supported assets from all work items in the specified InNode (FTOPNode). // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from @@ -536,9 +663,15 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils static bool BakePDGTOPNodeBlueprints( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + bool bInRecenterBakedActors, TArray& OutBlueprints, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats); + + // Helper to bake only a specific PDG TOP node's outputs to blueprint(s). + static bool BakePDGTOPNodeBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); // Bake PDG output. This bakes all supported assets from all work items in the specified TOP network. // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from @@ -546,6 +679,8 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils static bool BakePDGTOPNetworkBlueprints( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNetwork* InNetwork, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + bool bInRecenterBakedActors, TArray& OutBlueprints, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats); @@ -553,7 +688,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils // Bake PDG output. This bakes assets from TOP networks and nodes according to // InPDGAssetLink->PDGBakeSelectionOption. It duplicates the output actors and bakes them to blueprints. Assets // that were baked are removed from PDG output actors. - static bool BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink); + static bool BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); // End: PDG Baking @@ -578,6 +713,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils // name. static bool GetTemporaryOutputObjectBakeName( const UObject* InObject, + EHoudiniOutputType InOutputType, const TArray& InAllOutputs, FString& OutBakeName); @@ -591,6 +727,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils bool bInReplacePreviousBake, EHoudiniEngineBakeOption BakeOption, bool bInRemoveHACOutputOnSuccess, + bool bInRecenterBakedActors, bool& bOutNeedsReCook); // Position InActor at its bounding box center (keep components' world location) @@ -604,7 +741,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils AActor* InActor, bool bCreateIfMissing=true, EComponentMobility::Type InMobilityIfCreated=EComponentMobility::Static); // Helper function to return a unique object name if the given is already in use - static FName MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, FName InName, UObject* InObjectThatWouldBeRenamed=nullptr); + static FString MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, const FString& InName, UObject* InObjectThatWouldBeRenamed=nullptr); // Helper for getting the actor folder path for the world outliner, based unreal_bake_outliner_folder static FName GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); @@ -618,4 +755,6 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils bool bInDestroyBakedComponent, bool bInDestroyBakedInstancedActors, bool bInDestroyBakedInstancedComponents); + + static UMaterialInterface * BakeSingleMaterialToPackage(UMaterialInterface * InOriginalMaterial, const FHoudiniPackageParams & PackageParams, TArray& OutPackagesToSave, TMap& InOutAlreadyBakedMaterialsMap, FHoudiniEngineOutputStats& OutBakeStats); }; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp index 4414ba03e..7c0437da7 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,6 +38,7 @@ #include "HoudiniOutputTranslator.h" #include "HoudiniStaticMesh.h" #include "HoudiniOutput.h" +#include "HoudiniEngineStyle.h" #include "DesktopPlatformModule.h" #include "Interfaces/IMainFrameModule.h" @@ -58,6 +59,12 @@ #define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE FDelegateHandle FHoudiniEngineCommands::OnPostSaveWorldRefineProxyMeshesHandle = FDelegateHandle(); +FHoudiniEngineCommands::FOnHoudiniProxyMeshesRefinedDelegate FHoudiniEngineCommands::OnHoudiniProxyMeshesRefinedDelegate = FHoudiniEngineCommands::FOnHoudiniProxyMeshesRefinedDelegate(); + +FHoudiniEngineCommands::FHoudiniEngineCommands() + : TCommands (TEXT("HoudiniEngine"), NSLOCTEXT("Contexts", "HoudiniEngine", "Houdini Engine Plugin"), NAME_None, FHoudiniEngineStyle::GetStyleSetName()) +{ +} void FHoudiniEngineCommands::RegisterCommands() @@ -109,6 +116,12 @@ FHoudiniEngineCommands::RegisterCommands() void FHoudiniEngineCommands::SaveHIPFile() { + if (!FHoudiniEngine::IsInitialized() || FHoudiniEngine::Get().GetSession() == nullptr) + { + HOUDINI_LOG_ERROR(TEXT("Cannot save the Houdini scene, the Houdini Engine session hasn't been started.")); + return; + } + IDesktopPlatform * DesktopPlatform = FDesktopPlatformModule::Get(); if (!DesktopPlatform || !FHoudiniEngineUtils::IsInitialized()) return; @@ -151,8 +164,11 @@ FHoudiniEngineCommands::SaveHIPFile() void FHoudiniEngineCommands::OpenInHoudini() { - if (!FHoudiniEngine::IsInitialized()) + if(!FHoudiniEngine::IsInitialized() || FHoudiniEngine::Get().GetSession() == nullptr) + { + HOUDINI_LOG_ERROR(TEXT("Cannot open the scene in Houdini, the Houdini Engine session hasn't been started.")); return; + } // First, saves the current scene as a hip file // Creates a proper temporary file name @@ -173,15 +189,15 @@ FHoudiniEngineCommands::OpenInHoudini() FString Notification = TEXT("Opening scene in Houdini..."); FHoudiniEngineUtils::CreateSlateNotification(Notification); - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Opened scene in Houdini.")); - // Add quotes to the path to avoid issues with spaces UserTempPath = TEXT("\"") + UserTempPath + TEXT("\""); + // Then open the hip file in Houdini FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); - FPlatformProcess::CreateProc( + FString HoudiniExecutable = FHoudiniEngine::Get().GetHoudiniExecutable(); + FString HoudiniLocation = LibHAPILocation + TEXT("//") + HoudiniExecutable; + + FProcHandle ProcHandle = FPlatformProcess::CreateProc( *HoudiniLocation, *UserTempPath, true, false, false, @@ -189,8 +205,27 @@ FHoudiniEngineCommands::OpenInHoudini() FPlatformProcess::UserTempDir(), nullptr, nullptr); - // Unfortunately, LaunchFileInDefaultExternalApplication doesn't seem to be working properly - //FPlatformProcess::LaunchFileInDefaultExternalApplication( UserTempPath.GetCharArray().GetData(), nullptr, ELaunchVerb::Open ); + if (!ProcHandle.IsValid()) + { + // Try with the steam version executable instead + HoudiniLocation = LibHAPILocation + TEXT("//hindie.steam"); + + ProcHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *UserTempPath, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + if (!ProcHandle.IsValid()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to open scene in Houdini.")); + } + } + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Opened scene in Houdini.")); } void @@ -282,12 +317,13 @@ FHoudiniEngineCommands::CleanUpTempFolder() for (FAssetData Data : AssetDataList) { UPackage* CurrentPackage = Data.GetPackage(); - if (!CurrentPackage || CurrentPackage->IsPendingKill()) + if (!IsValid(CurrentPackage)) continue; // Do not try to delete the package if it's referenced anywhere TArray ReferenceNames; - AssetRegistryModule.Get().GetReferencers(CurrentPackage->GetFName(), ReferenceNames, EAssetRegistryDependencyType::All); + //AssetRegistryModule.Get().GetReferencers(CurrentPackage->GetFName(), ReferenceNames, EAssetRegistryDependencyType::All); + AssetRegistryModule.Get().GetReferencers(CurrentPackage->GetFName(), ReferenceNames, UE::AssetRegistry::EDependencyCategory::All); if (ReferenceNames.Num() > 0) continue; @@ -298,7 +334,7 @@ FHoudiniEngineCommands::CleanUpTempFolder() { // Check if the objects contained in the package are referenced by something that won't be garbage collected (*including* the undo buffer) UObject* AssetInPackage = AssetInfo.GetAsset(); - if (!AssetInPackage || AssetInPackage->IsPendingKill()) + if (!IsValid(AssetInPackage)) continue; FReferencerInformationList ReferencesIncludingUndo; @@ -311,7 +347,7 @@ FHoudiniEngineCommands::CleanUpTempFolder() for (auto ExtRef : ReferencesIncludingUndo.ExternalReferences) { UObject* Outer = ExtRef.Referencer->GetOuter(); - if (!Outer || Outer->IsPendingKill()) + if (!IsValid(Outer)) continue; bool bOuterFound = false; @@ -444,7 +480,7 @@ FHoudiniEngineCommands::BakeAllAssets() for (TObjectIterator Itr; Itr; ++Itr) { UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) { HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - Invalid Houdini Asset Component")); continue; @@ -475,7 +511,8 @@ FHoudiniEngineCommands::BakeAllAssets() FHoudiniEngineOutputStats BakeStats; TArray PackagesToSave; TArray Blueprints; - bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, true, BakeStats, Blueprints, PackagesToSave); + const bool bInReplaceAssets = true; + bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors, BakeStats, Blueprints, PackagesToSave); FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); if (bSuccess) @@ -509,7 +546,9 @@ FHoudiniEngineCommands::BakeAllAssets() // TODO: this used to have a way to not select in v1 // if (FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(HoudiniAssetComponent)) // bSuccess = true; - if (FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(HoudiniAssetComponent, true, true)) + const bool bReplaceActors = true; + const bool bReplaceAssets = true; + if (FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(HoudiniAssetComponent, bReplaceActors, bReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors)) { bSuccess = true; FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); @@ -559,7 +598,7 @@ FHoudiniEngineCommands::PauseAssetCooking() for (TObjectIterator Itr; Itr; ++Itr) { UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsValidLowLevel()) + if (!IsValid(HoudiniAssetComponent) || !HoudiniAssetComponent->IsValidLowLevel()) { HOUDINI_LOG_ERROR(TEXT("Failed to cook a Houdini Asset in the scene!")); continue; @@ -597,11 +636,11 @@ FHoudiniEngineCommands::RecookSelection() for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) { AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) continue; UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; HoudiniAssetComponent->MarkAsNeedCook(); @@ -628,7 +667,7 @@ FHoudiniEngineCommands::RecookAllAssets() for (TObjectIterator Itr; Itr; ++Itr) { UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; HoudiniAssetComponent->MarkAsNeedCook(); @@ -655,7 +694,7 @@ FHoudiniEngineCommands::RebuildAllAssets() for (TObjectIterator Itr; Itr; ++Itr) { UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; HoudiniAssetComponent->MarkAsNeedRebuild(); @@ -691,11 +730,11 @@ FHoudiniEngineCommands::RebuildSelection() for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) { AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) continue; UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())// || !HoudiniAssetComponent->IsComponentValid()) + if (!IsValid(HoudiniAssetComponent))// || !HoudiniAssetComponent->IsComponentValid()) continue; HoudiniAssetComponent->MarkAsNeedRebuild(); @@ -731,11 +770,11 @@ FHoudiniEngineCommands::BakeSelection() for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) { AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) continue; UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) { HOUDINI_LOG_ERROR(TEXT("Failed to export a Houdini Asset in the scene!")); continue; @@ -758,7 +797,8 @@ FHoudiniEngineCommands::BakeSelection() FHoudiniEngineOutputStats BakeStats; TArray PackagesToSave; TArray Blueprints; - const bool bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, true, BakeStats, Blueprints, PackagesToSave); + const bool bReplaceAssets = true; + const bool bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, bReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors, BakeStats, Blueprints, PackagesToSave); FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); if (bSuccess) @@ -819,7 +859,7 @@ void FHoudiniEngineCommands::RecentreSelection() for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) { AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) continue; UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); @@ -854,7 +894,7 @@ void FHoudiniEngineCommands::RecentreSelection() const TArray< UHoudiniInput* >& AssetInputs = HoudiniAssetComponent->Inputs; for (const UHoudiniInput* pInput : AssetInputs) { - if (!pInput || pInput->IsPendingKill()) + if (!IsValid(pInput)) continue; // to world space @@ -954,8 +994,21 @@ FHoudiniEngineCommands::OpenSessionSync() if (!FPlatformProcess::IsProcRunning(PreviousHESS)) { // Start houdini with the -hess commandline args - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); + const FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); +# if PLATFORM_MAC + const FString HoudiniExeLocationRelativeToLibHAPI = TEXT("/../Resources/bin"); +# elif PLATFORM_LINUX + const FString HoudiniExeLocationRelativeToLibHAPI = TEXT("/../bin"); +# elif PLATFORM_WINDOWS + const FString HoudiniExeLocationRelativeToLibHAPI; +# else + // Treat an unknown platform the same as Windows for now + const FString HoudiniExeLocationRelativeToLibHAPI; +# endif + + FString HoudiniExecutable = FHoudiniEngine::Get().GetHoudiniExecutable(); + FString HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/") + HoudiniExecutable; + HOUDINI_LOG_MESSAGE(TEXT("Path to houdini executable: %s"), *HoudiniLocation); FProcHandle HESSHandle = FPlatformProcess::CreateProc( *HoudiniLocation, *SessionSyncArgs, @@ -964,6 +1017,27 @@ FHoudiniEngineCommands::OpenSessionSync() FPlatformProcess::UserTempDir(), nullptr, nullptr); + if (!HESSHandle.IsValid()) + { + // Try with the steam version executable instead + HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/hindie.steam"); + HOUDINI_LOG_MESSAGE(TEXT("Path to hindie.steam executable: %s"), *HoudiniLocation); + + HESSHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *SessionSyncArgs, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + if (!HESSHandle.IsValid()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to launch Houdini in Session Sync mode.")); + return; + } + } + // Keep track of the SessionSync ProcHandle FHoudiniEngine::Get().SetHESSProcHandle(HESSHandle); } @@ -1136,7 +1210,7 @@ FHoudiniEngineCommands::MarkAllHACsAsNeedInstantiation() for (TObjectIterator Itr; Itr; ++Itr) { UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; HoudiniAssetComponent->MarkAsNeedInstantiation(); @@ -1172,7 +1246,7 @@ FHoudiniEngineCommands::StopSession() } } -void +EHoudiniProxyRefineRequestResult FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE) { // Get current world selection @@ -1184,7 +1258,7 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelecte if (NumSelectedHoudiniAssets <= 0) { HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; + return EHoudiniProxyRefineRequestResult::Invalid; } } @@ -1202,11 +1276,11 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelecte for (int32 Index = 0; Index < NumSelectedHoudiniAssets; ++Index) { AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Index]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) continue; UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and @@ -1219,7 +1293,7 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelecte for (TObjectIterator Itr; Itr; ++Itr) { UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != HoudiniAssetComponent->GetWorld()) @@ -1231,7 +1305,7 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelecte } } - RefineTriagedHoudiniProxyMesehesToStaticMeshes( + return RefineTriagedHoudiniProxyMesehesToStaticMeshes( ComponentsToRefine, ComponentsToCook, SkippedComponents, @@ -1243,7 +1317,7 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelecte ); } -void +EHoudiniProxyRefineRequestResult FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent) { const bool bRefineAll = true; @@ -1258,11 +1332,11 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TAr TArray SkippedComponents; for (const AHoudiniAssetActor* HoudiniAssetActor : InActorsToRefine) { - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) continue; UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and @@ -1270,7 +1344,7 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TAr TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); } - RefineTriagedHoudiniProxyMesehesToStaticMeshes( + return RefineTriagedHoudiniProxyMesehesToStaticMeshes( ComponentsToRefine, ComponentsToCook, SkippedComponents, @@ -1328,16 +1402,16 @@ FHoudiniEngineCommands::SetPDGCommandletEnabled(bool InEnabled) void FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped) { - if (!InHAC || InHAC->IsPendingKill()) + if (!IsValid(InHAC)) return; // Make sure that the component's World and Owner are valid AActor *Owner = InHAC->GetOwner(); - if (!Owner || Owner->IsPendingKill()) + if (!IsValid(Owner)) return; UWorld *World = InHAC->GetWorld(); - if (!World || World->IsPendingKill()) + if (!IsValid(World)) return; if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != World) @@ -1390,7 +1464,7 @@ FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoud for (uint32 Index = 0; Index < NumOutputs; ++Index) { UHoudiniOutput *Output = InHAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) continue; TMap& OutputObjects = Output->GetOutputObjects(); @@ -1401,7 +1475,7 @@ FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoud { // The proxy is not current, delete it and its component USceneComponent* FoundProxyComponent = Cast(CurrentOutputObject.ProxyComponent); - if (FoundProxyComponent && !FoundProxyComponent->IsPendingKill()) + if (IsValid(FoundProxyComponent)) { // Remove from the HoudiniAssetActor if (FoundProxyComponent->GetOwner()) @@ -1413,12 +1487,12 @@ FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoud } UObject* ProxyObject = CurrentOutputObject.ProxyObject; - if (!ProxyObject || ProxyObject->IsPendingKill()) + if (!IsValid(ProxyObject)) continue; ProxyObject->MarkPendingKill(); ProxyObject->MarkPackageDirty(); - UPackage* const Package = ProxyObject->GetOutermost();//GetPackage(); + UPackage* const Package = ProxyObject->GetPackage(); if (IsValid(Package)) ProxyMeshPackagesToSave.Add(Package); } @@ -1432,7 +1506,7 @@ FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoud for (uint32 Index = 0; Index < NumOutputs; ++Index) { UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) continue; TMap& OutputObjects = Output->GetOutputObjects(); @@ -1441,7 +1515,7 @@ FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoud FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; if (CurrentOutputObject.bProxyIsCurrent && CurrentOutputObject.ProxyObject) { - UPackage* const Package = CurrentOutputObject.ProxyObject->GetOutermost();//GetPackage(); + UPackage* const Package = CurrentOutputObject.ProxyObject->GetPackage(); if (IsValid(Package) && Package->IsDirty()) ProxyMeshPackagesToSave.Add(Package); } @@ -1457,7 +1531,7 @@ FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoud } } -void +EHoudiniProxyRefineRequestResult FHoudiniEngineCommands::RefineTriagedHoudiniProxyMesehesToStaticMeshes( const TArray& InComponentsToRefine, const TArray& InComponentsToCook, @@ -1474,8 +1548,28 @@ FHoudiniEngineCommands::RefineTriagedHoudiniProxyMesehesToStaticMeshes( const uint32 NumComponentsToCook = InComponentsToCook.Num(); const uint32 NumComponentsToRefine = InComponentsToRefine.Num(); const uint32 NumComponentsToProcess = NumComponentsToCook + NumComponentsToRefine; + TArray SuccessfulComponents; - uint32 NumSkippedComponents = InSkippedComponents.Num(); + TArray FailedComponents; + TArray SkippedComponents(InSkippedComponents); + + auto AllowPlayInEditorRefinementFn = [&bInOnPrePIEBeginPlay, &InComponentsToCook, &InComponentsToRefine] (bool bEnabled, bool bRefinementDone){ + if (bInOnPrePIEBeginPlay) + { + // Flag the components that need cooking / refinement as cookable in PIE mode. No other cooking will be allowed. + // Once refinement is done, we'll unset these flags again. + SetAllowPlayInEditorRefinement(InComponentsToCook, true); + SetAllowPlayInEditorRefinement(InComponentsToRefine, true); + if (bRefinementDone) + { + // Don't tick during PIE. We'll resume ticking when PIE is stopped. + FHoudiniEngine::Get().StopTicking(); + } + } + }; + + AllowPlayInEditorRefinementFn(true, false); + if (NumComponentsToProcess > 0) { // The task progress pointer is potentially going to be shared with a background thread and tasks @@ -1499,28 +1593,52 @@ FHoudiniEngineCommands::RefineTriagedHoudiniProxyMesehesToStaticMeshes( bCancelled = TaskProgress->ShouldCancel(); if (bCancelled) { - NumSkippedComponents += NumComponentsToRefine - ComponentIndex - 1; + for (uint32 SkippedIndex = ComponentIndex + 1; SkippedIndex < NumComponentsToRefine; ++SkippedIndex) + { + SkippedComponents.Add(InComponentsToRefine[ComponentIndex]); + } break; } } + if (bCancelled && NumComponentsToCook > 0) + { + for (UHoudiniAssetComponent* const HAC : InComponentsToCook) + { + SkippedComponents.Add(HAC); + } + } + if (NumComponentsToCook > 0 && !bCancelled) { // Now use an async task to check on the progress of the cooking components - Async(EAsyncExecution::Thread, [InComponentsToCook, TaskProgress, NumComponentsToProcess, NumSkippedComponents, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents]() { - RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(InComponentsToCook, TaskProgress, NumComponentsToProcess, NumSkippedComponents, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); + Async(EAsyncExecution::Thread, [InComponentsToCook, TaskProgress, NumComponentsToProcess, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents]() { + RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread( + InComponentsToCook, TaskProgress, NumComponentsToProcess, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents); }); + + // We have to wait for cook(s) before completing refinement + return EHoudiniProxyRefineRequestResult::PendingCooks; } else { - RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(NumComponentsToProcess, NumSkippedComponents, 0, TaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); + RefineHoudiniProxyMeshesToStaticMeshesNotifyDone( + NumComponentsToProcess, TaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents); + + // We didn't have to cook anything, so refinement is complete. + AllowPlayInEditorRefinementFn(false, true); + return EHoudiniProxyRefineRequestResult::Refined; } } + + // Nothing to refine + AllowPlayInEditorRefinementFn(false, true); + return EHoudiniProxyRefineRequestResult::None; } void -FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray& InComponentsToCook, TSharedPtr InTaskProgress, uint32 InNumComponentsToProcess, uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents) +FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray& InComponentsToCook, TSharedPtr InTaskProgress, const uint32 InNumComponentsToProcess, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents) { // Copy to a double linked list so that we can loop through // to check progress of each component and remove it easily @@ -1531,8 +1649,10 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgrou CookList.AddTail(HAC); } - // Add the successfully cooked compoments to the incoming successful components (previously refined) + // Add the successfully cooked components to the incoming successful components (previously refined) TArray SuccessfulComponents(InSuccessfulComponents); + TArray FailedComponents(InFailedComponents); + TArray SkippedComponents(InSkippedComponents); bool bCancelled = false; uint32 NumFailedToCook = 0; @@ -1544,7 +1664,7 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgrou TDoubleLinkedList::TDoubleLinkedListNode *Next = Node->GetNextNode(); UHoudiniAssetComponent* HAC = Node->GetValue(); - if (HAC && !HAC->IsPendingKill()) + if (IsValid(HAC)) { const EHoudiniAssetState State = HAC->GetAssetState(); const EHoudiniAssetStateResult ResultState = HAC->GetAssetStateResult(); @@ -1553,7 +1673,7 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgrou { // Cooked, count as success, remove node CookList.RemoveNode(Node); - SuccessfulComponents.Add(Node->GetValue()); + SuccessfulComponents.Add(HAC); bUpdateProgress = true; } else if (ResultState != EHoudiniAssetStateResult::None && ResultState != EHoudiniAssetStateResult::Working) @@ -1561,6 +1681,7 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgrou // Failed, remove node HOUDINI_LOG_ERROR(TEXT("Failed to cook %s to obtain static mesh."), *(HAC->GetPathName())); CookList.RemoveNode(Node); + FailedComponents.Add(HAC); bUpdateProgress = true; NumFailedToCook++; } @@ -1574,6 +1695,11 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgrou }).Get(); } } + else + { + SkippedComponents.Add(HAC); + CookList.RemoveNode(Node); + } Node = Next; } @@ -1583,28 +1709,40 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgrou if (bCancelled) { HOUDINI_LOG_WARNING(TEXT("Mesh refinement cancelled while waiting for %d components to cook."), CookList.Num()); + // Mark any remaining HACs in the cook list as skipped + TDoubleLinkedList::TDoubleLinkedListNode* Node = CookList.GetHead(); + while (Node) + { + TDoubleLinkedList::TDoubleLinkedListNode* const Next = Node->GetNextNode(); + UHoudiniAssetComponent* HAC = Node->GetValue(); + if (HAC) + SkippedComponents.Add(HAC); + CookList.RemoveNode(Node); + Node = Next; + } } // Cooking is done, or failed, display the notifications on the main thread - const uint32 NumRemaining = CookList.Num(); - Async(EAsyncExecution::TaskGraphMainThread, [InNumComponentsToProcess, InNumSkippedComponents, NumFailedToCook, NumRemaining, InTaskProgress, bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents]() { - RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(InNumComponentsToProcess, InNumSkippedComponents + NumRemaining, NumFailedToCook, InTaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); + Async(EAsyncExecution::TaskGraphMainThread, [InNumComponentsToProcess, InTaskProgress, bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents]() { + RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(InNumComponentsToProcess, InTaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents); }); } void -FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(uint32 InNumTotalComponents, uint32 InNumSkippedComponents, uint32 InNumFailedToCook, FSlowTask *InTaskProgress, bool bCancelled, bool bOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents) +FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(const uint32 InNumTotalComponents, FSlowTask* const InTaskProgress, const bool bCancelled, const bool bOnPreSaveWorld, UWorld* const InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents) { FString Notification; - if (InNumSkippedComponents + InNumFailedToCook > 0) + const uint32 NumSkippedComponents = InSkippedComponents.Num(); + const uint32 NumFailedToCook = InFailedComponents.Num(); + if (NumSkippedComponents + NumFailedToCook > 0) { if (bCancelled) { - Notification = FString::Printf(TEXT("Refinement cancelled after completing %d / %d components. The remaining components were skipped, in an invalid state, or could not be cooked. See the log for details."), InNumSkippedComponents + InNumFailedToCook, InNumTotalComponents); + Notification = FString::Printf(TEXT("Refinement cancelled after completing %d / %d components. The remaining components were skipped, in an invalid state, or could not be cooked. See the log for details."), NumSkippedComponents + NumFailedToCook, InNumTotalComponents); } else { - Notification = FString::Printf(TEXT("Failed to refine %d / %d components, the components were in an invalid state, and were either not cooked or could not be cooked. See the log for details."), InNumSkippedComponents + InNumFailedToCook, InNumTotalComponents); + Notification = FString::Printf(TEXT("Failed to refine %d / %d components, the components were in an invalid state, and were either not cooked or could not be cooked. See the log for details."), NumSkippedComponents + NumFailedToCook, InNumTotalComponents); } FHoudiniEngineUtils::CreateSlateNotification(Notification); HOUDINI_LOG_ERROR(TEXT("%s"), *Notification); @@ -1644,6 +1782,27 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(uint32 } }); } + + SetAllowPlayInEditorRefinement(InSuccessfulComponents, false); + SetAllowPlayInEditorRefinement(InFailedComponents, false); + SetAllowPlayInEditorRefinement(InSkippedComponents, false); + + // Broadcast refinement result per HAC + for (UHoudiniAssetComponent* const HAC : InSuccessfulComponents) + { + if (OnHoudiniProxyMeshesRefinedDelegate.IsBound()) + OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Success); + } + for (UHoudiniAssetComponent* const HAC : InFailedComponents) + { + if (OnHoudiniProxyMeshesRefinedDelegate.IsBound()) + OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Failed); + } + for (UHoudiniAssetComponent* const HAC : InSkippedComponents) + { + if (OnHoudiniProxyMeshesRefinedDelegate.IsBound()) + OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Skipped); + } } void @@ -1653,14 +1812,14 @@ FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArrayIsPendingKill()) + if (!IsValid(HAC)) continue; const int32 NumOutputs = HAC->GetNumOutputs(); for (int32 Index = 0; Index < NumOutputs; ++Index) { UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) continue; if (Output->GetType() != EHoudiniOutputType::Mesh) @@ -1669,7 +1828,7 @@ FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArrayGetOutputObjects()) { UObject *Obj = OutputObjectPair.Value.OutputObject; - if (!Obj || Obj->IsPendingKill()) + if (!IsValid(Obj)) continue; UStaticMesh *SM = Cast(Obj); @@ -1677,7 +1836,7 @@ FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArrayGetOutermost(); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) continue; if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) @@ -1691,5 +1850,18 @@ FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArray& InComponents, + bool bEnabled) +{ +#if WITH_EDITORONLY_DATA + for (UHoudiniAssetComponent* Component : InComponents) + { + Component->SetAllowPlayInEditorRefinement(false); + } +#endif +} + #undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h index 1c33eb13d..079fe1698 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,10 +26,10 @@ #pragma once -#include "HoudiniEngineStyle.h" - #include "Framework/Commands/Commands.h" +#include "Misc/SlowTask.h" #include "Delegates/IDelegateInstance.h" +#include "HoudiniEngineRuntimeCommon.h" class UHoudiniAssetComponent; class AHoudiniAssetActor; @@ -39,16 +39,10 @@ struct FSlowTask; class FHoudiniEngineCommands : public TCommands { public: - FHoudiniEngineCommands() - : TCommands - ( - TEXT("HoudiniEngine"), // Context name for fast lookup - NSLOCTEXT("Contexts", "HoudiniEngine", "Houdini Engine Plugin"), // Localized context name for displaying - NAME_None, // Parent context name. - FHoudiniEngineStyle::GetStyleSetName() // Icon Style Set - ) - { - } + // Multi-cast delegate type for broadcasting when proxy mesh refinement of a HAC is complete. + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnHoudiniProxyMeshesRefinedDelegate, UHoudiniAssetComponent* const, const EHoudiniProxyRefineResult); + + FHoudiniEngineCommands(); // TCommand<> interface virtual void RegisterCommands() override; @@ -134,10 +128,10 @@ class FHoudiniEngineCommands : public TCommands // against the settings of the component to determine if refinement should take place. // If bOnPreSaveWorld is true, then OnPreSaveWorld should be the World that is being saved. In // that case, only proxy meshes attached to components from that world will be refined. - static void RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent=false, bool bRefineAll=true, bool bOnPreSaveWorld=false, UWorld *PreSaveWorld=nullptr, bool bOnPrePIEBeginPlay=false); + static EHoudiniProxyRefineRequestResult RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent=false, bool bRefineAll=true, bool bOnPreSaveWorld=false, UWorld *PreSaveWorld=nullptr, bool bOnPrePIEBeginPlay=false); // Refine all proxy meshes on UHoudiniAssetCompoments of InActorsToRefine. - static void RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent=false); + static EHoudiniProxyRefineRequestResult RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent=false); static void StartPDGCommandlet(); @@ -153,6 +147,8 @@ class FHoudiniEngineCommands : public TCommands static FDelegateHandle& GetOnPostSaveWorldRefineProxyMeshesHandle() { return OnPostSaveWorldRefineProxyMeshesHandle; } + static FOnHoudiniProxyMeshesRefinedDelegate& GetOnHoudiniProxyMeshesRefinedDelegate() { return OnHoudiniProxyMeshesRefinedDelegate; } + public: // UI Action to create a Houdini Engine Session @@ -230,7 +226,7 @@ class FHoudiniEngineCommands : public TCommands // Triage a HoudiniAssetComponent with UHoudiniStaticMesh as needing cooking or if a UStaticMesh can be immediately built static void TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped); - static void RefineTriagedHoudiniProxyMesehesToStaticMeshes( + static EHoudiniProxyRefineRequestResult RefineTriagedHoudiniProxyMesehesToStaticMeshes( const TArray& InComponentsToRefine, const TArray& InComponentsToCook, const TArray& InSkippedComponents, @@ -242,15 +238,17 @@ class FHoudiniEngineCommands : public TCommands // Called in a background thread by RefineHoudiniProxyMeshesToStaticMeshes when some components need to be cooked to generate UStaticMeshes. Checks and waits for // cooking of each component to complete, and then calls RefineHoudiniProxyMeshesToStaticMeshesNotifyDone on the main thread. - static void RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray &InComponentsToCook, TSharedPtr InTaskProgress, const uint32 InNumComponentsToProcess, const uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents); + static void RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray &InComponentsToCook, TSharedPtr InTaskProgress, const uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents); // Display a notification / end/close progress dialog, when refining mesh proxies to static meshes is complete - static void RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(uint32 InNumTotalComponents, uint32 InNumSkippedComponents, uint32 InNumFailedToCook, FSlowTask *InTaskProgress, bool bCancelled, bool bOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents); + static void RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(const uint32 InNumTotalComponents, FSlowTask* const InTaskProgress, const bool bCancelled, const bool bOnPreSaveWorld, UWorld* const InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents); // Handle OnPostSaveWorld for refining proxy meshes: this saves all the dirty UPackages of the UStaticMeshes that were created during RefineHoudiniProxyMeshesToStaticMeshes // if it was called as a result of a PreSaveWorld. static void RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess); + static void SetAllowPlayInEditorRefinement(const TArray& InComponents, bool bEnabled); + // Helper function used to indicate to all HAC that they need to be instantiated in the new HE session // Needs to be call after starting/restarting/connecting/session syncing a HE session.. static void MarkAllHACsAsNeedInstantiation(); @@ -258,5 +256,7 @@ class FHoudiniEngineCommands : public TCommands // Delegate that is set up to refined proxy meshes post save world (it removes itself afterwards) static FDelegateHandle OnPostSaveWorldRefineProxyMeshesHandle; + // Delegate for broadcasting when proxy mesh refinement of a HAC's output is complete. + static FOnHoudiniProxyMeshesRefinedDelegate OnHoudiniProxyMeshesRefinedDelegate; }; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp index cf76fa2bc..33132da3d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,6 +29,7 @@ #include "HoudiniEngineEditorPrivatePCH.h" #include "HoudiniAssetComponent.h" +#include "HoudiniAssetComponentDetails.h" #include "HoudiniAssetActor.h" #include "HoudiniAsset.h" #include "HoudiniParameter.h" @@ -39,6 +40,7 @@ #include "HoudiniEngineEditor.h" #include "HoudiniEngineEditorUtils.h" +#include "CoreMinimal.h" #include "DetailCategoryBuilder.h" #include "IDetailGroup.h" #include "DetailWidgetRow.h" @@ -112,22 +114,25 @@ FHoudiniEngineDetails::CreateWidget( UHoudiniAssetComponent* MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; // 0. Houdini Engine Icon FHoudiniEngineDetails::CreateHoudiniEngineIconWidget(HoudiniEngineCategoryBuilder, InHACs); + + // 1. Houdini Engine Session Status + FHoudiniAssetComponentDetails::AddSessionStatusRow(HoudiniEngineCategoryBuilder); - // 1. Create Generate Category + // 2. Create Generate Category FHoudiniEngineDetails::CreateGenerateWidgets(HoudiniEngineCategoryBuilder, InHACs); - // 2. Create Bake Category + // 3. Create Bake Category FHoudiniEngineDetails::CreateBakeWidgets(HoudiniEngineCategoryBuilder, InHACs); - // 3. Create Asset Options Category + // 4. Create Asset Options Category FHoudiniEngineDetails::CreateAssetOptionsWidgets(HoudiniEngineCategoryBuilder, InHACs); - // 4. Create Help and Debug Category + // 5. Create Help and Debug Category FHoudiniEngineDetails::CreateHelpAndDebugWidgets(HoudiniEngineCategoryBuilder, InHACs); } @@ -142,7 +147,7 @@ FHoudiniEngineDetails::CreateHoudiniEngineIconWidget( UHoudiniAssetComponent* MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; // Skip drawing the icon if the icon image is not loaded correctly. @@ -188,14 +193,14 @@ FHoudiniEngineDetails::CreateGenerateWidgets( UHoudiniAssetComponent* MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; auto OnReBuildClickedLambda = [InHACs]() { for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->MarkAsNeedRebuild(); @@ -208,7 +213,7 @@ FHoudiniEngineDetails::CreateGenerateWidgets( { for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->MarkAsNeedCook(); @@ -221,7 +226,7 @@ FHoudiniEngineDetails::CreateGenerateWidgets( { for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; // Reset parameters to default values? @@ -241,7 +246,7 @@ FHoudiniEngineDetails::CreateGenerateWidgets( { for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; // Reset parameters to default values? @@ -261,7 +266,7 @@ FHoudiniEngineDetails::CreateGenerateWidgets( auto OnCookFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; FString NewPathStr = Val.ToString(); @@ -295,7 +300,7 @@ FHoudiniEngineDetails::CreateGenerateWidgets( for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; if (NextHAC->TemporaryCookFolder.Path.Equals(NewPathStr)) @@ -318,133 +323,133 @@ FHoudiniEngineDetails::CreateGenerateWidgets( FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); TSharedRef ButtonHorizontalBox = SNew(SHorizontalBox); - // Rebuild button - TSharedPtr RebuildButton; - TSharedPtr RebuildButtonHorizontalBox; + // Recook button + TSharedPtr RecookButton; + TSharedPtr RecookButtonHorizontalBox; ButtonHorizontalBox->AddSlot() .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(15.0f, 0.0f, 0.0f, 2.0f) //.Padding(2.0f, 0.0f, 0.0f, 2.0f) [ SNew(SBox) .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) [ - SAssignNew(RebuildButton, SButton) + SAssignNew(RecookButton, SButton) .VAlign(VAlign_Center) .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsRebuildAssetButton", "Rebuild the selected Houdini Asset: its source .HDA file is reimported and updated, the asset's nodes in Houdini are destroyed and recreated, and the asset is then forced to recook.")) - //.Text(FText::FromString("Rebuild")) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsRecookAssetButton", "Recook the selected Houdini Asset: all parameters and inputs are re-upload to Houdini and the asset is then forced to recook.")) + //.Text(FText::FromString("Recook")) .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnRecookClickedLambda) .Content() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .HAlign(HAlign_Center) [ - SAssignNew(RebuildButtonHorizontalBox, SHorizontalBox) + SAssignNew(RecookButtonHorizontalBox, SHorizontalBox) ] ] - .OnClicked_Lambda(OnReBuildClickedLambda) ] ]; - if (HoudiniEngineUIRebuildIconBrush.IsValid()) + if (HoudiniEngineUIRecookIconBrush.IsValid()) { - TSharedPtr RebuildImage; - RebuildButtonHorizontalBox->AddSlot() - //.Padding(25.0f, 0.0f, 3.0f, 0.0f) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + TSharedPtr RecookImage; + RecookButtonHorizontalBox->AddSlot() .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) [ SNew(SBox) .WidthOverride(16.0f) .HeightOverride(16.0f) [ - SAssignNew(RebuildImage, SImage) + SAssignNew(RecookImage, SImage) //.ColorAndOpacity(FSlateColor::UseForeground()) ] ]; - RebuildImage->SetImage( + RecookImage->SetImage( TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIRebuildIconBrush]() { - return HoudiniEngineUIRebuildIconBrush.Get(); + TAttribute::FGetter::CreateLambda([HoudiniEngineUIRecookIconBrush]() { + return HoudiniEngineUIRecookIconBrush.Get(); }))); } - RebuildButtonHorizontalBox->AddSlot() + RecookButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + //.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) .VAlign(VAlign_Center) .AutoWidth() - .Padding(5.0, 0.0, 0.0, 0.0) [ SNew(STextBlock) - .Text(FText::FromString("Rebuild")) + .Text(FText::FromString("Recook")) ]; - - ButtonRow.WholeRowWidget.Widget = ButtonHorizontalBox; - ButtonRow.IsEnabled(false); - - // Recook button - TSharedPtr RecookButton; - TSharedPtr RecookButtonHorizontalBox; + + // Rebuild button + TSharedPtr RebuildButton; + TSharedPtr RebuildButtonHorizontalBox; ButtonHorizontalBox->AddSlot() .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(15.0f, 0.0f, 0.0f, 2.0f) //.Padding(2.0f, 0.0f, 0.0f, 2.0f) [ SNew(SBox) .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) [ - SAssignNew(RecookButton, SButton) + SAssignNew(RebuildButton, SButton) .VAlign(VAlign_Center) .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsRecookAssetButton", "Recook the selected Houdini Asset: all parameters and inputs are re-upload to Houdini and the asset is then forced to recook.")) - //.Text(FText::FromString("Recook")) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsRebuildAssetButton", "Rebuild the selected Houdini Asset: its source .HDA file is reimported and updated, the asset's nodes in Houdini are destroyed and recreated, and the asset is then forced to recook.")) + //.Text(FText::FromString("Rebuild")) .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnRecookClickedLambda) .Content() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .HAlign(HAlign_Center) [ - SAssignNew(RecookButtonHorizontalBox, SHorizontalBox) + SAssignNew(RebuildButtonHorizontalBox, SHorizontalBox) ] ] + .OnClicked_Lambda(OnReBuildClickedLambda) ] ]; - if (HoudiniEngineUIRecookIconBrush.IsValid()) + if (HoudiniEngineUIRebuildIconBrush.IsValid()) { - TSharedPtr RecookImage; - RecookButtonHorizontalBox->AddSlot() + TSharedPtr RebuildImage; + RebuildButtonHorizontalBox->AddSlot() + //.Padding(25.0f, 0.0f, 3.0f, 0.0f) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) [ SNew(SBox) .WidthOverride(16.0f) .HeightOverride(16.0f) [ - SAssignNew(RecookImage, SImage) + SAssignNew(RebuildImage, SImage) //.ColorAndOpacity(FSlateColor::UseForeground()) ] ]; - RecookImage->SetImage( + RebuildImage->SetImage( TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIRecookIconBrush]() { - return HoudiniEngineUIRecookIconBrush.Get(); + TAttribute::FGetter::CreateLambda([HoudiniEngineUIRebuildIconBrush]() { + return HoudiniEngineUIRebuildIconBrush.Get(); }))); } - RecookButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - //.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + RebuildButtonHorizontalBox->AddSlot() .VAlign(VAlign_Center) .AutoWidth() + .Padding(5.0, 0.0, 0.0, 0.0) [ SNew(STextBlock) - .Text(FText::FromString("Recook")) + .Text(FText::FromString("Rebuild")) ]; + + ButtonRow.WholeRowWidget.Widget = ButtonHorizontalBox; + ButtonRow.IsEnabled(false); // Reset Parameters button TSharedPtr ResetParametersButton; @@ -567,7 +572,8 @@ FHoudiniEngineDetails::OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAsset HAC, HAC->bReplacePreviousBake, HAC->HoudiniEngineBakeOption, - HAC->bRemoveOutputAfterBake); + HAC->bRemoveOutputAfterBake, + HAC->bRecenterBakedActors); }); } } @@ -581,7 +587,7 @@ FHoudiniEngineDetails::CreateBakeWidgets( return; UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_BAKE); @@ -593,14 +599,15 @@ FHoudiniEngineDetails::CreateBakeWidgets( { for (auto & NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( - NextHAC, - MainHAC->bReplacePreviousBake, - MainHAC->HoudiniEngineBakeOption, - MainHAC->bRemoveOutputAfterBake); + NextHAC, + MainHAC->bReplacePreviousBake, + MainHAC->HoudiniEngineBakeOption, + MainHAC->bRemoveOutputAfterBake, + MainHAC->bRecenterBakedActors); } return FReply::Handled(); @@ -608,42 +615,14 @@ FHoudiniEngineDetails::CreateBakeWidgets( auto OnBakeFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; FString NewPathStr = Val.ToString(); - if (NewPathStr.IsEmpty()) - return; - - if (NewPathStr.StartsWith("Game/")) - { - NewPathStr = "/" + NewPathStr; - } - - FString AbsolutePath; - if (NewPathStr.StartsWith("/Game/")) - { - FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6); - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr); - } - - if (!FPaths::DirectoryExists(AbsolutePath)) - { - HOUDINI_LOG_WARNING(TEXT("Invalid path")); - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - return; - } - - for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; if (NextHAC->BakeFolder.Path.Equals(NewPathStr)) @@ -756,10 +735,10 @@ FHoudiniEngineDetails::CreateBakeWidgets( const EHoudiniEngineBakeOption NewOption = FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; + for (auto & NextHAC : InHACs) + { + if (!IsValid(NextHAC)) + continue; MainHAC->HoudiniEngineBakeOption = NewOption; } @@ -838,7 +817,7 @@ FHoudiniEngineDetails::CreateBakeWidgets( for (auto & NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bRemoveOutputAfterBake = bNewState; @@ -873,7 +852,7 @@ FHoudiniEngineDetails::CreateBakeWidgets( for (auto & NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bRecenterBakedActors = bNewState; @@ -891,7 +870,7 @@ FHoudiniEngineDetails::CreateBakeWidgets( // managing the delegate in this way). for (auto & NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; const bool bState = NextHAC->IsBakeAfterNextCookEnabled(); @@ -923,7 +902,7 @@ FHoudiniEngineDetails::CreateBakeWidgets( for (auto & NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->SetBakeAfterNextCookEnabled(bNewState); @@ -960,7 +939,7 @@ FHoudiniEngineDetails::CreateBakeWidgets( for (auto & NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; MainHAC->bReplacePreviousBake = bNewState; @@ -988,7 +967,11 @@ FHoudiniEngineDetails::CreateBakeWidgets( [ SNew(STextBlock) .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) - .ToolTipText(LOCTEXT("HoudiniEngineBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini Asset when baking.")) + .ToolTipText(LOCTEXT( + "HoudiniEngineBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) ] ]; @@ -1001,10 +984,19 @@ FHoudiniEngineDetails::CreateBakeWidgets( [ SNew(SEditableTextBox) .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT("HoudiniEngineBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini Asset when baking.")) + .ToolTipText(LOCTEXT( + "HoudiniEngineBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainHAC->BakeFolder.Path)) + .Text_Lambda([MainHAC]() + { + if (!IsValid(MainHAC)) + return FText(); + return FText::FromString(MainHAC->BakeFolder.Path); + }) .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) ] ]; @@ -1091,7 +1083,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( return; UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; // Header Row @@ -1102,7 +1094,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( auto IsCheckedParameterChangedLambda = [MainHAC]() { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return ECheckBoxState::Unchecked; return MainHAC->bCookOnParameterChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -1113,7 +1105,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( bool bChecked = (NewState == ECheckBoxState::Checked); for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bCookOnParameterChange = bChecked; @@ -1122,7 +1114,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( auto IsCheckedTransformChangeLambda = [MainHAC]() { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return ECheckBoxState::Unchecked; return MainHAC->bCookOnTransformChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -1133,7 +1125,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( bool bChecked = (NewState == ECheckBoxState::Checked); for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bCookOnTransformChange = bChecked; @@ -1144,7 +1136,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( auto IsCheckedAssetInputCookLambda = [MainHAC]() { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return ECheckBoxState::Unchecked; return MainHAC->bCookOnAssetInputCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -1155,7 +1147,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( bool bChecked = (NewState == ECheckBoxState::Checked); for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bCookOnAssetInputCook = bChecked; @@ -1164,7 +1156,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( auto IsCheckedPushTransformToHoudiniLambda = [MainHAC]() { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return ECheckBoxState::Unchecked; return MainHAC->bUploadTransformsToHoudiniEngine ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -1175,7 +1167,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( bool bChecked = (NewState == ECheckBoxState::Checked); for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bUploadTransformsToHoudiniEngine = bChecked; @@ -1186,7 +1178,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( auto IsCheckedDoNotGenerateOutputsLambda = [MainHAC]() { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return ECheckBoxState::Unchecked; return MainHAC->bOutputless ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -1197,7 +1189,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( bool bChecked = (NewState == ECheckBoxState::Checked); for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bOutputless = bChecked; @@ -1208,7 +1200,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( auto IsCheckedOutputTemplatedGeosLambda = [MainHAC]() { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return ECheckBoxState::Unchecked; return MainHAC->bOutputTemplateGeos ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -1219,7 +1211,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( bool bChecked = (NewState == ECheckBoxState::Checked); for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bOutputTemplateGeos = bChecked; @@ -1228,35 +1220,98 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( } }; - // Checkboxes row - FDetailWidgetRow & CheckBoxesRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef CheckBoxesHorizontalBox = SNew(SHorizontalBox); + auto IsCheckedUseOutputNodesLambda = [MainHAC]() + { + if (!IsValid(MainHAC)) + return ECheckBoxState::Unchecked; - TSharedPtr LeftColumnVerticalBox; - TSharedPtr RightColumnVerticalBox; + return MainHAC->bUseOutputNodes ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedUseOutputNodesLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!IsValid(NextHAC)) + continue; + + NextHAC->bUseOutputNodes = bChecked; - CheckBoxesHorizontalBox->AddSlot() - .Padding(30.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) + NextHAC->MarkAsNeedCook(); + } + }; + + // Checkboxes row + FDetailWidgetRow & CheckBoxesRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedPtr FirstLeftColumnVerticalBox; + TSharedPtr FirstRightColumnVerticalBox; + TSharedPtr SecondLeftColumnVerticalBox; + TSharedPtr SecondRightColumnVerticalBox; + TSharedRef WidgetBox = SNew(SHorizontalBox); + WidgetBox->AddSlot() [ - SNew(SBox) - .WidthOverride(200.f) + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() [ - SAssignNew(LeftColumnVerticalBox, SVerticalBox) + //First Line + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + // First Left + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(FirstLeftColumnVerticalBox, SVerticalBox) + ] + ] + + SHorizontalBox::Slot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + // First Right + SNew(SBox) + [ + SAssignNew(FirstRightColumnVerticalBox, SVerticalBox) + ] + ] ] - ]; - - CheckBoxesHorizontalBox->AddSlot() - .Padding(20.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) + + SVerticalBox::Slot() + .AutoHeight() [ - SAssignNew(RightColumnVerticalBox, SVerticalBox) + //Second Line + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + // Second Left + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(SecondLeftColumnVerticalBox, SVerticalBox) + ] + ] + + SHorizontalBox::Slot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + // Second Right + SNew(SBox) + [ + SAssignNew(SecondRightColumnVerticalBox, SVerticalBox) + ] + ] ] ]; - LeftColumnVerticalBox->AddSlot() + // + // First line - left + // + FirstLeftColumnVerticalBox->AddSlot() .AutoHeight() .Padding(0.0f, 0.0f, 0.0f, 3.5f) [ @@ -1270,7 +1325,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( // Parameter change check box FText TooltipText = LOCTEXT("HoudiniEngineParameterChangeTooltip", "If enabled, modifying a parameter or input on this Houdini Asset will automatically trigger a cook of the HDA in Houdini."); - LeftColumnVerticalBox->AddSlot() + FirstLeftColumnVerticalBox->AddSlot() .AutoHeight() [ SNew(SHorizontalBox) @@ -1293,7 +1348,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( // Transform change check box TooltipText = LOCTEXT("HoudiniEngineTransformChangeTooltip", "If enabled, changing the Houdini Asset Actor's transform in Unreal will also update its HDA's node transform in Houdini, and trigger a recook of the HDA with the updated transform."); - LeftColumnVerticalBox->AddSlot() + FirstLeftColumnVerticalBox->AddSlot() .AutoHeight() [ SNew(SHorizontalBox) @@ -1316,7 +1371,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( // Triggers Downstream cook checkbox TooltipText = LOCTEXT("HoudiniEngineAssetInputCookTooltip", "When enabled, this asset will automatically re-cook after one its asset input has finished cooking."); - LeftColumnVerticalBox->AddSlot() + FirstLeftColumnVerticalBox->AddSlot() .AutoHeight() [ SNew(SHorizontalBox) @@ -1328,7 +1383,6 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( .Text(LOCTEXT("HoudiniEngineAssetInputCheckBoxLabel", "On Asset Input Cook")) .ToolTipText(TooltipText) ] - + SHorizontalBox::Slot() [ SNew(SCheckBox) @@ -1338,17 +1392,20 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( ] ]; - RightColumnVerticalBox->AddSlot() + // + // First line - right + // + FirstRightColumnVerticalBox->AddSlot() .AutoHeight() .Padding(0.0f, 0.0f, 0.0f, 3.5f) [ SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineMiscLabel", "Miscellaneous")) + .Text(LOCTEXT("HoudiniEngineOutputLabel", "Outputs")) ]; - // Push Transform to Houdini check box - TooltipText = LOCTEXT("HoudiniEnginePushTransformTooltip", "If enabled, modifying this Houdini Asset Actor's transform will automatically update the HDA's node transform in Houdini."); - RightColumnVerticalBox->AddSlot() + // Do not generate output check box + TooltipText = LOCTEXT("HoudiniEnginOutputlessTooltip", "If enabled, this Houdini Asset will cook normally but will not generate any output in Unreal. This is especially usefull when chaining multiple assets together via Asset Inputs."); + FirstRightColumnVerticalBox->AddSlot() .AutoHeight() [ SNew(SHorizontalBox) @@ -1357,22 +1414,21 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( [ SNew(STextBlock) .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEnginePushTransformToHoudiniCheckBoxLabel", "Push Transform to Houdini")) + .Text(LOCTEXT("HoudiniEngineDoNotGenerateOutputsCheckBoxLabel", "Do Not Generate Outputs")) .ToolTipText(TooltipText) ] - + SHorizontalBox::Slot() [ SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedPushTransformToHoudiniLambda) - .IsChecked_Lambda(IsCheckedPushTransformToHoudiniLambda) + .OnCheckStateChanged_Lambda(OnCheckStateChangedDoNotGenerateOutputsLambda) + .IsChecked_Lambda(IsCheckedDoNotGenerateOutputsLambda) .ToolTipText(TooltipText) ] ]; - // Do not generate output check box - TooltipText = LOCTEXT("HoudiniEnginOutputlessTooltip", "If enabled, this Houdini Asset will cook normally but will not generate any output in Unreal. This is especially usefull when chaining multiple assets together via Asset Inputs."); - RightColumnVerticalBox->AddSlot() + // Use Output Nodes geos check box + TooltipText = LOCTEXT("HoudiniEnginUseOutputNodesTooltip", "If enabled, Output nodes found in this Houdini asset will be used alongside the Display node to create outputs."); + FirstRightColumnVerticalBox->AddSlot() .AutoHeight() [ SNew(SHorizontalBox) @@ -1381,21 +1437,21 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( [ SNew(STextBlock) .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineDoNotGenerateOutputsCheckBoxLabel", "Do Not Generate Outputs")) + .Text(LOCTEXT("HoudiniEnginUseOutputNodesCheckBoxLabel", "Use Output Nodes")) .ToolTipText(TooltipText) ] + SHorizontalBox::Slot() [ SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedDoNotGenerateOutputsLambda) - .IsChecked_Lambda(IsCheckedDoNotGenerateOutputsLambda) + .OnCheckStateChanged_Lambda(OnCheckStateChangedUseOutputNodesLambda) + .IsChecked_Lambda(IsCheckedUseOutputNodesLambda) .ToolTipText(TooltipText) ] ]; // Output templated geos check box TooltipText = LOCTEXT("HoudiniEnginOutputTemplatesTooltip", "If enabled, Geometry nodes in the asset that have the template flag will be outputed."); - RightColumnVerticalBox->AddSlot() + FirstRightColumnVerticalBox->AddSlot() .AutoHeight() [ SNew(SHorizontalBox) @@ -1404,7 +1460,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( [ SNew(STextBlock) .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEnginOutputTemplatesCheckBoxLabel", "Output Templated Geos")) + .Text(LOCTEXT("HoudiniEnginOutputTemplatesCheckBoxLabel", "Use Templated Geos")) .ToolTipText(TooltipText) ] + SHorizontalBox::Slot() @@ -1416,7 +1472,43 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( ] ]; - CheckBoxesRow.WholeRowWidget.Widget = CheckBoxesHorizontalBox; + + // + // Second line + // + SecondLeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineMiscLabel", "Miscellaneous")) + ]; + + // Push Transform to Houdini check box + TooltipText = LOCTEXT("HoudiniEnginePushTransformTooltip", "If enabled, modifying this Houdini Asset Actor's transform will automatically update the HDA's node transform in Houdini."); + SecondLeftColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEnginePushTransformToHoudiniCheckBoxLabel", "Push Transform to Houdini")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedPushTransformToHoudiniLambda) + .IsChecked_Lambda(IsCheckedPushTransformToHoudiniLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Use whole widget + CheckBoxesRow.WholeRowWidget.Widget = WidgetBox; } void @@ -1428,7 +1520,7 @@ FHoudiniEngineDetails::CreateHelpAndDebugWidgets( return; UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; // Header Row @@ -1749,7 +1841,7 @@ FHoudiniEngineDetails::ShowAssetHelp(UHoudiniAssetComponent * InHAC) void FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, UHoudiniAssetComponent * HoudiniAssetComponent, int32 MenuSection) { - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return; FOnClicked OnExpanderClick = FOnClicked::CreateLambda([HoudiniAssetComponent, MenuSection]() @@ -1841,7 +1933,7 @@ FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(IDetailCategoryBuild void FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(IDetailCategoryBuilder& PDGCategoryBuilder, UHoudiniPDGAssetLink* InPDGAssetLink, int32 MenuSection) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return; FOnClicked OnExpanderClick = FOnClicked::CreateLambda([InPDGAssetLink, MenuSection]() diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h index 998725c16..bd6b0b568 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,10 +23,19 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + #pragma once #include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Framework/SlateDelegates.h" +#include "Styling/SlateBrush.h" +#include "Widgets/Layout/SBorder.h" +#include "Framework/SlateDelegates.h" +#include "Widgets/Input/SButton.h" + class IDetailCategoryBuilder; class UHoudiniAssetComponent; class UHoudiniPDGAssetLink; @@ -34,10 +43,6 @@ class FMenuBuilder; class SBorder; class SButton; -#include "Widgets/DeclarativeSyntaxSupport.h" -#include "Widgets/SCompoundWidget.h" -#include "Framework/SlateDelegates.h" - class SHoudiniAssetLogWidget : public SCompoundWidget { public: @@ -53,7 +58,7 @@ class SHoudiniAssetLogWidget : public SCompoundWidget void Construct(const FArguments & InArgs); }; -class FHoudiniEngineDetails : public TSharedFromThis +class FHoudiniEngineDetails : public TSharedFromThis { public: static void CreateWidget( diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp index f729653c6..a59bb452c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -496,12 +496,12 @@ FHoudiniEngineEditor::BindMenuCommands() HEngineCommands->MapAction( Commands._RefineAll, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); }), + FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); }), FCanExecuteAction::CreateLambda([]() { return true; })); HEngineCommands->MapAction( Commands._RefineSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), + FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), FCanExecuteAction::CreateLambda([]() { return true; })); HEngineCommands->MapAction( @@ -1079,13 +1079,13 @@ FHoudiniEngineEditor::GetLevelViewportContextMenuExtender(const TSharedRef(CurrentActor); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) continue; HoudiniAssetActors.Add(HoudiniAssetActor); UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; HoudiniAssets.AddUnique(HoudiniAssetComponent->GetHoudiniAsset()); @@ -1149,7 +1149,7 @@ FHoudiniEngineEditor::GetLevelViewportContextMenuExtender(const TSharedRef 0); }) ) ); @@ -1163,6 +1163,64 @@ FHoudiniEngineEditor::GetLevelViewportContextMenuExtender(const TSharedRef Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,9 @@ #include "CoreTypes.h" #include "Templates/SharedPointer.h" +#include "Framework/Commands/UICommandList.h" +#include "Brushes/SlateDynamicImageBrush.h" + class FExtender; class IAssetTools; @@ -330,6 +333,9 @@ class HOUDINIENGINEEDITOR_API FHoudiniEngineEditor : public IHoudiniEngineEditor // Delegate handle for the PreBeginPIE editor delegate FDelegateHandle PreBeginPIEEditorDelegateHandle; + // Delegate handle for the EndPIE editor delegate + FDelegateHandle EndPIEEditorDelegateHandle; + // Delegate handle for OnDeleteActorsBegin FDelegateHandle OnDeleteActorsBegin; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.cpp deleted file mode 100644 index c5fdea932..000000000 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.h deleted file mode 100644 index 1bfdf8b0c..000000000 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.h +++ /dev/null @@ -1,27 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h index 35a709559..b6765739d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp index 8ce8a5596..a7c38dd5c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,7 @@ #include "HoudiniEngineEditor.h" #include "HoudiniRuntimeSettings.h" #include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" #include "HoudiniGeoPartObject.h" #include "HoudiniAsset.h" #include "HoudiniOutput.h" @@ -61,7 +62,7 @@ FHoudiniEngineEditorUtils::GetContentBrowserSelection(TArray< UObject* >& Conten { // Get the current object UObject * Object = SelectedAssets[n].GetAsset(); - if (!Object || Object->IsPendingKill()) + if (!IsValid(Object)) continue; // Only static meshes are supported @@ -75,7 +76,7 @@ FHoudiniEngineEditorUtils::GetContentBrowserSelection(TArray< UObject* >& Conten } int32 -FHoudiniEngineEditorUtils::GetWorldSelection(TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly) +FHoudiniEngineEditorUtils::GetWorldSelection(TArray& WorldSelection, bool bHoudiniAssetActorsOnly) { WorldSelection.Empty(); @@ -83,12 +84,12 @@ FHoudiniEngineEditorUtils::GetWorldSelection(TArray< UObject* >& WorldSelection, if (GEditor) { USelection* SelectedActors = GEditor->GetSelectedActors(); - if (SelectedActors && !SelectedActors->IsPendingKill()) + if (IsValid(SelectedActors)) { for (FSelectionIterator It(*SelectedActors); It; ++It) { - AActor * Actor = Cast< AActor >(*It); - if (!Actor && Actor->IsPendingKill()) + AActor * Actor = Cast(*It); + if (!IsValid(Actor)) continue; // Ignore the SkySphere? @@ -101,7 +102,6 @@ FHoudiniEngineEditorUtils::GetWorldSelection(TArray< UObject* >& WorldSelection, WorldSelection.Add(Actor); } } - } // If we only want Houdini Actors... @@ -111,7 +111,7 @@ FHoudiniEngineEditorUtils::GetWorldSelection(TArray< UObject* >& WorldSelection, for (int32 Idx = WorldSelection.Num() - 1; Idx >= 0; Idx--) { AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) WorldSelection.RemoveAt(Idx); } } @@ -466,28 +466,40 @@ FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset } } -void -FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform) +AActor* +FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform, UWorld* InSpawnInWorld, ULevel* InSpawnInLevelOverride) { if (!InHoudiniAsset) - return; + return nullptr; FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); // Load the asset UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); if (!AssetObj) - return; + return nullptr; // Get the asset Factory UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); if (!Factory) - return; + return nullptr; + + // Determine the level to spawn in from the supplied parameters + // InSpawnInLevelOverride if valid, else if InSpawnInWorld is valid its current level + // lastly, get the editor world's current level + ULevel* LevelToSpawnIn = InSpawnInLevelOverride; + if (!IsValid(LevelToSpawnIn)) + { + if (IsValid(InSpawnInWorld)) + LevelToSpawnIn = InSpawnInWorld->GetCurrentLevel(); + else + LevelToSpawnIn = GEditor->GetEditorWorldContext().World()->GetCurrentLevel(); + } // We only need to create a single instance of the asset, regarding of the selection - AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), InTransform); + AActor* CreatedActor = Factory->CreateActor(AssetObj, LevelToSpawnIn, InTransform); if (!CreatedActor) - return; + return nullptr; // Select the Actor we just created if (GEditor->CanSelectActor(CreatedActor, true, true)) @@ -495,6 +507,8 @@ FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAss GEditor->SelectNone(true, true, false); GEditor->SelectActor(CreatedActor, true, true, true); } + + return CreatedActor; } @@ -509,7 +523,7 @@ FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) for (TObjectIterator Itr; Itr; ++Itr) { UHoudiniAssetComponent * HAC = *Itr; - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) continue; if (InSaveWorld && InSaveWorld != HAC->GetWorld()) @@ -519,7 +533,7 @@ FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) for (int32 Index = 0; Index < NumOutputs; ++Index) { UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) continue; // TODO: Also save landscape layer info objects? @@ -529,7 +543,7 @@ FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) for (auto &OutputObjectPair : Output->GetOutputObjects()) { UObject *Obj = OutputObjectPair.Value.OutputObject; - if (!Obj || Obj->IsPendingKill()) + if (!IsValid(Obj)) continue; UStaticMesh *SM = Cast(Obj); @@ -537,7 +551,7 @@ FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) continue; UPackage *Package = SM->GetOutermost(); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) continue; if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) @@ -549,11 +563,11 @@ FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) for (auto& MaterialAssignementPair : Output->GetAssignementMaterials()) { UMaterialInterface* MatInterface = MaterialAssignementPair.Value; - if (!MatInterface || MatInterface->IsPendingKill()) + if (!IsValid(MatInterface)) continue; UPackage *Package = MatInterface->GetOutermost(); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) continue; if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) @@ -584,6 +598,27 @@ FHoudiniEngineEditorUtils::ReselectSelectedActors() } } +void +FHoudiniEngineEditorUtils::ReselectActorIfSelected(AActor* const InActor) +{ + if (IsValid(InActor) && InActor->IsSelected()) + { + GEditor->SelectActor(InActor, false, false, false, false); + GEditor->SelectActor(InActor, true, true, true, true); + } +} + +void +FHoudiniEngineEditorUtils::ReselectComponentOwnerIfSelected(USceneComponent* const InComponent) +{ + if (!IsValid(InComponent)) + return; + AActor* const Actor = InComponent->GetOwner(); + if (!IsValid(Actor)) + return; + ReselectActorIfSelected(Actor); +} + FString FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding, const TCHAR PathSep) { diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h index 4723fb478..fbedb292f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,8 @@ class FString; class UObject; class UHoudiniAsset; +class AActor; +class ULevel; enum class EHoudiniCurveType : int8; enum class EHoudiniCurveMethod : int8; @@ -64,8 +66,10 @@ struct FHoudiniEngineEditorUtils static FTransform GetMeanWorldSelectionTransform(); - // Instantiate a HoudiniAsset at a given position - static void InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform); + // Instantiate a HoudiniAsset at a given position. If InSpawnInLevelOverride is non-null, spawns in that level. + // Otherwise if InSpawnInWorld, spawns in the current level of InSpawnInWorld. Lastly, if both are null, spawns + // in the current level of the editor context world. + static AActor* InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform, UWorld* InSpawnInWorld=nullptr, ULevel* InSpawnInLevelOverride=nullptr); // Instantiate the given HDA, and handles the current CB/World selection static void InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType); @@ -76,6 +80,14 @@ struct FHoudiniEngineEditorUtils // Deselect and reselect all selected actors to get rid of component not showing bug after create. static void ReselectSelectedActors(); + // Deselect and reselect InActor, but only if it is currently selected. Related to ReselectSelectedActors and + // the component not showing bug after create. + static void ReselectActorIfSelected(AActor* const InActor); + + // Deselect and reselect the owning actor of InComopnent, but only if the actor is currently selected. Related to + // ReselectSelectedActors and the component not showing bug after create. + static void ReselectComponentOwnerIfSelected(USceneComponent* const InComponent); + // Gets the node name indent from the left with the specified number of spaces based on the path depth. static FString GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding=4, const TCHAR PathSep='/'); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp index 3b06bc10a..06848f70c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -96,9 +96,15 @@ FHoudiniEngineStyle::Initialize() StyleSet->Set( "HoudiniEngine.HoudiniEngineLogo", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set( "ClassIcon.HoudiniAssetActor", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set( + "ClassThumbnail.HoudiniAssetActor", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_128.png"), Icon64x64)); + StyleSet->Set( "HoudiniEngine.HoudiniEngineLogo40", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_40.png"), Icon40x40)); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h index adc6c51a7..ac64f0faa 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp index d2293b7fa..67224f86d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -266,7 +266,7 @@ UHoudiniGeoFactory::Import(UClass* InClass, UPackage* InParent, const FString & TArray Results = BGEOImporter->GetOutputObjects(); for (UObject* Object : Results) { - if (!Object || Object->IsPendingKill()) + if (!IsValid(Object)) continue; Object->SetFlags(Flags); @@ -322,18 +322,18 @@ UHoudiniGeoFactory::Reimport(UObject * Obj) return EReimportResult::Failed; }; - if (!Obj || Obj->IsPendingKill()) + if (!IsValid(Obj)) return FailReimport(); UPackage* Package = Cast(Obj->GetOuter()); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return FailReimport(); UAssetImportData* ImportData = nullptr; if (Obj->GetClass() == UStaticMesh::StaticClass()) { UStaticMesh* StaticMesh = Cast(Obj); - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return FailReimport(); ImportData = StaticMesh->AssetImportData; @@ -342,13 +342,13 @@ UHoudiniGeoFactory::Reimport(UObject * Obj) else if(Obj->GetClass() == USkeletalMesh::StaticClass()) { USkeletalMesh* SkeletalMesh = Cast(Obj); - if (!SkeletalMesh || SkeletalMesh->IsPendingKill()) + if (!IsValid(SkeletalMesh)) return FailReimport(); ImportData = SkeletalMesh->AssetImportData; } */ - if (!ImportData || ImportData->IsPendingKill()) + if (!IsValid(ImportData)) return FailReimport(); if (ImportData->GetSourceFileCount() <= 0) diff --git a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h index 6865f2bc9..8dd0a42cb 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp index 3b4d58dd0..13b4057bd 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h index eb09d1a4c..5eed6461b 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp index 9413728b8..621a3932d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,12 +24,9 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#pragma once - #include "HoudiniHandleDetails.h" - +#include "HoudiniEngineRuntimePrivatePCH.h" #include "HoudiniEngineEditorPrivatePCH.h" - #include "HoudiniHandleComponent.h" #include "HoudiniHandleTranslator.h" #include "HoudiniHandleComponentVisualizer.h" @@ -54,7 +51,7 @@ FHoudiniHandleDetails::CreateWidget(IDetailCategoryBuilder & HouHandleCategory, UHoudiniHandleComponent* MainHandle = InHandles[0]; - if (!MainHandle || MainHandle->IsPendingKill()) + if (!IsValid(MainHandle)) return; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h index 3a9418326..14dfd7c8c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp index c3b1feb1d..a21e8e9eb 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,8 +24,6 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#pragma once - #include "HoudiniInputDetails.h" #include "HoudiniEngineEditorPrivatePCH.h" @@ -108,7 +106,7 @@ FHoudiniInputDetails::CreateWidget( return; UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Get thumbnail pool for this builder. @@ -212,7 +210,7 @@ void FHoudiniInputDetails::CreateNameWidget( UHoudiniInput* InInput, FDetailWidgetRow & Row, bool bLabel, int32 InInputCount) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return; FString InputLabelStr = InInput->GetLabel(); @@ -274,7 +272,7 @@ FHoudiniInputDetails::AddInputTypeComboBox(IDetailCategoryBuilder& CategoryBuild return; UHoudiniInput * MainInput = InInputsToUpdate[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; UHoudiniAssetBlueprintComponent* HAB = MainInput->GetTypedOuter(); @@ -289,7 +287,7 @@ FHoudiniInputDetails::AddInputTypeComboBox(IDetailCategoryBuilder& CategoryBuild for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetInputType() == NewInputType) @@ -431,7 +429,7 @@ FHoudiniInputDetails::AddKeepWorldTransformCheckBox(TSharedRef< SVerticalBox > V return; UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Lambda returning a CheckState from the input's current KeepWorldTransform state @@ -443,7 +441,7 @@ FHoudiniInputDetails::AddKeepWorldTransformCheckBox(TSharedRef< SVerticalBox > V // Lambda for changing KeepWorldTransform state auto CheckStateChangedKeepWorldTransform = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -458,7 +456,7 @@ FHoudiniInputDetails::AddKeepWorldTransformCheckBox(TSharedRef< SVerticalBox > V for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetKeepWorldTransform() == bNewState) @@ -512,13 +510,13 @@ FHoudiniInputDetails::AddPackBeforeMergeCheckbox(TSharedRef< SVerticalBox > Vert UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Lambda returning a CheckState from the input's current PackBeforeMerge state auto IsCheckedPackBeforeMerge = [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->GetPackBeforeMerge() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -527,7 +525,7 @@ FHoudiniInputDetails::AddPackBeforeMergeCheckbox(TSharedRef< SVerticalBox > Vert // Lambda for changing PackBeforeMerge state auto CheckStateChangedPackBeforeMerge = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -543,7 +541,7 @@ FHoudiniInputDetails::AddPackBeforeMergeCheckbox(TSharedRef< SVerticalBox > Vert for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetPackBeforeMerge() == bNewState) @@ -586,13 +584,13 @@ FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > Ve UHoudiniInput * MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Lambda returning a CheckState from the input's current PackBeforeMerge state auto IsCheckedImportAsReference= [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->GetImportAsReference() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -601,7 +599,7 @@ FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > Ve // Lambda for changing PackBeforeMerge state auto CheckStateChangedImportAsReference= [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -617,7 +615,7 @@ FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > Ve for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetImportAsReference() == bNewState) @@ -629,11 +627,14 @@ FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > Ve // Mark all its input objects as changed to trigger recook. for (auto CurInputObj : *InputObjs) { - if (!CurInputObj || CurInputObj->IsPendingKill()) + if (!IsValid(CurInputObj)) continue; if (CurInputObj->GetImportAsReference() != bNewState) + { + CurInputObj->SetImportAsReference(bNewState); CurInputObj->MarkChanged(true); + } } } @@ -662,6 +663,85 @@ FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > Ve return CheckStateChangedImportAsReference(InInputs, NewState); }) ]; + + // Add Rot/Scale to input as reference + auto IsCheckedImportAsReferenceRotScale= [](UHoudiniInput* InInput) + { + if (!IsValid(InInput)) + return ECheckBoxState::Unchecked; + + return InInput->GetImportAsReferenceRotScaleEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing PackBeforeMerge state + auto CheckStateChangedImportAsReferenceRotScale= [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!IsValid(MainInput)) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetImportAsReferenceRotScaleEnabled() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputAsReferenceRotScale", "Houdini Input: Changing InputAsReference Rot/Scale"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!IsValid(CurInput)) + continue; + + if (CurInput->GetImportAsReferenceRotScaleEnabled() == bNewState) + continue; + + TArray * InputObjs = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); + if (InputObjs) + { + // Mark all its input objects as changed to trigger recook. + for (auto CurInputObj : *InputObjs) + { + if (!IsValid(CurInputObj)) + continue; + + if (CurInputObj->GetImportAsReferenceRotScaleEnabled() != bNewState) + { + CurInputObj->SetImportAsReferenceRotScaleEnabled(bNewState); + CurInputObj->MarkChanged(true); + } + } + } + + CurInput->Modify(); + CurInput->SetImportAsReferenceRotScaleEnabled(bNewState); + } + }; + + TSharedPtr< SCheckBox > CheckBoxImportAsReferenceRotScale; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxImportAsReferenceRotScale, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ImportInputAsRefRotScaleCheckbox", "Add rot/scale to input references")) + .ToolTipText(LOCTEXT("ImportInputAsRefRotScaleCheckboxTip", "Add rot/scale attributes to the input references when Import input as references is enabled")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedImportAsReferenceRotScale(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedImportAsReferenceRotScale(InInputs, NewState); + }) + .IsEnabled(IsCheckedImportAsReference(MainInput)) + + ]; } void FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) @@ -670,13 +750,13 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox return; UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Lambda returning a CheckState from the input's current ExportLODs state auto IsCheckedExportLODs = [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->GetExportLODs() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -685,7 +765,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox // Lambda returning a CheckState from the input's current ExportSockets state auto IsCheckedExportSockets = [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->GetExportSockets() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -694,7 +774,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox // Lambda returning a CheckState from the input's current ExportColliders state auto IsCheckedExportColliders = [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->GetExportColliders() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -703,7 +783,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox // Lambda for changing ExportLODs state auto CheckStateChangedExportLODs = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -719,7 +799,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetExportLODs() == bNewState) @@ -736,7 +816,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox // Lambda for changing ExportSockets state auto CheckStateChangedExportSockets = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -752,7 +832,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetExportSockets() == bNewState) @@ -769,7 +849,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox // Lambda for changing ExportColliders state auto CheckStateChangedExportColliders = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -785,7 +865,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetExportColliders() == bNewState) @@ -894,7 +974,7 @@ FHoudiniInputDetails::AddGeometryInputUI( // Lambda for changing ExportColliders state auto SetGeometryInputObjectsCount = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& NewInputCount) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -905,7 +985,7 @@ FHoudiniInputDetails::AddGeometryInputUI( for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry) == NewInputCount) @@ -992,10 +1072,10 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( // Lambda for adding new geometry input objects auto UpdateGeometryObjectAt = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& AtIndex, UObject* InObject) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return; // Record a transaction for undo/redo @@ -1006,7 +1086,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; UObject* InputObject = CurInput->GetInputObjectAt(EHoudiniInputType::Geometry, AtIndex); @@ -1073,15 +1153,17 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( ] ]; + TWeakPtr WeakStaticMeshThumbnailBorder(StaticMeshThumbnailBorder); StaticMeshThumbnailBorder->SetBorderImage(TAttribute::Create( - TAttribute::FGetter::CreateLambda([StaticMeshThumbnailBorder]() + TAttribute::FGetter::CreateLambda([WeakStaticMeshThumbnailBorder]() { - if (StaticMeshThumbnailBorder.IsValid() && StaticMeshThumbnailBorder->IsHovered()) + TSharedPtr ThumbnailBorder = WeakStaticMeshThumbnailBorder.Pin(); + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); else return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); } - ) ) ); + ))); FText MeshNameText = FText::GetEmpty(); if (InputObject) @@ -1118,30 +1200,36 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( ]; + TWeakPtr WeakStaticMeshComboButton(StaticMeshComboButton); StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( - [MainInput, InInputs, InGeometryObjectIdx, StaticMeshComboButton, UpdateGeometryObjectAt]() - { - TArray< const UClass * > AllowedClasses = UHoudiniInput::GetAllowedClasses(EHoudiniInputType::Geometry); - UObject* DefaultObj = MainInput->GetInputObjectAt(InGeometryObjectIdx); - - TArray< UFactory * > NewAssetFactories; - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(DefaultObj), - true, - AllowedClasses, - NewAssetFactories, - FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda([InInputs, InGeometryObjectIdx, StaticMeshComboButton, UpdateGeometryObjectAt](const FAssetData & AssetData) + [MainInput, InInputs, InGeometryObjectIdx, WeakStaticMeshComboButton, UpdateGeometryObjectAt]() { - if (StaticMeshComboButton.IsValid()) - { - StaticMeshComboButton->SetIsOpen(false); - UObject * Object = AssetData.GetAsset(); - UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); - } - }), - FSimpleDelegate::CreateLambda([]() {})); - })); + TArray< const UClass * > AllowedClasses = UHoudiniInput::GetAllowedClasses(EHoudiniInputType::Geometry); + UObject* DefaultObj = MainInput->GetInputObjectAt(InGeometryObjectIdx); + + TArray< UFactory * > NewAssetFactories; + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(DefaultObj), + true, + AllowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda( + [InInputs, InGeometryObjectIdx, WeakStaticMeshComboButton, UpdateGeometryObjectAt](const FAssetData & AssetData) + { + TSharedPtr ComboButton = WeakStaticMeshComboButton.Pin(); + if (ComboButton.IsValid()) + { + ComboButton->SetIsOpen(false); + UObject * Object = AssetData.GetAsset(); + UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); + } + } + ), + FSimpleDelegate::CreateLambda([]() {}) + ); + } + )); // Add buttons @@ -1185,7 +1273,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( break; } - if (Object && !Object->IsPendingKill()) + if (IsValid(Object)) { UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); } @@ -1245,7 +1333,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( PropertyCustomizationHelpers::MakeInsertDeleteDuplicateButton( FExecuteAction::CreateLambda( [ InInputs, InGeometryObjectIdx, MainInput ]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; FScopedTransaction Transaction( @@ -1255,7 +1343,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( // Insert for (auto CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; CurInput->Modify(); @@ -1264,7 +1352,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( } ), FExecuteAction::CreateLambda([MainInput, InInputs, InGeometryObjectIdx]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; FScopedTransaction Transaction( @@ -1275,7 +1363,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( // Delete for (auto CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; CurInput->Modify(); @@ -1287,7 +1375,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( } ), FExecuteAction::CreateLambda([InInputs, InGeometryObjectIdx, MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; FScopedTransaction Transaction( @@ -1298,7 +1386,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( // Duplicate for (auto CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; CurInput->Modify(); @@ -1325,7 +1413,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( .Visibility( EVisibility::Visible ) .OnClicked(FOnClicked::CreateLambda([InInputs, InGeometryObjectIdx, MainInput, &CategoryBuilder]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return FReply::Handled();; FScopedTransaction Transaction( @@ -1336,7 +1424,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( // Expand transform for (auto CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; CurInput->Modify(); @@ -1366,21 +1454,25 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( ] ]; + TWeakPtr WeakExpanderArrow(ExpanderArrow); // Set delegate for image ExpanderImage->SetImage( TAttribute::Create( - TAttribute::FGetter::CreateLambda( [=]() { - FName ResourceName; - if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx) ) + TAttribute::FGetter::CreateLambda([InGeometryObjectIdx, MainInput, WeakExpanderArrow]() { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + FName ResourceName; + TSharedPtr ExpanderArrowPtr = WeakExpanderArrow.Pin(); + if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx)) + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + return FEditorStyle::GetBrush(ResourceName); } - return FEditorStyle::GetBrush( ResourceName ); - } ) ) ); + ))); } // Lambda for changing the transform values @@ -1395,7 +1487,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( bool bChanged = true; for (int Idx = 0; Idx < InInputs.Num(); Idx++) { - if (!InInputs[Idx] || InInputs[Idx]->IsPendingKill()) + if (!IsValid(InInputs[Idx])) continue; UHoudiniInputObject* InputObject = InInputs[Idx]->GetHoudiniInputObjectAt(AtIndex); @@ -1426,7 +1518,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( bool bResetButtonVisibleScale = false; for (auto & CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; FTransform* CurTransform = CurInput->GetTransformOffset(InGeometryObjectIdx); @@ -1688,12 +1780,12 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( { for (auto & CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; UHoudiniInputObject*CurInputObject = CurInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - if (!CurInputObject || CurInputObject->IsPendingKill()) + if (!IsValid(CurInputObject)) continue; CurInputObject->SwitchUniformScaleLock(); @@ -1742,7 +1834,7 @@ FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TA UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Houdini Asset Picker Widget @@ -1762,7 +1854,7 @@ FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TA TSharedPtr< SHorizontalBox > HorizontalBox = NULL; auto IsClearButtonEnabled = [MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return false; TArray* AssetInputObjectsArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); @@ -1778,7 +1870,7 @@ FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TA FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return FReply::Handled(); FScopedTransaction Transaction( @@ -1788,7 +1880,7 @@ FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TA for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; TArray* AssetInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); @@ -1844,11 +1936,11 @@ FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, T // lambda for inserting an input Houdini curve. auto InsertAnInputCurve = [MainInput, &CategoryBuilder](const int32& NewInputCount) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; UHoudiniAssetComponent* OuterHAC = Cast(MainInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return; // Do not insert input object when the HAC does not finish cooking @@ -1883,8 +1975,45 @@ FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, T CategoryBuilder.GetParentLayout().ForceRefreshDetails(); }; + // Add Rot/Scale attribute checkbox + FText TooltipText = LOCTEXT("HoudiniEngineCurveAddRotScaleAttributesTooltip", "If enabled, rot and scale attributes will be added per to the input curve on each control points."); + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SNew(SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineCurveAddRotScaleAttributesLabel", "Add rot & scale Attributes")) + .ToolTipText(TooltipText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + //.MinDesiredWidth(160.f) + ] + .OnCheckStateChanged_Lambda([InInputs](ECheckBoxState NewState) + { + const bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& CurrentInput : InInputs) + { + if (!IsValid(CurrentInput)) + continue; + + CurrentInput->SetAddRotAndScaleAttributes(bChecked); + } + }) + .IsChecked_Lambda([MainInput]() + { + if (!IsValid(MainInput)) + return ECheckBoxState::Unchecked; + + return MainInput->IsAddRotAndScaleAttributesEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .ToolTipText(TooltipText) + ]; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() @@ -1923,11 +2052,11 @@ FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, T { UHoudiniInputHoudiniSplineComponent* HoudiniInput = Cast ((*CurveInputComponentArray)[n]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) + if (!IsValid(HoudiniInput)) continue; UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); @@ -1948,11 +2077,11 @@ FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, T { UHoudiniInputHoudiniSplineComponent* HoudiniInput = Cast ((*CurveInputComponentArray)[n]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) + if (!IsValid(HoudiniInput)) continue; UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; MainInput->RemoveSplineFromInputObject(HoudiniInput, bBlueprintStructureModified); @@ -2014,17 +2143,17 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( { UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; UHoudiniAssetComponent * OuterHAC = Cast(MainInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return; auto GetHoudiniSplineComponentAtIndex = [](UHoudiniInput * Input, int32 Index) { UHoudiniSplineComponent* FoundHoudiniSplineComponent = nullptr; - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) return FoundHoudiniSplineComponent; // Get the TArray ptr to the curve objects in this input @@ -2117,7 +2246,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda for deleting the current curve input auto DeleteHoudiniCurveAtIndex = [InInputs, InCurveObjectIdx, OuterHAC, CurveInputComponentArray, &CategoryBuilder]() { - if (!OuterHAC|| OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return; // Record a transaction for undo/redo. @@ -2129,7 +2258,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( int MainInputCurveArraySize = CurveInputComponentArray->Num(); for (auto & Input : InInputs) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) continue; Input->Modify(); @@ -2146,7 +2275,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( UHoudiniInputHoudiniSplineComponent* HoudiniInput = Cast((*InputObjectArr)[InCurveObjectIdx]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) + if (!IsValid(HoudiniInput)) return; UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); @@ -2182,7 +2311,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda returning a closed state auto IsCheckedClosedCurve = [HoudiniSplineComponent]() { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return ECheckBoxState::Unchecked; return HoudiniSplineComponent->IsClosedCurve() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -2191,7 +2320,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda for changing Closed state auto CheckStateChangedClosedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) { - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -2204,12 +2333,12 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( for (auto & Input : InInputs) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; if (HoudiniSplineComponent->IsClosedCurve() == bNewState) @@ -2246,7 +2375,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda returning a reversed state auto IsCheckedReversedCurve = [HoudiniSplineComponent]() { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return ECheckBoxState::Unchecked; return HoudiniSplineComponent->IsReversed() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -2255,7 +2384,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda for changing reversed state auto CheckStateChangedReversedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) { - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -2268,11 +2397,11 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( for (auto & Input : InInputs) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; if (HoudiniSplineComponent->IsReversed() == bNewState) @@ -2310,7 +2439,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda returning a visible state auto IsCheckedVisibleCurve = [HoudiniSplineComponent]() { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return ECheckBoxState::Unchecked; return HoudiniSplineComponent->IsHoudiniSplineVisible() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -2323,7 +2452,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( for (auto & Input : InInputs) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); @@ -2365,7 +2494,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda for changing Houdini curve type auto OnCurveTypeChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) { - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return; if (!InNewChoice.IsValid()) @@ -2381,11 +2510,11 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( for (auto & Input : InInputs) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; if (HoudiniSplineComponent->GetCurveType() == NewInputType) @@ -2454,7 +2583,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda for changing Houdini curve method auto OnCurveMethodChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) { - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return; if (!InNewChoice.IsValid()) @@ -2470,11 +2599,11 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( for (auto & Input : InInputs) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; if (HoudiniSplineComponent->GetCurveMethod() == NewInputMethod) @@ -2535,15 +2664,15 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( { for (auto & NextInput : Inputs) { - if (!NextInput || NextInput->IsPendingKill()) + if (!IsValid(NextInput)) continue; UHoudiniAssetComponent * OuterHAC = Cast(NextInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) continue; AActor * OwnerActor = OuterHAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) continue; TArray * CurveInputComponentArray = NextInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); @@ -2557,11 +2686,11 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast(HoudiniInputObject); - if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) + if (!IsValid(HoudiniSplineInputObject)) continue; UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; FHoudiniPackageParams PackageParams; @@ -2660,7 +2789,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, // Lambda returning a CheckState from the input's current KeepWorldTransform state auto IsCheckedUpdateInputLandscape = [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->GetUpdateInputLandscape() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -2669,7 +2798,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, // Lambda for changing KeepWorldTransform state auto CheckStateChangedUpdateInputLandscape = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -2681,7 +2810,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (bNewState == CurInput->GetUpdateInputLandscape()) @@ -2754,7 +2883,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, [ SNew(STextBlock) .Text(LOCTEXT("LandscapeUpdateInputCheckbox", "Update Input Landscape Data")) - .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, the input landscape's data will be updated instead of creating a new landscape Actor")) + .ToolTipText(LOCTEXT("LandscapeUpdateInputTooltip", "If enabled, the input landscape's data will be updated instead of creating a new landscape Actor")) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] .IsChecked_Lambda([IsCheckedUpdateInputLandscape, MainInput]() @@ -2792,7 +2921,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, auto IsCheckedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) return ECheckBoxState::Unchecked; if (Input->GetLandscapeExportType() == LandscapeExportType) @@ -2803,7 +2932,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, auto CheckStateChangedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) return false; if (Input->GetLandscapeExportType() == LandscapeExportType) @@ -2972,14 +3101,14 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, ] .IsChecked_Lambda([MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return ECheckBoxState::Unchecked; return MainInput->bLandscapeExportSelectionOnly ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -2990,7 +3119,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -2998,9 +3127,11 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, continue; CurrentInput->Modify(); - CurrentInput->bLandscapeExportSelectionOnly = bNewState; + CurrentInput->UpdateLandscapeInputSelection(); + CurrentInput->MarkChanged(true); + } }) ]; @@ -3021,14 +3152,14 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, ] .IsChecked_Lambda([MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return ECheckBoxState::Unchecked; return MainInput->bLandscapeAutoSelectComponent ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -3039,7 +3170,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -3049,6 +3180,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, CurrentInput->Modify(); CurrentInput->bLandscapeAutoSelectComponent = bNewState; + CurrentInput->UpdateLandscapeInputSelection(); CurrentInput->MarkChanged(true); } }) @@ -3067,7 +3199,66 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, CheckBoxAutoSelectComponents->SetEnabled(bEnable); } + // Button : Update landscape component selection + { + auto OnButtonUpdateComponentSelection = [InInputs, MainInput]() + { + if (!IsValid(MainInput)) + return FReply::Handled(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniRecommitSelectedComponents", "Houdini Input: Recommit selected landscapes."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!IsValid(CurrentInput)) + continue; + + CurrentInput->UpdateLandscapeInputSelection(); + + CurrentInput->Modify(); + CurrentInput->MarkChanged(true); + } + return FReply::Handled(); + }; + + auto IsLandscapeExportEnabled = [MainInput, InInputs]() + { + bool bEnable = false; + for (auto CurrentInput : InInputs) + { + if (!MainInput->bLandscapeExportSelectionOnly) + continue; + + bEnable = true; + break; + } + + return bEnable; + }; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1, 2, 4, 2) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("LandscapeInputUpdateSelectedComponents", "Update Selected Components")) + .ToolTipText(LOCTEXT("LandscapeInputUpdateSelectedComponentsTooltip", "Updates the selected components. Only valid if export selected components is true.")) + .OnClicked_Lambda(OnButtonUpdateComponentSelection) + .IsEnabled_Lambda(IsLandscapeExportEnabled) + ] + ]; + + } + // The following checkbox are only added when not in heightfield mode if (MainInput->LandscapeExportType != EHoudiniLandscapeExportType::Heightfield) { @@ -3086,14 +3277,14 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, ] .IsChecked_Lambda([MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return ECheckBoxState::Unchecked; return MainInput->bLandscapeExportMaterials ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -3104,7 +3295,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -3141,14 +3332,14 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, ] .IsChecked_Lambda([MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return ECheckBoxState::Unchecked; return MainInput->bLandscapeExportTileUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -3159,7 +3350,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -3196,14 +3387,14 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, ] .IsChecked_Lambda([MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return ECheckBoxState::Unchecked; return MainInput->bLandscapeExportNormalizedUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -3214,7 +3405,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -3251,14 +3442,14 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, ] .IsChecked_Lambda([MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return ECheckBoxState::Unchecked; return MainInput->bLandscapeExportLighting ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -3269,7 +3460,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -3338,7 +3529,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, { auto IsClearButtonEnabled = [MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return false; if (MainInput->GetInputType() != EHoudiniInputType::Landscape) @@ -3360,7 +3551,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, return FReply::Handled(); UHoudiniInput * MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return FReply::Handled(); if (MainInput->GetInputType() != EHoudiniInputType::Landscape) @@ -3381,7 +3572,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto & CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; TArray* LandscapeInputObjectsArray = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); @@ -3426,7 +3617,7 @@ FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArrayIsPendingKill()) + if (!IsValid(Actor)) return false; if (!Actor->IsA()) @@ -3453,7 +3644,7 @@ FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArrayIsPendingKill()) + if (!IsValid(Actor)) return false; const TArray* InputObjects = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); @@ -3463,11 +3654,11 @@ FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArrayIsPendingKill()) + if (!IsValid(CurInputObject)) continue; AActor* CurActor = Cast(CurInputObject->GetObject()); - if (!CurActor || CurActor->IsPendingKill()) + if (!IsValid(CurActor)) continue; if (CurActor == Actor) @@ -3498,7 +3689,7 @@ FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArrayIsPendingKill()) + if (!IsValid(MainInput)) return true; switch (MainInput->GetInputType()) @@ -3582,7 +3773,7 @@ FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArrayIsPendingKill()) + if (!IsValid(CurInput)) return; switch (CurInput->GetInputType()) @@ -3693,7 +3884,7 @@ FHoudiniInputDetails::Helper_CreateHoudiniAssetPickerWidget(TArrayIsPendingKill()) + if (!IsValid(MainInput)) return true; return OnShouldFilterHoudiniAsset(Actor); @@ -3701,7 +3892,7 @@ FHoudiniInputDetails::Helper_CreateHoudiniAssetPickerWidget(TArrayIsPendingKill() || !Input || Input->IsPendingKill()) + if (!IsValid(Actor) || !IsValid(Input)) return; AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); @@ -3740,7 +3931,7 @@ FHoudiniInputDetails::Helper_CreateHoudiniAssetPickerWidget(TArrayIsPendingKill()) + if (!IsValid(CurInput)) return; OnHoudiniAssetActorSelected(Actor, CurInput); @@ -3809,7 +4000,7 @@ FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) { - if (!Actor || Actor->IsPendingKill()) + if (!IsValid(Actor)) return false; if (!Actor->IsA()) @@ -3823,7 +4014,7 @@ FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& // We need to get the AttachParent on the root componet, GteOwner will not return the parent actor! AActor* OwnerActor = nullptr; USceneComponent* RootComponent = LandscapeProxy->GetRootComponent(); - if (RootComponent && !RootComponent->IsPendingKill()) + if (IsValid(RootComponent)) OwnerActor = RootComponent->GetAttachParent() ? RootComponent->GetAttachParent()->GetOwner() : LandscapeProxy->GetOwner(); // Get our Actor @@ -3838,7 +4029,7 @@ FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& for (int32 Idx = 0; Idx < MyHAC->GetNumInputs(); Idx++) { UHoudiniInput* CurrentInput = MyHAC->GetInputAt(Idx); - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) @@ -3862,7 +4053,7 @@ FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& // Filters are only based on the MainInput auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape](const AActor* const Actor) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return true; return OnShouldFilterLandscape(Actor, MainInput); @@ -3907,7 +4098,7 @@ FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& return; UHoudiniInput * MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -3918,7 +4109,7 @@ FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& for (auto CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; CurInput->Modify(); @@ -3988,7 +4179,7 @@ FHoudiniInputDetails::Helper_CreateWorldActorPickerWidget(TArray UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; auto OnShouldFilterWorld = [MainInput](const AActor* const Actor) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return true; const TArray* InputObjects = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); @@ -3998,19 +4189,19 @@ FHoudiniInputDetails::Helper_CreateWorldActorPickerWidget(TArray // Only return actors that are currently selected by our input for (const auto& CurInputObject : *InputObjects) { - if (!CurInputObject || CurInputObject->IsPendingKill()) + if (!IsValid(CurInputObject)) continue; AActor* CurActor = Cast(CurInputObject->GetObject()); - if (!CurActor || CurActor->IsPendingKill()) + if (!IsValid(CurActor)) { // See if the input object is a HAC, if it is, get its parent actor UHoudiniAssetComponent* CurHAC = Cast(CurInputObject->GetObject()); - if (CurHAC && !CurHAC->IsPendingKill()) + if (IsValid(CurHAC)) CurActor = CurHAC->GetOwner(); } - if (!CurActor || CurActor->IsPendingKill()) + if (!IsValid(CurActor)) continue; if (CurActor == Actor) @@ -4073,7 +4264,7 @@ FHoudiniInputDetails::Helper_CreateBoundSelectorPickerWidget(TArray 0 ? InInputs[0] : nullptr; auto OnShouldFilter = [MainInput](const AActor* const Actor) { - if (!Actor || Actor->IsPendingKill()) + if (!IsValid(Actor)) return false; const TArray* BoundObjects = MainInput->GetBoundSelectorObjectArray(); @@ -4083,7 +4274,7 @@ FHoudiniInputDetails::Helper_CreateBoundSelectorPickerWidget(TArrayIsPendingKill()) + if (!IsValid(CurActor)) continue; if (CurActor == Actor) @@ -4258,7 +4449,7 @@ FHoudiniInputDetails::AddWorldInputUI( FOnClicked OnSelectAll = FOnClicked::CreateLambda([InInputs, MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return FReply::Handled(); // Record a transaction for undo/redo @@ -4269,7 +4460,7 @@ FHoudiniInputDetails::AddWorldInputUI( for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; // Get the parent component/actor/world of the current input @@ -4281,7 +4472,7 @@ FHoudiniInputDetails::AddWorldInputUI( for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) { AActor *CurrentActor = *ActorItr; - if (!CurrentActor || CurrentActor->IsPendingKill()) + if (!IsValid(CurrentActor)) continue; // Ignore the SkySpheres? @@ -4306,7 +4497,7 @@ FHoudiniInputDetails::AddWorldInputUI( FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput, &CategoryBuilder]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return FReply::Handled(); const bool bMainInputBoundSelection = MainInput->IsWorldInputBoundSelector(); @@ -4389,7 +4580,7 @@ FHoudiniInputDetails::AddWorldInputUI( // Lambda returning a CheckState from the input's current bound selector state auto IsCheckedBoundSelector = [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->IsWorldInputBoundSelector() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -4398,7 +4589,7 @@ FHoudiniInputDetails::AddWorldInputUI( // Lambda for changing bound selector state auto CheckStateChangedIsBoundSelector = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -4410,7 +4601,7 @@ FHoudiniInputDetails::AddWorldInputUI( bool bNewState = (NewState == ECheckBoxState::Checked); for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->IsWorldInputBoundSelector() == bNewState) @@ -4453,7 +4644,7 @@ FHoudiniInputDetails::AddWorldInputUI( // Lambda returning a CheckState from the input's current auto update state auto IsCheckedAutoUpdate = [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->GetWorldInputBoundSelectorAutoUpdates() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -4462,7 +4653,7 @@ FHoudiniInputDetails::AddWorldInputUI( // Lambda for changing the auto update state auto CheckStateChangedBoundAutoUpdates = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -4474,7 +4665,7 @@ FHoudiniInputDetails::AddWorldInputUI( bool bNewState = (NewState == ECheckBoxState::Checked); for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetWorldInputBoundSelectorAutoUpdates() == bNewState) @@ -4542,7 +4733,7 @@ FHoudiniInputDetails::AddWorldInputUI( [ SNew(STextBlock) .Text(LOCTEXT("SplineRes", "Unreal Spline Resolution")) - .ToolTipText(LOCTEXT("SplineResTooltip", "Resolution used when marshalling the Unreal Splines to HoudiniEngine.\n(step in cm betweem control points)\nSet this to 0 to only export the control points.")) + .ToolTipText(LOCTEXT("SplineResTooltip", "Resolution used when marshalling the Unreal Splines to HoudiniEngine.\n(step in cm between control points)\nSet this to 0 to only export the control points.")) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] + SHorizontalBox::Slot() @@ -4559,7 +4750,7 @@ FHoudiniInputDetails::AddWorldInputUI( .Value(MainInput->GetUnrealSplineResolution()) .OnValueChanged_Lambda([MainInput, InInputs](float Val) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -4570,7 +4761,7 @@ FHoudiniInputDetails::AddWorldInputUI( for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (CurrentInput->GetUnrealSplineResolution() == Val) @@ -4606,7 +4797,7 @@ FHoudiniInputDetails::AddWorldInputUI( //.OnClicked(FOnClicked::CreateUObject(&InParam, &UHoudiniAssetInput::OnResetSplineResolutionClicked)) .OnClicked_Lambda([MainInput, InInputs]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return FReply::Handled(); // Record a transaction for undo/redo @@ -4620,7 +4811,7 @@ FHoudiniInputDetails::AddWorldInputUI( for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (CurrentInput->GetUnrealSplineResolution() == DefaultSplineResolution) @@ -4667,7 +4858,7 @@ FReply FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName, const bool& bUseWorldInAsWorldSelector) { UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return FReply::Handled(); // There's no undo operation for button. @@ -4713,7 +4904,7 @@ FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& C for (int32 Idx = 0; Idx < NumBoundSelectors; Idx++) { AActor* Actor = MainInput->GetBoundSelectorObjectAt(Idx); - if (!Actor || Actor->IsPendingKill()) + if (!IsValid(Actor)) continue; GEditor->SelectActor(Actor, true, true); @@ -4733,7 +4924,7 @@ FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& C AActor* Actor = nullptr; UHoudiniInputActor* InputActor = Cast(CurInputObject); - if (InputActor && !InputActor->IsPendingKill()) + if (IsValid(InputActor)) { // Get the input actor Actor = InputActor->GetActor(); @@ -4742,13 +4933,13 @@ FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& C { // See if the input object is a HAC UHoudiniInputHoudiniAsset* InputHAC = Cast(CurInputObject); - if (InputHAC && !InputHAC->IsPendingKill()) + if (IsValid(InputHAC)) { Actor = InputHAC->GetHoudiniAssetComponent() ? InputHAC->GetHoudiniAssetComponent()->GetOwner() : nullptr; } } - if (!Actor || Actor->IsPendingKill()) + if (!IsValid(Actor)) continue; GEditor->SelectActor(Actor, true, true); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h index 4a025c964..b6be86b23 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,6 +28,9 @@ #include "CoreMinimal.h" +#include "Widgets/SBoxPanel.h" +#include "IDetailsView.h" + class UHoudiniInput; class UHoudiniSplineComponent; @@ -39,7 +42,7 @@ class IDetailsView; class FReply; class FAssetThumbnailPool; -class FHoudiniInputDetails : public TSharedFromThis +class FHoudiniInputDetails : public TSharedFromThis { public: static void CreateWidget( diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp index 0bc5a9ce1..bca0fc17d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,8 +24,6 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#pragma once - #include "HoudiniOutputDetails.h" #include "HoudiniEngineEditorPrivatePCH.h" @@ -70,6 +68,7 @@ #include "Engine/SkeletalMesh.h" #include "Particles/ParticleSystem.h" //#include "Landscape.h" +#include "HoudiniEngineOutputStats.h" #include "LandscapeProxy.h" #include "ScopedTransaction.h" #include "PhysicsEngine/BodySetup.h" @@ -88,16 +87,15 @@ FHoudiniOutputDetails::CreateWidget( return; UHoudiniOutput* MainOutput = InOutputs[0]; + if (!IsValid(MainOutput)) + return; // Don't create UI for editable curve. - if (!MainOutput || MainOutput->IsPendingKill() || MainOutput->IsEditableNode()) + if (MainOutput->IsEditableNode() && MainOutput->GetType() == EHoudiniOutputType::Curve) return; // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); - - // TODO - // For now we just handle Mesh Outputs + TSharedPtr AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); switch (MainOutput->GetType()) { @@ -130,9 +128,7 @@ FHoudiniOutputDetails::CreateWidget( FHoudiniOutputDetails::CreateDefaultOutputWidget(HouOutputCategory, MainOutput); break; } - } - } @@ -141,17 +137,13 @@ FHoudiniOutputDetails::CreateLandscapeOutputWidget( IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; // Go through this output's objects TMap& OutputObjects = InOutput->GetOutputObjects(); for (auto& CurrentOutputObj : OutputObjects) { - UHoudiniLandscapePtr* LandscapePointer = Cast(CurrentOutputObj.Value.OutputObject); - if (!LandscapePointer) - continue; - FHoudiniOutputObjectIdentifier& Identifier = CurrentOutputObj.Key; const FHoudiniGeoPartObject *HGPO = nullptr; for (const auto& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) @@ -162,11 +154,23 @@ FHoudiniOutputDetails::CreateLandscapeOutputWidget( HGPO = &CurHGPO; break; } - + if (!HGPO) continue; + + + if (UHoudiniLandscapePtr* LandscapePointer = Cast(CurrentOutputObj.Value.OutputObject)) + { + CreateLandscapeOutputWidget_Helper(HouOutputCategory, InOutput, *HGPO, LandscapePointer, Identifier); + } + else if (UHoudiniLandscapeEditLayer* LandscapeLayer = Cast(CurrentOutputObj.Value.OutputObject)) + { + // TODO: Create widget for landscape editlayer output + CreateLandscapeEditLayerOutputWidget_Helper(HouOutputCategory, InOutput, *HGPO, LandscapeLayer, Identifier); + } + + - CreateLandscapeOutputWidget_Helper(HouOutputCategory, InOutput, *HGPO, LandscapePointer, Identifier); } } @@ -178,29 +182,67 @@ FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( UHoudiniLandscapePtr* LandscapePointer, const FHoudiniOutputObjectIdentifier & OutputIdentifier) { - if (!LandscapePointer || LandscapePointer->IsPendingKill() || !LandscapePointer->LandscapeSoftPtr.IsValid()) + if (!IsValid(LandscapePointer) || !LandscapePointer->LandscapeSoftPtr.IsValid()) return; - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; AActor * OwnerActor = HAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) return; ALandscapeProxy * Landscape = LandscapePointer->LandscapeSoftPtr.Get(); - if (!Landscape || Landscape->IsPendingKill()) + if (!IsValid(Landscape)) return; // TODO: Get bake base name FString Label = Landscape->GetName(); + if (!LandscapePointer->EditLayerName.IsNone()) + { + Label = FString::Format(TEXT("{0} ({1})"), {Label, (LandscapePointer->EditLayerName.ToString())}); + } + EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType = LandscapePointer->BakeType; + // Create a group for this output object + IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + + // -------------------------------- + // Modified edit layers, if there are any. + // -------------------------------- + + if (!LandscapePointer->EditLayerName.IsNone()) + { + // Create label to display the edit layer name. + + LandscapeGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeOutputEditLayers", "Edit Layer")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(STextBlock) + .Text(FText::AsCultureInvariant(FText::AsCultureInvariant(LandscapePointer->EditLayerName.ToString())) ) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ]; + } + + + + // -------------------------------- + // Bake options for landscape tile + // -------------------------------- + // Get thumbnail pool for this builder IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); @@ -208,7 +250,7 @@ FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( TArray>* BakeOptionString = FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels(); // Create bake mesh name textfield. - IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + LandscapeGrp.AddWidgetRow() .NameContent() [ @@ -312,7 +354,7 @@ FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( .HAlign(HAlign_Center) .Text(LOCTEXT("Bake", "Bake")) .IsEnabled(true) - .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, OwnerActor, HGPO, Landscape, LandscapeOutputBakeType]() + .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, HGPO, Landscape, LandscapeOutputBakeType]() { FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); if (FoundOutputObject) @@ -324,8 +366,9 @@ FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( FoundOutputObject->BakeName, Landscape, OutputIdentifier, + *FoundOutputObject, HGPO, - OwnerActor->GetName(), + HAC, HAC->BakeFolder.Path, HAC->TemporaryCookFolder.Path, InOutput->GetType(), @@ -545,7 +588,343 @@ FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); } } - +} + +void FHoudiniOutputDetails::CreateLandscapeEditLayerOutputWidget_Helper(IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& HGPO, UHoudiniLandscapeEditLayer* LandscapeEditLayer, + const FHoudiniOutputObjectIdentifier& OutputIdentifier) +{ + if (!IsValid(LandscapeEditLayer) || !LandscapeEditLayer->LandscapeSoftPtr.IsValid()) + return; + + if (!IsValid(InOutput)) + return; + + UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); + if (!IsValid(HAC)) + return; + + AActor * OwnerActor = HAC->GetOwner(); + if (!IsValid(OwnerActor)) + return; + + ALandscapeProxy * Landscape = LandscapeEditLayer->LandscapeSoftPtr.Get(); + if (!IsValid(Landscape)) + return; + + const FString Label = Landscape->GetName(); + const FString LayerName = LandscapeEditLayer->LayerName; + + // Get thumbnail pool for this builder + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // Create labels to display the edit layer name. + IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + LandscapeGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeEditLayerName", "Edit Layer Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(STextBlock) + .Text(FText::AsCultureInvariant(LayerName)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ]; + + // // Create the thumbnail for the landscape output object. + // TSharedPtr< FAssetThumbnail > LandscapeThumbnail = + // MakeShareable(new FAssetThumbnail(Landscape, 64, 64, AssetThumbnailPool)); + // + // TSharedPtr< SBorder > LandscapeThumbnailBorder; + // TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + // + // LandscapeGrp.AddWidgetRow() + // .NameContent() + // [ + // SNew(SSpacer) + // .Size(FVector2D(250, 64)) + // ] + // .ValueContent() + // .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + // [ + // VerticalBox + // ]; + // + // VerticalBox->AddSlot().Padding(0, 2).AutoHeight() + // [ + // SNew(SBox).WidthOverride(175) + // [ + // SNew(SHorizontalBox) + // + SHorizontalBox::Slot() + // .Padding(0.0f, 0.0f, 2.0f, 0.0f) + // .AutoWidth() + // [ + // SAssignNew(LandscapeThumbnailBorder, SBorder) + // .Padding(5.0f) + // .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)Landscape) + // .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)Landscape) + // [ + // SNew(SBox) + // .WidthOverride(64) + // .HeightOverride(64) + // .ToolTipText(FText::FromString(Landscape->GetPathName())) + // [ + // LandscapeThumbnail->MakeThumbnailWidget() + // ] + // ] + // ] + // + // + SHorizontalBox::Slot() + // .Padding(0.0f, 4.0f, 4.0f, 4.0f) + // .VAlign(VAlign_Center) + // [ + // SNew(SBox).WidthOverride(40.0f) + // [ + // SNew(SButton) + // .VAlign(VAlign_Center) + // .HAlign(HAlign_Center) + // .Text(LOCTEXT("Bake", "Bake")) + // .IsEnabled(true) + // .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, OwnerActor, HGPO, Landscape, LandscapeOutputBakeType]() + // { + // FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + // if (FoundOutputObject) + // { + // TArray AllOutputs; + // AllOutputs.Reserve(HAC->GetNumOutputs()); + // HAC->GetOutputs(AllOutputs); + // FHoudiniOutputDetails::OnBakeOutputObject( + // FoundOutputObject->BakeName, + // Landscape, + // OutputIdentifier, + // *FoundOutputObject, + // HGPO, + // HAC, + // OwnerActor->GetName(), + // HAC->BakeFolder.Path, + // HAC->TemporaryCookFolder.Path, + // InOutput->GetType(), + // LandscapeOutputBakeType, + // AllOutputs); + // } + // + // // TODO: Remove the output landscape if the landscape bake type is Detachment? + // return FReply::Handled(); + // }) + // .ToolTipText(LOCTEXT("HoudiniLandscapeBakeButton", "Bake this landscape")) + // ] + // ] + // + SHorizontalBox::Slot() + // .Padding(0.0f, 4.0f, 4.0f, 4.0f) + // .VAlign(VAlign_Center) + // [ + // SNew(SBox).WidthOverride(120.f) + // [ + // SNew(SComboBox>) + // .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels()) + // .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels())[(uint8)LandscapeOutputBakeType]) + // .OnGenerateWidget_Lambda( + // [](TSharedPtr< FString > InItem) + // { + // return SNew(STextBlock).Text(FText::FromString(*InItem)); + // }) + // .OnSelectionChanged_Lambda( + // [LandscapePointer, InOutput](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + // { + // if (SelectType != ESelectInfo::Type::OnMouseClick) + // return; + // + // FString *NewChoiceStr = NewChoice.Get(); + // if (!NewChoiceStr) + // return; + // + // if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::Detachment)) + // { + // LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::Detachment); + // } + // else if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::BakeToImage)) + // { + // LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToImage); + // } + // else + // { + // LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToWorld); + // } + // + // FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + // }) + // [ + // SNew(STextBlock) + // .Text_Lambda([LandscapePointer]() + // { + // FString BakeTypeString = FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(LandscapePointer->GetLandscapeOutputBakeType()); + // return FText::FromString(BakeTypeString); + // }) + // .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + // ] + // ] + // ] + // ] + // ]; + // + // // Store thumbnail for this landscape. + // OutputObjectThumbnailBorders.Add(Landscape, LandscapeThumbnailBorder); + // + // // We need to add material box for each the landscape and landscape hole materials + // for (int32 MaterialIdx = 0; MaterialIdx < 2; ++MaterialIdx) + // { + // UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); + // TSharedPtr MaterialThumbnailBorder; + // TSharedPtr HorizontalBox = NULL; + // + // FString MaterialName, MaterialPathName; + // if (MaterialInterface) + // { + // MaterialName = MaterialInterface->GetName(); + // MaterialPathName = MaterialInterface->GetPathName(); + // } + // + // // Create thumbnail for this material. + // TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = + // MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); + // + // VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + // [ + // SNew(STextBlock) + // .Text(MaterialIdx == 0 ? LOCTEXT("LandscapeMaterial", "Landscape Material") : LOCTEXT("LandscapeHoleMaterial", "Landscape Hole Material")) + // .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + // ]; + // + // VerticalBox->AddSlot().Padding(0, 2) + // [ + // SNew(SAssetDropTarget) + // .OnIsAssetAcceptableForDrop(this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver) + // .OnAssetDropped(this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, Landscape, InOutput, MaterialIdx) + // [ + // SAssignNew(HorizontalBox, SHorizontalBox) + // ] + // ]; + // + // HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + // [ + // SAssignNew(MaterialThumbnailBorder, SBorder) + // .Padding(5.0f) + // .BorderImage(this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)Landscape, MaterialIdx) + // .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) + // [ + // SNew(SBox) + // .WidthOverride(64) + // .HeightOverride(64) + // .ToolTipText(FText::FromString(MaterialPathName)) + // [ + // MaterialInterfaceThumbnail->MakeThumbnailWidget() + // ] + // ] + // ]; + // + // // Store thumbnail for this landscape and material index. + // { + // TPairInitializer Pair(Landscape, MaterialIdx); + // MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); + // } + // + // // Combox Box and Button Box + // TSharedPtr ComboAndButtonBox; + // HorizontalBox->AddSlot() + // .FillWidth(1.0f) + // .Padding(0.0f, 4.0f, 4.0f, 4.0f) + // .VAlign(VAlign_Center) + // [ + // SAssignNew(ComboAndButtonBox, SVerticalBox) + // ]; + // + // // Combo row + // TSharedPtr< SComboButton > AssetComboButton; + // ComboAndButtonBox->AddSlot().FillHeight(1.0f) + // [ + // SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f) + // [ + // SAssignNew(AssetComboButton, SComboButton) + // //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) + // .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + // .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + // .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, MaterialInterface, (UObject*)Landscape, InOutput, MaterialIdx) + // .ContentPadding(2.0f) + // .ButtonContent() + // [ + // SNew(STextBlock) + // .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + // .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + // .Text(FText::FromString(MaterialName)) + // ] + // ] + // ]; + // + // // Buttons row + // TSharedPtr ButtonBox; + // ComboAndButtonBox->AddSlot().FillHeight(1.0f) + // [ + // SAssignNew(ButtonBox, SHorizontalBox) + // ]; + // + // // Add use Content Browser selection arrow + // ButtonBox->AddSlot() + // .AutoWidth() + // .Padding(2.0f, 0.0f) + // .VAlign(VAlign_Center) + // [ + // PropertyCustomizationHelpers::MakeUseSelectedButton( + // FSimpleDelegate::CreateSP( + // this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + // (UObject*)Landscape, InOutput, MaterialIdx), + // TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) + // ]; + // + // // Create tooltip. + // FFormatNamedArguments Args; + // Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + // FText MaterialTooltip = FText::Format( + // LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + // + // ButtonBox->AddSlot() + // .AutoWidth() + // .Padding(2.0f, 0.0f) + // .VAlign(VAlign_Center) + // [ + // PropertyCustomizationHelpers::MakeBrowseButton( + // FSimpleDelegate::CreateSP( + // this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), + // TAttribute< FText >(MaterialTooltip)) + // ]; + // + // ButtonBox->AddSlot() + // .AutoWidth() + // .Padding(2.0f, 0.0f) + // .VAlign(VAlign_Center) + // [ + // SNew(SButton) + // .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) + // .ButtonStyle(FEditorStyle::Get(), "NoBorder") + // .ContentPadding(0) + // .Visibility(EVisibility::Visible) + // .OnClicked( this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, Landscape, InOutput, MaterialIdx) + // [ + // SNew(SImage) + // .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + // ] + // ]; + // + // // Store combo button for this mesh and index. + // { + // TPairInitializer Pair(Landscape, MaterialIdx); + // MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); + // } + // } } void @@ -553,27 +932,13 @@ FHoudiniOutputDetails::CreateMeshOutputWidget( IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; UHoudiniAssetComponent* HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; - FString HoudiniAssetName; - if (HAC->GetOwner() && (HAC->GetOwner()->IsPendingKill())) - { - HoudiniAssetName = HAC->GetOwner()->GetName(); - } - else if (HAC->GetHoudiniAsset()) - { - HoudiniAssetName = HAC->GetHoudiniAsset()->GetName(); - } - else - { - HoudiniAssetName = HAC->GetName(); - } - // Go through this output's object int32 OutputObjIdx = 0; TMap& OutputObjects = InOutput->GetOutputObjects(); @@ -582,8 +947,8 @@ FHoudiniOutputDetails::CreateMeshOutputWidget( UStaticMesh* StaticMesh = Cast(IterObject.Value.OutputObject); UHoudiniStaticMesh* ProxyMesh = Cast(IterObject.Value.ProxyObject); - if ((!StaticMesh || StaticMesh->IsPendingKill()) - && (!ProxyMesh || ProxyMesh->IsPendingKill())) + if ((!IsValid(StaticMesh)) + && (!IsValid(ProxyMesh))) continue; FHoudiniOutputObjectIdentifier & OutputIdentifier = IterObject.Key; @@ -599,19 +964,19 @@ FHoudiniOutputDetails::CreateMeshOutputWidget( break; } - if (StaticMesh && !StaticMesh->IsPendingKill()) + if (IsValid(StaticMesh)) { bool bIsProxyMeshCurrent = IterObject.Value.bProxyIsCurrent; // If we have a static mesh, alway display its widget even if the proxy is more recent CreateStaticMeshAndMaterialWidgets( - HouOutputCategory, InOutput, StaticMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject, bIsProxyMeshCurrent); + HouOutputCategory, InOutput, StaticMesh, OutputIdentifier, HAC->BakeFolder.Path, HoudiniGeoPartObject, bIsProxyMeshCurrent); } else { // If we only have a proxy mesh, then show the proxy widget CreateProxyMeshAndMaterialWidgets( - HouOutputCategory, InOutput, ProxyMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject); + HouOutputCategory, InOutput, ProxyMesh, OutputIdentifier, HAC->BakeFolder.Path, HoudiniGeoPartObject); } } } @@ -619,7 +984,7 @@ FHoudiniOutputDetails::CreateMeshOutputWidget( void FHoudiniOutputDetails::CreateCurveOutputWidget(IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; int32 OutputObjIdx = 0; @@ -628,7 +993,7 @@ FHoudiniOutputDetails::CreateCurveOutputWidget(IDetailCategoryBuilder& HouOutput { FHoudiniOutputObject& CurrentOutputObject = IterObject.Value; USceneComponent* SplineComponent = Cast(IterObject.Value.OutputComponent); - if (!SplineComponent || SplineComponent->IsPendingKill()) + if (!IsValid(SplineComponent)) continue; FHoudiniOutputObjectIdentifier& OutputIdentifier = IterObject.Key; @@ -655,20 +1020,20 @@ FHoudiniOutputDetails::CreateCurveWidgets( FHoudiniOutputObjectIdentifier& OutputIdentifier, FHoudiniGeoPartObject& HoudiniGeoPartObject) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; // We support Unreal Spline out only for now USplineComponent* SplineOutput = Cast(SplineComponent); - if (!SplineOutput || SplineOutput->IsPendingKill()) + if (!IsValid(SplineOutput)) return; UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; AActor * OwnerActor = HAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) return; FHoudiniCurveOutputProperties* OutputProperty = &(OutputObject.CurveOutputProperty); @@ -807,7 +1172,7 @@ FHoudiniOutputDetails::CreateCurveWidgets( { // Set the curve point type locally USplineComponent* Spline = Cast(SplineComponent); - if (!Spline || Spline->IsPendingKill()) + if (!IsValid(Spline)) return; FString *NewChoiceStr = NewChoice.Get(); @@ -872,7 +1237,7 @@ FHoudiniOutputDetails::CreateCurveWidgets( SAssignNew(ClosedCheckBox, SCheckBox) .OnCheckStateChanged_Lambda([UnrealSpline, InOutput](ECheckBoxState NewState) { - if (!UnrealSpline || UnrealSpline->IsPendingKill()) + if (!IsValid(UnrealSpline)) return; UnrealSpline->SetClosedLoop(NewState == ECheckBoxState::Checked); @@ -881,7 +1246,7 @@ FHoudiniOutputDetails::CreateCurveWidgets( }) .IsChecked_Lambda([UnrealSpline]() { - if (!UnrealSpline || UnrealSpline->IsPendingKill()) + if (!IsValid(UnrealSpline)) return ECheckBoxState::Unchecked; return UnrealSpline->IsClosedLoop() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -904,7 +1269,7 @@ FHoudiniOutputDetails::CreateCurveWidgets( .Text(LOCTEXT("OutputCurveBakeButtonText", "Bake")) .IsEnabled(true) .ToolTipText(LOCTEXT("OutputCurveBakeButtonUnrealSplineTooltipText", "Bake to Unreal spline")) - .OnClicked_Lambda([InOutput, SplineComponent, OutputIdentifier, HoudiniGeoPartObject, HAC, OwnerActor, OutputCurveName]() + .OnClicked_Lambda([InOutput, SplineComponent, OutputIdentifier, HoudiniGeoPartObject, HAC, OutputCurveName, OutputObject]() { TArray AllOutputs; AllOutputs.Reserve(HAC->GetNumOutputs()); @@ -913,8 +1278,9 @@ FHoudiniOutputDetails::CreateCurveWidgets( OutputCurveName, SplineComponent, OutputIdentifier, + OutputObject, HoudiniGeoPartObject, - OwnerActor->GetName(), + HAC, HAC->BakeFolder.Path, HAC->TemporaryCookFolder.Path, InOutput->GetType(), @@ -932,12 +1298,11 @@ FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( UHoudiniOutput* InOutput, UStaticMesh * StaticMesh, FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, const FString BakeFolder, FHoudiniGeoPartObject& HoudiniGeoPartObject, const bool& bIsProxyMeshCurrent) { - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return; UHoudiniAssetComponent* OwningHAC = Cast(InOutput->GetOuter()); @@ -1035,8 +1400,8 @@ FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( } int32 NumSimpleColliders = 0; - if (StaticMesh->BodySetup && !StaticMesh->BodySetup->IsPendingKill()) - NumSimpleColliders = StaticMesh->BodySetup->AggGeom.GetElementCount(); + if (IsValid(StaticMesh->GetBodySetup())) + NumSimpleColliders = StaticMesh->GetBodySetup()->AggGeom.GetElementCount(); if(NumSimpleColliders > 0) { @@ -1057,8 +1422,19 @@ FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( if ( StaticMesh->GetNumLODs() > 1 ) MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->GetNumLODs() ) + TEXT(" LODs)"); - if ( StaticMesh->Sockets.Num() > 0 ) - MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->Sockets.Num() ) + TEXT(" sockets)"); + if (HoudiniGeoPartObject.AllMeshSockets.Num() > 0) + { + if (bIsProxyMeshCurrent) + { + // Proxy is current, show the number of sockets on the HGPO + MeshLabel += TEXT("\n(") + FString::FromInt(HoudiniGeoPartObject.AllMeshSockets.Num()) + TEXT(" sockets)"); + } + else + { + // Show the number of sockets on the SM + MeshLabel += TEXT("\n(") + FString::FromInt(StaticMesh->Sockets.Num()) + TEXT(" sockets)"); + } + } UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); StaticMeshGrp.AddWidgetRow() @@ -1115,28 +1491,32 @@ FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( .HAlign( HAlign_Center ) .Text( LOCTEXT( "Bake", "Bake" ) ) .IsEnabled(true) - .OnClicked_Lambda([BakeName, StaticMesh, OutputIdentifier, HoudiniGeoPartObject, HoudiniAssetName, BakeFolder, InOutput, OwningHAC]() + .OnClicked_Lambda([BakeName, StaticMesh, OutputIdentifier, HoudiniGeoPartObject, BakeFolder, InOutput, OwningHAC, FoundOutputObject]() { - TArray AllOutputs; - FString TempCookFolder; - if (IsValid(OwningHAC)) + if (FoundOutputObject) { - AllOutputs.Reserve(OwningHAC->GetNumOutputs()); - OwningHAC->GetOutputs(AllOutputs); + TArray AllOutputs; + FString TempCookFolder; + if (IsValid(OwningHAC)) + { + AllOutputs.Reserve(OwningHAC->GetNumOutputs()); + OwningHAC->GetOutputs(AllOutputs); - TempCookFolder = OwningHAC->TemporaryCookFolder.Path; + TempCookFolder = OwningHAC->TemporaryCookFolder.Path; + } + FHoudiniOutputDetails::OnBakeOutputObject( + BakeName, + StaticMesh, + OutputIdentifier, + *FoundOutputObject, + HoudiniGeoPartObject, + OwningHAC, + BakeFolder, + TempCookFolder, + InOutput->GetType(), + EHoudiniLandscapeOutputBakeType::InValid, + AllOutputs); } - FHoudiniOutputDetails::OnBakeOutputObject( - BakeName, - StaticMesh, - OutputIdentifier, - HoudiniGeoPartObject, - HoudiniAssetName, - BakeFolder, - TempCookFolder, - InOutput->GetType(), - EHoudiniLandscapeOutputBakeType::InValid, - AllOutputs); return FReply::Handled(); }) @@ -1160,7 +1540,7 @@ FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( OutputObjectThumbnailBorders.Add((UObject*)StaticMesh, StaticMeshThumbnailBorder); // We need to add material box for each material present in this static mesh. - auto & StaticMeshMaterials = StaticMesh->StaticMaterials; + auto & StaticMeshMaterials = StaticMesh->GetStaticMaterials(); for ( int32 MaterialIdx = 0; MaterialIdx < StaticMeshMaterials.Num(); ++MaterialIdx ) { UMaterialInterface * MaterialInterface = StaticMeshMaterials[ MaterialIdx ].MaterialInterface; @@ -1168,8 +1548,8 @@ FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( TSharedPtr< SHorizontalBox > HorizontalBox = NULL; FString MaterialName, MaterialPathName; - if ( MaterialInterface && !MaterialInterface->IsPendingKill() - && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill() ) + if ( IsValid(MaterialInterface) + && IsValid(MaterialInterface->GetOuter()) ) { MaterialName = MaterialInterface->GetName(); MaterialPathName = MaterialInterface->GetPathName(); @@ -1324,11 +1704,10 @@ FHoudiniOutputDetails::CreateProxyMeshAndMaterialWidgets( UHoudiniOutput* InOutput, UHoudiniStaticMesh * ProxyMesh, FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, const FString BakeFolder, FHoudiniGeoPartObject& HoudiniGeoPartObject) { - if (!ProxyMesh || ProxyMesh->IsPendingKill()) + if (!IsValid(ProxyMesh)) return; FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); @@ -1416,6 +1795,11 @@ FHoudiniOutputDetails::CreateProxyMeshAndMaterialWidgets( MeshLabel += TEXT("\n(templated)"); } + if (HoudiniGeoPartObject.AllMeshSockets.Num() > 0) + { + MeshLabel += TEXT("\n(") + FString::FromInt(HoudiniGeoPartObject.AllMeshSockets.Num()) + TEXT(" sockets)"); + } + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); StaticMeshGrp.AddWidgetRow() .NameContent() @@ -1489,8 +1873,8 @@ FHoudiniOutputDetails::CreateProxyMeshAndMaterialWidgets( TSharedPtr< SHorizontalBox > HorizontalBox = NULL; FString MaterialName, MaterialPathName; - if (MaterialInterface && !MaterialInterface->IsPendingKill() - && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill()) + if (IsValid(MaterialInterface) + && IsValid(MaterialInterface->GetOuter())) { MaterialName = MaterialInterface->GetName(); MaterialPathName = MaterialInterface->GetPathName(); @@ -1706,7 +2090,7 @@ FHoudiniOutputDetails::GetOutputDebugDescription(UHoudiniOutput* InOutput) UObject* OutComp = Iter.Value.OutputComponent; if (OutComp) { - OutputValStr += OutObject->GetFullName() + TEXT(" (comp)\n"); + OutputValStr += OutComp->GetFullName() + TEXT(" (comp)\n"); } } } @@ -1780,21 +2164,18 @@ FHoudiniOutputDetails::OnThumbnailDoubleClick( FReply FHoudiniOutputDetails::OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject) { - if (HoudiniAssetComponent && StaticMesh && !HoudiniAssetComponent->IsPendingKill() && !StaticMesh->IsPendingKill()) + if (IsValid(HoudiniAssetComponent) && IsValid(StaticMesh)) { FHoudiniPackageParams PackageParms; - FHoudiniEngineBakeUtils::BakeStaticMesh(HoudiniAssetComponent, GeoPartObject, StaticMesh, PackageParms); // TODO: Bake the SM - // We need to locate corresponding geo part object in component. const FHoudiniGeoPartObject& HoudiniGeoPartObject = HoudiniAssetComponent->LocateGeoPartObject(StaticMesh); // (void)FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( - // StaticMesh, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::ReplaceExisitingAssets); - + // StaticMesh, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::ReplaceExisitingAssets); } return FReply::Handled(); @@ -1815,14 +2196,16 @@ FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( int32 MaterialIdx) { FReply RetValue = FReply::Handled(); - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return RetValue; - if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) + TArray& StaticMaterials = StaticMesh->GetStaticMaterials(); + + if (!StaticMaterials.IsValidIndex(MaterialIdx)) return RetValue; // Retrieve material interface which is being replaced. - UMaterialInterface * MaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; + UMaterialInterface * MaterialInterface = StaticMaterials[MaterialIdx].MaterialInterface; if (!MaterialInterface) return RetValue; @@ -1856,7 +2239,7 @@ FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( // Replace material on static mesh. StaticMesh->Modify(); - StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = AssignMaterial; + StaticMaterials[MaterialIdx].MaterialInterface = AssignMaterial; // Replace the material on any component (SMC/ISMC) that uses the above SM // TODO: ?? Replace for all? @@ -1889,7 +2272,7 @@ FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( int32 InMaterialIdx) { FReply RetValue = FReply::Handled(); - if (!InLandscape || InLandscape->IsPendingKill()) + if (!IsValid(InLandscape)) return RetValue; // Retrieve the material interface which is being replaced. @@ -2089,19 +2472,20 @@ FHoudiniOutputDetails::OnMaterialInterfaceDropped( int32 MaterialIdx) { UMaterialInterface * MaterialInterface = Cast(InObject); - if (!MaterialInterface || MaterialInterface->IsPendingKill()) + if (!IsValid(MaterialInterface)) return; - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return; - if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) + TArray& StaticMaterials = StaticMesh->GetStaticMaterials(); + if (!StaticMaterials.IsValidIndex(MaterialIdx)) return; bool bViewportNeedsUpdate = false; // Retrieve material interface which is being replaced. - UMaterialInterface * OldMaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; + UMaterialInterface * OldMaterialInterface = StaticMaterials[MaterialIdx].MaterialInterface; if (OldMaterialInterface == MaterialInterface) return; @@ -2153,14 +2537,14 @@ FHoudiniOutputDetails::OnMaterialInterfaceDropped( // Replace material on static mesh. StaticMesh->Modify(); - StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = MaterialInterface; + StaticMaterials[MaterialIdx].MaterialInterface = MaterialInterface; // Replace the material on any component (SMC/ISMC) that uses the above SM for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) { // Only look at MeshComponents UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); - if (SMC && !SMC->IsPendingKill()) + if (IsValid(SMC)) { if (SMC->GetStaticMesh() == StaticMesh) { @@ -2171,7 +2555,7 @@ FHoudiniOutputDetails::OnMaterialInterfaceDropped( else { UStaticMesh* SM = Cast(OutputObject.Value.OutputObject); - if (SM && !SM->IsPendingKill()) + if (IsValid(SM)) { SM->Modify(); SM->SetMaterial(MaterialIdx, MaterialInterface); @@ -2201,10 +2585,10 @@ FHoudiniOutputDetails::OnMaterialInterfaceDropped( int32 MaterialIdx) { UMaterialInterface * MaterialInterface = Cast< UMaterialInterface >(InDroppedObject); - if (!MaterialInterface || MaterialInterface->IsPendingKill()) + if (!IsValid(MaterialInterface)) return; - if (!InLandscape || InLandscape->IsPendingKill()) + if (!IsValid(InLandscape)) return; bool bViewportNeedsUpdate = false; @@ -2243,7 +2627,7 @@ FHoudiniOutputDetails::OnMaterialInterfaceDropped( else { // External Material? - if (OldMaterialInterface && !OldMaterialInterface->IsPendingKill()) + if (IsValid(OldMaterialInterface)) MaterialString = OldMaterialInterface->GetName(); } } @@ -2310,13 +2694,13 @@ FHoudiniOutputDetails::OnMaterialInterfaceSelected( UObject * Object = AssetData.GetAsset(); UStaticMesh* SM = Cast(OutputObject); - if (SM && !SM->IsPendingKill()) + if (IsValid(SM)) { return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); } ALandscapeProxy* Landscape = Cast(OutputObject); - if (Landscape && !Landscape->IsPendingKill()) + if (IsValid(Landscape)) { return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); } @@ -2329,10 +2713,10 @@ FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface( UHoudiniOutput * InOutput, int32 MaterialIdx) { - if (!OutputObject || OutputObject->IsPendingKill()) + if (!IsValid(OutputObject)) return; - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; if (GEditor) @@ -2353,16 +2737,16 @@ FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface( break; } - if (Object && !Object->IsPendingKill()) + if (IsValid(Object)) { UStaticMesh* SM = Cast(OutputObject); - if (SM && !SM->IsPendingKill()) + if (IsValid(SM)) { return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); } ALandscapeProxy* Landscape = Cast(OutputObject); - if (Landscape && !Landscape->IsPendingKill()) + if (IsValid(Landscape)) { return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); } @@ -2375,7 +2759,7 @@ FHoudiniOutputDetails::CreateInstancerOutputWidget( IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; // Do not display instancer UI for one-instance instancers @@ -2512,7 +2896,7 @@ FHoudiniOutputDetails::CreateInstancerOutputWidget( for( int32 VariationIdx = 0; VariationIdx < CurInstanceOutput.VariationObjects.Num(); VariationIdx++ ) { UObject * InstancedObject = CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous(); - if ( !InstancedObject || InstancedObject->IsPendingKill() ) + if ( !IsValid(InstancedObject) ) { HOUDINI_LOG_WARNING( TEXT("Null Object found for instance variation %d"), VariationIdx ); continue; @@ -2563,15 +2947,16 @@ FHoudiniOutputDetails::CreateInstancerOutputWidget( // Add an asset drop target PickerVerticalBox->AddSlot() - .Padding( 0, 2 ) + .Padding(0, 2) .AutoHeight() [ - SNew( SAssetDropTarget ) - .OnIsAssetAcceptableForDrop( SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( - [=]( const UObject* Obj ) { - for ( auto Klass : DisallowedClasses ) + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop(SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( + [DisallowedClasses](const UObject* Obj) + { + for (auto Klass : DisallowedClasses) { - if ( Obj && Obj->IsA( Klass ) ) + if (Obj && Obj->IsA(Klass)) return false; } return true; @@ -2582,7 +2967,7 @@ FHoudiniOutputDetails::CreateInstancerOutputWidget( return SetObjectAt(CurInstanceOutput, VariationIdx, InObject); }) [ - SAssignNew( PickerHorizontalBox, SHorizontalBox ) + SAssignNew(PickerHorizontalBox, SHorizontalBox) ] ]; @@ -2602,15 +2987,17 @@ FHoudiniOutputDetails::CreateInstancerOutputWidget( ] ]; + TWeakPtr WeakVariationThumbnailBorder(VariationThumbnailBorder); VariationThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush *>::Create( - TAttribute::FGetter::CreateLambda([=]() + TAttribute::FGetter::CreateLambda([WeakVariationThumbnailBorder]() { - if (VariationThumbnailBorder.IsValid() && VariationThumbnailBorder->IsHovered()) + TSharedPtr ThumbnailBorder = WeakVariationThumbnailBorder.Pin(); + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); else return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); } - ) ) ); + ))); PickerHorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 28.0f, 0.0f, 28.0f) [ @@ -2673,6 +3060,7 @@ FHoudiniOutputDetails::CreateInstancerOutputWidget( // Create asset picker for this combo button. { + TWeakPtr WeakAssetComboButton(AssetComboButton); TArray NewAssetFactories; TSharedRef PropertyMenuAssetPicker = PropertyCustomizationHelpers::MakeAssetPickerWithMenu( FAssetData(InstancedObject), @@ -2681,15 +3069,18 @@ FHoudiniOutputDetails::CreateInstancerOutputWidget( DisallowedClasses, NewAssetFactories, FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda([&CurInstanceOutput, VariationIdx, SetObjectAt, AssetComboButton](const FAssetData& AssetData) - { - if ( AssetComboButton.IsValid() ) + FOnAssetSelected::CreateLambda( + [&CurInstanceOutput, VariationIdx, SetObjectAt, WeakAssetComboButton](const FAssetData& AssetData) { - AssetComboButton->SetIsOpen( false ); - UObject * Object = AssetData.GetAsset(); - SetObjectAt( CurInstanceOutput, VariationIdx, Object); + TSharedPtr AssetComboButtonPtr = WeakAssetComboButton.Pin(); + if (AssetComboButtonPtr.IsValid()) + { + AssetComboButtonPtr->SetIsOpen(false); + UObject * Object = AssetData.GetAsset(); + SetObjectAt(CurInstanceOutput, VariationIdx, Object); + } } - }), + ), // Nothing to do on close FSimpleDelegate::CreateLambda([](){}) ); @@ -3132,37 +3523,41 @@ FHoudiniOutputDetails::OnBakeOutputObject( const FString& InBakeName, UObject * BakedOutputObject, const FHoudiniOutputObjectIdentifier & OutputIdentifier, + const FHoudiniOutputObject& InOutputObject, const FHoudiniGeoPartObject & HGPO, - const FString & HoudiniAssetName, + const UObject* OutputOwner, const FString & BakeFolder, const FString & TempCookFolder, const EHoudiniOutputType & Type, const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, const TArray& InAllOutputs) { - if (!BakedOutputObject || BakedOutputObject->IsPendingKill()) + if (!IsValid(BakedOutputObject)) return; - FString ObjectName = InBakeName; - - // Set Object name according to priority Default Name > Attrib Custom Name > UI Custom Name - if(InBakeName.IsEmpty()) - { - if (HGPO.bHasCustomPartName) - ObjectName = HGPO.PartName; - else - ObjectName = BakedOutputObject->GetName(); - } - // Fill in the package params FHoudiniPackageParams PackageParams; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - OutputIdentifier, - BakeFolder, - ObjectName, - HoudiniAssetName); - + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + // Determine the relevant WorldContext based on the output owner + UWorld* WorldContext = OutputOwner ? OutputOwner->GetWorld() : GWorld; + const UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(OutputOwner); + check(IsValid(HAC)); + const FString HoudiniAssetName = IsValid(HAC->GetHoudiniAsset()) ? HAC->GetHoudiniAsset()->GetName() : TEXT(""); + const FString HoudiniAssetActorName = IsValid(HAC->GetOwner()) ? HAC->GetOwner()->GetName() : TEXT(""); + const bool bAutomaticallySetAttemptToLoadMissingPackages = true; + const bool bSkipObjectNameResolutionAndUseDefault = !InBakeName.IsEmpty(); // If InBakeName is set use it as is for the object name + const bool bSkipBakeFolderResolutionAndUseDefault = false; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + WorldContext, HAC, OutputIdentifier, InOutputObject, BakedOutputObject->GetName(), + PackageParams, Resolver, BakeFolder, EPackageReplaceMode::ReplaceExistingAssets, + HoudiniAssetName, HoudiniAssetActorName, + bAutomaticallySetAttemptToLoadMissingPackages, bSkipObjectNameResolutionAndUseDefault, + bSkipBakeFolderResolutionAndUseDefault); + + FHoudiniEngineOutputStats BakeStats; + switch (Type) { case EHoudiniOutputType::Mesh: @@ -3172,8 +3567,9 @@ FHoudiniOutputDetails::OnBakeOutputObject( { FDirectoryPath TempCookFolderPath; TempCookFolderPath.Path = TempCookFolder; + TMap AlreadyBakedMaterialsMap; UStaticMesh* DuplicatedMesh = FHoudiniEngineBakeUtils::BakeStaticMesh( - StaticMesh, PackageParams, InAllOutputs, TempCookFolderPath); + StaticMesh, PackageParams, InAllOutputs, TempCookFolderPath, AlreadyBakedMaterialsMap, BakeStats); } } break; @@ -3184,7 +3580,8 @@ FHoudiniOutputDetails::OnBakeOutputObject( { AActor* BakedActor; USplineComponent* BakedSplineComponent; - FHoudiniEngineBakeUtils::BakeCurve(SplineComponent, GWorld->GetCurrentLevel(), PackageParams, BakedActor, BakedSplineComponent); + FHoudiniEngineBakeUtils::BakeCurve( + SplineComponent, GWorld->GetCurrentLevel(), PackageParams, FName(PackageParams.ObjectName), BakedActor, BakedSplineComponent, BakeStats); } } break; @@ -3193,11 +3590,18 @@ FHoudiniOutputDetails::OnBakeOutputObject( ALandscapeProxy* Landscape = Cast(BakedOutputObject); if (Landscape) { - FHoudiniEngineBakeUtils::BakeHeightfield(Landscape, PackageParams, LandscapeBakeType); + FHoudiniEngineBakeUtils::BakeHeightfield(Landscape, PackageParams, LandscapeBakeType, BakeStats); } } break; } + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + } FReply @@ -3242,4 +3646,4 @@ FHoudiniOutputDetails::OnRevertBakeNameToDefault(UHoudiniOutput * InOutput, cons FoundOutputObject->BakeName = FString(); } -#undef LOCTEXT_NAMESPACE \ No newline at end of file +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h index f6d5b36d6..d79163bff 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,6 +28,10 @@ #include "CoreMinimal.h" #include "ContentBrowserDelegates.h" +#include "Materials/MaterialInterface.h" +#include "Components/Border.h" +#include "Components/ComboBox.h" + class IDetailCategoryBuilder; class FDetailWidgetRow; @@ -37,6 +41,7 @@ class FAssetThumbnailPool; class ALandscapeProxy; class USplineComponent; class UHoudiniLandscapePtr; +class UHoudiniLandscapeEditLayer; class UHoudiniStaticMesh; class UMaterialInterface; class SBorder; @@ -49,7 +54,7 @@ struct FHoudiniOutputObject; enum class EHoudiniOutputType : uint8; enum class EHoudiniLandscapeOutputBakeType : uint8; -class FHoudiniOutputDetails : public TSharedFromThis +class FHoudiniOutputDetails : public TSharedFromThis { public: void CreateWidget( @@ -69,7 +74,6 @@ class FHoudiniOutputDetails : public TSharedFromThis UHoudiniOutput* InOutput, UStaticMesh * StaticMesh, FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, const FString BakeFolder, FHoudiniGeoPartObject& HoudiniGeoPartObject, const bool& bIsProxyMeshCurrent); @@ -79,7 +83,6 @@ class FHoudiniOutputDetails : public TSharedFromThis UHoudiniOutput* InOutput, UHoudiniStaticMesh * ProxyMesh, FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, const FString BakeFolder, FHoudiniGeoPartObject& HoudiniGeoPartObject); @@ -102,6 +105,13 @@ class FHoudiniOutputDetails : public TSharedFromThis UHoudiniLandscapePtr * LandscapePointer, const FHoudiniOutputObjectIdentifier & OutputIdentifier); + void CreateLandscapeEditLayerOutputWidget_Helper( + IDetailCategoryBuilder & HouOutputCategory, + UHoudiniOutput * InOutput, + const FHoudiniGeoPartObject & HGPO, + UHoudiniLandscapeEditLayer * LandscapeEditLayer, + const FHoudiniOutputObjectIdentifier & OutputIdentifier); + void CreateInstancerOutputWidget( IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput * InOutput); @@ -127,8 +137,9 @@ class FHoudiniOutputDetails : public TSharedFromThis const FString& InBakeName, UObject * BakedOutputObject, const FHoudiniOutputObjectIdentifier & OutputIdentifier, + const FHoudiniOutputObject& InOutputObject, const FHoudiniGeoPartObject & HGPO, - const FString & HoudiniAssetName, + const UObject* OutputOwner, const FString & BakeFolder, const FString & TempCookFolder, const EHoudiniOutputType & Type, diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp index 780a6d0b0..2bad5faad 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -68,7 +68,7 @@ FHoudiniPDGDetails::CreateWidget( IDetailCategoryBuilder& HouPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return; // PDG ASSET @@ -500,7 +500,7 @@ FHoudiniPDGDetails::AddPDGAssetWidget( // we bind an auto-bake helper function here. Maybe the baking code can move to HoudiniEngine? if (InPDGAssetLink->AutoBakeDelegateHandle.IsValid()) InPDGAssetLink->OnWorkResultObjectLoaded.Remove(InPDGAssetLink->AutoBakeDelegateHandle); - InPDGAssetLink->AutoBakeDelegateHandle = InPDGAssetLink->OnWorkResultObjectLoaded.AddStatic(FHoudiniEngineBakeUtils::AutoBakePDGWorkResultObject); + InPDGAssetLink->AutoBakeDelegateHandle = InPDGAssetLink->OnWorkResultObjectLoaded.AddStatic(FHoudiniEngineBakeUtils::CheckPDGAutoBakeAfterResultObjectLoaded); // WORK ITEM STATUS { @@ -664,12 +664,12 @@ FHoudiniPDGDetails::GetWorkItemTallyValueAndColor( return false; bool bFound = false; - FWorkItemTally* TallyPtr = nullptr; + const FWorkItemTallyBase* TallyPtr = nullptr; if (bInForSelectedNode) { UTOPNode* const TOPNode = InAssetLink->GetSelectedTOPNode(); if (TOPNode && !TOPNode->bHidden) - TallyPtr = &(TOPNode->WorkItemTally); + TallyPtr = &(TOPNode->GetWorkItemTally()); } else TallyPtr = &(InAssetLink->WorkItemTally); @@ -679,25 +679,25 @@ FHoudiniPDGDetails::GetWorkItemTallyValueAndColor( if (InTallyItemString == TEXT("WAITING")) { // For now we add waiting and scheduled together, since there is no separate column for scheduled on the UI - OutValue = TallyPtr->WaitingWorkItems + TallyPtr->ScheduledWorkItems; + OutValue = TallyPtr->NumWaitingWorkItems() + TallyPtr->NumScheduledWorkItems(); OutColor = OutValue > 0 ? FLinearColor(0.0f, 1.0f, 1.0f) : FLinearColor::White; bFound = true; } else if (InTallyItemString == TEXT("COOKING")) { - OutValue = TallyPtr->CookingWorkItems; + OutValue = TallyPtr->NumCookingWorkItems(); OutColor = OutValue > 0 ? FLinearColor::Yellow : FLinearColor::White; bFound = true; } else if (InTallyItemString == TEXT("COOKED")) { - OutValue = TallyPtr->CookedWorkItems; + OutValue = TallyPtr->NumCookedWorkItems(); OutColor = OutValue > 0 ? FLinearColor::Green : FLinearColor::White; bFound = true; } else if (InTallyItemString == TEXT("FAILED")) { - OutValue = TallyPtr->ErroredWorkItems; + OutValue = TallyPtr->NumErroredWorkItems(); OutColor = OutValue > 0 ? FLinearColor::Red : FLinearColor::White; bFound = true; } @@ -1300,7 +1300,7 @@ FHoudiniPDGDetails::AddTOPNetworkWidget( else { // Delete and unload the result objects and actors now - TOPNet->DeleteWorkResultOutputObjects(); + TOPNet->DeleteAllWorkResultObjectOutputs(); } } } @@ -1953,7 +1953,7 @@ FHoudiniPDGDetails::AddTOPNodeWidget( else { // Delete and unload the result objects and actors now - TOPNode->DeleteWorkResultOutputObjects(); + TOPNode->DeleteAllWorkResultObjectOutputs(); } } } @@ -2028,7 +2028,7 @@ FHoudiniPDGDetails::RefreshPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) void FHoudiniPDGDetails::RefreshUI(UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return; // Update the workitem stats @@ -2041,7 +2041,7 @@ FHoudiniPDGDetails::RefreshUI(UHoudiniPDGAssetLink* InPDGAssetLink, const bool& void FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return; FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(InPDGCategory, InPDGAssetLink, HOUDINI_ENGINE_UI_SECTION_PDG_BAKE); @@ -2058,7 +2058,7 @@ FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, // if (InPDGAssetLink->bIsReplace) // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(InPDGAssetLink); // else - FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(InPDGAssetLink); + FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->PDGBakeSelectionOption, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); } break; @@ -2067,7 +2067,7 @@ FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, // if (InPDGAssetLink->bIsReplace) // FHoudiniEngineBakeUtils::ReplaceWithBlueprint(InPDGAssetLink); // else - FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(InPDGAssetLink); + FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(InPDGAssetLink, InPDGAssetLink->PDGBakeSelectionOption, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); } break; // @@ -2462,7 +2462,11 @@ FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, [ SNew(STextBlock) .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) - .ToolTipText(LOCTEXT("HoudiniEnginePDGBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini PDG Asset when baking.")) + .ToolTipText(LOCTEXT( + "HoudiniEnginePDGBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini PDG Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) ] ]; @@ -2476,7 +2480,11 @@ FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, [ SNew(SEditableTextBox) .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT("HoudiniEnginePDGBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini PDG Asset when baking.")) + .ToolTipText(LOCTEXT( + "HoudiniEnginePDGBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini PDG Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) .Text_Lambda([InPDGAssetLink](){ return FText::FromString(InPDGAssetLink->BakeFolder.Path); }) @@ -2571,13 +2579,13 @@ FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, ] .IsChecked_Lambda([InPDGAssetLink]() { - return InPDGAssetLink->bBakeAfterWorkResultObjectLoaded ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + return InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) { const bool bNewState = (NewState == ECheckBoxState::Checked); - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return; // Record a transaction for undo/redo @@ -2587,11 +2595,11 @@ FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, InPDGAssetLink); InPDGAssetLink->Modify(); - InPDGAssetLink->bBakeAfterWorkResultObjectLoaded = bNewState; + InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded = bNewState; // Notify that we have changed the property FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeAfterWorkResultObjectLoaded), InPDGAssetLink); + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeAfterAllWorkResultObjectsLoaded), InPDGAssetLink); }) ] ]; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h index 75c79b1e2..00a8b3629 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,7 +55,7 @@ struct FTextAndTooltip int32 Value; }; -class FHoudiniPDGDetails : public TSharedFromThis +class FHoudiniPDGDetails : public TSharedFromThis { public: diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp index e88a2121e..8676dad4c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -83,10 +83,14 @@ #include "SAssetDropTarget.h" #include "AssetThumbnail.h" +#include "Sound/SoundBase.h" +#include "Engine/SkeletalMesh.h" +#include "Particles/ParticleSystem.h" +#include "FoliageType.h" + #include "HoudiniInputDetails.h" #include "Framework/SlateDelegates.h" -//#include "Programs/UnrealLightmass/Private/ImportExport/3DVisualizer.h" #include "Templates/SharedPointer.h" #define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE @@ -281,7 +285,7 @@ SCustomizedBox::SetHoudiniParameter(TArray& InParams) return; UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; @@ -319,7 +323,7 @@ SCustomizedBox::SetHoudiniParameter(TArray& InParams) case EHoudiniParameterType::ColorRamp: { UHoudiniParameterRampColor * ColorRampParameter = Cast(MainParam); - if (!ColorRampParameter || ColorRampParameter->IsPendingKill()) + if (!IsValid(ColorRampParameter)) return; MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP; @@ -392,7 +396,7 @@ SCustomizedBox::SetHoudiniParameter(TArray& InParams) case EHoudiniParameterType::FloatRamp: { UHoudiniParameterRampFloat * FloatRampParameter = Cast(MainParam); - if (!FloatRampParameter || FloatRampParameter->IsPendingKill()) + if (!IsValid(FloatRampParameter)) return; MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP; @@ -424,12 +428,12 @@ SCustomizedBox::SetHoudiniParameter(TArray& InParams) { UHoudiniParameterOperatorPath* InputParam = Cast(MainParam); - if (!InputParam || InputParam->IsPendingKill() || !InputParam->HoudiniInput.IsValid()) + if (!IsValid(InputParam) || !InputParam->HoudiniInput.IsValid()) break; UHoudiniInput* Input = InputParam->HoudiniInput.Get(); - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) break; @@ -659,7 +663,7 @@ float SCustomizedBox::AddIndentation(UHoudiniParameter* InParam, TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return 0.0f; bool bIsMainParmSimpleFolder = false; @@ -1855,7 +1859,7 @@ bool FHoudiniParameterDetails::CastParameters( for (auto CurrentParam : InParams) { T* CastedParam = Cast(CurrentParam); - if (CastedParam && !CastedParam->IsPendingKill()) + if (IsValid(CastedParam)) OutCastedParams.Add(CastedParam); } @@ -1870,7 +1874,7 @@ FHoudiniParameterDetails::CreateWidget(IDetailCategoryBuilder & HouParameterCate return; UHoudiniParameter* InParam = InParams[0]; - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return; // The directory won't parse if parameter ids are -1 @@ -1878,17 +1882,27 @@ FHoudiniParameterDetails::CreateWidget(IDetailCategoryBuilder & HouParameterCate if (InParam->GetParmId() < 0) return; - // This parameter is a part of the last float ramp. if (CurrentRampFloat) { - CreateWidgetFloatRamp(HouParameterCategory, InParams); - return; + // CreateWidgetFloatRamp(HouParameterCategory, InParams); + // If this parameter is a part of the last float ramp, skip it + if (InParam->GetIsChildOfMultiParm() && InParam->GetParentParmId() == CurrentRampFloat->GetParmId()) + return; + + // This parameter is not part of the last float ramp (we've passed all of its points/instances), reset + // CurrentRampFloat in order to continue normal processing of parameters + CurrentRampFloat = nullptr; } - // This parameter is a part of the last float ramp. if (CurrentRampColor) { - CreateWidgetColorRamp(HouParameterCategory, InParams); - return; + // CreateWidgetColorRamp(HouParameterCategory, InParams); + // if this parameter is a part of the last color ramp, skip it + if (InParam->GetIsChildOfMultiParm() && InParam->GetParentParmId() == CurrentRampColor->GetParmId()) + return; + + // This parameter is not part of the last color ramp (we've passed all of its points/instances), reset + // CurrentRampColor in order to continue normal processing of parameters + CurrentRampColor = nullptr; } switch (InParam->GetParameterType()) @@ -2044,7 +2058,7 @@ FHoudiniParameterDetails::CreateNameWidget(FDetailWidgetRow* Row, TArrayIsPendingKill()) + if (!IsValid(MainParam)) return; if (!Row) @@ -2114,7 +2128,7 @@ FHoudiniParameterDetails::CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, return; UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; FString ParameterLabelStr = MainParam->GetParameterLabel(); @@ -2203,7 +2217,7 @@ FHoudiniParameterDetails::CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, auto IsAutoUpdateChecked = [MainParam]() { - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return ECheckBoxState::Unchecked; return MainParam->IsAutoUpdate() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -2332,7 +2346,7 @@ FHoudiniParameterDetails::CreateNestedRow(IDetailCategoryBuilder & HouParameterC UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return nullptr; // Created row for the current parameter (if there is not a row created, do not show the parameter). @@ -2350,7 +2364,7 @@ FHoudiniParameterDetails::CreateNestedRow(IDetailCategoryBuilder & HouParameterC return nullptr; UHoudiniParameterFolderList* ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); - if (!ParentFolderList || ParentFolderList->IsPendingKill()) + if (!IsValid(ParentFolderList)) return nullptr; // This should not happen ParentMultiParmId = ParentFolderList->GetParentParmId(); @@ -2462,7 +2476,7 @@ FHoudiniParameterDetails::HandleUnsupportedParmType(IDetailCategoryBuilder & Hou return; UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; CreateNestedRow(HouParameterCategory, (TArray)InParams); @@ -2481,7 +2495,7 @@ FHoudiniParameterDetails::CreateWidgetFloat( return; UHoudiniParameterFloat* MainParam = FloatParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -2606,11 +2620,11 @@ FHoudiniParameterDetails::CreateWidgetFloat( // Ignore the swapping if that parameter has the noswap tag bool SwapVector3 = !MainParam->GetNoSwap(); - auto ChangeFloatValueUniformly = [FloatParams, ChangeFloatValueAt](const float & Val) + auto ChangeFloatValueUniformly = [FloatParams, ChangeFloatValueAt](const float& Val, const bool& bDoChange) { - ChangeFloatValueAt(Val, 0, true, FloatParams); - ChangeFloatValueAt(Val, 1, true, FloatParams); - ChangeFloatValueAt(Val, 2, true, FloatParams); + ChangeFloatValueAt(Val, 0, bDoChange, FloatParams); + ChangeFloatValueAt(Val, 1, bDoChange, FloatParams); + ChangeFloatValueAt(Val, 2, bDoChange, FloatParams); }; VerticalBox->AddSlot().Padding(2, 2, 5, 2) @@ -2621,30 +2635,54 @@ FHoudiniParameterDetails::CreateWidgetFloat( [ SNew(SVectorInputBox) .bColorAxisLabels(true) + .AllowSpin(true) .X(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, 0))) .Y(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 2 : 1))) .Z(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 1 : 2))) .OnXCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) { if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val); + ChangeFloatValueUniformly(Val, true); else ChangeFloatValueAt( Val, 0, true, FloatParams); }) .OnYCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) { if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val); + ChangeFloatValueUniformly(Val, true); else ChangeFloatValueAt( Val, SwapVector3 ? 2 : 1, true, FloatParams); }) .OnZCommitted_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) { if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val); + ChangeFloatValueUniformly(Val, true); else ChangeFloatValueAt( Val, SwapVector3 ? 1 : 2, true, FloatParams); }) + .OnXChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, false); + else + ChangeFloatValueAt(Val, 0, false, FloatParams); + }) + .OnYChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, false); + else + ChangeFloatValueAt(Val, SwapVector3 ? 2 : 1, false, FloatParams); + }) + .OnZChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, false); + else + ChangeFloatValueAt(Val, SwapVector3 ? 1 : 2, false, FloatParams); + }) + .OnBeginSliderMovement_Lambda([SliderBegin, FloatParams]() { SliderBegin(FloatParams); }) + .OnEndSliderMovement_Lambda([SliderEnd, FloatParams](const float NewValue) { SliderEnd(FloatParams); }) .TypeInterface(paramTypeInterface) ] + SHorizontalBox::Slot() @@ -2666,12 +2704,12 @@ FHoudiniParameterDetails::CreateWidgetFloat( ] .OnClicked_Lambda([FloatParams, MainParam]() { - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return FReply::Handled(); for (auto & CurParam : FloatParams) { - if (!CurParam || CurParam->IsPendingKill()) + if (!IsValid(CurParam)) continue; CurParam->SwitchUniformLock(); @@ -2734,10 +2772,10 @@ FHoudiniParameterDetails::CreateWidgetFloat( .MaxSliderValue(MainParam->GetUIMax()) .Value(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, Idx))) - .OnValueChanged_Lambda([=](float Val) { ChangeFloatValueAt(Val, Idx, false, FloatParams); }) - .OnValueCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) { ChangeFloatValueAt(Val, Idx, true, FloatParams); }) - .OnBeginSliderMovement_Lambda([=]() { SliderBegin(FloatParams); }) - .OnEndSliderMovement_Lambda([=](const float NewValue) { SliderEnd(FloatParams); }) + .OnValueChanged_Lambda([ChangeFloatValueAt, Idx, FloatParams](float Val) { ChangeFloatValueAt(Val, Idx, false, FloatParams); }) + .OnValueCommitted_Lambda([ChangeFloatValueAt, Idx, FloatParams](float Val, ETextCommit::Type TextCommitType) { ChangeFloatValueAt(Val, Idx, true, FloatParams); }) + .OnBeginSliderMovement_Lambda([SliderBegin, FloatParams]() { SliderBegin(FloatParams); }) + .OnEndSliderMovement_Lambda([SliderEnd, FloatParams](const float NewValue) { SliderEnd(FloatParams); }) .SliderExponent(MainParam->IsLogarithmic() ?8.0f : 1.0f) .TypeInterface(paramTypeInterface) ] @@ -2789,7 +2827,7 @@ FHoudiniParameterDetails::CreateWidgetInt(IDetailCategoryBuilder & HouParameterC return; UHoudiniParameterInt* MainParam = IntParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -2964,7 +3002,7 @@ FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParame return; UHoudiniParameterString* MainParam = StringParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -3054,8 +3092,8 @@ FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParame if (bIsUnrealRef) { - TSharedPtr< SEditableTextBox > EditableTextBox; - TSharedPtr< SHorizontalBox > HorizontalBox; + TSharedPtr EditableTextBox; + TSharedPtr HorizontalBox; VerticalBox->AddSlot().Padding(2, 2, 5, 2) [ SNew(SAssetDropTarget) @@ -3119,15 +3157,19 @@ FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParame ] ]; - ThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush * >::Create( - TAttribute< const FSlateBrush * >::FGetter::CreateLambda([ThumbnailBorder]() - { - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); - } - ))); + TWeakPtr WeakThumbnailBorder(ThumbnailBorder); + ThumbnailBorder->SetBorderImage(TAttribute::Create( + TAttribute::FGetter::CreateLambda( + [WeakThumbnailBorder]() + { + TSharedPtr ThumbnailBorderPtr = WeakThumbnailBorder.Pin(); + if (ThumbnailBorderPtr.IsValid() && ThumbnailBorderPtr->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); + } + ) + )); FText MeshNameText = FText::GetEmpty(); //if (InputObject) @@ -3163,10 +3205,33 @@ FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParame ] ]; - StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda([UnrealRefClass, StaticMeshComboButton, ChangeStringValueAt, Idx, StringParams]() + TWeakPtr WeakStaticMeshComboButton(StaticMeshComboButton); + StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( + [UnrealRefClass, WeakStaticMeshComboButton, ChangeStringValueAt, Idx, StringParams]() { TArray AllowedClasses; - AllowedClasses.Add(UnrealRefClass); + if (UnrealRefClass != UObject::StaticClass()) + { + // Use the class specified by the user + AllowedClasses.Add(UnrealRefClass); + } + else + { + // Using UObject would list way too many assets, and take a long time to open the menu, + // so we need to reestrict the classes a bit + AllowedClasses.Add(UStaticMesh::StaticClass()); + AllowedClasses.Add(UHoudiniAsset::StaticClass()); + AllowedClasses.Add(USkeletalMesh::StaticClass()); + AllowedClasses.Add(UBlueprint::StaticClass()); + AllowedClasses.Add(UMaterialInterface::StaticClass()); + AllowedClasses.Add(UTexture::StaticClass()); + AllowedClasses.Add(ULevel::StaticClass()); + AllowedClasses.Add(UStreamableRenderAsset::StaticClass()); + AllowedClasses.Add(USoundBase::StaticClass()); + AllowedClasses.Add(UParticleSystem::StaticClass()); + AllowedClasses.Add(UFoliageType::StaticClass()); + } + TArray NewAssetFactories; return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( FAssetData(nullptr), @@ -3174,17 +3239,23 @@ FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParame AllowedClasses, NewAssetFactories, FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda([StaticMeshComboButton, ChangeStringValueAt, Idx, StringParams](const FAssetData & AssetData) - { - UObject * Object = AssetData.GetAsset(); + FOnAssetSelected::CreateLambda( + [WeakStaticMeshComboButton, ChangeStringValueAt, Idx, StringParams](const FAssetData & AssetData) + { + TSharedPtr StaticMeshComboButtonPtr = WeakStaticMeshComboButton.Pin(); + if (StaticMeshComboButtonPtr.IsValid()) + { + StaticMeshComboButtonPtr->SetIsOpen(false); - // Get the asset reference string for this object - // !! Accept null objects to allow clearing the asset picker !! - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(Object); + UObject * Object = AssetData.GetAsset(); + // Get the asset reference string for this object + // !! Accept null objects to allow clearing the asset picker !! + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(Object); - StaticMeshComboButton->SetIsOpen(false); - ChangeStringValueAt(ReferenceStr, Object, Idx, true, StringParams); - }), + ChangeStringValueAt(ReferenceStr, Object, Idx, true, StringParams); + } + } + ), FSimpleDelegate::CreateLambda([]() {})); }) ); @@ -3325,10 +3396,9 @@ FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParamete return; UHoudiniParameterColor* MainParam = ColorParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; - - // Create a new detail row + // Create a new detail row FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); if (!Row) @@ -3347,50 +3417,45 @@ FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParamete SAssignNew(ColorBlock, SColorBlock) .Color(MainParam->GetColorValue()) .ShowBackgroundForAlpha(bHasAlpha) - .OnMouseButtonDown(FPointerEventHandler::CreateLambda( - [MainParam, ColorParams, ColorBlock, bHasAlpha](const FGeometry & MyGeometry, const FPointerEvent & MouseEvent) - { - if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) - return FReply::Unhandled(); - - FColorPickerArgs PickerArgs; - PickerArgs.ParentWidget = ColorBlock; - PickerArgs.bUseAlpha = bHasAlpha; - PickerArgs.DisplayGamma = TAttribute< float >::Create( - TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); - PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([&](FLinearColor InColor) { - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterColorChange", "Houdini Parameter Color: Changing value"), - MainParam->GetOuter(), true); - - bool bChanged = false; - for (auto & Param : ColorParams) + .OnMouseButtonDown_Lambda([this, ColorParams, MainParam, bHasAlpha](const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) + { + FColorPickerArgs PickerArgs; + PickerArgs.ParentWidget = FSlateApplication::Get().GetActiveTopLevelWindow(); + PickerArgs.bUseAlpha = bHasAlpha; + PickerArgs.DisplayGamma = TAttribute< float >::Create( + TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); + PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([&](FLinearColor InColor) { - if (!Param) - continue; + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterColorChange", "Houdini Parameter Color: Changing value"), + MainParam->GetOuter(), true); - Param->Modify(); - if (Param->SetColorValue(InColor)) + bool bChanged = false; + for (auto & Param : ColorParams) { - Param->MarkChanged(true); - bChanged = true; - } - } + if (!Param) + continue; - // cancel the transaction if there is actually no value changed - if (!bChanged) - { - Transaction.Cancel(); - } + Param->Modify(); + if (Param->SetColorValue(InColor)) + { + Param->MarkChanged(true); + bChanged = true; + } + } - }); - PickerArgs.InitialColorOverride = MainParam->GetColorValue(); - PickerArgs.bOnlyRefreshOnOk = true; - OpenColorPicker(PickerArgs); - return FReply::Handled(); - })) + // cancel the transaction if there is actually no value changed + if (!bChanged) + { + Transaction.Cancel(); + } + }); + PickerArgs.InitialColorOverride = MainParam->GetColorValue(); + PickerArgs.bOnlyRefreshOnOk = true; + OpenColorPicker(PickerArgs); + return FReply::Handled(); + }) ]; Row->ValueWidget.Widget = VerticalBox; @@ -3409,7 +3474,7 @@ FHoudiniParameterDetails::CreateWidgetButton(IDetailCategoryBuilder & HouParamet return; UHoudiniParameterButton* MainParam = ButtonParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -3466,7 +3531,7 @@ FHoudiniParameterDetails::CreateWidgetButtonStrip(IDetailCategoryBuilder & HouPa return; UHoudiniParameterButtonStrip* MainParam = ButtonStripParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -3492,7 +3557,7 @@ FHoudiniParameterDetails::CreateWidgetButtonStrip(IDetailCategoryBuilder & HouPa for (auto & NextParam : ButtonStripParams) { - if (!NextParam || NextParam->IsPendingKill()) + if (!IsValid(NextParam)) continue; if (!NextParam->Values.IsValidIndex(Idx)) @@ -3565,7 +3630,7 @@ FHoudiniParameterDetails::CreateWidgetLabel(IDetailCategoryBuilder & HouParamete return; UHoudiniParameterLabel* MainParam = LabelParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -3609,7 +3674,7 @@ FHoudiniParameterDetails::CreateWidgetToggle(IDetailCategoryBuilder & HouParamet return; UHoudiniParameterToggle* MainParam = ToggleParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -3708,7 +3773,7 @@ void FHoudiniParameterDetails::CreateWidgetFile(IDetailCategoryBuilder & HouPara return; UHoudiniParameterFile* MainParam = FileParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -3728,6 +3793,17 @@ void FHoudiniParameterDetails::CreateWidgetFile(IDetailCategoryBuilder & HouPara FString BrowseWidgetDirectory = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN); + TMap& Tags = MainParam->GetTags(); + if (Tags.Contains(HAPI_PARAM_TAG_DEFAULT_DIR)) + { + if(!Tags[HAPI_PARAM_TAG_DEFAULT_DIR].IsEmpty()) + { + FString DefaultDir = Tags[HAPI_PARAM_TAG_DEFAULT_DIR]; + if(FPaths::DirectoryExists(DefaultDir)) + BrowseWidgetDirectory = DefaultDir; + } + } + auto UpdateCheckRelativePath = [MainParam](const FString & PickedPath) { UHoudiniAssetComponent* HoudiniAssetComponent = Cast(MainParam->GetOuter()); @@ -3741,9 +3817,9 @@ void FHoudiniParameterDetails::CreateWidgetFile(IDetailCategoryBuilder & HouPara } // Check if the path is relative to the asset - if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) + if (IsValid(HoudiniAssetComponent)) { - if (HoudiniAssetComponent->HoudiniAsset && !HoudiniAssetComponent->HoudiniAsset->IsPendingKill()) + if (IsValid(HoudiniAssetComponent->HoudiniAsset)) { FString AssetFilePath = FPaths::GetPath(HoudiniAssetComponent->HoudiniAsset->AssetFileName); if (FPaths::FileExists(AssetFilePath)) @@ -3853,7 +3929,7 @@ FHoudiniParameterDetails::CreateWidgetChoice(IDetailCategoryBuilder & HouParamet return; UHoudiniParameterChoice* MainParam = ChoiceParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -3904,9 +3980,9 @@ FHoudiniParameterDetails::CreateWidgetChoice(IDetailCategoryBuilder & HouParamet MainParam->UpdateChoiceLabelsPtr(); TArray>* OptionSource = MainParam->GetChoiceLabelsPtr(); TSharedPtr IntialSelec; - if (OptionSource && OptionSource->IsValidIndex(MainParam->GetIntValue())) + if (OptionSource && OptionSource->IsValidIndex(MainParam->GetIntValueIndex())) { - IntialSelec = (*OptionSource)[MainParam->GetIntValue()]; + IntialSelec = (*OptionSource)[MainParam->GetIntValueIndex()]; } TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); @@ -3978,7 +4054,7 @@ FHoudiniParameterDetails::CreateWidgetOperatorPath(IDetailCategoryBuilder & HouP return; UHoudiniParameterOperatorPath* MainParam = OperatorPathParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; UHoudiniInput* MainInput = MainParam->HoudiniInput.Get(); @@ -3993,7 +4069,7 @@ FHoudiniParameterDetails::CreateWidgetOperatorPath(IDetailCategoryBuilder & HouP for (int LinkedIdx = 1; LinkedIdx < OperatorPathParams.Num(); LinkedIdx++) { UHoudiniInput* LinkedInput = OperatorPathParams[LinkedIdx]->HoudiniInput.Get(); - if (!LinkedInput || LinkedInput->IsPendingKill()) + if (!IsValid(LinkedInput)) continue; // Linked params should match the main param! If not try to find one that matches @@ -4023,154 +4099,168 @@ FHoudiniParameterDetails::CreateWidgetFloatRamp(IDetailCategoryBuilder & HouPara return; UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Parsing a float ramp: 1->(2->3->4)*->5 // - switch (MainParam->GetParameterType()) - { - //*****State 1: Float Ramp*****// - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* FloatRampParameter = Cast(MainParam); - if (FloatRampParameter) - { - CurrentRampFloat = FloatRampParameter; - CurrentRampFloatPointsArray.Empty(); - - CurrentRampParameterList = InParams; - - FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); - CurrentRampRow = Row; - } - break; - } - - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* FloatParameter = Cast(MainParam); - if (FloatParameter) - { - bool bCreateNewPoint = true; - if (CurrentRampFloatPointsArray.Num() > 0) - { - UHoudiniParameterRampFloatPoint* LastPtInArr = CurrentRampFloatPointsArray.Last(); - if (LastPtInArr && !LastPtInArr->ValueParentParm) - bCreateNewPoint = false; - } - - //*****State 2: Float Parameter (position)*****// - if (bCreateNewPoint) - { - UHoudiniParameterRampFloatPoint* NewRampFloatPoint = nullptr; - - int32 PointIndex = CurrentRampFloatPointsArray.Num(); - if (CurrentRampFloat->Points.IsValidIndex(PointIndex)) - { - - // TODO: We should reuse existing point objects, if they exist. Currently - // this causes results in unexpected behaviour in other parts of this detail code. - // Give this code a bit of an overhaul at some point. - // NewRampFloatPoint = CurrentRampFloat->Points[PointIndex]; - } - - if (!NewRampFloatPoint) - { - // Create a new float ramp point, and add its pointer to the current float points buffer array. - NewRampFloatPoint = NewObject< UHoudiniParameterRampFloatPoint >(CurrentRampFloat, FName(), CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); - - } - - CurrentRampFloatPointsArray.Add(NewRampFloatPoint); - - if (FloatParameter->GetNumberOfValues() <= 0) - return; - // Set the float ramp point's position parent parm, and value - NewRampFloatPoint->PositionParentParm = FloatParameter; - NewRampFloatPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); - } - //*****State 3: Float Parameter (value)*****// - else - { - if (FloatParameter->GetNumberOfValues() <= 0) - return; - // Get the last point in the buffer array - if (CurrentRampFloatPointsArray.Num() > 0) - { - // Set the last inserted float ramp point's float parent parm, and value - UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); - LastAddedFloatRampPoint->ValueParentParm = FloatParameter; - LastAddedFloatRampPoint->SetValue(FloatParameter->GetValuesPtr()[0]); - } - } - } - - break; + if (!IsValid(MainParam)) + return; + + // TODO: remove this once we have verified that updating the Points and CachedPoints arrays in + // TODO: HoudiniParameterTranslator::BuildAllParameters() (via RampParam->UpdatePointsArray()) is sufficient. + // // Parsing a float ramp: 1->(2->3->4)*->5 // + // switch (MainParam->GetParameterType()) + // { + // //*****State 1: Float Ramp*****// + // case EHoudiniParameterType::FloatRamp: + // { + // UHoudiniParameterRampFloat* FloatRampParameter = Cast(MainParam); + // if (FloatRampParameter) + // { + // CurrentRampFloat = FloatRampParameter; + // CurrentRampFloatPointsArray.Empty(); + // + // CurrentRampParameterList = InParams; + // + // FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + // CurrentRampRow = Row; + // } + // break; + // } + // + // case EHoudiniParameterType::Float: + // { + // UHoudiniParameterFloat* FloatParameter = Cast(MainParam); + // if (FloatParameter) + // { + // bool bCreateNewPoint = true; + // if (CurrentRampFloatPointsArray.Num() > 0) + // { + // UHoudiniParameterRampFloatPoint* LastPtInArr = CurrentRampFloatPointsArray.Last(); + // if (LastPtInArr && !LastPtInArr->ValueParentParm) + // bCreateNewPoint = false; + // } + // + // //*****State 2: Float Parameter (position)*****// + // if (bCreateNewPoint) + // { + // UHoudiniParameterRampFloatPoint* NewRampFloatPoint = nullptr; + // + // int32 PointIndex = CurrentRampFloatPointsArray.Num(); + // if (CurrentRampFloat->Points.IsValidIndex(PointIndex)) + // { + // + // // TODO: We should reuse existing point objects, if they exist. Currently + // // this causes results in unexpected behaviour in other parts of this detail code. + // // Give this code a bit of an overhaul at some point. + // // NewRampFloatPoint = CurrentRampFloat->Points[PointIndex]; + // } + // + // if (!NewRampFloatPoint) + // { + // // Create a new float ramp point, and add its pointer to the current float points buffer array. + // NewRampFloatPoint = NewObject< UHoudiniParameterRampFloatPoint >(CurrentRampFloat, FName(), CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); + // + // } + // + // CurrentRampFloatPointsArray.Add(NewRampFloatPoint); + // + // if (FloatParameter->GetNumberOfValues() <= 0) + // return; + // // Set the float ramp point's position parent parm, and value + // NewRampFloatPoint->PositionParentParm = FloatParameter; + // NewRampFloatPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); + // } + // //*****State 3: Float Parameter (value)*****// + // else + // { + // if (FloatParameter->GetNumberOfValues() <= 0) + // return; + // // Get the last point in the buffer array + // if (CurrentRampFloatPointsArray.Num() > 0) + // { + // // Set the last inserted float ramp point's float parent parm, and value + // UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); + // LastAddedFloatRampPoint->ValueParentParm = FloatParameter; + // LastAddedFloatRampPoint->SetValue(FloatParameter->GetValuesPtr()[0]); + // } + // } + // } + // + // break; + // } + // //*****State 4: Choice parameter*****// + // case EHoudiniParameterType::IntChoice: + // { + // UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); + // if (ChoiceParameter && CurrentRampFloatPointsArray.Num() > 0) + // { + // // Set the last inserted float ramp point's interpolation parent parm, and value + // UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); + // + // LastAddedFloatRampPoint->InterpolationParentParm = ChoiceParameter; + // LastAddedFloatRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); + // + // // Set the index of this point in the multi parm. + // LastAddedFloatRampPoint->InstanceIndex = CurrentRampFloatPointsArray.Num() - 1; + // } + // + // + // //*****State 5: All ramp points have been parsed, finish!*****// + // if (CurrentRampFloatPointsArray.Num() >= (int32)CurrentRampFloat->MultiParmInstanceCount) + // { + // CurrentRampFloatPointsArray.Sort([](const UHoudiniParameterRampFloatPoint& P1, const UHoudiniParameterRampFloatPoint& P2) { + // return P1.Position < P2.Position; + // }); + // + // CurrentRampFloat->Points = CurrentRampFloatPointsArray; + // + // // Not caching, points are synced, update cached points + // if (!CurrentRampFloat->bCaching) + // { + // const int32 NumPoints = CurrentRampFloat->Points.Num(); + // CurrentRampFloat->CachedPoints.SetNum(NumPoints); + // for (int32 i = 0; i < NumPoints; ++i) + // { + // UHoudiniParameterRampFloatPoint* FromPoint = CurrentRampFloat->Points[i]; + // UHoudiniParameterRampFloatPoint* ToPoint = CurrentRampFloat->CachedPoints[i]; + // ToPoint = nullptr; + // check(FromPoint) + // if (!ToPoint) + // { + // ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampFloat, RF_NoFlags, CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); + // } + // else + // { + // ToPoint->CopyStateFrom(FromPoint, true); + // } + // CurrentRampFloat->CachedPoints[i] = ToPoint; + // } + // } + // + // CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampFloat, CurrentRampParameterList); + // + // CurrentRampFloat->SetDefaultValues(); + // + // CurrentRampFloat = nullptr; + // CurrentRampRow = nullptr; + // } + // + // break; + // } + // + // default: + // break; + // } + + //*****Float Ramp*****// + if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat* FloatRampParameter = Cast(MainParam); + if (FloatRampParameter) + { + CurrentRampFloat = FloatRampParameter; + FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + CreateWidgetRampPoints(HouParameterCategory, Row, FloatRampParameter, InParams); + //FloatRampParameter->SetDefaultValues(); } - //*****State 4: Choice parameter*****// - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); - if (ChoiceParameter && CurrentRampFloatPointsArray.Num() > 0) - { - // Set the last inserted float ramp point's interpolation parent parm, and value - UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); - - LastAddedFloatRampPoint->InterpolationParentParm = ChoiceParameter; - LastAddedFloatRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); - - // Set the index of this point in the multi parm. - LastAddedFloatRampPoint->InstanceIndex = CurrentRampFloatPointsArray.Num() - 1; - } - - - //*****State 5: All ramp points have been parsed, finish!*****// - if (CurrentRampFloatPointsArray.Num() >= (int32)CurrentRampFloat->MultiParmInstanceCount) - { - CurrentRampFloatPointsArray.Sort([](const UHoudiniParameterRampFloatPoint& P1, const UHoudiniParameterRampFloatPoint& P2) { - return P1.Position < P2.Position; - }); - - CurrentRampFloat->Points = CurrentRampFloatPointsArray; - - // Not caching, points are synced, update cached points - if (!CurrentRampFloat->bCaching) - { - const int32 NumPoints = CurrentRampFloat->Points.Num(); - CurrentRampFloat->CachedPoints.SetNum(NumPoints); - for (int32 i = 0; i < NumPoints; ++i) - { - UHoudiniParameterRampFloatPoint* FromPoint = CurrentRampFloat->Points[i]; - UHoudiniParameterRampFloatPoint* ToPoint = CurrentRampFloat->CachedPoints[i]; - ToPoint = nullptr; - check(FromPoint) - if (!ToPoint) - { - ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampFloat, RF_NoFlags, CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); - } - else - { - ToPoint->CopyStateFrom(FromPoint, true); - } - CurrentRampFloat->CachedPoints[i] = ToPoint; - } - } - - CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampFloat, CurrentRampParameterList); - - CurrentRampFloat->SetDefaultValues(); - - CurrentRampFloat = nullptr; - CurrentRampRow = nullptr; - } - - break; - } - - default: - break; } - } void @@ -4180,139 +4270,154 @@ FHoudiniParameterDetails::CreateWidgetColorRamp(IDetailCategoryBuilder & HouPara return; UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Parsing a color ramp: 1->(2->3->4)*->5 // - switch (MainParam->GetParameterType()) + if (!IsValid(MainParam)) + return; + + // TODO: remove this once we have verified that updating the Points and CachedPoints arrays in + // TODO: HoudiniParameterTranslator::BuildAllParameters() (via RampParam->UpdatePointsArray()) is sufficient. + // // Parsing a color ramp: 1->(2->3->4)*->5 // + // switch (MainParam->GetParameterType()) + // { + // //*****State 1: Color Ramp*****// + // case EHoudiniParameterType::ColorRamp: + // { + // UHoudiniParameterRampColor* RampColor = Cast(MainParam); + // if (RampColor) + // { + // CurrentRampColor = RampColor; + // CurrentRampColorPointsArray.Empty(); + // + // CurrentRampParameterList = InParams; + // + // FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + // CurrentRampRow = Row; + // } + // + // break; + // } + // //*****State 2: Float parameter*****// + // case EHoudiniParameterType::Float: + // { + // UHoudiniParameterFloat* FloatParameter = Cast(MainParam); + // if (FloatParameter) + // { + // // Create a new color ramp point, and add its pointer to the current color points buffer array. + // UHoudiniParameterRampColorPoint* NewRampColorPoint = nullptr; + // int32 PointIndex = CurrentRampColorPointsArray.Num(); + // + // if (CurrentRampColor->Points.IsValidIndex(PointIndex)) + // { + // NewRampColorPoint = CurrentRampColor->Points[PointIndex]; + // } + // + // if (!NewRampColorPoint) + // { + // NewRampColorPoint = NewObject< UHoudiniParameterRampColorPoint >(CurrentRampColor, FName(), CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); + // } + // + // CurrentRampColorPointsArray.Add(NewRampColorPoint); + // + // if (FloatParameter->GetNumberOfValues() <= 0) + // return; + // // Set the color ramp point's position parent parm, and value + // NewRampColorPoint->PositionParentParm = FloatParameter; + // NewRampColorPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); + // } + // + // break; + // } + // //*****State 3: Color parameter*****// + // case EHoudiniParameterType::Color: + // { + // UHoudiniParameterColor* ColorParameter = Cast(MainParam); + // if (ColorParameter && CurrentRampColorPointsArray.Num() > 0) + // { + // // Set the last inserted color ramp point's color parent parm, and value + // UHoudiniParameterRampColorPoint* LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); + // LastAddedColorRampPoint->ValueParentParm = ColorParameter; + // LastAddedColorRampPoint->SetValue(ColorParameter->GetColorValue()); + // } + // + // break; + // } + // //*****State 4: Choice Parameter*****// + // case EHoudiniParameterType::IntChoice: + // { + // UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); + // if (ChoiceParameter) + // { + // // Set the last inserted color ramp point's interpolation parent parm, and value + // UHoudiniParameterRampColorPoint*& LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); + // + // LastAddedColorRampPoint->InterpolationParentParm = ChoiceParameter; + // LastAddedColorRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); + // + // // Set the index of this point in the multi parm. + // LastAddedColorRampPoint->InstanceIndex = CurrentRampColorPointsArray.Num() - 1; + // } + // + // + // //*****State 5: All ramp points have been parsed, finish!*****// + // if (CurrentRampColorPointsArray.Num() >= (int32)CurrentRampColor->MultiParmInstanceCount) + // { + // CurrentRampColorPointsArray.Sort([](const UHoudiniParameterRampColorPoint& P1, const UHoudiniParameterRampColorPoint& P2) + // { + // return P1.Position < P2.Position; + // }); + // + // CurrentRampColor->Points = CurrentRampColorPointsArray; + // + // // Not caching, points are synced, update cached points + // + // if (!CurrentRampColor->bCaching) + // { + // const int32 NumPoints = CurrentRampColor->Points.Num(); + // CurrentRampColor->CachedPoints.SetNum(NumPoints); + // + // for (int32 i = 0; i < NumPoints; ++i) + // { + // UHoudiniParameterRampColorPoint* FromPoint = CurrentRampColor->Points[i]; + // UHoudiniParameterRampColorPoint* ToPoint = CurrentRampColor->CachedPoints[i]; + // + // if (!ToPoint) + // { + // ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampColor, RF_NoFlags, CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); + // } + // else + // { + // ToPoint->CopyStateFrom(FromPoint, true); + // } + // CurrentRampColor->CachedPoints[i] = ToPoint; + // } + // } + // + // + // CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampColor, CurrentRampParameterList); + // + // CurrentRampColor->SetDefaultValues(); + // + // CurrentRampColor = nullptr; + // CurrentRampRow = nullptr; + // } + // + // break; + // } + // + // default: + // break; + // } + + //*****Color Ramp*****// + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) { - //*****State 1: Color Ramp*****// - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* RampColor = Cast(MainParam); - if (RampColor) - { - CurrentRampColor = RampColor; - CurrentRampColorPointsArray.Empty(); - - CurrentRampParameterList = InParams; - - FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); - CurrentRampRow = Row; - } - - break; - } - //*****State 2: Float parameter*****// - case EHoudiniParameterType::Float: + UHoudiniParameterRampColor* RampColor = Cast(MainParam); + if (RampColor) { - UHoudiniParameterFloat* FloatParameter = Cast(MainParam); - if (FloatParameter) - { - // Create a new color ramp point, and add its pointer to the current color points buffer array. - UHoudiniParameterRampColorPoint* NewRampColorPoint = nullptr; - int32 PointIndex = CurrentRampColorPointsArray.Num(); - - if (CurrentRampColor->Points.IsValidIndex(PointIndex)) - { - NewRampColorPoint = CurrentRampColor->Points[PointIndex]; - } - - if (!NewRampColorPoint) - { - NewRampColorPoint = NewObject< UHoudiniParameterRampColorPoint >(CurrentRampColor, FName(), CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); - } - - CurrentRampColorPointsArray.Add(NewRampColorPoint); - - if (FloatParameter->GetNumberOfValues() <= 0) - return; - // Set the color ramp point's position parent parm, and value - NewRampColorPoint->PositionParentParm = FloatParameter; - NewRampColorPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); - } - - break; + CurrentRampColor = RampColor; + FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + CreateWidgetRampPoints(HouParameterCategory, Row, RampColor, InParams); + //RampColor->SetDefaultValues(); } - //*****State 3: Color parameter*****// - case EHoudiniParameterType::Color: - { - UHoudiniParameterColor* ColorParameter = Cast(MainParam); - if (ColorParameter && CurrentRampColorPointsArray.Num() > 0) - { - // Set the last inserted color ramp point's color parent parm, and value - UHoudiniParameterRampColorPoint* LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); - LastAddedColorRampPoint->ValueParentParm = ColorParameter; - LastAddedColorRampPoint->SetValue(ColorParameter->GetColorValue()); - } - - break; - } - //*****State 4: Choice Parameter*****// - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); - if (ChoiceParameter) - { - // Set the last inserted color ramp point's interpolation parent parm, and value - UHoudiniParameterRampColorPoint*& LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); - - LastAddedColorRampPoint->InterpolationParentParm = ChoiceParameter; - LastAddedColorRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); - - // Set the index of this point in the multi parm. - LastAddedColorRampPoint->InstanceIndex = CurrentRampColorPointsArray.Num() - 1; - } - - - //*****State 5: All ramp points have been parsed, finish!*****// - if (CurrentRampColorPointsArray.Num() >= (int32)CurrentRampColor->MultiParmInstanceCount) - { - CurrentRampColorPointsArray.Sort([](const UHoudiniParameterRampColorPoint& P1, const UHoudiniParameterRampColorPoint& P2) - { - return P1.Position < P2.Position; - }); - - CurrentRampColor->Points = CurrentRampColorPointsArray; - - // Not caching, points are synced, update cached points - - if (!CurrentRampColor->bCaching) - { - const int32 NumPoints = CurrentRampColor->Points.Num(); - CurrentRampColor->CachedPoints.SetNum(NumPoints); - - for (int32 i = 0; i < NumPoints; ++i) - { - UHoudiniParameterRampColorPoint* FromPoint = CurrentRampColor->Points[i]; - UHoudiniParameterRampColorPoint* ToPoint = CurrentRampColor->CachedPoints[i]; - - if (!ToPoint) - { - ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampColor, RF_NoFlags, CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); - } - else - { - ToPoint->CopyStateFrom(FromPoint, true); - } - CurrentRampColor->CachedPoints[i] = ToPoint; - } - } - - - CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampColor, CurrentRampParameterList); - - CurrentRampColor->SetDefaultValues(); - - CurrentRampColor = nullptr; - CurrentRampRow = nullptr; - } - - break; - } - - default: - break; } } @@ -4366,7 +4471,7 @@ FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & H ColorGradientEditor->EnableToolTipForceField(true); CurrentRampParameterColorCurve = NewObject( - MainParam, UHoudiniColorRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); + GetTransientPackage(), UHoudiniColorRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); if (!CurrentRampParameterColorCurve) return nullptr; @@ -4393,6 +4498,7 @@ FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & H RichCurve.Keys.Empty(); } ColorGradientEditor->SetCurveOwner(CurrentRampParameterColorCurve); + CreatedColorGradientEditors.Add(ColorGradientEditor); } else if(MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) { @@ -4438,7 +4544,7 @@ FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & H FloatCurveEditor->EnableToolTipForceField(true); CurrentRampParameterFloatCurve = NewObject( - MainParam, UHoudiniFloatRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); + GetTransientPackage(), UHoudiniFloatRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); if (!CurrentRampParameterFloatCurve) return nullptr; @@ -4457,6 +4563,7 @@ FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & H FloatCurveEditor->HoudiniFloatRampCurve = CurrentRampParameterFloatCurve; FloatCurveEditor->SetCurveOwner(CurrentRampParameterFloatCurve, true); + CreatedFloatCurveEditors.Add(FloatCurveEditor); } Row->ValueWidget.Widget = VerticalBox; @@ -4477,7 +4584,7 @@ FHoudiniParameterDetails::CreateWidgetRampPoints(IDetailCategoryBuilder& Categor return; UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; UHoudiniParameterRampFloat * MainFloatRampParameter = nullptr; @@ -5510,7 +5617,7 @@ FHoudiniParameterDetails::CreateWidgetFolderList(IDetailCategoryBuilder & HouPar return; UHoudiniParameterFolderList* MainParam = FolderListParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Add this folder list to the folder map @@ -5562,11 +5669,12 @@ FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParamet return; UHoudiniParameterFolder* MainParam = FolderParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; - if (!CurrentFolderList || CurrentFolderList->IsPendingKill()) // This should not happen + if (!IsValid(CurrentFolderList)) // This should not happen return; + // If a folder is invisible, its children won't be listed by HAPI. // So just reduce FolderListSize by 1, reduce the child counter of its parent folder by 1 if necessary, // and prune the stack in such case. @@ -5579,7 +5687,7 @@ FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParamet if (FolderStack.Num() > 1) { TArray &ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) + if (ParentFolderQueue.Num() > 0 && IsValid(ParentFolderQueue[0])) ParentFolderQueue[0]->GetChildCounter() -= 1; } @@ -5662,7 +5770,7 @@ FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParamet // Case 2-1: The folder is in another folder. if (FolderStack.Num() > 1 && CurrentFolderListSize > 0) { - TArray & MyFolderQueue = FolderStack.Last(); + TArray & MyFolderQueue = FolderStack.Last(); TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; if (ParentFolderQueue.Num() <= 0) //This should happen @@ -5729,8 +5837,7 @@ FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParamet } } } - - + CurrentFolderListSize -= 1; // Prune the stack if finished parsing current folderlist @@ -5739,7 +5846,7 @@ FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParamet if (FolderStack.Num() > 1 && !MainParam->IsDirectChildOfMultiParm()) { TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) + if (ParentFolderQueue.Num() > 0 && IsValid(ParentFolderQueue[0])) ParentFolderQueue[0]->GetChildCounter() -= 1; } @@ -5764,7 +5871,7 @@ FHoudiniParameterDetails::CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArr UHoudiniParameterFolder* MainParam = FolderParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; TSharedPtr VerticalBox; @@ -5823,22 +5930,27 @@ FHoudiniParameterDetails::CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArr .Text(LabelText) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ]; - + + TWeakPtr WeakExpanderArrow(ExpanderArrow); ExpanderImage->SetImage( TAttribute::Create( - TAttribute::FGetter::CreateLambda([=]() { - FName ResourceName; - if(MainParam->IsExpanded()) - { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } + TAttribute::FGetter::CreateLambda([MainParam, WeakExpanderArrow]() + { + FName ResourceName; + TSharedPtr ExpanderArrowPtr = WeakExpanderArrow.Pin(); + if (MainParam->IsExpanded()) + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } - return FEditorStyle::GetBrush(ResourceName); - }))); + return FEditorStyle::GetBrush(ResourceName); + } + ) + )); if(MainParam->GetFolderType() == EHoudiniFolderParameterType::Simple) ExpanderArrow->SetEnabled(false); @@ -5847,7 +5959,7 @@ FHoudiniParameterDetails::CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArr void FHoudiniParameterDetails::CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InFolder, const bool& bIsShown) { - if (!InFolder || InFolder->IsPendingKill() || !CurrentFolderList) + if (!IsValid(InFolder) || !CurrentFolderList) return; if (FolderStack.Num() <= 0) // error state @@ -5897,7 +6009,7 @@ void FHoudiniParameterDetails::CreateWidgetTab(IDetailCategoryBuilder & HouParam for (auto & CurTab : CurrentTabs) { - if (!CurTab || CurTab->IsPendingKill()) + if (!IsValid(CurTab)) continue; CurTab->SetIsContentShown(CurTab->IsChosen()); @@ -5978,7 +6090,7 @@ FHoudiniParameterDetails::CreateWidgetMultiParm(IDetailCategoryBuilder & HouPara return; UHoudiniParameterMultiParm* MainParam = MultiParmParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Add current multiparm parameter to AllmultiParms map @@ -6004,22 +6116,8 @@ FHoudiniParameterDetails::CreateWidgetMultiParm(IDetailCategoryBuilder & HouPara if (InValue < 0) return; - int32 ChangesCount = FMath::Abs(MainParam->MultiParmInstanceLastModifyArray.Num() - InValue); - - if (MainParam->MultiParmInstanceLastModifyArray.Num() > InValue) - { - for (int32 Idx = 0; Idx < ChangesCount; ++Idx) - MainParam->RemoveElement(-1); - - MainParam->MarkChanged(true); - } - else if (MainParam->MultiParmInstanceLastModifyArray.Num() < InValue) - { - for (int32 Idx = 0; Idx < ChangesCount; ++Idx) - MainParam->InsertElement(); - + if (MainParam->SetNumElements(InValue)) MainParam->MarkChanged(true); - } }; // Add multiparm UI. @@ -6166,7 +6264,7 @@ FHoudiniParameterDetails::CreateWidgetMultiParmObjectButtons(TSharedPtrIsPendingKill()) + if (!IsValid(MainParam)) return; if (!HorizontalBox || !AllMultiParms.Contains(MainParam->GetParentParmId()) || !MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) @@ -6307,7 +6405,7 @@ FHoudiniParameterDetails::PruneStack() for (int32 QueueItr = CurrentQueue.Num() - 1; QueueItr >= 0; --QueueItr) { UHoudiniParameterFolder * CurrentFolder = CurrentQueue[QueueItr]; - if (!CurrentFolder || CurrentFolder->IsPendingKill()) + if (!IsValid(CurrentFolder)) continue; if (CurrentFolder->GetChildCounter() == 0) @@ -6326,7 +6424,7 @@ FHoudiniParameterDetails::PruneStack() FText FHoudiniParameterDetails::GetParameterTooltip(UHoudiniParameter* InParam) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return FText(); // Tooltip starts with Label (name) @@ -6634,7 +6732,7 @@ FHoudiniParameterDetails::SyncCachedColorRampPoints(UHoudiniParameterRampColor* void FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return; UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( @@ -6653,7 +6751,7 @@ FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(UHoudiniParameterR void FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return; UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( @@ -6673,7 +6771,7 @@ void FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return; UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( @@ -6695,7 +6793,7 @@ void FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return; UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( @@ -7030,9 +7128,7 @@ FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(UHoud // Use Synced points if the MainParam is on auto update mode // Use Cached points if the Mainparam is on manual update mode - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - if (Param->IsAutoUpdate() && bCookingEnabled) { TArray & Points = Param->Points; @@ -7097,7 +7193,6 @@ FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(UHoud for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) { UHoudiniParameterRampColorPoint*& NextMainPoint = MainPoints[PointInsertIdx]; - if (!NextMainPoint) continue; @@ -7111,7 +7206,6 @@ FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(UHoud for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) { UHoudiniParameterRampColorPoint*& NextPoint = Points[PointDeleteIdx]; - if (!NextPoint) continue; @@ -7181,7 +7275,6 @@ FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(UHoud continue; UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampColorPoint::StaticClass()); - if (!NewCachedPoint) continue; @@ -7204,11 +7297,11 @@ FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(UHoud } } -// Check recussively if a parameter hits the end of a visible tabs +// Check recursively if a parameter hits the end of a visible tabs void FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return; // When the paramId is invalid, the directory won't parse. @@ -7224,7 +7317,7 @@ FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameter if (ParmType == EHoudiniParameterType::MultiParm) { - UHoudiniParameterMultiParm * InMultiParm = Cast(InParam); + UHoudiniParameterMultiParm* InMultiParm = Cast(InParam); if (!InMultiParm) return; @@ -7236,12 +7329,12 @@ FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameter UHoudiniParameter* CurParam = InParam; while (AllFoldersAndFolderLists.Contains(ParentParamId) || AllMultiParms.Contains(ParentParamId)) - { - // The parent is a multiparm + { if (AllMultiParms.Contains(ParentParamId)) { + // The parent is a multiparm UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentParamId]; - if (!ParentMultiParm || ParentMultiParm->IsPendingKill()) + if (!IsValid(ParentMultiParm)) return; if (ParentMultiParm->MultiParmInstanceCount * ParentMultiParm->MultiParmInstanceLength - 1 == CurParam->GetChildIndex()) @@ -7257,27 +7350,26 @@ FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameter return; } } - // The parent is a folder or folderlist else { + // The parent is a folder or folderlist UHoudiniParameter* ParentFolderParam = AllFoldersAndFolderLists[ParentParamId]; CurParam = ParentFolderParam; - if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) + if (!IsValid(ParentFolderParam)) return; - // The parent is a folder if (ParentFolderParam->GetParameterType() == EHoudiniParameterType::Folder) { - ParentParamId = ParentFolderParam->GetParentParmId(); - + // The parent is a folder + ParentParamId = ParentFolderParam->GetParentParmId(); continue; } - // The parent is a folderlist else { + // The parent is a folderlist UHoudiniParameterFolderList* ParentFolderList = Cast(ParentFolderParam); - if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) + if (!IsValid(ParentFolderList)) return; if (ParentFolderList->IsTabMenu() && ParentFolderList->IsTabsShown() && ParentFolderList->IsTabParseFinished() && DividerLinePositions.Num() > 0) @@ -7285,7 +7377,7 @@ FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameter if (!CurrentTabEndingRow) CreateTabEndingRow(HouParameterCategory); - if (CurrentTabEndingRow) + if (CurrentTabEndingRow && CurrentTabEndingRow->DividerLinePositions.Num() > 0) { CurrentTabEndingRow->EndingDividerLinePositions.Add(DividerLinePositions.Top()); CurrentTabEndingRow->DividerLinePositions.Pop(); @@ -7299,9 +7391,7 @@ FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameter { return; } - } - } } } diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h index 98a392de9..73cf64d3f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -65,6 +65,9 @@ class UHoudiniParameterOperatorPath; class UHoudiniParameterRampColorPoint; class UHoudiniParameterRampFloatPoint; +class UHoudiniColorRampCurve; +class UHoudiniFloatRampCurve; + class IDetailCategoryBuilder; class FDetailWidgetRow; class SHorizontalBox; @@ -297,7 +300,7 @@ class UHoudiniColorRampCurve : public UCurveLinearColor //class FHoudiniParameterDetails : public TSharedFromThis, public TNumericUnitTypeInterface, public TNumericUnitTypeInterface -class FHoudiniParameterDetails : public TSharedFromThis +class FHoudiniParameterDetails : public TSharedFromThis { public: void CreateWidget( @@ -428,6 +431,10 @@ class FHoudiniParameterDetails : public TSharedFromThis CreatedFloatRampCurves; TArray CreatedColorRampCurves; + // The curve editors reference the UHoudini*Curves as "CurveOwners" as raw (non UObject) pointers, so we have + // to set their owners to null here before we destroy the Created*RampCuvers + TArray> CreatedColorGradientEditors; + TArray> CreatedFloatCurveEditors; private: // The parameter directory is flattened with BFS inside of DFS. diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp new file mode 100644 index 000000000..49169caa9 --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp @@ -0,0 +1,274 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniPublicAPI.h" + +#include "HoudiniAsset.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniPublicAPIAssetWrapper.h" +#include "HoudiniPublicAPIInputTypes.h" +#include "HoudiniEngineCommands.h" + +UHoudiniPublicAPI::UHoudiniPublicAPI() +{ +} + +bool +UHoudiniPublicAPI::IsSessionValid_Implementation() const +{ + return FHoudiniEngineCommands::IsSessionValid(); +} + +void +UHoudiniPublicAPI::CreateSession_Implementation() +{ + if (!IsSessionValid()) + FHoudiniEngineCommands::CreateSession(); +} + +void +UHoudiniPublicAPI::StopSession_Implementation() +{ + if (IsSessionValid()) + FHoudiniEngineCommands::StopSession(); +} + +void +UHoudiniPublicAPI::RestartSession_Implementation() +{ + if (IsSessionValid()) + FHoudiniEngineCommands::RestartSession(); + else + FHoudiniEngineCommands::CreateSession(); +} + +UHoudiniPublicAPIAssetWrapper* +UHoudiniPublicAPI::InstantiateAsset_Implementation( + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + UObject* InWorldContextObject, + ULevel* InSpawnInLevelOverride, + const bool bInEnableAutoCook, + const bool bInEnableAutoBake, + const FString& InBakeDirectoryPath, + const EHoudiniEngineBakeOption InBakeMethod, + const bool bInRemoveOutputAfterBake, + const bool bInRecenterBakedActors, + const bool bInReplacePreviousBake) +{ + if (!IsValid(InHoudiniAsset) || !(InHoudiniAsset->AssetImportData)) + { + SetErrorMessage(TEXT("InHoudiniAsset is invalid or does not have AssetImportData.")); + return nullptr; + } + + // Create wrapper for asset instance + UHoudiniPublicAPIAssetWrapper* Wrapper = UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper(this); + + if (Wrapper) + { + // Enable/disable error logging based on the API setting + Wrapper->SetLoggingErrorsEnabled(IsLoggingErrors()); + + if (!InstantiateAssetWithExistingWrapper( + Wrapper, + InHoudiniAsset, + InInstantiateAt, + InWorldContextObject, + InSpawnInLevelOverride, + bInEnableAutoCook, + bInEnableAutoBake, + InBakeDirectoryPath, + InBakeMethod, + bInRemoveOutputAfterBake, + bInRecenterBakedActors, + bInReplacePreviousBake)) + { + // failed to instantiate asset, return null + return nullptr; + } + } + + return Wrapper; +} + +bool +UHoudiniPublicAPI::InstantiateAssetWithExistingWrapper_Implementation( + UHoudiniPublicAPIAssetWrapper* InWrapper, + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + UObject* InWorldContextObject, + ULevel* InSpawnInLevelOverride, + const bool bInEnableAutoCook, + const bool bInEnableAutoBake, + const FString& InBakeDirectoryPath, + const EHoudiniEngineBakeOption InBakeMethod, + const bool bInRemoveOutputAfterBake, + const bool bInRecenterBakedActors, + const bool bInReplacePreviousBake) +{ + if (!IsValid(InWrapper)) + { + SetErrorMessage(TEXT("InWrapper is not valid.")); + return false; + } + + if (!IsValid(InHoudiniAsset) || !(InHoudiniAsset->AssetImportData)) + { + SetErrorMessage(TEXT("InHoudiniAsset is invalid or does not have AssetImportData.")); + return false; + } + + UWorld* OverrideWorldToSpawnIn = IsValid(InWorldContextObject) ? InWorldContextObject->GetWorld() : nullptr; + AActor* HoudiniAssetActor = FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(InHoudiniAsset, InInstantiateAt, OverrideWorldToSpawnIn, InSpawnInLevelOverride); + if (!IsValid(HoudiniAssetActor)) + { + // Determine the path of what would have been the owning level/world of the actor for error logging purposes + const FString LevelPath = IsValid(InSpawnInLevelOverride) ? InSpawnInLevelOverride->GetPathName() : FString(); + const FString WorldPath = IsValid(OverrideWorldToSpawnIn) ? OverrideWorldToSpawnIn->GetPathName() : FString(); + const FString OwnerPath = LevelPath.IsEmpty() ? WorldPath : LevelPath; + SetErrorMessage(FString::Printf(TEXT("Failed to spawn a AHoudiniAssetActor in %s."), *OwnerPath)); + return false; + } + + // Wrap the instantiated asset + if (!InWrapper->WrapHoudiniAssetObject(HoudiniAssetActor)) + { + FString WrapperError; + InWrapper->GetLastErrorMessage(WrapperError); + SetErrorMessage(FString::Printf( + TEXT("Failed to wrap '%s': %s."), *(HoudiniAssetActor->GetName()), *WrapperError)); + return false; + } + + InWrapper->SetAutoCookingEnabled(bInEnableAutoCook); + + FDirectoryPath BakeDirectoryPath; + BakeDirectoryPath.Path = InBakeDirectoryPath; + InWrapper->SetBakeFolder(BakeDirectoryPath); + InWrapper->SetBakeMethod(InBakeMethod); + InWrapper->SetRemoveOutputAfterBake(bInRemoveOutputAfterBake); + InWrapper->SetRecenterBakedActors(bInRecenterBakedActors); + InWrapper->SetReplacePreviousBake(bInReplacePreviousBake); + InWrapper->SetAutoBakeEnabled(bInEnableAutoBake); + + return true; +} + +bool +UHoudiniPublicAPI::IsAssetCookingPaused_Implementation() const +{ + return FHoudiniEngineCommands::IsAssetCookingPaused(); +} + +void +UHoudiniPublicAPI::PauseAssetCooking_Implementation() +{ + if (!IsAssetCookingPaused()) + FHoudiniEngineCommands::PauseAssetCooking(); +} + +void +UHoudiniPublicAPI::ResumeAssetCooking_Implementation() +{ + if (IsAssetCookingPaused()) + FHoudiniEngineCommands::PauseAssetCooking(); +} + +UHoudiniPublicAPIInput* +UHoudiniPublicAPI::CreateEmptyInput_Implementation(TSubclassOf InInputClass, UObject* InOuter) +{ + UObject* Outer = InOuter; + if (!IsValid(Outer)) + Outer = this; + UHoudiniPublicAPIInput* const NewInput = NewObject(Outer, InInputClass.Get()); + if (!IsValid(NewInput)) + { + SetErrorMessage(TEXT("Could not create a new valid UHoudiniPublicAPIInput instance.")); + } + else if (Outer) + { + // Enable/disable error logging based on the outer's setting (if it is a sub-class of UHoudiniPublicAPIObjectBase) + const bool bShouldLogErrors = Outer->IsA() ? + Cast(Outer)->IsLoggingErrors() : IsLoggingErrors(); + NewInput->SetLoggingErrorsEnabled(bShouldLogErrors); + } + + return NewInput; +} + +EHoudiniPublicAPIRampInterpolationType +UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(const EHoudiniRampInterpolationType InInterpolationType) +{ + switch (InInterpolationType) + { + case EHoudiniRampInterpolationType::InValid: + return EHoudiniPublicAPIRampInterpolationType::InValid; + case EHoudiniRampInterpolationType::BEZIER: + return EHoudiniPublicAPIRampInterpolationType::BEZIER; + case EHoudiniRampInterpolationType::LINEAR: + return EHoudiniPublicAPIRampInterpolationType::LINEAR; + case EHoudiniRampInterpolationType::BSPLINE: + return EHoudiniPublicAPIRampInterpolationType::BSPLINE; + case EHoudiniRampInterpolationType::HERMITE: + return EHoudiniPublicAPIRampInterpolationType::HERMITE; + case EHoudiniRampInterpolationType::CONSTANT: + return EHoudiniPublicAPIRampInterpolationType::CONSTANT; + case EHoudiniRampInterpolationType::CATMULL_ROM: + return EHoudiniPublicAPIRampInterpolationType::CATMULL_ROM; + case EHoudiniRampInterpolationType::MONOTONE_CUBIC: + return EHoudiniPublicAPIRampInterpolationType::MONOTONE_CUBIC; + } + + return EHoudiniPublicAPIRampInterpolationType::InValid; +} + +EHoudiniRampInterpolationType +UHoudiniPublicAPI::ToHoudiniRampInterpolationType(const EHoudiniPublicAPIRampInterpolationType InInterpolationType) +{ + switch (InInterpolationType) + { + case EHoudiniPublicAPIRampInterpolationType::InValid: + return EHoudiniRampInterpolationType::InValid; + case EHoudiniPublicAPIRampInterpolationType::BEZIER: + return EHoudiniRampInterpolationType::BEZIER; + case EHoudiniPublicAPIRampInterpolationType::LINEAR: + return EHoudiniRampInterpolationType::LINEAR; + case EHoudiniPublicAPIRampInterpolationType::BSPLINE: + return EHoudiniRampInterpolationType::BSPLINE; + case EHoudiniPublicAPIRampInterpolationType::HERMITE: + return EHoudiniRampInterpolationType::HERMITE; + case EHoudiniPublicAPIRampInterpolationType::CONSTANT: + return EHoudiniRampInterpolationType::CONSTANT; + case EHoudiniPublicAPIRampInterpolationType::CATMULL_ROM: + return EHoudiniRampInterpolationType::CATMULL_ROM; + case EHoudiniPublicAPIRampInterpolationType::MONOTONE_CUBIC: + return EHoudiniRampInterpolationType::MONOTONE_CUBIC; + } + + return EHoudiniRampInterpolationType::InValid; +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp new file mode 100644 index 000000000..942928325 --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp @@ -0,0 +1,4195 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniPublicAPIAssetWrapper.h" + +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniOutputDetails.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniPDGManager.h" +#include "HoudiniPublicAPI.h" +#include "HoudiniPublicAPIBlueprintLib.h" +#include "HoudiniPublicAPIInputTypes.h" + +FHoudiniPublicAPIRampPoint::FHoudiniPublicAPIRampPoint() + : Position(0) + , Interpolation(EHoudiniPublicAPIRampInterpolationType::LINEAR) +{ +} + +FHoudiniPublicAPIRampPoint::FHoudiniPublicAPIRampPoint( + const float InPosition, + const EHoudiniPublicAPIRampInterpolationType InInterpolation) + : Position(InPosition) + , Interpolation(InInterpolation) +{ + +} + +FHoudiniPublicAPIFloatRampPoint::FHoudiniPublicAPIFloatRampPoint() + : Value(0) +{ +} + +FHoudiniPublicAPIFloatRampPoint::FHoudiniPublicAPIFloatRampPoint( + const float InPosition, + const float InValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation) + : FHoudiniPublicAPIRampPoint(InPosition, InInterpolation) + , Value(InValue) +{ + +} + +FHoudiniPublicAPIColorRampPoint::FHoudiniPublicAPIColorRampPoint() + : Value(FLinearColor::Black) +{ +} + +FHoudiniPublicAPIColorRampPoint::FHoudiniPublicAPIColorRampPoint( + const float InPosition, + const FLinearColor& InValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation) + : FHoudiniPublicAPIRampPoint(InPosition, InInterpolation) + , Value(InValue) +{ + +} + +FHoudiniParameterTuple::FHoudiniParameterTuple() + : BoolValues() + , Int32Values() + , FloatValues() + , StringValues() +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const bool& InValue) + : FHoudiniParameterTuple() +{ + BoolValues.Add(InValue); +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) + : BoolValues(InValues) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const int32& InValue) + : FHoudiniParameterTuple() +{ + Int32Values.Add(InValue); +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) + : Int32Values(InValues) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const float& InValue) + : FHoudiniParameterTuple() +{ + FloatValues.Add(InValue); +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) + : FloatValues(InValues) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const FString& InValue) + : FHoudiniParameterTuple() +{ + StringValues.Add(InValue); +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) + : StringValues(InValues) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InRampPoints) + : FloatRampPoints(InRampPoints) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InRampPoints) + : ColorRampPoints(InRampPoints) +{ +} + +// +// UHoudiniPublicAPIAssetWrapper +// + + +UHoudiniPublicAPIAssetWrapper::UHoudiniPublicAPIAssetWrapper() + : HoudiniAssetObject(nullptr) + , bAssetLinkSetupAttemptComplete(false) +{ + +} + +UHoudiniPublicAPIAssetWrapper* +UHoudiniPublicAPIAssetWrapper::CreateWrapper(UObject* InOuter, UObject* InHoudiniAssetActorOrComponent) +{ + if (!IsValid(InHoudiniAssetActorOrComponent)) + return nullptr; + + // Check if InHoudiniAssetActorOrComponent is supported + if (!CanWrapHoudiniObject(InHoudiniAssetActorOrComponent)) + return nullptr; + + UHoudiniPublicAPIAssetWrapper* NewWrapper = CreateEmptyWrapper(InOuter); + if (!IsValid(NewWrapper)) + return nullptr; + + // If we cannot wrap the specified actor, return nullptr. + if (!NewWrapper->WrapHoudiniAssetObject(InHoudiniAssetActorOrComponent)) + { + NewWrapper->MarkPendingKill(); + return nullptr; + } + + return NewWrapper; +} + +UHoudiniPublicAPIAssetWrapper* +UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper(UObject* InOuter) +{ + UObject* const Outer = InOuter ? InOuter : GetTransientPackage(); + UClass* const Class = StaticClass(); + UHoudiniPublicAPIAssetWrapper* NewWrapper = NewObject( + Outer, Class, + MakeUniqueObjectName(Outer, Class)); + if (!IsValid(NewWrapper)) + return nullptr; + + return NewWrapper; +} + +bool +UHoudiniPublicAPIAssetWrapper::CanWrapHoudiniObject(UObject* InObject) +{ + if (!IsValid(InObject)) + return false; + + return InObject->IsA() || InObject->IsA(); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetTemporaryCookFolder_Implementation(FDirectoryPath& OutDirectoryPath) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + OutDirectoryPath = HAC->TemporaryCookFolder; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetTemporaryCookFolder_Implementation(const FDirectoryPath& InDirectoryPath) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + if (HAC->TemporaryCookFolder.Path != InDirectoryPath.Path) + HAC->TemporaryCookFolder = InDirectoryPath; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetBakeFolder_Implementation(FDirectoryPath& OutDirectoryPath) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + OutDirectoryPath = HAC->BakeFolder; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetBakeFolder_Implementation(const FDirectoryPath& InDirectoryPath) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + if (HAC->BakeFolder.Path != InDirectoryPath.Path) + HAC->BakeFolder = InDirectoryPath; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::BakeAllOutputs_Implementation() +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + HAC, + HAC->bReplacePreviousBake, + HAC->HoudiniEngineBakeOption, + HAC->bRemoveOutputAfterBake, + HAC->bRecenterBakedActors); +} + +bool +UHoudiniPublicAPIAssetWrapper::BakeAllOutputsWithSettings_Implementation( + EHoudiniEngineBakeOption InBakeOption, + bool bInReplacePreviousBake, + bool bInRemoveTempOutputsOnSuccess, + bool bInRecenterBakedActors) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(HAC, bInReplacePreviousBake, InBakeOption, bInRemoveTempOutputsOnSuccess, bInRecenterBakedActors); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetAutoBakeEnabled_Implementation(const bool bInAutoBakeEnabled) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->SetBakeAfterNextCookEnabled(bInAutoBakeEnabled); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::IsAutoBakeEnabled_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->IsBakeAfterNextCookEnabled(); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetBakeMethod_Implementation(const EHoudiniEngineBakeOption InBakeMethod) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->HoudiniEngineBakeOption = InBakeMethod; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetBakeMethod_Implementation(EHoudiniEngineBakeOption& OutBakeMethod) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + OutBakeMethod = HAC->HoudiniEngineBakeOption; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetRemoveOutputAfterBake_Implementation(const bool bInRemoveOutputAfterBake) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->bRemoveOutputAfterBake = bInRemoveOutputAfterBake; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetRemoveOutputAfterBake_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->bRemoveOutputAfterBake; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetRecenterBakedActors_Implementation(const bool bInRecenterBakedActors) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->bRecenterBakedActors = bInRecenterBakedActors; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetRecenterBakedActors_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->bRecenterBakedActors; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetReplacePreviousBake_Implementation(const bool bInReplacePreviousBake) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->bReplacePreviousBake = bInReplacePreviousBake; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetReplacePreviousBake_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->bReplacePreviousBake; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidHoudiniAssetActorWithError(AHoudiniAssetActor*& OutActor) const +{ + AHoudiniAssetActor* const Actor = GetHoudiniAssetActor(); + if (!IsValid(Actor)) + { + SetErrorMessage( + TEXT("Could not find a valid AHoudiniAssetActor for the wrapped asset, or no asset has been wrapped.")); + return false; + } + + OutActor = Actor; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidHoudiniAssetComponentWithError(UHoudiniAssetComponent*& OutHAC) const +{ + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (!IsValid(HAC)) + { + SetErrorMessage( + TEXT("Could not find a valid HoudiniAssetComponent for the wrapped asset, or no asset has been wrapped.")); + return false; + } + + OutHAC = HAC; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidOutputAtWithError(const int32 InOutputIndex, UHoudiniOutput*& OutOutput) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + // Check if InOutputIndex is a valid/in-range index + const int32 NumOutputs = HAC->GetNumOutputs(); + if (InOutputIndex < 0 || InOutputIndex >= NumOutputs) + { + SetErrorMessage(FString::Printf( + TEXT("Output index %d is out of range [0, %d]"), InOutputIndex, NumOutputs)); + return false; + } + + UHoudiniOutput* const Output= HAC->GetOutputAt(InOutputIndex); + if (!IsValid(Output)) + { + SetErrorMessage(FString::Printf(TEXT("Output at index %d is invalid."), InOutputIndex)); + return false; + } + + OutOutput = Output; + return true; +} + + +UHoudiniPDGAssetLink* +UHoudiniPublicAPIAssetWrapper::GetHoudiniPDGAssetLink() const +{ + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (!IsValid(HAC)) + return nullptr; + + return HAC->GetPDGAssetLink(); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidHoudiniPDGAssetLinkWithError(UHoudiniPDGAssetLink*& OutAssetLink) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + UHoudiniPDGAssetLink* const AssetLink = HAC->GetPDGAssetLink(); + if (!IsValid(AssetLink)) + { + SetErrorMessage( + TEXT("Could not find a valid HoudiniPDGAssetLink for the wrapped asset. Does it contain a TOP network?")); + return false; + } + + OutAssetLink = AssetLink; + return true; +} + +void +UHoudiniPublicAPIAssetWrapper::ClearHoudiniAssetObject_Implementation() +{ + UHoudiniPDGAssetLink* const AssetLink = GetHoudiniPDGAssetLink(); + if (IsValid(AssetLink)) + { + if (OnPDGPostTOPNetworkCookDelegateHandle.IsValid()) + AssetLink->GetOnPostTOPNetworkCookDelegate().Remove(OnPDGPostTOPNetworkCookDelegateHandle); + if (OnPDGPostBakeDelegateHandle.IsValid()) + AssetLink->GetOnPostBakeDelegate().Remove(OnPDGPostBakeDelegateHandle); + } + + bAssetLinkSetupAttemptComplete = false; + + FHoudiniEngineCommands::GetOnHoudiniProxyMeshesRefinedDelegate().Remove(OnHoudiniProxyMeshesRefinedDelegateHandle); + + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (IsValid(HAC)) + { + if (OnAssetStateChangeDelegateHandle.IsValid()) + HAC->GetOnAssetStateChangeDelegate().Remove(OnAssetStateChangeDelegateHandle); + if (OnPostCookDelegateHandle.IsValid()) + HAC->GetOnPostCookDelegate().Remove(OnPostCookDelegateHandle); + if (OnPostBakeDelegateHandle.IsValid()) + HAC->GetOnPostBakeDelegate().Remove(OnPostBakeDelegateHandle); + } + + OnPDGPostTOPNetworkCookDelegateHandle.Reset(); + OnPDGPostBakeDelegateHandle.Reset(); + OnAssetStateChangeDelegateHandle.Reset(); + OnPostCookDelegateHandle.Reset(); + OnPostBakeDelegateHandle.Reset(); + OnHoudiniProxyMeshesRefinedDelegateHandle.Reset(); + + HoudiniAssetObject = nullptr; + CachedHoudiniAssetActor = nullptr; + CachedHoudiniAssetComponent = nullptr; +} + +bool +UHoudiniPublicAPIAssetWrapper::WrapHoudiniAssetObject_Implementation(UObject* InHoudiniAssetObjectToWrap) +{ + // If InHoudiniAssetObjectToWrap is null, just unwrap any currently wrapped asset + if (!InHoudiniAssetObjectToWrap) + { + ClearHoudiniAssetObject(); + return true; + } + + // Check if InHoudiniAssetObjectToWrap is supported + if (!CanWrapHoudiniObject(InHoudiniAssetObjectToWrap)) + { + UClass* const ObjectClass = IsValid(InHoudiniAssetObjectToWrap) ? InHoudiniAssetObjectToWrap->GetClass() : nullptr; + SetErrorMessage(FString::Printf( + TEXT("Cannot wrap objects of class '%s'."), ObjectClass ? *(ObjectClass->GetName()) : TEXT("Unknown"))); + return false; + } + + // First unwrap/unbind if we are currently wrapping an instantiated asset + ClearHoudiniAssetObject(); + + HoudiniAssetObject = InHoudiniAssetObjectToWrap; + + // Cache the HoudiniAssetActor and HoudiniAssetComponent + if (HoudiniAssetObject->IsA()) + { + CachedHoudiniAssetActor = Cast(InHoudiniAssetObjectToWrap); + CachedHoudiniAssetComponent = CachedHoudiniAssetActor->HoudiniAssetComponent; + } + else if (HoudiniAssetObject->IsA()) + { + CachedHoudiniAssetComponent = Cast(InHoudiniAssetObjectToWrap); + CachedHoudiniAssetActor = Cast(CachedHoudiniAssetComponent->GetOwner()); + } + + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (IsValid(HAC)) + { + // Bind to HandleOnHoudiniAssetStateChange from the HAC: we also implement IHoudiniAssetStateEvents, and + // in the default implementation HandleOnHoudiniAssetStateChange will call the appropriate Handle functions + // for PostInstantiate, PostCook etc + OnAssetStateChangeDelegateHandle = HAC->GetOnAssetStateChangeDelegate().AddUFunction(this, TEXT("HandleOnHoudiniAssetComponentStateChange")); + OnPostCookDelegateHandle = HAC->GetOnPostCookDelegate().AddUFunction(this, TEXT("HandleOnHoudiniAssetComponentPostCook")); + OnPostBakeDelegateHandle = HAC->GetOnPostBakeDelegate().AddUFunction(this, TEXT("HandleOnHoudiniAssetComponentPostBake")); + } + + OnHoudiniProxyMeshesRefinedDelegateHandle = FHoudiniEngineCommands::GetOnHoudiniProxyMeshesRefinedDelegate().AddUFunction(this, TEXT("HandleOnHoudiniProxyMeshesRefinedGlobal")); + + // PDG asset link bindings: We attempt to bind to PDG here, but it likely is not available yet. + // We have to wait until post instantiation in order to know if there is a PDG asset link + // for this HDA. This is checked again in HandleOnHoudiniAssetComponentStateChange and sets + // bAssetLinkSetupAttemptComplete. + BindToPDGAssetLink(); + + return true; +} + +AHoudiniAssetActor* +UHoudiniPublicAPIAssetWrapper::GetHoudiniAssetActor_Implementation() const +{ + return CachedHoudiniAssetActor.Get(); +} + +UHoudiniAssetComponent* +UHoudiniPublicAPIAssetWrapper::GetHoudiniAssetComponent_Implementation() const +{ + return CachedHoudiniAssetComponent.Get(); +} + +bool +UHoudiniPublicAPIAssetWrapper::DeleteInstantiatedAsset_Implementation() +{ + AHoudiniAssetActor* AssetActor = nullptr; + if (!GetValidHoudiniAssetActorWithError(AssetActor)) + return false; + + // Unbind / unwrap the HDA actor + ClearHoudiniAssetObject(); + AssetActor->Destroy(); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::Rebuild_Implementation() +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->MarkAsNeedRebuild(); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::Recook_Implementation() +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->MarkAsNeedCook(); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetAutoCookingEnabled_Implementation(const bool bInSetEnabled) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + if (HAC->IsCookingEnabled() == bInSetEnabled) + return false; + + HAC->SetCookingEnabled(bInSetEnabled); + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::IsAutoCookingEnabled_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->IsCookingEnabled(); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetFloatParameterValue_Implementation(FName InParameterTupleName, float InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is a float + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::Float) + { + UHoudiniParameterFloat* FloatParam = Cast(Param); + if (!IsValid(FloatParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= FloatParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FloatParam->GetNumberOfValues())); + return false; + } + + bDidChangeValue = FloatParam->SetValueAt(InValue, InAtIndex); + } + else if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParam = Cast(Param); + if (!IsValid(ColorParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + static const int32 NumColorChannels = 4; + if (InAtIndex >= NumColorChannels) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, NumColorChannels)); + return false; + } + + FLinearColor CurrentColorValue = ColorParam->GetColorValue(); + if (CurrentColorValue.Component(InAtIndex) != InValue) + { + CurrentColorValue.Component(InAtIndex) = InValue; + ColorParam->SetColorValue(CurrentColorValue); + bDidChangeValue = true; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetFloatParamterValue."), *InParameterTupleName.ToString())); + return false; + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetFloatParameterValue_Implementation(FName InParameterTupleName, float& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is a float + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::Float) + { + UHoudiniParameterFloat* FloatParam = Cast(Param); + if (!IsValid(FloatParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= FloatParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FloatParam->GetNumberOfValues())); + return false; + } + + return FloatParam->GetValueAt(InAtIndex, OutValue); + } + else if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParam = Cast(Param); + if (!IsValid(ColorParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + static const int32 NumColorChannels = 4; + if (InAtIndex >= NumColorChannels) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, NumColorChannels)); + return false; + } + + OutValue = ColorParam->GetColorValue().Component(InAtIndex); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetColorParameterValue_Implementation(FName InParameterTupleName, const FLinearColor& InValue, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is a float + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParam = Cast(Param); + if (!IsValid(ColorParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ColorParam->SetColorValue(InValue); + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetColorParamterValue."), *InParameterTupleName.ToString())); + return false; + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetColorParameterValue_Implementation(FName InParameterTupleName, FLinearColor& OutValue) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is a float + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParam = Cast(Param); + if (!IsValid(ColorParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ColorParam->GetColorValue(); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetIntParameterValue_Implementation(FName InParameterTupleName, int32 InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::Int) + { + UHoudiniParameterInt* IntParam = Cast(Param); + if (!IsValid(IntParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= IntParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, IntParam->GetNumberOfValues())); + return false; + } + + bDidChangeValue = IntParam->SetValueAt(InValue, InAtIndex); + } + else if (ParamType == EHoudiniParameterType::IntChoice) + { + UHoudiniParameterChoice* ChoiceParam = Cast(Param); + if (!IsValid(ChoiceParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ChoiceParam->SetIntValue(InValue); + } + else if (ParamType == EHoudiniParameterType::MultiParm) + { + UHoudiniParameterMultiParm* MultiParam = Cast(Param); + if (!IsValid(MultiParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = MultiParam->SetNumElements(InValue); + } + else if (ParamType == EHoudiniParameterType::Toggle) + { + UHoudiniParameterToggle* ToggleParam = Cast(Param); + if (!IsValid(ToggleParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ToggleParam->SetValueAt(InValue != 0, InAtIndex); + } + else if (ParamType == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParam = Cast(Param); + if (!IsValid(FolderParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + const bool NewValue = InValue != 0; + if (FolderParam->IsChosen() != NewValue) + { + FolderParam->SetChosen(NewValue); + bDidChangeValue = true; + } + } + else if (ParamType == EHoudiniParameterType::FloatRamp || ParamType == EHoudiniParameterType::ColorRamp) + { + // For ramps we have to use the appropriate function so that delete/insert operations are managed correctly + bDidChangeValue = SetRampParameterNumPoints(InParameterTupleName, InValue); + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetIntParameterValue."), *InParameterTupleName.ToString())); + return false; + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetIntParameterValue_Implementation(FName InParameterTupleName, int32& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::Int) + { + UHoudiniParameterInt* IntParam = Cast(Param); + if (!IsValid(IntParam)) + return false; + + if (InAtIndex >= IntParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, IntParam->GetNumberOfValues())); + return false; + } + + return IntParam->GetValueAt(InAtIndex, OutValue); + } + else if (ParamType == EHoudiniParameterType::IntChoice) + { + UHoudiniParameterChoice* ChoiceParam = Cast(Param); + if (!IsValid(ChoiceParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ChoiceParam->GetIntValue(ChoiceParam->GetIntValueIndex()); + return true; + } + else if (ParamType == EHoudiniParameterType::MultiParm) + { + UHoudiniParameterMultiParm* MultiParam = Cast(Param); + if (!IsValid(MultiParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = MultiParam->GetNextInstanceCount(); + return true; + } + else if (ParamType == EHoudiniParameterType::Toggle) + { + UHoudiniParameterToggle* ToggleParam = Cast(Param); + if (!IsValid(ToggleParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ToggleParam->GetValueAt(InAtIndex); + return true; + } + else if (ParamType == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParam = Cast(Param); + if (!IsValid(FolderParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = FolderParam->IsChosen(); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetBoolParameterValue_Implementation(FName InParameterTupleName, bool InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::Toggle) + { + UHoudiniParameterToggle* ToggleParam = Cast(Param); + if (!IsValid(ToggleParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ToggleParam->SetValueAt(InValue, InAtIndex); + } + else if (ParamType == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParam = Cast(Param); + if (!IsValid(FolderParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (FolderParam->IsChosen() != InValue) + { + FolderParam->SetChosen(InValue); + bDidChangeValue = true; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetBoolParameterValue."), *InParameterTupleName.ToString())); + return false; + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetBoolParameterValue_Implementation(FName InParameterTupleName, bool& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::Toggle) + { + UHoudiniParameterToggle* ToggleParam = Cast(Param); + if (!IsValid(ToggleParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ToggleParam->GetValueAt(InAtIndex); + return true; + } + else if (ParamType == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParam = Cast(Param); + if (!IsValid(FolderParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = FolderParam->IsChosen(); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetStringParameterValue_Implementation(FName InParameterTupleName, const FString& InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::String || ParamType == EHoudiniParameterType::StringAssetRef) + { + UHoudiniParameterString* StringParam = Cast(Param); + if (!IsValid(StringParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= StringParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); + return false; + } + + // We have to handle asset references differently + if (ParamType == EHoudiniParameterType::StringAssetRef) + { + // Find/load the asset, make sure it is a valid reference/object + const FSoftObjectPath AssetRef(InValue); + UObject* const Asset = AssetRef.TryLoad(); + if (IsValid(Asset)) + { + UObject* const CurrentAsset = StringParam->GetAssetAt(InAtIndex); + if (CurrentAsset != Asset) + { + StringParam->SetAssetAt(Asset, InAtIndex); + bDidChangeValue = true; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Asset reference '%s' is invalid. Not setting parameter value."), *InValue)); + return false; + } + } + else + { + bDidChangeValue = StringParam->SetValueAt(InValue, InAtIndex); + } + } + else if (ParamType == EHoudiniParameterType::StringChoice) + { + UHoudiniParameterChoice* ChoiceParam = Cast(Param); + if (!IsValid(ChoiceParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ChoiceParam->SetStringValue(InValue); + } + else if (ParamType == EHoudiniParameterType::File || ParamType == EHoudiniParameterType::FileDir || + ParamType == EHoudiniParameterType::FileGeo || ParamType == EHoudiniParameterType::FileImage) + { + UHoudiniParameterFile* FileParam = Cast(Param); + if (!IsValid(FileParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= FileParam->GetNumValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FileParam->GetNumValues())); + return false; + } + + bDidChangeValue = FileParam->SetValueAt(InValue, InAtIndex); + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetStringParameterValue."), *InParameterTupleName.ToString())); + return false; + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetStringParameterValue_Implementation(FName InParameterTupleName, FString& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::String || ParamType == EHoudiniParameterType::StringAssetRef) + { + UHoudiniParameterString* StringParam = Cast(Param); + if (!IsValid(StringParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= StringParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); + return false; + } + + // For asset references: get the asset, and then get the string reference from it and return that. + // If the asset is null, return the empty string. + if (ParamType == EHoudiniParameterType::StringAssetRef) + { + UObject* const Asset = StringParam->GetAssetAt(InAtIndex); + if (IsValid(Asset)) + { + OutValue = UHoudiniParameterString::GetAssetReference(Asset); + } + else + { + OutValue.Empty(); + } + } + else + { + OutValue = StringParam->GetValueAt(InAtIndex); + } + return true; + } + else if (ParamType == EHoudiniParameterType::StringChoice) + { + UHoudiniParameterChoice* ChoiceParam = Cast(Param); + if (!IsValid(ChoiceParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ChoiceParam->GetStringValue(); + return true; + } + else if (ParamType == EHoudiniParameterType::File || ParamType == EHoudiniParameterType::FileDir || + ParamType == EHoudiniParameterType::FileGeo || ParamType == EHoudiniParameterType::FileImage) + { + UHoudiniParameterFile* FileParam = Cast(Param); + if (!IsValid(FileParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= FileParam->GetNumValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FileParam->GetNumValues())); + return false; + } + + OutValue = FileParam->GetValueAt(InAtIndex); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetAssetRefParameterValue_Implementation(FName InParameterTupleName, UObject* InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::StringAssetRef) + { + UHoudiniParameterString* StringParam = Cast(Param); + if (!IsValid(StringParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= StringParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); + return false; + } + + // Find/load the asset, make sure it is a valid reference/object + UObject* const CurrentAsset = StringParam->GetAssetAt(InAtIndex); + if (CurrentAsset != InValue) + { + StringParam->SetAssetAt(InValue, InAtIndex); + bDidChangeValue = true; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetAssetRefParamter."), *InParameterTupleName.ToString())); + return false; + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetAssetRefParameterValue_Implementation(FName InParameterTupleName, UObject*& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::StringAssetRef) + { + UHoudiniParameterString* StringParam = Cast(Param); + if (!IsValid(StringParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= StringParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); + return false; + } + + OutValue = StringParam->GetAssetAt(InAtIndex); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetRampParameterNumPoints_Implementation(FName InParameterTupleName, const int32 InNumPoints) const +{ + if (InNumPoints < 1) + { + SetErrorMessage(TEXT("InNumPoints must be >= 1.")); + return false; + } + + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter '%s' is not a float or color ramp parameter."), *(InParameterTupleName.ToString()))); + return false; + } + + if (bUseCachedPoints) + { + // When using the cached points we only have to resize the array + if (FloatRampParam) + { + const int32 CurrentNumPoints = FloatRampParam->CachedPoints.Num(); + if (CurrentNumPoints != InNumPoints) + { + FloatRampParam->CachedPoints.SetNum(InNumPoints); + // FloatRampParam->MarkChanged(true); + } + } + else + { + const int32 CurrentNumPoints = ColorRampParam->CachedPoints.Num(); + if (CurrentNumPoints != InNumPoints) + { + ColorRampParam->CachedPoints.SetNum(InNumPoints); + // ColorRampParam->MarkChanged(true); + } + } + + // Update the ramp's widget if it is currently visible/selected + const bool bForceFullUpdate = true; + FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); + } + else + { + int32 NumPendingInsertOperations = 0; + int32 NumPendingDeleteOperations = 0; + TSet InstanceIndexesPendingDelete; + TArray& ModificationEvents = FloatRampParam ? FloatRampParam->ModificationEvents : ColorRampParam->ModificationEvents; + for (UHoudiniParameterRampModificationEvent const* const Event : ModificationEvents) + { + if (!IsValid(Event)) + continue; + + if (Event->IsInsertEvent()) + NumPendingInsertOperations++; + else if (Event->IsDeleteEvent()) + { + InstanceIndexesPendingDelete.Add(Event->DeleteInstanceIndex); + NumPendingDeleteOperations++; + } + } + + const int32 PointsArraySize = FloatRampParam ? FloatRampParam->Points.Num() : ColorRampParam->Points.Num(); + int32 CurrentNumPoints = PointsArraySize + NumPendingInsertOperations - NumPendingDeleteOperations; + + if (InNumPoints < CurrentNumPoints) + { + // When deleting points, first remove pending insert operations from the end + if (NumPendingInsertOperations > 0) + { + const int32 NumEvents = ModificationEvents.Num(); + TArray TempModificationArray; + TempModificationArray.Reserve(NumEvents); + + for (int32 Index = NumEvents - 1; Index >= 0; --Index) + { + UHoudiniParameterRampModificationEvent* const Event = ModificationEvents[Index]; + if (InNumPoints < CurrentNumPoints && IsValid(Event) && Event->IsInsertEvent()) + { + CurrentNumPoints--; + NumPendingInsertOperations--; + continue; + } + + TempModificationArray.Add(Event); + } + + Algo::Reverse(TempModificationArray); + ModificationEvents = MoveTemp(TempModificationArray); + } + + // If we still have points to delete... + if (InNumPoints < CurrentNumPoints) + { + // Deleting points, add delete operations, deleting from the end of Points (points that are not yet + // pending delete) + for (int32 Index = PointsArraySize - 1; (InNumPoints < CurrentNumPoints && Index >= 0); --Index) + { + int32 InstanceIndex = INDEX_NONE; + + if (FloatRampParam) + { + UHoudiniParameterRampFloatPoint const* const PointData = FloatRampParam->Points[Index]; + if (!IsValid(PointData)) + continue; + + InstanceIndex = PointData->InstanceIndex; + } + else + { + UHoudiniParameterRampColorPoint const* const PointData = ColorRampParam->Points[Index]; + if (!IsValid(PointData)) + continue; + + InstanceIndex = PointData->InstanceIndex; + } + + if (!InstanceIndexesPendingDelete.Contains(InstanceIndex)) + { + InstanceIndexesPendingDelete.Add(InstanceIndex); + CurrentNumPoints--; + + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + FloatRampParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (DeleteEvent) + { + if (FloatRampParam) + { + DeleteEvent->SetFloatRampEvent(); + } + else + { + DeleteEvent->SetColorRampEvent(); + } + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InstanceIndex; + + ModificationEvents.Add(DeleteEvent); + } + } + } + } + + Param->MarkChanged(true); + } + else if (InNumPoints > CurrentNumPoints) + { + // Adding points, add insert operations + while (InNumPoints > CurrentNumPoints) + { + CurrentNumPoints++; + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + Param, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (InsertEvent) + { + if (FloatRampParam) + { + InsertEvent->SetFloatRampEvent(); + } + else + { + InsertEvent->SetColorRampEvent(); + } + InsertEvent->SetInsertEvent(); + // Leave point position, value and interpolation at default + + ModificationEvents.Add(InsertEvent); + } + } + + Param->MarkChanged(true); + } + + // If at this point InNumPoints != CurrentNumPoints then something went wrong, we couldn't delete all the + // desired points + if (InNumPoints != CurrentNumPoints) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected error: could not delete the required number of ramp points " + "(target # points = %d; have # points %d)."), InNumPoints, CurrentNumPoints)); + return false; + } + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetRampParameterNumPoints_Implementation(FName InParameterTupleName, int32& OutNumPoints) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter '%s' is not a float or color ramp parameter."), *(InParameterTupleName.ToString()))); + return false; + } + + if (bUseCachedPoints) + { + // When using the cached points we only have to resize the array + if (FloatRampParam) + { + OutNumPoints = FloatRampParam->CachedPoints.Num(); + } + else + { + OutNumPoints = ColorRampParam->CachedPoints.Num(); + } + } + else + { + int32 NumPendingInsertOperations = 0; + int32 NumPendingDeleteOperations = 0; + TArray& ModificationEvents = FloatRampParam ? FloatRampParam->ModificationEvents : ColorRampParam->ModificationEvents; + for (UHoudiniParameterRampModificationEvent const* const Event : ModificationEvents) + { + if (!IsValid(Event)) + continue; + + if (Event->IsInsertEvent()) + NumPendingInsertOperations++; + else if (Event->IsDeleteEvent()) + NumPendingDeleteOperations++; + } + + const int32 PointsArraySize = FloatRampParam ? FloatRampParam->Points.Num() : ColorRampParam->Points.Num(); + OutNumPoints = PointsArraySize + NumPendingInsertOperations - NumPendingDeleteOperations; + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetFloatRampParameterPointValue_Implementation( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPointPosition, + const float InPointValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolationType, + const bool bInMarkChanged) +{ + return SetRampParameterPointValue( + InParameterTupleName, InPointIndex, InPointPosition, InPointValue, FLinearColor::Black, InInterpolationType, bInMarkChanged); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetFloatRampParameterPointValue_Implementation( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPointPosition, + float& OutPointValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const +{ + FLinearColor ColorValue; + return GetRampParameterPointValue( + InParameterTupleName, InPointIndex, OutPointPosition, OutPointValue, ColorValue, OutInterpolationType); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetFloatRampParameterPoints_Implementation( + FName InParameterTupleName, + const TArray& InRampPoints, + const bool bInMarkChanged) +{ + const int32 TargetNumPoints = InRampPoints.Num(); + if (TargetNumPoints == 0) + { + SetErrorMessage(TEXT("InRampPoints must have at least one entry.")); + return false; + } + + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp or color ramp parameter."), *(Param->GetName()))); + return false; + } + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + // Set the ramp point count to match the size of InRampPoints + if (!SetRampParameterNumPoints(InParameterTupleName, TargetNumPoints)) + return false; + + // Get all ramp points (INDEX_NONE == get all points) + TArray> RampPointData; + if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) + return false; + + // Check that we fetched the correct number of point data objects + if (RampPointData.Num() != TargetNumPoints) + { + SetErrorMessage(FString::Printf(TEXT("Failed to set the number of ramp points to %d."), TargetNumPoints)); + return false; + } + + for (int32 Index = 0; Index < TargetNumPoints; ++Index) + { + TPair const& Entry = RampPointData[Index]; + UObject* const PointData = Entry.Key; + const bool bIsPointData = Entry.Value; + if (!IsValid(PointData)) + return false; + + const FHoudiniPublicAPIFloatRampPoint& NewRampPoint = InRampPoints[Index]; + const EHoudiniRampInterpolationType NewInterpolation = UHoudiniPublicAPI::ToHoudiniRampInterpolationType( + NewRampPoint.Interpolation); + + if (bIsPointData) + { + UHoudiniParameterRampFloatPoint* FloatPointData = Cast(PointData); + if (!IsValid(FloatPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + if (bUseCachedPoints) + { + // When setting the cached points, we set the values directly instead of using the setters, but we set + // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed + if (FloatPointData->Position != NewRampPoint.Position) + { + FloatPointData->Position = NewRampPoint.Position; + FloatRampParam->bCaching = true; + } + + if (FloatPointData->Value != NewRampPoint.Value) + { + FloatPointData->Value = NewRampPoint.Value; + FloatRampParam->bCaching = true; + } + + if (FloatPointData->Interpolation != NewInterpolation) + { + FloatPointData->Interpolation = NewInterpolation; + FloatRampParam->bCaching = true; + } + } + else + { + // When setting the main points, we set the values using the setters on the point data but still manually + // mark the position/value/interpolation parent parameters as changed + if (FloatPointData->Position != NewRampPoint.Position && FloatPointData->PositionParentParm) + { + FloatPointData->SetPosition(NewRampPoint.Position); + if (bInMarkChanged) + FloatPointData->PositionParentParm->MarkChanged(bInMarkChanged); + } + + if (FloatPointData->Value != NewRampPoint.Value && FloatPointData->ValueParentParm) + { + FloatPointData->SetValue(NewRampPoint.Value); + if (bInMarkChanged) + FloatPointData->ValueParentParm->MarkChanged(bInMarkChanged); + } + + if (FloatPointData->Interpolation != NewInterpolation && FloatPointData->InterpolationParentParm) + { + FloatPointData->SetInterpolation(NewInterpolation); + if (bInMarkChanged) + FloatPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); + } + } + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + Event->InsertPosition = NewRampPoint.Position; + Event->InsertFloat = NewRampPoint.Value; + Event->InsertInterpolation = NewInterpolation; + } + } + + if (bUseCachedPoints) + { + // Update the ramp's widget if it is currently visible/selected + const bool bForceFullUpdate = true; + FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetFloatRampParameterPoints_Implementation( + FName InParameterTupleName, + TArray& OutRampPoints) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp parameter."), *(Param->GetName()))); + return false; + } + + // Get all ramp points (INDEX_NONE == get all points) + TArray> RampPointData; + if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) + return false; + + OutRampPoints.Reserve(RampPointData.Num()); + const bool bAllowShrinking = false; + OutRampPoints.SetNum(0, bAllowShrinking); + for (TPair const& Entry : RampPointData) + { + UObject* const PointData = Entry.Key; + const bool bIsPointData = Entry.Value; + if (!IsValid(PointData)) + return false; + + FHoudiniPublicAPIFloatRampPoint TempPointData; + + if (bIsPointData) + { + UHoudiniParameterRampFloatPoint* const FloatPointData = Cast(PointData); + if (!IsValid(FloatPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + TempPointData.Position = FloatPointData->Position; + TempPointData.Value = FloatPointData->Value; + TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( + FloatPointData->Interpolation); + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + TempPointData.Position = Event->InsertPosition; + TempPointData.Value = Event->InsertFloat; + TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(Event->InsertInterpolation); + } + + OutRampPoints.Add(TempPointData); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetColorRampParameterPointValue_Implementation( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPointPosition, + const FLinearColor& InPointValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolationType, + const bool bInMarkChanged) +{ + const float FloatValue = 0; + return SetRampParameterPointValue( + InParameterTupleName, InPointIndex, InPointPosition, FloatValue, InPointValue, InInterpolationType, bInMarkChanged); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetColorRampParameterPointValue_Implementation( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPointPosition, + FLinearColor& OutPointValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const +{ + float FloatValue = 0; + return GetRampParameterPointValue( + InParameterTupleName, InPointIndex, OutPointPosition, FloatValue, OutPointValue, OutInterpolationType); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetColorRampParameterPoints_Implementation( + FName InParameterTupleName, + const TArray& InRampPoints, + const bool bInMarkChanged) +{ + const int32 TargetNumPoints = InRampPoints.Num(); + if (TargetNumPoints == 0) + { + SetErrorMessage(TEXT("InRampPoints must have at least one entry.")); + return false; + } + + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a color ramp parameter."), *(Param->GetName()))); + return false; + } + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + // Set the ramp point count to match the size of InRampPoints + if (!SetRampParameterNumPoints(InParameterTupleName, TargetNumPoints)) + return false; + + // Get all ramp points (INDEX_NONE == get all points) + TArray> RampPointData; + if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) + return false; + + // Check that we fetched the correct number of point data objects + if (RampPointData.Num() != TargetNumPoints) + { + SetErrorMessage(FString::Printf(TEXT("Failed to set the number of ramp points to %d."), TargetNumPoints)); + return false; + } + + for (int32 Index = 0; Index < TargetNumPoints; ++Index) + { + TPair const& Entry = RampPointData[Index]; + UObject* const PointData = Entry.Key; + const bool bIsPointData = Entry.Value; + if (!IsValid(PointData)) + return false; + + const FHoudiniPublicAPIColorRampPoint& NewRampPoint = InRampPoints[Index]; + const EHoudiniRampInterpolationType NewInterpolation = UHoudiniPublicAPI::ToHoudiniRampInterpolationType( + NewRampPoint.Interpolation); + + if (bIsPointData) + { + UHoudiniParameterRampColorPoint* ColorPointData = Cast(PointData); + if (!IsValid(ColorPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + if (bUseCachedPoints) + { + // When setting the cached points, we set the values directly instead of using the setters, but we set + // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed + if (ColorPointData->Position != NewRampPoint.Position) + { + ColorPointData->Position = NewRampPoint.Position; + ColorRampParam->bCaching = true; + } + + if (ColorPointData->Value != NewRampPoint.Value) + { + ColorPointData->Value = NewRampPoint.Value; + ColorRampParam->bCaching = true; + } + + if (ColorPointData->Interpolation != NewInterpolation) + { + ColorPointData->Interpolation = NewInterpolation; + ColorRampParam->bCaching = true; + } + } + else + { + // When setting the main points, we set the values using the setters on the point data but still manually + // mark the position/value/interpolation parent parameters as changed + if (ColorPointData->Position != NewRampPoint.Position && ColorPointData->PositionParentParm) + { + ColorPointData->SetPosition(NewRampPoint.Position); + if (bInMarkChanged) + ColorPointData->PositionParentParm->MarkChanged(bInMarkChanged); + } + + if (ColorPointData->Value != NewRampPoint.Value && ColorPointData->ValueParentParm) + { + ColorPointData->SetValue(NewRampPoint.Value); + if (bInMarkChanged) + ColorPointData->ValueParentParm->MarkChanged(bInMarkChanged); + } + + if (ColorPointData->Interpolation != NewInterpolation && ColorPointData->InterpolationParentParm) + { + ColorPointData->SetInterpolation(NewInterpolation); + if (bInMarkChanged) + ColorPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); + } + } + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + Event->InsertPosition = NewRampPoint.Position; + Event->InsertColor = NewRampPoint.Value; + Event->InsertInterpolation = NewInterpolation; + } + } + + if (bUseCachedPoints) + { + // Update the ramp's widget if it is currently visible/selected + const bool bForceFullUpdate = true; + FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetColorRampParameterPoints_Implementation( + FName InParameterTupleName, + TArray& OutRampPoints) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp parameter."), *(Param->GetName()))); + return false; + } + + // Get all ramp points (INDEX_NONE == get all points) + TArray> RampPointData; + if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) + return false; + + OutRampPoints.Reserve(RampPointData.Num()); + const bool bAllowShrinking = false; + OutRampPoints.SetNum(0, bAllowShrinking); + for (TPair const& Entry : RampPointData) + { + UObject* const PointData = Entry.Key; + const bool bIsPointData = Entry.Value; + if (!IsValid(PointData)) + return false; + + FHoudiniPublicAPIColorRampPoint TempPointData; + + if (bIsPointData) + { + UHoudiniParameterRampColorPoint* const ColorPointData = Cast(PointData); + if (!IsValid(ColorPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + TempPointData.Position = ColorPointData->Position; + TempPointData.Value = ColorPointData->Value; + TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( + ColorPointData->Interpolation); + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + TempPointData.Position = Event->InsertPosition; + TempPointData.Value = Event->InsertColor; + TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(Event->InsertInterpolation); + } + + OutRampPoints.Add(TempPointData); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::TriggerButtonParameter_Implementation(FName InButtonParameterName) +{ + UHoudiniParameter* Param = FindValidParameterByName(InButtonParameterName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + // bool bDidTrigger = false; + if (ParamType == EHoudiniParameterType::Button) + { + UHoudiniParameterButton* ButtonParam = Cast(Param); + if (!IsValid(ButtonParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + // Marking the button as changed will result in it being triggered/clicked via HAPI + if (!ButtonParam->HasChanged() || !ButtonParam->NeedsToTriggerUpdate()) + { + ButtonParam->MarkChanged(true); + // bDidTrigger = true; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is not a button."), *InButtonParameterName.ToString())); + return false; + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetParameterTuples_Implementation(TMap& OutParameterTuples) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + const int32 NumParameters = HAC->GetNumParameters(); + OutParameterTuples.Empty(NumParameters); + OutParameterTuples.Reserve(NumParameters); + for (int32 Index = 0; Index < NumParameters; ++Index) + { + const UHoudiniParameter* const Param = HAC->GetParameterAt(Index); + const EHoudiniParameterType ParameterType = Param->GetParameterType(); + const int32 TupleSize = Param->GetTupleSize(); + const FName PTName(Param->GetParameterName()); + + FHoudiniParameterTuple ParameterTuple; + + bool bSkipped = false; + switch (ParameterType) + { + case EHoudiniParameterType::Color: + case EHoudiniParameterType::Float: + { + // Output as float + ParameterTuple.FloatValues.SetNumZeroed(TupleSize); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + GetFloatParameterValue(PTName, ParameterTuple.FloatValues[TupleIndex], TupleIndex); + } + break; + } + + case EHoudiniParameterType::Int: + case EHoudiniParameterType::IntChoice: + case EHoudiniParameterType::MultiParm: + { + // Output as int + ParameterTuple.Int32Values.SetNumZeroed(TupleSize); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + GetIntParameterValue(PTName, ParameterTuple.Int32Values[TupleIndex], TupleIndex); + } + break; + } + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringChoice: + case EHoudiniParameterType::StringAssetRef: + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + // Output as string + ParameterTuple.StringValues.SetNumZeroed(TupleSize); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + GetStringParameterValue(PTName, ParameterTuple.StringValues[TupleIndex], TupleIndex); + } + break; + } + + case EHoudiniParameterType::Toggle: + { + // Output as bool + ParameterTuple.BoolValues.SetNumZeroed(TupleSize); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + GetBoolParameterValue(PTName, ParameterTuple.BoolValues[TupleIndex], TupleIndex); + } + break; + } + + case EHoudiniParameterType::ColorRamp: + { + GetColorRampParameterPoints(PTName, ParameterTuple.ColorRampPoints); + break; + } + case EHoudiniParameterType::FloatRamp: + { + GetFloatRampParameterPoints(PTName, ParameterTuple.FloatRampPoints); + break; + } + + case EHoudiniParameterType::Button: + case EHoudiniParameterType::ButtonStrip: + case EHoudiniParameterType::Input: + case EHoudiniParameterType::Invalid: + case EHoudiniParameterType::Folder: + case EHoudiniParameterType::FolderList: + case EHoudiniParameterType::Label: + case EHoudiniParameterType::Separator: + default: + // Skipped + bSkipped = true; + break; + } + + if (!bSkipped) + OutParameterTuples.Add(PTName, ParameterTuple); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetParameterTuples_Implementation(const TMap& InParameterTuples) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + bool bSuccess = true; + for (const TPair& Entry : InParameterTuples) + { + const FName& ParameterTupleName = Entry.Key; + const FHoudiniParameterTuple& ParameterTuple = Entry.Value; + if (ParameterTuple.BoolValues.Num() > 0) + { + // Set as bool + const int32 TupleSize = ParameterTuple.BoolValues.Num(); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + if (!SetBoolParameterValue(ParameterTupleName, ParameterTuple.BoolValues[TupleIndex], TupleIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("SetParameterTuples: Failed to set %s as a bool at tuple index %d."), *ParameterTupleName.ToString(), TupleIndex)); + bSuccess = false; + break; + } + } + } + else if (ParameterTuple.FloatValues.Num() > 0) + { + // Set as float + const int32 TupleSize = ParameterTuple.FloatValues.Num(); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + if (!SetFloatParameterValue(ParameterTupleName, ParameterTuple.FloatValues[TupleIndex], TupleIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("SetParameterTuples: Failed to set %s as a float at tuple index %d."), + *ParameterTupleName.ToString(), TupleIndex)); + bSuccess = false; + break; + } + } + } + else if (ParameterTuple.Int32Values.Num() > 0) + { + // Set as int + const int32 TupleSize = ParameterTuple.Int32Values.Num(); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + if (!SetIntParameterValue(ParameterTupleName, ParameterTuple.Int32Values[TupleIndex], TupleIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("SetParameterTuples: Failed to set %s as a int at tuple index %d."), + *ParameterTupleName.ToString(), TupleIndex)); + bSuccess = false; + break; + } + } + } + else if (ParameterTuple.StringValues.Num() > 0) + { + // Set as string + const int32 TupleSize = ParameterTuple.StringValues.Num(); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + if (!SetStringParameterValue(ParameterTupleName, ParameterTuple.StringValues[TupleIndex], TupleIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("SetParameterTuples: Failed to set %s as a string at tuple index %d."), + *ParameterTupleName.ToString(), TupleIndex)); + bSuccess = false; + break; + } + } + } + else if (ParameterTuple.FloatRampPoints.Num() > 0) + { + // Set as a float ramp + if (!SetFloatRampParameterPoints(ParameterTupleName, ParameterTuple.FloatRampPoints)) + bSuccess = false; + } + else if (ParameterTuple.ColorRampPoints.Num() > 0) + { + // Set as a color ramp + if (!SetColorRampParameterPoints(ParameterTupleName, ParameterTuple.ColorRampPoints)) + bSuccess = false; + } + } + + return bSuccess; +} + +UHoudiniPublicAPIInput* +UHoudiniPublicAPIAssetWrapper::CreateEmptyInput_Implementation(TSubclassOf InInputClass) +{ + UHoudiniPublicAPI* API = UHoudiniPublicAPIBlueprintLib::GetAPI(); + if (!IsValid(API)) + return nullptr; + + UHoudiniPublicAPIInput* const NewInput = API->CreateEmptyInput(InInputClass, this); + if (!IsValid(NewInput)) + { + SetErrorMessage(FString::Printf( + TEXT("Failed to create a new input of class '%s'."), + *(IsValid(InInputClass.Get()) ? InInputClass->GetName() : FString()))); + + return nullptr; + } + + return NewInput; +} + +int32 +UHoudiniPublicAPIAssetWrapper::GetNumNodeInputs_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return -1; + + int32 NumNodeInputs = 0; + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + + if (!Input->IsObjectPathParameter()) + NumNodeInputs++; + } + + return NumNodeInputs; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetInputAtIndex_Implementation(const int32 InNodeInputIndex, const UHoudiniPublicAPIInput* InInput) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + UHoudiniInput* HoudiniInput = GetHoudiniNodeInputByIndex(InNodeInputIndex); + if (!IsValid(HoudiniInput)) + { + SetErrorMessage(FString::Printf( + TEXT("SetInputAtIndex: Could not find a HoudiniInput for InNodeInputIndex %d. Has the HDA been instantiated?"), + InNodeInputIndex)); + return false; + } + + const bool bSuccess = PopulateHoudiniInput(InInput, HoudiniInput); + + // Update the details panel (mostly for when new curves/components are created where visualizers are driven + // through the details panel) + FHoudiniEngineEditorUtils::ReselectComponentOwnerIfSelected(HAC); + + return bSuccess; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetInputAtIndex_Implementation(const int32 InNodeInputIndex, UHoudiniPublicAPIInput*& OutInput) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + const UHoudiniInput* HoudiniInput = GetHoudiniNodeInputByIndex(InNodeInputIndex); + if (!IsValid(HoudiniInput)) + { + SetErrorMessage(FString::Printf( + TEXT("GetInputAtIndex: Could not find a HoudiniInput for InNodeInputIndex %d. Has the HDA been instantiated?"), + InNodeInputIndex)); + return false; + } + + UHoudiniPublicAPIInput* APIInput = nullptr; + const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); + if (!IsValid(APIInput)) + return false; + + OutInput = APIInput; + return bSuccessfullyCopied; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetInputsAtIndices_Implementation(const TMap& InInputs) +{ + bool bAnyFailures = false; + for (const TPair& Entry : InInputs) + { + if (!SetInputAtIndex(Entry.Key, Entry.Value)) + { + SetErrorMessage(FString::Printf( + TEXT("SetInputsAtIndices: Failed to set node input at index %d"), Entry.Key)); + bAnyFailures = true; + } + } + + return !bAnyFailures; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetInputsAtIndices_Implementation(TMap& OutInputs) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + bool bAnyFailures = false; + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const HoudiniInput = HAC->GetInputAt(Index); + if (!IsValid(HoudiniInput) || HoudiniInput->IsObjectPathParameter()) + continue; + + UHoudiniPublicAPIInput* APIInput = nullptr; + const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); + if (!IsValid(APIInput)) + { + bAnyFailures = true; + continue; + } + if (!bSuccessfullyCopied) + bAnyFailures = true; + + OutInputs.Add(HoudiniInput->GetInputIndex(), APIInput); + } + + return !bAnyFailures; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetInputParameter_Implementation(const FName& InParameterName, const UHoudiniPublicAPIInput* InInput) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + UHoudiniInput* HoudiniInput = FindValidHoudiniNodeInputParameter(InParameterName); + if (!IsValid(HoudiniInput)) + { + SetErrorMessage(FString::Printf( + TEXT("SetInputParameter: Could not find a parameter-based HoudiniInput with name %s. Has the HDA been instantiated?"), + *(InParameterName.ToString()))); + return false; + } + + const bool bSuccess = PopulateHoudiniInput(InInput, HoudiniInput); + + // Update the details panel (mostly for when new curves/components are created where visualizers are driven + // through the details panel) + FHoudiniEngineEditorUtils::ReselectComponentOwnerIfSelected(HAC); + + return bSuccess; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetInputParameter_Implementation(const FName& InParameterName, UHoudiniPublicAPIInput*& OutInput) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + const UHoudiniInput* HoudiniInput = FindValidHoudiniNodeInputParameter(InParameterName); + if (!IsValid(HoudiniInput)) + { + SetErrorMessage(FString::Printf( + TEXT("GetInputParameter: Could not find a parameter-based HoudiniInput with name %s. Has the HDA been instantiated?"), + *(InParameterName.ToString()))); + return false; + } + + UHoudiniPublicAPIInput* APIInput = nullptr; + const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); + if (!IsValid(APIInput)) + return false; + + OutInput = APIInput; + return bSuccessfullyCopied; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetInputParameters_Implementation(const TMap& InInputs) +{ + bool bAnyFailures = false; + for (const TPair& Entry : InInputs) + { + if (!SetInputParameter(Entry.Key, Entry.Value)) + { + SetErrorMessage(FString::Printf( + TEXT("SetInputParameters: Failed to set input parameter %s"), *(Entry.Key.ToString()))); + bAnyFailures = true; + } + } + + return !bAnyFailures; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetInputParameters_Implementation(TMap& OutInputs) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + bool bAnyFailures = false; + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const HoudiniInput = HAC->GetInputAt(Index); + if (!IsValid(HoudiniInput) || !HoudiniInput->IsObjectPathParameter()) + continue; + + UHoudiniPublicAPIInput* APIInput = nullptr; + const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); + if (!IsValid(APIInput)) + { + bAnyFailures = true; + continue; + } + if (!bSuccessfullyCopied) + bAnyFailures = true; + + OutInputs.Add(FName(HoudiniInput->GetName()), APIInput); + } + + return !bAnyFailures; +} + +int32 +UHoudiniPublicAPIAssetWrapper::GetNumOutputs_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return -1; + + return HAC->GetNumOutputs(); +} + +EHoudiniOutputType +UHoudiniPublicAPIAssetWrapper::GetOutputTypeAt_Implementation(const int32 InIndex) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return EHoudiniOutputType::Invalid; + + return Output->GetType(); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetOutputIdentifiersAt_Implementation(const int32 InIndex, TArray& OutIdentifiers) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + const TMap& OutputObjects = Output->GetOutputObjects(); + OutIdentifiers.Empty(); + OutIdentifiers.Reserve(OutputObjects.Num()); + for (const TPair& Entry : OutputObjects) + { + OutIdentifiers.Add(FHoudiniPublicAPIOutputObjectIdentifier(Entry.Key)); + } + + return true; +} + +UObject* +UHoudiniPublicAPIAssetWrapper::GetOutputObjectAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return nullptr; + + const TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject const* const OutputObject = OutputObjects.Find(InIdentifier.GetIdentifier()); + if (!OutputObject) + return nullptr; + + return OutputObject->bProxyIsCurrent ? OutputObject->ProxyObject : OutputObject->OutputObject; +} + +UObject* +UHoudiniPublicAPIAssetWrapper::GetOutputComponentAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return nullptr; + + const TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject const* const OutputObject = OutputObjects.Find(InIdentifier.GetIdentifier()); + if (!OutputObject) + return nullptr; + + return OutputObject->bProxyIsCurrent ? OutputObject->ProxyComponent : OutputObject->OutputComponent; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetOutputBakeNameFallbackAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, FString& OutBakeName) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + const TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject const* const OutputObject =OutputObjects.Find(InIdentifier.GetIdentifier()); + if (!OutputObject) + return false; + + OutBakeName = OutputObject->BakeName; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetOutputBakeNameFallbackAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FString& InBakeName) +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject* const OutputObject = OutputObjects.Find(InIdentifier.GetIdentifier()); + if (!OutputObject) + return false; + + OutputObject->BakeName = InBakeName; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::BakeOutputObjectAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FName InBakeName, const EHoudiniLandscapeOutputBakeType InLandscapeBakeType) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + TMap& OutputObjects = Output->GetOutputObjects(); + const FHoudiniOutputObjectIdentifier& Identifier = InIdentifier.GetIdentifier(); + FHoudiniOutputObject* const OutputObject = OutputObjects.Find(Identifier); + if (!OutputObject) + { + SetErrorMessage(FString::Printf( + TEXT("BakeOutputObjectAt: Could not find an output object using the specified identifier."))); + return false; + } + + // Determine the object to bake (this is different depending on landscape, curve or mesh + const EHoudiniOutputType OutputType = Output->GetType(); + + if (OutputType == EHoudiniOutputType::Mesh && OutputObject->bProxyIsCurrent) + { + // Output is currently a proxy, this cannot be baked without cooking first. + SetErrorMessage(FString::Printf( + TEXT("BakeOutputObjectAt: Object is a proxy mesh, please refine it before baking to CB."))); + + return false; + } + + UObject* ObjectToBake = nullptr; + switch (OutputType) + { + case EHoudiniOutputType::Landscape: + { + UHoudiniLandscapePtr* const LandscapePtr = Cast(OutputObject->OutputObject); + if (IsValid(LandscapePtr)) + { + ObjectToBake = LandscapePtr->LandscapeSoftPtr.IsValid() ? LandscapePtr->LandscapeSoftPtr.Get() : nullptr; + } + break; + } + case EHoudiniOutputType::Curve: + ObjectToBake = OutputObject->OutputComponent; + break; + case EHoudiniOutputType::Mesh: + ObjectToBake = OutputObject->OutputObject; + break; + case EHoudiniOutputType::Instancer: + case EHoudiniOutputType::Skeletal: + case EHoudiniOutputType::Invalid: + default: + SetErrorMessage(FString::Printf( + TEXT("BakeOutputObjectAt: unsupported output type (%d) for baking to CB."), OutputType)); + return false; + } + + if (!IsValid(ObjectToBake)) + { + SetErrorMessage(FString::Printf(TEXT("BakeOutputObjectAt: Could not find a valid object to bake to CB."))); + return false; + } + + // Find the corresponding HGPO in the output + FHoudiniGeoPartObject HoudiniGeoPartObject; + for (const auto& HGPO : Output->GetHoudiniGeoPartObjects()) + { + if (!Identifier.Matches(HGPO)) + continue; + + HoudiniGeoPartObject = HGPO; + break; + } + + if (!HoudiniGeoPartObject.IsValid()) + { + SetErrorMessage(TEXT("BakeOutputObjectAt: Could not find a valid HGPO for the output object. Please recook.")); + return false; + } + + TArray AllOutputs; + HAC->GetOutputs(AllOutputs); + + FHoudiniOutputDetails::OnBakeOutputObject( + InBakeName.IsNone() ? OutputObject->BakeName : InBakeName.ToString(), + ObjectToBake, + Identifier, + *OutputObject, + HoudiniGeoPartObject, + HAC, + HAC->BakeFolder.Path, + HAC->TemporaryCookFolder.Path, + OutputType, + InLandscapeBakeType, + AllOutputs); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::HasAnyCurrentProxyOutput_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->HasAnyCurrentProxyOutput(); +} + +bool +UHoudiniPublicAPIAssetWrapper::HasAnyCurrentProxyOutputAt_Implementation(const int32 InIndex) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + return Output->HasAnyCurrentProxy(); +} + +bool +UHoudiniPublicAPIAssetWrapper::IsOutputCurrentProxyAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + return Output->IsProxyCurrent(InIdentifier.GetIdentifier()); +} + +EHoudiniProxyRefineRequestResult +UHoudiniPublicAPIAssetWrapper::RefineAllCurrentProxyOutputs_Implementation(const bool bInSilent) +{ + AHoudiniAssetActor* AssetActor = nullptr; + if (!GetValidHoudiniAssetActorWithError(AssetActor)) + return EHoudiniProxyRefineRequestResult::Invalid; + + return FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes({ AssetActor }, bInSilent); +} + +bool +UHoudiniPublicAPIAssetWrapper::HasPDGAssetLink_Implementation() const +{ + return IsValid(GetHoudiniPDGAssetLink()); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGTOPNetworkPaths_Implementation(TArray& OutTOPNetworkPaths) const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + const uint32 NumNetworks = AssetLink->AllTOPNetworks.Num(); + OutTOPNetworkPaths.Empty(NumNetworks); + for (UTOPNetwork const* const TOPNet : AssetLink->AllTOPNetworks) + { + OutTOPNetworkPaths.Add(TOPNet->NodePath); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGTOPNodePaths_Implementation(const FString& InNetworkRelativePath, TArray& OutTOPNodePaths) const +{ + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* TOPNet = nullptr; + if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, TOPNet)) + return false; + + const uint32 NumNodes = TOPNet->AllTOPNodes.Num(); + OutTOPNodePaths.Empty(NumNodes); + for (UTOPNode const* const TOPNode : TOPNet->AllTOPNodes) + { + OutTOPNodePaths.Add(TOPNode->NodePath); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGDirtyAllNetworks_Implementation() +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + for (UTOPNetwork* const TOPNetwork : AssetLink->AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + FHoudiniPDGManager::DirtyAll(TOPNetwork); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGDirtyNetwork_Implementation(const FString& InNetworkRelativePath) +{ + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* TOPNet = nullptr; + if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, TOPNet)) + return false; + + FHoudiniPDGManager::DirtyAll(TOPNet); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGDirtyNode_Implementation(const FString& InNetworkRelativePath, const FString& InNodeRelativePath) +{ + int32 NetworkIndex = INDEX_NONE; + int32 NodeIndex = INDEX_NONE; + UTOPNode* TOPNode = nullptr; + if (!GetValidTOPNodeByPathWithError(InNetworkRelativePath, InNodeRelativePath, NetworkIndex, NodeIndex, TOPNode)) + return false; + + FHoudiniPDGManager::DirtyTOPNode(TOPNode); + + return true; +} + +// bool +// UHoudiniPublicAPIAssetWrapper::PDGCookOutputsForAllNetworks_Implementation() +// { +// UHoudiniPDGAssetLink* const AssetLink = GetHoudiniPDGAssetLink(); +// if (!IsValid(AssetLink)) +// return false; +// +// for (UTOPNetwork* const TOPNetwork : AssetLink->AllTOPNetworks) +// { +// if (!IsValid(TOPNetwork)) +// continue; +// +// FHoudiniPDGManager::CookOutput(TOPNetwork); +// } +// +// return true; +// } + +bool +UHoudiniPublicAPIAssetWrapper::PDGCookOutputsForNetwork_Implementation(const FString& InNetworkRelativePath) +{ + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* TOPNet = nullptr; + if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, TOPNet)) + return false; + + FHoudiniPDGManager::CookOutput(TOPNet); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGCookNode_Implementation(const FString& InNetworkRelativePath, const FString& InNodeRelativePath) +{ + int32 NetworkIndex = INDEX_NONE; + int32 NodeIndex = INDEX_NONE; + UTOPNode* TOPNode = nullptr; + if (!GetValidTOPNodeByPathWithError(InNetworkRelativePath, InNodeRelativePath, NetworkIndex, NodeIndex, TOPNode)) + return false; + + FHoudiniPDGManager::CookTOPNode(TOPNode); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGBakeAllOutputs_Implementation() +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + return PDGBakeAllOutputsWithSettings( + AssetLink->HoudiniEngineBakeOption, + AssetLink->PDGBakeSelectionOption, + AssetLink->PDGBakePackageReplaceMode, + AssetLink->bRecenterBakedActors); +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGBakeAllOutputsWithSettings_Implementation( + const EHoudiniEngineBakeOption InBakeOption, + const EPDGBakeSelectionOption InBakeSelection, + const EPDGBakePackageReplaceModeOption InBakeReplacementMode, + const bool bInRecenterBakedActors) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + bool bBakeSuccess = false; + switch (InBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + bBakeSuccess = FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors( + AssetLink, + InBakeSelection, + InBakeReplacementMode, + bInRecenterBakedActors); + break; + case EHoudiniEngineBakeOption::ToBlueprint: + bBakeSuccess = FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints( + AssetLink, + InBakeSelection, + InBakeReplacementMode, + bInRecenterBakedActors); + break; + default: + bBakeSuccess = false; + break; + } + + return bBakeSuccess; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGAutoBakeEnabled_Implementation(const bool bInAutoBakeEnabled) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->bBakeAfterAllWorkResultObjectsLoaded = bInAutoBakeEnabled; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::IsPDGAutoBakeEnabled_Implementation() const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + return AssetLink->bBakeAfterAllWorkResultObjectsLoaded; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGBakeMethod_Implementation(const EHoudiniEngineBakeOption InBakeMethod) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->HoudiniEngineBakeOption = InBakeMethod; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGBakeMethod_Implementation(EHoudiniEngineBakeOption& OutBakeMethod) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + OutBakeMethod = AssetLink->HoudiniEngineBakeOption; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGBakeSelection_Implementation(const EPDGBakeSelectionOption InBakeSelection) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->PDGBakeSelectionOption = InBakeSelection; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGBakeSelection_Implementation(EPDGBakeSelectionOption& OutBakeSelection) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + OutBakeSelection = AssetLink->PDGBakeSelectionOption; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGRecenterBakedActors_Implementation(const bool bInRecenterBakedActors) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->bRecenterBakedActors = bInRecenterBakedActors; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGRecenterBakedActors_Implementation() const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + return AssetLink->bRecenterBakedActors; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGBakingReplacementMode_Implementation(const EPDGBakePackageReplaceModeOption InBakingReplacementMode) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->PDGBakePackageReplaceMode = InBakingReplacementMode; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGBakingReplacementMode_Implementation(EPDGBakePackageReplaceModeOption& OutBakingReplacementMode) const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + OutBakingReplacementMode = AssetLink->PDGBakePackageReplaceMode; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::BindToPDGAssetLink() +{ + if (bAssetLinkSetupAttemptComplete) + return true; + + UHoudiniPDGAssetLink* const PDGAssetLink = GetHoudiniPDGAssetLink(); + if (IsValid(PDGAssetLink)) + { + OnPDGPostTOPNetworkCookDelegateHandle = PDGAssetLink->GetOnPostTOPNetworkCookDelegate().AddUFunction(this, TEXT("HandleOnHoudiniPDGAssetLinkTOPNetPostCook")); + OnPDGPostBakeDelegateHandle = PDGAssetLink->GetOnPostBakeDelegate().AddUFunction(this, TEXT("HandleOnHoudiniPDGAssetLinkPostBake")); + bAssetLinkSetupAttemptComplete = true; + + return true; + } + + return false; +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniAssetComponentStateChange(UHoudiniAssetComponent* InHAC, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) +{ + if (!IsValid(InHAC)) + return; + + if (InHAC != GetHoudiniAssetComponent()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniAssetComponentStateChange: unexpected InHAC: %s, expected the wrapper's HAC."), + IsValid(InHAC) ? *InHAC->GetName() : TEXT(""))); + return; + } + + if (InToState == EHoudiniAssetState::PreInstantiation) + { + if (OnPreInstantiationDelegate.IsBound()) + OnPreInstantiationDelegate.Broadcast(this); + } + + if (InFromState == EHoudiniAssetState::Instantiating && InToState == EHoudiniAssetState::PreCook) + { + // PDG link setup / bindings: we have to wait until post instantiation to check if we have an asset link and + // configure bindings + if (!bAssetLinkSetupAttemptComplete) + { + BindToPDGAssetLink(); + bAssetLinkSetupAttemptComplete = true; + } + + if (OnPostInstantiationDelegate.IsBound()) + OnPostInstantiationDelegate.Broadcast(this); + } + + if (InFromState == EHoudiniAssetState::PreProcess) + { + if (OnPreProcessStateExitedDelegate.IsBound()) + OnPreProcessStateExitedDelegate.Broadcast(this); + } + + if (InFromState == EHoudiniAssetState::Processing && InToState == EHoudiniAssetState::None) + { + if (OnPostProcessingDelegate.IsBound()) + OnPostProcessingDelegate.Broadcast(this); + } +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniAssetComponentPostCook(UHoudiniAssetComponent* InHAC, const bool bInCookSuccess) +{ + if (!IsValid(InHAC)) + return; + + if (InHAC != GetHoudiniAssetComponent()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniAssetComponentPostCook: unexpected InHAC: %s, expected the wrapper's HAC."), + IsValid(InHAC) ? *InHAC->GetName() : TEXT(""))); + return; + } + + if (OnPostCookDelegate.IsBound()) + OnPostCookDelegate.Broadcast(this, bInCookSuccess); +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniAssetComponentPostBake(UHoudiniAssetComponent* InHAC, const bool bInBakeSuccess) +{ + if (!IsValid(InHAC)) + return; + + if (InHAC != GetHoudiniAssetComponent()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniAssetComponentPostBake: unexpected InHAC: %s, expected the wrapper's HAC."), + IsValid(InHAC) ? *InHAC->GetName() : TEXT(""))); + return; + } + + if (OnPostBakeDelegate.IsBound()) + OnPostBakeDelegate.Broadcast(this, bInBakeSuccess); +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniPDGAssetLinkTOPNetPostCook(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNetwork* InTOPNet, const bool bInAnyWorkItemsFailed) +{ + if (!IsValid(InPDGAssetLink)) + return; + + if (InPDGAssetLink != GetHoudiniPDGAssetLink()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniPDGAssetLinkTOPNetPostCook: unexpected InPDGAssetLink: %s, expected the wrapper's PDGAssetLink."), + IsValid(InPDGAssetLink) ? *InPDGAssetLink->GetName() : TEXT(""))); + return; + } + + if (OnPostPDGTOPNetworkCookDelegate.IsBound()) + OnPostPDGTOPNetworkCookDelegate.Broadcast(this, !bInAnyWorkItemsFailed); +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniPDGAssetLinkPostBake(UHoudiniPDGAssetLink* InPDGAssetLink, const bool bInBakeSuccess) +{ + if (!IsValid(InPDGAssetLink)) + return; + + if (InPDGAssetLink != GetHoudiniPDGAssetLink()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniPDGAssetLinkPostBake: unexpected InPDGAssetLink: %s, expected the wrapper's PDGAssetLink."), + IsValid(InPDGAssetLink) ? *InPDGAssetLink->GetName() : TEXT(""))); + return; + } + + if (OnPostPDGBakeDelegate.IsBound()) + OnPostPDGBakeDelegate.Broadcast(this, bInBakeSuccess); +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniProxyMeshesRefinedGlobal(UHoudiniAssetComponent* InHAC, const EHoudiniProxyRefineResult InResult) +{ + if (!IsValid(InHAC)) + return; + + if (InHAC != GetHoudiniAssetComponent()) + return; + + if (OnProxyMeshesRefinedDelegate.IsBound()) + OnProxyMeshesRefinedDelegate.Broadcast(this, InResult); +} + +UHoudiniParameter* +UHoudiniPublicAPIAssetWrapper::FindValidParameterByName(const FName& InParameterTupleName) const +{ + AActor* const Actor = GetHoudiniAssetActor(); + const FString ActorName = IsValid(Actor) ? Actor->GetName() : FString(); + + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (!IsValid(HAC)) + { + SetErrorMessage(FString::Printf(TEXT("Could not find HAC on Actor '%s'"), *ActorName)); + return nullptr; + } + + UHoudiniParameter* const Param = HAC->FindParameterByName(InParameterTupleName.ToString()); + if (!IsValid(Param)) + { + SetErrorMessage(FString::Printf( + TEXT("Could not find valid parameter tuple '%s' on '%s'."), + *InParameterTupleName.ToString(), *ActorName)); + return nullptr; + } + + return Param; +} + +bool +UHoudiniPublicAPIAssetWrapper::FindRampPointData( + UHoudiniParameter* const InParam, + const int32 InIndex, + TArray>& OutPointData) const +{ + if (!IsValid(InParam)) + return false; + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!InParam->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !InParam->IsAutoUpdate(); + + const bool bFetchAllPoints = (InIndex == INDEX_NONE); + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = InParam->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(InParam); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *InParam->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(InParam); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *InParam->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter '%s' is not a float or color ramp parameter."), *(InParam->GetName()))); + return false; + } + + if (bUseCachedPoints) + { + // When using the cached points we only have to resize the array + if (FloatRampParam) + { + if (!bFetchAllPoints && !FloatRampParam->CachedPoints.IsValidIndex(InIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("Ramp point index %d is out of range [0, %d]."), InIndex, FloatRampParam->CachedPoints.Num() - 1)); + return false; + } + + if (bFetchAllPoints) + { + // Get all points + OutPointData.Reserve(FloatRampParam->CachedPoints.Num()); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + for (UHoudiniParameterRampFloatPoint* const RampPoint : FloatRampParam->CachedPoints) + { + const bool bIsPointData = true; + OutPointData.Add(TPair(RampPoint, bIsPointData)); + } + } + else + { + OutPointData.Reserve(1); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + + const bool bIsPointData = true; + OutPointData.Add(TPair(FloatRampParam->CachedPoints[InIndex], bIsPointData)); + } + + return true; + } + else + { + if (!bFetchAllPoints && !ColorRampParam->CachedPoints.IsValidIndex(InIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("Ramp point index %d is out of range [0, %d]."), InIndex, ColorRampParam->CachedPoints.Num() - 1)); + return false; + } + + if (bFetchAllPoints) + { + // Get all points + OutPointData.Reserve(ColorRampParam->CachedPoints.Num()); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + for (UHoudiniParameterRampColorPoint* const RampPoint : ColorRampParam->CachedPoints) + { + const bool bIsPointData = true; + OutPointData.Add(TPair(RampPoint, bIsPointData)); + } + } + else + { + OutPointData.Reserve(1); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + + const bool bIsPointData = true; + OutPointData.Add(TPair(ColorRampParam->CachedPoints[InIndex], bIsPointData)); + } + + return true; + } + } + else + { + TSet InstanceIndexesPendingDelete; + int32 NumInsertOps = 0; + TArray& ModificationEvents = FloatRampParam ? FloatRampParam->ModificationEvents : ColorRampParam->ModificationEvents; + for (UHoudiniParameterRampModificationEvent const* const Event : ModificationEvents) + { + if (!IsValid(Event)) + continue; + + if (Event->IsInsertEvent()) + NumInsertOps++; + else if (Event->IsDeleteEvent()) + InstanceIndexesPendingDelete.Add(Event->DeleteInstanceIndex); + } + + const int32 PointsArraySize = FloatRampParam ? FloatRampParam->Points.Num() : ColorRampParam->Points.Num(); + const int32 NumActivePointsInArray = PointsArraySize - InstanceIndexesPendingDelete.Num(); + const int32 TotalNumPoints = NumActivePointsInArray + NumInsertOps; + + // Reserve the expected amount of space needed in the array and reset / destruct existing items so we can + // add from index 0 + if (bFetchAllPoints) + OutPointData.Reserve(TotalNumPoints); + else + OutPointData.Reserve(1); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + + if (bFetchAllPoints || InIndex < NumActivePointsInArray) + { + // Getting all points or point is in the points array + if (FloatRampParam) + { + const int32 ArraySize = FloatRampParam->Points.Num(); + int32 PointIndex = 0; + for (int32 Index = 0; Index < ArraySize && (bFetchAllPoints || PointIndex <= InIndex); ++Index) + { + UHoudiniParameterRampFloatPoint* const PointData = FloatRampParam->Points[Index]; + const bool bIsDeletedPoint = (PointData && InstanceIndexesPendingDelete.Contains(PointData->InstanceIndex)); + if (!bIsDeletedPoint) + { + if (PointIndex == InIndex || bFetchAllPoints) + { + const bool bIsPointData = true; + OutPointData.Add(TPair(PointData, bIsPointData)); + } + + // If we are fetching only the point at InIndex, then we are done here + if (PointIndex == InIndex) + return true; + + PointIndex++; + } + } + } + else + { + const int32 ArraySize = ColorRampParam->Points.Num(); + int32 PointIndex = 0; + for (int32 Index = 0; Index < ArraySize && (bFetchAllPoints || PointIndex <= InIndex); ++Index) + { + UHoudiniParameterRampColorPoint* const PointData = ColorRampParam->Points[Index]; + const bool bIsDeletedPoint = (PointData && InstanceIndexesPendingDelete.Contains(PointData->InstanceIndex)); + if (!bIsDeletedPoint) + { + if (PointIndex == InIndex || bFetchAllPoints) + { + const bool bIsPointData = true; + OutPointData.Add(TPair(PointData, bIsPointData)); + } + + // If we are fetching only the point at InIndex, then we are done here + if (PointIndex == InIndex) + return true; + + PointIndex++; + } + } + } + } + + if (bFetchAllPoints || InIndex < TotalNumPoints) + { + // Point is an insert operation + const int32 NumEvents = ModificationEvents.Num(); + int32 PointIndex = NumActivePointsInArray; + for (int32 Index = 0; Index < NumEvents && (bFetchAllPoints || PointIndex <= InIndex); ++Index) + { + UHoudiniParameterRampModificationEvent* const Event = ModificationEvents[Index]; + if (!IsValid(Event)) + continue; + + if (!Event->IsInsertEvent()) + continue; + + if (PointIndex == InIndex || bFetchAllPoints) + { + const bool bIsPointData = false; + OutPointData.Add(TPair(Event, bIsPointData)); + } + + if (PointIndex == InIndex) + return true; + + PointIndex++; + } + } + else + { + // Point is out of range + SetErrorMessage(FString::Printf( + TEXT("Ramp point index %d is out of range [0, %d]."), InIndex, TotalNumPoints)); + return false; + } + + if (bFetchAllPoints) + { + if (TotalNumPoints != OutPointData.Num()) + { + SetErrorMessage(FString::Printf( + TEXT("Failed to fetch all ramp points. Got %d, expected %d."), OutPointData.Num(), TotalNumPoints)); + return false; + } + + return true; + } + } + + // If we reach this point we didn't find the point + SetErrorMessage(FString::Printf(TEXT("Could not find valid ramp point at index %d."), InIndex)); + return false; +} + +bool UHoudiniPublicAPIAssetWrapper::SetRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPosition, + const float InFloatValue, + const FLinearColor& InColorValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation, + const bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp or color ramp parameter."), *(Param->GetName()))); + return false; + } + + const EHoudiniRampInterpolationType NewInterpolation = UHoudiniPublicAPI::ToHoudiniRampInterpolationType( + InInterpolation); + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + // Get the point at InPointIndex's data + TArray> RampPointData; + if (!FindRampPointData(Param, InPointIndex, RampPointData) || RampPointData.Num() < 1) + return false; + + UObject* const PointData = RampPointData[0].Key; + const bool bIsPointData = RampPointData[0].Value; + if (!IsValid(PointData)) + return false; + + if (bIsPointData) + { + UHoudiniParameterRampFloatPoint* FloatPointData = nullptr; + UHoudiniParameterRampColorPoint* ColorPointData = nullptr; + + if (FloatRampParam) + { + FloatPointData = Cast(PointData); + if (!IsValid(FloatPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + } + else + { + ColorPointData = Cast(PointData); + if (!IsValid(ColorPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + } + + if (bUseCachedPoints) + { + // When setting the cached points, we set the values directly instead of using the setters, but we set + // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed + if (FloatPointData) + { + if (FloatPointData->Position != InPosition) + { + FloatPointData->Position = InPosition; + FloatRampParam->bCaching = true; + } + + if (FloatPointData->Value != InFloatValue) + { + FloatPointData->Value = InFloatValue; + FloatRampParam->bCaching = true; + } + + if (FloatPointData->Interpolation != NewInterpolation) + { + FloatPointData->Interpolation = NewInterpolation; + FloatRampParam->bCaching = true; + } + } + else if (ColorPointData) + { + if (ColorPointData->Position != InPosition) + { + ColorPointData->Position = InPosition; + ColorRampParam->bCaching = true; + } + + if (ColorPointData->Value != InColorValue) + { + ColorPointData->Value = InColorValue; + ColorRampParam->bCaching = true; + } + + if (ColorPointData->Interpolation != NewInterpolation) + { + ColorPointData->Interpolation = NewInterpolation; + ColorRampParam->bCaching = true; + } + } + + // Update the ramp's widget if it is currently visible/selected + const bool bForceFullUpdate = true; + FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); + } + else + { + // When setting the main points, we set the values using the setters on the point data but still manually + // mark the position/value/interpolation parent parameters as changed + if (FloatPointData) + { + if (FloatPointData->Position != InPosition && FloatPointData->PositionParentParm) + { + FloatPointData->SetPosition(InPosition); + if (bInMarkChanged) + FloatPointData->PositionParentParm->MarkChanged(bInMarkChanged); + } + + if (FloatPointData->Value != InFloatValue && FloatPointData->ValueParentParm) + { + FloatPointData->SetValue(InFloatValue); + if (bInMarkChanged) + FloatPointData->ValueParentParm->MarkChanged(bInMarkChanged); + } + + if (FloatPointData->Interpolation != NewInterpolation && FloatPointData->InterpolationParentParm) + { + FloatPointData->SetInterpolation(NewInterpolation); + if (bInMarkChanged) + FloatPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); + } + } + else if (ColorPointData) + { + if (ColorPointData->Position != InPosition && ColorPointData->PositionParentParm) + { + ColorPointData->SetPosition(InPosition); + if (bInMarkChanged) + ColorPointData->PositionParentParm->MarkChanged(bInMarkChanged); + } + + if (ColorPointData->Value != InColorValue && ColorPointData->ValueParentParm) + { + ColorPointData->SetValue(InColorValue); + if (bInMarkChanged) + ColorPointData->ValueParentParm->MarkChanged(bInMarkChanged); + } + + if (ColorPointData->Interpolation != NewInterpolation && ColorPointData->InterpolationParentParm) + { + ColorPointData->SetInterpolation(NewInterpolation); + if (bInMarkChanged) + ColorPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); + } + } + } + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + Event->InsertPosition = InPosition; + if (FloatRampParam) + { + Event->InsertFloat = InFloatValue; + } + else if (ColorRampParam) + { + Event->InsertColor = InColorValue; + } + Event->InsertInterpolation = NewInterpolation; + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPosition, + float& OutFloatValue, + FLinearColor& OutColorValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolation) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp or color ramp parameter."), *(Param->GetName()))); + return false; + } + + TArray> RampPointData; + if (!FindRampPointData(Param, InPointIndex, RampPointData) || RampPointData.Num() < 1) + return false; + + UObject* const PointData = RampPointData[0].Key; + const bool bIsPointData = RampPointData[0].Value; + if (!IsValid(PointData)) + return false; + + if (bIsPointData) + { + UHoudiniParameterRampFloatPoint* FloatPointData = nullptr; + UHoudiniParameterRampColorPoint* ColorPointData = nullptr; + + if (FloatRampParam) + { + FloatPointData = Cast(PointData); + if (!IsValid(FloatPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + } + else + { + ColorPointData = Cast(PointData); + if (!IsValid(ColorPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + } + + // When setting the cached points, we set the values directly instead of using the setters, but we set + // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed + if (FloatPointData) + { + OutPosition = FloatPointData->Position; + OutFloatValue = FloatPointData->Value; + OutInterpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( + FloatPointData->Interpolation); + } + else if (ColorPointData) + { + OutPosition = ColorPointData->Position; + OutColorValue = ColorPointData->Value; + OutInterpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( + ColorPointData->Interpolation); + } + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + OutPosition = Event->InsertPosition; + if (FloatRampParam) + { + OutFloatValue = Event->InsertFloat; + } + else if (ColorRampParam) + { + OutColorValue = Event->InsertColor; + } + OutInterpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(Event->InsertInterpolation); + } + + return true; +} + +UHoudiniInput* +UHoudiniPublicAPIAssetWrapper::GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex) +{ + if (InNodeInputIndex < 0) + return nullptr; + + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return nullptr; + + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + if (Input->GetInputIndex() == InNodeInputIndex) + return Input; + } + + return nullptr; +} + +const UHoudiniInput* +UHoudiniPublicAPIAssetWrapper::GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex) const +{ + if (InNodeInputIndex < 0) + return nullptr; + + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return nullptr; + + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + if (Input->GetInputIndex() == InNodeInputIndex) + return Input; + } + + return nullptr; +} + +UHoudiniInput* +UHoudiniPublicAPIAssetWrapper::FindValidHoudiniNodeInputParameter(const FName& InInputParameterName) +{ + if (InInputParameterName == NAME_None) + return nullptr; + + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return nullptr; + + const FString InputParameterName = InInputParameterName.ToString(); + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + if (Input->IsObjectPathParameter() && Input->GetName() == InputParameterName) + return Input; + } + + return nullptr; +} + +const UHoudiniInput* +UHoudiniPublicAPIAssetWrapper::FindValidHoudiniNodeInputParameter(const FName& InInputParameterName) const +{ + if (InInputParameterName == NAME_None) + return nullptr; + + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return nullptr; + + const FString InputParameterName = InInputParameterName.ToString(); + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + if (Input->IsObjectPathParameter() && Input->GetName() == InputParameterName) + return Input; + } + + return nullptr; +} + +bool +UHoudiniPublicAPIAssetWrapper::CreateAndPopulateAPIInput(const UHoudiniInput* InHoudiniInput, UHoudiniPublicAPIInput*& OutAPIInput) +{ + if (!IsValid(InHoudiniInput)) + return false; + + TSubclassOf APIInputClass; + const EHoudiniInputType InputType = InHoudiniInput->GetInputType(); + switch (InputType) + { + case EHoudiniInputType::Geometry: + APIInputClass = UHoudiniPublicAPIGeoInput::StaticClass(); + break; + case EHoudiniInputType::Curve: + APIInputClass = UHoudiniPublicAPICurveInput::StaticClass(); + break; + case EHoudiniInputType::Asset: + APIInputClass = UHoudiniPublicAPIAssetInput::StaticClass(); + break; + case EHoudiniInputType::World: + APIInputClass = UHoudiniPublicAPIWorldInput::StaticClass(); + break; + case EHoudiniInputType::Landscape: + APIInputClass = UHoudiniPublicAPILandscapeInput::StaticClass(); + break; + case EHoudiniInputType::Skeletal: + // Not yet implemented + SetErrorMessage(FString::Printf(TEXT("GetInputAtIndex: Input type not yet implemented %d"), InputType)); + return false; + case EHoudiniInputType::Invalid: + SetErrorMessage(FString::Printf(TEXT("GetInputAtIndex: Invalid input type %d"), InputType)); + return false; + } + + UHoudiniPublicAPIInput* APIInput = CreateEmptyInput(APIInputClass); + if (!IsValid(APIInput)) + { + return false; + } + + const bool bSuccessfullyCopied = APIInput->PopulateFromHoudiniInput(InHoudiniInput); + OutAPIInput = APIInput; + return bSuccessfullyCopied; +} + +bool +UHoudiniPublicAPIAssetWrapper::PopulateHoudiniInput(const UHoudiniPublicAPIInput* InAPIInput, UHoudiniInput* InHoudiniInput) const +{ + if (!IsValid(InHoudiniInput)) + return false; + + return InAPIInput->UpdateHoudiniInput(InHoudiniInput); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidTOPNetworkByPathWithError(const FString& InNetworkRelativePath, int32& OutNetworkIndex, UTOPNetwork*& OutNetwork) const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* const Network = UHoudiniPDGAssetLink::GetTOPNetworkByNodePath( + InNetworkRelativePath, AssetLink->AllTOPNetworks, NetworkIndex); + if (!IsValid(Network)) + { + SetErrorMessage(FString::Printf( + TEXT("Could not find valid TOP network at relative path '%s'."), *InNetworkRelativePath)); + return false; + } + + OutNetworkIndex = NetworkIndex; + OutNetwork = Network; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidTOPNodeByPathWithError( + const FString& InNetworkRelativePath, + const FString& InNodeRelativePath, + int32& OutNetworkIndex, + int32& OutNodeIndex, + UTOPNode*& OutNode) const +{ + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* Network = nullptr; + if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, Network)) + return false; + + int32 NodeIndex = INDEX_NONE; + UTOPNode* const Node = UHoudiniPDGAssetLink::GetTOPNodeByNodePath( + InNodeRelativePath, Network->AllTOPNodes, NodeIndex); + if (!IsValid(Node)) + { + SetErrorMessage(FString::Printf( + TEXT("Could not find valid TOP node at relative path '%s'."), *InNodeRelativePath)); + return false; + } + + OutNetworkIndex = NetworkIndex; + OutNodeIndex = NodeIndex; + OutNode = Node; + return true; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.h b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIBlueprintLib.cpp similarity index 72% rename from Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.h rename to Source/HoudiniEngineEditor/Private/HoudiniPublicAPIBlueprintLib.cpp index 6830f53b3..1f3802e71 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIBlueprintLib.cpp @@ -1,5 +1,5 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. +/* +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,5 +24,17 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#pragma once +#include "HoudiniPublicAPIBlueprintLib.h" +#include "HoudiniPublicAPI.h" + +UHoudiniPublicAPIBlueprintLib::UHoudiniPublicAPIBlueprintLib(class FObjectInitializer const & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniPublicAPI* UHoudiniPublicAPIBlueprintLib::GetAPI() +{ + static UHoudiniPublicAPI* Obj = NewObject(GetTransientPackage(), NAME_None, RF_MarkAsRootSet); + return Obj; +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp new file mode 100644 index 000000000..ea801d450 --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp @@ -0,0 +1,1011 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPublicAPIInputTypes.h" + +#include "HoudiniPublicAPIAssetWrapper.h" + +#include "HoudiniInput.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" + +UHoudiniPublicAPIInput::UHoudiniPublicAPIInput() +{ + bKeepWorldTransform = false; + bImportAsReference = false; +} + +bool +UHoudiniPublicAPIInput::IsAcceptableObjectForInput_Implementation(UObject* InObject) const +{ + return UHoudiniInput::IsObjectAcceptable(GetInputType(), InObject); +} + +bool +UHoudiniPublicAPIInput::SetInputObjects_Implementation(const TArray& InObjects) +{ + bool bHasFailures = false; + InputObjects.Empty(InObjects.Num()); + for (UObject* const Object : InObjects) + { + if (!IsValid(Object)) + { + SetErrorMessage(FString::Printf(TEXT("An input object is null or invalid."))); + bHasFailures = true; + continue; + } + else if (!IsAcceptableObjectForInput(Object)) + { + SetErrorMessage(FString::Printf( + TEXT("Object '%s' is not of an acceptable type for inputs of class %s."), + *(Object->GetName()), *(GetClass()->GetName()))); + bHasFailures = true; + continue; + } + + InputObjects.Add(Object); + } + + return !bHasFailures; +} + +bool +UHoudiniPublicAPIInput::GetInputObjects_Implementation(TArray& OutObjects) +{ + OutObjects = InputObjects; + + return true; +} + +bool +UHoudiniPublicAPIInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + const EHoudiniInputType InputType = GetInputType(); + if (!IsValid(InInput)) + { + SetErrorMessage(TEXT("InInput is invalid.")); + return false; + } + + if (InInput->GetInputType() != InputType) + { + SetErrorMessage(FString::Printf( + TEXT("Incompatible input types %d vs %d"), InInput->GetInputType(), InputType)); + return false; + } + + bKeepWorldTransform = InInput->GetKeepWorldTransform(); + bImportAsReference = InInput->GetImportAsReference(); + + const TArray* SrcInputObjectsPtr = InInput->GetHoudiniInputObjectArray(InputType); + if (SrcInputObjectsPtr && SrcInputObjectsPtr->Num() > 0) + { + InputObjects.Empty(SrcInputObjectsPtr->Num()); + for (UHoudiniInputObject const* const SrcInputObject : *SrcInputObjectsPtr) + { + if (!IsValid(SrcInputObject)) + continue; + + UObject* NewInputObject = ConvertInternalInputObject(SrcInputObject->GetObject()); + + // TODO: PENDINGKILL replacement ? + if (NewInputObject && NewInputObject->IsPendingKill()) + { + SetErrorMessage(FString::Printf( + TEXT("One of the input objects is non-null but pending kill/invalid."))); + return false; + } + + InputObjects.Add(NewInputObject); + + CopyHoudiniInputObjectProperties(SrcInputObject, NewInputObject); + } + } + + return true; +} + +bool +UHoudiniPublicAPIInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!IsValid(InInput)) + { + SetErrorMessage(TEXT("InInput is invalid.")); + return false; + } + + bool bAnyChanges = false; + + // If the input type didn't change, but the new/incoming InputObjects array is now smaller than the current input + // objects array on the input, delete the surplus objects + const EHoudiniInputType InputType = GetInputType(); + const int32 NumInputObjects = InputObjects.Num(); + if (InputType == InInput->GetInputType()) + { + const int32 OldNumInputObjects = InInput->GetNumberOfInputObjects(); + if (NumInputObjects < OldNumInputObjects) + { + for (int32 Index = OldNumInputObjects - 1; Index >= NumInputObjects; --Index) + { + InInput->DeleteInputObjectAt(Index); + } + bAnyChanges = true; + } + } + else + { + // Set / change the input type + bool bBlueprintStructureModified = false; + InInput->SetInputType(InputType, bBlueprintStructureModified); + } + + // Set any general settings + if (InInput->GetKeepWorldTransform() != bKeepWorldTransform) + { + InInput->SetKeepWorldTransform(bKeepWorldTransform); + bAnyChanges = true; + } + if (InInput->GetImportAsReference() != bImportAsReference) + { + InInput->SetImportAsReference(bImportAsReference); + bAnyChanges = true; + } + + // Copy / set the input objects on the Houdini Input + InInput->SetInputObjectsNumber(InputType, NumInputObjects); + for (int32 Index = 0; Index < NumInputObjects; ++Index) + { + UObject* const InputObject = InputObjects[Index]; + UObject const* CurrentInputObject = InInput->GetInputObjectAt(Index); + + if (!IsValid(InputObject)) + { + // Delete existing input object, but leave its space in the array, we'll set that to nullptr + if (CurrentInputObject) + { + const bool bRemoveIndexFromArray = false; + InInput->DeleteInputObjectAt(Index, bRemoveIndexFromArray); + } + InInput->SetInputObjectAt(Index, nullptr); + + if (!bAnyChanges && CurrentInputObject) + bAnyChanges = true; + } + else + { + UObject const* const NewInputObject = ConvertAPIInputObjectAndAssignToInput(InputObject, InInput, Index); + UHoudiniInputObject *DstHoudiniInputObject = InInput->GetHoudiniInputObjectAt(Index); + if (DstHoudiniInputObject) + CopyPropertiesToHoudiniInputObject(InputObject, DstHoudiniInputObject); + + if (!bAnyChanges && NewInputObject != CurrentInputObject) + bAnyChanges = true; + } + } + + if (bAnyChanges) + { + InInput->MarkChanged(true); + } + + return true; +} + +bool +UHoudiniPublicAPIInput::CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject) +{ + if (!IsValid(InInputObject) || !IsValid(InObject)) + return false; + + return true; +} + +bool +UHoudiniPublicAPIInput::CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const +{ + if (!IsValid(InObject) || !IsValid(InInputObject)) + return false; + + // const EHoudiniInputObjectType InputObjectType = InInputObject->Type; + + if (InInputObject->GetImportAsReference() != bImportAsReference) + { + InInputObject->SetImportAsReference(bImportAsReference); + InInputObject->MarkChanged(true); + } + + // switch (InputObjectType) + // { + // case EHoudiniInputObjectType::StaticMesh: + // case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + // break; + // case EHoudiniInputObjectType::Object: + // case EHoudiniInputObjectType::SkeletalMesh: + // case EHoudiniInputObjectType::HoudiniSplineComponent: + // case EHoudiniInputObjectType::SceneComponent: + // case EHoudiniInputObjectType::StaticMeshComponent: + // case EHoudiniInputObjectType::InstancedStaticMeshComponent: + // case EHoudiniInputObjectType::SplineComponent: + // case EHoudiniInputObjectType::HoudiniAssetComponent: + // case EHoudiniInputObjectType::Actor: + // case EHoudiniInputObjectType::Landscape: + // case EHoudiniInputObjectType::Brush: + // case EHoudiniInputObjectType::CameraComponent: + // case EHoudiniInputObjectType::DataTable: + // case EHoudiniInputObjectType::HoudiniAssetActor: + // break; + // + // case EHoudiniInputObjectType::Invalid: + // return false; + // } + + return true; +} + +UObject* +UHoudiniPublicAPIInput::ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const +{ + if (!IsValid(InHoudiniInput)) + return nullptr; + + UObject const* const CurrentInputObject = InHoudiniInput->GetInputObjectAt(InInputIndex); + + UObject* const ObjectToSet = (IsValid(InAPIInputObject)) ? InAPIInputObject : nullptr; + + // Delete the existing input object if it is invalid or differs from ObjectToSet + if (CurrentInputObject && (!IsValid(CurrentInputObject) || CurrentInputObject != ObjectToSet)) + { + // Keep the space/index in the array, we're going to set the new input object at the same index + const bool bRemoveIndexFromArray = false; + InHoudiniInput->DeleteInputObjectAt(InInputIndex, bRemoveIndexFromArray); + InHoudiniInput->MarkChanged(true); + } + + InHoudiniInput->SetInputObjectAt(InInputIndex, ObjectToSet); + + return ObjectToSet; +} + +UHoudiniPublicAPIGeoInput::UHoudiniPublicAPIGeoInput() +{ + bKeepWorldTransform = false; + bPackBeforeMerge = false; + bExportLODs = false; + bExportSockets = false; + bExportColliders = false; +} + +bool +UHoudiniPublicAPIGeoInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + bPackBeforeMerge = InInput->GetPackBeforeMerge(); + bExportLODs = InInput->GetExportLODs(); + bExportSockets = InInput->GetExportSockets(); + bExportColliders = InInput->GetExportColliders(); + + return true; +} + +bool +UHoudiniPublicAPIGeoInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + bool bAnyChanges = false; + if (InInput->GetPackBeforeMerge() != bPackBeforeMerge) + { + InInput->SetPackBeforeMerge(bPackBeforeMerge); + bAnyChanges = true; + } + if (InInput->GetExportLODs() != bExportLODs) + { + InInput->SetExportLODs(bExportLODs); + bAnyChanges = true; + } + if (InInput->GetExportSockets() != bExportSockets) + { + InInput->SetExportSockets(bExportSockets); + bAnyChanges = true; + } + if (InInput->GetExportColliders() != bExportColliders) + { + InInput->SetExportColliders(bExportColliders); + bAnyChanges = true; + } + + if (bAnyChanges) + { + InInput->MarkChanged(true); + } + + return true; +} + +bool +UHoudiniPublicAPIGeoInput::CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject) +{ + if (!Super::CopyHoudiniInputObjectProperties(InInputObject, InObject)) + return false; + + if (!IsValid(InInputObject) || !IsValid(InObject)) + return false; + + // Copy the transform offset + SetObjectTransformOffset(InObject, InInputObject->Transform); + + return true; +} + + +bool +UHoudiniPublicAPIGeoInput::CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const +{ + if (!Super::CopyPropertiesToHoudiniInputObject(InObject, InInputObject)) + return false; + + if (!IsValid(InObject) || !IsValid(InInputObject)) + return false; + + // Copy the transform offset + FTransform Transform; + if (GetObjectTransformOffset(InObject, Transform)) + { + if (!InInputObject->Transform.Equals(Transform)) + { + InInputObject->Transform = Transform; + InInputObject->MarkChanged(true); + } + } + + return true; +} + +bool +UHoudiniPublicAPIGeoInput::SetObjectTransformOffset_Implementation(UObject* InObject, const FTransform& InTransform) +{ + // Ensure that InObject is valid and has already been added as input object + if (!IsValid(InObject)) + { + SetErrorMessage(TEXT("InObject is invalid.")); + return false; + } + + if (INDEX_NONE == InputObjects.Find(InObject)) + { + SetErrorMessage(FString::Printf( + TEXT("InObject '%s' is not currently set as input object on this input."), *(InObject->GetName()))); + return false; + } + + InputObjectTransformOffsets.Add(InObject, InTransform); + + return true; +} + +bool +UHoudiniPublicAPIGeoInput::GetObjectTransformOffset_Implementation(UObject* InObject, FTransform& OutTransform) const +{ + // Ensure that InObject is valid and has already been added as input object + if (!IsValid(InObject)) + { + SetErrorMessage(TEXT("InObject is invalid.")); + return false; + } + + if (INDEX_NONE == InputObjects.Find(InObject)) + { + SetErrorMessage(FString::Printf( + TEXT("InObject '%s' is not currently set as input object on this input."), *(InObject->GetName()))); + return false; + } + + FTransform const* const TransformPtr = InputObjectTransformOffsets.Find(InObject); + if (!TransformPtr) + { + SetErrorMessage(FString::Printf( + TEXT("InObject '%s' does not have a transform offset set."), *(InObject->GetName()))); + return false; + } + + OutTransform = *TransformPtr; + return true; +} + + +UHoudiniPublicAPICurveInputObject::UHoudiniPublicAPICurveInputObject() + : bClosed(false) + , bReversed(false) + , CurveType(EHoudiniPublicAPICurveType::Polygon) + , CurveMethod(EHoudiniPublicAPICurveMethod::CVs) +{ + +} + + +void +UHoudiniPublicAPICurveInputObject::PopulateFromHoudiniSplineComponent(UHoudiniSplineComponent const* const InSpline) +{ + if (!IsValid(InSpline)) + return; + + bClosed = InSpline->IsClosedCurve(); + bReversed = InSpline->IsReversed(); + CurveType = ToHoudiniPublicAPICurveType(InSpline->GetCurveType()); + CurveMethod = ToHoudiniPublicAPICurveMethod(InSpline->GetCurveMethod()); + CurvePoints = InSpline->CurvePoints; +} + +void +UHoudiniPublicAPICurveInputObject::CopyToHoudiniSplineComponent(UHoudiniSplineComponent* const InSpline) const +{ + if (!IsValid(InSpline)) + return; + + bool bAnyChanges = false; + if (bClosed != InSpline->IsClosedCurve()) + { + InSpline->SetClosedCurve(bClosed); + bAnyChanges = true; + } + if (bReversed != InSpline->IsReversed()) + { + InSpline->SetReversed(bReversed); + bAnyChanges = true; + } + const EHoudiniCurveType HoudiniCurveType = ToHoudiniCurveType(CurveType); + if (HoudiniCurveType != InSpline->GetCurveType()) + { + InSpline->SetCurveType(HoudiniCurveType); + bAnyChanges = true; + } + const EHoudiniCurveMethod HoudiniCurveMethod = ToHoudiniCurveMethod(CurveMethod); + if (HoudiniCurveMethod != InSpline->GetCurveMethod()) + { + InSpline->SetCurveMethod(HoudiniCurveMethod); + bAnyChanges = true; + } + + // Check if there are curve point differences + bool bUpdatePoints = false; + if (CurvePoints.Num() == InSpline->CurvePoints.Num()) + { + const int32 NumPoints = CurvePoints.Num(); + for (int32 Index = 0; Index < NumPoints; ++Index) + { + const FTransform& A = CurvePoints[Index]; + const FTransform& B = InSpline->CurvePoints[Index]; + + if (!A.Equals(B, 0.0f)) + { + bUpdatePoints = true; + break; + } + } + } + else + { + bUpdatePoints = true; + } + + // If there are curve point differences, update the points + if (bUpdatePoints) + { + InSpline->ResetCurvePoints(); + InSpline->ResetDisplayPoints(); + InSpline->CurvePoints = CurvePoints; + bAnyChanges = true; + } + + if (bAnyChanges) + InSpline->MarkChanged(true); +} + +EHoudiniCurveType +UHoudiniPublicAPICurveInputObject::ToHoudiniCurveType(const EHoudiniPublicAPICurveType InCurveType) +{ + switch (InCurveType) + { + case EHoudiniPublicAPICurveType::Invalid: + return EHoudiniCurveType::Invalid; + case EHoudiniPublicAPICurveType::Polygon: + return EHoudiniCurveType::Polygon; + case EHoudiniPublicAPICurveType::Nurbs: + return EHoudiniCurveType::Nurbs; + case EHoudiniPublicAPICurveType::Bezier: + return EHoudiniCurveType::Bezier; + case EHoudiniPublicAPICurveType::Points: + return EHoudiniCurveType::Points; + } + + return EHoudiniCurveType::Invalid; +} + +EHoudiniCurveMethod +UHoudiniPublicAPICurveInputObject::ToHoudiniCurveMethod(const EHoudiniPublicAPICurveMethod InCurveMethod) +{ + switch (InCurveMethod) + { + case EHoudiniPublicAPICurveMethod::Invalid: + return EHoudiniCurveMethod::Invalid; + case EHoudiniPublicAPICurveMethod::CVs: + return EHoudiniCurveMethod::CVs; + case EHoudiniPublicAPICurveMethod::Breakpoints: + return EHoudiniCurveMethod::Breakpoints; + case EHoudiniPublicAPICurveMethod::Freehand: + return EHoudiniCurveMethod::Freehand; + } + + return EHoudiniCurveMethod::Invalid; +} + +EHoudiniPublicAPICurveType +UHoudiniPublicAPICurveInputObject::ToHoudiniPublicAPICurveType(const EHoudiniCurveType InCurveType) +{ + switch (InCurveType) + { + case EHoudiniCurveType::Invalid: + return EHoudiniPublicAPICurveType::Invalid; + case EHoudiniCurveType::Polygon: + return EHoudiniPublicAPICurveType::Polygon; + case EHoudiniCurveType::Nurbs: + return EHoudiniPublicAPICurveType::Nurbs; + case EHoudiniCurveType::Bezier: + return EHoudiniPublicAPICurveType::Bezier; + case EHoudiniCurveType::Points: + return EHoudiniPublicAPICurveType::Points; + } + + return EHoudiniPublicAPICurveType::Invalid; +} + +EHoudiniPublicAPICurveMethod +UHoudiniPublicAPICurveInputObject::ToHoudiniPublicAPICurveMethod(const EHoudiniCurveMethod InCurveMethod) +{ + switch (InCurveMethod) + { + case EHoudiniCurveMethod::Invalid: + return EHoudiniPublicAPICurveMethod::Invalid; + case EHoudiniCurveMethod::CVs: + return EHoudiniPublicAPICurveMethod::CVs; + case EHoudiniCurveMethod::Breakpoints: + return EHoudiniPublicAPICurveMethod::Breakpoints; + case EHoudiniCurveMethod::Freehand: + return EHoudiniPublicAPICurveMethod::Freehand; + } + + return EHoudiniPublicAPICurveMethod::Invalid; +} + +UHoudiniPublicAPICurveInput::UHoudiniPublicAPICurveInput() +{ + bKeepWorldTransform = false; + bCookOnCurveChanged = true; + bAddRotAndScaleAttributesOnCurves = false; +} + +bool +UHoudiniPublicAPICurveInput::IsAcceptableObjectForInput_Implementation(UObject* InObject) const +{ + if (!IsValid(InObject)) + return false; + + if (InObject->IsA()) + return true; + + return Super::IsAcceptableObjectForInput_Implementation(InObject); +} + +bool +UHoudiniPublicAPICurveInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + bCookOnCurveChanged = InInput->GetCookOnCurveChange(); + bAddRotAndScaleAttributesOnCurves = InInput->IsAddRotAndScaleAttributesEnabled(); + + return true; +} + +bool +UHoudiniPublicAPICurveInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + bool bAnyChanges = false; + if (InInput->GetCookOnCurveChange() != bCookOnCurveChanged) + { + InInput->SetCookOnCurveChange(bCookOnCurveChanged); + bAnyChanges = true; + } + if (InInput->IsAddRotAndScaleAttributesEnabled() != bAddRotAndScaleAttributesOnCurves) + { + InInput->SetAddRotAndScaleAttributes(bAddRotAndScaleAttributesOnCurves); + bAnyChanges = true; + } + + if (bAnyChanges) + { + InInput->MarkChanged(true); + } + + return true; +} + +UObject* +UHoudiniPublicAPICurveInput::ConvertInternalInputObject(UObject* InInternalInputObject) +{ + UObject* Object = Super::ConvertInternalInputObject(InInternalInputObject); + + // If the input object is a houdini spline component, convert it to an API curve wrapper + if (IsValid(Object) && Object->IsA()) + { + UHoudiniPublicAPICurveInputObject* const Curve = NewObject( + this, UHoudiniPublicAPICurveInputObject::StaticClass()); + if (IsValid(Curve)) + { + Curve->PopulateFromHoudiniSplineComponent(Cast(Object)); + return Curve; + } + } + + return Object; +} + +UObject* +UHoudiniPublicAPICurveInput::ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const +{ + UObject* Object = nullptr; + + // If the input is an API curve wrapper, convert it to a UHoudiniSplineComponent + if (IsValid(InAPIInputObject) && InAPIInputObject->IsA() && IsValid(InHoudiniInput)) + { + UHoudiniPublicAPICurveInputObject* const InAPICurveInputObject = Cast(InAPIInputObject); + + // If there is an existing input object at this index, and it is a HoudiniSplineComponent, then just update it + // otherwise, create a new input object wrapper + bool bCreateNew = false; + UHoudiniInputObject const* const CurrentHoudiniInputObject = InHoudiniInput->GetHoudiniInputObjectAt(InInputIndex); + UObject* const CurrentInputObject = InHoudiniInput->GetInputObjectAt(InInputIndex); + if (IsValid(CurrentInputObject) && CurrentInputObject->IsA() && + IsValid(CurrentHoudiniInputObject) && CurrentHoudiniInputObject->IsA()) + { + UHoudiniSplineComponent* CurrentSpline = Cast(CurrentInputObject); + if (IsValid(CurrentSpline)) + { + if (IsValid(InAPICurveInputObject)) + { + InAPICurveInputObject->CopyToHoudiniSplineComponent(CurrentSpline); + // Currently the CopyToHoudiniSplineComponent function does not return an indication of if anything + // actually changed, so we have to assume this is a change + + InHoudiniInput->MarkChanged(true); + } + Object = CurrentSpline; + } + else + { + bCreateNew = true; + } + } + else + { + bCreateNew = true; + } + + if (bCreateNew) + { + // Replace any object that is already at this index: we remove the current input object first, then + // we create the new one + if (CurrentInputObject) + { + // Keep the space/index in the array, we're going to set the new input object at the same index + const bool bRemoveIndexFromArray = false; + InHoudiniInput->DeleteInputObjectAt(InInputIndex, bRemoveIndexFromArray); + InHoudiniInput->MarkChanged(true); + } + + UHoudiniInputHoudiniSplineComponent* FromHoudiniSplineInputComponent = nullptr; + const bool bAttachToParent = true; + const bool bAppendToInputArray = false; + bool bBlueprintStructureModified; + UHoudiniInputHoudiniSplineComponent* const NewHoudiniInputObject = InHoudiniInput->CreateHoudiniSplineInput( + FromHoudiniSplineInputComponent, bAttachToParent, bAppendToInputArray, bBlueprintStructureModified); + if (IsValid(NewHoudiniInputObject)) + { + UHoudiniSplineComponent* HoudiniSplineComponent = NewHoudiniInputObject->GetCurveComponent(); + if (IsValid(HoudiniSplineComponent)) + { + // Populate the HoudiniSplineComponent from the curve wrapper + if (IsValid(InAPICurveInputObject)) + InAPICurveInputObject->CopyToHoudiniSplineComponent(HoudiniSplineComponent); + Object = HoudiniSplineComponent; + } + } + + TArray* HoudiniInputObjectArray = InHoudiniInput->GetHoudiniInputObjectArray(InHoudiniInput->GetInputType()); + if (HoudiniInputObjectArray && HoudiniInputObjectArray->IsValidIndex(InInputIndex)) + { + (*HoudiniInputObjectArray)[InInputIndex] = IsValid(NewHoudiniInputObject) ? NewHoudiniInputObject : nullptr; + InHoudiniInput->MarkChanged(true); + } + } + } + else + { + Object = Super::ConvertAPIInputObjectAndAssignToInput(InAPIInputObject, InHoudiniInput, InInputIndex); + } + + return Object; +} + + +UHoudiniPublicAPIAssetInput::UHoudiniPublicAPIAssetInput() +{ + bKeepWorldTransform = true; +} + +bool +UHoudiniPublicAPIAssetInput::IsAcceptableObjectForInput_Implementation(UObject* InObject) const +{ + if (IsValid(InObject) && InObject->IsA()) + { + UHoudiniPublicAPIAssetWrapper* const Wrapper = Cast(InObject); + AHoudiniAssetActor* const AssetActor = Cast(Wrapper->GetHoudiniAssetActor()); + if (IsValid(AssetActor) && IsValid(AssetActor->HoudiniAssetComponent)) + return true; + } + + return Super::IsAcceptableObjectForInput_Implementation(InObject); +} + +bool +UHoudiniPublicAPIAssetInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + return true; +} + +bool +UHoudiniPublicAPIAssetInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + return true; +} + +UObject* +UHoudiniPublicAPIAssetInput::ConvertInternalInputObject(UObject* InInternalInputObject) +{ + // If InInternalInputObject is a Houdini Asset Component or Houdini Asset Actor, wrap it with the API and return + // wrapper. + if (IsValid(InInternalInputObject)) + { + if ((InInternalInputObject->IsA() || InInternalInputObject->IsA()) && + UHoudiniPublicAPIAssetWrapper::CanWrapHoudiniObject(InInternalInputObject)) + { + return UHoudiniPublicAPIAssetWrapper::CreateWrapper(this, InInternalInputObject); + } + } + + return Super::ConvertInternalInputObject(InInternalInputObject); +} + +UObject* +UHoudiniPublicAPIAssetInput::ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const +{ + // If InAPIInputObject is an asset wrapper, extract the underlying HoudiniAssetComponent. + if (IsValid(InAPIInputObject) && InAPIInputObject->IsA()) + { + UHoudiniPublicAPIAssetWrapper* const Wrapper = Cast(InAPIInputObject); + if (Wrapper) + { + UHoudiniAssetComponent* const HAC = Wrapper->GetHoudiniAssetComponent(); + if (IsValid(HAC)) + { + return Super::ConvertAPIInputObjectAndAssignToInput(HAC, InHoudiniInput, InInputIndex); + } + } + } + + return Super::ConvertAPIInputObjectAndAssignToInput(InAPIInputObject, InHoudiniInput, InInputIndex); +} + + +UHoudiniPublicAPIWorldInput::UHoudiniPublicAPIWorldInput() +{ + bKeepWorldTransform = true; + bIsWorldInputBoundSelector = false; + bWorldInputBoundSelectorAutoUpdate = false; + UnrealSplineResolution = 50.0f; +} + +bool +UHoudiniPublicAPIWorldInput::SetInputObjects_Implementation(const TArray& InObjects) +{ + if (bIsWorldInputBoundSelector) + { + SetErrorMessage( + TEXT("This world input is not currently configured as a bound selector (bIsWorldInputBoundSelector == false)")); + return false; + } + + return Super::SetInputObjects_Implementation(InObjects); +} + +bool +UHoudiniPublicAPIWorldInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + TArray const* const BoundSelectorObjectArray = InInput->GetBoundSelectorObjectArray(); + if (BoundSelectorObjectArray) + WorldInputBoundSelectorObjects = *BoundSelectorObjectArray; + else + WorldInputBoundSelectorObjects.Empty(); + bIsWorldInputBoundSelector = InInput->IsWorldInputBoundSelector(); + bWorldInputBoundSelectorAutoUpdate = InInput->GetWorldInputBoundSelectorAutoUpdates(); + UnrealSplineResolution = InInput->GetUnrealSplineResolution(); + + return true; +} + +bool +UHoudiniPublicAPIWorldInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + InInput->SetBoundSelectorObjectsNumber(WorldInputBoundSelectorObjects.Num()); + TArray* const BoundSelectorObjectArray = InInput->GetBoundSelectorObjectArray(); + if (BoundSelectorObjectArray) + *BoundSelectorObjectArray = WorldInputBoundSelectorObjects; + InInput->SetWorldInputBoundSelector(bIsWorldInputBoundSelector); + InInput->SetWorldInputBoundSelectorAutoUpdates(bWorldInputBoundSelectorAutoUpdate); + InInput->SetUnrealSplineResolution(UnrealSplineResolution); + InInput->MarkChanged(true); + + return true; +} + + +UHoudiniPublicAPILandscapeInput::UHoudiniPublicAPILandscapeInput() + : bUpdateInputLandscape(false) + , LandscapeExportType(EHoudiniLandscapeExportType::Heightfield) + , bLandscapeExportSelectionOnly(false) + , bLandscapeAutoSelectComponent(false) + , bLandscapeExportMaterials(false) + , bLandscapeExportLighting(false) + , bLandscapeExportNormalizedUVs(false) + , bLandscapeExportTileUVs(false) +{ + +} + +bool +UHoudiniPublicAPILandscapeInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + bUpdateInputLandscape = InInput->bUpdateInputLandscape; + LandscapeExportType = InInput->GetLandscapeExportType(); + bLandscapeExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; + bLandscapeAutoSelectComponent = InInput->bLandscapeAutoSelectComponent; + bLandscapeExportMaterials = InInput->bLandscapeExportMaterials; + bLandscapeExportLighting = InInput->bLandscapeExportLighting; + bLandscapeExportNormalizedUVs = InInput->bLandscapeExportNormalizedUVs; + bLandscapeExportTileUVs = InInput->bLandscapeExportTileUVs; + + return true; +} + +bool +UHoudiniPublicAPILandscapeInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + bool bAnyChanges = false; + if (InInput->bUpdateInputLandscape != bUpdateInputLandscape) + { + InInput->bUpdateInputLandscape = bUpdateInputLandscape; + bAnyChanges = true; + } + + if (InInput->GetLandscapeExportType() != LandscapeExportType) + { + InInput->SetLandscapeExportType(LandscapeExportType); + InInput->SetHasLandscapeExportTypeChanged(true); + + // Mark each input object as changed as well + TArray* LandscapeInputObjectsArray = InInput->GetHoudiniInputObjectArray(GetInputType()); + if (LandscapeInputObjectsArray) + { + for (UHoudiniInputObject *NextInputObj : *LandscapeInputObjectsArray) + { + if (!NextInputObj) + continue; + NextInputObj->MarkChanged(true); + } + } + + bAnyChanges = true; + } + + if (InInput->bLandscapeExportSelectionOnly != bLandscapeExportSelectionOnly) + { + InInput->bLandscapeExportSelectionOnly = bLandscapeExportSelectionOnly; + bAnyChanges = true; + } + + if (InInput->bLandscapeAutoSelectComponent != bLandscapeAutoSelectComponent) + { + InInput->bLandscapeAutoSelectComponent = bLandscapeAutoSelectComponent; + bAnyChanges = true; + } + + if (InInput->bLandscapeExportMaterials != bLandscapeExportMaterials) + { + InInput->bLandscapeExportMaterials = bLandscapeExportMaterials; + bAnyChanges = true; + } + + if (InInput->bLandscapeExportLighting != bLandscapeExportLighting) + { + InInput->bLandscapeExportLighting = bLandscapeExportLighting; + bAnyChanges = true; + } + + if (InInput->bLandscapeExportNormalizedUVs != bLandscapeExportNormalizedUVs) + { + InInput->bLandscapeExportNormalizedUVs = bLandscapeExportNormalizedUVs; + bAnyChanges = true; + } + + if (InInput->bLandscapeExportTileUVs != bLandscapeExportTileUVs) + { + InInput->bLandscapeExportTileUVs = bLandscapeExportTileUVs; + bAnyChanges = true; + } + + if (bAnyChanges) + { + InInput->MarkChanged(true); + } + + + return true; +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp new file mode 100644 index 000000000..7eb658fcb --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp @@ -0,0 +1,79 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPublicAPIObjectBase.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +UHoudiniPublicAPIObjectBase::UHoudiniPublicAPIObjectBase() + : LastErrorMessage() + , bHasError(false) + , bIsLoggingErrors(true) +{ + +} + +bool +UHoudiniPublicAPIObjectBase::GetLastErrorMessage_Implementation(FString& OutLastErrorMessage) const +{ + if (!bHasError) + { + OutLastErrorMessage = FString(); + return false; + } + + OutLastErrorMessage = LastErrorMessage; + return true; +} + +void +UHoudiniPublicAPIObjectBase::ClearErrorMessages_Implementation() +{ + LastErrorMessage = FString(); + bHasError = false; +} + +void +UHoudiniPublicAPIObjectBase::SetErrorMessage_Implementation( + const FString& InErrorMessage, + const EHoudiniPublicAPIErrorLogOption InLoggingOption) const +{ + LastErrorMessage = InErrorMessage; + bHasError = true; + switch (InLoggingOption) + { + case EHoudiniPublicAPIErrorLogOption::Invalid: + case EHoudiniPublicAPIErrorLogOption::Auto: + case EHoudiniPublicAPIErrorLogOption::Log: + { + static const FString Prefix = TEXT("[HoudiniEngine:PublicAPI]"); + HOUDINI_LOG_WARNING(TEXT("%s %s"), *Prefix, *InErrorMessage); + break; + } + case EHoudiniPublicAPIErrorLogOption::NoLog: + // Don't log + break; + } +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIOutputTypes.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIOutputTypes.cpp new file mode 100644 index 000000000..5dcd99ecb --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIOutputTypes.cpp @@ -0,0 +1,79 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPublicAPIOutputTypes.h" + +#include "HoudiniOutput.h" + +FHoudiniPublicAPIOutputObjectIdentifier::FHoudiniPublicAPIOutputObjectIdentifier() + : SplitIdentifier() + , PartName() + , ObjectId(-1) + , GeoId(-1) + , PartId(-1) + + , PrimitiveIndex(-1) + , PointIndex(-1) + , bLoaded(false) +{ +} + +FHoudiniPublicAPIOutputObjectIdentifier::FHoudiniPublicAPIOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier) + : SplitIdentifier() + , PartName() +{ + SetIdentifier(InIdentifier); +} + +void +FHoudiniPublicAPIOutputObjectIdentifier::SetIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + ObjectId = InIdentifier.ObjectId; + GeoId = InIdentifier.GeoId; + PartId = InIdentifier.PartId; + SplitIdentifier = InIdentifier.SplitIdentifier; + PartName = InIdentifier.PartName; + PrimitiveIndex = InIdentifier.PrimitiveIndex; + PointIndex = InIdentifier.PointIndex; + bLoaded = InIdentifier.bLoaded; +} + +/** Returns the internal output object identifier wrapped by this class. */ +FHoudiniOutputObjectIdentifier +FHoudiniPublicAPIOutputObjectIdentifier::GetIdentifier() const +{ + FHoudiniOutputObjectIdentifier Identifier; + Identifier.ObjectId = ObjectId; + Identifier.GeoId = GeoId; + Identifier.PartId = PartId; + Identifier.SplitIdentifier = SplitIdentifier; + Identifier.PartName = PartName; + Identifier.PrimitiveIndex = PrimitiveIndex; + Identifier.PointIndex = PointIndex; + Identifier.bLoaded = bLoaded; + + return Identifier; +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIProcessHDANode.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIProcessHDANode.cpp new file mode 100644 index 000000000..029b67388 --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIProcessHDANode.cpp @@ -0,0 +1,316 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPublicAPIProcessHDANode.h" + +#include "HoudiniPublicAPI.h" +#include "HoudiniPublicAPIBlueprintLib.h" +#include "HoudiniPublicAPIAssetWrapper.h" +#include "HoudiniPublicAPIInputTypes.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + + +UHoudiniPublicAPIProcessHDANode::UHoudiniPublicAPIProcessHDANode(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + if ( HasAnyFlags(RF_ClassDefaultObject) == false ) + { + AddToRoot(); + } + + AssetWrapper = nullptr; + bCookSuccess = false; + bBakeSuccess = false; + + HoudiniAsset = nullptr; + InstantiateAt = FTransform::Identity; + WorldContextObject = nullptr; + SpawnInLevelOverride = nullptr; + bEnableAutoCook = true; + bEnableAutoBake = false; + BakeDirectoryPath = FString(); + BakeMethod = EHoudiniEngineBakeOption::ToActor; + bRemoveOutputAfterBake = false; + bRecenterBakedActors = false; + bReplacePreviousBake = false; + bDeleteInstantiatedAssetOnCompletionOrFailure = false; +} + +UHoudiniPublicAPIProcessHDANode* +UHoudiniPublicAPIProcessHDANode::ProcessHDA( + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + const TMap& InParameters, + const TMap& InNodeInputs, + const TMap& InParameterInputs, + UObject* InWorldContextObject, + ULevel* InSpawnInLevelOverride, + const bool bInEnableAutoCook, + const bool bInEnableAutoBake, + const FString& InBakeDirectoryPath, + const EHoudiniEngineBakeOption InBakeMethod, + const bool bInRemoveOutputAfterBake, + const bool bInRecenterBakedActors, + const bool bInReplacePreviousBake, + const bool bInDeleteInstantiatedAssetOnCompletionOrFailure) +{ + UHoudiniPublicAPIProcessHDANode* Node = NewObject(); + + Node->HoudiniAsset = InHoudiniAsset; + Node->InstantiateAt = InInstantiateAt; + Node->Parameters = InParameters; + Node->NodeInputs = InNodeInputs; + Node->ParameterInputs = InParameterInputs; + Node->WorldContextObject = InWorldContextObject; + Node->SpawnInLevelOverride = InSpawnInLevelOverride; + Node->bEnableAutoCook = bInEnableAutoCook; + Node->bEnableAutoBake = bInEnableAutoBake; + Node->BakeDirectoryPath = InBakeDirectoryPath; + Node->BakeMethod = InBakeMethod; + Node->bRemoveOutputAfterBake = bInRemoveOutputAfterBake; + Node->bRecenterBakedActors = bInRecenterBakedActors; + Node->bReplacePreviousBake = bInReplacePreviousBake; + Node->bDeleteInstantiatedAssetOnCompletionOrFailure = bInDeleteInstantiatedAssetOnCompletionOrFailure; + + return Node; +} + + +void +UHoudiniPublicAPIProcessHDANode::Activate() +{ + UHoudiniPublicAPI* API = UHoudiniPublicAPIBlueprintLib::GetAPI(); + if (!IsValid(API)) + { + HandleFailure(); + return; + } + + AssetWrapper = UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper(API); + if (!IsValid(AssetWrapper)) + { + HandleFailure(); + return; + } + + AssetWrapper->GetOnPreInstantiationDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreInstantiation); + AssetWrapper->GetOnPostInstantiationDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostInstantiation); + AssetWrapper->GetOnPostCookDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoCook); + AssetWrapper->GetOnPreProcessStateExitedDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreProcess); + AssetWrapper->GetOnPostProcessingDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostProcessing); + AssetWrapper->GetOnPostBakeDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoBake); + + if (!API->InstantiateAssetWithExistingWrapper( + AssetWrapper, + HoudiniAsset, + InstantiateAt, + WorldContextObject, + SpawnInLevelOverride, + bEnableAutoCook, + bEnableAutoBake, + BakeDirectoryPath, + BakeMethod, + bRemoveOutputAfterBake, + bRecenterBakedActors, + bReplacePreviousBake)) + { + HandleFailure(); + return; + } +} + +void +UHoudiniPublicAPIProcessHDANode::UnbindDelegates() +{ + AssetWrapper->GetOnPreInstantiationDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreInstantiation); + AssetWrapper->GetOnPostInstantiationDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostInstantiation); + AssetWrapper->GetOnPostCookDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoCook); + AssetWrapper->GetOnPreProcessStateExitedDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreProcess); + AssetWrapper->GetOnPostProcessingDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostProcessing); + AssetWrapper->GetOnPostBakeDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoBake); +} + +void +UHoudiniPublicAPIProcessHDANode::HandleFailure() +{ + if (Failed.IsBound()) + Failed.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + UnbindDelegates(); + + RemoveFromRoot(); + + if (bDeleteInstantiatedAssetOnCompletionOrFailure && IsValid(AssetWrapper)) + AssetWrapper->DeleteInstantiatedAsset(); +} + +void +UHoudiniPublicAPIProcessHDANode::HandleComplete() +{ + if (Completed.IsBound()) + Completed.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + UnbindDelegates(); + + RemoveFromRoot(); + + if (bDeleteInstantiatedAssetOnCompletionOrFailure && IsValid(AssetWrapper)) + AssetWrapper->DeleteInstantiatedAsset(); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePreInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + // Set any parameters specified when the node was created + if (Parameters.Num() > 0 && IsValid(AssetWrapper)) + { + AssetWrapper->SetParameterTuples(Parameters); + } + + if (PreInstantiation.IsBound()) + PreInstantiation.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePostInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + // Set any inputs specified when the node was created + if (IsValid(AssetWrapper)) + { + if (NodeInputs.Num() > 0) + { + AssetWrapper->SetInputsAtIndices(NodeInputs); + } + if (ParameterInputs.Num() > 0) + { + AssetWrapper->SetInputParameters(ParameterInputs); + } + + // // Set any parameters specified when the node was created + // if (Parameters.Num() > 0) + // { + // AssetWrapper->SetParameterTuples(Parameters); + // } + } + + if (PostInstantiation.IsBound()) + PostInstantiation.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + if (!bEnableAutoCook) + HandleComplete(); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePostAutoCook(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInCookSuccess) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + bCookSuccess = bInCookSuccess; + + if (PostAutoCook.IsBound()) + PostAutoCook.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePreProcess(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + if (PreProcess.IsBound()) + PreProcess.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePostProcessing(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + if (PostProcessing.IsBound()) + PostProcessing.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + if (!bEnableAutoBake) + HandleComplete(); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePostAutoBake(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInBakeSuccess) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + bBakeSuccess = bInBakeSuccess; + + if (PostAutoBake.IsBound()) + PostAutoBake.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + HandleComplete(); +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp index 0a43be77d..2520b6b90 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,9 +66,12 @@ FHoudiniRuntimeSettingsDetails::CustomizeDetails(IDetailLayoutBuilder & DetailBu DetailBuilder.EditCategory("CollisionGeneration", FText::GetEmpty(), ECategoryPriority::Important); DetailBuilder.EditCategory("GeometryMarshalling", FText::GetEmpty(), ECategoryPriority::Important); DetailBuilder.EditCategory("GeometryScalingAndImport", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Static Mesh", FText::GetEmpty(), ECategoryPriority::Important); DetailBuilder.EditCategory("GeneratedStaticMeshSettings", FText::GetEmpty(), ECategoryPriority::Important); DetailBuilder.EditCategory("StaticMeshBuildSettings", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("PDGSettings", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("PDG Settings", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Legacy", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("HoudiniLocation", FText::GetEmpty(), ECategoryPriority::Important); // Create Plugin Information category. { @@ -146,9 +149,7 @@ FHoudiniRuntimeSettingsDetails::CustomizeDetails(IDetailLayoutBuilder & DetailBu CreateHAPILicenseEntry(HAPILicenseType, InformationCategoryBuilder); } - } - - DetailBuilder.EditCategory("HoudiniLocation", FText::GetEmpty(), ECategoryPriority::Important); + } } void diff --git a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h index 60fcf14df..1123bbdcf 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp index 99294fe7c..2b3c71466 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,22 +53,6 @@ IMPLEMENT_HIT_PROXY(HHoudiniSplineVisProxy, HComponentVisProxy); IMPLEMENT_HIT_PROXY(HHoudiniSplineControlPointVisProxy, HHoudiniSplineVisProxy); IMPLEMENT_HIT_PROXY(HHoudiniSplineCurveSegmentVisProxy, HHoudiniSplineVisProxy); -HHoudiniSplineVisProxy::HHoudiniSplineVisProxy(const UActorComponent * InComponent) - : HComponentVisProxy(InComponent, HPP_Wireframe) -{} - -HHoudiniSplineControlPointVisProxy::HHoudiniSplineControlPointVisProxy( - const UActorComponent * InComponent, int32 InControlPointIndex) - : HHoudiniSplineVisProxy(InComponent) - , ControlPointIndex(InControlPointIndex) -{} - -HHoudiniSplineCurveSegmentVisProxy::HHoudiniSplineCurveSegmentVisProxy( - const UActorComponent * InComponent, int32 InDisplayPointIndex) - : HHoudiniSplineVisProxy(InComponent) - , DisplayPointIndex(InDisplayPointIndex) -{} - FHoudiniSplineComponentVisualizerCommands::FHoudiniSplineComponentVisualizerCommands() : TCommands< FHoudiniSplineComponentVisualizerCommands >( "HoudiniSplineComponentVisualizer", @@ -153,9 +137,8 @@ FHoudiniSplineComponentVisualizer::DrawVisualization( { const UHoudiniSplineComponent * HoudiniSplineComponent = Cast< const UHoudiniSplineComponent >(Component); - if (!HoudiniSplineComponent + if (!IsValid(HoudiniSplineComponent) || !PDI - || HoudiniSplineComponent->IsPendingKill() || !HoudiniSplineComponent->IsVisible() || !HoudiniSplineComponent->IsHoudiniSplineVisible()) return; @@ -207,13 +190,14 @@ FHoudiniSplineComponentVisualizer::DrawVisualization( for (int32 Index = 0; Index < DisplayPoints.Num(); ++Index) { const FVector & CurrentPoint = DisplayPoints[Index]; - FVector CurrentPosition = CurrentPoint + HoudiniSplineComponentTransform.GetLocation(); - //CurrentPosition = CurrentPoint; + // Fix incorrect scale when actor has been scaled + //FVector CurrentPosition = CurrentPoint + HoudiniSplineComponentTransform.GetLocation(); + FVector CurrentPosition = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint); if (Index > 0) { // Add a hitproxy for the line segment PDI->SetHitProxy(new HHoudiniSplineCurveSegmentVisProxy(HoudiniSplineComponent, Index)); - // Draw a line connecting the previous point and the current point + // Draw a line connecting the previous point and the current point PDI->DrawLine(PreviousPosition, CurrentPosition, ColorNormal, SDPG_Foreground); PDI->SetHitProxy(nullptr); } @@ -242,7 +226,6 @@ FHoudiniSplineComponentVisualizer::DrawVisualization( if (Index == 1) DrawColor = ColorNormalHandleSecond; - // If this is an point that being editted if (EditedHoudiniSplineComponent == HoudiniSplineComponent && EditedHoudiniSplineComponent->EditedControlPointsIndexes.Contains(Index)) @@ -312,7 +295,7 @@ FHoudiniSplineComponentVisualizer::VisProxyHandleClick( EditedHoudiniSplineComponent = const_cast(HoudiniSplineComponent); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return false; @@ -403,7 +386,7 @@ bool FHoudiniSplineComponentVisualizer::HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return false; if (Key == EKeys::Enter) @@ -472,7 +455,7 @@ void FHoudiniSplineComponentVisualizer::EndEditing() { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return; // Clear edited spline if the EndEditing() function is not called from postUndo @@ -493,7 +476,7 @@ FHoudiniSplineComponentVisualizer::GetWidgetLocation( FVector& OutLocation) const { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return false; TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; @@ -501,17 +484,23 @@ FHoudiniSplineComponentVisualizer::GetWidgetLocation( if (EditedControlPointsIndexes.Num() <= 0) return false; - const TArray< FTransform > & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + const TArray& CurvePoints = EditedHoudiniSplineComponent->CurvePoints; // Set the widget location to the center of mass of the selected control points + int32 Sum = 0; FVector CenterLocation = FVector::ZeroVector; - - for (int i = 0; i < EditedControlPointsIndexes.Num(); ++i) + for (int32 EditedIdx = 0; EditedIdx < EditedControlPointsIndexes.Num(); EditedIdx++) { - CenterLocation += CurvePoints[EditedControlPointsIndexes[i]].GetLocation(); + if (!CurvePoints.IsValidIndex(EditedIdx)) + continue; + + CenterLocation += CurvePoints[EditedControlPointsIndexes[EditedIdx]].GetLocation(); + Sum++; } - CenterLocation /= EditedControlPointsIndexes.Num(); + if(Sum > 0) + CenterLocation /= Sum; + OutLocation = EditedHoudiniSplineComponent->GetComponentTransform().TransformPosition(CenterLocation); return true; @@ -533,7 +522,7 @@ FHoudiniSplineComponentVisualizer::HandleInputDelta( FVector& DeltaScale) { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!ViewportClient || !EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!ViewportClient || !IsValid(EditedHoudiniSplineComponent)) return false; TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; @@ -613,7 +602,7 @@ FHoudiniSplineComponentVisualizer::GenerateContextMenu() const // Create the context menu section UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + if (IsValid(EditedHoudiniSplineComponent)) { MenuBuilder.AddMenuEntry( FHoudiniSplineComponentVisualizerCommands::Get().CommandAddControlPoint, @@ -653,7 +642,7 @@ int32 FHoudiniSplineComponentVisualizer::OnInsertControlPointWithoutUpdate() { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return -1; TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; @@ -700,7 +689,7 @@ void FHoudiniSplineComponentVisualizer::OnInsertControlPoint() { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return; int32 NewPointIndex = OnInsertControlPointWithoutUpdate(); @@ -726,7 +715,7 @@ void FHoudiniSplineComponentVisualizer::OnAddControlPoint() { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return; TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; @@ -811,7 +800,7 @@ bool FHoudiniSplineComponentVisualizer::IsAddControlPointValid() const { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - return EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill() && + return IsValid(EditedHoudiniSplineComponent) && EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; } @@ -819,7 +808,7 @@ void FHoudiniSplineComponentVisualizer::OnDeleteControlPoint() { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return; TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; @@ -862,7 +851,7 @@ bool FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid() const { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return false; TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; @@ -881,7 +870,7 @@ void FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint() { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return; TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; @@ -925,7 +914,7 @@ bool FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid() const { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if(!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill() + if(!IsValid(EditedHoudiniSplineComponent) || EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() == 0) return false; @@ -936,7 +925,7 @@ void FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints() { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + if (IsValid(EditedHoudiniSplineComponent)) EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); } @@ -944,7 +933,7 @@ bool FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid() const { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + if (IsValid(EditedHoudiniSplineComponent)) return EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; return false; @@ -956,7 +945,7 @@ FHoudiniSplineComponentVisualizer::AddControlPointAfter( const int32 & nIndex) { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return nIndex; const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h index 17a006068..d98c976d0 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,6 +28,7 @@ #include "HoudiniSplineComponent.h" +#include "HitProxies.h" #include "ComponentVisualizer.h" #include "Framework/Commands/UICommandList.h" #include "Framework/Commands/Commands.h" @@ -38,14 +39,19 @@ class FEditorViewportClient; struct HHoudiniSplineVisProxy : public HComponentVisProxy { DECLARE_HIT_PROXY(); - HHoudiniSplineVisProxy(const UActorComponent * InComponent); + HHoudiniSplineVisProxy(const UActorComponent * InComponent) + : HComponentVisProxy(InComponent, HPP_Wireframe) + {} }; /** Proxy for a spline control point. **/ struct HHoudiniSplineControlPointVisProxy : public HHoudiniSplineVisProxy { DECLARE_HIT_PROXY(); - HHoudiniSplineControlPointVisProxy(const UActorComponent * InComponent, int32 InControlPointIndex); + HHoudiniSplineControlPointVisProxy(const UActorComponent * InComponent, int32 InControlPointIndex) + : HHoudiniSplineVisProxy(InComponent) + , ControlPointIndex(InControlPointIndex) + {} int32 ControlPointIndex; }; @@ -54,7 +60,10 @@ struct HHoudiniSplineControlPointVisProxy : public HHoudiniSplineVisProxy struct HHoudiniSplineCurveSegmentVisProxy : public HHoudiniSplineVisProxy { DECLARE_HIT_PROXY(); - HHoudiniSplineCurveSegmentVisProxy(const UActorComponent * InComponent, int32 IndisplayPointIndex); + HHoudiniSplineCurveSegmentVisProxy(const UActorComponent * InComponent, int32 InDisplayPointIndex) + : HHoudiniSplineVisProxy(InComponent) + , DisplayPointIndex(InDisplayPointIndex) + {} int32 DisplayPointIndex; }; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp b/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp index 685d4d24c..7f8046a09 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineEditor/Private/HoudiniTool.h b/Source/HoudiniEngineEditor/Private/HoudiniTool.h index 76b955f05..87d50681e 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniTool.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniTool.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,7 +24,6 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - #pragma once UENUM() diff --git a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp index 044461aec..85fca9b51 100644 --- a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp +++ b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp @@ -1,24 +1,27 @@ /* -* Copyright (c) <2017> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. * -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: * -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. * -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. * +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "SNewFilePathPicker.h" @@ -346,4 +349,5 @@ void SNewFilePathPicker::HandleTextBoxTextCommitted( const FText& NewText, EText OnPathPicked.ExecuteIfBound(NewText.ToString()); } -#undef LOCTEXT_NAMESPACE \ No newline at end of file +#undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h index 3892580f5..bfd0bf407 100644 --- a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h +++ b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h @@ -1,24 +1,27 @@ /* -* Copyright (c) <2017> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. * -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: * -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. * -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. * +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // This is the same as SFilePathPicker but it uses SaveFileDialog instead of OpenFileDialog @@ -145,3 +148,4 @@ class SNewFilePathPicker /** Holds a delegate that is executed when a file was picked. */ FOnPathPicked OnPathPicked; }; + diff --git a/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp new file mode 100644 index 000000000..20695a5e5 --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp @@ -0,0 +1,450 @@ +#if WITH_DEV_AUTOMATION_TESTS +#include "HoudiniEditorTestUtils.h" +#include "IAssetViewport.h" +#include "Slate/SceneViewport.h" +#include "Widgets/SViewport.h" +#include "FileHelpers.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" +#include "LevelEditor.h" +#include "AssetRegistryModule.h" +#include "Core/Public/HAL/FileManager.h" +#include "Core/Public/HAL/PlatformFilemanager.h" +#include "Editor/EditorPerformanceSettings.h" +#include "Engine/Selection.h" +#include "Interfaces/IMainFrameModule.h" +#include "Misc/AutomationTest.h" +#include "Tests/AutomationCommon.h" + +const FVector2D FHoudiniEditorTestUtils::GDefaultEditorSize = FVector2D(1280, 720); + +void FHoudiniEditorTestUtils::InitializeTests(FAutomationTestBase* Test) +{ + FHoudiniEditorTestUtils::GetMainFrameWindow()->Resize(GDefaultEditorSize); + FEditorFileUtils::LoadMap(TEXT("/Game/TestLevel"), false, false); +} + +UObject* FHoudiniEditorTestUtils::FindAssetUObject(FAutomationTestBase* Test, const FName AssetUObjectPath) +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( "AssetRegistry" ); + TArray AssetData; + AssetRegistryModule.Get().GetAssetsByPackageName( AssetUObjectPath, AssetData ); + if( AssetData.Num() > 0 ) + { + return AssetData[ 0 ].GetAsset(); + } + + Test->AddError(FString::Printf(TEXT("Could not find UObject: %s"), *AssetUObjectPath.ToString())); + return nullptr; +} + +UHoudiniAssetComponent* FHoudiniEditorTestUtils::InstantiateAsset(FAutomationTestBase* Test, + const FName AssetUObjectPath, TFunction OnFinishInstantiate, const bool ErrorOnFail) +{ + SetUseLessCPUInTheBackground(); + + UHoudiniAsset * HoudiniAsset = Cast(FindAssetUObject(Test, AssetUObjectPath)); + + if (!HoudiniAsset) + { + Test->AddError(FString::Printf(TEXT("Could not find UObject: %s"), *AssetUObjectPath.ToString())); + return nullptr; + } + + + FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, FTransform::Identity); + + USelection* SelectedActors = GEditor->GetSelectedActors(); + TArray Actors; + TArray UniqueLevels; + for (FSelectionIterator Iter(*SelectedActors); Iter; ++Iter) + { + AActor* Actor = Cast(*Iter); + if (Actor) + { + Actors.Add(Actor); + } + } + Test->TestEqual(TEXT("Only one actor should be selected"), Actors.Num(), 1); + + AActor* TheActor = Actors[0]; + UHoudiniAssetComponent * HoudiniComponent = TheActor->FindComponentByClass(); + + + // Need to allocate on heap otherwise it will be garbage collected. + bool * FinishedCook = new bool(false); + bool * CookSuccessful = new bool(false); + FDelegateHandle * PostCookDelegateHandle = new FDelegateHandle(); + + auto OnPostCookLambda = [=](UHoudiniAssetComponent* HAC, bool IsSuccess) + { + if (FinishedCook != nullptr && CookSuccessful != nullptr) + { + *FinishedCook = true; + *CookSuccessful = IsSuccess; + if (PostCookDelegateHandle != nullptr) + HoudiniComponent->GetOnPostCookDelegate().Remove(*PostCookDelegateHandle); + } + + }; + + *PostCookDelegateHandle = HoudiniComponent->GetOnPostCookDelegate().AddLambda(OnPostCookLambda); + + Test->AddCommand(new FFunctionLatentCommand([=]() + { + const bool FinishedCookResult = *FinishedCook; + const bool CookSuccessfulResult = *CookSuccessful; + + if (FinishedCookResult == true && HoudiniComponent->GetAssetState() == EHoudiniAssetState::None) + { + if (ErrorOnFail && CookSuccessfulResult == false) + { + Test->AddError(FString::Printf(TEXT("Cook was unsuccessful: %s"), *AssetUObjectPath.ToString())); + } + + ForceRefreshViewport(); + + OnFinishInstantiate(HoudiniComponent, CookSuccessfulResult); + delete FinishedCook; + delete CookSuccessful; + delete PostCookDelegateHandle; + + return true; + } + + return false; + } + )); + + return HoudiniComponent; +} + +void FHoudiniEditorTestUtils::SetUseLessCPUInTheBackground() +{ + // Equivalent of setting Edit > Editor Preferences > General > Performance > "Use less CPU when in background" is OFF + // This ensures that objects are rendered even in the background + UEditorPerformanceSettings* Settings = GetMutableDefault(); + Settings->bThrottleCPUWhenNotForeground = false; + Settings->bMonitorEditorPerformance = false; + Settings->PostEditChange(); +} + +TSharedPtr FHoudiniEditorTestUtils::GetMainFrameWindow() +{ + + if (FModuleManager::Get().IsModuleLoaded("MainFrame")) + { + // Check if the main frame is loaded. When using the old main frame it may not be. + IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); + return MainFrame.GetParentWindow(); + } + + return nullptr; +} + +TSharedPtr FHoudiniEditorTestUtils::GetActiveTopLevelWindow() +{ + return FSlateApplication::Get().GetActiveTopLevelWindow(); +} + +static bool ShouldShowProperty(const FPropertyAndParent& PropertyAndParent, bool bHaveTemplate) +{ + const FProperty& Property = PropertyAndParent.Property; + + if ( bHaveTemplate ) + { + const UClass* PropertyOwnerClass = Property.GetOwner(); + const bool bDisableEditOnTemplate = PropertyOwnerClass + && PropertyOwnerClass->IsNative() + && Property.HasAnyPropertyFlags(CPF_DisableEditOnTemplate); + if(bDisableEditOnTemplate) + { + return false; + } + } + return true; +} + +TSharedPtr FHoudiniEditorTestUtils::CreateNewDetailsWindow() +{ + if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) + { + // Check if the main frame is loaded. When using the old main frame it may not be. + FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked("PropertyEditor"); + + USelection* SelectedActors = GEditor->GetSelectedActors(); + TArray Actors; + for (FSelectionIterator Iter(*SelectedActors); Iter; ++Iter) + { + AActor* Actor = Cast(*Iter); + if (Actor) + { + Actors.Add(Actor); + } + } + + TSharedRef Details = PropertyEditorModule.CreateFloatingDetailsView(Actors, false);// + + return Details; + } + + return nullptr; +} + +TSharedPtr FHoudiniEditorTestUtils::CreateViewportWindow() +{ + if (!FModuleManager::Get().IsModuleLoaded("LevelEditor")) + { + return nullptr; + } + + TSharedRef NewSlateWindow = SNew(SWindow) + .Title( NSLOCTEXT("ViewportEditor", "WindowTitle", "Viewport Editor") ) + .ClientSize( FVector2D(400, 550) ); + + // If the main frame exists parent the window to it + TSharedPtr< SWindow > ParentWindow; + if( FModuleManager::Get().IsModuleLoaded( "MainFrame" ) ) + { + IMainFrameModule& MainFrame = FModuleManager::GetModuleChecked( "MainFrame" ); + ParentWindow = MainFrame.GetParentWindow(); + } + + if( ParentWindow.IsValid() ) + { + // Parent the window to the main frame + FSlateApplication::Get().AddWindowAsNativeChild( NewSlateWindow, ParentWindow.ToSharedRef() ); + } + else + { + FSlateApplication::Get().AddWindow( NewSlateWindow ); + } + + FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked("LevelEditor"); + TSharedPtr Viewport = LevelEditor.GetFirstActiveViewport(); + + NewSlateWindow->SetContent( + SNew(SBorder) + [ + Viewport->AsWidget() + ] + ); + + return NewSlateWindow; +} + +void FHoudiniEditorTestUtils::TakeScreenshotEditor(FAutomationTestBase* Test, const FString ScreenshotName, const EEditorScreenshotType EditorScreenshotType, const FVector2D Size) +{ + // Wait one frame just in case for pending redraws + Test->AddCommand(new FDelayedFunctionLatentCommand( [=]() + { + TSharedPtr ScreenshotWindow; + + bool DestroyWindowOnEnd = false; + + switch (EditorScreenshotType) + { + case EEditorScreenshotType::ENTIRE_EDITOR: + ScreenshotWindow = GetMainFrameWindow(); + break; + case EEditorScreenshotType::ACTIVE_WINDOW: + ScreenshotWindow = GetActiveTopLevelWindow(); + break; + case EEditorScreenshotType::DETAILS_WINDOW: + ScreenshotWindow = CreateNewDetailsWindow(); + DestroyWindowOnEnd = true; + break; + case EEditorScreenshotType::VIEWPORT: + ScreenshotWindow = CreateViewportWindow(); + DestroyWindowOnEnd = true; + break; + default: + break; + } + + if (!ScreenshotWindow) + { + return; + } + + WindowScreenshotParameters ScreenshotParameters; + ScreenshotParameters.ScreenshotName = ScreenshotName; + ScreenshotParameters.CurrentWindow = ScreenshotWindow; + + // Creates a file in Engine\Saved\Automation\Tmp + TSharedRef WidgetToFind = ScreenshotWindow.ToSharedRef(); + + bool ScreenshotSuccessful; + + TArray OutImageData; + FIntVector OutImageSize; + + bool bRenderOffScreen = FParse::Param(FCommandLine::Get(), TEXT("RenderOffScreen")); + + if (!bRenderOffScreen) + { + // Take a screenshot like a normal person + // Note that this sizing method is slightly different than the offscreen one, so DO NOT copy the result to SVN + ScreenshotWindow->Resize(Size); + ScreenshotSuccessful = FSlateApplication::Get().TakeScreenshot(WidgetToFind, OutImageData, OutImageSize); + } + else + { + // Rendering offscreen results in a slightly different render pipeline. + // Resizing as usual doesn't seem to work unless we do it in this very specific way + // Mostly copied from FSlateApplication::Get().TakeScreenshot(WindowRef, OutImageData, OutImageSize) , but forces size + FWidgetPath WidgetPath; + FSlateApplication::Get().GeneratePathToWidgetChecked(WidgetToFind, WidgetPath); + + FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(WidgetToFind).Get(FArrangedWidget::GetNullWidget()); + FVector2D Position = ArrangedWidget.Geometry.AbsolutePosition; + FVector2D WindowPosition = ScreenshotWindow->GetPositionInScreen(); + + FIntRect ScreenshotRect = FIntRect(0, 0, (int32)Size.X, (int32)Size.Y); + + ScreenshotRect.Min.X += ( Position.X - WindowPosition.X ); + ScreenshotRect.Min.Y += ( Position.Y - WindowPosition.Y ); + ScreenshotRect.Max.X += ( Position.X - WindowPosition.X ); + ScreenshotRect.Max.Y += ( Position.Y - WindowPosition.Y ); + + FSlateApplication::Get().GetRenderer()->RequestResize(ScreenshotWindow, Size.X, Size.Y); + ScreenshotWindow->Resize(Size); + FSlateApplication::Get().ForceRedrawWindow(ScreenshotWindow.ToSharedRef()); + + FSlateApplication::Get().GetRenderer()->PrepareToTakeScreenshot(ScreenshotRect, &OutImageData, ScreenshotWindow.Get()); + FSlateApplication::Get().ForceRedrawWindow(ScreenshotWindow.ToSharedRef()); + ScreenshotSuccessful = (ScreenshotRect.Size().X != 0 && ScreenshotRect.Size().Y != 0 && OutImageData.Num() >= ScreenshotRect.Size().X * ScreenshotRect.Size().Y); + OutImageSize.X = ScreenshotRect.Size().X; + OutImageSize.Y = ScreenshotRect.Size().Y; + } + + if (!ScreenshotSuccessful) + { + Test->AddError("Taking screenshot not successful!"); + return; + } + + FAutomationScreenshotData Data; + Data.Width = OutImageSize.X; + Data.Height = OutImageSize.Y; + Data.ScreenshotName = ScreenshotName; + + FAutomationTestFramework::Get().OnScreenshotCaptured().ExecuteIfBound(OutImageData, Data); + + WaitForScreenshotAndCopy(Test, ScreenshotName, [=] (FAutomationTestBase* AutomationTest, FString BaseName) + { + CopyScreenshotToTestFolder(AutomationTest, BaseName); + + if (DestroyWindowOnEnd) + { + ScreenshotWindow->RequestDestroyWindow(); + } + }); + + }, 0.1f)); +} + +void FHoudiniEditorTestUtils::TakeScreenshotViewport(FAutomationTestBase* Test, const FString ScreenshotName) +{ + Test->AddCommand(new FDelayedFunctionLatentCommand([=]() + { + const FString BaseName = ScreenshotName; + const FString ScreenshotPath = GetUnrealTestDirectory() + BaseName; + FScreenshotRequest::RequestScreenshot(ScreenshotPath, false, false); + ForceRefreshViewport(); + + WaitForScreenshotAndCopy(Test, BaseName, CopyScreenshotToTestFolder); + }, 0.1f)); +} + +void FHoudiniEditorTestUtils::WaitForScreenshotAndCopy(FAutomationTestBase* Test, FString BaseName, TFunction OnScreenshotGenerated) +{ + const FString TestDirectory = GetUnrealTestDirectory(); + const FString FileName = TestDirectory + BaseName; + + // Wait for screenshot to finish generating, and then copy to $RT/hapi/unreal/ + Test->AddCommand(new FFunctionLatentCommand([=]() + { + IPlatformFile& FileManager = FPlatformFileManager::Get().GetPlatformFile(); + if (FileManager.FileExists(*FileName)) + { + OnScreenshotGenerated(Test, BaseName); + return true; + } + else + { + ForceRefreshViewport(); + return false; + } + })); +} + +void FHoudiniEditorTestUtils::CopyScreenshotToTestFolder(FAutomationTestBase* Test, FString BaseName) +{ + const FString TestDirectory = GetUnrealTestDirectory(); + const FString FileName = TestDirectory + BaseName; + FString DestFileName = GetTestDirectory(); + if (!DestFileName.IsEmpty()) + { + DestFileName += FormatScreenshotOutputName(BaseName); + } + + IPlatformFile& FileManager = FPlatformFileManager::Get().GetPlatformFile(); + + // Copy output file to our test directory, if it exists. + if (!DestFileName.IsEmpty()) + { + UE_LOG(LogTemp, Verbose, TEXT("Copied file to: %s"), *DestFileName); + FileManager.CopyFile(*DestFileName, *FileName); + } + else + { + UE_LOG(LogTemp, Warning, TEXT("Unable to copy file!")); + } + + Test->AddCommand(new FFunctionLatentCommand([=]() + { + IPlatformFile& CopyFileManager = FPlatformFileManager::Get().GetPlatformFile(); + if (CopyFileManager.FileExists(*FileName)) + { + return true; + } + else + { + return false; + } + })); + +} + +FString FHoudiniEditorTestUtils::GetTestDirectory() +{ + return FPlatformMisc::GetEnvironmentVariable(TEXT("TEST_OUTPUT_DIR")) + FPlatformMisc::GetDefaultPathSeparator(); +} + +FString FHoudiniEditorTestUtils::GetUnrealTestDirectory() +{ + //return FPaths::AutomationDir() + "/Incoming/"; // 4.25 + return FPaths::AutomationTransientDir(); // 4.26 +} + +FString FHoudiniEditorTestUtils::FormatScreenshotOutputName(FString BaseName) +{ + const FString Prefix = FPlatformMisc::GetEnvironmentVariable(TEXT("TEST_PREFIX")); + return FString::Printf(TEXT("%s_%s"), *Prefix, *BaseName); +} + +void FHoudiniEditorTestUtils::ForceRefreshViewport() +{ + // Force redraws viewport even if not in focus to prevent hanging + FLevelEditorModule &PropertyEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); + if (FModuleManager::Get().IsModuleLoaded("LevelEditor")) + { + PropertyEditorModule.BroadcastRedrawViewports(false); + } +} + +#endif diff --git a/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.h b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.h new file mode 100644 index 000000000..0d2e678de --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.h @@ -0,0 +1,58 @@ +#pragma once + +#if WITH_DEV_AUTOMATION_TESTS + +#include "CoreMinimal.h" + +class UHoudiniAssetComponent; +class ULevel; +class SWindow; + +class FHoudiniEditorTestUtils +{ +public: + enum EEditorScreenshotType + { + ENTIRE_EDITOR, + ACTIVE_WINDOW, // Gets the active window. Probably never use this. + DETAILS_WINDOW, + VIEWPORT + }; + + static void InitializeTests(FAutomationTestBase* Test); + + static UObject* FindAssetUObject(FAutomationTestBase* Test, const FName AssetUObjectPath); + + static UHoudiniAssetComponent* InstantiateAsset(FAutomationTestBase* Test, const FName AssetUObjectPath, TFunction OnFinishInstantiate, const bool ErrorOnFail = true); + + static void TakeScreenshotEditor(FAutomationTestBase* Test, const FString ScreenshotName, const EEditorScreenshotType EditorScreenshotType, const FVector2D Size); + + static void TakeScreenshotViewport(FAutomationTestBase* Test, const FString ScreenshotName); + + static void SetUseLessCPUInTheBackground(); + + static TSharedPtr GetMainFrameWindow(); + + static TSharedPtr GetActiveTopLevelWindow(); + + static TSharedPtr CreateNewDetailsWindow(); + + static TSharedPtr CreateViewportWindow(); + + static const FVector2D GDefaultEditorSize; + +private: + static void WaitForScreenshotAndCopy(FAutomationTestBase* Test, FString BaseName, TFunction OnScreenshotGenerated); + + static void CopyScreenshotToTestFolder(FAutomationTestBase* Test, FString BaseName); + + static FString GetTestDirectory(); + + static FString GetUnrealTestDirectory(); + + static FString FormatScreenshotOutputName(FString BaseName); + + static void ForceRefreshViewport(); +}; + +#endif \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTests.cpp b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTests.cpp new file mode 100644 index 000000000..4fe68b1ae --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTests.cpp @@ -0,0 +1,31 @@ +#include "HoudiniEditorTests.h" + + +#if WITH_DEV_AUTOMATION_TESTS +#include "HoudiniEditorTestUtils.h" + +#include "Core/Public/HAL/FileManager.h" +#include "Misc/AutomationTest.h" +#include "HoudiniAssetComponent.h" + + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(HoudiniEditorEvergreenTest, "Houdini.Editor.EvergreenScreenshots", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter) + +bool HoudiniEditorEvergreenTest::RunTest(const FString & Parameters) +{ + // Really force editor size + // TODO: Move to HoudiniEditorUtils + FHoudiniEditorTestUtils::InitializeTests(this); + + FHoudiniEditorTestUtils::InstantiateAsset(this, TEXT("/Game/TestHDAs/Evergreen"), + [=](UHoudiniAssetComponent * HAC, const bool IsSuccessful) + { + FHoudiniEditorTestUtils::TakeScreenshotEditor(this, "EverGreen_EntireEditor.png", FHoudiniEditorTestUtils::ENTIRE_EDITOR, FHoudiniEditorTestUtils::GDefaultEditorSize); + FHoudiniEditorTestUtils::TakeScreenshotEditor(this, "EverGreen_Details.png", FHoudiniEditorTestUtils::DETAILS_WINDOW, FVector2D(400, 1130)); + FHoudiniEditorTestUtils::TakeScreenshotEditor(this, "EverGreen_EditorViewport.png", FHoudiniEditorTestUtils::VIEWPORT, FVector2D(640, 360)); + //FHoudiniEditorTestUtils::TakeScreenshotViewport(this, "EverGreen_Viewport.png"); // Viewport resolution might be inconsisent + }); + return true; +} + +#endif diff --git a/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTests.h b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTests.h new file mode 100644 index 000000000..a2f02c8f9 --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTests.h @@ -0,0 +1,6 @@ +#pragma once +#if WITH_DEV_AUTOMATION_TESTS + +#include "CoreMinimal.h" + +#endif diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h new file mode 100644 index 000000000..00f5b1bff --- /dev/null +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h @@ -0,0 +1,209 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" + +#include "HoudiniPublicAPIInputTypes.h" +#include "HoudiniPublicAPIObjectBase.h" +#include "HoudiniEngineRuntimeCommon.h" + +#include "HoudiniPublicAPI.generated.h" + +class ULevel; + +class UHoudiniAsset; +class UHoudiniPublicAPIAssetWrapper; +class UHoudiniPublicAPIInput; + +/** Public API version of EHoudiniRampInterpolationType: blueprints do not support int8 based enums. */ +UENUM(BlueprintType) +enum class EHoudiniPublicAPIRampInterpolationType : uint8 +{ + InValid = 0, + + CONSTANT = 1, + LINEAR = 2, + CATMULL_ROM = 3, + MONOTONE_CUBIC = 4, + BEZIER = 5, + BSPLINE = 6, + HERMITE = 7 +}; + +/** + * The Houdini Engine v2 Plug-in's Public API. + * + * The API allows one to manage a Houdini Engine session (Create/Stop/Restart), Pause/Resume asset cooking and + * instantiate HDA's and interact with it (set/update inputs, parameters, cook, iterate over outputs and bake outputs). + * + * Interaction with an instantiated HDA is done via UHoudiniPublicAPIAssetWrapper. + * + */ +UCLASS(BlueprintType, Blueprintable, Category="Houdini|Public API") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPI : public UHoudiniPublicAPIObjectBase +{ + GENERATED_BODY() + +public: + + UHoudiniPublicAPI(); + + // Session + + /** Returns true if there is a valid Houdini Engine session running/connected */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsSessionValid() const; + + /** Start a new Houdini Engine Session if there is no current session */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void CreateSession(); + + /** Stops the current session */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void StopSession(); + + /** Stops, then creates a new session */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void RestartSession(); + + // Assets + + /** + * Instantiates an HDA in the specified world/level. Returns a wrapper for instantiated asset. + * @param InHoudiniAsset The HDA to instantiate. + * @param InInstantiateAt The Transform to instantiate the HDA with. + * @param InWorldContextObject A world context object for identifying the world to spawn in, if + * @InSpawnInLevelOverride is null. + * @param InSpawnInLevelOverride If not nullptr, then the AHoudiniAssetActor is spawned in that level. If both + * InSpawnInLevelOverride and InWorldContextObject are null, then the actor is spawned in the current editor + * context world's current level. + * @param bInEnableAutoCook If true (the default) the HDA will cook automatically after instantiation and after + * parameter, transform and input changes. + * @param bInEnableAutoBake If true, the HDA output is automatically baked after a cook. Defaults to false. + * @param InBakeDirectoryPath The directory to bake to if the bake path is not set via attributes on the HDA output. + * @param InBakeMethod The bake target (to actor vs blueprint). @see EHoudiniEngineBakeOption. + * @param bInRemoveOutputAfterBake If true, HDA temporary outputs are removed after a bake. Defaults to false. + * @param bInRecenterBakedActors Recenter the baked actors to their bounding box center. Defaults to false. + * @param bInReplacePreviousBake If true, on every bake replace the previous bake's output (assets + actors) with + * the new bake's output. Defaults to false. + * @return A wrapper for the instantiated asset, or nullptr if InHoudiniAsset or InInstantiateAt is invalid, or + * the AHoudiniAssetActor could not be spawned. See UHoudiniPublicAPIAssetWrapper. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InInstantiateAt")) + UHoudiniPublicAPIAssetWrapper* InstantiateAsset( + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + UObject* InWorldContextObject=nullptr, + ULevel* InSpawnInLevelOverride=nullptr, + const bool bInEnableAutoCook=true, + const bool bInEnableAutoBake=false, + const FString& InBakeDirectoryPath="", + const EHoudiniEngineBakeOption InBakeMethod=EHoudiniEngineBakeOption::ToActor, + const bool bInRemoveOutputAfterBake=false, + const bool bInRecenterBakedActors=false, + const bool bInReplacePreviousBake=false); + + /** + * Instantiates an HDA in the specified world/level using an existing wrapper. + * @param InWrapper The wrapper to instantiate the HDA with. + * @param InHoudiniAsset The HDA to instantiate. + * @param InInstantiateAt The Transform to instantiate the HDA with. + * @param InWorldContextObject A world context object for identifying the world to spawn in, if + * InSpawnInLevelOverride is null. + * @param InSpawnInLevelOverride If not nullptr, then the AHoudiniAssetActor is spawned in that level. If both + * InSpawnInLevelOverride and InWorldContextObject are null, then the actor is spawned in the current editor + * context world's current level. + * @param bInEnableAutoCook If true (the default) the HDA will cook automatically after instantiation and after + * parameter, transform and input changes. + * @param bInEnableAutoBake If true, the HDA output is automatically baked after a cook. Defaults to false. + * @param InBakeDirectoryPath The directory to bake to if the bake path is not set via attributes on the HDA output. + * @param InBakeMethod The bake target (to actor vs blueprint). @see EHoudiniEngineBakeOption. + * @param bInRemoveOutputAfterBake If true, HDA temporary outputs are removed after a bake. Defaults to false. + * @param bInRecenterBakedActors Recenter the baked actors to their bounding box center. Defaults to false. + * @param bInReplacePreviousBake If true, on every bake replace the previous bake's output (assets + actors) with + * the new bake's output. Defaults to false. + * @return true if InWrapper and InHoudiniAsset is valid and the AHoudiniAssetActor was spawned. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InInstantiateAt")) + bool InstantiateAssetWithExistingWrapper( + UHoudiniPublicAPIAssetWrapper* InWrapper, + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + UObject* InWorldContextObject=nullptr, + ULevel* InSpawnInLevelOverride=nullptr, + const bool bInEnableAutoCook=true, + const bool bInEnableAutoBake=false, + const FString& InBakeDirectoryPath="", + const EHoudiniEngineBakeOption InBakeMethod=EHoudiniEngineBakeOption::ToActor, + const bool bInRemoveOutputAfterBake=false, + const bool bInRecenterBakedActors=false, + const bool bInReplacePreviousBake=false); + + // Cooking + + /** Returns true if asset cooking is paused. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsAssetCookingPaused() const; + + /** Pause asset cooking (if not already paused) */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void PauseAssetCooking(); + + /** Resume asset cooking (if it was paused) */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void ResumeAssetCooking(); + + // Inputs + + /** + * Create a new empty API input object. The user must populate it and then set it as an input on an asset wrapper. + * @param InInputClass The class of the input to create, must be a subclass of UHoudiniPublicAPIInput. + * @param InOuter The owner of the input, if nullptr, then this API instance will be set as the outer. + * @return The newly created empty input object instance. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(DeterminesOutputType="InInputClass")) + UHoudiniPublicAPIInput* CreateEmptyInput(TSubclassOf InInputClass, UObject* InOuter=nullptr); + + // Helpers -- enum conversions + + /** + * Helper for converting from EHoudiniRampInterpolationType to EHoudiniPublicAPIRampInterpolationType + * @param InInterpolationType The EHoudiniRampInterpolationType to convert. + * @return The EHoudiniPublicAPIRampInterpolationType value of InInterpolationType. + */ + static EHoudiniPublicAPIRampInterpolationType ToHoudiniPublicAPIRampInterpolationType(const EHoudiniRampInterpolationType InInterpolationType); + + /** + * Helper for converting from EHoudiniPublicAPIRampInterpolationType to EHoudiniRampInterpolationType + * @param InInterpolationType The EHoudiniPublicAPIRampInterpolationType to convert. + * @return The EHoudiniRampInterpolationType value of InInterpolationType. + */ + static EHoudiniRampInterpolationType ToHoudiniRampInterpolationType(const EHoudiniPublicAPIRampInterpolationType InInterpolationType); + +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h new file mode 100644 index 000000000..23d71e30d --- /dev/null +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h @@ -0,0 +1,1575 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "GameFramework/Actor.h" + +#include "HoudiniPublicAPIObjectBase.h" +#include "HoudiniPublicAPI.h" +#include "HoudiniPublicAPIOutputTypes.h" +#include "HoudiniEngineRuntimeCommon.h" + +#include "HoudiniPublicAPIAssetWrapper.generated.h" + + +class UHoudiniPublicAPIInput; +class UHoudiniOutput; +class UHoudiniParameter; +class UHoudiniInput; +class UTOPNode; +class UHoudiniAssetComponent; +class AHoudiniAssetActor; + +/** + * The base class of a struct for Houdini Ramp points. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API") +struct HOUDINIENGINEEDITOR_API FHoudiniPublicAPIRampPoint +{ + GENERATED_BODY(); + +public: + FHoudiniPublicAPIRampPoint(); + + FHoudiniPublicAPIRampPoint( + const float InPosition, + const EHoudiniPublicAPIRampInterpolationType InInterpolation=EHoudiniPublicAPIRampInterpolationType::LINEAR); + + /** The position of the point on the Ramp's x-axis [0,1]. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + float Position; + + /** The interpolation type of the point. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + EHoudiniPublicAPIRampInterpolationType Interpolation; +}; + +/** + * A struct for Houdini float ramp points. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API") +struct HOUDINIENGINEEDITOR_API FHoudiniPublicAPIFloatRampPoint : public FHoudiniPublicAPIRampPoint +{ + GENERATED_BODY(); + +public: + + FHoudiniPublicAPIFloatRampPoint(); + + FHoudiniPublicAPIFloatRampPoint( + const float InPosition, + const float InValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation=EHoudiniPublicAPIRampInterpolationType::LINEAR); + + /** The value of the point. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + float Value; + +}; + +/** + * A struct for Houdini color ramp points. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API") +struct HOUDINIENGINEEDITOR_API FHoudiniPublicAPIColorRampPoint : public FHoudiniPublicAPIRampPoint +{ + GENERATED_BODY(); + +public: + + FHoudiniPublicAPIColorRampPoint(); + + FHoudiniPublicAPIColorRampPoint( + const float InPosition, + const FLinearColor& InValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation=EHoudiniPublicAPIRampInterpolationType::LINEAR); + + /** The value of the point. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + FLinearColor Value; + +}; + +/** + * A struct for storing the values of a Houdini parameter tuple. + * Currently supports bool, int32, float and FString storage. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API") +struct HOUDINIENGINEEDITOR_API FHoudiniParameterTuple +{ + GENERATED_BODY(); + +public: + FHoudiniParameterTuple(); + + /** + * Wrap a single bool value. + * @param InValue The value to wrap. + */ + FHoudiniParameterTuple(const bool& InValue); + /** + * Wrap a bool tuple. + * @param InValues The tuple to wrap. + */ + FHoudiniParameterTuple(const TArray& InValues); + + /** + * Wrap a single int32 value. + * @param InValue The value to wrap. + */ + FHoudiniParameterTuple(const int32& InValue); + /** + * Wrap a int32 tuple. + * @param InValues The tuple to wrap. + */ + FHoudiniParameterTuple(const TArray& InValues); + + /** + * Wrap a single float value. + * @param InValue The value to wrap. + */ + FHoudiniParameterTuple(const float& InValue); + /** + * Wrap a float tuple. + * @param InValues The tuple to wrap. + */ + FHoudiniParameterTuple(const TArray& InValues); + + /** + * Wrap a single FString value. + * @param InValue The value to wrap. + */ + FHoudiniParameterTuple(const FString& InValue); + /** + * Wrap a FString tuple. + * @param InValues The tuple to wrap. + */ + FHoudiniParameterTuple(const TArray& InValues); + + /** + * Wrap float ramp points + * @param InRampPoints The float ramp points. + */ + FHoudiniParameterTuple(const TArray& InRampPoints); + + /** + * Wrap color ramp points + * @param InRampPoints The color ramp points. + */ + FHoudiniParameterTuple(const TArray& InRampPoints); + + // Parameter tuple storage + + /** For bool compatible parameters, the bool parameter tuple values. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray BoolValues; + + /** For int32 compatible parameters, the int32 parameter tuple values. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray Int32Values; + + /** For float compatible parameters, the float parameter tuple values. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray FloatValues; + + /** For string compatible parameters, the string parameter tuple values. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray StringValues; + + /** For float ramp parameters, the ramp points. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray FloatRampPoints; + + /** For color ramp parameters, the ramp points. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray ColorRampPoints; +}; + +/** + * A wrapper for spawned/instantiating HDAs. + * + * The wrapper/HDA should be instantiated via UHoudiniPublicAPI::InstantiateAsset(). Alternatively an empty + * wrapper can be created via UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper() and an HDA later instantiated and + * assigned to the wrapper via UHoudiniPublicAPI::InstantiateAssetWithExistingWrapper(). + * + * The wrapper provides functionality for interacting/manipulating a AHoudiniAssetActor / UHoudiniAssetComponent: + * - Get/Set Inputs + * - Get/Set Parameters + * - Manually initiate a cook/recook + * - Subscribe to delegates: + * - #OnPreInstantiationDelegate (good place to set parameter values before the first cook) + * - #OnPostInstantiationDelegate (good place to set/configure inputs before the first cook) + * - #OnPostCookDelegate + * - #OnPreProcessStateExitedDelegate + * - #OnPostProcessingDelegate (output objects are available if the cook was successful) + * - #OnPostBakeDelegate + * - #OnPostPDGTOPNetworkCookDelegate + * - #OnPostPDGBakeDelegate + * - #OnProxyMeshesRefinedDelegate + * - Iterate over outputs and find the output assets + * - Bake outputs + * - PDG: Dirty all, cook outputs + * + * Important: In the current implementation of the plugin, nodes are cooked asynchronously. That means that cooking + * (including rebuilding the HDA and auto-cooks triggered from, for example, parameter changes) does not happen + * immediately. Functions in the API, such as Recook() and Rebuild(), do not block until the cook is complete, but + * instead immediately return after arranging for the cook to take place. This means that if a cook is triggered + * (either automatically, via parameter changes, or by calling Recook()) and there is a reliance on data that will only + * be available after the cook (such as an updated parameter interface, or the output objects of the cook), one of the + * delegates mentioned above (#OnPostProcessingDelegate or #OnPostCookDelegate, for example) would have to be used to + * execute the dependent code after the cook. + */ +UCLASS(BlueprintType, Blueprintable, Category="Houdini Engine|Public API") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPublicAPIObjectBase +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIAssetWrapper(); + + // Delegate types + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHoudiniAssetStateChange, UHoudiniPublicAPIAssetWrapper*, InAssetWrapper); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoudiniAssetPostCook, UHoudiniPublicAPIAssetWrapper*, InAssetWrapper, const bool, bInCookSuccess); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoudiniAssetPostBake, UHoudiniPublicAPIAssetWrapper*, InAssetWrapper, const bool, bInBakeSuccess); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoudiniAssetProxyMeshesRefinedDelegate, UHoudiniPublicAPIAssetWrapper* const, InAssetWrapper, const EHoudiniProxyRefineResult, InResult); + + /** + * Factory function for creating new wrapper instances around instantiated assets. + * @param InOuter The outer for the new wrapper instance. + * @param InHoudiniAssetActorOrComponent The AHoudiniAssetActor or UHoudiniAssetComponent to wrap. + * @return The newly instantiated wrapper that wraps InHoudiniAssetActor, or nullptr if the wrapper could not + * be created, or if InHoudiniAssetActorOrComponent is invalid or not of a supported type. + */ + UFUNCTION(BlueprintCallable, Category="Houdini|Public API") + static UHoudiniPublicAPIAssetWrapper* CreateWrapper(UObject* InOuter, UObject* InHoudiniAssetActorOrComponent); + + /** + * Factory function for creating a new empty wrapper instance. + * An instantiated actor can be wrapped using SetHoudiniAssetActor. + * @param InOuter the outer for the new wrapper instance. + * @return The newly instantiated wrapper. + */ + UFUNCTION(BlueprintCallable, Category="Houdini|Public API") + static UHoudiniPublicAPIAssetWrapper* CreateEmptyWrapper(UObject* InOuter); + + /** + * Checks if InObject can be wrapped by instances of UHoudiniPublicAPIAssetWrapper. + * @param InObject The object to check for compatiblity. + * @return true if InObject can be wrapped by instances of UHoudiniPublicAPIAssetWrapper. + */ + UFUNCTION(BlueprintCallable, Category="Houdini|Public API") + static bool CanWrapHoudiniObject(UObject* InObject); + + /** + * Wrap the specified instantiated houdini asset object. Supported objects are: AHoudiniAssetActor, + * UHoudiniAssetComponent. This will first unwrap/unbind the currently wrapped instantiated + * asset if InHoudiniAssetObjectToWrap is valid and of a supported class. + * + * If InHoudiniAssetObjectToWrap is nullptr, then this wrapper will unwrap/unbind the currently wrapped + * instantiated asset and return true. + * + * @param InHoudiniAssetObjectToWrap The object to wrap, or nullptr to unwrap/unbind if currently wrapping an + * asset. + * @return true if InHoudiniAssetObjectToWrap is valid, of a supported class and was successfully wrapped, or true + * if InHoudiniAssetObjectToWrap is nullptr. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool WrapHoudiniAssetObject(UObject* InHoudiniAssetObjectToWrap); + + // Accessors and mutators + + /** + * Get the wrapped instantiated houdini asset object. + * @return The wrapped instantiated houdini asset object. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UObject* GetHoudiniAssetObject() const; + FORCEINLINE + virtual UObject* GetHoudiniAssetObject_Implementation() const { return HoudiniAssetObject.Get(); } + + /** + * Helper function for getting the instantiated HDA asset actor, if HoudiniAssetObject is an AHoudiniAssetActor or + * a UHoudiniAssetComponent owned by a AHoudiniAssetActor. + * @return The instantiated AHoudiniAssetActor, if HoudiniAssetObject is an AHoudiniAssetActor or + * a UHoudiniAssetComponent owned by a AHoudiniAssetActor, otherwise nullptr. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + AHoudiniAssetActor* GetHoudiniAssetActor() const; + + /** + * Helper function for getting the UHoudiniAssetComponent of the HDA, if HoudiniAssetObject is a + * UHoudiniAssetComponent or an AHoudiniAssetActor. + * @return The instantiated AHoudiniAssetActor, if HoudiniAssetObject is a + * UHoudiniAssetComponent or an AHoudiniAssetActor, otherwise nullptr. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UHoudiniAssetComponent* GetHoudiniAssetComponent() const; + + /** + * Get the Temp Folder fallback as configured on asset details panel + * @param OutDirectoryPath The currently configured fallback temporary cook folder. + * @return true if the wrapper is valid and the value was fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetTemporaryCookFolder(FDirectoryPath& OutDirectoryPath) const; + + /** + * Set the Temp Folder fallback as configured on asset details panel. Returns true if the value was changed. + * @param InDirectoryPath The new temp folder fallback to set. + * @return true if the wrapper is valid and the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetTemporaryCookFolder(const FDirectoryPath& InDirectoryPath) const; + + /** + * Get the Bake Folder fallback as configured on asset details panel. + * @param OutDirectoryPath The current bake folder fallback. + * @return true if the wrapper is valid and the value was fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetBakeFolder(FDirectoryPath& OutDirectoryPath) const; + + /** + * Set the Bake Folder fallback as configured on asset details panel. Returns true if the value was changed. + * @param InDirectoryPath The new bake folder fallback. + * @return true if the wrapper is valid and the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetBakeFolder(const FDirectoryPath& InDirectoryPath) const; + + // Houdini Engine Actions + + /** Delete the instantiated asset from its level and mark the wrapper for destruction. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool DeleteInstantiatedAsset(); + + /** + * Marks the HDA as needing to be rebuilt in Houdini Engine and immediately returns. The rebuild happens + * asynchronously. If you need to take action after the rebuild and cook completes, one of the wrapper's delegates + * can be used, such as: OnPostCookDelegate or OnPostProcessingDelegate. + * + * @returns true If the HDA was successfully marked as needing to be rebuilt. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool Rebuild(); + + // Cooking + + /** + * Marks the HDA as needing to be cooked and immediately returns. The cook happens asynchronously. If you need + * to take action after the cook completes, one of the wrapper's delegates can be used, such as: + * OnPostCookDelegate or OnPostProcessingDelegate. + * + * @returns true If the HDA was successfully marked as needing to be cooked. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool Recook(); + + /** + * Enable or disable auto cooking of the asset (on parameter changes, input updates and transform changes, for + * example) + * @param bInSetEnabled Whether or not enable auto cooking. + * @return true if the value was changed. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetAutoCookingEnabled(const bool bInSetEnabled); + + /** Returns true if auto cooking is enabled for this instantiated asset. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsAutoCookingEnabled() const; + + // Baking + + /** + * Bake all outputs of the instantiated asset using the settings configured on the asset. + * @return true if the wrapper is valid and the baking process was started. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool BakeAllOutputs(); + + /** + * Bake all outputs of the instantiated asset using the specified settings. + * @param InBakeOption The bake method/target, (to actor vs blueprint, for example). + * @param bInReplacePreviousBake If true, replace the previous bake output (assets + actor) with the + * new results. + * @param bInRemoveTempOutputsOnSuccess If true, the temporary outputs of the wrapper asset are removed + * after a successful bake. + * @param bInRecenterBakedActors If true, recenter the baked actors to their bounding box center after the bake. + * @return true if the wrapper is valid and the baking process was started. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool BakeAllOutputsWithSettings( + EHoudiniEngineBakeOption InBakeOption, + bool bInReplacePreviousBake=false, + bool bInRemoveTempOutputsOnSuccess=false, + bool bInRecenterBakedActors=false); + + /** + * Set whether to automatically bake all outputs after a successful cook. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetAutoBakeEnabled(const bool bInAutoBakeEnabled); + + /** Returns true if auto bake is enabled. See SetAutoBakeEnabled. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsAutoBakeEnabled() const; + + /** + * Sets the bake method to use (to actor, blueprint, foliage). + * @param InBakeMethod The new bake method to set. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetBakeMethod(const EHoudiniEngineBakeOption InBakeMethod); + + /** + * Gets the currently set bake method to use (to actor, blueprint, foliage). + * @param OutBakeMethod The current bake method. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetBakeMethod(EHoudiniEngineBakeOption& OutBakeMethod); + + /** + * Set the bRemoveOutputAfterBake property, that controls if temporary outputs are removed after a successful bake. + * @param bInRemoveOutputAfterBake If true, then after a successful bake, the HACs outputs will be cleared and + * removed. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetRemoveOutputAfterBake(const bool bInRemoveOutputAfterBake); + + /** + * Get the bRemoveOutputAfterBake property, that controls if temporary outputs are removed after a successful bake. + * @return true if bRemoveOutputAfterBake is true. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetRemoveOutputAfterBake() const; + + /** + * Set the bRecenterBakedActors property that controls if baked actors are recentered around their bounding box center. + * @param bInRecenterBakedActors If true, recenter baked actors to their bounding box center after bake. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetRecenterBakedActors(const bool bInRecenterBakedActors); + + /** Gets the bRecenterBakedActors property. If true, recenter baked actors to their bounding box center after bake. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetRecenterBakedActors() const; + + /** + * Set the bReplacePreviousBake property, if true, replace the previously baked output (if any) instead of creating + * new objects. + * @param bInReplacePreviousBake If true, replace the previously baked output (if any) instead of creating new + * objects. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetReplacePreviousBake(const bool bInReplacePreviousBake); + + /** Get the bReplacePreviousBake property. + * @return The value of bReplacePreviousBake. If true, previous bake output (if any) will be replaced by subsequent + * bakes. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetReplacePreviousBake() const; + + // Parameters + + /** + * Set the value of a float-based parameter. + * Supported parameter types: + * - Float + * - Color + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. Defaults to 0. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set or the parameter already had the given value. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetFloatParameterValue(FName InParameterTupleName, float InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of a float parameter. Returns true if the parameter and index was found and the value set in OutValue. + * Supported parameter types: + * - Float + * - Color + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return true if the wrapper is valid and the parameter was found. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetFloatParameterValue(FName InParameterTupleName, float& OutValue, int32 InAtIndex=0) const; + + /** + * Set the value of a color parameter. + * Supported parameter types: + * - Color + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set or the parameter already had the given value. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetColorParameterValue(FName InParameterTupleName, const FLinearColor& InValue, bool bInMarkChanged=true); + + /** + * Get the value of a color parameter. Returns true if the parameter was found and the value set in OutValue. + * Supported parameter types: + * - Color + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetColorParameterValue(FName InParameterTupleName, FLinearColor& OutValue) const; + + /** + * Set the value of a int32 parameter. + * Supported parameter types: + * - Int + * - IntChoice + * - MultiParm + * - Toggle + * - Folder (set the folder as currently shown) + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. Defaults to 0. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set or the parameter already had the given value. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetIntParameterValue(FName InParameterTupleName, int32 InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of a int32 parameter. + * Supported parameter types: + * - Int + * - IntChoice + * - MultiParm + * - Toggle + * - Folder (get if the folder is currently shown) + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return true if the parameter and index was found and the value set in OutValue. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetIntParameterValue(FName InParameterTupleName, int32& OutValue, int32 InAtIndex=0) const; + + /** + * Set the value of a bool parameter. + * Supported parameter types: + * - Toggle + * - Folder (set the folder as currently shown) + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set or the parameter already had the given value. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetBoolParameterValue(FName InParameterTupleName, bool InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of a bool parameter. + * Supported parameter types: + * - Toggle + * - Folder (get if the folder is currently shown) + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return true if the parameter and index was found and the value set in OutValue. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetBoolParameterValue(FName InParameterTupleName, bool& OutValue, int32 InAtIndex=0) const; + + /** + * Set the value of a String parameter. + * Supported parameter types: + * - String + * - StringChoice + * - StringAssetRef + * - File + * - FileDir + * - FileGeo + * - FileImage + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set or the parameter already had the given value. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetStringParameterValue(FName InParameterTupleName, const FString& InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of a String parameter. + * Supported parameter types: + * - String + * - StringChoice + * - StringAssetRef + * - File + * - FileDir + * - FileGeo + * - FileImage + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return true if the parameter was found and the value set in OutValue. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetStringParameterValue(FName InParameterTupleName, FString& OutValue, int32 InAtIndex=0) const; + + /** + * Set the value of an AssetRef parameter. + * Supported parameter types: + * - StringAssetRef + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set or the parameter already had the given value. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetAssetRefParameterValue(FName InParameterTupleName, UObject* InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of an AssetRef parameter. + * Supported parameter types: + * - StringAssetRef + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return True if the parameter was found and the value set in OutValue. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetAssetRefParameterValue(FName InParameterTupleName, UObject*& OutValue, int32 InAtIndex=0) const; + + /** + * Sets the number of points of the specified ramp parameter. This will insert or remove points from the end + * as necessary. + * @param InParameterTupleName The name of the parameter tuple. + * @param InNumPoints The new number of points to set. Must be >= 1. + * @return true if the parameter was found and the number of points set, or if the number of points was already InNumPoints. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetRampParameterNumPoints(FName InParameterTupleName, const int32 InNumPoints) const; + + /** + * Gets the number of points of the specified ramp parameter. + * @param InParameterTupleName The name of the parameter tuple. + * @param OutNumPoints The number of points the ramp has. + * @return true if the parameter was found and the number of points fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetRampParameterNumPoints(FName InParameterTupleName, int32& OutNumPoints) const; + + /** + * Set the position, value and interpolation of a point of a FloatRamp parameter. + * Supported parameter types: + * - FloatRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to set. + * @param InPointPosition The point position to set [0, 1]. + * @param InPointValue The value to set for the point. + * @param InInterpolationType The interpolation to set at the point. Defaults to EHoudiniPublicAPIRampInterpolationType.Linear. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetFloatRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPointPosition, + const float InPointValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolationType=EHoudiniPublicAPIRampInterpolationType::LINEAR, + const bool bInMarkChanged=true); + + /** + * Get the position, value and interpolation of a point of a FloatRamp parameter. + * Supported parameter types: + * - FloatRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to get. + * @param OutPointPosition The point position [0, 1]. + * @param OutPointValue The value at the point. + * @param OutInterpolationType The interpolation of the point. + * @return True if the parameter was found and output values set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetFloatRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPointPosition, + float& OutPointValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const; + + /** + * Set all of the points (position, value and interpolation) of float ramp. + * Supported parameter types: + * - FloatRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InRampPoints An array of structs to set as the ramp's points. + * @param bInMarkChanged If true, parameters are marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the values were set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetFloatRampParameterPoints( + FName InParameterTupleName, + const TArray& InRampPoints, + const bool bInMarkChanged=true); + + /** + * Get the all of the points (position, value and interpolation) of a FloatRamp parameter. + * Supported parameter types: + * - FloatRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param OutRampPoints The array to populate with the ramp's points. + * @return True if the parameter was found and output values set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetFloatRampParameterPoints( + FName InParameterTupleName, + TArray& OutRampPoints) const; + + /** + * Set the position, value and interpolation of a point of a ColorRamp parameter. + * Supported parameter types: + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to set. + * @param InPointPosition The point position to set [0, 1]. + * @param InPointValue The value to set for the point. + * @param InInterpolationType The interpolation to set at the point. Defaults to EHoudiniPublicAPIRampInterpolationType.Linear. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetColorRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPointPosition, + const FLinearColor& InPointValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolationType=EHoudiniPublicAPIRampInterpolationType::LINEAR, + const bool bInMarkChanged=true); + + /** + * Get the position, value and interpolation of a point of a ColorRamp parameter. + * Supported parameter types: + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to get. + * @param OutPointPosition The point position [0, 1]. + * @param OutPointValue The value at the point. + * @param OutInterpolationType The interpolation of the point. + * @return True if the parameter was found and output values set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetColorRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPointPosition, + FLinearColor& OutPointValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const; + + /** + * Set all of the points (position, value and interpolation) of color ramp. + * Supported parameter types: + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InRampPoints An array of structs to set as the ramp's points. + * @param bInMarkChanged If true, parameters are marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the values were set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetColorRampParameterPoints( + FName InParameterTupleName, + const TArray& InRampPoints, + const bool bInMarkChanged=true); + + /** + * Get the all of the points (position, value and interpolation) of a ColorRamp parameter. + * Supported parameter types: + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param OutRampPoints The array to populate with the ramp's points. + * @return True if the parameter was found and output values set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetColorRampParameterPoints( + FName InParameterTupleName, + TArray& OutRampPoints) const; + + /** + * Trigger / click the specified button parameter. + * @return True if the button was found and triggered/clicked, or was already marked to be triggered. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool TriggerButtonParameter(FName InButtonParameterName); + + /** + * Gets all parameter tuples (with their values) from this asset and outputs it to OutParameterTuples. + * @param OutParameterTuples Populated with all parameter tuples and their values. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetParameterTuples(TMap& OutParameterTuples) const; + + /** + * Sets all parameter tuple values (matched by name and compatible type) from InParameterTuples on this + * instantiated asset. + * @param InParameterTuples The parameter tuples to set. + * @return false if any entry in InParameterTuples could not be found on the asset or had an incompatible type/size. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetParameterTuples(const TMap& InParameterTuples); + + // Inputs + + /** + * Creates an empty input wrapper. + * @param InInputClass the class of the input to create. See the UHoudiniPublicAPIInput class hierarchy. + * @return The newly created input wrapper, or null if the input wrapper could not be created. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(DeterminesOutputType="InInputClass")) + UHoudiniPublicAPIInput* CreateEmptyInput(TSubclassOf InInputClass); + + /** + * Get the number of node inputs supported by the asset. + * @return The number of node inputs (inputs on the HDA node, excluding parameter-based inputs). Returns -1 if the + * asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UPARAM(DisplayName="OutNumNodeInputs") int32 GetNumNodeInputs() const; + + /** + * Set a node input at the specific index. + * @param InNodeInputIndex The index of the node input, starts at 0. + * @param InInput The input wrapper to use to set the input. + * @return false if the the input could not be set, for example, if the wrapper is invalid, or if the input index + * is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetInputAtIndex(const int32 InNodeInputIndex, const UHoudiniPublicAPIInput* InInput); + + /** + * Get the node input at the specific index and sets OutInput. This is a copy of the input structure. Changes to + * properties in OutInput won't affect the instantiated HDA until a subsequent call to SetInputAtIndex. + * @param InNodeInputIndex The index of the node input to get. + * @param OutInput Copy of the input configuration and data for node input index InNodeInputIndex. + * @return false if the input could be fetched, for example if the wrapper is invalid or the input index is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetInputAtIndex(const int32 InNodeInputIndex, UHoudiniPublicAPIInput*& OutInput); + + /** + * Set node inputs at the specified indices via a map. + * @param InInputs A map of node input index to input wrapper to use to set inputs on the instantiated asset. + * @return true if all inputs from InInputs were set successfully. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetInputsAtIndices(const TMap& InInputs); + + /** + * Get all node inputs. + * @param OutInputs All node inputs as a map, with the node input index as key. The input configuration is copied + * from the instantiated asset, and changing an input property from the entry in this map will not affect the + * instantiated asset until a subsequent SetInputsAtIndices() call or SetInputAtIndex() call. + * @return false if the wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetInputsAtIndices(TMap& OutInputs); + + /** + * Set a parameter-based input via parameter name. + * @param InParameterName The name of the input parameter. + * @param InInput The input wrapper to use to set/configure the input. + * @return false if the wrapper is invalid, InParameterName is not a valid input parameter, or if InInput could + * not be used to successfully configure/set the input. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) + bool SetInputParameter(const FName& InParameterName, const UHoudiniPublicAPIInput* InInput); + + /** + * Get a parameter-based input via parameter name. This is a copy of the input structure. Changes to properties in + * OutInput won't affect the instantiated HDA until a subsequent call to SetInputParameter. + * @param InParameterName The name of the input parameter. + * @param OutInput A copy of the input configuration for InParameterName. + * @return false if the wrapper is invalid, InParameterName is not a valid input parameter, or the current input + * configuration could not be successfully copied to a new UHoudiniPublicAPIInput wrapper. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) + bool GetInputParameter(const FName& InParameterName, UHoudiniPublicAPIInput*& OutInput); + + /** + * Set a parameter-based inputs via a map, + * @param InInputs A map of input parameter names to input wrapper to use to set inputs on the instantiated asset. + * @return true if all inputs from InInputs were set successfully. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) + bool SetInputParameters(const TMap& InInputs); + + /** + * Get a parameter-based inputs as a map + * @param OutInputs All parameter inputs as a map, with the input parameter name as key. The input configuration is + * copied from the instantiated asset, and changing an input property from the entry in this map will not affect the + * instantiated asset until a subsequent SetInputParameters() call or SetInputParameter() call. + * @return false if the wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) + bool GetInputParameters(TMap& OutInputs); + + // Outputs + + /** + * Gets the number of outputs of the instantiated asset. + * @return the number of outputs of the instantiated asset. -1 if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + int32 GetNumOutputs() const; + + /** + * Gets the output type of the output at index InIndex. + * @param InIndex The output index to get the type for. + * @return the output type of the output at index InIndex. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + EHoudiniOutputType GetOutputTypeAt(const int32 InIndex) const; + + /** + * Populates OutIdentifiers with the output object identifiers of all the output objects of the output at InIndex. + * @param InIndex The output index to get output identifiers for. + * @param OutIdentifiers The output identifiers of the output objects at output index InIndex. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetOutputIdentifiersAt(const int32 InIndex, TArray& OutIdentifiers) const; + + /** + * Gets the output object at InIndex identified by InIdentifier. + * @param InIndex The output index to get output object from. + * @param InIdentifier The output identifier of the output object to get from output index InIndex. + * @return nullptr if the index/identifier is invalid or if the asset/wrapper is invalid, otherwise the output + * object. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UObject* GetOutputObjectAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const; + + /** + * Gets the output component at InIndex identified by InIdentifier. + * @param InIndex The output index to get output component from. + * @param InIdentifier The output identifier of the output component to get from output index InIndex. + * @return nullptr if the index/identifier is invalid or if the asset/wrapper is invalid, otherwise the output + * component. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UObject* GetOutputComponentAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const; + + /** + * Gets the output's fallback BakeName (as configured on the output details panel) for the output at InIndex + * identified by InIdentifier. + * @param InIndex The output index of the output object to get fallback BakeName for. + * @param InIdentifier The output identifier of the output object to get fallback BakeName for. + * @param OutBakeName The fallback BakeName configured for the output object identified by InIndex and InIdentifier. + * @return false if the index/identifier is invalid or if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetOutputBakeNameFallbackAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, FString& OutBakeName) const; + + /** + * Sets the output's fallback BakeName (as configured on the output details panel) for the output at InIndex + * identified by InIdentifier. + * @param InIndex The output index of the output object to set fallback BakeName for. + * @param InIdentifier The output identifier of the output object to set fallback BakeName for. + * @param InBakeName The fallback BakeName to set for the output object identified by InIndex and InIdentifier. + * @return false if the index/identifier is invalid or if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetOutputBakeNameFallbackAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FString& InBakeName); + + /** + * Bake the specified output object to the content browser. + * @param InIndex The output index of the output object to bake. + * @param InIdentifier The output identifier of the output object to bake. + * @param InBakeName The bake name to bake with. + * @param InLandscapeBakeType For landscape assets, the output bake type. + * @return true if the output was baked successfully, false if the wrapper/asset is invalid, or the output index + * and output identifier combination is invalid, or if baking failed. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool BakeOutputObjectAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FName InBakeName=NAME_None, const EHoudiniLandscapeOutputBakeType InLandscapeBakeType=EHoudiniLandscapeOutputBakeType::InValid); + + /** + * Returns true if the wrapped asset has any proxy output on any outputs. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool HasAnyCurrentProxyOutput() const; + + /** + * Returns true if the wrapped asset has any proxy output at output InIndex. + * @param InIndex The output index to check for proxies. + * @return true if the wrapped asset has any proxy output at output InIndex. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool HasAnyCurrentProxyOutputAt(const int32 InIndex) const; + + /** + * Returns true if the output object at output InIndex with identifier InIdentifier is a proxy. + * @param InIndex The output index of the output object to check. + * @param InIdentifier The output identifier of the output object at output index InIndex to check. + * @return true if the output object at output InIndex with identifier InIdentifier is a proxy. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsOutputCurrentProxyAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const; + + // Proxy mesh + + /** + * Refines all current proxy mesh outputs (if any) to static mesh. This could trigger a cook if the asset is loaded + * and has no cook data. + * @param bInSilent If true, then slate notifications about the refinement process are not shown. + * @return Whether the refinement process is needed, requires a pending asynchronous cook, or was completed + * synchronously. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + EHoudiniProxyRefineRequestResult RefineAllCurrentProxyOutputs(const bool bInSilent); + + // PDG + + /** + * Returns true if the wrapped asset is valid and has a PDG asset link. + * @return true if the wrapped asset is valid and has a PDG asset link. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool HasPDGAssetLink() const; + + /** + * Gets the paths (relative to the instantiated asset) of all TOP networks in the HDA. + * @param OutTOPNetworkPaths The relative paths of the TOP networks in the HDA. + * @return false if the asset/wrapper is invalid, or does not contain any TOP networks. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGTOPNetworkPaths(TArray& OutTOPNetworkPaths) const; + + /** + * Gets the paths (relative to the specified TOP network) of all TOP nodes in the network. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(), to fetch TOP node paths for. + * @return false if the asset/wrapper is invalid, or does not contain the specified TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGTOPNodePaths(const FString& InNetworkRelativePath, TArray& OutTOPNodePaths) const; + + /** + * Dirty all TOP networks in this asset. + * @return true if TOP networks were dirtied. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGDirtyAllNetworks(); + + /** + * Dirty the specified TOP network. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(). + * @return true if the TOP network was dirtied. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGDirtyNetwork(const FString& InNetworkRelativePath); + + /** + * Dirty the specified TOP node. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(). + * @param InNodeRelativePath The relative path of the TOP node inside the specified TOP network. + * @return true if TOP nodes were dirtied. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGDirtyNode(const FString& InNetworkRelativePath, const FString& InNodeRelativePath); + + // // Cook all outputs for all TOP networks in this asset. + // // Returns true if TOP networks were set to cook. + // UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + // bool PDGCookOutputsForAllNetworks(); + + /** + * Cook all outputs for the specified TOP network. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(). + * @return true if the TOP network was set to cook. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGCookOutputsForNetwork(const FString& InNetworkRelativePath); + + /** + * Cook the specified TOP node. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(). + * @param InNodeRelativePath The relative path of the TOP node inside the specified TOP network. + * @return true if the TOP node was set to cook. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGCookNode(const FString& InNetworkRelativePath, const FString& InNodeRelativePath); + + /** + * Bake all outputs of the instantiated asset's PDG contexts using the settings configured on the asset. + * @return true if the bake was successful. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGBakeAllOutputs(); + + /** + * Bake all outputs of the instantiated asset's PDG contexts using the specified settings. + * @param InBakeOption The bake option (to actors, blueprints or foliage). + * @param InBakeSelection Whether to bake outputs from all networks, the selected network or the selected node. + * @param InBakeReplacementMode Whether to replace previous bake results/existing assets with the same name + * when baking. + * @param bInRecenterBakedActors If true, recenter baked actors to their bounding box center. Defaults to false. + * @return true if the bake was successful. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGBakeAllOutputsWithSettings( + const EHoudiniEngineBakeOption InBakeOption, + const EPDGBakeSelectionOption InBakeSelection, + const EPDGBakePackageReplaceModeOption InBakeReplacementMode, + const bool bInRecenterBakedActors=false); + + /** + * Set whether to automatically bake PDG work items after a successfully loading them. + * @param bInAutoBakeEnabled If true, automatically bake work items after successfully loading them. + * @return false if the asset/wrapper is invalid, or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGAutoBakeEnabled(const bool bInAutoBakeEnabled); + + /** Returns true if PDG auto bake is enabled. See SetPDGAutoBakeEnabled(). */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsPDGAutoBakeEnabled() const; + + /** + * Sets the bake method to use for PDG baking (to actor, blueprint, foliage). + * @param InBakeMethod The new bake method to set. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGBakeMethod(const EHoudiniEngineBakeOption InBakeMethod); + + /** + * Gets the currently set bake method to use for PDG baking (to actor, blueprint, foliage). + * @param OutBakeMethod The current bake method. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGBakeMethod(EHoudiniEngineBakeOption& OutBakeMethod); + + /** + * Set which outputs to bake for PDG, for example, all, selected network, selected node + * @param InBakeSelection The new bake selection. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGBakeSelection(const EPDGBakeSelectionOption InBakeSelection); + + /** + * Get which outputs to bake for PDG, for example, all, selected network, selected node + * @param OutBakeSelection The current bake selection setting. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGBakeSelection(EPDGBakeSelectionOption& OutBakeSelection); + + /** + * Setter for the bRecenterBakedActors property, that determines if baked actors are recentered to their bounding + * box center after a PDG bake, on the PDG asset link. + * @param bInRecenterBakedActors If true, recenter baked actors to their bounding box center after bake (PDG) + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGRecenterBakedActors(const bool bInRecenterBakedActors); + + /** + * Getter for the bRecenterBakedActors property, that determines if baked actors are recentered to their bounding + * box center after a PDG bake, on the PDG asset link. + * @return true if baked actors should be recentered to their bounding box center after bake (PDG) + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGRecenterBakedActors() const; + + /** + * Set the replacement mode to use for PDG baking (replace previous bake output vs increment) + * @param InBakingReplacementMode The new replacement mode to set. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGBakingReplacementMode(const EPDGBakePackageReplaceModeOption InBakingReplacementMode); + + /** + * Get the replacement mode to use for PDG baking (replace previous bake output vs increment) + * @param OutBakingReplacementMode The current replacement mode. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGBakingReplacementMode(EPDGBakePackageReplaceModeOption& OutBakingReplacementMode) const; + + /** + * Getter for the OnPreInstantiationDelegate, broadcast before the HDA is instantiated. The HDA's default parameter + * definitions are available, but the node has not yet been instantiated in HAPI/Houdini Engine. Parameter values + * can be set at this point. + */ + FOnHoudiniAssetStateChange& GetOnPreInstantiationDelegate() { return OnPreInstantiationDelegate; } + + /** + * Getter for the OnPostInstantiationDelegate, broadcast after the HDA is instantiated. This is a good place to + * set/configure inputs before the first cook. + */ + FOnHoudiniAssetStateChange& GetOnPostInstantiationDelegate() { return OnPostInstantiationDelegate; } + + /** + * Getter for the OnPostCookDelegate, broadcast after the HDA has cooked. + */ + FOnHoudiniAssetPostCook& GetOnPostCookDelegate() { return OnPostCookDelegate; } + + /** + * Getter for the OnPreProcessStateExitedDelegate, broadcast after the output pre-processing phase of the HDA, + * but before it enters the post processing phase. When this delegate is broadcast output objects/assets have not + * yet been created. + */ + FOnHoudiniAssetStateChange& GetOnPreProcessStateExitedDelegate() { return OnPreProcessStateExitedDelegate; } + + /** + * Getter for the OnPostProcessingDelegate, broadcast after the HDA has processed its outputs and created output + * objects/assets. + */ + FOnHoudiniAssetStateChange& GetOnPostProcessingDelegate() { return OnPostProcessingDelegate; } + + /** + * Getter for the OnPostBakeDelegate, broadcast after the HDA has finished baking outputs (not called for + * individual outputs that are baked to the content browser). + */ + FOnHoudiniAssetPostBake& GetOnPostBakeDelegate() { return OnPostBakeDelegate; } + + /** + * Getter for the OnPostPDGTOPNetworkCookDelegate, broadcast after the HDA/PDG has cooked a TOP Network. Work item + * results have not necessarily yet been loaded. + */ + FOnHoudiniAssetPostCook& GetOnPostPDGTOPNetworkCookDelegate() { return OnPostPDGTOPNetworkCookDelegate; } + + /** + * Getter for the OnPostPDGBakeDelegate, broadcast after the HDA/PDG has finished baking outputs (not called for + * individual outputs that are baked to the content browser). + */ + FOnHoudiniAssetPostBake& GetOnPostPDGBakeDelegate() { return OnPostPDGBakeDelegate; } + + /** + * Getter for the OnProxyMeshesRefinedDelegate, broadcast for each proxy mesh of the HDA's outputs that has been + * refined to a UStaticMesh. + */ + FOnHoudiniAssetProxyMeshesRefinedDelegate& GetOnProxyMeshesRefinedDelegate() { return OnProxyMeshesRefinedDelegate; } + +protected: + + /** This will unwrap/unbind the currently wrapped instantiated asset. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void ClearHoudiniAssetObject(); + + /** + * Attempt to bind to the asset's PDG asset link, if it has one, and if the wrapper is not already bound to its + * events. + */ + UFUNCTION() + bool BindToPDGAssetLink(); + + /** Handler that is bound to the wrapped HAC's state change delegate. */ + UFUNCTION() + void HandleOnHoudiniAssetComponentStateChange(UHoudiniAssetComponent* InHAC, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState); + + /** Handler that is bound to the wrapped HAC's PostCook delegate. */ + UFUNCTION() + void HandleOnHoudiniAssetComponentPostCook(UHoudiniAssetComponent* InHAC, const bool bInCookSuccess); + + /** Handler that is bound to the wrapped HAC's PostBake delegate. */ + UFUNCTION() + void HandleOnHoudiniAssetComponentPostBake(UHoudiniAssetComponent* InHAC, const bool bInBakeSuccess); + + /** Handler that is bound to the wrapped PDG asset link's OnPostTOPNetworkCookDelegate delegate. */ + UFUNCTION() + void HandleOnHoudiniPDGAssetLinkTOPNetPostCook(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNetwork* InTOPNet, const bool bInAnyWorkItemsFailed); + + /** Handler that is bound to the wrapped PDG asset link's OnPostBake delegate. */ + UFUNCTION() + void HandleOnHoudiniPDGAssetLinkPostBake(UHoudiniPDGAssetLink* InPDGAssetLink, const bool bInBakeSuccess); + + /** + * Handler that is bound to FHoudiniEngineCommands::GetOnHoudiniProxyMeshesRefined(). It is called for any HAC + * that has its proxy meshes refined. If relevant for this wrapped asset, then + * #OnProxyMeshesRefinedDelegate is broadcast. + */ + UFUNCTION() + void HandleOnHoudiniProxyMeshesRefinedGlobal(UHoudiniAssetComponent* InHAC, const EHoudiniProxyRefineResult InResult); + + /** + * Helper function for getting the instantiated asset's AHoudiniAssetActor. If there is no valid + * AHoudiniAssetActor an error is set with SetErrorMessage() and false is returned. + * @param OutActor Set to the AHoudiniAssetActor of the wrapped asset, if found. + * @return true if the wrapped asset has/is a valid AHoudiniAssetActor, false otherwise. + */ + bool GetValidHoudiniAssetActorWithError(AHoudiniAssetActor*& OutActor) const; + + /** + * Helper function for getting the instantiated asset's UHoudiniAssetComponent. If there is no valid + * HoudiniAssetComponent an error is set with SetErrorMessage() and false is returned. + * @param OutHAC Set to the HoudiniAssetComponent of the wrapped asset, if found. + * @return true if the wrapped asset has a valid HoudiniAssetComponent, false otherwise. + */ + bool GetValidHoudiniAssetComponentWithError(UHoudiniAssetComponent*& OutHAC) const; + + /** + * Helper function for getting a valid output at the specified index. If there is no valid + * UHoudiniOutput at that index (either the index is out of range or the output is null/invalid) an error is set + * with SetErrorMessage() and false is returned. + * @param InOutputIndex The output index. + * @param OutOutput Set to the valid UHoudiniOutput at InOutputIndex, if found. + * @return true if there is a valid UHoudiniOutput at index InOutputIndex. + */ + bool GetValidOutputAtWithError(const int32 InOutputIndex, UHoudiniOutput*& OutOutput) const; + + /** Helper function for getting the instantiated asset's PDG asset link. */ + UHoudiniPDGAssetLink* GetHoudiniPDGAssetLink() const; + + /** + * Helper function for getting the instantiated asset's UHoudiniPDGAssetLink. If there is no valid + * UHoudiniPDGAssetLink an error is set with SetErrorMessage() and false is returned. + * @param OutAssetLink Set to the UHoudiniPDGAssetLink of the wrapped asset, if found. + * @return true if the wrapped asset has a valid UHoudiniPDGAssetLink, false otherwise. + */ + bool GetValidHoudiniPDGAssetLinkWithError(UHoudiniPDGAssetLink*& OutAssetLink) const; + + /** Helper function for getting a valid parameter by name */ + UHoudiniParameter* FindValidParameterByName(const FName& InParameterTupleName) const; + + /** + * Helper function to find the appropriate array index for a ramp point. + * @param InParam The parameter. + * @param InIndex The index of the ramp point to get. If INDEX_NONE, all points are fetched. + * @param OutPointData Array populated with all fetched points. The bool in the pair is true if the + * object is UHoudiniParameterRampFloatPoint or UHoudiniParameterRampColorPoint and false if it is + * UHoudiniParameterRampModificationEvent. + * @return true if the point was, or all points were, fetched successfully. + */ + bool FindRampPointData( + UHoudiniParameter* const InParam, const int32 InIndex, TArray>& OutPointData) const; + + /** + * Set the position, value and interpolation of a point of a ramp parameter. + * Supported parameter types: + * - FloatRamp + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to set. + * @param InPosition The point position [0, 1]. + * @param InFloatValue The float value at the point (if this is a float ramp). + * @param InColorValue The color value at the point (if this is a color ramp). + * @param InInterpolation The interpolation of the point. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + bool SetRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPosition, + const float InFloatValue, + const FLinearColor& InColorValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation, + const bool bInMarkChanged=true); + + /** + * Get the position, value and interpolation of a point of a ramp parameter. + * Supported parameter types: + * - FloatRamp + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to get. + * @param OutPosition The point position [0, 1]. + * @param OutFloatValue The float value at the point (if this is a float ramp). + * @param OutColorValue The color value at the point (if this is a color ramp). + * @param OutInterpolation The interpolation of the point. + * @return True if the parameter was found and output values set. + */ + bool GetRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPosition, + float& OutFloatValue, + FLinearColor& OutColorValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolation) const; + + /** Helper function for getting an input by node index */ + UHoudiniInput* GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex); + const UHoudiniInput* GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex) const; + + /** Helper function for getting an input by parameter name */ + UHoudiniInput* FindValidHoudiniNodeInputParameter(const FName& InInputParameterName); + const UHoudiniInput* FindValidHoudiniNodeInputParameter(const FName& InInputParameterName) const; + + /** Helper function for populating a UHoudiniPublicAPIInput from a UHoudiniInput */ + bool CreateAndPopulateAPIInput(const UHoudiniInput* InHoudiniInput, UHoudiniPublicAPIInput*& OutAPIInput); + /** Helper function for populating a UHoudiniInput from a UHoudiniPublicAPIInput */ + bool PopulateHoudiniInput(const UHoudiniPublicAPIInput* InAPIInput, UHoudiniInput* InHoudiniInput) const; + + /** + * Helper functions for getting a TOP network by path. If the TOP network could not be found, an error is set + * with SetErrorMessage, and false is returned. + * @param InNetworkRelativePath The relative path to the network inside the asset. + * @param OutNetworkIndex The index to the network in the asset link's AllNetworks array. + * @param OutNetwork The network that was found at InNetworkRelativePath. + * @return true if the network was found and is valid, false otherwise. + */ + bool GetValidTOPNetworkByPathWithError(const FString& InNetworkRelativePath, int32& OutNetworkIndex, UTOPNetwork*& OutNetwork) const; + + /** + * Helper functions for getting a TOP node by path. If the TOP node could not be found, an error is set + * with SetErrorMessage, and false is returned. + * @param InNetworkRelativePath The relative path to the network inside the asset. + * @param InNodeRelativePath The relative path to the node inside the network. + * @param OutNetworkIndex The index to the network in the asset link's AllTOPNetworks array. + * @param OutNodeIndex The index to the TOP node in the network's AllTOPNodes array. + * @param OutNode The node that was found. + * @return true if the node was found and is valid, false otherwise. + */ + bool GetValidTOPNodeByPathWithError( + const FString& InNetworkRelativePath, + const FString& InNodeRelativePath, + int32& OutNetworkIndex, + int32& OutNodeIndex, + UTOPNode*& OutNode) const; + + /** The wrapped Houdini Asset object (not the uasset, an AHoudiniAssetActor or UHoudiniAssetComponent). */ + UPROPERTY(BlueprintReadOnly, Category="Houdini|Public API") + TSoftObjectPtr HoudiniAssetObject; + + /** The wrapped AHoudiniAssetActor (derived from HoudiniAssetObject when calling WrapHoudiniAssetObject()). */ + UPROPERTY(BlueprintReadOnly, Category="Houdini|Public API") + TSoftObjectPtr CachedHoudiniAssetActor; + + /** The wrapped UHoudiniAssetComponent (derived from HoudiniAssetObject when calling WrapHoudiniAssetObject()). */ + UPROPERTY(BlueprintReadOnly, Category="Houdini|Public API") + TSoftObjectPtr CachedHoudiniAssetComponent; + + /** + * Delegate that is broadcast when entering the PreInstantiation state: the HDA's default parameter definitions are + * available, but the node has not yet been instantiated in HAPI/Houdini Engine. Parameters can be set at this point. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetStateChange OnPreInstantiationDelegate; + + /** + * Delegate that is broadcast after the asset was successfully instantiated. This is a good place to set/configure + * inputs before the first cook. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetStateChange OnPostInstantiationDelegate; + + /** Delegate that is broadcast after a cook completes. Output objects/assets have not yet been created/updated. */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetPostCook OnPostCookDelegate; + + /** Delegate that is broadcast when PreProcess is exited. Output objects/assets have not yet been created/updated. */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetStateChange OnPreProcessStateExitedDelegate; + + /** + * Delegate that is broadcast when the Processing state is exited and the None state is entered. Output objects + * assets have been created/updated. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetStateChange OnPostProcessingDelegate; + + /** + * Delegate that is broadcast after baking the asset (not called for individual outputs that are baked to the + * content browser). + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetPostBake OnPostBakeDelegate; + + /** + * Delegate that is broadcast after a cook of a TOP network completes. Work item results have not necessarily yet + * been loaded. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetPostCook OnPostPDGTOPNetworkCookDelegate; + + /** + * Delegate that is broadcast after baking PDG outputs (not called for individual outputs that are baked to the + * content browser). + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetPostBake OnPostPDGBakeDelegate; + + /** Delegate that is broadcast after refining all proxy meshes for this wrapped asset. */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetProxyMeshesRefinedDelegate OnProxyMeshesRefinedDelegate; + + /** + * This starts as false and is set to true in HandleOnHoudiniAssetComponentStateChange during post instantiation, + * once we have checked for a PDG asset link and configured the bindings if there is one. + */ + UPROPERTY() + bool bAssetLinkSetupAttemptComplete; + + // Delegate handles + + /** Handle for the binding to the HAC's asset state change delegate */ + FDelegateHandle OnAssetStateChangeDelegateHandle; + /** Handle for the binding to the HAC's post cook delegate */ + FDelegateHandle OnPostCookDelegateHandle; + /** Handle for the binding to the HAC's post bake delegate */ + FDelegateHandle OnPostBakeDelegateHandle; + /** Handle for the binding to the global proxy mesh refined delegate */ + FDelegateHandle OnHoudiniProxyMeshesRefinedDelegateHandle; + /** Handle for the binding to the HAC's PDG asset link's post TOP Network cook delegate */ + FDelegateHandle OnPDGPostTOPNetworkCookDelegateHandle; + /** Handle for the binding to the HAC's PDG asset link's post bake delegate */ + FDelegateHandle OnPDGPostBakeDelegateHandle; + +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineTool.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIBlueprintLib.h similarity index 66% rename from Source/HoudiniEngineEditor/Private/HoudiniEngineTool.h rename to Source/HoudiniEngineEditor/Public/HoudiniPublicAPIBlueprintLib.h index 1bfdf8b0c..d0b5ec1aa 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineTool.h +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIBlueprintLib.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,3 +25,24 @@ */ #pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" + +#include "HoudiniPublicAPIBlueprintLib.generated.h" + +class UHoudiniPublicAPI; + +/** + * Houdini Public API Blueprint function library + */ +UCLASS(Category="Houdini Engine|Public API") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIBlueprintLib : public UBlueprintFunctionLibrary +{ + GENERATED_UCLASS_BODY() + +public: + /** Returns the Houdini Public API instance. */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "GetHoudiniEnginePublicAPI"), Category = "Houdini Engine") + static UHoudiniPublicAPI* GetAPI(); +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h new file mode 100644 index 000000000..3ac2bf80b --- /dev/null +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h @@ -0,0 +1,555 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HoudiniEngineRuntimeCommon.h" +#include "HoudiniPublicAPIObjectBase.h" + +#include "HoudiniPublicAPIInputTypes.generated.h" + +class UHoudiniInput; +class UHoudiniInputObject; +class UHoudiniSplineComponent; + +/** + * This class is the base class of a hierarchy that represents an input to an HDA in the public API. + * + * Each type of input has a derived class: + * - Geometry: UHoudiniPublicAPIGeoInput + * - Asset: UHoudiniPublicAPIAssetInput + * - Curve: UHoudiniPublicAPICurveInput + * - World: UHoudiniPublicAPIWorldInput + * - Landscape: UHoudiniPublicAPILandscapeInput + * + * Each instance of one of these classes represents the configuration of an input and wraps the actor/object/asset + * used as the input. These instances are always treated as copies of the actual state of the HDA's input: changing + * a property of one of these instances does not immediately affect the instantiated HDA: one has to pass the input + * instances as arguments to UHoudiniPublicAPIAssetWrapper::SetInputAtIndex() or + * UHoudiniPublicAPIAssetWrapper::SetInputParameter() functions to actually change the inputs on the instantiated asset. + * A copy of the existing input state of an instantiated HDA can be fetched via + * UHoudiniPublicAPIAssetWrapper::GetInputAtIndex() and UHoudiniPublicAPIAssetWrapper::GetInputParameter(). + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIInput : public UHoudiniPublicAPIObjectBase +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIInput(); + + /** Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value .*/ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bKeepWorldTransform; + + /** + * Indicates that all the input objects are imported to Houdini as references instead of actual geo + * (for Geo/World/Asset input types only) + */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bImportAsReference; + + /** Returns true if InObject is acceptable for this input type. + * @param InObject The object to check for acceptance as an input object on this input. + * @return true if InObject is acceptable for this input type. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs") + bool IsAcceptableObjectForInput(UObject* InObject) const; + virtual bool IsAcceptableObjectForInput_Implementation(UObject* InObject) const; + + /** + * Sets the specified objects as the input objects. + * @param InObjects The objects to set as input objects for this input. + * @return false if any object was incompatible (all compatible objects are added). + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs") + bool SetInputObjects(const TArray& InObjects); + + /** + * Gets the currently assigned input objects. + * @param OutObjects The current input objects of this input. + * @return true if input objects were successfully added to OutObjects (even if there are no input objects). + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs") + bool GetInputObjects(TArray& OutObjects); + + /** + * Populate this input instance from the internal UHoudiniInput instance InInput. + * This copies the configuration and UHoudiniInputObject(s). + * @param InInput The internal UHoudiniInput to copy to this instance. + * @return false if there were any errors while copying the input data. + */ + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput); + + /** + * Update an internal UHoudiniInput instance InInput from this API input instance. + * This copies the configuration and UHoudiniInputObject(s) from this API instance instance to InInput. + * @param InInput The internal UHoudiniInput to update from to this API instance. + * @return false if there were any errors while copying the input data. + */ + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const; + +protected: + /** + * Copy any properties we need from the UHoudiniInputObject InSrc for the the input object it wraps. + * @param InInputObject The UHoudiniInputObject to copy from. + * @param InObject The input object to copy per-object properties for. + * @return false if the copy failed, for example if InSrc is invalid or of incompatible type. + */ + virtual bool CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject); + + /** + * Copy any properties for InObject from this input wrapper to InInputObject. + * @param InObject The input object to copy per-object properties for. + * @param InInputObject The Houdini input object to copy the properties to. + * @return false if the copy failed, for example if InSrc is invalid or of incompatible type. + */ + virtual bool CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const; + + /** + * Convert an object used as an input in an internal UHoudiniInput to be API compatible. + * @param InInternalInputObject An object as an input by UHoudiniInput + * @return An API compatible input object created from InInternalInputObject. By default this is just + * InInternalInputObject. + */ + virtual UObject* ConvertInternalInputObject(UObject* InInternalInputObject) { return InInternalInputObject; } + + /** + * Convert an object used as an input in the API to be UHoudiniInput to be compatible and assign it to the + * InHoudiniInput at index InIndex. + * @param InAPIInputObject An input object in the API. + * @param InHoudiniInput The UHoudiniInput to assign the input to. + * @param InInputIndex The object index in InHoudiniInput to assign the converted input object to. + * @return A UHoudiniInput compatible input object created from InAPIInputObject. By default this is just + * InAPIInputObject. + */ + virtual UObject* ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const; + + /** + * Returns the type of the input. Subclasses should override this and return the appropriate input type. + * The base class / default implementation returns EHoudiniInputType::Invalid + */ + virtual EHoudiniInputType GetInputType() const { return EHoudiniInputType::Invalid; } + + /** The input objects for this input. */ + UPROPERTY() + TArray InputObjects; + +}; + + +/** + * API wrapper input class for geometry inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIGeoInput : public UHoudiniPublicAPIInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIGeoInput(); + + /** Indicates that the geometry must be packed before merging it into the input */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bPackBeforeMerge; + + /** Indicates that all LODs in the input should be marshalled to Houdini */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bExportLODs; + + /** Indicates that all sockets in the input should be marshalled to Houdini */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bExportSockets; + + /** Indicates that all colliders in the input should be marshalled to Houdini */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bExportColliders; + + /** + * Set the transform offset of the specified input object InObject (must already have been set via SetInputObjects()). + * @param InObject The input object to set a transform offset for. + * @param InTransform The transform offset to set. + * @return true if the object was found and the transform offset set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs", Meta=(AutoCreateRefTerm="InTransform")) + bool SetObjectTransformOffset(UObject* InObject, const FTransform& InTransform); + + /** + * Get the transform offset of the specified input object InObject (must already have been set via SetInputObjects()). + * @param InObject The input object to get a transform offset for. + * @param OutTransform The transform offset that was fetched. + * @return true if the object was found and the transform offset fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs", Meta=(AutoCreateRefTerm="InTransform")) + bool GetObjectTransformOffset(UObject* InObject, FTransform& OutTransform) const; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual bool CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject) override; + + virtual bool CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const override; + + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Geometry; } + + /** Per-Input-Object data: the transform offset per input object. */ + UPROPERTY() + TMap InputObjectTransformOffsets; +}; + +UENUM(BlueprintType) +enum class EHoudiniPublicAPICurveType : uint8 +{ + Invalid = 0, + + Polygon = 1, + Nurbs = 2, + Bezier = 3, + Points = 4 +}; + +UENUM(BlueprintType) +enum class EHoudiniPublicAPICurveMethod : uint8 +{ + Invalid = 0, + + CVs = 1, + Breakpoints = 2, + Freehand = 3 +}; + +/** + * API wrapper input class for curve inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs | Input Objects") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPICurveInputObject : public UHoudiniPublicAPIObjectBase +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPICurveInputObject(); + + /** + * Set the points of the curve (replacing any previously set points with InCurvePoints). + * @param InCurvePoints The new points to set / replace the curve's points with. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetCurvePoints(const TArray& InCurvePoints); + FORCEINLINE + virtual void SetCurvePoints_Implementation(const TArray& InCurvePoints) { CurvePoints = InCurvePoints; } + + /** + * Append a point to the end of this curve. + * @param InCurvePoint The point to append. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Meta=(AutoCreateRefTerm="InCurvePoint"), Category="Houdini Engine | Public API | Inputs | Input Objects") + void AppendCurvePoint(const FTransform& InCurvePoint); + FORCEINLINE + virtual void AppendCurvePoint_Implementation(const FTransform& InCurvePoint) { CurvePoints.Add(InCurvePoint); } + + /** Remove all points from the curve. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void ClearCurvePoints(); + FORCEINLINE + virtual void ClearCurvePoints_Implementation() { CurvePoints.Empty(); } + + /** + * Get all points of the curve. + * @param OutCurvePoints Set to a copy of all of the points of the curve. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void GetCurvePoints(TArray& OutCurvePoints) const; + FORCEINLINE + virtual void GetCurvePoints_Implementation(TArray& OutCurvePoints) const { OutCurvePoints = CurvePoints; } + + /** Returns true if this is a closed curve. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + bool IsClosed() const; + FORCEINLINE + virtual bool IsClosed_Implementation() const { return bClosed; } + + /** + * Set whether the curve is closed or not. + * @param bInClosed The new closed setting for the curve: set to true if the curve is closed. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetClosed(const bool bInClosed); + FORCEINLINE + virtual void SetClosed_Implementation(const bool bInClosed) { bClosed = bInClosed; } + + /** Returns true if the curve is reversed. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + bool IsReversed() const; + FORCEINLINE + virtual bool IsReversed_Implementation() const { return bReversed; } + + /** + * Set whether the curve is reversed or not. + * @param bInReversed The new reversed setting for the curve: set to true if the curve is reversed. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetReversed(const bool bInReversed); + FORCEINLINE + virtual void SetReversed_Implementation(const bool bInReversed) { bReversed = bInReversed; } + + /** Returns the curve type (for example: polygon, nurbs, bezier) */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + EHoudiniPublicAPICurveType GetCurveType() const; + FORCEINLINE + virtual EHoudiniPublicAPICurveType GetCurveType_Implementation() const { return CurveType; } + + /** + * Set the curve type (for example: polygon, nurbs, bezier). + * @param InCurveType The new curve type. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetCurveType(const EHoudiniPublicAPICurveType InCurveType); + FORCEINLINE + virtual void SetCurveType_Implementation(const EHoudiniPublicAPICurveType InCurveType) { CurveType = InCurveType; } + + /** Get the curve method, for example CVs, or freehand. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + EHoudiniPublicAPICurveMethod GetCurveMethod() const; + FORCEINLINE + virtual EHoudiniPublicAPICurveMethod GetCurveMethod_Implementation() const { return CurveMethod; } + + /** + * Set the curve method, for example CVs, or freehand. + * @param InCurveMethod The new curve method. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetCurveMethod(const EHoudiniPublicAPICurveMethod InCurveMethod); + FORCEINLINE + virtual void SetCurveMethod_Implementation(const EHoudiniPublicAPICurveMethod InCurveMethod) { CurveMethod = InCurveMethod; } + + /** + * Populate this wrapper from a UHoudiniSplineComponent. + * @param InSpline The spline to populate this wrapper from. + */ + void PopulateFromHoudiniSplineComponent(UHoudiniSplineComponent const* const InSpline); + + /** + * Copies the curve data to a UHoudiniSplineComponent. + * @param InSpline The spline to copy to. + */ + void CopyToHoudiniSplineComponent(UHoudiniSplineComponent* const InSpline) const; + +protected: + /** The control points of the curve. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Houdini Engine | Public API | Inputs | Input Objects") + TArray CurvePoints; + + /** Whether the curve is closed (true) or not. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") + bool bClosed; + + /** Whether the curve is reversed (true) or not. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") + bool bReversed; + + /** The curve type (for example: polygon, nurbs, bezier). */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") + EHoudiniPublicAPICurveType CurveType; + + /** The curve method, for example CVs, or freehand. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") + EHoudiniPublicAPICurveMethod CurveMethod; + + /** Helper function for converting EHoudiniPublicAPICurveType to EHoudiniCurveType */ + static EHoudiniCurveType ToHoudiniCurveType(const EHoudiniPublicAPICurveType InCurveType); + + /** Helper function for converting EHoudiniPublicAPICurveMethod to EHoudiniCurveMethod */ + static EHoudiniCurveMethod ToHoudiniCurveMethod(const EHoudiniPublicAPICurveMethod InCurveMethod); + + /** Helper function for converting EHoudiniCurveType to EHoudiniPublicAPICurveType */ + static EHoudiniPublicAPICurveType ToHoudiniPublicAPICurveType(const EHoudiniCurveType InCurveType); + + /** Helper function for converting EHoudiniCurveMethod to EHoudiniPublicAPICurveMethod */ + static EHoudiniPublicAPICurveMethod ToHoudiniPublicAPICurveMethod(const EHoudiniCurveMethod InCurveMethod); +}; + +/** + * API wrapper input class for curve inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPICurveInput : public UHoudiniPublicAPIInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPICurveInput(); + + /** Indicates that if trigger cook automatically on curve Input spline modified */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bCookOnCurveChanged; + + /** Set this to true to add rot and scale attributes on curve inputs. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bAddRotAndScaleAttributesOnCurves; + + virtual bool IsAcceptableObjectForInput_Implementation(UObject* InObject) const override; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Curve; } + + virtual UObject* ConvertInternalInputObject(UObject* InInternalInputObject) override; + + virtual UObject* ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const override; +}; + + +/** + * API wrapper input class for asset inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetInput : public UHoudiniPublicAPIInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIAssetInput(); + + virtual bool IsAcceptableObjectForInput_Implementation(UObject* InObject) const override; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Asset; } + + virtual UObject* ConvertInternalInputObject(UObject* InInternalInputObject) override; + + virtual UObject* ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const override; +}; + + +/** + * API wrapper input class for world inputs. Derived from UHoudiniPublicAPIGeoInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIWorldInput : public UHoudiniPublicAPIGeoInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIWorldInput(); + + /** Objects used for automatic bound selection */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + TArray WorldInputBoundSelectorObjects; + + /** Indicates that this world input is in "BoundSelector" mode */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bIsWorldInputBoundSelector; + + /** Indicates that selected actors by the bound selectors should update automatically */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bWorldInputBoundSelectorAutoUpdate; + + /** Resolution used when converting unreal splines to houdini curves */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + float UnrealSplineResolution; + + /** + * Setter for world input object array. If this is a bounds selector (#bIsWorldInputBoundSelector is true), then + * this function always returns false (and sets nothing), in that case only #WorldInputBoundSelectorObjects can be + * modified. + * @param InObjects The world input objects. + * @return true if all objects in InObjects could be set as world input objects, false otherwise. Always false + * if #bIsWorldInputBoundSelector is true. + */ + virtual bool SetInputObjects_Implementation(const TArray& InObjects) override; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::World; } + +}; + + +/** + * API wrapper input class for landscape inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPILandscapeInput : public UHoudiniPublicAPIInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPILandscapeInput(); + + /** Indicates that the landscape input's source landscape should be updated instead of creating a new component */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bUpdateInputLandscape; + + /** Indicates if the landscape should be exported as heightfield, mesh or points */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + EHoudiniLandscapeExportType LandscapeExportType; + + /** Is set to true when landscape input is set to selection only. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportSelectionOnly; + + /** Is set to true when the automatic selection of landscape component is active */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeAutoSelectComponent; + + /** Is set to true when materials are to be exported. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportMaterials; + + /** Is set to true when lightmap information export is desired. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportLighting; + + /** Is set to true when uvs should be exported in [0,1] space. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportNormalizedUVs; + + /** Is set to true when uvs should be exported for each tile separately. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportTileUVs; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Landscape; } + +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIObjectBase.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIObjectBase.h new file mode 100644 index 000000000..94c839b40 --- /dev/null +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIObjectBase.h @@ -0,0 +1,110 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "HoudiniPublicAPIObjectBase.generated.h" + +/** An enum with values that determine if API errors are logged. */ +UENUM(BlueprintType) +enum class EHoudiniPublicAPIErrorLogOption : uint8 +{ + Invalid = 0, + Auto = 1, + Log = 2, + NoLog = 3, +}; + +/** + * Base class for API UObjects. Implements error logging: record and get a error messages for Houdini Public API objects. + */ +UCLASS() +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIObjectBase : public UObject +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIObjectBase(); + + /** + * Gets the last error message recorded. + * @param OutLastErrorMessage Set to the last error message recorded, or the empty string if there are no errors + * messages. + * @return true if there was an error message to set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + bool GetLastErrorMessage(FString& OutLastErrorMessage) const; + + /** Clear any error messages that have been set. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + void ClearErrorMessages(); + + /** + * Returns whether or not API errors are written to the log. + * @return true if API errors are logged as warnings, false if API errors are not logged. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + bool IsLoggingErrors() const; + FORCEINLINE + virtual bool IsLoggingErrors_Implementation() const { return bIsLoggingErrors; } + + /** + * Sets whether or not API errors are written to the log. + * @param bInEnabled True if API errors should be logged as warnings, false if API errors should not logged. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + void SetLoggingErrorsEnabled(const bool bInEnabled); + FORCEINLINE + virtual void SetLoggingErrorsEnabled_Implementation(const bool bInEnabled) { bIsLoggingErrors = bInEnabled; } + +protected: + /** + * Set an error message. This is recorded as the current/last error message. + * @param InErrorMessage The error message to set. + * @param InLoggingOption Determines the behavior around logging the error message. If + * EHoudiniPublicAPIErrorLogOption.Invalid or EHoudiniPublicAPIErrorLogOption.Auto then IsLoggingErrors() is used to + * determine if the error message should be logged. If EHoudiniPublicAPIErrorLogOption.Log, then the error message + * is logged as a warning. If EHoudiniPublicAPIErrorLogOption.NoLog then the error message is not logged. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + void SetErrorMessage( + const FString& InErrorMessage, + const EHoudiniPublicAPIErrorLogOption InLoggingOption=EHoudiniPublicAPIErrorLogOption::Auto) const; + + /** The last error message that was set. */ + UPROPERTY() + mutable FString LastErrorMessage; + + /** True if an errors have been set and not yet cleared. */ + UPROPERTY() + mutable bool bHasError; + + /** If true, API errors logged with SetErrorMessage are written to the log as warnings by default. */ + UPROPERTY() + bool bIsLoggingErrors; +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIOutputTypes.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIOutputTypes.h new file mode 100644 index 000000000..cd2cd707d --- /dev/null +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIOutputTypes.h @@ -0,0 +1,94 @@ +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HoudiniEngineRuntimeCommon.h" + +#include "HoudiniPublicAPIOutputTypes.generated.h" + + +struct FHoudiniOutputObjectIdentifier; + +/** + * This class represents an output object identifier for an output object of a wrapped Houdini asset in the + * public API. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API | Outputs") +struct FHoudiniPublicAPIOutputObjectIdentifier +{ + GENERATED_BODY() + +public: + FHoudiniPublicAPIOutputObjectIdentifier(); + + FHoudiniPublicAPIOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier); + + /** Returns the internal output object identifier wrapped by this class. */ + FHoudiniOutputObjectIdentifier GetIdentifier() const; + + /** + * Sets the internal output object identifier wrapped by this class. + * @param InIdentifier The internal output object identifier. + */ + void SetIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier); + + /** String identifier for the split that created the output object identified by this identifier. */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Houdini Engine | Public API | Outputs") + FString SplitIdentifier; + + /** Name of the part used to generate the output object identified by this identifier. */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Houdini Engine | Public API | Outputs") + FString PartName; + +protected: + + // NodeId of corresponding Houdini Object. + UPROPERTY() + int32 ObjectId = -1; + + // NodeId of corresponding Houdini Geo. + UPROPERTY() + int32 GeoId = -1; + + // PartId + UPROPERTY() + int32 PartId = -1; + + // First valid primitive index for this output + // (used to read generic attributes) + UPROPERTY() + int32 PrimitiveIndex = -1; + + // First valid point index for this output + // (used to read generic attributes) + UPROPERTY() + int32 PointIndex = -1; + + bool bLoaded = false; +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIProcessHDANode.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIProcessHDANode.h new file mode 100644 index 000000000..93183b5c2 --- /dev/null +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIProcessHDANode.h @@ -0,0 +1,285 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Kismet/BlueprintAsyncActionBase.h" + +#include "HoudiniPublicAPIAssetWrapper.h" + +#include "HoudiniPublicAPIProcessHDANode.generated.h" + + +class UHoudiniPublicAPIInput; +class UHoudiniAsset; + +// Delegate type for output pins on the node. +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnProcessHDANodeOutputPinDelegate, UHoudiniPublicAPIAssetWrapper*, AssetWrapper, const bool, bCookSuccess, const bool, bBakeSuccess); + +/** + * A Blueprint async node for instantiating and cooking/processing an HDA, with delegate output pins for the + * various phases/state changes of the HDA. + */ +UCLASS() +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIProcessHDANode : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIProcessHDANode(const FObjectInitializer& ObjectInitializer); + + /** + * Instantiates an HDA in the specified world/level. Sets parameters and inputs supplied in InParameters, + * InNodeInputs and InParameterInputs. If bInEnableAutoCook is true, cooks the HDA. If bInEnableAutoBake is + * true, bakes the cooked outputs according to the supplied baking parameters. + * This all happens asynchronously, with the various output pins firing at the various points in the process: + * - PreInstantiation: before the HDA is instantiated, a good place to set parameter values before the first cook. + * - PostInstantiation: after the HDA is instantiated, a good place to set/configure inputs before the first cook. + * - PostAutoCook: right after a cook + * - PreProcess: after a cook but before output objects have been created/processed + * - PostProcessing: after output objects have been created + * - PostAutoBake: after outputs have been baked + * - Completed: upon successful completion (could be PostInstantiation if auto cook is disabled, PostProcessing + * if auto bake is disabled, or after PostAutoBake if auto bake is enabled. + * - Failed: If the process failed at any point. + * @param InHoudiniAsset The HDA to instantiate. + * @param InInstantiateAt The Transform to instantiate the HDA with. + * @param InParameters The parameter values to set before cooking the instantiated HDA. + * @param InNodeInputs The node inputs to set before cooking the instantiated HDA. + * @param InParameterInputs The parameter-based inputs to set before cooking the instantiated HDA. + * @param InWorldContextObject A world context object for identifying the world to spawn in, if + * InSpawnInLevelOverride is null. + * @param InSpawnInLevelOverride If not nullptr, then the AHoudiniAssetActor is spawned in that level. If both + * InSpawnInLevelOverride and InWorldContextObject are null, then the actor is spawned in the current editor + * context world's current level. + * @param bInEnableAutoCook If true (the default) the HDA will cook automatically after instantiation and after + * parameter, transform and input changes. + * @param bInEnableAutoBake If true, the HDA output is automatically baked after a cook. Defaults to false. + * @param InBakeDirectoryPath The directory to bake to if the bake path is not set via attributes on the HDA output. + * @param InBakeMethod The bake target (to actor vs blueprint). @see EHoudiniEngineBakeOption. + * @param bInRemoveOutputAfterBake If true, HDA temporary outputs are removed after a bake. Defaults to false. + * @param bInRecenterBakedActors Recenter the baked actors to their bounding box center. Defaults to false. + * @param bInReplacePreviousBake If true, on every bake replace the previous bake's output (assets + actors) with + * the new bake's output. Defaults to false. + * @param bInDeleteInstantiatedAssetOnCompletionOrFailure If true, deletes the instantiated asset actor on + * completion or failure. Defaults to false. + * @return The blueprint async node. + */ + UFUNCTION(BlueprintCallable, meta=(AdvancedDisplay=5,AutoCreateRefTerm="InInstantiateAt,InParameters,InNodeInputs,InParameterInputs",BlueprintInternalUseOnly="true", WorldContext="WorldContextObject"), Category="Houdini|Public API") + static UHoudiniPublicAPIProcessHDANode* ProcessHDA( + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + const TMap& InParameters, + const TMap& InNodeInputs, + const TMap& InParameterInputs, + UObject* InWorldContextObject=nullptr, + ULevel* InSpawnInLevelOverride=nullptr, + const bool bInEnableAutoCook=true, + const bool bInEnableAutoBake=false, + const FString& InBakeDirectoryPath="", + const EHoudiniEngineBakeOption InBakeMethod=EHoudiniEngineBakeOption::ToActor, + const bool bInRemoveOutputAfterBake=false, + const bool bInRecenterBakedActors=false, + const bool bInReplacePreviousBake=false, + const bool bInDeleteInstantiatedAssetOnCompletionOrFailure=false); + + virtual void Activate() override; + + /** + * Delegate that is broadcast when entering the PreInstantiation state: the HDA's default parameter definitions are + * available, but the node has not yet been instantiated in HAPI/Houdini Engine + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PreInstantiation; + + /** Delegate that is broadcast after the asset was successfully instantiated */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PostInstantiation; + + /** + * Delegate that is broadcast after a cook completes, but before outputs have been created. This will not be + * broadcast from this node if bInAutoCookEnabled is false. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PostAutoCook; + + /** + * Delegate that is broadcast just after PostCook (after parameters have been updated) but before creating + * output objects. This will not be broadcast from this node if bInAutoCookEnabled is false. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PreProcess; + + /** + * Delegate that is broadcast after processing HDA outputs after a cook, the output objects have been created. + * This will not be broadcast from this node if bInAutoCookEnabled is false. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PostProcessing; + + /** + * Delegate that is broadcast after auto-baking the asset (not called for individual outputs that are baked to the + * content browser). This will not be broadcast from this node if bInAutoBake or bInAutoCookEnabled is false. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PostAutoBake; + + /** + * Deletate that is broadcast on completion of async processing of the instantiated asset by this node. + * After this broadcast, the instantiated asset will be deleted if bInDeleteInstantiatedAssetOnCompletion=true + * was set on creation. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate Completed; + + /** Deletate that is broadcast if we fail during activation of the node. */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate Failed; + +protected: + + // Output pin data + + /** The asset wrapper for the instantiated HDA processed by this node. */ + UPROPERTY() + UHoudiniPublicAPIAssetWrapper* AssetWrapper; + + /** True if the last cook was successful. */ + UPROPERTY() + bool bCookSuccess; + + /** True if the last bake was successful. */ + UPROPERTY() + bool bBakeSuccess; + + // End: Output pin data + + /** The HDA to instantiate. */ + UPROPERTY() + UHoudiniAsset* HoudiniAsset; + + /** The transform the instantiate the asset with. */ + UPROPERTY() + FTransform InstantiateAt; + + /** The parameters to set on #PreInstantiation */ + UPROPERTY() + TMap Parameters; + + /** The node inputs to set on #PostInstantiation */ + UPROPERTY() + TMap NodeInputs; + + /** The object path parameter inputs to set on #PostInstantiation */ + UPROPERTY() + TMap ParameterInputs; + + /** The world context object: spawn in this world if #SpawnInLevelOverride is not set. */ + UPROPERTY() + UObject* WorldContextObject; + + /** The level to spawn in. If both this and #WorldContextObject is not set, spawn in the editor context's level. */ + UPROPERTY() + ULevel* SpawnInLevelOverride; + + /** Whether to set the instantiated asset to auto cook. */ + UPROPERTY() + bool bEnableAutoCook; + + /** Whether to set the instantiated asset to auto bake after a cook. */ + UPROPERTY() + bool bEnableAutoBake; + + /** Set the fallback bake directory, for if output attributes do not specify it. */ + UPROPERTY() + FString BakeDirectoryPath; + + /** The bake method/target: for example, to actors vs to blueprints. */ + UPROPERTY() + EHoudiniEngineBakeOption BakeMethod; + + /** Remove temporary HDA output after a bake. */ + UPROPERTY() + bool bRemoveOutputAfterBake; + + /** Recenter the baked actors at their bounding box center. */ + UPROPERTY() + bool bRecenterBakedActors; + + /** + * Replace previous bake output on each bake. For the purposes of this node, this would mostly apply to .uassets and + * not actors. + */ + UPROPERTY() + bool bReplacePreviousBake; + + /** Whether or not to delete the instantiated asset after Complete is called. */ + UPROPERTY() + bool bDeleteInstantiatedAssetOnCompletionOrFailure; + + /** Unbind all delegates */ + void UnbindDelegates(); + + /** Broadcast Failure and removes the node from the root set. */ + UFUNCTION() + virtual void HandleFailure(); + + /** Broadcast Complete and removes the node from the root set. */ + UFUNCTION() + virtual void HandleComplete(); + + /** + * Bound to the asset wrapper's pre-instantiation delegate. Sets the HDAs parameters from #Parameters and + * broadcasts #PreInstantiation. + */ + UFUNCTION() + virtual void HandlePreInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); + + /** + * Bound to the asset wrapper's post-instantiation delegate. Sets the HDAs inputs from #NodeInputs and + * #ParameterInputs and broadcasts #PostInstantiation. + */ + UFUNCTION() + virtual void HandlePostInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); + + /** Bound to the asset wrapper's post-cook delegate. Broadcasts #PostAutoCook. */ + UFUNCTION() + virtual void HandlePostAutoCook(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInCookSuccess); + + /** Bound to the asset wrapper's pre-processing delegate. Broadcasts #PreProcess. */ + UFUNCTION() + virtual void HandlePreProcess(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); + + /** Bound to the asset wrapper's post-processing delegate. Broadcasts #PostProcessing. */ + UFUNCTION() + virtual void HandlePostProcessing(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); + + /** Bound to the asset wrapper's post-bake delegate. Broadcasts #PostAutoBake. */ + UFUNCTION() + virtual void HandlePostAutoBake(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInBakeSuccess); +}; diff --git a/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs b/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs index b0e189cba..3df57b4aa 100644 --- a/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs +++ b/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs @@ -1,32 +1,29 @@ /* - * Copyright (c) <2020> Side Effects Software Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * Produced by: - * Side Effects Software Inc - * 123 Front Street West, Suite 1401 - * Toronto, Ontario - * Canada M5J 2M2 - * 416-504-9876 - * - */ +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + using UnrealBuildTool; using System; @@ -69,7 +66,8 @@ public HoudiniEngineRuntime( ReadOnlyTargetRules Target ) : base( Target ) "InputCore", "RHI", "Foliage", - "Landscape" + "Landscape", + "MeshUtilitiesCommon" } ); @@ -77,8 +75,7 @@ public HoudiniEngineRuntime( ReadOnlyTargetRules Target ) : base( Target ) new string[] { "Landscape", - "PropertyPath" - + "PhysicsCore" } ); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp index 42532d617..a05783499 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp index aece71f01..8bc9aa5f1 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,6 +25,7 @@ */ #include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" #include "HoudiniAsset.h" #include "HoudiniPDGAssetLink.h" @@ -67,7 +68,7 @@ AHoudiniAssetActor::ShouldImport(FString * ActorPropString, bool IsMovingLevel) // happens on copy / paste. ActorPropString->Empty(); - if (!CopiedActor || CopiedActor->IsPendingKill()) + if (!IsValid(CopiedActor)) { HOUDINI_LOG_WARNING(TEXT("Failed to import from copy: Duplicated actor not found")); return false; @@ -75,7 +76,7 @@ AHoudiniAssetActor::ShouldImport(FString * ActorPropString, bool IsMovingLevel) // Get Houdini component of an actor which is being copied. UHoudiniAssetComponent * CopiedActorHoudiniAssetComponent = CopiedActor->HoudiniAssetComponent; - if (!CopiedActorHoudiniAssetComponent || CopiedActorHoudiniAssetComponent->IsPendingKill()) + if (!IsValid(CopiedActorHoudiniAssetComponent)) return false; HoudiniAssetComponent->OnComponentClipboardCopy(CopiedActorHoudiniAssetComponent); @@ -94,16 +95,17 @@ AHoudiniAssetActor::ShouldImport(FString * ActorPropString, bool IsMovingLevel) } #endif */ + #if WITH_EDITOR bool AHoudiniAssetActor::GetReferencedContentObjects(TArray< UObject * >& Objects) const { Super::GetReferencedContentObjects(Objects); - if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) + if (IsValid(HoudiniAssetComponent)) { UHoudiniAsset* HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset(); - if (HoudiniAsset && !HoudiniAsset->IsPendingKill()) + if (IsValid(HoudiniAsset)) Objects.AddUnique(HoudiniAsset); } @@ -118,7 +120,7 @@ AHoudiniAssetActor::PostEditChangeProperty(FPropertyChangedEvent & PropertyChang Super::PostEditChangeProperty(PropertyChangedEvent); // Some property changes need to be forwarded to the component (ie Transform) - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return; FProperty* Property = PropertyChangedEvent.MemberProperty; @@ -142,4 +144,10 @@ AHoudiniAssetActor::IsUsedForPreview() const return HasAnyFlags(RF_Transient); } +UHoudiniPDGAssetLink* +AHoudiniAssetActor::GetPDGAssetLink() const +{ + return IsValid(HoudiniAssetComponent) ? HoudiniAssetComponent->GetPDGAssetLink() : nullptr; +} + #undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp index 44ed741b4..7c34de333 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,7 +24,6 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - #include "HoudiniAssetBlueprintComponent.h" #include "HoudiniEngineCopyPropertiesInterface.h" @@ -39,6 +38,7 @@ #include "HoudiniParameterFloat.h" #include "HoudiniParameterToggle.h" #include "HoudiniInput.h" +#include "HoudiniEngineRuntimePrivatePCH.h" #if WITH_EDITOR #include "Editor.h" @@ -135,9 +135,6 @@ UHoudiniAssetBlueprintComponent::CopyStateToTemplateComponent() // to copy state back to the BPGC at all! FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); check(BlueprintEditor); - - TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); - check(SCSEditor); USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); // check(SCSHACNode); @@ -167,7 +164,10 @@ UHoudiniAssetBlueprintComponent::CopyStateToTemplateComponent() UHoudiniOutput* InstanceOutput = nullptr; InstanceOutput = Outputs[i]; - check(InstanceOutput) + //check(InstanceOutput) + if (!IsValid(InstanceOutput)) + continue; + // Ensure that instance outputs won't delete houdini content. // Houdini content should only be allowed to be deleted from // the component template. @@ -568,12 +568,17 @@ UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent(UHoudiniAssetBlu else { InstanceOutput = TemplateOutput->DuplicateAndCopyProperties(this, FName(TemplateOutput->GetName())); - InstanceOutput->ClearFlags(RF_ArchetypeObject|RF_DefaultSubObject); + if (IsValid(InstanceOutput)) + InstanceOutput->ClearFlags(RF_ArchetypeObject|RF_DefaultSubObject); } - InstanceOutput->SetCanDeleteHoudiniNodes(false); Outputs[i] = InstanceOutput; + if (!IsValid(InstanceOutput)) + continue; + + InstanceOutput->SetCanDeleteHoudiniNodes(false); + TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); TMap& InstanceOutputObjects = InstanceOutput->GetOutputObjects(); TArray StaleOutputObjects; @@ -1363,7 +1368,7 @@ UHoudiniAssetBlueprintComponent::NotifyHoudiniRegisterCompleted() AssetId = -1; // Template component's have very limited update requirements / capabilities. // Mostly just cache parameters and cook state. - AssetState = EHoudiniAssetState::ProcessTemplate; + SetAssetState(EHoudiniAssetState::ProcessTemplate); } Super::NotifyHoudiniRegisterCompleted(); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h index a035a1345..d02640bd5 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp index 6d1198be0..ec24dd3d4 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,6 +33,8 @@ #include "HoudiniInput.h" #include "HoudiniOutput.h" #include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" #include "HoudiniParameterOperatorPath.h" #include "HoudiniHandleComponent.h" #include "HoudiniPDGAssetLink.h" @@ -48,6 +50,39 @@ #include "InstancedFoliageActor.h" #include "UObject/DevObjectVersion.h" #include "Serialization/CustomVersion.h" +#include "PhysicsEngine/BodySetup.h" +#include "UObject/UObjectGlobals.h" + +#if WITH_EDITOR + #include "Editor/UnrealEd/Private/GeomFitUtils.h" +#endif + +#include "ComponentReregisterContext.h" + +// Macro to update given properties on all children components of the HAC. +#define HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( COMPONENT_CLASS, PROPERTY ) \ + do \ + { \ + TArray ReregisterComponents; \ + TArray LocalAttachChildren;\ + GetChildrenComponents(true, LocalAttachChildren); \ + for (TArray::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) \ + { \ + COMPONENT_CLASS * Component = Cast(*Iter); \ + if (Component) \ + { \ + Component->PROPERTY = PROPERTY; \ + ReregisterComponents.Add(Component); \ + } \ + } \ + \ + if (ReregisterComponents.Num() > 0) \ + { \ + FMultiComponentReregisterContext MultiComponentReregisterContext(ReregisterComponents); \ + } \ + } \ + while(0) + void UHoudiniAssetComponent::Serialize(FArchive& Ar) @@ -110,11 +145,11 @@ UHoudiniAssetComponent::Serialize(FArchive& Ar) bool UHoudiniAssetComponent::ConvertLegacyData() { - if (!Version1CompatibilityHAC || Version1CompatibilityHAC->IsPendingKill()) + if (!IsValid(Version1CompatibilityHAC)) return false; // Set the Houdini Asset - if (!Version1CompatibilityHAC->HoudiniAsset || Version1CompatibilityHAC->HoudiniAsset->IsPendingKill()) + if (!IsValid(Version1CompatibilityHAC->HoudiniAsset)) return false; HoudiniAsset = Version1CompatibilityHAC->HoudiniAsset; @@ -149,7 +184,7 @@ UHoudiniAssetComponent::ConvertLegacyData() FoundOutput = Outputs.FindByPredicate( [InNewHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(InNewHGPO) : false; }); - if (FoundOutput && *FoundOutput && !(*FoundOutput)->IsPendingKill()) + if (FoundOutput && IsValid(*FoundOutput)) { // FoundOutput is valid, add to it NewOutput = *FoundOutput; @@ -177,7 +212,7 @@ UHoudiniAssetComponent::ConvertLegacyData() bool bCreatedNew = false; UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) + if (!IsValid(NewOutput)) continue; // Add the HGPO if we've just created it @@ -202,10 +237,10 @@ UHoudiniAssetComponent::ConvertLegacyData() OutputObj.bProxyIsCurrent = false; // Handle the SMC for this SM / HGPO - if (LegacySM.Value && !LegacySM.Value->IsPendingKill()) + if (IsValid(LegacySM.Value)) { UStaticMeshComponent** FoundSMC = Version1CompatibilityHAC->StaticMeshComponents.Find(LegacySM.Value); - if (FoundSMC && *FoundSMC && !(*FoundSMC)->IsPendingKill()) + if (FoundSMC && IsValid(*FoundSMC)) OutputObj.OutputComponent = *FoundSMC; } @@ -227,7 +262,7 @@ UHoudiniAssetComponent::ConvertLegacyData() bool bCreatedNew = false; UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) + if (!IsValid(NewOutput)) continue; // Add the HGPO if we've just created it @@ -263,7 +298,7 @@ UHoudiniAssetComponent::ConvertLegacyData() // ... instancers for (auto& LegacyInstanceIn : Version1CompatibilityHAC->InstanceInputs) { - if (!LegacyInstanceIn || LegacyInstanceIn->IsPendingKill()) + if (!IsValid(LegacyInstanceIn)) continue; FHoudiniGeoPartObject InstancerHGPO = LegacyInstanceIn->HoudiniGeoPartObject.ConvertLegacyData(); @@ -291,7 +326,7 @@ UHoudiniAssetComponent::ConvertLegacyData() bool bCreatedNew = false; UHoudiniOutput* NewOutput = FindOrCreateOutput(InstancerHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) + if (!IsValid(NewOutput)) continue; // Add the HGPO if we've just created it @@ -391,7 +426,7 @@ UHoudiniAssetComponent::ConvertLegacyData() for (auto& LegacyCurve : Version1CompatibilityHAC->SplineComponents) { UHoudiniSplineComponent* CurSplineComp = LegacyCurve.Value; - if (!CurSplineComp || CurSplineComp->IsPendingKill()) + if (!IsValid(CurSplineComp)) continue; // TODO: Needed? @@ -406,7 +441,7 @@ UHoudiniAssetComponent::ConvertLegacyData() // Look for an output for that HGPO bool bCreatedNew = false; UHoudiniOutput* NewOutput = FindOrCreateOutput(CurHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) + if (!IsValid(NewOutput)) continue; // Add the HGPO if we've just created it @@ -447,7 +482,7 @@ UHoudiniAssetComponent::ConvertLegacyData() // ... Materials UHoudiniAssetComponentMaterials_V1* LegacyMaterials = Version1CompatibilityHAC->HoudiniAssetComponentMaterials; - if(LegacyMaterials && !LegacyMaterials->IsPendingKill()) + if(IsValid(LegacyMaterials)) { // Assignements: Apply to all outputs since they're not tied to an HGPO... for (auto& CurOutput : Outputs) @@ -470,7 +505,7 @@ UHoudiniAssetComponent::ConvertLegacyData() bool bCreatedNew = false; UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) + if (!IsValid(NewOutput)) continue; if (bCreatedNew) @@ -498,19 +533,19 @@ UHoudiniAssetComponent::ConvertLegacyData() } // Then convert all remaing flags and properties - bGeneratedDoubleSidedGeometry = Version1CompatibilityHAC->bGeneratedDoubleSidedGeometry; - GeneratedPhysMaterial = Version1CompatibilityHAC->GeneratedPhysMaterial; - DefaultBodyInstance = Version1CompatibilityHAC->DefaultBodyInstance; - GeneratedCollisionTraceFlag = Version1CompatibilityHAC->GeneratedCollisionTraceFlag; - GeneratedLightMapResolution = Version1CompatibilityHAC->GeneratedLightMapResolution; - GeneratedLpvBiasMultiplier = Version1CompatibilityHAC->GeneratedLpvBiasMultiplier; - GeneratedDistanceFieldResolutionScale = Version1CompatibilityHAC->GeneratedDistanceFieldResolutionScale; - GeneratedWalkableSlopeOverride = Version1CompatibilityHAC->GeneratedWalkableSlopeOverride; - GeneratedLightMapCoordinateIndex = Version1CompatibilityHAC->GeneratedLightMapCoordinateIndex; - bGeneratedUseMaximumStreamingTexelRatio = Version1CompatibilityHAC->bGeneratedUseMaximumStreamingTexelRatio; - GeneratedStreamingDistanceMultiplier = Version1CompatibilityHAC->GeneratedStreamingDistanceMultiplier; - //GeneratedFoliageDefaultSettings = Version1CompatibilityHAC->GeneratedFoliageDefaultSettings; - GeneratedAssetUserData = Version1CompatibilityHAC->GeneratedAssetUserData; + StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry = Version1CompatibilityHAC->bGeneratedDoubleSidedGeometry; + StaticMeshGenerationProperties.GeneratedPhysMaterial = Version1CompatibilityHAC->GeneratedPhysMaterial; + StaticMeshGenerationProperties.DefaultBodyInstance = Version1CompatibilityHAC->DefaultBodyInstance; + StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = Version1CompatibilityHAC->GeneratedCollisionTraceFlag; + StaticMeshGenerationProperties.GeneratedLightMapResolution = Version1CompatibilityHAC->GeneratedLightMapResolution; + StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = Version1CompatibilityHAC->GeneratedWalkableSlopeOverride; + StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = Version1CompatibilityHAC->GeneratedLightMapCoordinateIndex; + StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = Version1CompatibilityHAC->bGeneratedUseMaximumStreamingTexelRatio; + StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier = Version1CompatibilityHAC->GeneratedStreamingDistanceMultiplier; + //StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings = Version1CompatibilityHAC->GeneratedFoliageDefaultSettings; + StaticMeshGenerationProperties.GeneratedAssetUserData = Version1CompatibilityHAC->GeneratedAssetUserData; + + StaticMeshBuildSettings.DistanceFieldResolutionScale = Version1CompatibilityHAC->GeneratedDistanceFieldResolutionScale; BakeFolder.Path = Version1CompatibilityHAC->BakeFolder.ToString(); TemporaryCookFolder.Path = Version1CompatibilityHAC->TempCookFolder.ToString(); @@ -525,6 +560,7 @@ UHoudiniAssetComponent::ConvertLegacyData() bCookOnAssetInputCook = true; bOutputless = false; bOutputTemplateGeos = false; + bUseOutputNodes = false; bFullyLoaded = Version1CompatibilityHAC->bFullyLoaded; //bContainsHoudiniLogoGeometry = Version1CompatibilityHAC->bContainsHoudiniLogoGeometry; @@ -579,7 +615,7 @@ UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & Object bCookOnAssetInputCook = true; AssetId = -1; - AssetState = EHoudiniAssetState::PreInstantiation; + AssetState = EHoudiniAssetState::NewHDA; AssetStateResult = EHoudiniAssetStateResult::None; AssetCookCount = 0; @@ -588,6 +624,8 @@ UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & Object // Make an invalid GUID, since we do not have any cooking requests. HapiGUID.Invalidate(); + HapiAssetName = FString(); + // Create unique component GUID. ComponentGUID = FGuid::NewGuid(); @@ -617,13 +655,14 @@ UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & Object bFullyLoaded = false; bOutputless = false; - bOutputTemplateGeos = false; + bUseOutputNodes = true; PDGAssetLink = nullptr; StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; + bOverrideGlobalProxyStaticMeshSettings = false; const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); if (HoudiniRuntimeSettings) { @@ -633,7 +672,15 @@ UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & Object bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; } - + else + { + bEnableProxyStaticMeshOverride = false; + bEnableProxyStaticMeshRefinementByTimerOverride = true; + ProxyMeshAutoRefineTimeoutSecondsOverride = 10.0f; + bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = true; + bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = true; + } + bNoProxyMeshNextCookRequested = false; bBakeAfterNextCook = false; @@ -648,6 +695,8 @@ UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & Object bRemoveOutputAfterBake = false; bRecenterBakedActors = false; bReplacePreviousBake = false; + + bAllowPlayInEditorRefinement = false; #endif // @@ -666,19 +715,14 @@ UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & Object // This component requires render update. bNeverNeedsRenderUpdate = false; - // Initialize static mesh generation parameters. - bGeneratedDoubleSidedGeometry = false; - GeneratedPhysMaterial = nullptr; - DefaultBodyInstance.SetCollisionProfileName("BlockAll"); - GeneratedCollisionTraceFlag = CTF_UseDefault; - GeneratedLpvBiasMultiplier = 1.0f; - GeneratedLightMapResolution = 32; - GeneratedLightMapCoordinateIndex = 1; - bGeneratedUseMaximumStreamingTexelRatio = false; - GeneratedStreamingDistanceMultiplier = 1.0f; - GeneratedDistanceFieldResolutionScale = 0.0f; - Bounds = FBox(ForceInitToZero); + + LastTickTime = 0.0; + + // Initialize the default SM Build settings with the plugin's settings default values + StaticMeshBuildSettings = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); + + } UHoudiniAssetComponent::~UHoudiniAssetComponent() @@ -695,6 +739,23 @@ void UHoudiniAssetComponent::PostInitProperties() { Super::PostInitProperties(); + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + { + // Copy default static mesh generation parameters from settings. + StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry; + StaticMeshGenerationProperties.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; + StaticMeshGenerationProperties.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; + StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; + StaticMeshGenerationProperties.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; + StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; + StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; + StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier; + StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; + StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; + StaticMeshGenerationProperties.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; + } + // Register ourself to the HER singleton RegisterHoudiniComponent(this); } @@ -825,11 +886,12 @@ UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const } } + void UHoudiniAssetComponent::SetHoudiniAsset(UHoudiniAsset * InHoudiniAsset) { // Check the asset validity - if (!InHoudiniAsset || InHoudiniAsset->IsPendingKill()) + if (!IsValid(InHoudiniAsset)) return; // If it is the same asset, do nothing. @@ -865,7 +927,7 @@ UHoudiniAssetComponent::NeedUpdateParameters() const // Go through all our parameters, return true if they have been updated for (auto CurrentParm : Parameters) { - if (!CurrentParm || CurrentParm->IsPendingKill()) + if (!IsValid(CurrentParm)) continue; if (!CurrentParm->HasChanged()) @@ -889,7 +951,7 @@ UHoudiniAssetComponent::NeedUpdateInputs() const // Go through all our inputs, return true if they have been updated for (auto CurrentInput : Inputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (!CurrentInput->HasChanged()) @@ -944,7 +1006,7 @@ UHoudiniAssetComponent::NeedUpdate() const return false; // We must have a valid asset - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + if (!IsValid(HoudiniAsset)) return false; if (bForceNeedUpdate) @@ -967,7 +1029,7 @@ UHoudiniAssetComponent::NeedUpdate() const // Go through all outputs, filter the editable nodes. Return true if they have been updated. for (auto CurrentOutput : Outputs) { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) + if (!IsValid(CurrentOutput)) continue; // We only care about editable outputs @@ -995,6 +1057,68 @@ UHoudiniAssetComponent::NeedUpdate() const return false; } +void +UHoudiniAssetComponent::PreventAutoUpdates() +{ + // It is important to check this when dealing with Blueprints since the + // preview components start receiving events from the template component + // before the preview component have finished initialization. + if (!IsFullyLoaded()) + return; + + bForceNeedUpdate = false; + bRecookRequested = false; + bRebuildRequested = false; + bHasComponentTransformChanged = false; + + // Go through all our parameters, prevent them from triggering updates + for (auto CurrentParm : Parameters) + { + if (!IsValid(CurrentParm)) + continue; + + // Prevent the parm from triggering an update + CurrentParm->SetNeedsToTriggerUpdate(false); + } + + // Same with inputs + for (auto CurrentInput : Inputs) + { + if (!IsValid(CurrentInput)) + continue; + + // Prevent the input from triggering an update + CurrentInput->SetNeedsToTriggerUpdate(false); + } + + // Go through all outputs, filter the editable nodes. + for (auto CurrentOutput : Outputs) + { + if (!IsValid(CurrentOutput)) + continue; + + // We only care about editable outputs + if (!CurrentOutput->IsEditableNode()) + continue; + + // Trigger an update if the output object is marked as modified by user. + TMap& OutputObjects = CurrentOutput->GetOutputObjects(); + for (auto& NextPair : OutputObjects) + { + // For now, only editable curves can trigger update + UHoudiniSplineComponent* HoudiniSplineComponent = Cast(NextPair.Value.OutputComponent); + if (!HoudiniSplineComponent) + continue; + + // Output curves cant trigger an update! + if (HoudiniSplineComponent->bIsOutputCurve) + continue; + + HoudiniSplineComponent->SetNeedsToTriggerUpdate(false); + } + } +} + // Indicates if any of the HAC's output components needs to be updated (no recook needed) bool UHoudiniAssetComponent::NeedOutputUpdate() const @@ -1002,7 +1126,7 @@ UHoudiniAssetComponent::NeedOutputUpdate() const // Go through all outputs for (auto CurrentOutput : Outputs) { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) + if (!IsValid(CurrentOutput)) continue; for (const auto& InstOutput : CurrentOutput->GetInstancedOutputs()) @@ -1040,12 +1164,12 @@ UHoudiniAssetComponent::NotifyCookedToDownstreamAssets() // Remove the downstream connection by default, // unless we actually were properly connected to one of this HDa's input. bool bRemoveDownstream = true; - if (CurrentDownstreamHAC && !CurrentDownstreamHAC->IsPendingKill()) + if (IsValid(CurrentDownstreamHAC)) { // Go through the HAC's input for (auto& CurrentDownstreamInput : CurrentDownstreamHAC->Inputs) { - if (!CurrentDownstreamInput || CurrentDownstreamInput->IsPendingKill()) + if (!IsValid(CurrentDownstreamInput)) continue; EHoudiniInputType CurrentDownstreamInputType = CurrentDownstreamInput->GetInputType(); @@ -1084,10 +1208,11 @@ UHoudiniAssetComponent::NeedsToWaitForInputHoudiniAssets() { for (auto& CurrentInput : Inputs) { - EHoudiniInputType CurrentInputType = CurrentInput->GetInputType(); - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; + EHoudiniInputType CurrentInputType = CurrentInput->GetInputType(); + if(CurrentInputType != EHoudiniInputType::Asset && CurrentInputType != EHoudiniInputType::World) continue; @@ -1111,7 +1236,7 @@ UHoudiniAssetComponent::NeedsToWaitForInputHoudiniAssets() if (InputHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) { // Tell the input HAC to instantiate - InputHAC->AssetState = EHoudiniAssetState::PreInstantiation; + InputHAC->SetAssetState(EHoudiniAssetState::PreInstantiation); // We need to wait return true; @@ -1160,10 +1285,37 @@ UHoudiniAssetComponent::MarkAsNeedCook() { if (!IsValid(CurrentParam)) continue; + + // Do not trigger parameter update for Button/Button strip when recooking + // As we don't want to trigger the buttons + if (CurrentParam->IsA() || CurrentParam->IsA()) + continue; + CurrentParam->MarkChanged(true); CurrentParam->SetNeedsToTriggerUpdate(true); } + // We need to mark all of our editable curves as changed + for (auto Output : Outputs) + { + if (!IsValid(Output) || Output->GetType() != EHoudiniOutputType::Curve || !Output->IsEditableNode()) + continue; + + for (auto& OutputObjectEntry : Output->GetOutputObjects()) + { + FHoudiniOutputObject& OutputObject = OutputObjectEntry.Value; + if (OutputObject.CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) + continue; + + UHoudiniSplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); + if (!IsValid(SplineComponent)) + continue; + + // This sets bHasChanged and bNeedsToTriggerUpdate + SplineComponent->MarkChanged(true); + } + } + // We need to mark all our inputs as changed/trigger update for (auto CurrentInput : Inputs) { @@ -1172,6 +1324,29 @@ UHoudiniAssetComponent::MarkAsNeedCook() CurrentInput->MarkChanged(true); CurrentInput->SetNeedsToTriggerUpdate(true); CurrentInput->MarkDataUploadNeeded(true); + + // In addition to marking the input as changed/need update, we also need to make sure that any changes on the + // Unreal side have been recorded for the input before sending to Houdini. For that we also mark each input + // object as changed/need update and explicitly call the Update function on each input object. For example, for + // input actors this would recreate the Houdini input actor components from the actor's components, picking up + // any new components since the last call to Update. + TArray* InputObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInput->GetInputType()); + if (InputObjectArray && InputObjectArray->Num() > 0) + { + for (auto CurrentInputObject : *InputObjectArray) + { + if (!IsValid(CurrentInputObject)) + continue; + + UObject* const Object = CurrentInputObject->GetObject(); + if (IsValid(Object)) + CurrentInputObject->Update(Object); + + CurrentInputObject->MarkChanged(true); + CurrentInputObject->SetNeedsToTriggerUpdate(true); + CurrentInputObject->MarkTransformChanged(true); + } + } } // Clear the static mesh bake timer @@ -1185,7 +1360,7 @@ UHoudiniAssetComponent::MarkAsNeedRebuild() //AssetId = -1; // Force the asset state to NeedRebuild - AssetState = EHoudiniAssetState::NeedRebuild; + SetAssetState(EHoudiniAssetState::NeedRebuild); AssetStateResult = EHoudiniAssetStateResult::None; // Reset some of the asset's flag @@ -1203,10 +1378,37 @@ UHoudiniAssetComponent::MarkAsNeedRebuild() { if (!IsValid(CurrentParam)) continue; + + // Do not trigger parameter update for Button/Button strip when rebuilding + // As we don't want to trigger the buttons + if (CurrentParam->IsA() || CurrentParam->IsA()) + continue; + CurrentParam->MarkChanged(true); CurrentParam->SetNeedsToTriggerUpdate(true); } + // We need to mark all of our editable curves as changed + for (auto Output : Outputs) + { + if (!IsValid(Output) || Output->GetType() != EHoudiniOutputType::Curve || !Output->IsEditableNode()) + continue; + + for (auto& OutputObjectEntry : Output->GetOutputObjects()) + { + FHoudiniOutputObject& OutputObject = OutputObjectEntry.Value; + if (OutputObject.CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) + continue; + + UHoudiniSplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); + if (!IsValid(SplineComponent)) + continue; + + // This sets bHasChanged and bNeedsToTriggerUpdate + SplineComponent->MarkChanged(true); + } + } + // We need to mark all our inputs as changed/trigger update for (auto CurrentInput : Inputs) { @@ -1232,16 +1434,16 @@ UHoudiniAssetComponent::MarkAsNeedInstantiation() { // The asset has no parameters or inputs. // This likely indicates it has never cooked/been instantiated. - // Set its state to PreInstantiation to force its instantiation + // Set its state to NewHDA to force its instantiation // so that we can have its parameters/input interface - AssetState = EHoudiniAssetState::PreInstantiation; + SetAssetState(EHoudiniAssetState::NewHDA); } else { // The asset has cooked before since we have a parameter/input interface // Set its state to need instantiation so that the asset is instantiated // after being modified - AssetState = EHoudiniAssetState::NeedInstantiation; + SetAssetState(EHoudiniAssetState::NeedInstantiation); } AssetStateResult = EHoudiniAssetStateResult::None; @@ -1312,7 +1514,7 @@ UHoudiniAssetComponent::PostLoad() // If we have deserialized legacy v1 data, attempt to convert it now ConvertLegacyData(); - if(bAutomaticLegacyHDARebuild) + if (bAutomaticLegacyHDARebuild) MarkAsNeedRebuild(); else MarkAsNeedInstantiation(); @@ -1331,6 +1533,15 @@ UHoudiniAssetComponent::PostLoad() // Register our PDG Asset link if we have any + // !!! Do not update rendering while loading, do it when setting up the render state + // UpdateRenderingInformation(); +} + +void +UHoudiniAssetComponent::CreateRenderState_Concurrent(FRegisterComponentContext* Context) +{ + UpdateRenderingInformation(); + Super::CreateRenderState_Concurrent(Context); } void @@ -1368,7 +1579,7 @@ UHoudiniAssetComponent::UpdatePostDuplicate() for (auto & NextChild : Children) { - if (!NextChild || NextChild->IsPendingKill()) + if (!IsValid(NextChild)) continue; USceneComponent * ComponentToRemove = nullptr; @@ -1476,7 +1687,7 @@ UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) // Clear Parameters for (UHoudiniParameter*& CurrentParm : Parameters) { - if (CurrentParm && !CurrentParm->IsPendingKill()) + if (IsValid(CurrentParm)) { CurrentParm->ConditionalBeginDestroy(); } @@ -1495,7 +1706,7 @@ UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) // Clear Inputs for (UHoudiniInput*& CurrentInput : Inputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (CurrentInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) @@ -1511,7 +1722,7 @@ UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) // Clear Output for (UHoudiniOutput*& CurrentOutput : Outputs) { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) + if (!IsValid(CurrentOutput)) continue; if (CurrentOutput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) @@ -1521,7 +1732,7 @@ UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) TArray & CurCreatedSocketActors = CurrentOutput->GetHoudiniCreatedSocketActors(); for (auto & CurCreatedActor : CurCreatedSocketActors) { - if (!CurCreatedActor || CurCreatedActor->IsPendingKill()) + if (!IsValid(CurCreatedActor)) continue; CurCreatedActor->Destroy(); @@ -1532,7 +1743,7 @@ UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) TArray & CurAttachedSocketActors = CurrentOutput->GetHoudiniAttachedSocketActors(); for (auto & CurAttachedSocketActor : CurAttachedSocketActors) { - if (!CurAttachedSocketActor || CurAttachedSocketActor->IsPendingKill()) + if (!IsValid(CurAttachedSocketActor)) continue; CurAttachedSocketActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); @@ -1549,21 +1760,30 @@ UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) continue; UStaticMesh* FoliageSM = FoliageHISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) + if (!IsValid(FoliageSM)) continue; // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, // if it is not, then we are just a "regular" HISMC AInstancedFoliageActor* InstancedFoliageActor = Cast(FoliageHISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + if (!IsValid(InstancedFoliageActor)) continue; UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) + if (!IsValid(FoliageType)) continue; - // Clean up the instances generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(this, FoliageType); + if (IsInGameThread() && IsGarbageCollecting()) + { + // TODO: ?? + // Calling DeleteInstancesForComponent during GC will cause unreal to crash... + HOUDINI_LOG_WARNING(TEXT("%s: Unable to clear foliage instances because of GC"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); + } + else + { + // Clean up the instances generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(this, FoliageType); + } if (FoliageHISMC->GetInstanceCount() > 0) { @@ -1641,7 +1861,7 @@ UHoudiniAssetComponent::OnRegister() for (TMap< UStaticMesh *, UStaticMeshComponent * >::TIterator Iter(StaticMeshComponents); Iter; ++Iter) { UStaticMeshComponent * StaticMeshComponent = Iter.Value(); - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + if (IsValid(StaticMeshComponent)) { // Recreate render state. StaticMeshComponent->RecreateRenderState_Concurrent(); @@ -1654,7 +1874,7 @@ UHoudiniAssetComponent::OnRegister() // Instanced static meshes. for (auto& InstanceInput : InstanceInputs) { - if (!InstanceInput || InstanceInput->IsPendingKill()) + if (!IsValid(InstanceInput)) continue; // Recreate render state. @@ -1693,12 +1913,12 @@ UHoudiniAssetComponent::OnRegister() UHoudiniParameter* UHoudiniAssetComponent::FindMatchingParameter(UHoudiniParameter* InOtherParam) { - if (!InOtherParam || InOtherParam->IsPendingKill()) + if (!IsValid(InOtherParam)) return nullptr; for (auto CurrentParam : Parameters) { - if (!CurrentParam || CurrentParam->IsPendingKill()) + if (!IsValid(CurrentParam)) continue; if (CurrentParam->Matches(*InOtherParam)) @@ -1711,12 +1931,12 @@ UHoudiniAssetComponent::FindMatchingParameter(UHoudiniParameter* InOtherParam) UHoudiniInput* UHoudiniAssetComponent::FindMatchingInput(UHoudiniInput* InOtherInput) { - if (!InOtherInput || InOtherInput->IsPendingKill()) + if (!IsValid(InOtherInput)) return nullptr; for (auto CurrentInput : Inputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (CurrentInput->Matches(*InOtherInput)) @@ -1729,12 +1949,12 @@ UHoudiniAssetComponent::FindMatchingInput(UHoudiniInput* InOtherInput) UHoudiniHandleComponent* UHoudiniAssetComponent::FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle) { - if (!InOtherHandle || InOtherHandle->IsPendingKill()) + if (!IsValid(InOtherHandle)) return nullptr; for (auto CurrentHandle : HandleComponents) { - if (!CurrentHandle || CurrentHandle->IsPendingKill()) + if (!IsValid(CurrentHandle)) continue; if (CurrentHandle->Matches(*InOtherHandle)) @@ -1749,7 +1969,7 @@ UHoudiniAssetComponent::FindParameterByName(const FString& InParamName) { for (auto CurrentParam : Parameters) { - if (!CurrentParam || CurrentParam->IsPendingKill()) + if (!IsValid(CurrentParam)) continue; if (CurrentParam->GetParameterName().Equals(InParamName)) @@ -1819,7 +2039,6 @@ UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyC // SetRefineMeshesTimer will check the relevant settings and only set the timer if enabled via settings SetRefineMeshesTimer(); } - //else if (PropertyName == TEXT("Mobility")) else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, Mobility)) { // Changed GetAttachChildren to 'GetAllDescendants' due to HoudiniMeshSplitInstanceComponent @@ -1833,14 +2052,12 @@ UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyC USceneComponent * SceneComponent = *Iter; SceneComponent->SetMobility(Mobility); } - } - //else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bVisible)) + } else if (PropertyName == TEXT("bVisible")) { // Visibility has changed, propagate it to children. SetVisibility(IsVisible(), true); } - //else if (PropertyName == TEXT("bHiddenInGame")) else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bHiddenInGame)) { // Visibility has changed, propagate it to children. @@ -1850,9 +2067,265 @@ UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyC { // TODO: // Propagate properties (mobility/visibility etc.. to children components) - // Look in v1 for: if (Property->HasMetaData(TEXT("Category"))) {} and HOUDINI_UPDATE_ALL_CHILD_COMPONENTS + // Look in v1 for: if (Property->HasMetaData(TEXT("Category"))) {} and HOUDINI_UPDATE_ALL_CHILD_COMPONENTS } + if (Property->HasMetaData(TEXT("Category"))) + { + const FString & Category = Property->GetMetaData(TEXT("Category")); + static const FString CategoryHoudiniGeneratedStaticMeshSettings = TEXT("HoudiniMeshGeneration"); + static const FString CategoryLighting = TEXT("Lighting"); + static const FString CategoryRendering = TEXT("Rendering"); + static const FString CategoryCollision = TEXT("Collision"); + static const FString CategoryPhysics = TEXT("Physics"); + static const FString CategoryLOD = TEXT("LOD"); + + if (CategoryHoudiniGeneratedStaticMeshSettings == Category) + { + // We are changing one of the mesh generation properties, we need to update all static meshes. + // As the StaticMeshComponents map contains only top-level static mesh components only, use the StaticMeshes map instead + for (UHoudiniOutput* CurOutput : Outputs) + { + if (!CurOutput) + continue; + + for (auto& Pair : CurOutput->GetOutputObjects()) + { + UStaticMesh* StaticMesh = Cast(Pair.Value.OutputObject); + if (!IsValid(StaticMesh)) + continue; + + SetStaticMeshGenerationProperties(StaticMesh); + FHoudiniScopedGlobalSilence ScopedGlobalSilence; + StaticMesh->Build(true); + RefreshCollisionChange(*StaticMesh); + } + } + + return; + } + else if (CategoryLighting == Category) + { + if (Property->GetName() == TEXT("CastShadow")) + { + // Stop cast-shadow being applied to invisible colliders children + // This prevent colliders only meshes from casting shadows + TArray ReregisterComponents; + { + TArray LocalAttachChildren; + GetChildrenComponents(true, LocalAttachChildren); + for (TArray< USceneComponent * >::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) + { + UStaticMeshComponent * Component = Cast< UStaticMeshComponent >(*Iter); + if (!IsValid(Component)) + continue; + + /*const FHoudiniGeoPartObject * pGeoPart = StaticMeshes.FindKey(Component->GetStaticMesh()); + if (pGeoPart && pGeoPart->IsCollidable()) + { + // This is an invisible collision mesh: + // Do not interfere with lightmap builds - disable shadow casting + Component->SetCastShadow(false); + } + else*/ + { + // Set normally + Component->SetCastShadow(CastShadow); + } + + ReregisterComponents.Add(Component); + } + } + + if (ReregisterComponents.Num() > 0) + { + FMultiComponentReregisterContext MultiComponentReregisterContext(ReregisterComponents); + } + } + else if (Property->GetName() == TEXT("bCastDynamicShadow")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bCastDynamicShadow); + } + else if (Property->GetName() == TEXT("bCastStaticShadow")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bCastStaticShadow); + } + else if (Property->GetName() == TEXT("bCastVolumetricTranslucentShadow")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bCastVolumetricTranslucentShadow); + } + else if (Property->GetName() == TEXT("bCastInsetShadow")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bCastInsetShadow); + } + else if (Property->GetName() == TEXT("bCastHiddenShadow")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bCastHiddenShadow); + } + else if (Property->GetName() == TEXT("bCastShadowAsTwoSided")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bCastShadowAsTwoSided); + } + /*else if ( Property->GetName() == TEXT( "bLightAsIfStatic" ) ) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bLightAsIfStatic ); + }*/ + else if (Property->GetName() == TEXT("bLightAttachmentsAsGroup")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bLightAttachmentsAsGroup); + } + else if (Property->GetName() == TEXT("IndirectLightingCacheQuality")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, IndirectLightingCacheQuality); + } + } + else if (CategoryRendering == Category) + { + if (Property->GetName() == TEXT("bVisibleInReflectionCaptures")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bVisibleInReflectionCaptures); + } + else if (Property->GetName() == TEXT("bRenderInMainPass")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bRenderInMainPass); + } + /* + else if ( Property->GetName() == TEXT( "bRenderInMono" ) ) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bRenderInMono ); + } + */ + else if (Property->GetName() == TEXT("bOwnerNoSee")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bOwnerNoSee); + } + else if (Property->GetName() == TEXT("bOnlyOwnerSee")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bOnlyOwnerSee); + } + else if (Property->GetName() == TEXT("bTreatAsBackgroundForOcclusion")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bTreatAsBackgroundForOcclusion); + } + else if (Property->GetName() == TEXT("bUseAsOccluder")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bUseAsOccluder); + } + else if (Property->GetName() == TEXT("bRenderCustomDepth")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bRenderCustomDepth); + } + else if (Property->GetName() == TEXT("CustomDepthStencilValue")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, CustomDepthStencilValue); + } + else if (Property->GetName() == TEXT("CustomDepthStencilWriteMask")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, CustomDepthStencilWriteMask); + } + else if (Property->GetName() == TEXT("TranslucencySortPriority")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, TranslucencySortPriority); + } + else if (Property->GetName() == TEXT("bReceivesDecals")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bReceivesDecals); + } + else if (Property->GetName() == TEXT("BoundsScale")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, BoundsScale); + } + else if (Property->GetName() == TEXT("bUseAttachParentBound")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(USceneComponent, bUseAttachParentBound); + } + } + else if (CategoryCollision == Category) + { + if (Property->GetName() == TEXT("bAlwaysCreatePhysicsState")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bAlwaysCreatePhysicsState); + } + /*else if ( Property->GetName() == TEXT( "bGenerateOverlapEvents" ) ) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bGenerateOverlapEvents ); + }*/ + else if (Property->GetName() == TEXT("bMultiBodyOverlap")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bMultiBodyOverlap); + } + /* + else if ( Property->GetName() == TEXT( "bCheckAsyncSceneOnMove" ) ) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bCheckAsyncSceneOnMove ); + } + */ + else if (Property->GetName() == TEXT("bTraceComplexOnMove")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bTraceComplexOnMove); + } + else if (Property->GetName() == TEXT("bReturnMaterialOnMove")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bReturnMaterialOnMove); + } + else if (Property->GetName() == TEXT("BodyInstance")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, BodyInstance); + } + else if (Property->GetName() == TEXT("CanCharacterStepUpOn")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, CanCharacterStepUpOn); + } + /*else if ( Property->GetName() == TEXT( "bCanEverAffectNavigation" ) ) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UActorComponent, bCanEverAffectNavigation ); + }*/ + } + else if (CategoryPhysics == Category) + { + if (Property->GetName() == TEXT("bIgnoreRadialImpulse")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bIgnoreRadialImpulse); + } + else if (Property->GetName() == TEXT("bIgnoreRadialForce")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bIgnoreRadialForce); + } + else if (Property->GetName() == TEXT("bApplyImpulseOnDamage")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bApplyImpulseOnDamage); + } + /* + else if ( Property->GetName() == TEXT( "bShouldUpdatePhysicsVolume" ) ) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( USceneComponent, bShouldUpdatePhysicsVolume ); + } + */ + } + else if (CategoryLOD == Category) + { + if (Property->GetName() == TEXT("MinDrawDistance")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, MinDrawDistance); + } + else if (Property->GetName() == TEXT("LDMaxDrawDistance")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, LDMaxDrawDistance); + } + else if (Property->GetName() == TEXT("CachedMaxDrawDistance")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, CachedMaxDrawDistance); + } + else if (Property->GetName() == TEXT("bAllowCullDistanceVolume")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(UPrimitiveComponent, bAllowCullDistanceVolume); + } + else if (Property->GetName() == TEXT("DetailMode")) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS(USceneComponent, DetailMode); + } + } + } } #endif @@ -1863,6 +2336,7 @@ UHoudiniAssetComponent::PostEditUndo() { Super::PostEditUndo(); + // TODO: PENDINGKILL replacement ? if (!IsPendingKill()) { // Make sure we are registered with the HER singleton @@ -1902,11 +2376,55 @@ UHoudiniAssetComponent::SetHasComponentTransformChanged(const bool& InHasChanged bHasComponentTransformChanged = InHasChanged; } +void UHoudiniAssetComponent::SetOutputNodeIds(const TArray& OutputNodes) +{ + NodeIdsToCook = OutputNodes; + // Remove stale entries from OutputNodeCookCounts: + TArray CachedNodeIds; + OutputNodeCookCounts.GetKeys(CachedNodeIds); + for(const int32 NodeId : CachedNodeIds) + { + if (!NodeIdsToCook.Contains(NodeId)) + { + OutputNodeCookCounts.Remove(NodeId); + } + } +} + +void UHoudiniAssetComponent::SetOutputNodeCookCount(const int& NodeId, const int& CookCount) +{ + OutputNodeCookCounts.Add(NodeId, CookCount); +} + +bool UHoudiniAssetComponent::HasOutputNodeChanged(const int& NodeId, const int& NewCookCount) +{ + if (!OutputNodeCookCounts.Contains(NodeId)) + { + return true; + } + if (OutputNodeCookCounts[NodeId] == NewCookCount) + { + return false; + } + return true; +} + +void UHoudiniAssetComponent::ClearOutputNodes() +{ + NodeIdsToCook.Empty(); + ClearOutputNodesCookCount(); +} + +void UHoudiniAssetComponent::ClearOutputNodesCookCount() +{ + OutputNodeCookCounts.Empty(); +} + void UHoudiniAssetComponent::SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) { // Check the object validity - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return; // If it is the same object, do nothing. @@ -1952,7 +2470,7 @@ UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& b // Query the bounds for all output objects for (auto & CurOutput : Outputs) { - if (!CurOutput || CurOutput->IsPendingKill()) + if (!IsValid(CurOutput)) continue; BoxBounds += CurOutput->GetBounds(); @@ -1961,7 +2479,7 @@ UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& b // Query the bounds for all our inputs for (auto & CurInput : Inputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; BoxBounds += CurInput->GetBounds(); @@ -1970,14 +2488,14 @@ UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& b // Query the bounds for all input parameters for (auto & CurParam : Parameters) { - if (!CurParam || CurParam->IsPendingKill()) + if (!IsValid(CurParam)) continue; if (CurParam->GetParameterType() != EHoudiniParameterType::Input) continue; UHoudiniParameterOperatorPath* InputParam = Cast(CurParam); - if (!CurParam || CurParam->IsPendingKill()) + if (!IsValid(CurParam)) continue; if (!InputParam->HoudiniInput.IsValid()) @@ -1989,7 +2507,7 @@ UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& b // Query the bounds for all our Houdini handles for (auto & CurHandleComp : HandleComponents) { - if (!CurHandleComp || CurHandleComp->IsPendingKill()) + if (!IsValid(CurHandleComp)) continue; BoxBounds += CurHandleComp->GetBounds(); @@ -2008,7 +2526,7 @@ UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& b USceneComponent * pChild = LocalAttachedChildren[Idx]; if (UStaticMeshComponent * StaticMeshComponent = Cast(pChild)) { - if (!StaticMeshComponent || StaticMeshComponent->IsPendingKill()) + if (!IsValid(StaticMeshComponent)) continue; FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox(); @@ -2142,6 +2660,7 @@ UHoudiniAssetComponent::IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDel bOutInvalidState = false; switch (AssetState) { + case EHoudiniAssetState::NewHDA: case EHoudiniAssetState::NeedInstantiation: case EHoudiniAssetState::PreInstantiation: case EHoudiniAssetState::Instantiating: @@ -2189,7 +2708,7 @@ UHoudiniAssetComponent::ApplyInputPresets() TArray InputArray; for (auto CurrentInput : Inputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (CurrentInput->GetInputType() != EHoudiniInputType::Curve) @@ -2200,7 +2719,7 @@ UHoudiniAssetComponent::ApplyInputPresets() for (TMap< UObject*, int32 >::TIterator IterToolPreset(InputPresets); IterToolPreset; ++IterToolPreset) { UObject * Object = IterToolPreset.Key(); - if (!Object || Object->IsPendingKill()) + if (!IsValid(Object)) continue; int32 InputNumber = IterToolPreset.Value(); @@ -2314,4 +2833,171 @@ bool UHoudiniAssetComponent::IsInstantiatingOrCooking() const { return HapiGUID.IsValid(); -} \ No newline at end of file +} + + +void +UHoudiniAssetComponent::SetStaticMeshGenerationProperties(UStaticMesh* InStaticMesh) const +{ +#if WITH_EDITOR + if (!InStaticMesh) + return; + + // Make sure static mesh has a new lighting guid. + InStaticMesh->SetLightingGuid(FGuid::NewGuid()); + InStaticMesh->LODGroup = NAME_None; + + // Set resolution of lightmap. + InStaticMesh->SetLightMapResolution(StaticMeshGenerationProperties.GeneratedLightMapResolution); + + const FStaticMeshRenderData* InRenderData = InStaticMesh->GetRenderData(); + // Set the global light map coordinate index if it looks valid + if (InRenderData && InRenderData->LODResources.Num() > 0) + { + int32 NumUVs = InRenderData->LODResources[0].GetNumTexCoords(); + if (NumUVs > StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex) + { + InStaticMesh->SetLightMapCoordinateIndex(StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex); + } + } + + // TODO + // Set method for LOD texture factor computation. + //InStaticMesh->bUseMaximumStreamingTexelRatio = StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio; + // Set distance where textures using UV 0 are streamed in/out. - GOES ON COMPONENT + // InStaticMesh->StreamingDistanceMultiplier = StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier; + + // Add user data. + for (int32 AssetUserDataIdx = 0; AssetUserDataIdx < StaticMeshGenerationProperties.GeneratedAssetUserData.Num(); AssetUserDataIdx++) + InStaticMesh->AddAssetUserData(StaticMeshGenerationProperties.GeneratedAssetUserData[AssetUserDataIdx]); + + // + if (!InStaticMesh->GetBodySetup()) + InStaticMesh->CreateBodySetup(); + + UBodySetup* BodySetup = InStaticMesh->GetBodySetup(); + if (!BodySetup) + return; + + // Set flag whether physics triangle mesh will use double sided faces when doing scene queries. + BodySetup->bDoubleSidedGeometry = StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry; + + // Assign physical material for simple collision. + BodySetup->PhysMaterial = StaticMeshGenerationProperties.GeneratedPhysMaterial; + + BodySetup->DefaultInstance.CopyBodyInstancePropertiesFrom(&StaticMeshGenerationProperties.DefaultBodyInstance); + + // Assign collision trace behavior. + BodySetup->CollisionTraceFlag = StaticMeshGenerationProperties.GeneratedCollisionTraceFlag; + + // Assign walkable slope behavior. + BodySetup->WalkableSlopeOverride = StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride; + + // We want to use all of geometry for collision detection purposes. + BodySetup->bMeshCollideAll = true; + +#endif +} + + +void +UHoudiniAssetComponent::UpdateRenderingInformation() +{ + // Need to send this to render thread at some point. + MarkRenderStateDirty(); + + // Update physics representation right away. + RecreatePhysicsState(); + + // Changed GetAttachChildren to 'GetAllDescendants' due to HoudiniMeshSplitInstanceComponent + // not propagating property changes to their own child StaticMeshComponents. + TArray LocalAttachChildren; + GetChildrenComponents(true, LocalAttachChildren); + for (TArray::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) + { + USceneComponent * SceneComponent = *Iter; + if (IsValid(SceneComponent)) + SceneComponent->RecreatePhysicsState(); + } + + // !!! Do not call UpdateBounds() here as this could cause + // a loading loop in post load on game builds! +} + + +FPrimitiveSceneProxy* +UHoudiniAssetComponent::CreateSceneProxy() +{ + /** Represents a UHoudiniAssetComponent to the scene manager. */ + class FHoudiniAssetSceneProxy final : public FPrimitiveSceneProxy + { + public: + SIZE_T GetTypeHash() const override + { + static size_t UniquePointer; + return reinterpret_cast(&UniquePointer); + } + + FHoudiniAssetSceneProxy(const UHoudiniAssetComponent* InComponent) + : FPrimitiveSceneProxy(InComponent) + { + } + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override + { + FPrimitiveViewRelevance Result; + Result.bDrawRelevance = IsShown(View); + return Result; + } + + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); } + uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } + }; + + return new FHoudiniAssetSceneProxy(this); +} + +void +UHoudiniAssetComponent::SetAssetState(EHoudiniAssetState InNewState) +{ + const EHoudiniAssetState OldState = AssetState; + AssetState = InNewState; + + HandleOnHoudiniAssetStateChange(this, OldState, InNewState); +} + +void +UHoudiniAssetComponent::HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) +{ + IHoudiniAssetStateEvents::HandleOnHoudiniAssetStateChange(InHoudiniAssetContext, InFromState, InToState); + + if (InFromState == InToState) + return; + + if (this != InHoudiniAssetContext) + return; + + FOnAssetStateChangeDelegate& StateChangeDelegate = GetOnAssetStateChangeDelegate(); + if (StateChangeDelegate.IsBound()) + StateChangeDelegate.Broadcast(this, InFromState, InToState); + + if (InToState == EHoudiniAssetState::PostCook) + { + HandleOnPostCook(); + } + +} + +void +UHoudiniAssetComponent::HandleOnPostCook() +{ + if (OnPostCookDelegate.IsBound()) + OnPostCookDelegate.Broadcast(this, bLastCookSuccess); +} + +void +UHoudiniAssetComponent::HandleOnPostBake(bool bInSuccess) +{ + if (OnPostBakeDelegate.IsBound()) + OnPostBakeDelegate.Broadcast(this, bInSuccess); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h index 1c05dfea2..ce1a8eeb4 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,12 +29,16 @@ #include "CoreMinimal.h" #include "UObject/ObjectMacros.h" +#include "HoudiniEngineRuntimeCommon.h" #include "HoudiniEngineRuntimeUtils.h" #include "HoudiniRuntimeSettings.h" #include "HoudiniOutput.h" #include "HoudiniInputTypes.h" #include "HoudiniPluginSerializationVersion.h" +#include "HoudiniAssetStateTypes.h" +#include "IHoudiniAssetStateEvents.h" +#include "Engine/EngineTypes.h" #include "Components/PrimitiveComponent.h" #include "Components/SceneComponent.h" @@ -48,93 +52,24 @@ class UHoudiniHandleComponent; class UHoudiniPDGAssetLink; class UHoudiniAssetComponent_V1; -UENUM() -enum class EHoudiniAssetState : uint8 -{ - // Loaded / Duplicated HDA, - // Will need to be instantiated upon change/update - NeedInstantiation, - - // Newly created HDA, needs to be instantiated immediately - PreInstantiation, - - // Instantiating task in progress - Instantiating, - - // Instantiated HDA, needs to be cooked immediately - PreCook, - - // Cooking task in progress - Cooking, - - // Cooking has finished - PostCook, - - // Cooked HDA, needs to be processed immediately - PreProcess, - - // Processing task in progress - Processing, - - // Processed / Updated HDA - // Will need to be cooked upon change/update - None, - - // Asset needs to be rebuilt (Deleted/Instantiated/Cooked) - NeedRebuild, - - // Asset needs to be deleted - NeedDelete, - - // Deleting - Deleting, - - // Process component template. This is ticking has very limited - // functionality, typically limited to checking for parameter updates - // in order to trigger PostEditChange() to run construction scripts again. - ProcessTemplate, -}; - -UENUM() -enum class EHoudiniAssetStateResult : uint8 -{ - None, - Working, - Success, - FinishedWithError, - FinishedWithFatalError, - Aborted -}; - UENUM() enum class EHoudiniStaticMeshMethod : uint8 { - // Use the RawMesh method to build the UStaticMesh + // Static Meshes will be generated by using Raw Meshes. RawMesh, - // Use the FMeshDescription method to build the UStaticMesh + // Static Meshes will be generated by using Mesh Descriptions. FMeshDescription, - // Build a fast proxy mesh: UHoudiniStaticMesh + // Always build Houdini Proxy Meshes (dev) UHoudiniStaticMesh, }; -#if WITH_EDITORONLY_DATA -UENUM() -enum class EHoudiniEngineBakeOption : uint8 -{ - ToActor, - ToBlueprint, - ToFoliage, - ToWorldOutliner, -}; -#endif - class UHoudiniAssetComponent; DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetEvent, UHoudiniAsset*); DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetComponentEvent, UHoudiniAssetComponent*) UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), ShowCategories = (Mobility), editinlinenew) -class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveComponent +class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveComponent, public IHoudiniAssetStateEvents { GENERATED_UCLASS_BODY() @@ -157,6 +92,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone // Declare the delegate that is broadcast when RefineMeshesTimer fires DECLARE_MULTICAST_DELEGATE_OneParam(FOnRefineMeshesTimerDelegate, UHoudiniAssetComponent*); DECLARE_DELEGATE_RetVal_OneParam(bool, FOnPostCookBakeDelegate, UHoudiniAssetComponent*); + // Delegate for when EHoudiniAssetState changes from InFromState to InToState on a Houdini Asset Component (InHAC). + DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnAssetStateChangeDelegate, UHoudiniAssetComponent*, const EHoudiniAssetState, const EHoudiniAssetState); + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostCookDelegate, UHoudiniAssetComponent*, bool); + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostBakeDelegate, UHoudiniAssetComponent*, bool); virtual ~UHoudiniAssetComponent(); @@ -184,6 +123,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone bool NeedBlueprintStructureUpdate() const; bool NeedBlueprintUpdate() const; + // Prevents automatic triggering of updates on this HAC in its current state. + // This is to prevent endless cook/instantiation loops when an issue happens + void PreventAutoUpdates(); + // Try to find one of our parameter that matches another (name, type, size and enabled) UHoudiniParameter* FindMatchingParameter(UHoudiniParameter* InOtherParam); @@ -221,6 +164,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone FString GetAssetStateAsString() const { return FHoudiniEngineRuntimeUtils::EnumToString(TEXT("EHoudiniAssetState"), GetAssetState()); }; EHoudiniAssetStateResult GetAssetStateResult() const { return AssetStateResult; }; FGuid GetHapiGUID() const { return HapiGUID; }; + FString GetHapiAssetName() const { return HapiAssetName; }; FGuid GetComponentGUID() const { return ComponentGUID; }; int32 GetNumInputs() const { return Inputs.Num(); }; @@ -271,7 +215,11 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone // Returns true if the asset should be bake after the next cook bool IsBakeAfterNextCookEnabled() const { return bBakeAfterNextCook; } + FOnPostCookDelegate& GetOnPostCookDelegate() { return OnPostCookDelegate; } FOnPostCookBakeDelegate& GetOnPostCookBakeDelegate() { return OnPostCookBakeDelegate; } + FOnPostBakeDelegate& GetOnPostBakeDelegate() { return OnPostBakeDelegate; } + + FOnAssetStateChangeDelegate& GetOnAssetStateChangeDelegate() { return OnAssetStateChangeDelegate; } // Derived blueprint based components will check whether the template // component contains updates that needs to processed. @@ -281,6 +229,12 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone // Returns true if the component has any previous baked output recorded in its outputs bool HasPreviousBakeOutput() const; + // Returns true if the last cook of the HDA was successful + bool WasLastCookSuccessful() const { return bLastCookSuccess; } + + // Returns true if a parameter definition update (excluding values) is needed. + bool IsParameterDefinitionUpdateNeeded() const { return bParameterDefinitionUpdateNeeded; } + //------------------------------------------------------------------------------------------------ // Mutators //------------------------------------------------------------------------------------------------ @@ -294,6 +248,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone //UFUNCTION(BlueprintSetter) virtual void SetHoudiniAsset(UHoudiniAsset * NewHoudiniAsset); + void SetCookingEnabled(const bool& bInCookingEnabled) { bEnableCooking = bInCookingEnabled; }; + void SetHasBeenLoaded(const bool& InLoaded) { bHasBeenLoaded = InLoaded; }; void SetHasBeenDuplicated(const bool& InDuplicated) { bHasBeenDuplicated = InDuplicated; }; @@ -310,7 +266,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone void MarkAsBlueprintStructureModified(); // The blueprint has been modified but not structurally changed. void MarkAsBlueprintModified(); - + // void SetAssetCookCount(const int32& InCount) { AssetCookCount = InCount; }; // @@ -320,6 +276,21 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone // void SetHasComponentTransformChanged(const bool& InHasChanged); + // Set an array of output nodes being tracked. + // This will remove any cook counts for nodes that are not in this list. + void SetOutputNodeIds(const TArray& OutputNodes); + TArray GetOutputNodeIds() const { return NodeIdsToCook; } + TMap GetOutputNodeCookCounts() const { return OutputNodeCookCounts; } + + // Store the latest cook count that was processed for this output node. + void SetOutputNodeCookCount(const int& NodeId, const int& CookCount); + // Compare the current node's cook count against the cached value. If they are different, return true. False otherwise. + bool HasOutputNodeChanged(const int& NodeId, const int& NewCookCount); + // Clear output nodes. This will also clear the output node cook counts. + void ClearOutputNodes(); + // Clear the cook counts for output nodes. This will trigger rebuild of data. + void ClearOutputNodesCookCount(); + // Set to True to force the next cook to not build a proxy mesh (regardless of global or override settings) and // instead build a UStaticMesh directly (if applicable for the output type). void SetNoProxyMeshNextCookRequested(bool bInNoProxyMeshNextCookRequested) { bNoProxyMeshNextCookRequested = bInNoProxyMeshNextCookRequested; } @@ -377,6 +348,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone #endif + void SetStaticMeshGenerationProperties(UStaticMesh* InStaticMesh) const; + virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent); virtual void OnRegister() override; @@ -395,6 +368,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone // return the cached component template, if available. virtual UHoudiniAssetComponent* GetCachedTemplate() const { return nullptr; } + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + //------------------------------------------------------------------------------------------------ // Supported Features //------------------------------------------------------------------------------------------------ @@ -449,6 +424,24 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone virtual void OnBlueprintStructureModified() { }; virtual void OnBlueprintModified() { }; + // + // Begin: IHoudiniAssetStateEvents + // + + virtual void HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) override; + + FORCEINLINE + virtual FOnHoudiniAssetStateChange& GetOnHoudiniAssetStateChangeDelegate() override { return OnHoudiniAssetStateChangeDelegate; } + + // + // End: IHoudiniAssetStateEvents + // + + // Called by HandleOnHoudiniAssetStateChange when entering the PostCook state. Broadcasts OnPostCookDelegate. + void HandleOnPostCook(); + + // Called by baking code after baking all outputs of this HAC (HoudiniEngineBakeOption) + void HandleOnPostBake(const bool bInSuccess); protected: @@ -460,6 +453,9 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone virtual void BeginDestroy() override; + // + virtual void CreateRenderState_Concurrent(FRegisterComponentContext* Context) override; + // Do any object - specific cleanup required immediately after loading an object. // This is not called for newly - created objects, and by default will always execute on the game thread. virtual void PostLoad() override; @@ -478,6 +474,15 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone // //static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); + // Updates physics state, bounds, and mark render state dirty + // Should be call PostLoad and PostProcessing + void UpdateRenderingInformation(); + + // Mutators + + // Set asset state + void SetAssetState(EHoudiniAssetState InNewState); + public: // Houdini Asset associated with this component. @@ -513,121 +518,55 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone UPROPERTY() bool bOutputTemplateGeos; + // Enabling this will allow outputing the asset's output nodes + UPROPERTY() + bool bUseOutputNodes; + // Temporary cook folder UPROPERTY() FDirectoryPath TemporaryCookFolder; - // Folder used for baking this asset's outputs + // Folder used for baking this asset's outputs (unless set by prim/detail attribute on the output). Falls back to + // the default from the plugin settings if not set. UPROPERTY() FDirectoryPath BakeFolder; + + // The method used to create Static Meshes + UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 0)) + EHoudiniStaticMeshMethod StaticMeshMethod; - // HoudiniGeneratedStaticMeshSettings - /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Double Sided Geometry")) - uint32 bGeneratedDoubleSidedGeometry : 1; - - /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Simple Collision Physical Material")) - UPhysicalMaterial * GeneratedPhysMaterial; - - /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ - UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, meta = (FullyExpand = "true")) - struct FBodyInstance DefaultBodyInstance; - - /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Collision Complexity")) - TEnumAsByte< enum ECollisionTraceFlag > GeneratedCollisionTraceFlag; - - /** Resolution of lightmap. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) - int32 GeneratedLightMapResolution; - - /** Bias multiplier for Light Propagation Volume lighting. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) - float GeneratedLpvBiasMultiplier; - - /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) - float GeneratedDistanceFieldResolutionScale; - - /** Custom walkable slope setting for generated mesh's body. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Walkable Slope Override")) - FWalkableSlopeOverride GeneratedWalkableSlopeOverride; - - /** The light map coordinate index. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Light map coordinate index")) - int32 GeneratedLightMapCoordinateIndex; - - /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) - uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; - - /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Streaming Distance Multiplier")) - float GeneratedStreamingDistanceMultiplier; - - /** Default settings when using this mesh for instanced foliage. */ - /* - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Foliage Default Settings")) - UFoliageType_InstancedStaticMesh * GeneratedFoliageDefaultSettings; - */ + // Generation properties for the Static Meshes generated by this Houdini Asset + UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 1)/*, meta = (ShowOnlyInnerProperties)*/) + FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; + + // Build Settings to be used when generating the Static Meshes for this Houdini Asset + UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 2)) + FMeshBuildSettings StaticMeshBuildSettings; - /** Array of user data stored with the asset. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Asset User Data")) - TArray GeneratedAssetUserData; - // Override the global fast proxy mesh settings on this component? - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayPriority = 0)) + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere/*, meta = (DisplayAfter = "StaticMeshGenerationProperties")*/) bool bOverrideGlobalProxyStaticMeshSettings; // For StaticMesh outputs: should a fast proxy be created first? - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Enable Proxy Static Mesh", EditCondition="bOverrideGlobalProxyStaticMeshSettings", DisplayPriority = 0)) + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Enable Proxy Static Mesh", EditCondition="bOverrideGlobalProxyStaticMeshSettings")) bool bEnableProxyStaticMeshOverride; // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes After a Timeout", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes After a Timeout", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) bool bEnableProxyStaticMeshRefinementByTimerOverride; // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride")) + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride")) float ProxyMeshAutoRefineTimeoutSecondsOverride; // Automatically refine proxy meshes to UStaticMesh before the map is saved - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes When Saving a Map", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes When Saving a Map", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) bool bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes On PIE", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes On PIE", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) bool bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; - // The method to use to create the mesh - UPROPERTY(Category = "HoudiniAsset | Development", EditAnywhere, meta = (DisplayPriority = 0)) - EHoudiniStaticMeshMethod StaticMeshMethod; - #if WITH_EDITORONLY_DATA UPROPERTY() bool bGenerateMenuExpanded; @@ -663,6 +602,15 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone UPROPERTY(DuplicateTransient) int32 AssetId; + // Ids of the nodes that should be cook for this HAC + // This is for additional output and templated nodes if they are used. + UPROPERTY(Transient, DuplicateTransient) + TArray NodeIdsToCook; + + // Cook counts for nodes in the NodeIdsToCook array. + UPROPERTY(Transient, DuplicateTransient) + TMap OutputNodeCookCounts; + // List of dependent downstream HACs that have us as an asset input UPROPERTY(DuplicateTransient) TSet DownstreamHoudiniAssets; @@ -675,6 +623,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone UPROPERTY(DuplicateTransient) FGuid HapiGUID; + // The asset name of the selected asset inside the asset library + UPROPERTY(DuplicateTransient) + FString HapiAssetName; + // Current state of the asset UPROPERTY(DuplicateTransient) EHoudiniAssetState AssetState; @@ -725,6 +677,12 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone UPROPERTY(DuplicateTransient) bool bLastCookSuccess; + // Indicates that the parameter state (excluding values) on the HAC and the instantiated node needs to be synced. + // The most common use for this would be a newly instantiated HDA that has only a default parameter interface + // from its asset definition, and needs to sync pre-cook. + UPROPERTY(DuplicateTransient) + bool bParameterDefinitionUpdateNeeded; + UPROPERTY(DuplicateTransient) bool bBlueprintStructureModified; @@ -787,12 +745,23 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone UPROPERTY(DuplicateTransient) bool bBakeAfterNextCook; + // Delegate to broadcast after a post cook event + // Arguments are (HoudiniAssetComponent* HAC, bool IsSuccessful) + FOnPostCookDelegate OnPostCookDelegate; + // Delegate to broadcast when baking after a cook. // Currently we cannot call the bake functions from here (Runtime module) // or from the HoudiniEngineManager (HoudiniEngine) module, so we use // a delegate. FOnPostCookBakeDelegate OnPostCookBakeDelegate; + // Delegate to broadcast after baking the HAC. Not called when just baking individual outputs directly. + // Arguments are (HoudiniAssetComponent* HAC, bool bIsSuccessful) + FOnPostBakeDelegate OnPostBakeDelegate; + + // Delegate that is broadcast when the asset state changes (HAC version). + FOnAssetStateChangeDelegate OnAssetStateChangeDelegate; + // Cached flag of whether this object is considered to be a 'preview' component or not. // This is typically useful in destructors when references to the World, for example, // is no longer available. @@ -803,4 +772,35 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone // Object used to convert V1 HAC to V2 HAC UHoudiniAssetComponent_V1* Version1CompatibilityHAC; + + // The last timestamp this component was ticked + // used to prioritize/limit the number of HAC processed per tick + UPROPERTY(Transient) + double LastTickTime; + + // + // Begin: IHoudiniAssetStateEvents + // + + // Delegate that is broadcast when AssetState changes + FOnHoudiniAssetStateChange OnHoudiniAssetStateChangeDelegate; + + // + // End: IHoudiniAssetStateEvents + // + +#if WITH_EDITORONLY_DATA + +public: + // Sets whether this HDA is allowed to be cooked in PIE + // for the purposes of refinement. + void SetAllowPlayInEditorRefinement(bool bEnabled) { bAllowPlayInEditorRefinement = bEnabled; } + bool IsPlayInEditorRefinementAllowed() const { return bAllowPlayInEditorRefinement; } + +protected: + UPROPERTY(Transient, DuplicateTransient) + bool bAllowPlayInEditorRefinement; + +#endif + }; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetStateTypes.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetStateTypes.h new file mode 100644 index 000000000..d18cf1ab4 --- /dev/null +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetStateTypes.h @@ -0,0 +1,92 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + + +UENUM() +enum class EHoudiniAssetState : uint8 +{ + // Loaded / Duplicated HDA, + // Will need to be instantiated upon change/update + NeedInstantiation, + + // Newly created HDA, fetch its default parameters then proceed to PreInstantiation + NewHDA, + + // Newly created HDA, after default parameters fetch, needs to be instantiated immediately + PreInstantiation, + + // Instantiating task in progress + Instantiating, + + // Instantiated HDA, needs to be cooked immediately + PreCook, + + // Cooking task in progress + Cooking, + + // Cooking has finished + PostCook, + + // Cooked HDA, needs to be processed immediately + PreProcess, + + // Processing task in progress + Processing, + + // Processed / Updated HDA + // Will need to be cooked upon change/update + None, + + // Asset needs to be rebuilt (Deleted/Instantiated/Cooked) + NeedRebuild, + + // Asset needs to be deleted + NeedDelete, + + // Deleting + Deleting, + + // Process component template. This is ticking has very limited + // functionality, typically limited to checking for parameter updates + // in order to trigger PostEditChange() to run construction scripts again. + ProcessTemplate, +}; + +UENUM() +enum class EHoudiniAssetStateResult : uint8 +{ + None, + Working, + Success, + FinishedWithError, + FinishedWithFatalError, + Aborted +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp index 5fcced98b..c1df7f412 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp @@ -1,24 +1,27 @@ /* -* Copyright (c) <2020> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. * -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: * -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. * -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. * +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "HoudiniCompatibilityHelpers.h" @@ -151,7 +154,7 @@ UHoudiniAssetComponent_V1::Serialize(FArchive & Ar) for (TMap::TIterator IterParams(Parameters); IterParams; ++IterParams) { UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); - if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill()) + if (!IsValid(HoudiniAssetParameter)) continue; if (HoudiniAssetParameter->GetFName() != NAME_None) @@ -179,7 +182,7 @@ UHoudiniAssetComponent_V1::Serialize(FArchive & Ar) for (TMap::TIterator IterParams(Parameters); IterParams; ++IterParams) { UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); - if (HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill()) + if (IsValid(HoudiniAssetParameter)) ParameterByName.Add(HoudiniAssetParameter->ParameterName, HoudiniAssetParameter); } } @@ -196,7 +199,7 @@ UHoudiniAssetComponent_V1::Serialize(FArchive & Ar) for (int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx) { UHoudiniAssetInput_V1 * HoudiniAssetInput = Inputs[InputIdx]; - if (HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill()) + if (IsValid(HoudiniAssetInput)) Inputs[InputIdx]->SetHoudiniAssetComponent(this); } } @@ -636,7 +639,7 @@ UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) { // Create a new InputObject wrapper UObject* CurObject = InputObjects[AtIndex]; - if (!CurObject || CurObject->IsPendingKill()) + if (!IsValid(CurObject)) continue; UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurObject, Input, FString::FromInt(AtIndex + 1)); @@ -655,7 +658,7 @@ UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) } } } - else if (InputType == EHoudiniInputType::Asset && InputAssetComponent != nullptr && !InputAssetComponent->IsPendingKill()) + else if (InputType == EHoudiniInputType::Asset && IsValid(InputAssetComponent)) { // Get the asset input object array TArray* AssetInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); @@ -664,7 +667,7 @@ UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent // We can simply use the v1's HAC outer for that UHoudiniAssetComponent* InputHAC = Cast(InputAssetComponent->GetOuter()); - if (InputHAC && !InputHAC->IsPendingKill()) + if (IsValid(InputHAC)) { // Create a new InputObject wrapper UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputHAC, Input, FString::FromInt(0)); @@ -682,7 +685,7 @@ UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) TArray* CurveInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); if (ensure(CurveInputObjectsPtr)) { - if (InputCurve && !InputCurve->IsPendingKill()) + if (IsValid(InputCurve)) { // Create a new InputObject wrapper UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputCurve, Input, FString::FromInt(0)); @@ -712,7 +715,7 @@ UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent // We can simply use the v1's HAC outer for that ALandscapeProxy* InLandscape = InputLandscapeProxy.Get(); - if (InLandscape && !InLandscape->IsPendingKill()) + if (IsValid(InLandscape)) { // Create a new InputObject wrapper UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InLandscape, Input, FString::FromInt(0)); @@ -761,7 +764,7 @@ UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) } } - if(!CurActor || CurActor->IsPendingKill()) + if(!IsValid(CurActor)) continue; // Create a new InputObject wrapper for the actor @@ -1036,7 +1039,7 @@ UHoudiniHandleComponent_V1::ConvertLegacyData(UObject* Outer) bool UHoudiniHandleComponent_V1::UpdateFromLegacyData(UHoudiniHandleComponent* NewHC) { - if (!NewHC || NewHC->IsPendingKill()) + if (!IsValid(NewHC)) return false; // TODO @@ -1107,7 +1110,7 @@ UHoudiniSplineComponent_V1::ConvertLegacyData(UObject* Outer) bool UHoudiniSplineComponent_V1::UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline) { - if (!NewSpline || NewSpline->IsPendingKill()) + if (!IsValid(NewSpline)) return false; NewSpline->SetFlags(RF_Transactional); @@ -1239,7 +1242,7 @@ UHoudiniAssetParameter::ConvertLegacyData(UObject* Outer) void UHoudiniAssetParameter::CopyLegacyParameterData(UHoudiniParameter* InNewParm) { - if (!InNewParm || InNewParm->IsPendingKill()) + if (!IsValid(InNewParm)) return; InNewParm->Name = ParameterName; @@ -1760,7 +1763,7 @@ UHoudiniMeshSplitInstancerComponent_V1::Serialize(FArchive & Ar) bool UHoudiniMeshSplitInstancerComponent_V1::UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC) { - if (!NewMSIC || NewMSIC->IsPendingKill()) + if (!IsValid(NewMSIC)) return false; NewMSIC->Instances = Instances; @@ -1788,7 +1791,7 @@ UHoudiniInstancedActorComponent_V1::Serialize(FArchive & Ar) bool UHoudiniInstancedActorComponent_V1::UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC) { - if (!NewIAC || NewIAC->IsPendingKill()) + if (!IsValid(NewIAC)) return false; //NewIAC->SetInstancedObject(InstancedAsset); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h index 391669c40..d35bb3898 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -898,12 +898,6 @@ class UHoudiniAssetComponent_V1 : public UPrimitiveComponent//, public IHoudiniC meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) int32 GeneratedLightMapResolution; - /** Bias multiplier for Light Propagation Volume lighting. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) - float GeneratedLpvBiasMultiplier; - /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp index 3e0e48ec4..8a5177375 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,3 +30,4 @@ void IHoudiniEngineCopyPropertiesInterface::CopyPropertiesFrom(UObject* FromObje { } + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h index 71fd0b034..aa49d8f5a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp index 61799b71e..b84148cb8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -124,7 +124,7 @@ FHoudiniEngineRuntime::CleanUpRegisteredHoudiniComponents() } UHoudiniAssetComponent* CurrentHAC = Ptr.Get(); - if (!CurrentHAC || CurrentHAC->IsPendingKill()) + if (!IsValid(CurrentHAC)) { UnRegisterHoudiniComponent(Idx); continue; @@ -150,7 +150,7 @@ FHoudiniEngineRuntime::RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, boo if (!FHoudiniEngineRuntime::IsInitialized()) return; - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; // RF_Transient indicates a temporary/preview object @@ -201,7 +201,7 @@ FHoudiniEngineRuntime::UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC) if (!IsInitialized()) return; - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; // Calling GetPathName here may lead to some crashes due to invalid outers... diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h index fd29f49f3..ecf7c8bfd 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h index 4e5378913..586655f58 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,14 +37,14 @@ // Declare the log category depending on the module we're in #ifdef HOUDINI_ENGINE_EDITOR #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_EDITOR - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineEditor, Log, All); +HOUDINIENGINEEDITOR_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineEditor, Log, All); #else #ifdef HOUDINI_ENGINE #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngine, Log, All); + HOUDINIENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngine, Log, All); #else #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_RUNTIME - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineRuntime, Log, All); + HOUDINIENGINERUNTIME_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineRuntime, Log, All); #endif #endif @@ -124,76 +124,115 @@ #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) #endif -// HOUDINI_ENGINE_DEBUG_BP: blueprint related debug logging -#ifndef HOUDINI_ENGINE_DEBUG_BP - #define HOUDINI_ENGINE_DEBUG_BP 0 -#endif - -// NOTE: Set HOUDINI_ENGINE_DEBUG_BP to enable BP logging -#if defined(HOUDINI_ENGINE_LOGGING) && HOUDINI_ENGINE_DEBUG_BP - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBlueprint, Log, All); - #define HOUDINI_BP_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEngineBlueprint); +#define HOUDINI_DEBUG_EXPAND_UE_LOG(LONG_NAME, VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngine##LONG_NAME, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) - #define HOUDINI_BP_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEngineBlueprint, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) +// --------------------------------------------------------- +// Blueprint Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_BP=1 to enable Blueprint logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_BP) + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBlueprint, Log, All); + #define HOUDINI_BP_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEngineBlueprint); #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_BP_DEFINE_LOG_CATEGORY() - #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_BP_DEFINE_LOG_CATEGORY() + #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) #endif -// HOUDINI_ENGINE_DEBUG_PDG: PDG related debug logging -#ifndef HOUDINI_ENGINE_DEBUG_PDG - #define HOUDINI_ENGINE_DEBUG_PDG 0 -#endif -// NOTE: Set HOUDINI_ENGINE_DEBUG_PDG to enable BP logging -#if defined(HOUDINI_ENGINE_LOGGING) && HOUDINI_ENGINE_DEBUG_PDG +// --------------------------------------------------------- +// PDG Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_PDG=1 to enable PDG logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_PDG) DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEnginePDG, Log, All); - #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEnginePDG); - - #define HOUDINI_PDG_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEnginePDG, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - + DEFINE_LOG_CATEGORY(LogHoudiniEnginePDG); #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() - #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() + #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) #endif + +// --------------------------------------------------------- +// Landscape Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_LANDSCAPE=1 to enable PDG logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineLandscape, Log, All); + #define HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEngineLandscape); + #define HOUDINI_LANDSCAPE_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_LANDSCAPE_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_LANDSCAPE_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_LANDSCAPE_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY() + #define HOUDINI_LANDSCAPE_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LANDSCAPE_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LANDSCAPE_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LANDSCAPE_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + + +// --------------------------------------------------------- +// Baking Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_BAKING=1 to enable PDG logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_BAKING) + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBaking, Log, All); + #define HOUDINI_BAKING_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEngineBaking); + #define HOUDINI_BAKING_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BAKING_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BAKING_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BAKING_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_BAKING_DEFINE_LOG_CATEGORY() + #define HOUDINI_BAKING_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BAKING_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BAKING_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BAKING_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + + // HAPI_Common enum HAPI_UNREAL_NodeType { @@ -211,9 +250,29 @@ enum HAPI_UNREAL_NodeFlags #define HAPI_UNREAL_DEFAULT_BAKE_FOLDER TEXT("/Game/HoudiniEngine/Bake"); #define HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER TEXT("/Game/HoudiniEngine/Temp"); +// Various variable names used to store meta information in generated packages. +// More in HoudiniEnginePrivatePCH.h +#define HAPI_UNREAL_PACKAGE_META_TEMP_GUID TEXT( "HoudiniPackageTempGUID" ) +#define HAPI_UNREAL_PACKAGE_META_COMPONENT_GUID TEXT( "HoudiniComponentGUID" ) + // Default PDG Filters #define HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER "HE_"; #define HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER "HE_OUT_"; - +// Struct to enable global silent flag - this will force dialogs to not show up. +struct FHoudiniScopedGlobalSilence +{ + FHoudiniScopedGlobalSilence() + { + bGlobalSilent = GIsSilent; + GIsSilent = true; + } + + ~FHoudiniScopedGlobalSilence() + { + GIsSilent = bGlobalSilent; + } + + bool bGlobalSilent; +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp index db7103133..bc7f226c2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,7 +25,11 @@ */ #include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" + #include "EngineUtils.h" +#include "Engine/EngineTypes.h" #if WITH_EDITOR #include "Editor.h" @@ -59,7 +63,7 @@ FHoudiniEngineRuntimeUtils::GetBoundingBoxesFromActors(const TArray InA for (auto CurrentActor : InActors) { - if (!CurrentActor || CurrentActor->IsPendingKill()) + if (!IsValid(CurrentActor)) continue; OutBBoxes.Add(CurrentActor->GetComponentsBoundingBox(true, true)); @@ -480,6 +484,20 @@ FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(UActorComponent* ComponentTe #if WITH_EDITOR + +// Centralized call to set actor label (changing Actor's implementation was too risky) +bool FHoudiniEngineRuntimeUtils::SetActorLabel(AActor* Actor, const FString& ActorLabel) +{ + // Clean up the incoming string a bit + FString NewActorLabel = ActorLabel.TrimStartAndEnd(); + if (NewActorLabel == Actor->GetActorLabel()) + { + return false; + } + Actor->SetActorLabel(NewActorLabel); + return true; +} + void FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) { @@ -529,7 +547,114 @@ void FHoudiniEngineRuntimeUtils::ForAllArchetypeInstances(UObject* InTemplateObj Operation(Instance); } } +#endif + +FHoudiniStaticMeshGenerationProperties +FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties() +{ + FHoudiniStaticMeshGenerationProperties SMGP; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + { + SMGP.bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry; + SMGP.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; + SMGP.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; + SMGP.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; + //SMGP.GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier; + SMGP.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; + SMGP.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; + SMGP.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; + SMGP.GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier; + SMGP.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; + SMGP.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; + SMGP.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; + } + + return SMGP; +} +FMeshBuildSettings +FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings() +{ + FMeshBuildSettings DefaultBuildSettings; -#endif + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + if(HoudiniRuntimeSettings) + { + DefaultBuildSettings.bRemoveDegenerates = HoudiniRuntimeSettings->bRemoveDegenerates; + DefaultBuildSettings.bUseMikkTSpace = HoudiniRuntimeSettings->bUseMikkTSpace; + DefaultBuildSettings.bBuildAdjacencyBuffer = HoudiniRuntimeSettings->bBuildAdjacencyBuffer; + DefaultBuildSettings.MinLightmapResolution = HoudiniRuntimeSettings->MinLightmapResolution; + DefaultBuildSettings.bUseFullPrecisionUVs = HoudiniRuntimeSettings->bUseFullPrecisionUVs; + DefaultBuildSettings.SrcLightmapIndex = HoudiniRuntimeSettings->SrcLightmapIndex; + DefaultBuildSettings.DstLightmapIndex = HoudiniRuntimeSettings->DstLightmapIndex; + + DefaultBuildSettings.bComputeWeightedNormals = HoudiniRuntimeSettings->bComputeWeightedNormals; + DefaultBuildSettings.bBuildReversedIndexBuffer = HoudiniRuntimeSettings->bBuildReversedIndexBuffer; + DefaultBuildSettings.bUseHighPrecisionTangentBasis = HoudiniRuntimeSettings->bUseHighPrecisionTangentBasis; + DefaultBuildSettings.bGenerateDistanceFieldAsIfTwoSided = HoudiniRuntimeSettings->bGenerateDistanceFieldAsIfTwoSided; + DefaultBuildSettings.bSupportFaceRemap = HoudiniRuntimeSettings->bSupportFaceRemap; + DefaultBuildSettings.DistanceFieldResolutionScale = HoudiniRuntimeSettings->DistanceFieldResolutionScale; + + // Recomputing normals. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeNormalFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeNormalsFlag; + switch (RecomputeNormalFlag) + { + case HRSRF_Never: + { + DefaultBuildSettings.bRecomputeNormals = false; + break; + } + + case HRSRF_Always: + case HRSRF_OnlyIfMissing: + default: + { + DefaultBuildSettings.bRecomputeNormals = true; + break; + } + } + + // Recomputing tangents. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeTangentFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag; + switch (RecomputeTangentFlag) + { + case HRSRF_Never: + { + DefaultBuildSettings.bRecomputeTangents = false; + break; + } + + case HRSRF_Always: + case HRSRF_OnlyIfMissing: + default: + { + DefaultBuildSettings.bRecomputeTangents = true; + break; + } + } + + // Lightmap UV generation. + EHoudiniRuntimeSettingsRecomputeFlag GenerateLightmapUVFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag; + switch (GenerateLightmapUVFlag) + { + case HRSRF_Never: + { + DefaultBuildSettings.bGenerateLightmapUVs = false; + break; + } + + case HRSRF_Always: + case HRSRF_OnlyIfMissing: + default: + { + DefaultBuildSettings.bGenerateLightmapUVs = true; + break; + } + } + } + + return DefaultBuildSettings; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h index cf8df231d..5295af722 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -27,6 +27,9 @@ #pragma once #include "UObject/ObjectMacros.h" +#include "UObject/UObjectGlobals.h" +#include "UObject/Class.h" +#include "UObject/Package.h" #if WITH_EDITOR #include "SSCSEditor.h" @@ -37,12 +40,15 @@ class AActor; class UWorld; +struct FHoudiniStaticMeshGenerationProperties; +struct FMeshBuildSettings; template class TSubclassOf; struct FBox; + struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils { public: @@ -50,6 +56,12 @@ struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils // Return platform specific name of libHAPI. static FString GetLibHAPIName(); + // Returns default SM Generation Properties using the default settings values + static FHoudiniStaticMeshGenerationProperties GetDefaultStaticMeshGenerationProperties(); + + // Returns default SM Build Settings using the default settings values + static FMeshBuildSettings GetDefaultMeshBuildSettings(); + // ----------------------------------------------- // Bounding Box utilities // ----------------------------------------------- @@ -105,30 +117,8 @@ struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils FORCEINLINE static bool GatherObjectReferencersForDeletion(UObject* InObject, bool& bOutIsReferenced, bool& bOutIsReferencedByUndo, FReferencerInformationList* OutMemoryReferences = nullptr, bool bInRequireReferencingProperties = false) { #if WITH_EDITOR - // DOESN'T EXIST IN 4.25 USING LEGACY EQUIVALENT - //ObjectTools::GatherObjectReferencersForDeletion(InObject, bOutIsReferenced, bOutIsReferencedByUndo, OutMemoryReferences, bInRequireReferencingProperties); - - bOutIsReferenced = false; - bOutIsReferencedByUndo = false; + ObjectTools::GatherObjectReferencersForDeletion(InObject, bOutIsReferenced, bOutIsReferencedByUndo, OutMemoryReferences, bInRequireReferencingProperties); - // Check and see whether we are referenced by any objects that won't be garbage collected. - bOutIsReferenced = IsReferenced(InObject, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, OutMemoryReferences); - if (bOutIsReferenced) - { - // determine whether the transaction buffer is the only thing holding a reference to the object - // and if so, offer the user the option to reset the transaction buffer. - GEditor->Trans->DisableObjectSerialization(); - bOutIsReferenced = IsReferenced(InObject, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, OutMemoryReferences); - GEditor->Trans->EnableObjectSerialization(); - - // If object is referenced both in undo and non-undo, we can't determine which one it is but - // it doesn't matter since the undo stack is only cleared if objects are only referenced by it. - if (!bOutIsReferenced) - { - bOutIsReferencedByUndo = true; - } - } - return true; #else return false; @@ -179,10 +169,16 @@ struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils // Taken from here: https://answers.unrealengine.com/questions/330496/conversion-of-enum-to-string.html // Return the string representation of an enum value. template - static FString EnumToString(const FString& enumName, const T value) + static FString EnumToString(const FString& EnumName, const T Value) + { + UEnum* Enum = FindObject(ANY_PACKAGE, *EnumName); + return *(Enum ? Enum->GetNameStringByValue(static_cast(Value)) : "null"); + } + + template + static FString EnumToString(const T Value) { - UEnum* pEnum = FindObject((UObject*)ANY_PACKAGE, *enumName); - return *(pEnum ? pEnum->GetNameStringByIndex(static_cast(value)) : "null"); + return UEnum::GetValueAsString(Value); } // ------------------------------------------------- @@ -205,6 +201,8 @@ struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils // Editor Helpers // ------------------------------------------------- #if WITH_EDITOR + static bool SetActorLabel(AActor* Actor, const FString& ActorLabel); + static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); static void DoPostEditChangeProperty(UObject* Obj, FProperty* Property); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp index 5a51bef74..6e7f7a4d2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,17 +26,21 @@ #include "HoudiniGenericAttribute.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" + #include "Engine/StaticMesh.h" #include "Components/ActorComponent.h" #include "Components/PrimitiveComponent.h" #include "Components/StaticMeshComponent.h" +#include "Landscape.h" #include "PhysicsEngine/BodySetup.h" #include "EditorFramework/AssetImportData.h" #include "AI/Navigation/NavCollisionBase.h" double -FHoudiniGenericAttribute::GetDoubleValue(int32 index) +FHoudiniGenericAttribute::GetDoubleValue(int32 index) const { if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) { @@ -58,7 +62,7 @@ FHoudiniGenericAttribute::GetDoubleValue(int32 index) } void -FHoudiniGenericAttribute::GetDoubleTuple(TArray& TupleValues, int32 index) +FHoudiniGenericAttribute::GetDoubleTuple(TArray& TupleValues, int32 index) const { TupleValues.SetNumZeroed(AttributeTupleSize); @@ -67,7 +71,7 @@ FHoudiniGenericAttribute::GetDoubleTuple(TArray& TupleValues, int32 inde } int64 -FHoudiniGenericAttribute::GetIntValue(int32 index) +FHoudiniGenericAttribute::GetIntValue(int32 index) const { if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) { @@ -89,7 +93,7 @@ FHoudiniGenericAttribute::GetIntValue(int32 index) } void -FHoudiniGenericAttribute::GetIntTuple(TArray& TupleValues, int32 index) +FHoudiniGenericAttribute::GetIntTuple(TArray& TupleValues, int32 index) const { TupleValues.SetNumZeroed(AttributeTupleSize); @@ -98,7 +102,7 @@ FHoudiniGenericAttribute::GetIntTuple(TArray& TupleValues, int32 index) } FString -FHoudiniGenericAttribute::GetStringValue(int32 index) +FHoudiniGenericAttribute::GetStringValue(int32 index) const { if (AttributeType == EAttribStorageType::STRING) { @@ -120,7 +124,7 @@ FHoudiniGenericAttribute::GetStringValue(int32 index) } void -FHoudiniGenericAttribute::GetStringTuple(TArray& TupleValues, int32 index) +FHoudiniGenericAttribute::GetStringTuple(TArray& TupleValues, int32 index) const { TupleValues.SetNumZeroed(AttributeTupleSize); @@ -129,7 +133,7 @@ FHoudiniGenericAttribute::GetStringTuple(TArray& TupleValues, int32 ind } bool -FHoudiniGenericAttribute::GetBoolValue(int32 index) +FHoudiniGenericAttribute::GetBoolValue(int32 index) const { if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) { @@ -151,7 +155,7 @@ FHoudiniGenericAttribute::GetBoolValue(int32 index) } void -FHoudiniGenericAttribute::GetBoolTuple(TArray& TupleValues, int32 index) +FHoudiniGenericAttribute::GetBoolTuple(TArray& TupleValues, int32 index) const { TupleValues.SetNumZeroed(AttributeTupleSize); @@ -181,11 +185,143 @@ FHoudiniGenericAttribute::GetData() return nullptr; } +void +FHoudiniGenericAttribute::SetDoubleValue(double InValue, int32 index) +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = InValue; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = InValue; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = FString::SanitizeFloat(InValue); + } +} + +void +FHoudiniGenericAttribute::SetDoubleTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetDoubleValue(InTupleValues[n], index * AttributeTupleSize + n); +} + +void +FHoudiniGenericAttribute::SetIntValue(int64 InValue, int32 index) +{ + if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = InValue; + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = InValue; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = FString::Printf(TEXT("%lld"), InValue); + } +} + +void +FHoudiniGenericAttribute::SetIntTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetIntValue(InTupleValues[n], index * AttributeTupleSize + n); +} + +void +FHoudiniGenericAttribute::SetStringValue(const FString& InValue, int32 index) +{ + if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = InValue; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = FCString::Atoi64(*InValue); + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = FCString::Atod(*InValue); + } +} + +void +FHoudiniGenericAttribute::SetStringTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetStringValue(InTupleValues[n], index * AttributeTupleSize + n); +} + +void +FHoudiniGenericAttribute::SetBoolValue(bool InValue, int32 index) +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = InValue ? 1.0 : 0.0; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = InValue ? 1 : 0; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = InValue ? "true" : "false"; + } +} + +void +FHoudiniGenericAttribute::SetBoolTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetBoolValue(InTupleValues[n], index * AttributeTupleSize + n); +} + bool FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( - UObject* InObject, FHoudiniGenericAttribute InPropertyAttribute, const int32& AtIndex) + UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; // Get the Property name @@ -194,15 +330,65 @@ FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( return false; // Some Properties need to be handle and modified manually... - if (PropertyName == "CollisionProfileName") + if (PropertyName.Equals("CollisionProfileName", ESearchCase::IgnoreCase)) { UPrimitiveComponent* PC = Cast(InObject); - if (PC && !PC->IsPendingKill()) + if (IsValid(PC)) { FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); FName Value = FName(*StringValue); PC->SetCollisionProfileName(Value); + // Patch the StaticMeshGenerationProperties on the HAC + UHoudiniAssetComponent* HAC = Cast(InObject); + if (IsValid(HAC)) + { + HAC->StaticMeshGenerationProperties.DefaultBodyInstance.SetCollisionProfileName(Value); + } + + return true; + } + return false; + } + + if (PropertyName.Equals("CollisionEnabled", ESearchCase::IgnoreCase)) + { + UPrimitiveComponent* PC = Cast(InObject); + if (IsValid(PC)) + { + FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); + if (StringValue.Equals("NoCollision", ESearchCase::IgnoreCase)) + { + PC->SetCollisionEnabled(ECollisionEnabled::NoCollision); + return true; + } + else if (StringValue.Equals("QueryOnly", ESearchCase::IgnoreCase)) + { + PC->SetCollisionEnabled(ECollisionEnabled::QueryOnly); + return true; + } + else if (StringValue.Equals("PhysicsOnly", ESearchCase::IgnoreCase)) + { + PC->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly); + return true; + } + else if (StringValue.Equals("QueryAndPhysics", ESearchCase::IgnoreCase)) + { + PC->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); + return true; + } + return false; + } + } + + // Specialize CastShadow to avoid paying the cost of finding property + calling Property change twice + if (PropertyName.Equals("CastShadow", ESearchCase::IgnoreCase)) + { + UPrimitiveComponent* Component = Cast< UPrimitiveComponent >(InObject); + if (IsValid(Component)) + { + bool Value = InPropertyAttribute.GetBoolValue(AtIndex); + Component->SetCastShadow(Value); return true; } return false; @@ -212,11 +398,11 @@ FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( if (PropertyName.Contains("Tags")) { UActorComponent* AC = Cast< UActorComponent >(InObject); - if (AC && !AC->IsPendingKill()) + if (IsValid(AC)) { FName NameAttr = FName(*InPropertyAttribute.GetStringValue(AtIndex)); if (!AC->ComponentTags.Contains(NameAttr)) - AC->ComponentTags.Add(NameAttr); + AC->ComponentTags.Add(NameAttr); /* for (int nIdx = 0; nIdx < InPropertyAttribute.AttributeCount; nIdx++) { @@ -229,12 +415,46 @@ FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( } return false; } +#if WITH_EDITOR + // Handle landscape edit layers toggling + if (PropertyName.Equals("EnableEditLayers", ESearchCase::IgnoreCase) + || PropertyName.Equals("bCanHaveLayersContent", ESearchCase::IgnoreCase)) + { + ALandscape* Landscape = Cast(InObject); + if (IsValid(Landscape)) + { + if(InPropertyAttribute.GetBoolValue(AtIndex) != Landscape->CanHaveLayersContent()) + Landscape->ToggleCanHaveLayersContent(); + + return true; + } + + return false; + } +#endif // Try to find the corresponding UProperty void* OutContainer = nullptr; FProperty* FoundProperty = nullptr; UObject* FoundPropertyObject = nullptr; - if (!FindPropertyOnObject(InObject, PropertyName, FoundProperty, FoundPropertyObject, OutContainer)) + +#if WITH_EDITOR + // Try to match to source model properties when possible + if (UStaticMesh* SM = Cast(InObject)) + { + if (IsValid(SM) && SM->GetNumSourceModels() > AtIndex) + { + bool bFoundProperty = false; + TryToFindProperty(&SM->GetSourceModel(AtIndex), SM->GetSourceModel(AtIndex).StaticStruct(), PropertyName, FoundProperty, bFoundProperty, OutContainer); + if (bFoundProperty) + { + FoundPropertyObject = InObject; + } + } + } +#endif + + if (!FoundProperty && !FindPropertyOnObject(InObject, PropertyName, FoundProperty, FoundPropertyObject, OutContainer)) return false; // Modify the Property we found @@ -254,14 +474,14 @@ FHoudiniGenericAttribute::FindPropertyOnObject( void*& OutContainer) { #if WITH_EDITOR - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; if (InPropertyName.IsEmpty()) return false; UClass* ObjectClass = InObject->GetClass(); - if (!ObjectClass || ObjectClass->IsPendingKill()) + if (!IsValid(ObjectClass)) return false; // Set the result pointer to null @@ -315,7 +535,7 @@ FHoudiniGenericAttribute::FindPropertyOnObject( { // Walk the structs' properties and try to find the one we're looking for UScriptStruct* Struct = StructProperty->Struct; - if (!Struct || Struct->IsPendingKill()) + if (!IsValid(Struct)) continue; for (TFieldIterator It(Struct); It; ++It) @@ -368,10 +588,10 @@ FHoudiniGenericAttribute::FindPropertyOnObject( // Handle common properties nested in classes // Static Meshes UStaticMesh* SM = Cast(InObject); - if (SM && !SM->IsPendingKill()) + if (IsValid(SM)) { - if (SM->BodySetup && FindPropertyOnObject( - SM->BodySetup, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + if (SM->GetBodySetup() && FindPropertyOnObject( + SM->GetBodySetup(), InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) { return true; } @@ -382,8 +602,8 @@ FHoudiniGenericAttribute::FindPropertyOnObject( return true; } - if (SM->NavCollision && FindPropertyOnObject( - SM->NavCollision, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + if (SM->GetNavCollision() && FindPropertyOnObject( + SM->GetNavCollision(), InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) { return true; } @@ -391,7 +611,7 @@ FHoudiniGenericAttribute::FindPropertyOnObject( // For Actors, parse their components AActor* Actor = Cast(InObject); - if (Actor && !Actor->IsPendingKill()) + if (IsValid(Actor)) { TArray AllComponents; Actor->GetComponents(AllComponents, true); @@ -399,7 +619,7 @@ FHoudiniGenericAttribute::FindPropertyOnObject( int32 CompIdx = 0; for (USceneComponent * SceneComponent : AllComponents) { - if (!SceneComponent || SceneComponent->IsPendingKill()) + if (!IsValid(SceneComponent)) continue; if (FindPropertyOnObject( @@ -432,7 +652,7 @@ FHoudiniGenericAttribute::TryToFindProperty( if (!InContainer) return false; - if (!InStruct || InStruct->IsPendingKill()) + if (!IsValid(InStruct)) return false; if (InPropertyName.IsEmpty()) @@ -468,7 +688,7 @@ FHoudiniGenericAttribute::TryToFindProperty( { // Walk the structs' properties and try to find the one we're looking for UScriptStruct* Struct = StructProperty->Struct; - if (!Struct || Struct->IsPendingKill()) + if (!IsValid(Struct)) continue; TryToFindProperty( @@ -504,12 +724,15 @@ FHoudiniGenericAttribute::ModifyPropertyValueOnObject( void* InContainer, const int32& InAtIndex) { - if (!InObject || InObject->IsPendingKill() || !FoundProperty) + if (!IsValid(InObject) || !FoundProperty) return false; // Determine the container to use (either InContainer if specified, or InObject) void* Container = InContainer ? InContainer : InObject; + // Property class name, used for logging + const FString PropertyClassName = FoundProperty->GetClass() ? FoundProperty->GetClass()->GetName() : TEXT("Unknown"); + // Initialize using the found property FProperty* InnerProperty = FoundProperty; @@ -533,455 +756,688 @@ FHoudiniGenericAttribute::ModifyPropertyValueOnObject( bHasModifiedProperty = true; }; - int32 NumberOfProperties = 1; FArrayProperty* ArrayProperty = CastField(FoundProperty); + TSharedPtr ArrayHelper; if (ArrayProperty) { InnerProperty = ArrayProperty->Inner; - NumberOfProperties = ArrayProperty->ArrayDim; - - // Do we need to add values to the array? - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - - //ArrayHelper.ExpandForIndex( InGenericAttribute.AttributeTupleSize - 1 ); - if (InGenericAttribute.AttributeTupleSize > NumberOfProperties) - { - ArrayHelper.Resize(InGenericAttribute.AttributeTupleSize); - NumberOfProperties = InGenericAttribute.AttributeTupleSize; - } + ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); } + // TODO: implement support for array attributes received from Houdini + // Get the "proper" AtIndex in the flat array by using the attribute tuple size // TODO: fix the issue when changing array of tuple properties! - int32 AtIndex = InAtIndex * InGenericAttribute.AttributeTupleSize; - - for (int32 nPropIdx = 0; nPropIdx < NumberOfProperties; nPropIdx++) + const int32 TupleSize = InGenericAttribute.AttributeTupleSize; + int32 AtIndex = InAtIndex * TupleSize; + FFieldClass* PropertyClass = InnerProperty->GetClass(); + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || + PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) { - if (FFloatProperty* FloatProperty = CastField(InnerProperty)) + // Supported non-struct properties + + // If the attribute from Houdini has a tuple size > 1, we support setting it on arrays on the unreal side + // For example: a 3 float from Houdini can be set as a TArray in Unreal. + + // If this is an ArrayProperty, ensure that it is at least large enough for our tuple + // TODO: should we just set this to exactly our tuple size? + if (ArrayHelper.IsValid()) + ArrayHelper->ExpandForIndex(TupleSize - 1); + + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) { - // FLOAT PROPERTY - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) { - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - FloatProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); - OnPropertyChanged(FloatProperty); - } + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); } else { - double Value = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } + // If this is not an ArrayProperty, it could be a fixed (standard C/C++ array), check the ArrayDim + // on the property to determine if our TupleIndex is in range, if not, give up, we cannot set any more + // of our tuple indices on this property. + if (TupleIndex >= InnerProperty->ArrayDim) + break; - if (ValuePtr) - { - FloatProperty->SetFloatingPointPropertyValue(ValuePtr, Value); - OnPropertyChanged(FloatProperty); - } + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); } - } - else if (FIntProperty* IntProperty = CastField(InnerProperty)) - { - // INT PROPERTY - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - if (ValuePtr) - { - IntProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); - OnPropertyChanged(IntProperty); - } - } - else + if (ValuePtr) { - int64 Value = InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else + // Handle each property type that we support + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + // Numeric properties are supported as floats and ints, and can also be set from a received string + FNumericProperty* const Property = CastField(InnerProperty); + if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + Property->SetNumericPropertyValueFromString(ValuePtr, *InGenericAttribute.GetStringValue(AtIndex + TupleIndex)); + } + else if (Property->IsFloatingPoint()) + { + Property->SetFloatingPointPropertyValue(ValuePtr, InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex)); + } + else if (Property->IsInteger()) + { + Property->SetIntPropertyValue(ValuePtr, InGenericAttribute.GetIntValue(AtIndex + TupleIndex)); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } } - - if (ValuePtr) + else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) { - IntProperty->SetIntPropertyValue(ValuePtr, Value); - OnPropertyChanged(IntProperty); + FBoolProperty* const Property = CastField(InnerProperty); + Property->SetPropertyValue(ValuePtr, InGenericAttribute.GetBoolValue(AtIndex + TupleIndex)); } - } - } - else if (FBoolProperty* BoolProperty = CastField(InnerProperty)) - { - // BOOL PROPERTY - bool Value = InGenericAttribute.GetBoolValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) + else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + FStrProperty* const Property = CastField(InnerProperty); + Property->SetPropertyValue(ValuePtr, InGenericAttribute.GetStringValue(AtIndex + TupleIndex)); } - else + else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + FNameProperty* const Property = CastField(InnerProperty); + Property->SetPropertyValue(ValuePtr, FName(*InGenericAttribute.GetStringValue(AtIndex + TupleIndex))); } - if (ValuePtr) + OnPropertyChanged(InnerProperty); + } + else { - BoolProperty->SetPropertyValue(ValuePtr, Value); - OnPropertyChanged(BoolProperty); + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s), tuple index %i"), *InGenericAttribute.AttributeName, *PropertyClassName, TupleIndex); + if (TupleIndex == 0) + return false; } } - else if (FStrProperty* StringProperty = CastField(InnerProperty)) - { - // STRING PROPERTY - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } + } + else if (FStructProperty* StructProperty = CastField(InnerProperty)) + { + // struct properties + + // If we receive an attribute with tuple size > 1 and the target is an Unreal struct property, then we set + // as many of the values as we can in the struct. For example: a 4-float received from Houdini where the + // target is an FVector, the FVector.X, Y and Z would be set from the 4-float indices 0-2. Index 3 from the + // 4-float would then be ignored. + + const int32 TupleIndex = 0; + // If this is an array property, ensure it has enough space + // TODO: should we just set the array size to 1 for non-arrays and to the array size for arrays (once we support array attributes from Houdini)? + // vs just ensuring there is enough space (and then potentially leaving previous/old data behind?) + if (ArrayHelper.IsValid()) + ArrayHelper->ExpandForIndex(TupleIndex); + + void* PropertyValue = nullptr; + if (ArrayHelper.IsValid()) + PropertyValue = ArrayHelper->GetRawPtr(TupleIndex); + else + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - if (ValuePtr) + if (PropertyValue) + { + const FName PropertyName = StructProperty->Struct->GetFName(); + if (PropertyName == NAME_Vector) { - StringProperty->SetPropertyValue(ValuePtr, Value); - OnPropertyChanged(StringProperty); + // Found a vector property, fill it with up to 3 tuple values + FVector& Vector = *static_cast(PropertyValue); + Vector = FVector::ZeroVector; + Vector.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + Vector.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + Vector.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); + + OnPropertyChanged(StructProperty); } - } - else if (FNumericProperty *NumericProperty = CastField(InnerProperty)) - { - // NUMERIC PROPERTY - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + else if (PropertyName == NAME_Transform) { - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - NumericProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); - OnPropertyChanged(NumericProperty); - } + // Found a transform property fill it with the attribute tuple values + FVector Translation; + Translation.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + Translation.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + Translation.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); + + FQuat Rotation; + if (InGenericAttribute.AttributeTupleSize > 3) + Rotation.W = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 3); + if (InGenericAttribute.AttributeTupleSize > 4) + Rotation.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 4); + if (InGenericAttribute.AttributeTupleSize > 5) + Rotation.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 5); + if (InGenericAttribute.AttributeTupleSize > 6) + Rotation.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 6); + + FVector Scale(1, 1, 1); + if (InGenericAttribute.AttributeTupleSize > 7) + Scale.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 7); + if (InGenericAttribute.AttributeTupleSize > 8) + Scale.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 8); + if (InGenericAttribute.AttributeTupleSize > 9) + Scale.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 9); + + FTransform& Transform = *static_cast(PropertyValue); + Transform = FTransform::Identity; + Transform.SetTranslation(Translation); + Transform.SetRotation(Rotation); + Transform.SetScale3D(Scale); + + OnPropertyChanged(StructProperty); } - else if (NumericProperty->IsInteger()) + else if (PropertyName == NAME_Color) { - int64 Value = InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } + FColor& Color = *static_cast(PropertyValue); + Color = FColor::Black; + Color.R = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + Color.G = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + Color.B = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + Color.A = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 3); + + OnPropertyChanged(StructProperty); + } + else if (PropertyName == NAME_LinearColor) + { + FLinearColor& LinearColor = *static_cast(PropertyValue); + LinearColor = FLinearColor::Black; + LinearColor.R = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + LinearColor.G = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + LinearColor.B = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + LinearColor.A = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 3); + + OnPropertyChanged(StructProperty); + } + else if (PropertyName == "Int32Interval") + { + FInt32Interval& Interval = *static_cast(PropertyValue); + Interval = FInt32Interval(); + Interval.Min = (int32)InGenericAttribute.GetIntValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + Interval.Max = (int32)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 1); - if (ValuePtr) - { - NumericProperty->SetIntPropertyValue(ValuePtr, (int64)Value); - OnPropertyChanged(NumericProperty); - } + OnPropertyChanged(StructProperty); } - else if (NumericProperty->IsFloatingPoint()) + else if (PropertyName == "FloatInterval") { - double Value = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } + FFloatInterval& Interval = *static_cast(PropertyValue); + Interval = FFloatInterval(); + Interval.Min = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + Interval.Max = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); - if (ValuePtr) - { - NumericProperty->SetFloatingPointPropertyValue(ValuePtr, Value); - OnPropertyChanged(NumericProperty); - } + OnPropertyChanged(StructProperty); } else { - // Numeric Property was found, but is of an unsupported type - HOUDINI_LOG_MESSAGE(TEXT("Unsupported Numeric UProperty")); + HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InGenericAttribute.AttributeName, *PropertyClassName, *PropertyName.ToString()); + return false; } } - else if (FNameProperty* NameProperty = CastField(InnerProperty)) + else { - // NAME PROPERTY - FString StringValue = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - FName Value = FName(*StringValue); + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + // OBJECT PATH PROPERTY + const int32 TupleIndex = 0; + // If this is an array property, ensure it has enough space + // TODO: should we just set the array size to 1 for non-arrays or to the array size for arrays (once we support array attributes from Houdini)? + // vs just ensuring there is enough space (and then potentially leaving previous/old data behind?) + if (ArrayHelper.IsValid()) + ArrayHelper->ExpandForIndex(TupleIndex); + + FString Value = InGenericAttribute.GetStringValue(AtIndex + TupleIndex); + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); + else + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + + if (ValuePtr) + { + // Using TryLoad() over LoadSynchronous() as the later could crash with invalid path + UObject* ValueObject = nullptr; + FSoftObjectPath ObjPath(Value); + if(ObjPath.IsValid()) + { + ValueObject = ObjPath.TryLoad(); + } - void * ValuePtr = nullptr; - if (ArrayProperty) + // Ensure the ObjectProperty class matches the ValueObject that we just loaded + if (!ValueObject || (ValueObject && ValueObject->IsA(ObjectProperty->PropertyClass))) { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + ObjectProperty->SetObjectPropertyValue(ValuePtr, ValueObject); + OnPropertyChanged(ObjectProperty); } else { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + HOUDINI_LOG_WARNING( + TEXT("Could net set object property %s: ObjectProperty's object class (%s) does not match referenced object class (%s)!"), + *InGenericAttribute.AttributeName, *(ObjectProperty->PropertyClass->GetName()), IsValid(ValueObject) ? *(ValueObject->GetClass()->GetName()) : TEXT("NULL")); + return false; } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else + { + // Property was found, but is of an unsupported type + HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InGenericAttribute.AttributeName); + return false; + } - if (ValuePtr) - { - NameProperty->SetPropertyValue(ValuePtr, Value); - OnPropertyChanged(NameProperty); - } + if (bHasModifiedProperty) + { +#if WITH_EDITOR + InObject->PostEditChange(); + if (InOwner) + { + InOwner->PostEditChange(); } - else if (FStructProperty* StructProperty = CastField(InnerProperty)) +#endif + } + + return true; +} + +bool +FHoudiniGenericAttribute::GetPropertyValueFromObject( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + FHoudiniGenericAttribute& InGenericAttribute, + const int32& InAtIndex) +{ + if (!IsValid(InObject) || !InFoundProperty) + return false; + + // Determine the container to use (either InContainer if specified, or InObject) + void* Container = InContainer ? InContainer : InObject; + + // Property class name, used for logging + const FString PropertyClassName = InFoundProperty->GetClass() ? InFoundProperty->GetClass()->GetName() : TEXT("Unknown"); + + // Initialize using the found property + FProperty* InnerProperty = InFoundProperty; + + FArrayProperty* ArrayProperty = CastField(InFoundProperty); + TSharedPtr ArrayHelper; + if (ArrayProperty) + { + InnerProperty = ArrayProperty->Inner; + ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); + } + + // TODO: implement support for array attributes received from Houdini + + // Get the "proper" AtIndex in the flat array by using the attribute tuple size + // TODO: fix the issue when changing array of tuple properties! + const int32 TupleSize = InGenericAttribute.AttributeTupleSize; + int32 AtIndex = InAtIndex * TupleSize; + FFieldClass* PropertyClass = InnerProperty->GetClass(); + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || + PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + // Supported non-struct properties + + // If the attribute from Houdini has a tuple size > 1, we support getting it on arrays on the unreal side + // For example: a 3 float in Houdini can be set from a TArray in Unreal. + + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) { - // STRUCT PROPERTY - const FName PropertyName = StructProperty->Struct->GetFName(); - if (PropertyName == NAME_Vector) + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) { - FVector* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } + // Check that we are not out of range + if (TupleIndex >= ArrayHelper->Num()) + break; + + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); + } + else + { + // If this is not an ArrayProperty, it could be a fixed (standard C/C++ array), check the ArrayDim + // on the property to determine if our TupleIndex is in range, if not, give up, we cannot get any more + // of our tuple indices from this property. + if (TupleIndex >= InnerProperty->ArrayDim) + break; - if (PropertyValue) - { - // Found a vector property, fill it with the 3 tuple values - PropertyValue->X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 0); - PropertyValue->Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - PropertyValue->Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); - OnPropertyChanged(StructProperty); - } + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); } - else if (PropertyName == NAME_Transform) + + if (ValuePtr) { - FTransform* PropertyValue = nullptr; - if (ArrayProperty) + // Handle each property type that we support + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + // Numeric properties are supported as floats and ints, and can also be set from a received string + FNumericProperty* const Property = CastField(InnerProperty); + if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + InGenericAttribute.SetStringValue(Property->GetNumericPropertyValueToString(ValuePtr), AtIndex + TupleIndex); + } + else if (Property->IsFloatingPoint()) + { + InGenericAttribute.SetDoubleValue(Property->GetFloatingPointPropertyValue(ValuePtr), AtIndex + TupleIndex); + } + else if (Property->IsInteger()) + { + InGenericAttribute.SetIntValue(Property->GetSignedIntPropertyValue(ValuePtr), AtIndex + TupleIndex); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } } - - if (PropertyValue) + else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) { - // Found a transform property fill it with the attribute tuple values - FVector Translation; - Translation.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 0); - Translation.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - Translation.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); - - FQuat Rotation; - Rotation.W = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 3); - Rotation.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 4); - Rotation.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 5); - Rotation.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 6); - - FVector Scale; - Scale.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 7); - Scale.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 8); - Scale.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 9); - - PropertyValue->SetTranslation(Translation); - PropertyValue->SetRotation(Rotation); - PropertyValue->SetScale3D(Scale); - - OnPropertyChanged(StructProperty); + FBoolProperty* const Property = CastField(InnerProperty); + InGenericAttribute.SetBoolValue(Property->GetPropertyValue(ValuePtr), AtIndex + TupleIndex); } - } - else if (PropertyName == NAME_Color) - { - FColor* PropertyValue = nullptr; - if (ArrayProperty) + else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); + FStrProperty* const Property = CastField(InnerProperty); + InGenericAttribute.SetStringValue(Property->GetPropertyValue(ValuePtr), AtIndex + TupleIndex); } - else + else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + FNameProperty* const Property = CastField(InnerProperty); + InGenericAttribute.SetStringValue(Property->GetPropertyValue(ValuePtr).ToString(), AtIndex + TupleIndex); } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s), tuple index %i"), *InGenericAttribute.AttributeName, *PropertyClassName, TupleIndex); + if (TupleIndex == 0) + return false; + } + } + } + else if (FStructProperty* StructProperty = CastField(InnerProperty)) + { + // struct properties - if (PropertyValue) - { - PropertyValue->R = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - PropertyValue->G = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 1); - PropertyValue->B = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 2); - if (InGenericAttribute.AttributeTupleSize == 4) - PropertyValue->A = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 3); + // Set as many as the tuple values as we can from the Unreal struct. + + const int32 TupleIndex = 0; - OnPropertyChanged(StructProperty); - } + void* PropertyValue = nullptr; + if (ArrayHelper.IsValid()) + { + if (ArrayHelper->IsValidIndex(TupleIndex)) + PropertyValue = ArrayHelper->GetRawPtr(TupleIndex); + } + else if (TupleIndex < InnerProperty->ArrayDim) + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + } + + if (PropertyValue) + { + const FName PropertyName = StructProperty->Struct->GetFName(); + if (PropertyName == NAME_Vector) + { + // Found a vector property, fill it with up to 3 tuple values + const FVector& Vector = *static_cast(PropertyValue); + InGenericAttribute.SetDoubleValue(Vector.X, AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(Vector.Y, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetDoubleValue(Vector.Z, AtIndex + TupleIndex + 2); + } + else if (PropertyName == NAME_Transform) + { + // Found a transform property fill it with the attribute tuple values + const FTransform& Transform = *static_cast(PropertyValue); + const FVector Translation = Transform.GetTranslation(); + const FQuat Rotation = Transform.GetRotation(); + const FVector Scale = Transform.GetScale3D(); + + InGenericAttribute.SetDoubleValue(Translation.X, AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(Translation.Y, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetDoubleValue(Translation.Z, AtIndex + TupleIndex + 2); + + if (InGenericAttribute.AttributeTupleSize > 3) + InGenericAttribute.SetDoubleValue(Rotation.W, AtIndex + TupleIndex + 3); + if (InGenericAttribute.AttributeTupleSize > 4) + InGenericAttribute.SetDoubleValue(Rotation.X, AtIndex + TupleIndex + 4); + if (InGenericAttribute.AttributeTupleSize > 5) + InGenericAttribute.SetDoubleValue(Rotation.Y, AtIndex + TupleIndex + 5); + if (InGenericAttribute.AttributeTupleSize > 6) + InGenericAttribute.SetDoubleValue(Rotation.Z, AtIndex + TupleIndex + 6); + + if (InGenericAttribute.AttributeTupleSize > 7) + InGenericAttribute.SetDoubleValue(Scale.X, AtIndex + TupleIndex + 7); + if (InGenericAttribute.AttributeTupleSize > 8) + InGenericAttribute.SetDoubleValue(Scale.Y, AtIndex + TupleIndex + 8); + if (InGenericAttribute.AttributeTupleSize > 9) + InGenericAttribute.SetDoubleValue(Scale.Z, AtIndex + TupleIndex + 9); + } + else if (PropertyName == NAME_Color) + { + const FColor& Color = *static_cast(PropertyValue); + InGenericAttribute.SetIntValue(Color.R, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetIntValue(Color.G, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetIntValue(Color.B, AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + InGenericAttribute.SetIntValue(Color.A, AtIndex + TupleIndex + 3); } else if (PropertyName == NAME_LinearColor) { - FLinearColor* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - PropertyValue->R = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - PropertyValue->G = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - PropertyValue->B = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); - if (InGenericAttribute.AttributeTupleSize == 4) - PropertyValue->A = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 3); - - OnPropertyChanged(StructProperty); - } + const FLinearColor& LinearColor = *static_cast(PropertyValue); + InGenericAttribute.SetDoubleValue(LinearColor.R, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(LinearColor.G, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetDoubleValue(LinearColor.B, AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + InGenericAttribute.SetDoubleValue(LinearColor.A, AtIndex + TupleIndex + 3); } else if (PropertyName == "Int32Interval") { - FInt32Interval* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - PropertyValue->Min = (float)InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - PropertyValue->Max = (float)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 1); - - OnPropertyChanged(StructProperty); - } + const FInt32Interval& Interval = *static_cast(PropertyValue); + InGenericAttribute.SetIntValue(Interval.Min, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetIntValue(Interval.Max, AtIndex + TupleIndex + 1); } else if (PropertyName == "FloatInterval") { - FFloatInterval* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } + const FFloatInterval& Interval = *static_cast(PropertyValue); + InGenericAttribute.SetDoubleValue(Interval.Min, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(Interval.Max, AtIndex + TupleIndex + 1); + } + else + { + HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InGenericAttribute.AttributeName, *PropertyClassName, *PropertyName.ToString()); + return false; + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + // OBJECT PATH PROPERTY + const int32 TupleIndex = 0; - if (PropertyValue) - { - PropertyValue->Min = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - PropertyValue->Max = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) + { + if (ArrayHelper->IsValidIndex(TupleIndex)) + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); + } + else if (TupleIndex < InnerProperty->ArrayDim) + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + } - OnPropertyChanged(StructProperty); - } - } + if (ValuePtr) + { + UObject* ValueObject = ObjectProperty->GetObjectPropertyValue(ValuePtr); + const TSoftObjectPtr ValueObjectPtr = ValueObject; + InGenericAttribute.SetStringValue(ValueObjectPtr.ToString(), AtIndex + TupleIndex); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; } - else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + } + else + { + // Property was found, but is of an unsupported type + HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InGenericAttribute.AttributeName); + return false; + } + + return true; +} + +bool +FHoudiniGenericAttribute::GetAttributeTupleSizeAndStorageFromProperty( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + int32& OutAttributeTupleSize, + EAttribStorageType& OutAttributeStorageType) +{ + if (!IsValid(InObject) || !InFoundProperty) + return false; + + // Determine the container to use (either InContainer if specified, or InObject) + void* Container = InContainer ? InContainer : InObject; + + // Property class name, used for logging + const FString PropertyClassName = InFoundProperty->GetClass() ? InFoundProperty->GetClass()->GetName() : TEXT("Unknown"); + + // Initialize using the found property + FProperty* InnerProperty = InFoundProperty; + + // FArrayProperty* ArrayProperty = CastField(InFoundProperty); + // TSharedPtr ArrayHelper; + // if (ArrayProperty) + // { + // InnerProperty = ArrayProperty->Inner; + // ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); + // } + + FFieldClass* PropertyClass = InnerProperty->GetClass(); + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || + PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + // Supported non-struct properties + + // Here we cannot really do better than tuple size of 1 (since we need to support arrays in the future, we + // cannot just assume array size == tuple size going to Houdini) + OutAttributeTupleSize = 1; + + // Handle each property type that we support + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) { - // OBJECT PATH PROPERTY - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) + // Numeric properties are supported as floats and ints, and can also be set from a received string + FNumericProperty* const Property = CastField(InnerProperty); + if (Property->IsFloatingPoint()) { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + OutAttributeStorageType = EAttribStorageType::FLOAT; } - else + else if (Property->IsInteger()) { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + OutAttributeStorageType = EAttribStorageType::INT; } - - if (ValuePtr) + else { - TSoftObjectPtr ValueObjectPtr; - ValueObjectPtr = Value; - UObject* ValueObject = ValueObjectPtr.LoadSynchronous(); - - // Ensure the ObjectProperty class matches the ValueObject that we just loaded - if (!ValueObject || (ValueObject && ValueObject->IsA(ObjectProperty->PropertyClass))) - { - ObjectProperty->SetObjectPropertyValue(ValuePtr, ValueObject); - OnPropertyChanged(ObjectProperty); - } + HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *Property->GetName(), *PropertyClassName); + return false; } } - else + else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) { - // Property was found, but is of an unsupported type - FString PropertyClass = FoundProperty->GetClass() ? FoundProperty->GetClass()->GetName() : TEXT("Unknown"); - HOUDINI_LOG_MESSAGE(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClass, *InGenericAttribute.AttributeName); - return false; + OutAttributeStorageType = EAttribStorageType::INT; + } + else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) + { + OutAttributeStorageType = EAttribStorageType::STRING; + } + else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + OutAttributeStorageType = EAttribStorageType::STRING; } } - - if (bHasModifiedProperty) + else if (FStructProperty* StructProperty = CastField(InnerProperty)) { -#if WITH_EDITOR - InObject->PostEditChange(); - if (InOwner) + // struct properties + + const FName PropertyName = StructProperty->Struct->GetFName(); + if (PropertyName == NAME_Vector) { - InOwner->PostEditChange(); + OutAttributeTupleSize = 3; + OutAttributeStorageType = EAttribStorageType::FLOAT; } -#endif + else if (PropertyName == NAME_Transform) + { + OutAttributeTupleSize = 10; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else if (PropertyName == NAME_Color) + { + OutAttributeTupleSize = 4; + OutAttributeStorageType = EAttribStorageType::INT; + } + else if (PropertyName == NAME_LinearColor) + { + OutAttributeTupleSize = 4; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else if (PropertyName == "Int32Interval") + { + OutAttributeTupleSize = 2; + OutAttributeStorageType = EAttribStorageType::INT; + } + else if (PropertyName == "FloatInterval") + { + OutAttributeTupleSize = 2; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else + { + HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InFoundProperty->GetName(), *PropertyClassName, *PropertyName.ToString()); + return false; + } + } + else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + OutAttributeTupleSize = 1; + OutAttributeStorageType = EAttribStorageType::STRING; + } + else + { + // Property was found, but is of an unsupported type + HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InFoundProperty->GetName()); + return false; } return true; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h index d59071484..52ea4322c 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -60,14 +60,14 @@ struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute FString AttributeName; UPROPERTY() - EAttribStorageType AttributeType; + EAttribStorageType AttributeType = EAttribStorageType::Invalid; UPROPERTY() - EAttribOwner AttributeOwner; + EAttribOwner AttributeOwner = EAttribOwner::Invalid; UPROPERTY() - int32 AttributeCount; + int32 AttributeCount = -1; UPROPERTY() - int32 AttributeTupleSize; + int32 AttributeTupleSize = -1; UPROPERTY() TArray DoubleValues; @@ -76,23 +76,39 @@ struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute UPROPERTY() TArray StringValues; - double GetDoubleValue(int32 index = 0); - void GetDoubleTuple(TArray& TupleValues, int32 index = 0); + // Accessors + + double GetDoubleValue(int32 index = 0) const; + void GetDoubleTuple(TArray& TupleValues, int32 index = 0) const; - int64 GetIntValue(int32 index = 0); - void GetIntTuple(TArray& TupleValues, int32 index = 0); + int64 GetIntValue(int32 index = 0) const; + void GetIntTuple(TArray& TupleValues, int32 index = 0) const; - FString GetStringValue(int32 index = 0); - void GetStringTuple(TArray& TupleValues, int32 index = 0); + FString GetStringValue(int32 index = 0) const; + void GetStringTuple(TArray& TupleValues, int32 index = 0) const; - bool GetBoolValue(int32 index = 0); - void GetBoolTuple(TArray& TupleValues, int32 index = 0); + bool GetBoolValue(int32 index = 0) const; + void GetBoolTuple(TArray& TupleValues, int32 index = 0) const; void* GetData(); + // Mutators + + void SetDoubleValue(double InValue, int32 index = 0); + void SetDoubleTuple(const TArray& InTupleValues, int32 index = 0); + + void SetIntValue(int64 InValue, int32 index = 0); + void SetIntTuple(const TArray& InTupleValues, int32 index = 0); + + void SetStringValue(const FString& InValue, int32 index = 0); + void SetStringTuple(const TArray& InTupleValues, int32 index = 0); + + void SetBoolValue(bool InValue, int32 index = 0); + void SetBoolTuple(const TArray& InTupleValues, int32 index = 0); + // static bool UpdatePropertyAttributeOnObject( - UObject* InObject, FHoudiniGenericAttribute InPropertyAttribute, const int32& AtIndex = 0); + UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex = 0); // Tries to find a Uproperty by name/label on an object // FoundPropertyObject will be the object that actually contains the property @@ -112,6 +128,23 @@ struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute void* InContainer, const int32& AtIndex = 0 ); + // Gets the value of a found Property and sets it in the appropriate + // array and index in InGenericAttribute. + static bool GetPropertyValueFromObject( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + FHoudiniGenericAttribute& InGenericAttribute, + const int32& InAtIndex = 0); + + // Helper: determines a valid tuple size and storage type for a Houdini attribute from an Unreal FProperty + static bool GetAttributeTupleSizeAndStorageFromProperty( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + int32& OutAttributeTupleSize, + EAttribStorageType& OutAttributeStorageType); + // Recursive search for a given property on a UObject static bool TryToFindProperty( void* InContainer, diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.cpp deleted file mode 100644 index c5fdea932..000000000 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp index a8ee8dc58..49171ceb6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h index 4209e8240..95fff3c08 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,6 +26,8 @@ #pragma once +#include "HoudiniEngineRuntimeCommon.h" + #include "HoudiniGeoPartObject.generated.h" UENUM() @@ -61,27 +63,6 @@ enum class EHoudiniInstancerType : uint8 OldSchoolAttributeInstancer }; -UENUM() -enum class EHoudiniCurveType : int8 -{ - Invalid = -1, - - Polygon = 0, - Nurbs = 1, - Bezier = 2, - Points = 3 -}; - -UENUM() -enum class EHoudiniCurveMethod : int8 -{ - Invalid = -1, - - CVs = 0, - Breakpoints = 1, - Freehand = 2 -}; - USTRUCT() struct HOUDINIENGINERUNTIME_API FHoudiniObjectInfo { @@ -361,6 +342,13 @@ struct HOUDINIENGINERUNTIME_API FHoudiniGeoPartObject UPROPERTY() FString VolumeName; + UPROPERTY() + bool bHasEditLayers; + + // Name of edit layer + UPROPERTY() + FString VolumeLayerName; + // UPROPERTY() int32 VolumeTileIndex; @@ -414,6 +402,10 @@ struct HOUDINIENGINERUNTIME_API FHoudiniGeoPartObject // CurveInfo cache FHoudiniCurveInfo CurveInfo; + // Stores the Mesh Sockets found for a given HGPO + UPROPERTY() + TArray AllMeshSockets; + // Cache of this HGPO split data //TArray SplitCache; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp index 2b81de958..6d76a2c12 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,6 +26,8 @@ #include "HoudiniHandleComponent.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + #include "HoudiniParameter.h" #include "HoudiniParameterFloat.h" #include "HoudiniParameterChoice.h" diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h index 5454b5e20..d422e95ea 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp index ce9ef72fb..78f34bf79 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,12 +43,13 @@ #include "Engine/StaticMesh.h" #include "Engine/SkeletalMesh.h" #include "UObject/UObjectGlobals.h" +#include "FoliageType_InstancedStaticMesh.h" #include "Components/SplineComponent.h" #include "Components/StaticMeshComponent.h" #include "Components/InstancedStaticMeshComponent.h" #include "Landscape.h" - +#include "LandscapeInfo.h" #if WITH_EDITOR #include "Kismet2/BlueprintEditorUtils.h" @@ -73,8 +74,8 @@ UHoudiniInput::UHoudiniInput() , bCookOnCurveChanged(true) , bStaticMeshChanged(false) , bInputAssetConnectedInHoudini(false) - , bSwitchedToCurve(false) , DefaultCurveOffset(0.f) + , bAddRotAndScaleAttributesOnCurves(false) , bIsWorldInputBoundSelector(false) , bWorldInputBoundSelectorAutoUpdate(false) , UnrealSplineResolution(50.0f) @@ -86,6 +87,7 @@ UHoudiniInput::UHoudiniInput() , bLandscapeExportLighting(false) , bLandscapeExportNormalizedUVs(false) , bLandscapeExportTileUVs(false) + , bCanDeleteHoudiniNodes(true) { Name = TEXT(""); Label = TEXT(""); @@ -98,6 +100,8 @@ UHoudiniInput::UHoudiniInput() const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); UnrealSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; + + bAddRotAndScaleAttributesOnCurves = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bAddRotAndScaleAttributesOnCurves : false; } void @@ -153,7 +157,7 @@ void UHoudiniInput::PostEditUndo() // The input array will be empty when undo adding asset (only support single asset input object in an input now) for (auto & NextAssetInputObj : *InputObjectsPtr) { - if (!NextAssetInputObj || NextAssetInputObj->IsPendingKill()) + if (!IsValid(NextAssetInputObj)) continue; NextAssetInputObj->MarkChanged(true); @@ -174,7 +178,7 @@ void UHoudiniInput::PostEditUndo() CreatedDataNodeIds.Empty(); if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + MarkInputNodeAsPendingDelete(); InputNodeId = -1; } } @@ -186,11 +190,11 @@ void UHoudiniInput::PostEditUndo() for (auto& NextInput : *GetHoudiniInputObjectArray(Type)) { UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(NextInput); - if (!SplineInput || SplineInput->IsPendingKill()) + if (!IsValid(SplineInput)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; USceneComponent* OuterComponent = Cast(GetOuter()); @@ -211,24 +215,24 @@ void UHoudiniInput::PostEditUndo() TArray< USceneComponent* > childActor; UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !OuterHAC->IsPendingKill()) + if (IsValid(OuterHAC)) childActor = OuterHAC->GetAttachChildren(); // Undo delete input objects action for (int Index = 0; Index < GetNumberOfInputObjects(); ++Index) { UHoudiniInputObject* InputObject = (*InputObjectsPtr)[Index]; - if (!InputObject || InputObject->IsPendingKill()) + if (!IsValid(InputObject)) continue; UHoudiniInputHoudiniSplineComponent * HoudiniSplineInputObject = Cast(InputObject); - if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) + if (!IsValid(HoudiniSplineInputObject)) continue; UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (!SplineComponent || SplineComponent->IsPendingKill()) + if (!IsValid(SplineComponent)) continue; // If the last change deleted this curve input, recreate this Houdini Spline input. @@ -244,7 +248,7 @@ void UHoudiniInput::PostEditUndo() UHoudiniSplineComponent * ReconstructedSpline = NewObject( GetOuter(), UHoudiniSplineComponent::StaticClass()); - if (!ReconstructedSpline || ReconstructedSpline->IsPendingKill()) + if (!IsValid(ReconstructedSpline)) continue; ReconstructedSpline->SetFlags(RF_Transactional); @@ -276,11 +280,11 @@ void UHoudiniInput::PostEditUndo() { bUndoInsert = true; UHoudiniInputHoudiniSplineComponent* SplineInputComponent = LastInsertedInputs[Index]; - if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) + if (!IsValid(SplineInputComponent)) continue; UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; HoudiniSplineComponent->DestroyComponent(); @@ -294,11 +298,11 @@ void UHoudiniInput::PostEditUndo() UHoudiniInputObject* NextInputObject = LastUndoDeletedInputs[Index]; UHoudiniInputHoudiniSplineComponent* SplineInputComponent = Cast(NextInputObject); - if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) + if (!IsValid(SplineInputComponent)) continue; UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); - if (!HoudiniSplineComponent || SplineInputComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); @@ -331,11 +335,11 @@ UHoudiniInput::GetBounds() const for (int32 Idx = 0; Idx < CurveInputObjects.Num(); ++Idx) { const UHoudiniInputHoudiniSplineComponent* CurInCurve = Cast(CurveInputObjects[Idx]); - if (!CurInCurve || CurInCurve->IsPendingKill()) + if (!IsValid(CurInCurve)) continue; UHoudiniSplineComponent* CurCurve = CurInCurve->GetCurveComponent(); - if (!CurCurve || CurCurve->IsPendingKill()) + if (!IsValid(CurCurve)) continue; FBox CurCurveBound(ForceInitToZero); @@ -346,7 +350,7 @@ UHoudiniInput::GetBounds() const UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !OuterHAC->IsPendingKill()) + if (IsValid(OuterHAC)) BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); } } @@ -357,11 +361,11 @@ UHoudiniInput::GetBounds() const for (int32 Idx = 0; Idx < AssetInputObjects.Num(); ++Idx) { UHoudiniInputHoudiniAsset* CurInAsset = Cast(AssetInputObjects[Idx]); - if (!CurInAsset || CurInAsset->IsPendingKill()) + if (!IsValid(CurInAsset)) continue; UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); - if (!CurInHAC || CurInHAC->IsPendingKill()) + if (!IsValid(CurInHAC)) continue; BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); @@ -374,10 +378,10 @@ UHoudiniInput::GetBounds() const for (int32 Idx = 0; Idx < WorldInputObjects.Num(); ++Idx) { UHoudiniInputActor* CurInActor = Cast(WorldInputObjects[Idx]); - if (CurInActor && !CurInActor->IsPendingKill()) + if (IsValid(CurInActor)) { AActor* Actor = CurInActor->GetActor(); - if (!Actor || Actor->IsPendingKill()) + if (!IsValid(Actor)) continue; FVector Origin, Extent; @@ -389,10 +393,10 @@ UHoudiniInput::GetBounds() const { // World Input now also support HoudiniAssets UHoudiniInputHoudiniAsset* CurInAsset = Cast(WorldInputObjects[Idx]); - if (CurInAsset && !CurInAsset->IsPendingKill()) + if (IsValid(CurInAsset)) { UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); - if (!CurInHAC || CurInHAC->IsPendingKill()) + if (!IsValid(CurInHAC)) continue; BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); @@ -408,11 +412,11 @@ UHoudiniInput::GetBounds() const for (int32 Idx = 0; Idx < LandscapeInputObjects.Num(); ++Idx) { UHoudiniInputLandscape* CurInLandscape = Cast(LandscapeInputObjects[Idx]); - if (!CurInLandscape || CurInLandscape->IsPendingKill()) + if (!IsValid(CurInLandscape)) continue; ALandscapeProxy* CurLandscape = CurInLandscape->GetLandscapeProxy(); - if (!CurLandscape || CurLandscape->IsPendingKill()) + if (!IsValid(CurLandscape)) continue; FVector Origin, Extent; @@ -432,6 +436,91 @@ UHoudiniInput::GetBounds() const return BoxBounds; } +void UHoudiniInput::UpdateLandscapeInputSelection() +{ + LandscapeSelectedComponents.Reset(); + if (!bLandscapeExportSelectionOnly) return; + +#if WITH_EDITOR + for (UHoudiniInputObject* NextInputObj : LandscapeInputObjects) + { + UHoudiniInputLandscape* CurrentInputLandscape = Cast(NextInputObj); + if (!CurrentInputLandscape) + continue; + + ALandscapeProxy* CurrentInputLandscapeProxy = CurrentInputLandscape->GetLandscapeProxy(); + if (!CurrentInputLandscapeProxy) + continue; + + // Get selected components if bLandscapeExportSelectionOnly or bLandscapeAutoSelectComponent is true + FBox Bounds(ForceInitToZero); + if ( bLandscapeAutoSelectComponent ) + { + // Get our asset's or our connected input asset's bounds + UHoudiniAssetComponent* AssetComponent = Cast(GetOuter()); + if (IsValid(AssetComponent)) + { + Bounds = AssetComponent->GetAssetBounds(this, true); + } + } + + if ( bLandscapeExportSelectionOnly ) + { + const ULandscapeInfo * LandscapeInfo = CurrentInputLandscapeProxy->GetLandscapeInfo(); + if ( IsValid(LandscapeInfo) ) + { + // Get the currently selected components + LandscapeSelectedComponents = LandscapeInfo->GetSelectedComponents(); + } + + if ( bLandscapeAutoSelectComponent && LandscapeSelectedComponents.Num() <= 0 && Bounds.IsValid ) + { + // We'll try to use the asset bounds to automatically "select" the components + for ( int32 ComponentIdx = 0; ComponentIdx < CurrentInputLandscapeProxy->LandscapeComponents.Num(); ComponentIdx++ ) + { + ULandscapeComponent * LandscapeComponent = CurrentInputLandscapeProxy->LandscapeComponents[ ComponentIdx ]; + if ( !IsValid(LandscapeComponent) ) + continue; + + FBoxSphereBounds WorldBounds = LandscapeComponent->CalcBounds( LandscapeComponent->GetComponentTransform()); + + if ( Bounds.IntersectXY( WorldBounds.GetBox() ) ) + LandscapeSelectedComponents.Add( LandscapeComponent ); + } + + int32 Num = LandscapeSelectedComponents.Num(); + HOUDINI_LOG_MESSAGE( TEXT("Landscape input: automatically selected %d components within the asset's bounds."), Num ); + } + } + else + { + // Add all the components of the landscape to the selected set + ULandscapeInfo* LandscapeInfo = CurrentInputLandscapeProxy->GetLandscapeInfo(); + if (LandscapeInfo) + { + LandscapeInfo->ForAllLandscapeComponents([&](ULandscapeComponent* Component) + { + LandscapeSelectedComponents.Add(Component); + }); + } + } + + CurrentInputLandscape->MarkChanged(true); + + } +#endif +} + +void +UHoudiniInput::MarkInputNodeAsPendingDelete() +{ + if (InputNodeId < 0) + return; + + InputNodesPendingDelete.Add(InputNodeId); + InputNodeId = -1; +} + FString UHoudiniInput::InputTypeToString(const EHoudiniInputType& InInputType) { @@ -676,13 +765,13 @@ UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlue { UHoudiniInputHoudiniSplineComponent * CurrentInputHoudiniSpline = Cast(CurrentInput); - if (!CurrentInputHoudiniSpline || CurrentInputHoudiniSpline->IsPendingKill()) + if (!IsValid(CurrentInputHoudiniSpline)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = CurrentInputHoudiniSpline->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; HoudiniSplineComponent->Modify(); @@ -740,12 +829,12 @@ UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlue { UHoudiniInputObject* InputObj = (*InputObjectsArray)[Idx]; - if (!InputObj || InputObj->IsPendingKill()) + if (!IsValid(InputObj)) continue; UHoudiniInputLandscape* InputLandscape = Cast(InputObj); - if (!InputLandscape || InputLandscape->IsPendingKill()) + if (!IsValid(InputLandscape)) continue; // do something? @@ -787,11 +876,11 @@ UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlue for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) { UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(CurrentInput); - if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) + if (!IsValid(HoudiniAssetInput)) continue; UHoudiniAssetComponent* CurrentHAC = HoudiniAssetInput->GetHoudiniAssetComponent(); - if (!CurrentHAC || CurrentHAC->IsPendingKill()) + if (!IsValid(CurrentHAC)) continue; CurrentHAC->AddDownstreamHoudiniAsset(OuterHAC); @@ -886,11 +975,11 @@ UHoudiniInput::CreateNewCurveInputObject(bool& bOutBlueprintStructureModified) return nullptr; UHoudiniInputHoudiniSplineComponent* NewCurveInputObject = CreateHoudiniSplineInput(nullptr, true, false, bOutBlueprintStructureModified); - if (!NewCurveInputObject || NewCurveInputObject->IsPendingKill()) + if (!IsValid(NewCurveInputObject)) return nullptr; UHoudiniSplineComponent * HoudiniSplineComponent = NewCurveInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return nullptr; // Default Houdini spline component input should not be visible at initialization @@ -921,7 +1010,7 @@ UHoudiniInput::MarkAllInputObjectsChanged(const bool& bInChanged) { for (auto CurInputObject : *NewInputObjects) { - if (CurInputObject && !CurInputObject->IsPendingKill()) + if (IsValid(CurInputObject)) CurInputObject->MarkChanged(bInChanged); } } @@ -1053,7 +1142,7 @@ void UHoudiniInput::InvalidateData() if (Type != EHoudiniInputType::Asset) { if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + MarkInputNodeAsPendingDelete(); } InputNodeId = -1; @@ -1201,7 +1290,7 @@ UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * Fr UHoudiniInputObject * NewInputObject = UHoudiniInputHoudiniSplineComponent::Create( nullptr, OuterObj, HoudiniSplineName.ToString()); - if (!NewInputObject || NewInputObject->IsPendingKill()) + if (!IsValid(NewInputObject)) return nullptr; HoudiniSplineInput = Cast(NewInputObject); @@ -1211,7 +1300,7 @@ UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * Fr HoudiniSplineComponent = NewObject( HoudiniSplineInput, UHoudiniSplineComponent::StaticClass()); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return nullptr; HoudiniSplineInput->Update(HoudiniSplineComponent); @@ -1254,10 +1343,11 @@ UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * Fr // NOTE: FAddComponentsToBlueprintParams was introduced in 4.26 so for the sake of // backwards compatibility, manually determine which SCSNode was added instead of // relying on Params.OutNodes. - //FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; - //Params.OptionalNewRootNode = HABNode; + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.OptionalNewRootNode = HABNode; const TSet PreviousSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components, FKismetEditorUtilities::EAddComponentToBPHarvestMode::None, HABNode, false); + + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components, Params); USCS_Node* NewNode = nullptr; const TSet CurrentSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); const TSet AddedNodes = CurrentSCSNodes.Difference(PreviousSCSNodes); @@ -1283,7 +1373,7 @@ UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * Fr HoudiniSplineInput = FromHoudiniSplineInputComponent; HoudiniSplineComponent = FromHoudiniSplineInputComponent->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return nullptr; // Attach the new Houdini spline component to it's owner. @@ -1444,6 +1534,12 @@ UHoudiniInput::GetBoundSelectorObjectArray() return &WorldInputBoundSelectorObjects; } +const TArray* +UHoudiniInput::GetBoundSelectorObjectArray() const +{ + return &WorldInputBoundSelectorObjects; +} + const TArray* UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const { @@ -1520,7 +1616,7 @@ UObject* UHoudiniInput::GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) { UHoudiniInputObject* HoudiniInputObject = GetHoudiniInputObjectAt(InType, AtIndex); - if (!HoudiniInputObject || HoudiniInputObject->IsPendingKill()) + if (!IsValid(HoudiniInputObject)) return nullptr; return HoudiniInputObject->GetObject(); @@ -1544,13 +1640,13 @@ UHoudiniInput::InsertInputObjectAt(const EHoudiniInputType& InType, const int32& } void -UHoudiniInput::DeleteInputObjectAt(const int32& AtIndex) +UHoudiniInput::DeleteInputObjectAt(const int32& AtIndex, const bool bInRemoveIndexFromArray) { - DeleteInputObjectAt(Type, AtIndex); + DeleteInputObjectAt(Type, AtIndex, bInRemoveIndexFromArray); } void -UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, const bool bInRemoveIndexFromArray) { TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); if (!InputObjectsPtr) @@ -1597,7 +1693,7 @@ UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& MarkChanged(true); UHoudiniInputObject* InputObjectToDelete = (*InputObjectsPtr)[AtIndex]; - if (InputObjectToDelete && !InputObjectToDelete->IsPendingKill()) + if (IsValid(InputObjectToDelete)) { // Mark the input object's nodes for deletion InputObjectToDelete->InvalidateData(); @@ -1606,14 +1702,21 @@ UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& MarkDataUploadNeeded(true); } - InputObjectsPtr->RemoveAt(AtIndex); + if (bInRemoveIndexFromArray) + { + InputObjectsPtr->RemoveAt(AtIndex); - // Delete the merge node when all the input objects are deleted. - if (InputObjectsPtr->Num() == 0 && InputNodeId >= 0) + // Delete the merge node when all the input objects are deleted. + if (InputObjectsPtr->Num() == 0 && InputNodeId >= 0) + { + if (bCanDeleteHoudiniNodes) + MarkInputNodeAsPendingDelete(); + InputNodeId = -1; + } + } + else { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); - InputNodeId = -1; + (*InputObjectsPtr)[AtIndex] = nullptr; } #if WITH_EDITOR @@ -1693,11 +1796,11 @@ UHoudiniInput::GetNumberOfInputMeshes(const EHoudiniInputType& InType) // Same thing for Actor InputObjects! for (auto InputObj : *InputObjectsPtr) { - if (!InputObj || InputObj->IsPendingKill()) + if (!IsValid(InputObj)) continue; UHoudiniInputStaticMesh* InputSM = Cast(InputObj); - if (InputSM && !InputSM->IsPendingKill()) + if (IsValid(InputSM)) { if (InputSM->BlueprintStaticMeshes.Num() > 0) { @@ -1706,11 +1809,11 @@ UHoudiniInput::GetNumberOfInputMeshes(const EHoudiniInputType& InType) } UHoudiniInputActor* InputActor = Cast(InputObj); - if (InputActor && !InputActor->IsPendingKill()) + if (IsValid(InputActor)) { - if (InputActor->ActorComponents.Num() > 0) + if (InputActor->GetActorComponents().Num() > 0) { - Num += (InputActor->ActorComponents.Num() - 1); + Num += (InputActor->GetActorComponents().Num() - 1); } } } @@ -1793,7 +1896,7 @@ UHoudiniInput::SetInputObjectAt(const EHoudiniInputType& InType, const int32& At // Mark that input object as changed so we know we need to update it NewInputObject->MarkChanged(true); - if (CurrentInputObjectWrapper && !CurrentInputObjectWrapper->IsPendingKill()) + if (IsValid(CurrentInputObjectWrapper)) { // TODO: // For some input type, we may have to copy some of the previous object's property before deleting it @@ -1833,7 +1936,7 @@ UHoudiniInput::SetInputObjectsNumber(const EHoudiniInputType& InType, const int3 for (int32 InObjIdx = InputObjectsPtr->Num() - 1; InObjIdx >= InNewCount; InObjIdx--) { UHoudiniInputObject* CurrentInputObject = (*InputObjectsPtr)[InObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + if (!IsValid(CurrentInputObject)) continue; if (bCanDeleteHoudiniNodes) @@ -1854,7 +1957,7 @@ UHoudiniInput::SetInputObjectsNumber(const EHoudiniInputType& InType, const int3 if (InNewCount == 0 && InputNodeId >= 0) { if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + MarkInputNodeAsPendingDelete(); InputNodeId = -1; } } @@ -1928,6 +2031,7 @@ UHoudiniInput::GetAllowedClasses(const EHoudiniInputType& InInputType) AllowedClasses.Add(USkeletalMesh::StaticClass()); AllowedClasses.Add(UBlueprint::StaticClass()); AllowedClasses.Add(UDataTable::StaticClass()); + AllowedClasses.Add(UFoliageType_InstancedStaticMesh::StaticClass()); break; case EHoudiniInputType::Curve: @@ -2217,6 +2321,18 @@ UHoudiniInput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, co return true; } +void +UHoudiniInput::SetAddRotAndScaleAttributes(const bool& InValue) +{ + if (bAddRotAndScaleAttributesOnCurves == InValue) + return; + + bAddRotAndScaleAttributesOnCurves = InValue; + + // Mark all input obj as changed + MarkAllInputObjectsChanged(true); +} + #if WITH_EDITOR FText UHoudiniInput::GetCurrentSelectionText() const @@ -2231,11 +2347,11 @@ UHoudiniInput::GetCurrentSelectionText() const UHoudiniInputObject* InputObject = LandscapeInputObjects[0]; UHoudiniInputLandscape* InputLandscape = Cast(InputObject); - if (!InputLandscape || InputLandscape->IsPendingKill()) + if (!IsValid(InputLandscape)) return CurrentSelectionText; ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); - if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) + if (!IsValid(LandscapeProxy)) return CurrentSelectionText; CurrentSelectionText = FText::FromString(LandscapeProxy->GetActorLabel()); @@ -2250,15 +2366,15 @@ UHoudiniInput::GetCurrentSelectionText() const UHoudiniInputObject* InputObject = AssetInputObjects[0]; UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(InputObject); - if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) + if (!IsValid(HoudiniAssetInput)) return CurrentSelectionText; UHoudiniAssetComponent* HAC = HoudiniAssetInput->GetHoudiniAssetComponent(); - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return CurrentSelectionText; UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + if (!IsValid(HoudiniAsset)) return CurrentSelectionText; CurrentSelectionText = FText::FromString(HoudiniAsset->GetName()); @@ -2322,7 +2438,7 @@ UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() TArray AllBBox; for (auto CurrentActor : WorldInputBoundSelectorObjects) { - if (!CurrentActor || CurrentActor->IsPendingKill()) + if (!IsValid(CurrentActor)) continue; AllBBox.Add(CurrentActor->GetComponentsBoundingBox(true, true)); @@ -2342,7 +2458,7 @@ UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) { AActor *CurrentActor = *ActorItr; - if (!CurrentActor || CurrentActor->IsPendingKill()) + if (!IsValid(CurrentActor)) continue; // Check that actor is currently not selected @@ -2362,7 +2478,7 @@ UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() ABrush* BrushActor = Cast(CurrentActor); if (BrushActor) { - if (!BrushActor->Brush || BrushActor->Brush->IsPendingKill()) + if (!IsValid(BrushActor->Brush)) continue; } @@ -2429,7 +2545,7 @@ UHoudiniInput::UpdateWorldSelection(const TArray& InNewSelection) bool UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; // Returns true if the object is one of our input object for the given type @@ -2439,7 +2555,7 @@ UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputT for (auto& CurrentInputObject : (*ObjectArray)) { - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + if (!IsValid(CurrentInputObject)) continue; if (CurrentInputObject->GetObject() == InObject) @@ -2449,36 +2565,59 @@ UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputT return false; } -void UHoudiniInput::ForAllHoudiniInputObjects(TFunctionRef Fn) const +void UHoudiniInput::ForAllHoudiniInputObjects(TFunctionRef Fn, const bool bFilterByInputType) const { - for(UHoudiniInputObject* InputObject : GeometryInputObjects) + auto ShouldIncludeFn = [&](const EHoudiniInputType InInputType) -> bool + { + return !bFilterByInputType || (bFilterByInputType && GetInputType() == InInputType); + }; + + if (ShouldIncludeFn(EHoudiniInputType::Geometry)) { - Fn(InputObject); + for(UHoudiniInputObject* InputObject : GeometryInputObjects) + { + Fn(InputObject); + } } - for(UHoudiniInputObject* InputObject : AssetInputObjects) + if (ShouldIncludeFn(EHoudiniInputType::Asset)) { - Fn(InputObject); + for(UHoudiniInputObject* InputObject : AssetInputObjects) + { + Fn(InputObject); + } } - for(UHoudiniInputObject* InputObject : CurveInputObjects) + if (ShouldIncludeFn(EHoudiniInputType::Curve)) { - Fn(InputObject); + for(UHoudiniInputObject* InputObject : CurveInputObjects) + { + Fn(InputObject); + } } - for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + if (ShouldIncludeFn(EHoudiniInputType::Landscape)) { - Fn(InputObject); + for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + { + Fn(InputObject); + } } - - for(UHoudiniInputObject* InputObject : WorldInputObjects) + + if (ShouldIncludeFn(EHoudiniInputType::World)) { - Fn(InputObject); + for(UHoudiniInputObject* InputObject : WorldInputObjects) + { + Fn(InputObject); + } } - for(UHoudiniInputObject* InputObject : SkeletalInputObjects) + if (ShouldIncludeFn(EHoudiniInputType::Skeletal)) { - Fn(InputObject); + for(UHoudiniInputObject* InputObject : SkeletalInputObjects) + { + Fn(InputObject); + } } } diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.h b/Source/HoudiniEngineRuntime/Private/HoudiniInput.h index f51b86438..cc401eec8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -131,6 +131,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject FString GetHelp() const { return Help; }; bool GetPackBeforeMerge() const { return bPackBeforeMerge; }; bool GetImportAsReference() const { return bImportAsReference; }; + bool GetImportAsReferenceRotScaleEnabled() const { return bImportAsReferenceRotScaleEnabled; }; bool GetExportLODs() const { return bExportLODs; }; bool GetExportSockets() const { return bExportSockets; }; bool GetExportColliders() const { return bExportColliders; }; @@ -142,6 +143,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType); const TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const; TArray* GetBoundSelectorObjectArray(); + const TArray* GetBoundSelectorObjectArray() const; UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex); const UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex) const; @@ -197,7 +199,9 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const; void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn); - void ForAllHoudiniInputObjects(TFunctionRef Fn) const; + // Return ALL input objects. Optionally, the results can be filtered to only return input objects + // relevant to the current *input type*. + void ForAllHoudiniInputObjects(TFunctionRef Fn, const bool bFilterByInputType=false) const; // Collect top-level HoudiniInputObjects from this UHoudiniInput. Does not traverse nested input objects. void GetAllHoudiniInputObjects(TArray& OutObjects) const; // Collect top-level UHoudiniInputSceneComponent from this UHoudiniInput. Does not traverse nested input objects. @@ -205,10 +209,13 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject void GetAllHoudiniInputSceneComponents(TArray& OutObjects) const; void GetAllHoudiniInputSplineComponents(TArray& OutObjects) const; - // Remove all instances of this input object from all object arrays. void RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject); + bool IsAddRotAndScaleAttributesEnabled() const { return bAddRotAndScaleAttributesOnCurves; }; + + const TSet< ULandscapeComponent * > GetLandscapeSelectedComponents() const { return LandscapeSelectedComponents; }; + //------------------------------------------------------------------------------------------------ // Mutators //------------------------------------------------------------------------------------------------ @@ -234,6 +241,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject void SetPreviousInputType(const EHoudiniInputType& InType) { PreviousType = InType; }; void SetPackBeforeMerge(const bool& bInPackBeforeMerge) { bPackBeforeMerge = bInPackBeforeMerge; }; void SetImportAsReference(const bool& bInImportAsReference) { bImportAsReference = bInImportAsReference; }; + void SetImportAsReferenceRotScaleEnabled(const bool& bInImportAsReferenceRotScaleEnabled) { bImportAsReferenceRotScaleEnabled = bInImportAsReferenceRotScaleEnabled; }; void SetExportLODs(const bool& bInExportLODs) { bExportLODs = bInExportLODs; }; void SetExportSockets(const bool& bInExportSockets) { bExportSockets = bInExportSockets; }; void SetExportColliders(const bool& bInExportColliders) { bExportColliders = bInExportColliders; }; @@ -252,8 +260,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject void InsertInputObjectAt(const int32& AtIndex); void InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - void DeleteInputObjectAt(const int32& AtIndex); - void DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + void DeleteInputObjectAt(const int32& AtIndex, const bool bInRemoveIndexFromArray=true); + void DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, const bool bInRemoveIndexFromArray=true); void DuplicateInputObjectAt(const int32& AtIndex); void DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); @@ -293,6 +301,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject void SetScaleOffsetY(float InValue, int32 AtIndex); void SetScaleOffsetZ(float InValue, int32 AtIndex); + void SetAddRotAndScaleAttributes(const bool& InValue); // Duplicate this object and copy its state to the resulting object. // This is typically used to transfer state between between template and instance components. @@ -343,6 +352,17 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject FBox GetBounds() const; + void UpdateLandscapeInputSelection(); + + // Add the current InputNodeId to the pending delete set and set it to -1 + void MarkInputNodeAsPendingDelete(); + + // Return the set of previous InputNodeIds that are pending delete + const TSet& GetInputNodesPendingDelete() const { return InputNodesPendingDelete; } + + // Clear the InputNodesPendingDelete set + void ClearInputNodesPendingDelete() { InputNodesPendingDelete.Empty(); } + protected: // Name of the input / Object path parameter @@ -419,6 +439,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject UPROPERTY() bool bImportAsReference = false; + // Indicates that whether or not to add the rot / scale attributes for reference improts + UPROPERTY() + bool bImportAsReferenceRotScaleEnabled = false; + // Indicates that all LODs in the input should be marshalled to Houdini UPROPERTY() bool bExportLODs; @@ -439,7 +463,6 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject // Geometry objects UPROPERTY() TArray GeometryInputObjects; - // ?? TArray GeometryInputObjects; // Is set to true when static mesh used for geometry input has changed. UPROPERTY() @@ -456,7 +479,6 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject // Asset inputs UPROPERTY() TArray AssetInputObjects; - // ?? TArray AssetInputObjects; // Is set to true if the asset input is actually connected inside Houdini. UPROPERTY() @@ -466,20 +488,19 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject // Curve/Spline inputs UPROPERTY() TArray CurveInputObjects; - // ?? TArray CurveInputObjects; - // Is set to true when choice switches to curve mode. + // Offset used when using muiltiple curves UPROPERTY() - bool bSwitchedToCurve; + float DefaultCurveOffset; + // Set this to true to add rot and scale attributes on curve inputs. UPROPERTY() - float DefaultCurveOffset; + bool bAddRotAndScaleAttributesOnCurves; //------------------------------------------------------------------------------------------------------------------------- // Landscape inputs UPROPERTY() TArray LandscapeInputObjects; - // ?? TArray LandscapeInputObjects; UPROPERTY() bool bLandscapeHasExportTypeChanged = false; @@ -488,10 +509,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject // World inputs UPROPERTY() TArray WorldInputObjects; - // ?? TArray WorldInputObjects; // Objects used for automatic bound selection - // ?? TArray WorldInputBoundSelectorObjects; UPROPERTY() TArray WorldInputBoundSelectorObjects; @@ -511,7 +530,14 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject // Skeletal Inputs UPROPERTY() TArray SkeletalInputObjects; - // ?? TArray SkeletalInputObjects; + + // A cache of the selected landscape components so that it is saved across levels + UPROPERTY() + TSet< ULandscapeComponent * > LandscapeSelectedComponents; + + // The node ids of InputNodeIds previously used by this input that are pending delete + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + TSet InputNodesPendingDelete; public: diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp index 191782bbc..bbf40ad44 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp @@ -1,1559 +1,1936 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInputObject.h" - -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniInput.h" - -#include "Engine/StaticMesh.h" -#include "Engine/SkeletalMesh.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/SplineComponent.h" -#include "Landscape.h" -#include "Engine/Brush.h" -#include "Engine/Engine.h" -#include "GameFramework/Volume.h" -#include "Camera/CameraComponent.h" - -#include "Model.h" -#include "Engine/Brush.h" - -#include "HoudiniEngineRuntimeUtils.h" -#include "Kismet/KismetSystemLibrary.h" - -//----------------------------------------------------------------------------------------------------------------------------- -// Constructors -//----------------------------------------------------------------------------------------------------------------------------- - -// -UHoudiniInputObject::UHoudiniInputObject(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , Transform(FTransform::Identity) - , Type(EHoudiniInputObjectType::Invalid) - , InputNodeId(-1) - , InputObjectNodeId(-1) - , bHasChanged(false) - , bNeedsToTriggerUpdate(false) - , bTransformChanged(false) - , bImportAsReference(false) - , bCanDeleteHoudiniNodes(true) -{ - -} - -// -UHoudiniInputStaticMesh::UHoudiniInputStaticMesh(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputSkeletalMesh::UHoudiniInputSkeletalMesh(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputSceneComponent::UHoudiniInputSceneComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputMeshComponent::UHoudiniInputMeshComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputInstancedMeshComponent::UHoudiniInputInstancedMeshComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputSplineComponent::UHoudiniInputSplineComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , NumberOfSplineControlPoints(-1) - , SplineLength(-1.0f) - , SplineResolution(-1.0f) - , SplineClosed(false) -{ - -} - -// -UHoudiniInputCameraComponent::UHoudiniInputCameraComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , FOV(0.0f) - , AspectRatio(1.0f) - , bIsOrthographic(false) - , OrthoWidth(2.0f) - , OrthoNearClipPlane(0.0f) - , OrthoFarClipPlane(-1.0f) -{ - -} - -// Returns true if the attached actor's (parent) transform has been modified -bool -UHoudiniInputSplineComponent::HasActorTransformChanged() const -{ - return false; -} - -// Returns true if the attached component's transform has been modified -bool -UHoudiniInputSplineComponent::HasComponentTransformChanged() const -{ - return false; -} - -// Return true if the component itself has been modified -bool -UHoudiniInputSplineComponent::HasComponentChanged() const -{ - USplineComponent* SplineComponent = Cast(InputObject.LoadSynchronous()); - - if (!SplineComponent) - return false; - - if (SplineClosed != SplineComponent->IsClosedLoop()) - return true; - - - if (SplineComponent->GetNumberOfSplinePoints() != NumberOfSplineControlPoints) - return true; - - for (int32 n = 0; n < SplineComponent->GetNumberOfSplinePoints(); ++n) - { - const FTransform &CurSplineComponentTransform = SplineComponent->GetTransformAtSplinePoint(n, ESplineCoordinateSpace::Local); - const FTransform &CurInputTransform = SplineControlPoints[n]; - - if (CurInputTransform.GetLocation() != CurSplineComponentTransform.GetLocation()) - return true; - - if (CurInputTransform.GetRotation().Rotator() != CurSplineComponentTransform.GetRotation().Rotator()) - return true; - - if (CurInputTransform.GetScale3D() != CurSplineComponentTransform.GetScale3D()) - return true; - } - - return false; -} - -bool -UHoudiniInputSplineComponent::HasSplineComponentChanged(float fCurrentSplineResolution) const -{ - return false; -} - -// -UHoudiniInputHoudiniSplineComponent::UHoudiniInputHoudiniSplineComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , CurveType(EHoudiniCurveType::Polygon) - , CurveMethod(EHoudiniCurveMethod::CVs) - , Reversed(false) -{ - -} - -// -UHoudiniInputHoudiniAsset::UHoudiniInputHoudiniAsset(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , AssetOutputIndex(-1) -{ - -} - -// -UHoudiniInputActor::UHoudiniInputActor(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputLandscape::UHoudiniInputLandscape(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputBrush::UHoudiniInputBrush() - : CombinedModel(nullptr) - , bIgnoreInputObject(false) -{ - -} - -//----------------------------------------------------------------------------------------------------------------------------- -// Accessors -//----------------------------------------------------------------------------------------------------------------------------- - -UObject* -UHoudiniInputObject::GetObject() -{ - return InputObject.LoadSynchronous(); -} - -UStaticMesh* -UHoudiniInputStaticMesh::GetStaticMesh() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UBlueprint* -UHoudiniInputStaticMesh::GetBlueprint() -{ - return Cast(InputObject.LoadSynchronous()); -} - -bool UHoudiniInputStaticMesh::bIsBlueprint() const -{ - return (InputObject.IsValid() && InputObject.Get()->IsA()); -} - -USkeletalMesh* -UHoudiniInputSkeletalMesh::GetSkeletalMesh() -{ - return Cast(InputObject.LoadSynchronous()); -} - -USceneComponent* -UHoudiniInputSceneComponent::GetSceneComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UStaticMeshComponent* -UHoudiniInputMeshComponent::GetStaticMeshComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UStaticMesh* -UHoudiniInputMeshComponent::GetStaticMesh() -{ - return StaticMesh.Get(); -} - -UInstancedStaticMeshComponent* -UHoudiniInputInstancedMeshComponent::GetInstancedStaticMeshComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -USplineComponent* -UHoudiniInputSplineComponent::GetSplineComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UHoudiniSplineComponent* -UHoudiniInputHoudiniSplineComponent::GetCurveComponent() -{ - return MyHoudiniSplineComponent; -} - -UCameraComponent* -UHoudiniInputCameraComponent::GetCameraComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UHoudiniAssetComponent* -UHoudiniInputHoudiniAsset::GetHoudiniAssetComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -AActor* -UHoudiniInputActor::GetActor() -{ - return Cast(InputObject.LoadSynchronous()); -} - -ALandscapeProxy* -UHoudiniInputLandscape::GetLandscapeProxy() -{ - return Cast(InputObject.LoadSynchronous()); -} - -void -UHoudiniInputLandscape::SetLandscapeProxy(UObject* InLandscapeProxy) -{ - UObject* LandscapeProxy = Cast(InLandscapeProxy); - if (LandscapeProxy) - InputObject = LandscapeProxy; -} - -ABrush* -UHoudiniInputBrush::GetBrush() const -{ - return Cast(InputObject.LoadSynchronous()); -} - - -//----------------------------------------------------------------------------------------------------------------------------- -// CREATE METHODS -//----------------------------------------------------------------------------------------------------------------------------- - -UHoudiniInputObject * -UHoudiniInputObject::CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InName) -{ - if (!InObject) - return nullptr; - - UHoudiniInputObject* HoudiniInputObject = nullptr; - - EHoudiniInputObjectType InputObjectType = GetInputObjectTypeFromObject(InObject); - switch (InputObjectType) - { - case EHoudiniInputObjectType::Object: - HoudiniInputObject = UHoudiniInputObject::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::StaticMesh: - HoudiniInputObject = UHoudiniInputStaticMesh::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::SkeletalMesh: - HoudiniInputObject = UHoudiniInputSkeletalMesh::Create(InObject, InOuter, InName); - break; - case EHoudiniInputObjectType::SceneComponent: - // Do not create input objects for unknown scene component! - //HoudiniInputObject = UHoudiniInputSceneComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::StaticMeshComponent: - HoudiniInputObject = UHoudiniInputMeshComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::InstancedStaticMeshComponent: - HoudiniInputObject = UHoudiniInputInstancedMeshComponent::Create(InObject, InOuter, InName); - break; - case EHoudiniInputObjectType::SplineComponent: - HoudiniInputObject = UHoudiniInputSplineComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::HoudiniSplineComponent: - HoudiniInputObject = UHoudiniInputHoudiniSplineComponent::Create(InObject, InOuter, InName); - break; - case EHoudiniInputObjectType::HoudiniAssetComponent: - HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(InObject, InOuter, InName); - break; - case EHoudiniInputObjectType::Actor: - HoudiniInputObject = UHoudiniInputActor::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::Landscape: - HoudiniInputObject = UHoudiniInputLandscape::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::Brush: - HoudiniInputObject = UHoudiniInputBrush::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::CameraComponent: - HoudiniInputObject = UHoudiniInputCameraComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::Invalid: - default: - break; - } - - return HoudiniInputObject; -} - - -UHoudiniInputObject * -UHoudiniInputInstancedMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_ISMC_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputInstancedMeshComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::InstancedStaticMeshComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SMC_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputMeshComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputMeshComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMeshComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Spline_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSplineComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputSplineComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::SplineComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputHoudiniSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_HoudiniSpline_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputHoudiniSplineComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniSplineComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -void -UHoudiniInputHoudiniSplineComponent::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) -{ - Super::CopyStateFrom(InInput, bCopyAllProperties); - // Clear component references since we don't currently support duplicating input components. - InputObject.Reset(); - MyHoudiniSplineComponent = nullptr; -} - -UHoudiniInputObject * -UHoudiniInputCameraComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Camera_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputCameraComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputCameraComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputCameraComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::CameraComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputHoudiniAsset::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - UHoudiniAssetComponent * InHoudiniAssetComponent = Cast(InObject); - if (!InHoudiniAssetComponent) - return nullptr; - - FString InputObjectNameStr = "HoudiniInputObject_HAC_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniAsset::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputHoudiniAsset * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputHoudiniAsset::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniAssetComponent; - HoudiniInputObject->InputNodeId = InHoudiniAssetComponent->GetAssetId(); - HoudiniInputObject->InputObjectNodeId = InHoudiniAssetComponent->GetAssetId(); - - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputSceneComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SceneComp_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSceneComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputSceneComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputSceneComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::SceneComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputLandscape::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Landscape_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputLandscape::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputLandscape * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputLandscape::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Landscape; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputBrush * -UHoudiniInputBrush::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Brush_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputBrush::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputBrush * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputBrush::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Brush; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputActor::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Actor_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputActor::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputActor * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputActor::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Actor; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputStaticMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SM_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputStaticMesh::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputStaticMesh * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputStaticMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMesh; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -// void UHoudiniInputStaticMesh::DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputStaticMesh*& OutNewInput) -// { -// UHoudiniInputStaticMesh* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); -// OutNewInput = NewInput; -// OutNewInput->CopyStateFrom(this, false); -// } - -void -UHoudiniInputStaticMesh::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) -{ - UHoudiniInputStaticMesh* StaticMeshInput = Cast(InInput); - check(InInput); - - TArray PrevInputs = BlueprintStaticMeshes; - - Super::CopyStateFrom(StaticMeshInput, bCopyAllProperties); - - const int32 NumInputs = StaticMeshInput->BlueprintStaticMeshes.Num(); - BlueprintStaticMeshes = PrevInputs; - TArray StaleInputs(BlueprintStaticMeshes); - - BlueprintStaticMeshes.SetNum(NumInputs); - - for (int i = 0; i < NumInputs; ++i) - { - UHoudiniInputStaticMesh* FromInput = StaticMeshInput->BlueprintStaticMeshes[i]; - UHoudiniInputStaticMesh* ToInput = BlueprintStaticMeshes[i]; - - if (!FromInput) - { - BlueprintStaticMeshes[i] = nullptr; - continue; - } - - if (ToInput) - { - // Check whether the ToInput can be reused - bool bIsValid = true; - bIsValid = bIsValid && ToInput->Matches(*FromInput); - bIsValid = bIsValid && ToInput->GetOuter() == this; - if (!bIsValid) - { - ToInput = nullptr; - } - } - - if (ToInput) - { - // We have a reusable input - ToInput->CopyStateFrom(FromInput, true); - } - else - { - // We need to create a new input - ToInput = Cast(FromInput->DuplicateAndCopyState(this)); - } - - BlueprintStaticMeshes[i] = ToInput; - } - - for(UHoudiniInputStaticMesh* StaleInput : StaleInputs) - { - if (!StaleInput) - continue; - StaleInput->InvalidateData(); - } -} - -void -UHoudiniInputStaticMesh::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - Super::SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) - { - if (!Input) - continue; - Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } -} - -void -UHoudiniInputStaticMesh::InvalidateData() -{ - for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) - { - if (!Input) - continue; - Input->InvalidateData(); - } - - Super::InvalidateData(); -} - - -UHoudiniInputObject * -UHoudiniInputSkeletalMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SkelMesh_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSkeletalMesh::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputSkeletalMesh * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputSkeletalMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::SkeletalMesh; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputObject::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputObject::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputObject * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputObject::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Object; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -bool -UHoudiniInputObject::Matches(const UHoudiniInputObject& Other) const -{ - return (Type == Other.Type - && InputNodeId == Other.InputNodeId - && InputObjectNodeId == Other.InputObjectNodeId - ); -} - -//----------------------------------------------------------------------------------------------------------------------------- -// DELETE METHODS -//----------------------------------------------------------------------------------------------------------------------------- - -void -UHoudiniInputObject::InvalidateData() -{ - // If valid, mark our input nodes for deletion.. - if (this->IsA() || !bCanDeleteHoudiniNodes) - { - // Unless if we're a HoudiniAssetInput! we don't want to delete the other HDA's node! - // just invalidate the node IDs! - InputNodeId = -1; - InputObjectNodeId = -1; - return; - } - - if (InputNodeId >= 0) - { - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); - InputNodeId = -1; - } - - // ... and the parent OBJ as well to clean up - if (InputObjectNodeId >= 0) - { - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputObjectNodeId); - InputObjectNodeId = -1; - } -} - -void -UHoudiniInputObject::BeginDestroy() -{ - // Invalidate and mark our input node for deletion - InvalidateData(); - - Super::BeginDestroy(); -} - -//----------------------------------------------------------------------------------------------------------------------------- -// UPDATE METHODS -//----------------------------------------------------------------------------------------------------------------------------- - -void -UHoudiniInputObject::Update(UObject * InObject) -{ - InputObject = InObject; -} - -void -UHoudiniInputStaticMesh::Update(UObject * InObject) -{ - // Nothing to do - Super::Update(InObject); - // Static Mesh input accpets either SM and BP. - UStaticMesh* SM = Cast(InObject); - UBlueprint* BP = Cast(InObject); - - ensure(SM || BP); -} - -void -UHoudiniInputSkeletalMesh::Update(UObject * InObject) -{ - // Nothing to do - Super::Update(InObject); - - USkeletalMesh* SkelMesh = Cast(InObject); - ensure(SkelMesh); -} - -void -UHoudiniInputSceneComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - USceneComponent* USC = Cast(InObject); - ensure(USC); - if (USC) - { - Transform = USC->GetComponentTransform(); - } -} - - -bool -UHoudiniInputSceneComponent::HasActorTransformChanged() const -{ - // Returns true if the attached actor's (parent) transform has been modified - USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); - if (!MyComp || MyComp->IsPendingKill()) - return false; - - AActor* MyActor = MyComp->GetOwner(); - if (!MyActor) - return false; - - return (!ActorTransform.Equals(MyActor->GetTransform())); -} - - -bool -UHoudiniInputSceneComponent::HasComponentTransformChanged() const -{ - // Returns true if the attached actor's (parent) transform has been modified - USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); - if (!MyComp || MyComp->IsPendingKill()) - return false; - - return !Transform.Equals(MyComp->GetComponentTransform()); -} - - -bool -UHoudiniInputSceneComponent::HasComponentChanged() const -{ - // Should return true if the component itself has been modified - // Should be overriden in child classes - return false; -} - - -bool -UHoudiniInputMeshComponent::HasComponentChanged() const -{ - UStaticMeshComponent* SMC = Cast(InputObject.LoadSynchronous()); - UStaticMesh* MySM = StaticMesh.Get(); - - // Return true if SMC's static mesh has been modified - return (MySM != SMC->GetStaticMesh()); -} - -bool -UHoudiniInputCameraComponent::HasComponentChanged() const -{ - UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); - if (Camera && !Camera->IsPendingKill()) - { - bool bOrtho = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; - if (bOrtho != bIsOrthographic) - return true; - - if (Camera->FieldOfView != FOV) - return true; - - if (Camera->AspectRatio != AspectRatio) - return true; - - if (Camera->OrthoWidth != OrthoWidth) - return true; - - if (Camera->OrthoNearClipPlane != OrthoNearClipPlane) - return true; - - if (Camera->OrthoFarClipPlane != OrthoFarClipPlane) - return true; - } - - return false; -} - - - -void -UHoudiniInputCameraComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); - - ensure(Camera); - - if (Camera && !Camera->IsPendingKill()) - { - bIsOrthographic = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; - FOV = Camera->FieldOfView; - AspectRatio = Camera->AspectRatio; - OrthoWidth = Camera->OrthoWidth; - OrthoNearClipPlane = Camera->OrthoNearClipPlane; - OrthoFarClipPlane = Camera->OrthoFarClipPlane; - } -} - -void -UHoudiniInputMeshComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - UStaticMeshComponent* SMC = Cast(InObject); - - ensure(SMC); - - if (SMC) - { - StaticMesh = TSoftObjectPtr(SMC->GetStaticMesh()); - - TArray Materials = SMC->GetMaterials(); - for (auto CurrentMat : Materials) - { - // TODO: Update material ref here - FString MatRef; - MeshComponentsMaterials.Add(MatRef); - } - } -} - -void -UHoudiniInputInstancedMeshComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - UInstancedStaticMeshComponent* ISMC = Cast(InObject); - - ensure(ISMC); - - if (ISMC) - { - uint32 InstanceCount = ISMC->GetInstanceCount(); - InstanceTransforms.SetNum(InstanceCount); - - // Copy the instances' transforms - for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) - { - FTransform CurTransform = FTransform::Identity; - ISMC->GetInstanceTransform(InstIdx, CurTransform); - InstanceTransforms[InstIdx] = CurTransform; - } - } -} - -bool -UHoudiniInputInstancedMeshComponent::HasInstancesChanged() const -{ - UInstancedStaticMeshComponent* ISMC = Cast(InputObject.LoadSynchronous()); - if (!ISMC) - return false; - - uint32 InstanceCount = ISMC->GetInstanceCount(); - if (InstanceTransforms.Num() != InstanceCount) - return true; - - // Copy the instances' transforms - for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) - { - FTransform CurTransform = FTransform::Identity; - ISMC->GetInstanceTransform(InstIdx, CurTransform); - - if(!InstanceTransforms[InstIdx].Equals(CurTransform)) - return true; - } - - return false; -} - -bool -UHoudiniInputInstancedMeshComponent::HasComponentTransformChanged() const -{ - if (Super::HasComponentTransformChanged()) - return true; - - return HasInstancesChanged(); -} - -void -UHoudiniInputSplineComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - USplineComponent* Spline = Cast(InObject); - - ensure(Spline); - - if (Spline) - { - NumberOfSplineControlPoints = Spline->GetNumberOfSplinePoints(); - SplineLength = Spline->GetSplineLength(); - SplineClosed = Spline->IsClosedLoop(); - - //SplineResolution = -1.0f; - - SplineControlPoints.SetNumZeroed(NumberOfSplineControlPoints); - for (int32 Idx = 0; Idx < NumberOfSplineControlPoints; Idx++) - { - SplineControlPoints[Idx] = Spline->GetTransformAtSplinePoint(Idx, ESplineCoordinateSpace::Local); - } - } -} - -void -UHoudiniInputHoudiniSplineComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - // We need a strong ref to the spline component to prevent it from being GCed - MyHoudiniSplineComponent = Cast(InObject); - - if (!MyHoudiniSplineComponent || MyHoudiniSplineComponent->IsPendingKill()) - { - // Use default values - CurveType = EHoudiniCurveType::Polygon; - CurveMethod = EHoudiniCurveMethod::CVs; - Reversed = false; - } - else - { - CurveType = MyHoudiniSplineComponent->GetCurveType(); - CurveMethod = MyHoudiniSplineComponent->GetCurveMethod(); - Reversed = false;//Spline->IsReversed(); - } -} - -void -UHoudiniInputHoudiniAsset::Update(UObject * InObject) -{ - Super::Update(InObject); - - UHoudiniAssetComponent* HAC = Cast(InObject); - - ensure(HAC); - - if (HAC) - { - // TODO: Notify HAC that we're a downstream? - - // TODO: Allow selection of the asset output - AssetOutputIndex = 0; - } -} - - -void -UHoudiniInputActor::Update(UObject * InObject) -{ - Super::Update(InObject); - - AActor* Actor = Cast(InObject); - ensure(Actor); - - if (Actor) - { - Transform = Actor->GetTransform(); - - // TODO: - // Iterate on all our scene component, creating child inputs for them if necessary - // - // The actor's components that can be sent as inputs - ActorComponents.Empty(); - - TArray AllComponents; - Actor->GetComponents(AllComponents, true); - - int32 CompIdx = 0; - ActorComponents.SetNum(AllComponents.Num()); - for (USceneComponent * SceneComponent : AllComponents) - { - if (!SceneComponent || SceneComponent->IsPendingKill()) - continue; - - UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( - SceneComponent, GetOuter(), Actor->GetName()); - if (!InputObj) - continue; - - UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); - if (!SceneInput) - continue; - - ActorComponents[CompIdx++] = SceneInput; - } - ActorComponents.SetNum(CompIdx); - } -} - -bool -UHoudiniInputActor::HasActorTransformChanged() -{ - if (!GetActor()) - return false; - - if (!Transform.Equals(GetActor()->GetTransform())) - return true; - - return false; -} - -bool -UHoudiniInputActor::HasContentChanged() const -{ - return false; -} - -bool -UHoudiniInputLandscape::HasActorTransformChanged() -{ - return Super::HasActorTransformChanged(); - //return false; -} - -void -UHoudiniInputLandscape::Update(UObject * InObject) -{ - Super::Update(InObject); - - ALandscapeProxy* Landscape = Cast(InObject); - - //ensure(Landscape); - - if (Landscape) - { - // Nothing to do for landscapes? - } -} - -EHoudiniInputObjectType -UHoudiniInputObject::GetInputObjectTypeFromObject(UObject* InObject) -{ - if (InObject->IsA(USceneComponent::StaticClass())) - { - // Handle component inputs - // UISMC derived from USMC, so always test instances before static meshes - if (InObject->IsA(UInstancedStaticMeshComponent::StaticClass())) - { - return EHoudiniInputObjectType::InstancedStaticMeshComponent; - } - else if (InObject->IsA(UStaticMeshComponent::StaticClass())) - { - return EHoudiniInputObjectType::StaticMeshComponent; - } - else if (InObject->IsA(USplineComponent::StaticClass())) - { - return EHoudiniInputObjectType::SplineComponent; - } - else if (InObject->IsA(UHoudiniSplineComponent::StaticClass())) - { - return EHoudiniInputObjectType::HoudiniSplineComponent; - } - else if (InObject->IsA(UHoudiniAssetComponent::StaticClass())) - { - return EHoudiniInputObjectType::HoudiniAssetComponent; - } - else if (InObject->IsA(UCameraComponent::StaticClass())) - { - return EHoudiniInputObjectType::CameraComponent; - } - else - { - return EHoudiniInputObjectType::SceneComponent; - } - } - else if (InObject->IsA(AActor::StaticClass())) - { - // Handle actors - if (InObject->IsA(ALandscapeProxy::StaticClass())) - { - return EHoudiniInputObjectType::Landscape; - } - else if (InObject->IsA(ABrush::StaticClass())) - { - return EHoudiniInputObjectType::Brush; - } - else - { - return EHoudiniInputObjectType::Actor; - } - } - else if (InObject->IsA(UBlueprint::StaticClass())) - { - return EHoudiniInputObjectType::StaticMesh; - } - else - { - if (InObject->IsA(UStaticMesh::StaticClass())) - { - return EHoudiniInputObjectType::StaticMesh; - } - else if (InObject->IsA(USkeletalMesh::StaticClass())) - { - return EHoudiniInputObjectType::SkeletalMesh; - } - else - { - return EHoudiniInputObjectType::Object; - } - } - - return EHoudiniInputObjectType::Invalid; -} - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UHoudiniInputBrush -//----------------------------------------------------------------------------------------------------------------------------- - -FHoudiniBrushInfo::FHoudiniBrushInfo() - : CachedTransform() - , CachedOrigin(ForceInitToZero) - , CachedExtent(ForceInitToZero) - , CachedBrushType(EBrushType::Brush_Default) - , CachedSurfaceHash(0) -{ -} - -FHoudiniBrushInfo::FHoudiniBrushInfo(ABrush* InBrushActor) - : CachedTransform() - , CachedOrigin(ForceInitToZero) - , CachedExtent(ForceInitToZero) - , CachedBrushType(EBrushType::Brush_Default) - , CachedSurfaceHash(0) -{ - if (!InBrushActor) - return; - - BrushActor = InBrushActor; - CachedTransform = BrushActor->GetActorTransform(); - BrushActor->GetActorBounds(false, CachedOrigin, CachedExtent); - CachedBrushType = BrushActor->BrushType; - -#if WITH_EDITOR - UModel* Model = BrushActor->Brush; - - // Cache the hash of the surface properties - if (IsValid(Model) && IsValid(Model->Polys)) - { - int32 NumPolys = Model->Polys->Element.Num(); - CachedSurfaceHash = 0; - for(int32 iPoly = 0; iPoly < NumPolys; ++iPoly) - { - const FPoly& Poly = Model->Polys->Element[iPoly]; - CombinePolyHash(CachedSurfaceHash, Poly); - } - } - else - { - CachedSurfaceHash = 0; - } -#endif -} - -bool FHoudiniBrushInfo::HasChanged() const -{ - if (!BrushActor.IsValid()) - return false; - - // Has the transform changed? - if (!BrushActor->GetActorTransform().Equals(CachedTransform)) - return true; - - if (BrushActor->BrushType != CachedBrushType) - return true; - - // Has the actor bounds changed? - FVector TmpOrigin, TmpExtent; - BrushActor->GetActorBounds(false, TmpOrigin, TmpExtent); - - if (!(TmpOrigin.Equals(CachedOrigin) && TmpExtent.Equals(CachedExtent) )) - return true; -#if WITH_EDITOR - // Is there a tracked surface property that changed? - UModel* Model = BrushActor->Brush; - if (IsValid(Model) && IsValid(Model->Polys)) - { - // Hash the incoming surface properties and compared it against the cached hash. - int32 NumPolys = Model->Polys->Element.Num(); - uint64 SurfaceHash = 0; - for (int32 iPoly = 0; iPoly < NumPolys; ++iPoly) - { - const FPoly& Poly = Model->Polys->Element[iPoly]; - CombinePolyHash(SurfaceHash, Poly); - } - if (SurfaceHash != CachedSurfaceHash) - return true; - } - else - { - if (CachedSurfaceHash != 0) - return true; - } -#endif - return false; -} - -int32 FHoudiniBrushInfo::GetNumVertexIndicesFromModel(const UModel* Model) -{ - const TArray& Nodes = Model->Nodes; - int32 NumIndices = 0; - // Build the face counts buffer by iterating over the BSP nodes. - for(const FBspNode& Node : Nodes) - { - NumIndices += Node.NumVertices; - } - return NumIndices; -} - -UModel* UHoudiniInputBrush::GetCachedModel() const -{ - return CombinedModel; -} - -bool UHoudiniInputBrush::HasBrushesChanged(const TArray& InBrushes) const -{ - if (InBrushes.Num() != BrushesInfo.Num()) - return true; - - int32 NumBrushes = BrushesInfo.Num(); - - for (int32 InfoIndex = 0; InfoIndex < NumBrushes; ++InfoIndex) - { - const FHoudiniBrushInfo& BrushInfo = BrushesInfo[InfoIndex]; - // Has the cached brush actor invalid? - if (!BrushInfo.BrushActor.IsValid()) - return true; - - // Has there been an order change in the actors list? - if (InBrushes[InfoIndex] != BrushInfo.BrushActor.Get()) - return true; - - // Has there been any other changes to the brush? - if (BrushInfo.HasChanged()) - return true; - } - - // Nothing has changed. - return false; -} - -void UHoudiniInputBrush::UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes) -{ - ABrush* InputBrush = GetBrush(); - if (IsValid(InputBrush)) - { - CachedInputBrushType = InputBrush->BrushType; - } - - // Cache the combined model aswell as the brushes used to generate this model. - CombinedModel = InCombinedModel; - - BrushesInfo.SetNumUninitialized(InBrushes.Num()); - for (int i = 0; i < InBrushes.Num(); ++i) - { - if (!InBrushes[i]) - continue; - BrushesInfo[i] = FHoudiniBrushInfo(InBrushes[i]); - } -} - - -void -UHoudiniInputBrush::Update(UObject * InObject) -{ - Super::Update(InObject); - - ABrush* BrushActor = GetBrush(); - if (!IsValid(BrushActor)) - { - bIgnoreInputObject = true; - return; - } - - CachedInputBrushType = BrushActor->BrushType; - - bIgnoreInputObject = ShouldIgnoreThisInput(); -} - -bool -UHoudiniInputBrush::ShouldIgnoreThisInput() -{ - // Invalid brush, should be ignored - ABrush* BrushActor = GetBrush(); - ensure(BrushActor); - if (!BrushActor) - return true; - - // If the BrushType has changed since caching this object, this object cannot be ignored. - if (CachedInputBrushType != BrushActor->BrushType) - return false; - - // If it's not an additive brush, we want to ignore it - bool bShouldBeIgnored = BrushActor->BrushType != EBrushType::Brush_Add; - - // If this is not a static brush (e.g., AVolume), ignore it. - if (!bShouldBeIgnored) - bShouldBeIgnored = !BrushActor->IsStaticBrush(); - - return bShouldBeIgnored; -} - -bool UHoudiniInputBrush::HasContentChanged() const -{ - ABrush* BrushActor = GetBrush(); - ensure(BrushActor); - - if (!BrushActor) - return false; - - if (BrushActor->BrushType != CachedInputBrushType) - return true; - - if (bIgnoreInputObject) - return false; - - // Find intersecting actors and capture their properties so that - // we can determine whether something has changed. - TArray IntersectingBrushes; - FindIntersectingSubtractiveBrushes(this, IntersectingBrushes); - - if (HasBrushesChanged(IntersectingBrushes)) - { - return true; - } - - return false; -} - -bool -UHoudiniInputBrush::HasActorTransformChanged() -{ - if (bIgnoreInputObject) - return false; - - return Super::HasActorTransformChanged(); -} - - -bool UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes) -{ - TArray IntersectingActors; - TArray Bounds; - - - if (!IsValid(InputBrush)) - return false; - - ABrush* BrushActor = InputBrush->GetBrush(); - if (!IsValid(BrushActor)) - return false; - - - OutBrushes.Empty(); - - Bounds.Add( BrushActor->GetComponentsBoundingBox(true, true) ); - - FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(BrushActor->GetWorld(), ABrush::StaticClass(), Bounds, nullptr, IntersectingActors); - - //-------------------------------------------------------------------------------------------------- - // Filter the actors to only keep intersecting subtractive brushes. - //-------------------------------------------------------------------------------------------------- - for (AActor* Actor : IntersectingActors) - { - // Filter out anything that is not a static brush (typically volume actors). - ABrush* Brush = Cast(Actor); - - // NOTE: The brush actor needs to be added in the correct map/level order - // together with the subtractive brushes otherwise the CSG operations - // will not match the BSP in the level. - if (Actor == BrushActor) - OutBrushes.Add(Brush); - - if (!(Brush && Brush->IsStaticBrush())) - continue; - - if (Brush->BrushType == Brush_Subtract) - OutBrushes.Add(Brush); - } - - return true; -} - -#if WITH_EDITOR -void -UHoudiniInputObject::PostEditUndo() -{ - Super::PostEditUndo(); - MarkChanged(true); -} -#endif - -UHoudiniInputObject* -UHoudiniInputObject::DuplicateAndCopyState(UObject * DestOuter) -{ - UHoudiniInputObject* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); - NewInput->CopyStateFrom(this, false); - return NewInput; -} - -void -UHoudiniInputObject::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) -{ - // Copy the state of this UHoudiniInput object. - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References should be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); - } - - InputNodeId = InInput->InputNodeId; - InputObjectNodeId = InInput->InputObjectNodeId; - bHasChanged = InInput->bHasChanged; - bNeedsToTriggerUpdate = InInput->bNeedsToTriggerUpdate; - bTransformChanged = InInput->bTransformChanged; - -#if WITH_EDITORONLY_DATA - bUniformScaleLocked = InInput->bUniformScaleLocked; -#endif - -} - -void -UHoudiniInputObject::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInputObject.h" + +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniInput.h" + +#include "Engine/StaticMesh.h" +#include "Engine/SkeletalMesh.h" +#include "Engine/DataTable.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/SplineComponent.h" +#include "Landscape.h" +#include "Engine/Brush.h" +#include "Engine/Engine.h" +#include "GameFramework/Volume.h" +#include "Camera/CameraComponent.h" +#include "FoliageType_InstancedStaticMesh.h" + +#include "Model.h" +#include "Engine/Brush.h" + +#include "HoudiniEngineRuntimeUtils.h" +#include "Kismet/KismetSystemLibrary.h" + +//----------------------------------------------------------------------------------------------------------------------------- +// Constructors +//----------------------------------------------------------------------------------------------------------------------------- + +// +UHoudiniInputObject::UHoudiniInputObject(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , Transform(FTransform::Identity) + , Type(EHoudiniInputObjectType::Invalid) + , InputNodeId(-1) + , InputObjectNodeId(-1) + , bHasChanged(false) + , bNeedsToTriggerUpdate(false) + , bTransformChanged(false) + , bImportAsReference(false) + , bCanDeleteHoudiniNodes(true) +{ + Guid = FGuid::NewGuid(); +} + +// +UHoudiniInputStaticMesh::UHoudiniInputStaticMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputSkeletalMesh::UHoudiniInputSkeletalMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputSceneComponent::UHoudiniInputSceneComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputMeshComponent::UHoudiniInputMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputInstancedMeshComponent::UHoudiniInputInstancedMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputSplineComponent::UHoudiniInputSplineComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , NumberOfSplineControlPoints(-1) + , SplineLength(-1.0f) + , SplineResolution(-1.0f) + , SplineClosed(false) +{ + +} + +// +UHoudiniInputCameraComponent::UHoudiniInputCameraComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , FOV(0.0f) + , AspectRatio(1.0f) + , bIsOrthographic(false) + , OrthoWidth(2.0f) + , OrthoNearClipPlane(0.0f) + , OrthoFarClipPlane(-1.0f) +{ + +} + +// Return true if the component itself has been modified +bool +UHoudiniInputSplineComponent::HasComponentChanged() const +{ + USplineComponent* SplineComponent = Cast(InputObject.LoadSynchronous()); + + if (!SplineComponent) + return false; + + if (SplineClosed != SplineComponent->IsClosedLoop()) + return true; + + + if (SplineComponent->GetNumberOfSplinePoints() != NumberOfSplineControlPoints) + return true; + + for (int32 n = 0; n < SplineComponent->GetNumberOfSplinePoints(); ++n) + { + const FTransform &CurSplineComponentTransform = SplineComponent->GetTransformAtSplinePoint(n, ESplineCoordinateSpace::Local); + const FTransform &CurInputTransform = SplineControlPoints[n]; + + if (CurInputTransform.GetLocation() != CurSplineComponentTransform.GetLocation()) + return true; + + if (CurInputTransform.GetRotation().Rotator() != CurSplineComponentTransform.GetRotation().Rotator()) + return true; + + if (CurInputTransform.GetScale3D() != CurSplineComponentTransform.GetScale3D()) + return true; + } + + return false; +} + +// +UHoudiniInputHoudiniSplineComponent::UHoudiniInputHoudiniSplineComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , CurveType(EHoudiniCurveType::Polygon) + , CurveMethod(EHoudiniCurveMethod::CVs) + , Reversed(false) +{ + +} + +// +UHoudiniInputHoudiniAsset::UHoudiniInputHoudiniAsset(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , AssetOutputIndex(-1) +{ + +} + +// +UHoudiniInputActor::UHoudiniInputActor(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , LastUpdateNumComponentsAdded(0) + , LastUpdateNumComponentsRemoved(0) +{ + +} + +// +UHoudiniInputLandscape::UHoudiniInputLandscape(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputBrush::UHoudiniInputBrush() + : CombinedModel(nullptr) + , bIgnoreInputObject(false) +{ + +} + +//----------------------------------------------------------------------------------------------------------------------------- +// Accessors +//----------------------------------------------------------------------------------------------------------------------------- + +UObject* +UHoudiniInputObject::GetObject() const +{ + return InputObject.LoadSynchronous(); +} + +UStaticMesh* +UHoudiniInputStaticMesh::GetStaticMesh() const +{ + return Cast(InputObject.LoadSynchronous()); +} + +UBlueprint* +UHoudiniInputStaticMesh::GetBlueprint() const +{ + return Cast(InputObject.LoadSynchronous()); +} + +bool UHoudiniInputStaticMesh::bIsBlueprint() const +{ + return (InputObject.IsValid() && InputObject.Get()->IsA()); +} + +USkeletalMesh* +UHoudiniInputSkeletalMesh::GetSkeletalMesh() +{ + return Cast(InputObject.LoadSynchronous()); +} + +USceneComponent* +UHoudiniInputSceneComponent::GetSceneComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UStaticMeshComponent* +UHoudiniInputMeshComponent::GetStaticMeshComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UStaticMesh* +UHoudiniInputMeshComponent::GetStaticMesh() +{ + return StaticMesh.Get(); +} + +UInstancedStaticMeshComponent* +UHoudiniInputInstancedMeshComponent::GetInstancedStaticMeshComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +USplineComponent* +UHoudiniInputSplineComponent::GetSplineComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UHoudiniSplineComponent* +UHoudiniInputHoudiniSplineComponent::GetCurveComponent() const +{ + return Cast(GetObject()); + //return Cast(InputObject.LoadSynchronous()); +} + +UCameraComponent* +UHoudiniInputCameraComponent::GetCameraComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UHoudiniAssetComponent* +UHoudiniInputHoudiniAsset::GetHoudiniAssetComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +AActor* +UHoudiniInputActor::GetActor() const +{ + return Cast(InputObject.LoadSynchronous()); +} + +ALandscapeProxy* +UHoudiniInputLandscape::GetLandscapeProxy() +{ + return Cast(InputObject.LoadSynchronous()); +} + +void +UHoudiniInputLandscape::SetLandscapeProxy(UObject* InLandscapeProxy) +{ + UObject* LandscapeProxy = Cast(InLandscapeProxy); + if (LandscapeProxy) + InputObject = LandscapeProxy; +} + +ABrush* +UHoudiniInputBrush::GetBrush() const +{ + return Cast(InputObject.LoadSynchronous()); +} + + +//----------------------------------------------------------------------------------------------------------------------------- +// CREATE METHODS +//----------------------------------------------------------------------------------------------------------------------------- + +UHoudiniInputObject * +UHoudiniInputObject::CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InName) +{ + if (!InObject) + return nullptr; + + UHoudiniInputObject* HoudiniInputObject = nullptr; + + EHoudiniInputObjectType InputObjectType = GetInputObjectTypeFromObject(InObject); + switch (InputObjectType) + { + case EHoudiniInputObjectType::Object: + HoudiniInputObject = UHoudiniInputObject::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::StaticMesh: + HoudiniInputObject = UHoudiniInputStaticMesh::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::SkeletalMesh: + HoudiniInputObject = UHoudiniInputSkeletalMesh::Create(InObject, InOuter, InName); + break; + case EHoudiniInputObjectType::SceneComponent: + // Do not create input objects for unknown scene component! + //HoudiniInputObject = UHoudiniInputSceneComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::StaticMeshComponent: + HoudiniInputObject = UHoudiniInputMeshComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::InstancedStaticMeshComponent: + HoudiniInputObject = UHoudiniInputInstancedMeshComponent::Create(InObject, InOuter, InName); + break; + case EHoudiniInputObjectType::SplineComponent: + HoudiniInputObject = UHoudiniInputSplineComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::HoudiniSplineComponent: + HoudiniInputObject = UHoudiniInputHoudiniSplineComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::HoudiniAssetActor: + { + AHoudiniAssetActor* HoudiniActor = Cast(InObject); + if (HoudiniActor) + { + HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniActor->GetHoudiniAssetComponent(), InOuter, InName); + } + else + { + HoudiniInputObject = nullptr; + } + } + break; + + case EHoudiniInputObjectType::HoudiniAssetComponent: + HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(InObject, InOuter, InName); + break; + case EHoudiniInputObjectType::Actor: + HoudiniInputObject = UHoudiniInputActor::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::Landscape: + HoudiniInputObject = UHoudiniInputLandscape::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::Brush: + HoudiniInputObject = UHoudiniInputBrush::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::CameraComponent: + HoudiniInputObject = UHoudiniInputCameraComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::DataTable: + HoudiniInputObject = UHoudiniInputDataTable::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + HoudiniInputObject = UHoudiniInputFoliageType_InstancedStaticMesh::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::Invalid: + default: + break; + } + + return HoudiniInputObject; +} + + +UHoudiniInputObject * +UHoudiniInputInstancedMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_ISMC_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputInstancedMeshComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::InstancedStaticMeshComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SMC_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputMeshComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputMeshComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMeshComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Spline_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSplineComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputSplineComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::SplineComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputHoudiniSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_HoudiniSpline_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputHoudiniSplineComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniSplineComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputCameraComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Camera_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputCameraComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputCameraComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputCameraComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::CameraComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputHoudiniAsset::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + UHoudiniAssetComponent * InHoudiniAssetComponent = Cast(InObject); + if (!InHoudiniAssetComponent) + return nullptr; + + FString InputObjectNameStr = "HoudiniInputObject_HAC_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniAsset::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputHoudiniAsset * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputHoudiniAsset::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniAssetComponent; + + HoudiniInputObject->InputNodeId = InHoudiniAssetComponent->GetAssetId(); + HoudiniInputObject->InputObjectNodeId = InHoudiniAssetComponent->GetAssetId(); + + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputSceneComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SceneComp_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSceneComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputSceneComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputSceneComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::SceneComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputLandscape::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Landscape_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputLandscape::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputLandscape * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputLandscape::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Landscape; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputBrush * +UHoudiniInputBrush::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Brush_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputBrush::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputBrush * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputBrush::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Brush; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputActor::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Actor_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputActor::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputActor * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputActor::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Actor; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputStaticMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SM_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputStaticMesh::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputStaticMesh * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputStaticMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMesh; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +// void UHoudiniInputStaticMesh::DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputStaticMesh*& OutNewInput) +// { +// UHoudiniInputStaticMesh* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); +// OutNewInput = NewInput; +// OutNewInput->CopyStateFrom(this, false); +// } + +void +UHoudiniInputStaticMesh::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) +{ + UHoudiniInputStaticMesh* StaticMeshInput = Cast(InInput); + check(InInput); + + TArray PrevInputs = BlueprintStaticMeshes; + + Super::CopyStateFrom(StaticMeshInput, bCopyAllProperties); + + const int32 NumInputs = StaticMeshInput->BlueprintStaticMeshes.Num(); + BlueprintStaticMeshes = PrevInputs; + TArray StaleInputs(BlueprintStaticMeshes); + + BlueprintStaticMeshes.SetNum(NumInputs); + + for (int i = 0; i < NumInputs; ++i) + { + UHoudiniInputStaticMesh* FromInput = StaticMeshInput->BlueprintStaticMeshes[i]; + UHoudiniInputStaticMesh* ToInput = BlueprintStaticMeshes[i]; + + if (!FromInput) + { + BlueprintStaticMeshes[i] = nullptr; + continue; + } + + if (ToInput) + { + // Check whether the ToInput can be reused + bool bIsValid = true; + bIsValid = bIsValid && ToInput->Matches(*FromInput); + bIsValid = bIsValid && ToInput->GetOuter() == this; + if (!bIsValid) + { + ToInput = nullptr; + } + } + + if (ToInput) + { + // We have a reusable input + ToInput->CopyStateFrom(FromInput, true); + } + else + { + // We need to create a new input + ToInput = Cast(FromInput->DuplicateAndCopyState(this)); + } + + BlueprintStaticMeshes[i] = ToInput; + } + + for(UHoudiniInputStaticMesh* StaleInput : StaleInputs) + { + if (!StaleInput) + continue; + StaleInput->InvalidateData(); + } +} + +void +UHoudiniInputStaticMesh::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + Super::SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) + { + if (!Input) + continue; + Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } +} + +void +UHoudiniInputStaticMesh::InvalidateData() +{ + for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) + { + if (!Input) + continue; + Input->InvalidateData(); + } + + Super::InvalidateData(); +} + + +UHoudiniInputObject * +UHoudiniInputSkeletalMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SkelMesh_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSkeletalMesh::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputSkeletalMesh * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputSkeletalMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::SkeletalMesh; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputObject::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputObject::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputObject * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputObject::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Object; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +bool +UHoudiniInputObject::Matches(const UHoudiniInputObject& Other) const +{ + return (Type == Other.Type + && InputNodeId == Other.InputNodeId + && InputObjectNodeId == Other.InputObjectNodeId + ); +} + +//----------------------------------------------------------------------------------------------------------------------------- +// DELETE METHODS +//----------------------------------------------------------------------------------------------------------------------------- + +void +UHoudiniInputObject::InvalidateData() +{ + // If valid, mark our input nodes for deletion.. + if (this->IsA() || !bCanDeleteHoudiniNodes) + { + // Unless if we're a HoudiniAssetInput! we don't want to delete the other HDA's node! + // just invalidate the node IDs! + InputNodeId = -1; + InputObjectNodeId = -1; + return; + } + + if (InputNodeId >= 0) + { + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); + InputNodeId = -1; + } + + // ... and the parent OBJ as well to clean up + if (InputObjectNodeId >= 0) + { + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputObjectNodeId); + InputObjectNodeId = -1; + } + + +} + +void +UHoudiniInputObject::BeginDestroy() +{ + // Invalidate and mark our input node for deletion + InvalidateData(); + + Super::BeginDestroy(); +} + +//----------------------------------------------------------------------------------------------------------------------------- +// UPDATE METHODS +//----------------------------------------------------------------------------------------------------------------------------- + +void +UHoudiniInputObject::Update(UObject * InObject) +{ + InputObject = InObject; +} + +void +UHoudiniInputStaticMesh::Update(UObject * InObject) +{ + // Nothing to do + Super::Update(InObject); + // Static Mesh input accepts SM, BP, FoliageType_InstancedStaticMesh (static mesh) and FoliageType_Actor (if blueprint actor). + UStaticMesh* SM = Cast(InObject); + UBlueprint* BP = Cast(InObject); + + ensure(SM || BP); +} + +void +UHoudiniInputSkeletalMesh::Update(UObject * InObject) +{ + // Nothing to do + Super::Update(InObject); + + USkeletalMesh* SkelMesh = Cast(InObject); + ensure(SkelMesh); +} + +void +UHoudiniInputSceneComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + USceneComponent* USC = Cast(InObject); + ensure(USC); + if (USC) + { + Transform = USC->GetComponentTransform(); + } +} + + +bool +UHoudiniInputSceneComponent::HasActorTransformChanged() const +{ + // Returns true if the attached actor's (parent) transform has been modified + USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); + if (!IsValid(MyComp)) + return false; + + AActor* MyActor = MyComp->GetOwner(); + if (!MyActor) + return false; + + return (!ActorTransform.Equals(MyActor->GetTransform())); +} + + +bool +UHoudiniInputSceneComponent::HasComponentTransformChanged() const +{ + // Returns true if the attached actor's (parent) transform has been modified + USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); + if (!IsValid(MyComp)) + return false; + + return !Transform.Equals(MyComp->GetComponentTransform()); +} + + +bool +UHoudiniInputSceneComponent::HasComponentChanged() const +{ + // Should return true if the component itself has been modified + // Should be overriden in child classes + return false; +} + + +bool +UHoudiniInputMeshComponent::HasComponentChanged() const +{ + UStaticMeshComponent* SMC = Cast(InputObject.LoadSynchronous()); + UStaticMesh* MySM = StaticMesh.Get(); + + // Return true if SMC's static mesh has been modified + return (MySM != SMC->GetStaticMesh()); +} + +bool +UHoudiniInputCameraComponent::HasComponentChanged() const +{ + UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); + if (IsValid(Camera)) + { + bool bOrtho = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; + if (bOrtho != bIsOrthographic) + return true; + + if (Camera->FieldOfView != FOV) + return true; + + if (Camera->AspectRatio != AspectRatio) + return true; + + if (Camera->OrthoWidth != OrthoWidth) + return true; + + if (Camera->OrthoNearClipPlane != OrthoNearClipPlane) + return true; + + if (Camera->OrthoFarClipPlane != OrthoFarClipPlane) + return true; + } + + return false; +} + + + +void +UHoudiniInputCameraComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); + + ensure(Camera); + + if (IsValid(Camera)) + { + bIsOrthographic = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; + FOV = Camera->FieldOfView; + AspectRatio = Camera->AspectRatio; + OrthoWidth = Camera->OrthoWidth; + OrthoNearClipPlane = Camera->OrthoNearClipPlane; + OrthoFarClipPlane = Camera->OrthoFarClipPlane; + } +} + +void +UHoudiniInputMeshComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + UStaticMeshComponent* SMC = Cast(InObject); + + ensure(SMC); + + if (SMC) + { + StaticMesh = TSoftObjectPtr(SMC->GetStaticMesh()); + + TArray Materials = SMC->GetMaterials(); + for (auto CurrentMat : Materials) + { + // TODO: Update material ref here + FString MatRef; + MeshComponentsMaterials.Add(MatRef); + } + } +} + +void +UHoudiniInputInstancedMeshComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + UInstancedStaticMeshComponent* ISMC = Cast(InObject); + + ensure(ISMC); + + if (ISMC) + { + uint32 InstanceCount = ISMC->GetInstanceCount(); + InstanceTransforms.SetNum(InstanceCount); + + // Copy the instances' transforms + for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) + { + FTransform CurTransform = FTransform::Identity; + ISMC->GetInstanceTransform(InstIdx, CurTransform); + InstanceTransforms[InstIdx] = CurTransform; + } + } +} + +bool +UHoudiniInputInstancedMeshComponent::HasInstancesChanged() const +{ + UInstancedStaticMeshComponent* ISMC = Cast(InputObject.LoadSynchronous()); + if (!ISMC) + return false; + + uint32 InstanceCount = ISMC->GetInstanceCount(); + if (InstanceTransforms.Num() != InstanceCount) + return true; + + // Copy the instances' transforms + for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) + { + FTransform CurTransform = FTransform::Identity; + ISMC->GetInstanceTransform(InstIdx, CurTransform); + + if(!InstanceTransforms[InstIdx].Equals(CurTransform)) + return true; + } + + return false; +} + +bool +UHoudiniInputInstancedMeshComponent::HasComponentTransformChanged() const +{ + if (Super::HasComponentTransformChanged()) + return true; + + return HasInstancesChanged(); +} + +void +UHoudiniInputSplineComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + USplineComponent* Spline = Cast(InObject); + + ensure(Spline); + + if (Spline) + { + NumberOfSplineControlPoints = Spline->GetNumberOfSplinePoints(); + SplineLength = Spline->GetSplineLength(); + SplineClosed = Spline->IsClosedLoop(); + + //SplineResolution = -1.0f; + + SplineControlPoints.SetNumZeroed(NumberOfSplineControlPoints); + for (int32 Idx = 0; Idx < NumberOfSplineControlPoints; Idx++) + { + SplineControlPoints[Idx] = Spline->GetTransformAtSplinePoint(Idx, ESplineCoordinateSpace::Local); + } + } +} + +void +UHoudiniInputHoudiniSplineComponent::Update(UObject* InObject) +{ + Super::Update(InObject); + + // We store the component references as a normal pointer property instead of using a soft object reference. + // If we use a soft object reference, the editor will complain about deleting a reference that is in use + // everytime we try to delete the actor, even though everything is contained within the actor. + + CachedComponent = Cast(InObject); + InputObject = nullptr; + + // We need a strong ref to the spline component to prevent it from being GCed + //MyHoudiniSplineComponent = Cast(InObject); + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + + if (!IsValid(HoudiniSplineComponent)) + { + // Use default values + CurveType = EHoudiniCurveType::Polygon; + CurveMethod = EHoudiniCurveMethod::CVs; + Reversed = false; + } + else + { + CurveType = HoudiniSplineComponent->GetCurveType(); + CurveMethod = HoudiniSplineComponent->GetCurveMethod(); + Reversed = false;//Spline->IsReversed(); + } +} + +UObject* +UHoudiniInputHoudiniSplineComponent::GetObject() const +{ + return CachedComponent; +} + +void +UHoudiniInputHoudiniSplineComponent::MarkChanged(const bool& bInChanged) +{ + Super::MarkChanged(bInChanged); + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent) + { + HoudiniSplineComponent->MarkChanged(bInChanged); + } +} + +void +UHoudiniInputHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) +{ + Super::SetNeedsToTriggerUpdate(bInTriggersUpdate); + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent) + { + HoudiniSplineComponent->SetNeedsToTriggerUpdate(bInTriggersUpdate); + } +} + +bool +UHoudiniInputHoudiniSplineComponent::HasChanged() const +{ + if (Super::HasChanged()) + return true; + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent && HoudiniSplineComponent->HasChanged()) + return true; + + return false; +} + +bool +UHoudiniInputHoudiniSplineComponent::NeedsToTriggerUpdate() const +{ + if (Super::NeedsToTriggerUpdate()) + return true; + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent && HoudiniSplineComponent->NeedsToTriggerUpdate()) + return true; + + return false; +} + +void +UHoudiniInputHoudiniAsset::Update(UObject * InObject) +{ + Super::Update(InObject); + + UHoudiniAssetComponent* HAC = Cast(InObject); + + ensure(HAC); + + if (HAC) + { + // TODO: Notify HAC that we're a downstream? + InputNodeId = HAC->GetAssetId(); + InputObjectNodeId = HAC->GetAssetId(); + + // TODO: Allow selection of the asset output + AssetOutputIndex = 0; + } +} + + +void +UHoudiniInputActor::Update(UObject * InObject) +{ + const bool bHasInputObjectChanged = InputObject != InObject; + + Super::Update(InObject); + + AActor* Actor = Cast(InObject); + ensure(Actor); + + if (Actor) + { + Transform = Actor->GetTransform(); + + // If we are updating (InObject == InputObject), then remove stale components and add new components, + // if InObject != InputObject, remove all components and rebuild + + if (bHasInputObjectChanged) + { + // The actor's components that can be sent as inputs + LastUpdateNumComponentsRemoved = ActorComponents.Num(); + + ActorComponents.Empty(); + ActorSceneComponents.Empty(); + + TArray AllComponents; + Actor->GetComponents(AllComponents, true); + + ActorComponents.Reserve(AllComponents.Num()); + for (USceneComponent * SceneComponent : AllComponents) + { + if (!IsValid(SceneComponent)) + continue; + if (!ShouldTrackComponent(SceneComponent)) + continue; + + UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( + SceneComponent, GetOuter(), Actor->GetName()); + if (!InputObj) + continue; + + UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); + if (!SceneInput) + continue; + + ActorComponents.Add(SceneInput); + ActorSceneComponents.Add(TSoftObjectPtr(SceneComponent)); + } + LastUpdateNumComponentsAdded = ActorComponents.Num(); + } + else + { + LastUpdateNumComponentsAdded = 0; + LastUpdateNumComponentsRemoved = 0; + + // Look for any components to add or remove + TSet NewComponents; + const bool bIncludeFromChildActors = true; + Actor->ForEachComponent(bIncludeFromChildActors, [&](USceneComponent* InComp) + { + if (IsValid(InComp)) + { + if (!ActorSceneComponents.Contains(InComp)) + { + NewComponents.Add(InComp); + } + } + }); + + // Update the actor input components (from the same actor) + TArray ComponentIndicesToRemove; + const int32 NumActorComponents = ActorComponents.Num(); + for (int32 Index = 0; Index < NumActorComponents; ++Index) + { + UHoudiniInputSceneComponent* CurActorComp = ActorComponents[Index]; + if (!IsValid(CurActorComp)) + { + ComponentIndicesToRemove.Add(Index); + continue; + } + + // Does the component still exist on Actor? + UObject* const CompObj = CurActorComp->GetObject(); + // Make sure the actor is still valid + if (!IsValid(CompObj)) + { + // If it's not, mark it for deletion + if ((CurActorComp->InputNodeId > 0) || (CurActorComp->InputObjectNodeId > 0)) + { + CurActorComp->InvalidateData(); + } + + ComponentIndicesToRemove.Add(Index); + continue; + } + } + + // Remove the destroyed/invalid components + const int32 NumToRemove = ComponentIndicesToRemove.Num(); + if (NumToRemove > 0) + { + for (int32 Index = NumToRemove - 1; Index >= 0; --Index) + { + const int32& IndexToRemove = ComponentIndicesToRemove[Index]; + + UHoudiniInputSceneComponent* const CurActorComp = ActorComponents[IndexToRemove]; + if (CurActorComp) + ActorSceneComponents.Remove(CurActorComp->InputObject); + + const bool bAllowShrink = false; + ActorComponents.RemoveAtSwap(IndexToRemove, 1, bAllowShrink); + + LastUpdateNumComponentsRemoved++; + } + } + + if (NewComponents.Num() > 0) + { + for (USceneComponent * SceneComponent : NewComponents) + { + if (!IsValid(SceneComponent)) + continue; + + UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( + SceneComponent, GetOuter(), Actor->GetName()); + if (!InputObj) + continue; + + UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); + if (!SceneInput) + continue; + + ActorComponents.Add(SceneInput); + ActorSceneComponents.Add(SceneComponent); + + LastUpdateNumComponentsAdded++; + } + } + + if (LastUpdateNumComponentsAdded > 0 || LastUpdateNumComponentsRemoved > 0) + { + ActorComponents.Shrink(); + } + } + } + else + { + // If we don't have a valid actor or null, delete any input components we still have and mark as changed + if (ActorComponents.Num() > 0) + { + LastUpdateNumComponentsAdded = 0; + LastUpdateNumComponentsRemoved = ActorComponents.Num(); + ActorComponents.Empty(); + ActorSceneComponents.Empty(); + } + else + { + LastUpdateNumComponentsAdded = 0; + LastUpdateNumComponentsRemoved = 0; + } + } +} + +bool +UHoudiniInputActor::HasActorTransformChanged() const +{ + if (!GetActor()) + return false; + + if (HasRootComponentTransformChanged()) + return true; + + if (HasComponentsTransformChanged()) + return true; + + return false; +} + +bool UHoudiniInputActor::HasRootComponentTransformChanged() const +{ + if (!Transform.Equals(GetActor()->GetTransform())) + return true; + return false; +} + +bool UHoudiniInputActor::HasComponentsTransformChanged() const +{ + // Search through all the child components to see if we have changed + // transforms in there. + for (auto CurActorComp : GetActorComponents()) + { + if (CurActorComp->HasComponentTransformChanged()) + { + return true; + } + } + + return false; +} + +bool +UHoudiniInputActor::HasContentChanged() const +{ + return false; +} + +bool +UHoudiniInputActor::GetChangedObjectsAndValidNodes(TArray& OutChangedObjects, TArray& OutNodeIdsOfUnchangedValidObjects) +{ + if (Super::GetChangedObjectsAndValidNodes(OutChangedObjects, OutNodeIdsOfUnchangedValidObjects)) + return true; + + bool bAnyChanges = false; + // Check each of its child objects (components) + for (auto* const CurrentComp : GetActorComponents()) + { + if (!IsValid(CurrentComp)) + continue; + + if (CurrentComp->GetChangedObjectsAndValidNodes(OutChangedObjects, OutNodeIdsOfUnchangedValidObjects)) + bAnyChanges = true; + } + + return bAnyChanges; +} + +void +UHoudiniInputActor::InvalidateData() +{ + // Call invalidate on input component objects + for (auto* const CurrentComp : GetActorComponents()) + { + if (!IsValid(CurrentComp)) + continue; + + CurrentComp->InvalidateData(); + } + + Super::InvalidateData(); +} + +bool UHoudiniInputLandscape::ShouldTrackComponent(UActorComponent* InComponent) +{ + // We explicitly disable tracking of any components for landscape inputs since the Landscape tools + // have this very interesting and creative way of adding components when the tool is activated + // (looking at you Flatten tool) which causes cooking loops. + // return false; + return true; +} + +void +UHoudiniInputLandscape::Update(UObject * InObject) +{ + Super::Update(InObject); + + ALandscapeProxy* Landscape = Cast(InObject); + + //ensure(Landscape); + + if (Landscape) + { + // Nothing to do for landscapes? + } +} + +EHoudiniInputObjectType +UHoudiniInputObject::GetInputObjectTypeFromObject(UObject* InObject) +{ + if (InObject->IsA(USceneComponent::StaticClass())) + { + // Handle component inputs + // UISMC derived from USMC, so always test instances before static meshes + if (InObject->IsA(UInstancedStaticMeshComponent::StaticClass())) + { + return EHoudiniInputObjectType::InstancedStaticMeshComponent; + } + else if (InObject->IsA(UStaticMeshComponent::StaticClass())) + { + return EHoudiniInputObjectType::StaticMeshComponent; + } + else if (InObject->IsA(USplineComponent::StaticClass())) + { + return EHoudiniInputObjectType::SplineComponent; + } + else if (InObject->IsA(UHoudiniSplineComponent::StaticClass())) + { + return EHoudiniInputObjectType::HoudiniSplineComponent; + } + else if (InObject->IsA(UHoudiniAssetComponent::StaticClass())) + { + return EHoudiniInputObjectType::HoudiniAssetComponent; + } + else if (InObject->IsA(UCameraComponent::StaticClass())) + { + return EHoudiniInputObjectType::CameraComponent; + } + else + { + return EHoudiniInputObjectType::SceneComponent; + } + } + else if (InObject->IsA(AActor::StaticClass())) + { + // Handle actors + if (InObject->IsA(ALandscapeProxy::StaticClass())) + { + return EHoudiniInputObjectType::Landscape; + } + else if (InObject->IsA(ABrush::StaticClass())) + { + return EHoudiniInputObjectType::Brush; + } + else if (InObject->IsA(AHoudiniAssetActor::StaticClass())) + { + return EHoudiniInputObjectType::HoudiniAssetActor; + } + else + { + return EHoudiniInputObjectType::Actor; + } + } + else if (InObject->IsA(UBlueprint::StaticClass())) + { + return EHoudiniInputObjectType::StaticMesh; + } + else if (InObject->IsA(UFoliageType_InstancedStaticMesh::StaticClass())) + { + return EHoudiniInputObjectType::FoliageType_InstancedStaticMesh; + } + else + { + if (InObject->IsA(UStaticMesh::StaticClass())) + { + return EHoudiniInputObjectType::StaticMesh; + } + else if (InObject->IsA(USkeletalMesh::StaticClass())) + { + return EHoudiniInputObjectType::SkeletalMesh; + } + else if (InObject->IsA(UDataTable::StaticClass())) + { + return EHoudiniInputObjectType::DataTable; + } + else + { + return EHoudiniInputObjectType::Object; + } + } + + return EHoudiniInputObjectType::Invalid; +} + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UHoudiniInputBrush +//----------------------------------------------------------------------------------------------------------------------------- + +FHoudiniBrushInfo::FHoudiniBrushInfo() + : CachedTransform() + , CachedOrigin(ForceInitToZero) + , CachedExtent(ForceInitToZero) + , CachedBrushType(EBrushType::Brush_Default) + , CachedSurfaceHash(0) +{ +} + +FHoudiniBrushInfo::FHoudiniBrushInfo(ABrush* InBrushActor) +{ + if (!InBrushActor) + return; + + BrushActor = InBrushActor; + CachedTransform = BrushActor->GetActorTransform(); + BrushActor->GetActorBounds(false, CachedOrigin, CachedExtent); + CachedBrushType = BrushActor->BrushType; + +#if WITH_EDITOR + UModel* Model = BrushActor->Brush; + + // Cache the hash of the surface properties + if (IsValid(Model) && IsValid(Model->Polys)) + { + int32 NumPolys = Model->Polys->Element.Num(); + CachedSurfaceHash = 0; + for(int32 iPoly = 0; iPoly < NumPolys; ++iPoly) + { + const FPoly& Poly = Model->Polys->Element[iPoly]; + CombinePolyHash(CachedSurfaceHash, Poly); + } + } + else + { + CachedSurfaceHash = 0; + } +#endif +} + +bool FHoudiniBrushInfo::HasChanged() const +{ + if (!BrushActor.IsValid()) + return false; + + // Has the transform changed? + if (!BrushActor->GetActorTransform().Equals(CachedTransform)) + return true; + + if (BrushActor->BrushType != CachedBrushType) + return true; + + // Has the actor bounds changed? + FVector TmpOrigin, TmpExtent; + BrushActor->GetActorBounds(false, TmpOrigin, TmpExtent); + + if (!(TmpOrigin.Equals(CachedOrigin) && TmpExtent.Equals(CachedExtent) )) + return true; +#if WITH_EDITOR + // Is there a tracked surface property that changed? + UModel* Model = BrushActor->Brush; + if (IsValid(Model) && IsValid(Model->Polys)) + { + // Hash the incoming surface properties and compared it against the cached hash. + int32 NumPolys = Model->Polys->Element.Num(); + uint64 SurfaceHash = 0; + for (int32 iPoly = 0; iPoly < NumPolys; ++iPoly) + { + const FPoly& Poly = Model->Polys->Element[iPoly]; + CombinePolyHash(SurfaceHash, Poly); + } + if (SurfaceHash != CachedSurfaceHash) + return true; + } + else + { + if (CachedSurfaceHash != 0) + return true; + } +#endif + return false; +} + +int32 FHoudiniBrushInfo::GetNumVertexIndicesFromModel(const UModel* Model) +{ + const TArray& Nodes = Model->Nodes; + int32 NumIndices = 0; + // Build the face counts buffer by iterating over the BSP nodes. + for(const FBspNode& Node : Nodes) + { + NumIndices += Node.NumVertices; + } + return NumIndices; +} + +UModel* UHoudiniInputBrush::GetCachedModel() const +{ + return CombinedModel; +} + +bool UHoudiniInputBrush::HasBrushesChanged(const TArray& InBrushes) const +{ + if (InBrushes.Num() != BrushesInfo.Num()) + return true; + + int32 NumBrushes = BrushesInfo.Num(); + + for (int32 InfoIndex = 0; InfoIndex < NumBrushes; ++InfoIndex) + { + const FHoudiniBrushInfo& BrushInfo = BrushesInfo[InfoIndex]; + // Has the cached brush actor invalid? + if (!BrushInfo.BrushActor.IsValid()) + return true; + + // Has there been an order change in the actors list? + if (InBrushes[InfoIndex] != BrushInfo.BrushActor.Get()) + return true; + + // Has there been any other changes to the brush? + if (BrushInfo.HasChanged()) + return true; + } + + // Nothing has changed. + return false; +} + +void UHoudiniInputBrush::UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes) +{ + ABrush* InputBrush = GetBrush(); + if (IsValid(InputBrush)) + { + CachedInputBrushType = InputBrush->BrushType; + } + + // Cache the combined model aswell as the brushes used to generate this model. + CombinedModel = InCombinedModel; + + BrushesInfo.SetNumUninitialized(InBrushes.Num()); + for (int i = 0; i < InBrushes.Num(); ++i) + { + if (!InBrushes[i]) + continue; + BrushesInfo[i] = FHoudiniBrushInfo(InBrushes[i]); + } +} + + +void +UHoudiniInputBrush::Update(UObject * InObject) +{ + Super::Update(InObject); + + ABrush* BrushActor = GetBrush(); + if (!IsValid(BrushActor)) + { + bIgnoreInputObject = true; + return; + } + + CachedInputBrushType = BrushActor->BrushType; + + bIgnoreInputObject = ShouldIgnoreThisInput(); +} + +bool +UHoudiniInputBrush::ShouldIgnoreThisInput() +{ + // Invalid brush, should be ignored + ABrush* BrushActor = GetBrush(); + ensure(BrushActor); + if (!BrushActor) + return true; + + // If the BrushType has changed since caching this object, this object cannot be ignored. + if (CachedInputBrushType != BrushActor->BrushType) + return false; + + // If it's not an additive brush, we want to ignore it + bool bShouldBeIgnored = BrushActor->BrushType != EBrushType::Brush_Add; + + // If this is not a static brush (e.g., AVolume), ignore it. + if (!bShouldBeIgnored) + bShouldBeIgnored = !BrushActor->IsStaticBrush(); + + return bShouldBeIgnored; +} + +bool UHoudiniInputBrush::HasContentChanged() const +{ + ABrush* BrushActor = GetBrush(); + ensure(BrushActor); + + if (!BrushActor) + return false; + + if (BrushActor->BrushType != CachedInputBrushType) + return true; + + if (bIgnoreInputObject) + return false; + + // Find intersecting actors and capture their properties so that + // we can determine whether something has changed. + TArray IntersectingBrushes; + FindIntersectingSubtractiveBrushes(this, IntersectingBrushes); + + if (HasBrushesChanged(IntersectingBrushes)) + { + return true; + } + + return false; +} + +bool +UHoudiniInputBrush::HasActorTransformChanged() const +{ + if (bIgnoreInputObject) + return false; + + return Super::HasActorTransformChanged(); +} + + +bool UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes) +{ + TArray IntersectingActors; + TArray Bounds; + + + if (!IsValid(InputBrush)) + return false; + + ABrush* BrushActor = InputBrush->GetBrush(); + if (!IsValid(BrushActor)) + return false; + + + OutBrushes.Empty(); + + Bounds.Add( BrushActor->GetComponentsBoundingBox(true, true) ); + + FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(BrushActor->GetWorld(), ABrush::StaticClass(), Bounds, nullptr, IntersectingActors); + + //-------------------------------------------------------------------------------------------------- + // Filter the actors to only keep intersecting subtractive brushes. + //-------------------------------------------------------------------------------------------------- + for (AActor* Actor : IntersectingActors) + { + // Filter out anything that is not a static brush (typically volume actors). + ABrush* Brush = Cast(Actor); + + // NOTE: The brush actor needs to be added in the correct map/level order + // together with the subtractive brushes otherwise the CSG operations + // will not match the BSP in the level. + if (Actor == BrushActor) + OutBrushes.Add(Brush); + + if (!(Brush && Brush->IsStaticBrush())) + continue; + + if (Brush->BrushType == Brush_Subtract) + OutBrushes.Add(Brush); + } + + return true; +} + +#if WITH_EDITOR +void +UHoudiniInputObject::PostEditUndo() +{ + Super::PostEditUndo(); + MarkChanged(true); +} +#endif + +UHoudiniInputObject* +UHoudiniInputObject::DuplicateAndCopyState(UObject * DestOuter) +{ + UHoudiniInputObject* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); + NewInput->CopyStateFrom(this, false); + return NewInput; +} + +void +UHoudiniInputObject::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) +{ + // Copy the state of this UHoudiniInput object. + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References should be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); + } + + InputNodeId = InInput->InputNodeId; + InputObjectNodeId = InInput->InputObjectNodeId; + bHasChanged = InInput->bHasChanged; + bNeedsToTriggerUpdate = InInput->bNeedsToTriggerUpdate; + bTransformChanged = InInput->bTransformChanged; + Guid = InInput->Guid; + +#if WITH_EDITORONLY_DATA + bUniformScaleLocked = InInput->bUniformScaleLocked; +#endif + +} + +void +UHoudiniInputObject::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; +} + +bool +UHoudiniInputObject::GetChangedObjectsAndValidNodes(TArray& OutChangedObjects, TArray& OutNodeIdsOfUnchangedValidObjects) +{ + if (HasChanged()) + { + // Has changed, needs to be recreated + OutChangedObjects.Add(this); + + return true; + } + + if (UsesInputObjectNode()) + { + if (InputObjectNodeId >= 0) + { + // No changes, keep it + OutNodeIdsOfUnchangedValidObjects.Add(InputObjectNodeId); + } + else + { + // Needs, but does not have, a current HAPI input node + OutChangedObjects.Add(this); + + return true; + } + } + + // No changes and valid object node exists (or no node is used by this object type) + return false; +} + +// +UHoudiniInputDataTable::UHoudiniInputDataTable(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +UHoudiniInputObject * +UHoudiniInputDataTable::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_DT_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputDataTable::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputDataTable * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputDataTable::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::DataTable; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UDataTable* +UHoudiniInputDataTable::GetDataTable() const +{ + return Cast(InputObject.LoadSynchronous()); +} + +UHoudiniInputFoliageType_InstancedStaticMesh::UHoudiniInputFoliageType_InstancedStaticMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +UHoudiniInputObject* +UHoudiniInputFoliageType_InstancedStaticMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_FoliageSM_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputFoliageType_InstancedStaticMesh::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputFoliageType_InstancedStaticMesh * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputFoliageType_InstancedStaticMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::FoliageType_InstancedStaticMesh; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +void +UHoudiniInputFoliageType_InstancedStaticMesh::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) +{ + UHoudiniInputFoliageType_InstancedStaticMesh* FoliageTypeSM = Cast(InInput); + if (!IsValid(FoliageTypeSM)) + return; + + UHoudiniInputObject::CopyStateFrom(FoliageTypeSM, bCopyAllProperties); + + // BlueprintStaticMeshes array is not used in UHoudiniInputFoliageType_InstancedStaticMesh + BlueprintStaticMeshes.Empty(); +} + +void +UHoudiniInputFoliageType_InstancedStaticMesh::Update(UObject * InObject) +{ + UHoudiniInputObject::Update(InObject); + UFoliageType_InstancedStaticMesh* const FoliageType = Cast(InObject); + ensure(FoliageType); + ensure(FoliageType->GetStaticMesh()); +} + +UStaticMesh* +UHoudiniInputFoliageType_InstancedStaticMesh::GetStaticMesh() const +{ + if (!InputObject.IsValid()) + return nullptr; + + UFoliageType_InstancedStaticMesh* const FoliageType = Cast(InputObject.LoadSynchronous()); + if (!IsValid(FoliageType)) + return nullptr; + + return FoliageType->GetStaticMesh(); } diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h index f3afb1168..9b40c50a5 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -78,6 +78,7 @@ enum class EHoudiniInputObjectType : uint8 CameraComponent, DataTable, HoudiniAssetActor, + FoliageType_InstancedStaticMesh, }; //----------------------------------------------------------------------------------------------------------------------------- @@ -127,6 +128,9 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputObject : public UObject void SetImportAsReference(const bool& bInImportAsRef) { bImportAsReference = bInImportAsRef; }; bool GetImportAsReference() const { return bImportAsReference; }; + void SetImportAsReferenceRotScaleEnabled(const bool& bInImportAsRefRotScaleEnabled) { bImportAsReferenceRotScaleEnabled = bInImportAsRefRotScaleEnabled; }; + bool GetImportAsReferenceRotScaleEnabled() const { return bImportAsReferenceRotScaleEnabled; }; + #if WITH_EDITOR void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; bool IsUniformScaleLocked() const { return bUniformScaleLocked; }; @@ -143,9 +147,24 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputObject : public UObject FGuid GetInputGuid() const { return Guid; } + /** + * Populate OutChangedObjects with any output objects (this object and its children if it has any) that has changed + * or have invalid HAPI node ids. + * Any objects that have not changed and have valid HAPI node ids have their node ids added to + * OutNodeIdsOfUnchangedValidObjects. + * + * @return true if this object, or any of its child objects, were added to OutChangedObjects. + */ + virtual bool GetChangedObjectsAndValidNodes(TArray& OutChangedObjects, TArray& OutNodeIdsOfUnchangedValidObjects); protected: + /** + * Returns true if this object type uses an input object (OBJ) node. This is not a recursive check, for objects + * with child objects (such as an actor with components), each child input object must be checked as well. + */ + virtual bool UsesInputObjectNode() const { return true; } + virtual void BeginDestroy() override; public: @@ -193,6 +212,9 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputObject : public UObject UPROPERTY() bool bImportAsReference; + UPROPERTY() + bool bImportAsReferenceRotScaleEnabled; + // Indicates if change the scale of Transfrom Offset of this object uniformly #if WITH_EDITORONLY_DATA UPROPERTY(Transient, DuplicateTransient, NonTransactional) @@ -230,13 +252,13 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputStaticMesh : public UHoudiniInputObj // Nothing to add for Static Meshes? // StaticMesh accessor - class UStaticMesh* GetStaticMesh(); + virtual class UStaticMesh* GetStaticMesh() const; // Blueprint accessor - class UBlueprint* GetBlueprint(); + virtual class UBlueprint* GetBlueprint() const; // Check if this SM Input object is passed in as a BP - bool bIsBlueprint() const; + virtual bool bIsBlueprint() const; // The Blueprint's Static Meshe Components that can be sent as inputs UPROPERTY() @@ -402,17 +424,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputSplineComponent : public UHoudiniInp // USplineComponent accessor USplineComponent* GetSplineComponent(); - // Returns true if the attached spline component has been modified - bool HasSplineComponentChanged(float fCurrentSplineResolution) const; - - // Returns true if the attached actor's (parent) transform has been modified - virtual bool HasActorTransformChanged() const; - - // Returns true if the attached component's transform has been modified - virtual bool HasComponentTransformChanged() const; - // Return true if the component itself has been modified - virtual bool HasComponentChanged() const; + virtual bool HasComponentChanged() const override; public: @@ -518,14 +531,21 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputCameraComponent : public UHoudiniInp public: + UPROPERTY() float FOV; + + UPROPERTY() float AspectRatio; + UPROPERTY() //TEnumAsByte ProjectionType; bool bIsOrthographic; + UPROPERTY() float OrthoWidth; + UPROPERTY() float OrthoNearClipPlane; + UPROPERTY() float OrthoFarClipPlane; }; @@ -574,8 +594,16 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputActor : public UHoudiniInputObject // virtual void Update(UObject * InObject) override; - // - virtual bool HasActorTransformChanged(); + // Check whether the actor transform, or any of its components have transform changes. + virtual bool HasActorTransformChanged() const; + +protected: + virtual bool HasRootComponentTransformChanged() const; + virtual bool HasComponentsTransformChanged() const; + +public: + // + virtual bool ShouldTrackComponent(UActorComponent* InComponent) { return true; } // Return true if any content of this actor has possibly changed (for example geometry edits on a // Brush or changes on procedurally generated content). @@ -583,13 +611,46 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputActor : public UHoudiniInputObject virtual bool HasContentChanged() const; // AActor accessor - AActor* GetActor(); + AActor* GetActor() const; -public: + const TArray& GetActorComponents() const { return ActorComponents; } + + // The number of components added with the last call to Update + int32 GetLastUpdateNumComponentsAdded() const { return LastUpdateNumComponentsAdded; } + // The number of components remove with the last call to Update + int32 GetLastUpdateNumComponentsRemoved() const { return LastUpdateNumComponentsRemoved; } + + /** + * Populate OutChangedObjects with any output objects (this object and its children if it has any) that has changed + * or have invalid HAPI node ids. + * Any objects that have not changed and have valid HAPI node is have their node ids added to + * OutNodeIdsOfUnchangedValidObjects. + * + * @return true if this object, or any of its child objects, were added to OutChangedObjects. + */ + virtual bool GetChangedObjectsAndValidNodes(TArray& OutChangedObjects, TArray& OutNodeIdsOfUnchangedValidObjects) override; + + virtual void InvalidateData() override; + +protected: + + virtual bool UsesInputObjectNode() const override { return false; } // The actor's components that can be sent as inputs UPROPERTY() TArray ActorComponents; + + // The USceneComponents the actor had the last time we called Update (matches the ones in ActorComponents). + UPROPERTY() + TSet> ActorSceneComponents; + + // The number of components added with the last call to Update + UPROPERTY() + int32 LastUpdateNumComponentsAdded; + + // The number of components remove with the last call to Update + UPROPERTY() + int32 LastUpdateNumComponentsRemoved; }; @@ -608,9 +669,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputLandscape : public UHoudiniInputActo static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); // + virtual void Update(UObject * InObject) override; - virtual bool HasActorTransformChanged() override; + virtual bool ShouldTrackComponent(UActorComponent* InComponent) override; // ALandscapeProxy accessor ALandscapeProxy* GetLandscapeProxy(); @@ -620,6 +682,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputLandscape : public UHoudiniInputActo // Used to restore an input landscape's transform to its original state UPROPERTY() FTransform CachedInputLandscapeTraqnsform; + +protected: + virtual bool UsesInputObjectNode() const override { return true; } + }; @@ -712,7 +778,8 @@ struct FHoudiniBrushInfo HashCombine(Hash, Poly.TextureU); HashCombine(Hash, Poly.TextureV); HashCombine(Hash, Poly.Normal); - HashCombine(Hash, (uint64)(Poly.Material)); + // Do not add addresses to the hash, otherwise it would force a recook every unreal session! + // HashCombine(Hash, (uint64)(Poly.Material)); } }; @@ -742,8 +809,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputBrush : public UHoudiniInputActor // Indicates if this input has changed and should be updated virtual bool HasTransformChanged() const override { return (!bIgnoreInputObject) && bTransformChanged; }; - - virtual bool HasActorTransformChanged() override; + + virtual bool HasActorTransformChanged() const override; virtual bool NeedsToTriggerUpdate() const override { return (!bIgnoreInputObject) && bNeedsToTriggerUpdate; }; @@ -773,6 +840,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputBrush : public UHoudiniInputActor static bool FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes); protected: + virtual bool UsesInputObjectNode() const override { return true; } + UPROPERTY() TArray BrushesInfo; @@ -802,4 +871,33 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputDataTable : public UHoudiniInputObje // DataTable accessor class UDataTable* GetDataTable() const; -}; \ No newline at end of file +}; + +//----------------------------------------------------------------------------------------------------------------------------- +// UFoliageType_InstancedStaticMesh input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputFoliageType_InstancedStaticMesh : public UHoudiniInputStaticMesh +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // UHoudiniInputObject overrides + + // virtual void DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputObject*& OutNewObject) override; + virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) override; + + // + virtual void Update(UObject * InObject) override; + + // StaticMesh accessor + virtual class UStaticMesh* GetStaticMesh() const override; + + virtual class UBlueprint* GetBlueprint() const override { return nullptr; } + + virtual bool bIsBlueprint() const override { return false; } +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h b/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h index 0242d0ba1..d9b7903a9 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,20 +28,8 @@ #include "CoreMinimal.h" #include "UObject/ObjectMacros.h" +#include "HoudiniEngineRuntimeCommon.h" -UENUM() -enum class EHoudiniInputType : uint8 -{ - Invalid, - - Geometry, - Curve, - Asset, - Landscape, - World, - Skeletal, - -}; // Maintain an iterable list of houdini input types static const EHoudiniInputType HoudiniInputTypeList[] = { EHoudiniInputType::Geometry, @@ -59,10 +47,3 @@ enum class EHoudiniXformType : uint8 Auto }; -UENUM() -enum class EHoudiniLandscapeExportType : uint8 -{ - Heightfield, - Mesh, - Points -}; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp index 9db53ec8e..f5ec4c082 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp @@ -1,24 +1,27 @@ /* -* Copyright (c) <2017> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. * -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: * -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. * -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. * +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "HoudiniInstancedActorComponent.h" @@ -120,9 +123,9 @@ void UHoudiniInstancedActorComponent::AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector ) { UHoudiniInstancedActorComponent * ThisHIAC = Cast< UHoudiniInstancedActorComponent >(InThis); - if ( ThisHIAC && !ThisHIAC->IsPendingKill() ) + if ( IsValid(ThisHIAC) ) { - if ( ThisHIAC->InstancedObject && !ThisHIAC->InstancedObject->IsPendingKill() ) + if ( IsValid(ThisHIAC->InstancedObject) ) Collector.AddReferencedObject( ThisHIAC->InstancedObject, ThisHIAC ); Collector.AddReferencedObjects(ThisHIAC->InstancedActors, ThisHIAC ); @@ -133,7 +136,7 @@ UHoudiniInstancedActorComponent::AddReferencedObjects(UObject * InThis, FReferen int32 UHoudiniInstancedActorComponent::AddInstance(const FTransform& InstanceTransform, AActor * NewActor) { - if (!NewActor || NewActor->IsPendingKill()) + if (!IsValid(NewActor)) return -1; NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); @@ -145,7 +148,7 @@ UHoudiniInstancedActorComponent::AddInstance(const FTransform& InstanceTransform bool UHoudiniInstancedActorComponent::SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor) { - if (!NewActor || NewActor->IsPendingKill()) + if (!IsValid(NewActor)) return false; if (!InstancedActors.IsValidIndex(Idx)) @@ -178,7 +181,7 @@ UHoudiniInstancedActorComponent::ClearAllInstances() { for ( AActor* Instance : InstancedActors ) { - if ( Instance && !Instance->IsPendingKill() ) + if ( IsValid(Instance) ) Instance->Destroy(); } InstancedActors.Empty(); @@ -196,7 +199,7 @@ UHoudiniInstancedActorComponent::SetNumberOfInstances(const int32& NewInstanceNu for (int32 Idx = NewInstanceNum - 1; Idx < InstancedActors.Num(); Idx++) { AActor* Instance = InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; - if (Instance && !Instance->IsPendingKill()) + if (IsValid(Instance)) Instance->Destroy(); } } @@ -215,7 +218,7 @@ UHoudiniInstancedActorComponent::OnComponentCreated() bool bNeedDuplicate = false; for (auto CurrentInstance : InstancedActors) { - if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) + if ( !IsValid(CurrentInstance) ) continue; if ( CurrentInstance->GetAttachParentActor() != GetOwner() ) @@ -231,7 +234,7 @@ UHoudiniInstancedActorComponent::OnComponentCreated() InstancedActors.Empty(); for (AActor* CurrentInstance : SourceInstances) { - if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) + if ( !IsValid(CurrentInstance) ) continue; FTransform InstanceTransform; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h index 707b60f87..32a61b696 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.cpp deleted file mode 100644 index c5fdea932..000000000 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp index dfc703863..8eb06bb17 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp @@ -1,24 +1,27 @@ /* -* Copyright (c) <2017> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. * -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: * -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. * -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. * +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "HoudiniMeshSplitInstancerComponent.h" @@ -118,7 +121,7 @@ void UHoudiniMeshSplitInstancerComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) { UHoudiniMeshSplitInstancerComponent * ThisMSIC = Cast< UHoudiniMeshSplitInstancerComponent >(InThis); - if ( ThisMSIC && !ThisMSIC->IsPendingKill() ) + if ( IsValid(ThisMSIC) ) { Collector.AddReferencedObject(ThisMSIC->InstancedMesh, ThisMSIC); for(auto& Mat : ThisMSIC->OverrideMaterials) @@ -134,14 +137,14 @@ UHoudiniMeshSplitInstancerComponent::SetInstanceTransforms( if (Instances.Num() <= 0 && InstanceTransforms.Num() <= 0) return false; - if (!GetOwner() || GetOwner()->IsPendingKill()) + if (!IsValid(GetOwner())) return false; // Destroy previous instances while keeping some of the one that we'll be able to reuse ClearInstances(InstanceTransforms.Num()); // - if( !InstancedMesh || InstancedMesh->IsPendingKill() ) + if( !IsValid(InstancedMesh) ) { HOUDINI_LOG_ERROR(TEXT("%s: Null InstancedMesh for split instanced mesh override"), *GetOwner()->GetName()); return false; @@ -169,7 +172,7 @@ UHoudiniMeshSplitInstancerComponent::SetInstanceTransforms( UStaticMeshComponent* SMC = Instances[iIns]; const FTransform& InstanceTransform = InstanceTransforms[iIns]; - if (!SMC || SMC->IsPendingKill()) + if (!IsValid(SMC)) continue; SMC->SetRelativeTransform(InstanceTransform); @@ -191,9 +194,9 @@ UHoudiniMeshSplitInstancerComponent::SetInstanceTransforms( MI = OverrideMaterials[0]; } - if (MI && !MI->IsPendingKill()) + if (IsValid(MI)) { - int32 MeshMaterialCount = InstancedMesh->StaticMaterials.Num(); + int32 MeshMaterialCount = InstancedMesh->GetStaticMaterials().Num(); for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) SMC->SetMaterial(Idx, MI); } diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h index 2a4b4f31c..ec8f6d319 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -72,6 +72,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniMeshSplitInstancerComponent : public USce // const Instance accessor const TArray& GetInstances() const { return Instances; } + TArray GetOverrideMaterials() const { return OverrideMaterials; } + private: UPROPERTY(VisibleInstanceOnly, Category = Instances) diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp index 07fdbdee0..f0044bf00 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,7 +24,6 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - #include "HoudiniOutput.h" #include "HoudiniAssetComponent.h" @@ -36,6 +35,7 @@ #include "Components/SplineComponent.h" #include "Misc/StringFormatArg.h" #include "Engine/Engine.h" +#include "LandscapeLayerInfoObject.h" UHoudiniLandscapePtr::UHoudiniLandscapePtr(class FObjectInitializer const& Initializer) { @@ -264,6 +264,56 @@ FHoudiniOutputObjectIdentifier::Matches(const FHoudiniGeoPartObject& InHGPO) con } +// ---------------------------------------------------- +// FHoudiniBakedOutputObjectIdentifier +// ---------------------------------------------------- + + +FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier() +{ + PartId = -1; + SplitIdentifier = FString(); +} + +FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier( + const int32& InPartId, const FString& InSplitIdentifier) +{ + PartId = InPartId; + SplitIdentifier = InSplitIdentifier; +} + +FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + PartId = InIdentifier.PartId; + SplitIdentifier = InIdentifier.SplitIdentifier; +} + +uint32 +FHoudiniBakedOutputObjectIdentifier::GetTypeHash() const +{ + const int32 HashBuffer = PartId; + const int32 Hash = FCrc::MemCrc32((void *)&HashBuffer, sizeof(HashBuffer)); + return FCrc::StrCrc32(*SplitIdentifier, Hash); +} + +uint32 +GetTypeHash(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) +{ + return InIdentifier.GetTypeHash(); +} + +bool +FHoudiniBakedOutputObjectIdentifier::operator==(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) const +{ + return (InIdentifier.PartId == PartId && InIdentifier.SplitIdentifier.Equals(SplitIdentifier)); +} + + +// ---------------------------------------------------- +// FHoudiniBakedOutputObject +// ---------------------------------------------------- + + FHoudiniBakedOutputObject::FHoudiniBakedOutputObject() : Actor() , ActorBakeName(NAME_None) @@ -354,6 +404,28 @@ FHoudiniBakedOutputObject::GetBlueprintIfValid(bool bInTryLoad) const return Cast(Object); } +ULandscapeLayerInfoObject* +FHoudiniBakedOutputObject::GetLandscapeLayerInfoIfValid(const FName& InLayerName, const bool bInTryLoad) const +{ + if (!LandscapeLayers.Contains(InLayerName)) + return nullptr; + + const FString& LayerInfoPathStr = LandscapeLayers.FindChecked(InLayerName); + const FSoftObjectPath LayerInfoPath(LayerInfoPathStr); + + if (!LayerInfoPath.IsValid()) + return nullptr; + + UObject* Object = LayerInfoPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = LayerInfoPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Cast(Object); +} + UHoudiniOutput::UHoudiniOutput(const FObjectInitializer & ObjectInitializer) : Super(ObjectInitializer) @@ -409,7 +481,7 @@ UHoudiniOutput::GetBounds() const MeshComp = Cast(CurObj.OutputComponent); } - if (!MeshComp || MeshComp->IsPendingKill()) + if (!IsValid(MeshComp)) continue; BoxBounds += MeshComp->Bounds.GetBox(); @@ -423,11 +495,11 @@ UHoudiniOutput::GetBounds() const { const FHoudiniOutputObject& CurObj = CurPair.Value; UHoudiniLandscapePtr* CurLandscapeObj = Cast(CurObj.OutputObject); - if (!CurLandscapeObj || CurLandscapeObj->IsPendingKill()) + if (!IsValid(CurLandscapeObj)) continue; ALandscapeProxy* Landscape = Cast(CurLandscapeObj->GetRawPtr()); - if (!Landscape || Landscape->IsPendingKill()) + if (!IsValid(Landscape)) continue; FVector Origin, Extent; @@ -445,7 +517,7 @@ UHoudiniOutput::GetBounds() const { const FHoudiniOutputObject& CurObj = CurPair.Value; USceneComponent* InstancedComp = Cast(CurObj.OutputObject); - if (!InstancedComp || InstancedComp->IsPendingKill()) + if (!IsValid(InstancedComp)) continue; BoxBounds += InstancedComp->Bounds.GetBox(); @@ -459,7 +531,7 @@ UHoudiniOutput::GetBounds() const { const FHoudiniOutputObject& CurObj = CurPair.Value; UHoudiniSplineComponent* CurHoudiniSplineComp = Cast(CurObj.OutputComponent); - if (!CurHoudiniSplineComp || CurHoudiniSplineComp->IsPendingKill()) + if (!IsValid(CurHoudiniSplineComp)) continue; FBox CurCurveBound(ForceInitToZero); @@ -469,7 +541,7 @@ UHoudiniOutput::GetBounds() const } UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !OuterHAC->IsPendingKill()) + if (IsValid(OuterHAC)) BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); } @@ -496,18 +568,29 @@ UHoudiniOutput::Clear() for (auto& CurrentOutputObject : OutputObjects) { + UHoudiniSplineComponent* SplineComponent = Cast(CurrentOutputObject.Value.OutputComponent); + if (IsValid(SplineComponent)) + { + // The spline component is a special case where the output + // object as associated Houdini nodes (as input object). + // We can only explicitly remove those nodes when the output object gets + // removed. + SplineComponent->MarkInputNodesAsPendingKill(); + } + // Clear the output component USceneComponent* SceneComp = Cast(CurrentOutputObject.Value.OutputComponent); - if (SceneComp && !SceneComp->IsPendingKill()) + if (IsValid(SceneComp)) { SceneComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); SceneComp->UnregisterComponent(); SceneComp->DestroyComponent(); } + // Also destroy proxy components USceneComponent* ProxyComp = Cast(CurrentOutputObject.Value.ProxyComponent); - if (ProxyComp && !ProxyComp->IsPendingKill()) + if (IsValid(ProxyComp)) { ProxyComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); ProxyComp->UnregisterComponent(); @@ -520,7 +603,7 @@ UHoudiniOutput::Clear() // will result in a StaticFindObject() call which will raise an exception during GC. UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentOutputObject.Value.OutputObject); ALandscapeProxy* LandscapeProxy = LandscapePtr ? LandscapePtr->GetRawPtr() : nullptr; - if (LandscapeProxy && !LandscapeProxy->IsPendingKill()) + if (IsValid(LandscapeProxy)) { LandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); LandscapeProxy->ConditionalBeginDestroy(); @@ -537,6 +620,15 @@ UHoudiniOutput::Clear() Type = EHoudiniOutputType::Invalid; } +bool +UHoudiniOutput::ShouldDeferClear() const +{ + if (Type == EHoudiniOutputType::Landscape) + return true; + + return false; +} + const bool UHoudiniOutput::HasGeoChanged() const { @@ -613,9 +705,26 @@ UHoudiniOutput::HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool // We've specified if we want the name to match/to be different: // when looking in previous outputs, we want the name to match // when looking in newly created outputs, we want to be sure the names are different - bool bNameMatch = InHGPO.VolumeName.Equals(currentHGPO.VolumeName, ESearchCase::IgnoreCase); - if (bNameMatch != bVolumeNameShouldMatch) - continue; + if (bVolumeNameShouldMatch) + { + // HasEditLayers state should match. + if (!(InHGPO.bHasEditLayers == currentHGPO.bHasEditLayers)) + { + continue; + } + + // If we have edit layers, ensure the names match + if (InHGPO.bHasEditLayers && !InHGPO.VolumeLayerName.Equals(currentHGPO.VolumeLayerName, ESearchCase::IgnoreCase)) + { + continue; + } + + // Check whether the volume name match. + if (!(InHGPO.VolumeName.Equals(currentHGPO.VolumeName, ESearchCase::IgnoreCase))) + { + continue; + } + } return true; } @@ -803,7 +912,7 @@ UHoudiniOutput::HasAnyProxy() const for (const auto& Pair : OutputObjects) { UObject* FoundProxy = Pair.Value.ProxyObject; - if (FoundProxy && !FoundProxy->IsPendingKill()) + if (IsValid(FoundProxy)) { return true; } @@ -820,7 +929,7 @@ UHoudiniOutput::HasProxy(const FHoudiniOutputObjectIdentifier& InIdentifier) con return false; UObject* FoundProxy = FoundOutputObject->ProxyObject; - if (!FoundProxy || FoundProxy->IsPendingKill()) + if (!IsValid(FoundProxy)) return false; return true; @@ -832,7 +941,7 @@ UHoudiniOutput::HasAnyCurrentProxy() const for (const auto& Pair : OutputObjects) { UObject* FoundProxy = Pair.Value.ProxyObject; - if (FoundProxy && !FoundProxy->IsPendingKill()) + if (IsValid(FoundProxy)) { if(Pair.Value.bProxyIsCurrent) { diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h index b61e071f4..52449abe3 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,6 +28,8 @@ #include "CoreMinimal.h" #include "HoudiniGeoPartObject.h" +#include "HoudiniEngineRuntimeCommon.h" + #include "LandscapeProxy.h" #include "Misc/StringFormatArg.h" #include "UObject/SoftObjectPtr.h" @@ -35,18 +37,8 @@ #include "HoudiniOutput.generated.h" class UMaterialInterface; +class ULandscapeLayerInfoObject; -UENUM() -enum class EHoudiniOutputType : uint8 -{ - Invalid, - - Mesh, - Instancer, - Landscape, - Curve, - Skeletal -}; UENUM() enum class EHoudiniCurveOutputType : uint8 @@ -55,15 +47,6 @@ enum class EHoudiniCurveOutputType : uint8 HoudiniSpline, }; -UENUM() -enum class EHoudiniLandscapeOutputBakeType : uint8 -{ - Detachment, - BakeToImage, - BakeToWorld, - InValid, -}; - USTRUCT() struct HOUDINIENGINERUNTIME_API FHoudiniCurveOutputProperties { @@ -116,6 +99,37 @@ class HOUDINIENGINERUNTIME_API UHoudiniLandscapePtr : public UObject UPROPERTY() EHoudiniLandscapeOutputBakeType BakeType; + + // Edit layer to which this output corresponds, if applicable. + UPROPERTY() + FName EditLayerName; +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniLandscapeEditLayer : public UObject +{ + GENERATED_BODY() + +public: + FORCEINLINE + void SetSoftPtr(TSoftObjectPtr InSoftPtr) { LandscapeSoftPtr = InSoftPtr; }; + + FORCEINLINE + TSoftObjectPtr GetSoftPtr() const { return LandscapeSoftPtr; }; + + // Calling Get() during GC will raise an exception because Get calls StaticFindObject. + FORCEINLINE + ALandscapeProxy* GetRawPtr() { return !IsGarbageCollecting() ? Cast(LandscapeSoftPtr.Get()) : nullptr; }; + + FORCEINLINE + FString GetSoftPtrPath() const { return LandscapeSoftPtr.ToSoftObjectPath().ToString(); }; + + UPROPERTY() + TSoftObjectPtr LandscapeSoftPtr; + + UPROPERTY() + FString LayerName; }; @@ -175,6 +189,37 @@ struct HOUDINIENGINERUNTIME_API FHoudiniOutputObjectIdentifier /** Function used by hashing containers to create a unique hash for this type of object. **/ HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier); +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObjectIdentifier +{ + GENERATED_USTRUCT_BODY() + +public: + // Constructors + FHoudiniBakedOutputObjectIdentifier(); + FHoudiniBakedOutputObjectIdentifier(const int32& InPartId, const FString& InSplitIdentifier); + FHoudiniBakedOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier); + + // Return hash value for this object, used when using this object as a key inside hashing containers. + uint32 GetTypeHash() const; + + // Comparison operator, used by hashing containers. + bool operator==(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) const; + +public: + + // PartId + UPROPERTY() + int32 PartId = -1; + + // String identifier for the split that created this + UPROPERTY() + FString SplitIdentifier = FString(); +}; + +/** Function used by hashing containers to create a unique hash for this type of object. **/ +HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniBakedOutputObjectIdentifier& InIdentifier); + USTRUCT() struct HOUDINIENGINERUNTIME_API FHoudiniInstancedOutput { @@ -224,6 +269,10 @@ struct HOUDINIENGINERUNTIME_API FHoudiniInstancedOutput UPROPERTY() TArray TransformVariationIndices; + // Original Indices of the variation instances + UPROPERTY() + TArray OriginalInstanceIndices; + // Indicates this instanced output's component should be recreated UPROPERTY() bool bChanged = false; @@ -263,6 +312,9 @@ struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObject // Returns Blueprint if valid, otherwise nullptr UBlueprint* GetBlueprintIfValid(bool bInTryLoad=true) const; + // Returns the ULandscapeLayerInfoObject, if valid and found in LandscapeLayers, otherwise nullptr + ULandscapeLayerInfoObject* GetLandscapeLayerInfoIfValid(const FName& InLayerName, const bool bInTryLoad=true) const; + // The actor that the baked output was associated with UPROPERTY() FString Actor; @@ -290,6 +342,10 @@ struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObject // In the case of mesh split instancer baking: this is the array of instance components UPROPERTY() TArray InstancedComponents; + + // For landscapes this is the previously bake layer info assets (layer name as key, soft object path as value) + UPROPERTY() + TMap LandscapeLayers; }; // Container to hold the map of baked objects. There should be one of @@ -303,7 +359,7 @@ struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutput public: UPROPERTY() - TMap BakedOutputObjects; + TMap BakedOutputObjects; }; USTRUCT() @@ -335,6 +391,10 @@ struct HOUDINIENGINERUNTIME_API FHoudiniOutputObject UPROPERTY() bool bProxyIsCurrent = false; + // Implicit output objects shouldn't be created as actors / components in the scene. + UPROPERTY() + bool bIsImplicit = false; + // Bake Name override for this output object UPROPERTY() FString BakeName; @@ -367,6 +427,7 @@ struct HOUDINIENGINERUNTIME_API FHoudiniOutputObject TMap CachedTokens; }; + UCLASS() class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject { @@ -376,6 +437,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject // and access our HGPO and Output objects friend struct FHoudiniMeshTranslator; friend struct FHoudiniInstanceTranslator; + friend struct FHoudiniOutputTranslator; virtual ~UHoudiniOutput(); @@ -496,6 +558,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject void Clear(); + bool ShouldDeferClear() const; + protected: virtual void BeginDestroy() override; @@ -523,6 +587,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject UPROPERTY() TMap AssignementMaterials; + // The material replacements for this output UPROPERTY() TMap ReplacementMaterials; @@ -547,7 +612,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject bool bIsEditableNode; // An editable node is only built once. This flag indicates whether this node has been built. - UPROPERTY(DuplicateTransient) + // Transient, so resets every unreal session so curves must be rebuilt to work properly. + UPROPERTY(Transient, DuplicateTransient) bool bHasEditableNodeBuilt; // The IsUpdating flag is set to true when this out exists and is being updated. diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp index fb868672b..a214e4678 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,6 +34,7 @@ #include "Engine/StaticMesh.h" #include "GameFramework/Actor.h" #include "Landscape.h" +#include "UObject/MetaData.h" #include "Components/HierarchicalInstancedStaticMeshComponent.h" #include "InstancedFoliageActor.h" @@ -70,7 +71,7 @@ UHoudiniPDGAssetLink::UHoudiniPDGAssetLink(const FObjectInitializer& ObjectIniti PDGBakeSelectionOption = EPDGBakeSelectionOption::All; PDGBakePackageReplaceMode = EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; bRecenterBakedActors = false; - bBakeAfterWorkResultObjectLoaded = false; + bBakeAfterAllWorkResultObjectsLoaded = false; #endif // Folder used for baking PDG outputs @@ -86,6 +87,8 @@ FTOPWorkResultObject::FTOPWorkResultObject() Name = FString(); FilePath = FString(); State = EPDGWorkResultState::None; + WorkItemResultInfoIndex = INDEX_NONE; + bAutoBakedSinceLastLoad = false; } FTOPWorkResultObject::~FTOPWorkResultObject() @@ -116,8 +119,184 @@ FTOPWorkResult::operator==(const FTOPWorkResult& OtherWorkResult) const return true; } +void +FTOPWorkResult::ClearAndDestroyResultObjects(const FGuid& InHoudiniComponentGuid) +{ + if (ResultObjects.Num() <= 0) + return; + + for (FTOPWorkResultObject& ResultObject : ResultObjects) + { + ResultObject.DestroyResultOutputsAndRemoveOutputActor(InHoudiniComponentGuid); + } + + ResultObjects.Empty(); +} + +int32 +FTOPWorkResult::IndexOfWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex) +{ + const int32 NumEntries = ResultObjects.Num(); + for (int32 Index = 0; Index < NumEntries; ++Index) + { + const FTOPWorkResultObject& CurResultObject = ResultObjects[Index]; + if (CurResultObject.WorkItemResultInfoIndex == InWorkItemResultInfoIndex) + { + return Index; + } + } + + return INDEX_NONE; +} + +FTOPWorkResultObject* +FTOPWorkResult::GetWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex) +{ + const int32 ArrayIndex = IndexOfWorkResultObjectByHAPIResultInfoIndex(InWorkItemResultInfoIndex); + if (ArrayIndex == INDEX_NONE) + { + return nullptr; + } + + return GetWorkResultObjectByArrayIndex(ArrayIndex); +} + +FTOPWorkResultObject* +FTOPWorkResult::GetWorkResultObjectByArrayIndex(const int32& InArrayIndex) +{ + if (!ResultObjects.IsValidIndex(InArrayIndex)) + { + return nullptr; + } + + return &ResultObjects[InArrayIndex]; +} + + +FWorkItemTallyBase::~FWorkItemTallyBase() +{ +} + +bool +FWorkItemTallyBase::AreAllWorkItemsComplete() const +{ + return ( + NumWaitingWorkItems() == 0 && NumCookingWorkItems() == 0 && NumScheduledWorkItems() == 0 + && (NumWorkItems() == (NumCookedWorkItems() + NumErroredWorkItems())) ); +} + +bool +FWorkItemTallyBase::AnyWorkItemsFailed() const +{ + return NumErroredWorkItems() > 0; +} + +bool +FWorkItemTallyBase::AnyWorkItemsPending() const +{ + return (NumWorkItems() > 0 && (NumWaitingWorkItems() > 0 || NumCookingWorkItems() > 0 || NumScheduledWorkItems() > 0)); +} + +FString +FWorkItemTallyBase::ProgressRatio() const +{ + const float Ratio = NumWorkItems() > 0 ? (NumCookedWorkItems() / NumWorkItems()) * 100.f : 0; + + return FString::Printf(TEXT("%.1f%%"), Ratio); +} + FWorkItemTally::FWorkItemTally() +{ + AllWorkItems.Empty(); + WaitingWorkItems.Empty(); + ScheduledWorkItems.Empty(); + CookingWorkItems.Empty(); + CookedWorkItems.Empty(); + ErroredWorkItems.Empty(); + CookCancelledWorkItems.Empty(); +} + +void +FWorkItemTally::ZeroAll() +{ + AllWorkItems.Empty(); + WaitingWorkItems.Empty(); + ScheduledWorkItems.Empty(); + CookingWorkItems.Empty(); + CookedWorkItems.Empty(); + ErroredWorkItems.Empty(); + CookCancelledWorkItems.Empty(); +} + +void +FWorkItemTally::RemoveWorkItem(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + AllWorkItems.Remove(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsWaiting(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + WaitingWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsScheduled(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + ScheduledWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsCooking(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + CookingWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsCooked(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + CookedWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsErrored(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + ErroredWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsCookCancelled(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + CookCancelledWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RemoveWorkItemFromAllStateSets(int32 InWorkItemID) +{ + WaitingWorkItems.Remove(InWorkItemID); + ScheduledWorkItems.Remove(InWorkItemID); + CookingWorkItems.Remove(InWorkItemID); + CookedWorkItems.Remove(InWorkItemID); + ErroredWorkItems.Remove(InWorkItemID); + CookCancelledWorkItems.Remove(InWorkItemID); +} + + +FAggregatedWorkItemTally::FAggregatedWorkItemTally() { TotalWorkItems = 0; WaitingWorkItems = 0; @@ -125,10 +304,11 @@ FWorkItemTally::FWorkItemTally() CookingWorkItems = 0; CookedWorkItems = 0; ErroredWorkItems = 0; + CookCancelledWorkItems = 0; } void -FWorkItemTally::ZeroAll() +FAggregatedWorkItemTally::ZeroAll() { TotalWorkItems = 0; WaitingWorkItems = 0; @@ -136,34 +316,31 @@ FWorkItemTally::ZeroAll() CookingWorkItems = 0; CookedWorkItems = 0; ErroredWorkItems = 0; + CookCancelledWorkItems = 0; } -bool -FWorkItemTally::AreAllWorkItemsComplete() const -{ - return ( - WaitingWorkItems == 0 && CookingWorkItems == 0 && ScheduledWorkItems == 0 - && (TotalWorkItems == (CookedWorkItems + ErroredWorkItems)) ); -} - -bool -FWorkItemTally::AnyWorkItemsFailed() const -{ - return ErroredWorkItems > 0; -} - -bool -FWorkItemTally::AnyWorkItemsPending() const +void +FAggregatedWorkItemTally::Add(const FWorkItemTallyBase& InWorkItemTally) { - return (TotalWorkItems > 0 && (WaitingWorkItems > 0 || CookingWorkItems > 0 || ScheduledWorkItems > 0)); + TotalWorkItems += InWorkItemTally.NumWorkItems(); + WaitingWorkItems += InWorkItemTally.NumWaitingWorkItems(); + ScheduledWorkItems += InWorkItemTally.NumScheduledWorkItems(); + CookingWorkItems += InWorkItemTally.NumCookingWorkItems(); + CookedWorkItems += InWorkItemTally.NumCookedWorkItems(); + ErroredWorkItems += InWorkItemTally.NumErroredWorkItems(); + CookCancelledWorkItems += InWorkItemTally.NumCookCancelledWorkItems(); } -FString -FWorkItemTally::ProgressRatio() const +void +FAggregatedWorkItemTally::Subtract(const FWorkItemTallyBase& InWorkItemTally) { - float Ratio = TotalWorkItems > 0 ? (CookedWorkItems / TotalWorkItems) * 100.f : 0; - - return FString::Printf(TEXT("%.1f%%"), Ratio); + TotalWorkItems -= InWorkItemTally.NumWorkItems(); + WaitingWorkItems -= InWorkItemTally.NumWaitingWorkItems(); + ScheduledWorkItems -= InWorkItemTally.NumScheduledWorkItems(); + CookingWorkItems -= InWorkItemTally.NumCookingWorkItems(); + CookedWorkItems -= InWorkItemTally.NumCookedWorkItems(); + ErroredWorkItems -= InWorkItemTally.NumErroredWorkItems(); + CookCancelledWorkItems -= InWorkItemTally.NumCookCancelledWorkItems(); } @@ -187,6 +364,10 @@ UTOPNode::UTOPNode() bHasChildNodes = false; bShow = false; + + bHasReceivedCookCompleteEvent = false; + + InvalidateLandscapeCache(); } bool @@ -209,6 +390,38 @@ UTOPNode::Reset() { NodeState = EPDGNodeState::None; WorkItemTally.ZeroAll(); + AggregatedWorkItemTally.ZeroAll(); +} + +UHoudiniPDGAssetLink* UTOPNode::GetOuterAssetLink() const +{ + return GetTypedOuter(); +} + +void UTOPNode::OnWorkItemWaiting(int32 InWorkItemID) +{ + FTOPWorkResult* const WorkItem = GetWorkResultByID(InWorkItemID); + if (WorkItem) + { + // Clear the bAutoBakedSinceLastLoad flag on the work results since we are expecting a cook of the work item + for (FTOPWorkResultObject& WRO : WorkItem->ResultObjects) + { + WRO.SetAutoBakedSinceLastLoad(false); + } + } + WorkItemTally.RecordWorkItemAsWaiting(InWorkItemID); +} + +void +UTOPNode::OnWorkItemCooked(int32 InWorkItemID) +{ + if (GetWorkItemTally().NumCookedWorkItems() == 0) + { + // We want to invalidate the landscape cache values in any situation where + // all the work items are being recooked. + InvalidateLandscapeCache(); + } + WorkItemTally.RecordWorkItemAsCooked(InWorkItemID); } void @@ -248,18 +461,18 @@ UTOPNode::UpdateOutputVisibilityInLevel() // We need to manually handle child landscape's visiblity for (UHoudiniOutput* ResultOutput : WRO.GetResultOutputs()) { - if (!ResultOutput || ResultOutput->IsPendingKill()) + if (!IsValid(ResultOutput)) continue; - for (auto Pair : ResultOutput->GetOutputObjects()) + for (auto& Pair : ResultOutput->GetOutputObjects()) { - FHoudiniOutputObject OutputObject = Pair.Value; + FHoudiniOutputObject& OutputObject = Pair.Value; ALandscapeProxy* LandscapeProxy = Cast(OutputObject.OutputObject); - if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) + if (!IsValid(LandscapeProxy)) continue; ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); - if (!Landscape || Landscape->IsPendingKill()) + if (!IsValid(Landscape)) continue; Landscape->SetHidden(!bShow); @@ -281,9 +494,12 @@ UTOPNode::SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad) { if (WRO.State == EPDGWorkResultState::NotLoaded || (WRO.State == EPDGWorkResultState::Deleted && bInAlsoSetDeletedToLoad)) + { WRO.State = EPDGWorkResultState::ToLoad; + WRO.SetAutoBakedSinceLastLoad(false); + } } - } + } } void @@ -299,51 +515,98 @@ UTOPNode::SetLoadedWorkResultsToDelete() } } +FGuid +UTOPNode::GetHoudiniComponentGuid() const +{ + UHoudiniPDGAssetLink const* const AssetLink = GetOuterAssetLink(); + if (!IsValid(AssetLink)) + return FGuid(); + + return AssetLink->GetOuterHoudiniComponentGuid(); +} void -UTOPNode::DeleteWorkResultOutputObjects() +UTOPNode::DeleteWorkResultObjectOutputs(const int32 InWorkResultArrayIndex, const int32 InWorkResultObjectArrayIndex, const bool bInDeleteOutputActors) { - for (FTOPWorkResult& WorkItem : WorkResult) + if (!WorkResult.IsValidIndex(InWorkResultArrayIndex)) + return; + + FTOPWorkResult& WorkItem = WorkResult[InWorkResultArrayIndex]; + if (!WorkItem.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) + return; + + FTOPWorkResultObject& WRO = WorkItem.ResultObjects[InWorkResultObjectArrayIndex]; + // Delete and clean up that WRObj + WRO.DestroyResultOutputs(GetHoudiniComponentGuid()); + if (bInDeleteOutputActors) + WRO.GetOutputActorOwner().DestroyOutputActor(); + WRO.State = EPDGWorkResultState::Deleted; +} + +void +UTOPNode::DeleteWorkItemOutputs(const int32 InWorkResultArrayIndex, const bool bInDeleteOutputActors) +{ + if (!WorkResult.IsValidIndex(InWorkResultArrayIndex)) + return; + + const FTOPWorkResult& WorkItem = WorkResult[InWorkResultArrayIndex]; + const int32 NumResultObjects = WorkItem.ResultObjects.Num(); + for (int32 ResultObjectIndex = 0; ResultObjectIndex < NumResultObjects; ++ResultObjectIndex) { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - if (WRO.State == EPDGWorkResultState::Loaded) - { - // Delete and clean up that WRObj - WRO.DestroyResultOutputs(); - WRO.GetOutputActorOwner().DestroyOutputActor(); - WRO.State = EPDGWorkResultState::Deleted; - } - } - } + DeleteWorkResultObjectOutputs(InWorkResultArrayIndex, ResultObjectIndex, bInDeleteOutputActors); + } +} + +void +UTOPNode::DeleteAllWorkResultObjectOutputs(const bool bInDeleteOutputActors) +{ + const int32 NumWorkItems = WorkResult.Num(); + for (int32 WorkItemIndex = 0; WorkItemIndex < NumWorkItems; ++WorkItemIndex) + { + DeleteWorkItemOutputs(WorkItemIndex, bInDeleteOutputActors); + } bCachedHaveLoadedWorkResults = false; } FString -UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex) +UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex) +{ + return FString::Printf(TEXT("%d_%d"), InWorkResultArrayIndex, InWorkResultObjectArrayIndex); +} + +FString +UTOPNode::GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex) const { - return FString::Printf(TEXT("%d_%d"), InWorkResultIndex, InWorkResultObjectIndex); + if (InWorkResult.WorkItemID == INDEX_NONE) + return FString(); + + const int32 WorkResultArrayIndex = ArrayIndexOfWorkResultByID(InWorkResult.WorkItemID); + if (WorkResultArrayIndex == INDEX_NONE) + return FString(); + + return GetBakedWorkResultObjectOutputsKey(WorkResultArrayIndex, InWorkResultObjectArrayIndex); } bool -UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FString& OutKey) const +UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FString& OutKey) const { // Check that indices are valid - if (!WorkResult.IsValidIndex(InWorkResultIndex)) + if (!WorkResult.IsValidIndex(InWorkResultArrayIndex)) return false; - if (!WorkResult[InWorkResultIndex].ResultObjects.IsValidIndex(InWorkResultObjectIndex)) + const FTOPWorkResult& WorkResultEntry = WorkResult[InWorkResultArrayIndex]; + if (!WorkResultEntry.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) return false; - OutKey = GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex); + OutKey = GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex); return true; } bool -UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput) +UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput) { FString Key; - if (!GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key)) + if (!GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key)) return false; OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); if (!OutBakedOutput) @@ -353,10 +616,10 @@ UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkR } bool -UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const +UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const { FString Key; - if (!GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key)) + if (!GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key)) return false; OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); if (!OutBakedOutput) @@ -365,6 +628,109 @@ UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkR return true; } +int32 +UTOPNode::ArrayIndexOfWorkResultByID(const int32& InWorkItemID) const +{ + const int32 NumEntries = WorkResult.Num(); + for (int32 Index = 0; Index < NumEntries; ++Index) + { + const FTOPWorkResult& CurResult = WorkResult[Index]; + if (CurResult.WorkItemID == InWorkItemID) + { + return Index; + } + } + + return INDEX_NONE; +} + +FTOPWorkResult* +UTOPNode::GetWorkResultByID(const int32& InWorkItemID) +{ + const int32 ArrayIndex = ArrayIndexOfWorkResultByID(InWorkItemID); + if (!WorkResult.IsValidIndex(ArrayIndex)) + return nullptr; + + return &WorkResult[ArrayIndex]; +} + +int32 +UTOPNode::ArrayIndexOfFirstInvalidWorkResult() const +{ + const int32 NumEntries = WorkResult.Num(); + for (int32 Index = 0; Index < NumEntries; ++Index) + { + const FTOPWorkResult& CurResult = WorkResult[Index]; + if (CurResult.WorkItemID == INDEX_NONE) + { + return Index; + } + } + + return INDEX_NONE; +} + +FTOPWorkResult* +UTOPNode::GetWorkResultByArrayIndex(const int32& InArrayIndex) +{ + if (!WorkResult.IsValidIndex(InArrayIndex)) + return nullptr; + return &WorkResult[InArrayIndex]; +} + +bool +UTOPNode::IsParentTOPNetwork(UTOPNetwork const * const InNetwork) const +{ + if (!IsValid(InNetwork)) + { + return false; + } + + return ParentName == FString::Printf(TEXT("%s_%s"), *InNetwork->ParentName, *InNetwork->NodeName); +} + +bool +UTOPNode::CanStillBeAutoBaked() const +{ + // Only nodes that have results auto-loaded are auto-baked + if (!bAutoLoad) + return false; + + // Nodes with failures are not auto-baked + if (AnyWorkItemsFailed()) + return false; + + // All work items are not yet complete, so node cannot yet be baked + if (!AreAllWorkItemsComplete()) + return true; + + // Work items that are currently loaded or has not tagged has auto baked since last load can still be baked + for (const FTOPWorkResult& WorkResultEntry : WorkResult) + { + for (const FTOPWorkResultObject& WRO : WorkResultEntry.ResultObjects) + { + switch (WRO.State) + { + case EPDGWorkResultState::NotLoaded: + case EPDGWorkResultState::ToLoad: + case EPDGWorkResultState::Loading: + return true; + case EPDGWorkResultState::Loaded: + if (!WRO.AutoBakedSinceLastLoad()) + return true; + break; + case EPDGWorkResultState::ToDelete: + case EPDGWorkResultState::Deleting: + case EPDGWorkResultState::Deleted: + case EPDGWorkResultState::None: + break; + } + } + } + + return false; +} + #if WITH_EDITOR void UTOPNode::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedEvent) @@ -402,6 +768,21 @@ UTOPNode::PostTransacted(const FTransactionObjectEvent& TransactionEvent) } #endif +void +UTOPNode::OnDirtyNode() +{ + InvalidateLandscapeCache(); + bHasReceivedCookCompleteEvent = false; +} + +void +UTOPNode::InvalidateLandscapeCache() +{ + LandscapeReferenceLocation.bIsCached = false; + LandscapeSizeInfo.bIsCached = false; + ClearedLandscapeLayers.Empty(); +} + UTOPNetwork::UTOPNetwork() { NodeId = -1; @@ -444,14 +825,14 @@ UTOPNetwork::SetLoadedWorkResultsToDelete() } void -UTOPNetwork::DeleteWorkResultOutputObjects() +UTOPNetwork::DeleteAllWorkResultObjectOutputs() { for (UTOPNode* Node : AllTOPNodes) { if (!IsValid(Node)) continue; - Node->DeleteWorkResultOutputObjects(); + Node->DeleteAllWorkResultObjectOutputs(); } } @@ -470,6 +851,54 @@ UTOPNetwork::AnyWorkItemsPending() const return false; } +bool +UTOPNetwork::AnyWorkItemsFailed() const +{ + for (const UTOPNode* const TOPNode : AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->AnyWorkItemsFailed()) + return true; + } + + return false; +} + +bool +UTOPNetwork::CanStillBeAutoBaked() const +{ + for (const UTOPNode* const TOPNode : AllTOPNodes) + { + if (TOPNode->CanStillBeAutoBaked()) + return true; + } + + return false; +} + +void +UTOPNetwork::HandleOnPDGEventCookCompleteReceivedByChildNode(UHoudiniPDGAssetLink* const InAssetLink, UTOPNode* const InTOPNode) +{ + if (!IsValid(InAssetLink)) + return; + + // Check if all nodes have received the HAPI_PDG_EVENT_COOK_COMPLETE event, if so, broadcast the OnPostCook handler. + for (const UTOPNode* const TOPNode : AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (!TOPNode->HasReceivedCookCompleteEvent()) + return; + } + + if (OnPostCookDelegate.IsBound()) + OnPostCookDelegate.Broadcast(this, AnyWorkItemsFailed()); + + InAssetLink->HandleOnTOPNetworkCookComplete(this); +} void UHoudiniPDGAssetLink::SelectTOPNetwork(const int32& AtIndex) @@ -500,6 +929,11 @@ UHoudiniPDGAssetLink::GetSelectedTOPNetwork() return GetTOPNetwork(SelectedTOPNetworkIndex); } +const UTOPNetwork* +UHoudiniPDGAssetLink::GetSelectedTOPNetwork() const +{ + return GetTOPNetwork(SelectedTOPNetworkIndex); +} UTOPNode* UHoudiniPDGAssetLink::GetSelectedTOPNode() @@ -518,6 +952,23 @@ UHoudiniPDGAssetLink::GetSelectedTOPNode() return SelectedTOPNode; } +const UTOPNode* +UHoudiniPDGAssetLink::GetSelectedTOPNode() const +{ + UTOPNetwork const* const SelectedTOPNetwork = GetSelectedTOPNetwork(); + if (!IsValid(SelectedTOPNetwork)) + return nullptr; + + if (!SelectedTOPNetwork->AllTOPNodes.IsValidIndex(SelectedTOPNetwork->SelectedTOPIndex)) + return nullptr; + + UTOPNode const* const SelectedTOPNode = SelectedTOPNetwork->AllTOPNodes[SelectedTOPNetwork->SelectedTOPIndex]; + if (!IsValid(SelectedTOPNode)) + return nullptr; + + return SelectedTOPNode; +} + FString UHoudiniPDGAssetLink::GetSelectedTOPNodeName() { @@ -553,6 +1004,17 @@ UHoudiniPDGAssetLink::GetTOPNetwork(const int32& AtIndex) return nullptr; } +const UTOPNetwork* +UHoudiniPDGAssetLink::GetTOPNetwork(const int32& AtIndex) const +{ + if(AllTOPNetworks.IsValidIndex(AtIndex)) + { + return AllTOPNetworks[AtIndex]; + } + + return nullptr; +} + UTOPNetwork* UHoudiniPDGAssetLink::GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex) { @@ -673,10 +1135,13 @@ UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(UTOPNode* TOPNode) { if (!IsValid(TOPNode)) return; - + + TOPNode->OnDirtyNode(); + + const FGuid HoudiniComponentGuid(TOPNode->GetHoudiniComponentGuid()); for(FTOPWorkResult& CurrentWorkResult : TOPNode->WorkResult) { - DestroyWorkItemResultData(CurrentWorkResult, TOPNode); + CurrentWorkResult.ClearAndDestroyResultObjects(HoudiniComponentGuid); } TOPNode->WorkResult.Empty(); @@ -697,7 +1162,7 @@ UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(UTOPNode* TOPNode) } } - if (TOPNode->WorkResultParent && !TOPNode->WorkResultParent->IsPendingKill()) + if (IsValid(TOPNode->WorkResultParent)) { // TODO: Destroy the Parent Object @@ -717,7 +1182,7 @@ UHoudiniPDGAssetLink::ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNod FTOPWorkResult* WorkResult = GetWorkResultByID(InWorkItemID, InTOPNode); if (WorkResult) { - DestroyWorkItemResultData(*WorkResult, InTOPNode); + WorkResult->ClearAndDestroyResultObjects(InTOPNode->GetHoudiniComponentGuid()); // TODO: Should we destroy the FTOPWorkResult struct entirely here? //TOPNode.WorkResult.RemoveByPredicate } @@ -744,23 +1209,13 @@ UHoudiniPDGAssetLink::GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InT { if (!IsValid(InTOPNode)) return nullptr; - - for(FTOPWorkResult& CurResult : InTOPNode->WorkResult) - { - if (CurResult.WorkItemID == InWorkItemID) - { - return &CurResult; - } - } - - return nullptr; + return InTOPNode->GetWorkResultByID(InWorkItemID); } FDirectoryPath UHoudiniPDGAssetLink::GetTemporaryCookFolder() const { - UObject* Owner = GetOuter(); - UHoudiniAssetComponent* HAC = Cast(Owner); + UHoudiniAssetComponent* HAC = GetOuterHoudiniAssetComponent(); if (HAC) return HAC->TemporaryCookFolder; @@ -769,31 +1224,48 @@ UHoudiniPDGAssetLink::GetTemporaryCookFolder() const return TempPath; } -void -UHoudiniPDGAssetLink::DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject) +FGuid +UHoudiniPDGAssetLink::GetOuterHoudiniComponentGuid() const { - ResultObject.DestroyResultOutputs(); - ResultObject.GetOutputActorOwner().DestroyOutputActor(); + UHoudiniAssetComponent const* const HAC = GetOuterHoudiniAssetComponent(); + if (!IsValid(HAC)) + return FGuid(); + + return HAC->GetComponentGUID(); } void -UHoudiniPDGAssetLink::DestroyWorkItemResultData(FTOPWorkResult& Result, UTOPNode* InTOPNode) +UHoudiniPDGAssetLink::HandleOnTOPNetworkCookComplete(UTOPNetwork* const InTOPNet) { - if (Result.ResultObjects.Num() <= 0) + if (!IsValid(InTOPNet)) return; - for (FTOPWorkResultObject& ResultObject : Result.ResultObjects) + if (OnPostTOPNetworkCookDelegate.IsBound()) { - DestoryWorkResultObjectData(ResultObject); + OnPostTOPNetworkCookDelegate.Broadcast(this, InTOPNet, InTOPNet->AnyWorkItemsFailed()); } - - Result.ResultObjects.Empty(); } UTOPNode* UHoudiniPDGAssetLink::GetTOPNode(const int32& InNodeID) { + UTOPNetwork* Network = nullptr; + UTOPNode* Node = nullptr; + + if (GetTOPNodeAndNetworkByNodeId(InNodeID, Network, Node)) + return Node; + + return nullptr; +} + + +bool +UHoudiniPDGAssetLink::GetTOPNodeAndNetworkByNodeId(const int32& InNodeID, UTOPNetwork*& OutNetwork, UTOPNode*& OutNode) +{ + OutNetwork = nullptr; + OutNode = nullptr; + for (UTOPNetwork* CurrentTOPNet : AllTOPNetworks) { if (!IsValid(CurrentTOPNet)) @@ -805,13 +1277,18 @@ UHoudiniPDGAssetLink::GetTOPNode(const int32& InNodeID) continue; if (CurrentTOPNode->NodeId == InNodeID) - return CurrentTOPNode; + { + OutNetwork = CurrentTOPNet; + OutNode = CurrentTOPNode; + return true; + } } } - return nullptr; + return false; } + void UHoudiniPDGAssetLink::UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork) { @@ -824,7 +1301,7 @@ UHoudiniPDGAssetLink::UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* I FString PrefixPath = InNode->NodePath; if (!PrefixPath.EndsWith("/")) PrefixPath += "/"; - InNode->WorkItemTally.ZeroAll(); + InNode->ZeroWorkItemTally(); InNode->NodeState = EPDGNodeState::None; TMap NodeStateOrder; @@ -844,15 +1321,10 @@ UHoudiniPDGAssetLink::UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* I if (Node->NodePath.StartsWith(PrefixPath) && !Node->bHasChildNodes) { - InNode->WorkItemTally.TotalWorkItems += Node->WorkItemTally.TotalWorkItems; - InNode->WorkItemTally.WaitingWorkItems += Node->WorkItemTally.WaitingWorkItems; - InNode->WorkItemTally.ScheduledWorkItems += Node->WorkItemTally.ScheduledWorkItems; - InNode->WorkItemTally.CookingWorkItems += Node->WorkItemTally.CookingWorkItems; - InNode->WorkItemTally.CookedWorkItems += Node->WorkItemTally.CookedWorkItems; - InNode->WorkItemTally.ErroredWorkItems += Node->WorkItemTally.ErroredWorkItems; - const int8 VisistedNodeState = NodeStateOrder.FindChecked(Node->NodeState); - if (VisistedNodeState > CurrentState) - CurrentState = VisistedNodeState; + InNode->AggregateTallyFromChildNode(Node); + const int8 VisitedNodeState = NodeStateOrder.FindChecked(Node->NodeState); + if (VisitedNodeState > CurrentState) + CurrentState = VisitedNodeState; } } @@ -882,12 +1354,7 @@ UHoudiniPDGAssetLink::UpdateWorkItemTally() } else { - WorkItemTally.TotalWorkItems += CurrentTOPNode->WorkItemTally.TotalWorkItems; - WorkItemTally.WaitingWorkItems += CurrentTOPNode->WorkItemTally.WaitingWorkItems; - WorkItemTally.ScheduledWorkItems += CurrentTOPNode->WorkItemTally.ScheduledWorkItems; - WorkItemTally.CookingWorkItems += CurrentTOPNode->WorkItemTally.CookingWorkItems; - WorkItemTally.CookedWorkItems += CurrentTOPNode->WorkItemTally.CookedWorkItems; - WorkItemTally.ErroredWorkItems += CurrentTOPNode->WorkItemTally.ErroredWorkItems; + WorkItemTally.Add(CurrentTOPNode->GetWorkItemTally()); } } } @@ -905,7 +1372,7 @@ UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork) if (!IsValid(CurTOPNode)) continue; - CurTOPNode->WorkItemTally.ZeroAll(); + CurTOPNode->ZeroWorkItemTally(); } } @@ -1080,6 +1547,11 @@ UHoudiniPDGAssetLink::UpdatePostDuplicate() } } +UHoudiniAssetComponent* UHoudiniPDGAssetLink::GetOuterHoudiniAssetComponent() const +{ + return Cast( GetTypedOuter() ); +} + void UHoudiniPDGAssetLink::UpdateTOPNodeAutoloadAndVisibility() { @@ -1152,6 +1624,60 @@ UHoudiniPDGAssetLink::FilterTOPNodesAndOutputs() } } +#if WITH_EDITORONLY_DATA +bool +UHoudiniPDGAssetLink::AnyRemainingAutoBakeNodes() const +{ + if (!bBakeAfterAllWorkResultObjectsLoaded) + return false; + + switch (PDGBakeSelectionOption) + { + case EPDGBakeSelectionOption::All: + { + for (const UTOPNetwork* const TOPNet : AllTOPNetworks) + { + if (!IsValid(TOPNet)) + continue; + + if (TOPNet->CanStillBeAutoBaked()) + { + return true; + } + } + break; + } + case EPDGBakeSelectionOption::SelectedNetwork: + { + const UTOPNetwork* const TOPNet = GetSelectedTOPNetwork(); + if (IsValid(TOPNet) && TOPNet->CanStillBeAutoBaked()) + { + return true; + } + } + case EPDGBakeSelectionOption::SelectedNode: + { + UTOPNode const* const TOPNode = GetSelectedTOPNode(); + if (IsValid(TOPNode) && TOPNode->CanStillBeAutoBaked()) + { + return true; + } + } + default: + return false; + } + + return false; +} +#endif + +void +UHoudiniPDGAssetLink::HandleOnPostBake(const bool bInSuccess) +{ + if (OnPostBakeDelegate.IsBound()) + OnPostBakeDelegate.Broadcast(this, bInSuccess); +} + #if WITH_EDITORONLY_DATA void UHoudiniPDGAssetLink::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedChainEvent) @@ -1213,8 +1739,12 @@ UHoudiniPDGAssetLink::PostTransacted(const FTransactionObjectEvent& TransactionE #endif void -FTOPWorkResultObject::DestroyResultOutputs() +FTOPWorkResultObject::DestroyResultOutputs(const FGuid& InHoudiniComponentGuid) { + TSet OutputObjectsToDelete; + + const FString ComponentGuidString = InHoudiniComponentGuid.IsValid() ? InHoudiniComponentGuid.ToString() : FString(); + // Delete output components and gather output objects for deletion bool bDidDestroyObjects = false; bool bDidModifyFoliage = false; @@ -1227,7 +1757,7 @@ FTOPWorkResultObject::DestroyResultOutputs() { FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; FHoudiniOutputObject& OutputObject = Pair.Value; - if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) + if (IsValid(OutputObject.OutputComponent)) { // Instancer components require some special handling around foliage // TODO: move/refactor so that we can use the InstanceTranslator's helper functions (RemoveAndDestroyComponent and CleanupFoliageInstances) @@ -1243,20 +1773,20 @@ FTOPWorkResultObject::DestroyResultOutputs() ParentComponent = Cast(OutputActor->GetRootComponent()); else ParentComponent = Cast(HISMC->GetOuter()); - if (ParentComponent && !ParentComponent->IsPendingKill()) + if (IsValid(ParentComponent)) { UStaticMesh* FoliageSM = HISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) + if (!IsValid(FoliageSM)) return; // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, // if it is not, then we are just a "regular" HISMC AInstancedFoliageActor* InstancedFoliageActor = Cast(HISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + if (!IsValid(InstancedFoliageActor)) return; UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) + if (!IsValid(FoliageType)) return; #if WITH_EDITOR // Clean up the instances previously generated for that component @@ -1270,34 +1800,36 @@ FTOPWorkResultObject::DestroyResultOutputs() #endif } - // do not delete FISMC that still have instances left - // as we have cleaned up our instances before, these have been hand-placed - if (HISMC->GetInstanceCount() > 0) - bDestroyComponent = false; - } + // // do not delete FISMC that still have instances left + // // as we have cleaned up our instances before, these have been hand-placed + // if (HISMC->GetInstanceCount() > 0) + bDestroyComponent = false; - if (bDestroyComponent) + OutputObject.OutputComponent = nullptr; + } + } + + if (bDestroyComponent) + { + USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); + if (IsValid(SceneComponent)) { - USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) - { - // Remove from its actor first - if (SceneComponent->GetOwner()) - SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); - - // Detach from its parent component if attached - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComponent->UnregisterComponent(); - SceneComponent->DestroyComponent(); - - bDidDestroyObjects = true; - - OutputObject.OutputComponent = nullptr; - } + // Remove from its actor first + if (SceneComponent->GetOwner()) + SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); + + // Detach from its parent component if attached + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComponent->UnregisterComponent(); + SceneComponent->DestroyComponent(); + + bDidDestroyObjects = true; + + OutputObject.OutputComponent = nullptr; } } } - if (OutputObject.OutputObject && !OutputObject.OutputObject->IsPendingKill()) + if (IsValid(OutputObject.OutputObject)) { // For actors we detach them first and then destroy AActor* Actor = Cast(OutputObject.OutputObject); @@ -1316,10 +1848,38 @@ FTOPWorkResultObject::DestroyResultOutputs() } else { - // ... if not an actor, mark as pending kill - // OutputObject.OutputObject->MarkPendingKill(); - if (IsValid(OutputObject.OutputObject)) - OutputObjectsToDelete.Add(OutputObject.OutputObject); + // ... if not an actor, destroy the object if it is a temp object created by the owning + // HoudiniAssetComponent. Don't delete anything if we don't have a valid component GUID. + if (IsValid(OutputObject.OutputObject) && !OutputObject.OutputObject->HasAnyFlags(RF_Transient) && + !ComponentGuidString.IsEmpty() && !OutputObjectsToDelete.Contains(OutputObject.OutputObject)) + { + // Only delete if the object has metadata indicating it is a plugin created temp object + UPackage* const Package = OutputObject.OutputObject->GetOutermost(); + if (IsValid(Package)) + { + UMetaData* const MetaData = Package->GetMetaData(); + if (IsValid(MetaData)) + { + // Check for HAPI_UNREAL_PACKAGE_META_TEMP_GUID in the package metadata to confirm that + // this is a temp package, and then ensure that the component GUID in the package metadata + // for the object matches the GUID of the owning HAC + if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_TEMP_GUID)) + { + FString TempGUID; + TempGUID = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_TEMP_GUID); + TempGUID.TrimStartAndEndInline(); + if (!TempGUID.IsEmpty()) + { + // PackageComponentGuidString will be the empty string if the object does not + // have the HAPI_UNREAL_PACKAGE_META_COMPONENT_GUID metadata key in the package + const FString PackageComponentGuidString = MetaData->GetValue(OutputObject.OutputObject, HAPI_UNREAL_PACKAGE_META_COMPONENT_GUID); + if (!PackageComponentGuidString.IsEmpty() && PackageComponentGuidString == ComponentGuidString) + OutputObjectsToDelete.Add(OutputObject.OutputObject); + } + } + } + } + } OutputObject.OutputObject = nullptr; } } @@ -1329,11 +1889,14 @@ FTOPWorkResultObject::DestroyResultOutputs() ResultOutputs.Empty(); if (bDidDestroyObjects) - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); // Delete the output objects we found if (OutputObjectsToDelete.Num() > 0) - FHoudiniEngineRuntimeUtils::SafeDeleteObjects(OutputObjectsToDelete); + { + TArray ObjectsToDelete(OutputObjectsToDelete.Array()); + FHoudiniEngineRuntimeUtils::SafeDeleteObjects(ObjectsToDelete); + } #if WITH_EDITOR if (bDidModifyFoliage) @@ -1351,16 +1914,22 @@ FTOPWorkResultObject::DestroyResultOutputs() #endif } +void FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor(const FGuid& InHoudiniComponentGuid) +{ + DestroyResultOutputs(InHoudiniComponentGuid); + GetOutputActorOwner().DestroyOutputActor(); +} + bool FOutputActorOwner::CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName) { // InAssetLink and InWorld must not be null - if (!InAssetLink || InAssetLink->IsPendingKill()) + if (!IsValid(InAssetLink)) { HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InAssetLink is null!")); return false; } - if (!InWorld || InWorld->IsPendingKill()) + if (!IsValid(InWorld)) { HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InWorld is null!")); return false; @@ -1410,12 +1979,12 @@ FOutputActorOwner::CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAs AActor *Actor = WorldToSpawnIn->SpawnActor(SpawnParams); SetOutputActor(Actor); #if WITH_EDITOR - Actor->SetActorLabel(InName.ToString()); + FHoudiniEngineRuntimeUtils::SetActorLabel(Actor, InName.ToString()); #endif // Set the actor transform: create a root component if it does not have one USceneComponent* RootComponent = Actor->GetRootComponent(); - if (!RootComponent || RootComponent->IsPendingKill()) + if (!IsValid(RootComponent)) { RootComponent = NewObject(Actor, USceneComponent::StaticClass(), NAME_None, RF_Transactional); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h index 627f9c1d9..89fc36a05 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,10 +28,11 @@ //#include "CoreMinimal.h" #include "UObject/ObjectMacros.h" +#include "Misc/Guid.h" -#include "HoudiniAsset.h" +// #include "HoudiniAsset.h" #include "HoudiniAssetComponent.h" - +#include "HoudiniTranslatorTypes.h" #include "HoudiniPDGAssetLink.generated.h" @@ -71,24 +72,6 @@ enum class EPDGWorkResultState : uint8 NotLoaded }; -#if WITH_EDITORONLY_DATA -UENUM() -enum class EPDGBakeSelectionOption : uint8 -{ - All, - SelectedNetwork, - SelectedNode -}; -#endif - -#if WITH_EDITORONLY_DATA -UENUM() -enum class EPDGBakePackageReplaceModeOption : uint8 -{ - CreateNewAssets, - ReplaceExistingAssets -}; -#endif USTRUCT() struct HOUDINIENGINERUNTIME_API FOutputActorOwner @@ -141,7 +124,7 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject const TArray& GetResultOutputs() const { return ResultOutputs; } // Destroy ResultOutputs - void DestroyResultOutputs(); + void DestroyResultOutputs(const FGuid& InHoudiniComponentGuid); // Get the OutputActor owner struct FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } @@ -149,6 +132,14 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject // Get the OutputActor owner struct const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } + // Destroy the ResultOutputs and remove the output actor. + void DestroyResultOutputsAndRemoveOutputActor(const FGuid& InHoudiniComponentGuid); + + // Getter for bAutoBakedSinceLastLoad: indicates if this work result object has been auto-baked since it's last load. + bool AutoBakedSinceLastLoad() const { return bAutoBakedSinceLastLoad; } + // Setter for bAutoBakedSinceLastLoad + void SetAutoBakedSinceLastLoad(bool bInAutoBakedSinceLastLoad) { bAutoBakedSinceLastLoad = bInAutoBakedSinceLastLoad; } + public: UPROPERTY(NonTransactional) @@ -157,6 +148,9 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject FString FilePath; UPROPERTY(NonTransactional) EPDGWorkResultState State; + // The index in the WorkItemResultInfo array of this item as it was received from HAPI. + UPROPERTY(NonTransactional) + int32 WorkItemResultInfoIndex; protected: // UPROPERTY() @@ -165,11 +159,11 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject UPROPERTY(NonTransactional) TArray ResultOutputs; -private: - // List of objects to delete, internal use only (DestroyResultOutputs) + // If true, indicates that the work result object has been auto-baked since it was last loaded. UPROPERTY(NonTransactional) - TArray OutputObjectsToDelete; + bool bAutoBakedSinceLastLoad = false; +private: UPROPERTY(NonTransactional) FOutputActorOwner OutputActorOwner; }; @@ -187,6 +181,16 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResult // Comparison operator, used by hashing containers and arrays. bool operator==(const FTOPWorkResult& OtherWorkResult) const; + // Calls FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor on each entry in ResultObjects and clears the array. + void ClearAndDestroyResultObjects(const FGuid& InHoudiniComponentGuid); + + // Search for the first FTOPWorkResultObject entry by WorkItemResultInfoIndex and return it, or nullptr if it could not be found. + int32 IndexOfWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex); + // Search for the first FTOPWorkResultObject entry by WorkItemResultInfoIndex and return it, or nullptr if it could not be found. + FTOPWorkResultObject* GetWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex); + // Return the FTOPWorkResultObject at InArrayIndex in the ResultObjects array, or nullptr if InArrayIndex is not a valid index. + FTOPWorkResultObject* GetWorkResultObjectByArrayIndex(const int32& InArrayIndex); + public: UPROPERTY(NonTransactional) @@ -210,9 +214,42 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResult */ }; +USTRUCT() +struct HOUDINIENGINERUNTIME_API FWorkItemTallyBase +{ + GENERATED_USTRUCT_BODY() + +public: + virtual ~FWorkItemTallyBase(); + + // + // Mutators + // + + // Zero all counts, including total. + virtual void ZeroAll() {}; + + // + // Accessors + // + + bool AreAllWorkItemsComplete() const; + bool AnyWorkItemsFailed() const; + bool AnyWorkItemsPending() const; + + virtual int32 NumWorkItems() const { return 0; }; + virtual int32 NumWaitingWorkItems() const { return 0; }; + virtual int32 NumScheduledWorkItems() const { return 0; }; + virtual int32 NumCookingWorkItems() const { return 0; }; + virtual int32 NumCookedWorkItems() const { return 0; }; + virtual int32 NumErroredWorkItems() const { return 0; }; + virtual int32 NumCookCancelledWorkItems() const { return 0; }; + + FString ProgressRatio() const; +}; USTRUCT() -struct HOUDINIENGINERUNTIME_API FWorkItemTally +struct HOUDINIENGINERUNTIME_API FWorkItemTally : public FWorkItemTallyBase { GENERATED_USTRUCT_BODY() @@ -221,16 +258,82 @@ struct HOUDINIENGINERUNTIME_API FWorkItemTally // Constructor FWorkItemTally(); - void ZeroAll(); + // + // Mutators + // + + // Empty all state sets, as well as AllWorkItems. + virtual void ZeroAll() override; - bool AreAllWorkItemsComplete() const; - bool AnyWorkItemsFailed() const; - bool AnyWorkItemsPending() const; + // Remove a work item from all state sets and AllWorkItems. + void RemoveWorkItem(int32 InWorkItemID); - FString ProgressRatio() const; + void RecordWorkItemAsWaiting(int32 InWorkItemID); + void RecordWorkItemAsScheduled(int32 InWorkItemID); + void RecordWorkItemAsCooking(int32 InWorkItemID); + void RecordWorkItemAsCooked(int32 InWorkItemID); + void RecordWorkItemAsErrored(int32 InWorkItemID); + void RecordWorkItemAsCookCancelled(int32 InWorkItemID); + + // + // Accessors + // + + virtual int32 NumWorkItems() const override { return AllWorkItems.Num(); } + virtual int32 NumWaitingWorkItems() const override { return WaitingWorkItems.Num(); } + virtual int32 NumScheduledWorkItems() const override { return ScheduledWorkItems.Num(); } + virtual int32 NumCookingWorkItems() const override { return CookingWorkItems.Num(); } + virtual int32 NumCookedWorkItems() const override { return CookedWorkItems.Num(); } + virtual int32 NumErroredWorkItems() const override { return ErroredWorkItems.Num(); } + virtual int32 NumCookCancelledWorkItems() const override { return CookCancelledWorkItems.Num(); } + +protected: + + // Removes the work item id from all state sets (but not from AllWorkItems -- use RemoveWorkItem for that). + void RemoveWorkItemFromAllStateSets(int32 InWorkItemID); + + // We use sets to keep track of in what state a work item is. The set stores the WorkItemID. + + UPROPERTY() + TSet AllWorkItems; + UPROPERTY() + TSet WaitingWorkItems; + UPROPERTY() + TSet ScheduledWorkItems; + UPROPERTY() + TSet CookingWorkItems; + UPROPERTY() + TSet CookedWorkItems; + UPROPERTY() + TSet ErroredWorkItems; + UPROPERTY() + TSet CookCancelledWorkItems; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FAggregatedWorkItemTally : public FWorkItemTallyBase +{ + GENERATED_USTRUCT_BODY() public: + // Constructor + FAggregatedWorkItemTally(); + + virtual void ZeroAll() override; + + void Add(const FWorkItemTallyBase& InWorkItemTally); + + void Subtract(const FWorkItemTallyBase& InWorkItemTally); + + virtual int32 NumWorkItems() const override { return TotalWorkItems; } + virtual int32 NumWaitingWorkItems() const override { return WaitingWorkItems; } + virtual int32 NumScheduledWorkItems() const override { return ScheduledWorkItems; } + virtual int32 NumCookingWorkItems() const override { return CookingWorkItems; } + virtual int32 NumCookedWorkItems() const override { return CookedWorkItems; } + virtual int32 NumErroredWorkItems() const override { return ErroredWorkItems; } + +protected: UPROPERTY() int32 TotalWorkItems; UPROPERTY() @@ -243,6 +346,9 @@ struct HOUDINIENGINERUNTIME_API FWorkItemTally int32 CookedWorkItems; UPROPERTY() int32 ErroredWorkItems; + UPROPERTY() + int32 CookCancelledWorkItems; + }; // Container for baked outputs of a PDG work result object. @@ -257,13 +363,15 @@ struct HOUDINIENGINERUNTIME_API FHoudiniPDGWorkResultObjectBakedOutput TArray BakedOutputs; }; +// Forward declare the UTOPNetwork here for some references in the UTOPNode +class UTOPNetwork; + UCLASS() class HOUDINIENGINERUNTIME_API UTOPNode : public UObject { GENERATED_BODY() public: - // Constructor UTOPNode(); @@ -272,9 +380,62 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject void Reset(); - bool AreAllWorkItemsComplete() const { return WorkItemTally.AreAllWorkItemsComplete(); }; - bool AnyWorkItemsFailed() const { return WorkItemTally.AnyWorkItemsFailed(); }; - bool AnyWorkItemsPending() const { return WorkItemTally.AnyWorkItemsPending(); }; + /** Get the owning/outer UHoudiniPDGAssetLink of this UTOPNode. */ + UHoudiniPDGAssetLink* GetOuterAssetLink() const; + + /** + * Get the Guid of the HoudiniAssetComponent that owns the AssetLink. Returns an invalid Guid if the HAC or + * asset link could not be found / is invalid. + */ + FGuid GetHoudiniComponentGuid() const; + + const FWorkItemTallyBase& GetWorkItemTally() const + { + if (bHasChildNodes) + return AggregatedWorkItemTally; + return WorkItemTally; + } + + void AggregateTallyFromChildNode(const UTOPNode* InChildNode) + { + if (IsValid(InChildNode)) + AggregatedWorkItemTally.Add(InChildNode->GetWorkItemTally()); + } + + bool AreAllWorkItemsComplete() const { return GetWorkItemTally().AreAllWorkItemsComplete(); }; + bool AnyWorkItemsFailed() const { return GetWorkItemTally().AnyWorkItemsFailed(); }; + bool AnyWorkItemsPending() const { return GetWorkItemTally().AnyWorkItemsPending(); }; + void ZeroWorkItemTally() + { + WorkItemTally.ZeroAll(); + AggregatedWorkItemTally.ZeroAll(); + } + + // Called by PDG manager when work item events are received + + // Notification that a work item has been created + void OnWorkItemCreated(int32 InWorkItemID) { }; + + // Notification that a work item has been removed. + void OnWorkItemRemoved(int32 InWorkItemID) { WorkItemTally.RemoveWorkItem(InWorkItemID); }; + + // Notification that a work item has moved to the waiting state. + void OnWorkItemWaiting(int32 InWorkItemID); + + // Notification that a work item has been scheduled. + void OnWorkItemScheduled(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsScheduled(InWorkItemID); }; + + // Notification that a work item has started cooking. + void OnWorkItemCooking(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsCooking(InWorkItemID); }; + + // Notification that a work item has been cooked. + void OnWorkItemCooked(int32 InWorkItemID); + + // Notification that a work item has errored. + void OnWorkItemErrored(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsErrored(InWorkItemID); }; + + // Notification that a work item cook has been cancelled. + void OnWorkItemCookCancelled(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsCookCancelled(InWorkItemID); }; bool IsVisibleInLevel() const { return bShow; } void SetVisibleInLevel(bool bInVisible); @@ -287,9 +448,17 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject // actors). void SetLoadedWorkResultsToDelete(); + // Immediately delete the Loaded work result output object (keeps the work item and result structs in the arrays but + // deletes the output object and the actor and sets the state to Deleted. + void DeleteWorkResultObjectOutputs(const int32 InWorkResultArrayIndex, const int32 InWorkResultObjectArrayIndex, const bool bInDeleteOutputActors=true); + + // Immediately delete Loaded work result output objects for the specified work item (keeps the work item and result + // arrays but deletes the output objects and actors and sets the state to Deleted. + void DeleteWorkItemOutputs(const int32 InWorkResultArrayIndex, const bool bInDeleteOutputActors=true); + // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output // objects and actors and sets the state to Deleted. - void DeleteWorkResultOutputObjects(); + void DeleteAllWorkResultObjectOutputs(const bool bInDeleteOutputActors=true); // Get the OutputActor owner struct FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } @@ -297,14 +466,36 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject // Get the OutputActor owner struct const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } - // Get the baked outputs from the last bake. The map keys are [work_result_index]_[work_result_object_index] + // Get the baked outputs from the last bake. The map keys are [work_result.work_item_index]_[work_result_object_index] TMap& GetBakedWorkResultObjectsOutputs() { return BakedWorkResultObjectOutputs; } const TMap& GetBakedWorkResultObjectsOutputs() const { return BakedWorkResultObjectOutputs; } - bool GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FString& OutKey) const; - static FString GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex); - bool GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput); - bool GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const; - + // Helper to construct the key used to look up baked work results. + static FString GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex); + // Helper to construct the key used to look up baked work results. + FString GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex) const; + // Helper to construct the key used to look up baked work results. + bool GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FString& OutKey) const; + // Get the FHoudiniPDGWorkResultObjectBakedOutput for a work item (FTOPWorkResult) and specific result object. + bool GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput); + // Get the FHoudiniPDGWorkResultObjectBakedOutput for a work item (FTOPWorkResult) and specific result object (const version). + bool GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const; + + // Search for the first FTOPWorkResult entry by WorkItemID and return its array index or INDEX_NONE, if it could not be found. + int32 ArrayIndexOfWorkResultByID(const int32& InWorkItemID) const; + // Search for the first FTOPWorkResult entry by WorkItemID and return it, or nullptr if it could not be found. + FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID); + // Search for the first FTOPWorkResult entry with an invalid (INDEX_NONE) work item id and return it, or INDEX_None + // if no invalid entry could be found. + int32 ArrayIndexOfFirstInvalidWorkResult() const; + // Return the FTOPWorkResult at InArrayIndex in the WorkResult array, or nullptr if InArrayIndex is not a valid index. + FTOPWorkResult* GetWorkResultByArrayIndex(const int32& InArrayIndex); + + // Returns true if InNetwork is the parent TOP Net of this node. + bool IsParentTOPNetwork(UTOPNetwork const * const InNetwork) const; + + // Returns true if this node can still be auto-baked + bool CanStillBeAutoBaked() const; + #if WITH_EDITOR void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; #endif @@ -338,9 +529,6 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject UPROPERTY(Transient, NonTransactional) EPDGNodeState NodeState; - UPROPERTY(Transient, NonTransactional) - FWorkItemTally WorkItemTally; - // This is set when the TOP node's work items are processed by // FHoudiniPDGManager based on if any NotLoaded work result objects are found UPROPERTY(NonTransactional) @@ -355,7 +543,35 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject UPROPERTY(NonTransactional) bool bHasChildNodes; + // These notification events have been introduced so that we can start encapsulating code. + // in this class as opposed to modifying this object in various places throughout the codebase. + + // Notification that this TOP node has been dirtied. + void OnDirtyNode(); + + // Accessors for the landscape data caches + FHoudiniLandscapeExtent& GetLandscapeExtent() { return LandscapeExtent; } + FHoudiniLandscapeReferenceLocation& GetLandscapeReferenceLocation() { return LandscapeReferenceLocation; } + FHoudiniLandscapeTileSizeInfo& GetLandscapeSizeInfo() { return LandscapeSizeInfo; } + // More cached landscape data + UPROPERTY() + TSet ClearedLandscapeLayers; + + // Returns true if the node has received the HAPI_PDG_EVENT_COOK_COMPLETE event since the last the cook started + bool HasReceivedCookCompleteEvent() const { return bHasReceivedCookCompleteEvent; } + // Handler for when the node receives the HAPI_PDG_EVENT_COOK_START (called for each node when a TOPNet starts cooking) + void HandleOnPDGEventCookStart() { bHasReceivedCookCompleteEvent = false; } + // Handler for when the node receives the HAPI_PDG_EVENT_COOK_COMPLETE event (called for each node when a TOPNet completes cooking) + void HandleOnPDGEventCookComplete() { bHasReceivedCookCompleteEvent = true; } + protected: + void InvalidateLandscapeCache(); + + // Value caches used during landscape tile creation. + FHoudiniLandscapeReferenceLocation LandscapeReferenceLocation; + FHoudiniLandscapeTileSizeInfo LandscapeSizeInfo; + FHoudiniLandscapeExtent LandscapeExtent; + // Visible in the level UPROPERTY() bool bShow; @@ -364,6 +580,17 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject UPROPERTY() TMap BakedWorkResultObjectOutputs; + // This node's own work items, used when bHasChildNodes is false. + UPROPERTY(Transient, NonTransactional) + FWorkItemTally WorkItemTally; + // This node's aggregated work item tallys (sum of child work item tally, use when bHasChildNodes is true) + UPROPERTY(Transient, NonTransactional) + FAggregatedWorkItemTally AggregatedWorkItemTally; + + // Set to true when the node recieves HAPI_PDG_EVENT_COOK_COMPLETE event + UPROPERTY(Transient, NonTransactional) + bool bHasReceivedCookCompleteEvent; + private: UPROPERTY() FOutputActorOwner OutputActorOwner; @@ -377,6 +604,9 @@ class HOUDINIENGINERUNTIME_API UTOPNetwork : public UObject public: + // Delegate that is broadcast when cook of the network is complete. Parameters are the UTOPNetwork and bAnyFailedWorkItems. + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostCookDelegate, UTOPNetwork*, const bool); + // Constructor UTOPNetwork(); @@ -389,11 +619,22 @@ class HOUDINIENGINERUNTIME_API UTOPNetwork : public UObject // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output // objects and actors and sets the state to Deleted. - void DeleteWorkResultOutputObjects(); + void DeleteAllWorkResultObjectOutputs(); // Returns true if any node in this TOP net has pending (waiting, scheduled, cooking) work items. bool AnyWorkItemsPending() const; + // Returns true if any node in this TOP net has failed/errored work items. + bool AnyWorkItemsFailed() const; + + // Returns true if this network has nodes that can still be auto-baked + bool CanStillBeAutoBaked() const; + + // Handler for when a node in the newtork receives the HAPI_PDG_EVENT_COOK_COMPLETE event (called for each node when a TOPNet completes cooking) + void HandleOnPDGEventCookCompleteReceivedByChildNode(UHoudiniPDGAssetLink* const InAssetLink, UTOPNode* const InTOPNode); + + FOnPostCookDelegate& GetOnPostCookDelegate() { return OnPostCookDelegate; } + public: UPROPERTY(Transient, NonTransactional) @@ -419,11 +660,13 @@ class HOUDINIENGINERUNTIME_API UTOPNetwork : public UObject bool bShowResults; UPROPERTY() bool bAutoLoadResults; + + FOnPostCookDelegate OnPostCookDelegate; }; class UHoudiniPDGAssetLink; -DECLARE_MULTICAST_DELEGATE_FourParams(FHoudiniPDGAssetLinkWorkResultObjectLoaded, UHoudiniPDGAssetLink*, UTOPNode*, int32 /*WorkItemId*/, const FString& /*WorkResultObjectName*/); +DECLARE_MULTICAST_DELEGATE_FourParams(FHoudiniPDGAssetLinkWorkResultObjectLoaded, UHoudiniPDGAssetLink*, UTOPNode*, int32 /*WorkItemArrayIndex*/, int32 /*WorkItemResultInfoIndex*/); UCLASS() class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject @@ -433,6 +676,11 @@ class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject public: friend class UHoudiniAssetComponent; + + // Delegate for when the entire bake operation is complete (all selected nodes/networks have been baked). + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostBakeDelegate, UHoudiniPDGAssetLink*, const bool); + // Delegate for when a network completes a cook. Passes the asset link, the network, a bAnyWorkItemsFailed. + DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnPostTOPNetworkCookDelegate, UHoudiniPDGAssetLink*, UTOPNetwork*, const bool); static FString GetAssetLinkStatus(const EPDGLinkState& InLinkState); static FString GetTOPNodeStatus(const UTOPNode* InTOPNode); @@ -448,13 +696,17 @@ class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject void SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex); UTOPNode* GetSelectedTOPNode(); + const UTOPNode* GetSelectedTOPNode() const; UTOPNetwork* GetSelectedTOPNetwork(); + const UTOPNetwork* GetSelectedTOPNetwork() const; FString GetSelectedTOPNodeName(); FString GetSelectedTOPNetworkName(); UTOPNode* GetTOPNode(const int32& InNodeID); + bool GetTOPNodeAndNetworkByNodeId(const int32& InNodeID, UTOPNetwork*& OutNetwork, UTOPNode*& OutNode); UTOPNetwork* GetTOPNetwork(const int32& AtIndex); + const UTOPNetwork* GetTOPNetwork(const int32& AtIndex) const; // Find the node with relative path 'InNodePath' from its topnet. static UTOPNode* GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex); @@ -484,6 +736,14 @@ class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject // Results must be tagged with 'file', and must have a file path, otherwise will not be loaded. //void LoadResults(FTOPNode TOPNode, HAPI_PDG_WorkitemInfo workItemInfo, HAPI_PDG_WorkitemResultInfo[] resultInfos, HAPI_PDG_WorkitemId workItemID) + // Return the first UHoudiniAssetComponent in the parent chain. If this asset link is not + // owned by a HoudiniAssetComponent, a nullptr will be returned. + UHoudiniAssetComponent* GetOuterHoudiniAssetComponent() const; + + // Helper function to get the GUID of the owning HoudiniAssetComponent. Returns an invalid FGuid if + // GetOuterHoudiniAssetComponent() returns null. + FGuid GetOuterHoudiniComponentGuid() const; + // Gets the temporary cook folder. If the parent of this asset link is a HoudiniAssetComponent use that, otherwise // use the default static mesh temporary cook folder. FDirectoryPath GetTemporaryCookFolder() const; @@ -502,7 +762,26 @@ class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject // On all FTOPNodes: Load not loaded items if bAutoload is true, and update the level visibility of work items // result. Used when FTOPNode.bShow and/or FTOPNode.bAutoload changed. void UpdateTOPNodeAutoloadAndVisibility(); + +#if WITH_EDITORONLY_DATA + // Returns true if there are any nodes left that can/must still be auto-baked. + bool AnyRemainingAutoBakeNodes() const; +#endif + + // Delegate handlers + + // Get the post bake delegate + FOnPostBakeDelegate& GetOnPostBakeDelegate() { return OnPostBakeDelegate; } + // Called by baking code after baking all of the outputs + void HandleOnPostBake(const bool bInSuccess); + + FOnPostTOPNetworkCookDelegate& GetOnPostTOPNetworkCookDelegate() { return OnPostTOPNetworkCookDelegate; } + + // Handler for when a TOP network completes a cook. Called by the TOP Net once all of its nodes have received + // HAPI_PDG_EVENT_COOK_COMPLETE. + void HandleOnTOPNetworkCookComplete(UTOPNetwork* const InTOPNet); + #if WITH_EDITORONLY_DATA void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; #endif @@ -515,10 +794,6 @@ class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject void ClearAllTOPData(); - static void DestroyWorkItemResultData(FTOPWorkResult& Result, UTOPNode* InTOPNode); - - static void DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject); - public: //UPROPERTY() @@ -560,7 +835,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject UPROPERTY(NonTransactional) int32 NumWorkitems; UPROPERTY(Transient, NonTransactional) - FWorkItemTally WorkItemTally; + FAggregatedWorkItemTally WorkItemTally; UPROPERTY() FString OutputCachePath; @@ -585,6 +860,12 @@ class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject // Delegate that is broadcast when a work result object has been loaded FHoudiniPDGAssetLinkWorkResultObjectLoaded OnWorkResultObjectLoaded; + // Delegate that is broadcast after a bake. + FOnPostBakeDelegate OnPostBakeDelegate; + + // Delegate that is broadcast after a TOP Network completes a cook. + FOnPostTOPNetworkCookDelegate OnPostTOPNetworkCookDelegate; + // // End: Notifications // @@ -610,9 +891,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject UPROPERTY() bool bRecenterBakedActors; - // Auto-bake: if this is true, it indicates that a work result object should be baked after it is loaded. + // Auto-bake: if this is true, it indicates that once all work result objects for the node is loaded they should + // all be baked UPROPERTY() - bool bBakeAfterWorkResultObjectLoaded; + bool bBakeAfterAllWorkResultObjectsLoaded; // The delegate handle of the auto bake helper function bound to OnWorkResultObjectLoaded. FDelegateHandle AutoBakeDelegateHandle; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp index 93312206b..c2d744215 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,7 +25,6 @@ */ #include "HoudiniParameter.h" -#include "Engine/Engine.h" UHoudiniParameter::UHoudiniParameter(const FObjectInitializer & ObjectInitializer) : Super(ObjectInitializer) diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h index 355caebd7..f829e7ec9 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,6 +29,7 @@ #include "UObject/Object.h" #include "Curves/RealCurve.h" #include "HoudiniInput.h" +#include "HoudiniEngineRuntimeCommon.h" #include "HoudiniParameter.generated.h" @@ -61,19 +62,7 @@ enum class EHoudiniParameterType : uint8 Toggle, }; -UENUM() -enum class EHoudiniRampInterpolationType : int8 -{ - InValid = -1, - - CONSTANT = 0, - LINEAR = 1, - CATMULL_ROM = 2, - MONOTONE_CUBIC = 3, - BEZIER = 4, - BSPLINE = 5, - HERMITE = 6 -}; + UCLASS(DefaultToInstanced) class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject @@ -114,7 +103,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject virtual int32 GetChildIndex() const { return ChildIndex; }; virtual bool IsVisible() const { return bIsVisible; }; - virtual bool ShouldDisplay() const{ return bIsVisible && ParmType != EHoudiniParameterType::Invalid; }; + virtual bool ShouldDisplay() const{ return bIsVisible && bIsParentFolderVisible && ParmType != EHoudiniParameterType::Invalid; }; virtual bool IsDisabled() const { return bIsDisabled; }; virtual bool HasChanged() const { return bHasChanged; }; virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; @@ -160,6 +149,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject virtual bool IsDirectChildOfMultiParm() const { return bIsDirectChildOfMultiParm; }; virtual void SetVisible(const bool& InIsVisible) { bIsVisible = InIsVisible; }; + virtual void SetVisibleParent(const bool& InIsVisible) { bIsParentFolderVisible = InIsVisible; }; virtual void SetDisabled(const bool& InIsDisabled) { bIsDisabled = InIsDisabled; }; virtual void SetDefault(const bool& InIsDefault) { bIsDefault = InIsDefault; }; virtual void SetSpare(const bool& InIsSpare) { bIsSpare = InIsSpare; }; @@ -248,6 +238,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject UPROPERTY() bool bIsVisible; + // Is visible in hierarchy. (e.g. parm can be visible, but containing folder is not) + UPROPERTY() + bool bIsParentFolderVisible; + // UPROPERTY() bool bIsDisabled; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp index 067e6326f..269c23cc6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h index 2ca48e2ad..e35ee513e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp index 36df73246..785c2fc90 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h index b470e8145..a515e51cb 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp index 5ece33aca..86a5c9c22 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -64,6 +64,8 @@ UHoudiniParameterChoice::BeginDestroy() StringChoiceLabels.Empty(); StringChoiceValues.Empty(); + IntValuesArray.Empty(); + Super::BeginDestroy(); } @@ -93,6 +95,8 @@ UHoudiniParameterChoice::SetNumChoices(const int32& InNumChoices) StringChoiceValues.SetNumZeroed(InNumChoices); StringChoiceLabels.SetNumZeroed(InNumChoices); + IntValuesArray.SetNumZeroed(InNumChoices); + UpdateChoiceLabelsPtr(); } @@ -257,4 +261,9 @@ UHoudiniParameterChoice::RevertToDefault() MarkChanged(true); } -} \ No newline at end of file +} + +int32 UHoudiniParameterChoice::GetIndexFromValueArray(int32 Index) const +{ + return IntValuesArray.Find(Index); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h index 54804876f..d6ca3fda2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,11 +49,13 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterChoice : public UHoudiniParamete // Accessors //------------------------------------------------------------------------------------------------ - const int32 GetIntValue() const { return IntValue; }; + const int32 GetIntValueIndex() const { return IntValue; }; const FString GetStringValue() const { return StringValue; }; const int32 GetNumChoices() const { return StringChoiceLabels.Num(); }; const FString GetLabel() const { return StringChoiceLabels.IsValidIndex(IntValue) ? StringChoiceLabels[IntValue] : FString(); }; - const bool IsStringChoice() const { return ParmType == EHoudiniParameterType::StringChoice; }; + const bool IsStringChoice() const { return ParmType == EHoudiniParameterType::StringChoice; }; + const int32 GetIntValue(int32 InIntValueIndex) { return IntValuesArray.IsValidIndex(InIntValueIndex) ? IntValuesArray[InIntValueIndex] : IntValue; } + void SetIntValueArray(int32 Index, int32 Value) { if (IntValuesArray.IsValidIndex(Index)) IntValuesArray[Index] = Value; } bool IsDefault() const override; @@ -90,9 +92,12 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterChoice : public UHoudiniParamete void RevertToDefault() override; + int32 GetIndexFromValueArray(int32 Index) const; + protected: // Current int value for this property. + // More of an index to IntValuesArray UPROPERTY() int32 IntValue; @@ -122,4 +127,11 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterChoice : public UHoudiniParamete UPROPERTY() bool bIsChildOfRamp; -}; \ No newline at end of file + + // An array containing the values of all choices + // IntValues[i] should be i unless UseMenuItemTokenAsValue is enabled. + UPROPERTY() + TArray IntValuesArray; + + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp index 0af1918e0..e981690ce 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h index 20e73e131..7224b183f 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp index 9f89f52ee..c474d3cf9 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h index ca7f19e15..00a4ea8b6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp index ad7780d87..2cb089f1e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -72,6 +72,17 @@ UHoudiniParameterFloat::GetValue(int32 Idx) const return TOptional< float >(); } +bool +UHoudiniParameterFloat::GetValueAt(const int32& AtIndex, float& OutValue) const +{ + if (!Values.IsValidIndex(AtIndex)) + return false; + + OutValue = Values[AtIndex]; + + return true; +} + bool UHoudiniParameterFloat::SetValueAt(const float& InValue, const int32& AtIndex) { diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h index 2b95865fe..396221fb8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,6 +66,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterFloat : public UHoudiniParameter // Get value of this property TOptional< float > GetValue(int32 Idx) const; + bool GetValueAt(const int32& AtIndex, float& OutValue) const; // Write access to the value array float* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp index 3beff2598..4bb4968bc 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h index ce08b2b6f..206ca2ac6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp index 8ad011670..4f06ab098 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -64,7 +64,7 @@ UHoudiniParameterFolderList::IsTabParseFinished() const { for (auto & CurTab : TabFolders) { - if (!CurTab || CurTab->IsPendingKill()) + if (!IsValid(CurTab)) continue; if (!CurTab->IsTab()) diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h index a4ea6532e..011118cf6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp index e7015190c..f86a0f3ae 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -70,6 +70,17 @@ UHoudiniParameterInt::GetValue(int32 Idx) const return TOptional(); } +bool +UHoudiniParameterInt::GetValueAt(const int32& AtIndex, int32& OutValue) const +{ + if (!Values.IsValidIndex(AtIndex)) + return false; + + OutValue = Values[AtIndex]; + + return true; +} + bool UHoudiniParameterInt::SetValueAt(const int32& InValue, const int32& AtIndex) { diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h index 7b31b1d3e..7559b3d4a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -58,6 +58,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterInt : public UHoudiniParameter // Get value of this property TOptional GetValue(int32 Idx) const; + bool GetValueAt(const int32& AtIndex, int32& OutValue) const; int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp index abbd27049..a1939516d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h index fa9d13e72..780dda43c 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp index 396934443..23f8935fe 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,6 +53,16 @@ UHoudiniParameterMultiParm::Create( return HoudiniAssetParameter; } +bool +UHoudiniParameterMultiParm::SetValue(const int32& InValue) +{ + if (InValue == Value) + return false; + + Value = InValue; + + return true; +} void UHoudiniParameterMultiParm::InsertElement() @@ -118,6 +128,82 @@ UHoudiniParameterMultiParm::EmptyElements() } } +int32 +UHoudiniParameterMultiParm::GetNextInstanceCount() const +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + { + return MultiParmInstanceCount; + } + + int32 CurrentInstanceCount = 0; + // First determine how many instances the multi parm would have based on the current values in + // MultiParmInstanceLastModifyArray + for (const EHoudiniMultiParmModificationType& ModificationType : MultiParmInstanceLastModifyArray) + { + switch (ModificationType) + { + case EHoudiniMultiParmModificationType::Inserted: + case EHoudiniMultiParmModificationType::Modified: + case EHoudiniMultiParmModificationType::None: + CurrentInstanceCount++; + break; + case EHoudiniMultiParmModificationType::Removed: + // Removed indices don't add to CurrentInstanceCount + break; + } + } + + return CurrentInstanceCount; +} + +bool +UHoudiniParameterMultiParm::SetNumElements(const int32 InInstanceCount) +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + // // Log the MultiParmInstanceLastModifyArray before the modification + // HOUDINI_LOG_WARNING(TEXT("MultiParmInstanceLastModifyArray (before): ")); + // for (const EHoudiniMultiParmModificationType Modification : MultiParmInstanceLastModifyArray) + // { + // HOUDINI_LOG_WARNING(TEXT("\t%s"), *UEnum::GetValueAsString(Modification)); + // } + + const int32 TargetInstanceCount = InInstanceCount >= 0 ? InInstanceCount : 0; + const int32 CurrentInstanceCount = GetNextInstanceCount(); + bool bModified = false; + if (CurrentInstanceCount > TargetInstanceCount) + { + // Remove entries from the end of the array + for (int32 Count = CurrentInstanceCount; Count > TargetInstanceCount; --Count) + { + RemoveElement(-1); + } + + bModified = true; + } + else if (CurrentInstanceCount < TargetInstanceCount) + { + // Insert new instances at the end + for (int32 Count = CurrentInstanceCount; Count < TargetInstanceCount; ++Count) + { + InsertElement(); + } + + bModified = true; + } + + // // Log the MultiParmInstanceLastModifyArray after the modification + // HOUDINI_LOG_WARNING(TEXT("MultiParmInstanceLastModifyArray (after): ")); + // for (const EHoudiniMultiParmModificationType Modification : MultiParmInstanceLastModifyArray) + // { + // HOUDINI_LOG_WARNING(TEXT("\t%s"), *UEnum::GetValueAsString(Modification)); + // } + + return bModified; +} + void UHoudiniParameterMultiParm::InitializeModifyArray() { diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h index fc4afae8b..82ecbf369 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,8 +59,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterMultiParm : public UHoudiniParam int32 GetInstanceCount() const { return MultiParmInstanceCount; }; // Mutators - FORCEINLINE - void SetValue(const int32& InValue) { Value = InValue; }; + bool SetValue(const int32& InValue); FORCEINLINE void SetInstanceCount(const int32 InCount) { MultiParmInstanceCount = InCount; }; @@ -82,6 +81,20 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterMultiParm : public UHoudiniParam /** Empty the values, used by Slate. **/ void EmptyElements(); + /** + * Returns the number of multiparm instances there'll be after the next upload to HAPI, after + * the current state of MultiParmInstanceLastModifyArray is applied. + */ + int32 GetNextInstanceCount() const; + + /** + * Helper function to modify MultiParmInstanceLastModifyArray with inserts/removes (at the end) as necessary so + * that the multi parm instance count will be equal to InInstanceCount after the next upload to HAPI. + * @param InInstanceCount The number of instances the multiparm should have. + * @returns True if any changes were made to MultiParmInstanceLastModifyArray. + */ + bool SetNumElements(const int32 InInstanceCount); + UPROPERTY() bool bIsShown; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp index 09740047e..2bf983369 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h index 8a2e6d5ea..c22b93e5e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,6 +23,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + #pragma once #include "HoudiniParameter.h" diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp index 62e5208f4..a9bf0f2e2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,6 +29,7 @@ #include "HoudiniParameterFloat.h" #include "HoudiniParameterColor.h" #include "HoudiniParameterChoice.h" + #include "UObject/UnrealType.h" @@ -224,6 +225,14 @@ void UHoudiniParameterRampColorPoint::RemapParameters(const TMap& InParameters, const int32 InStartParamIndex) +{ + // Copy existing points to NewPoints + TArray NewPoints; + const int32 NumInstances = GetInstanceCount(); + NewPoints.Reserve(NumInstances); + for (UHoudiniParameterRampFloatPoint* const PointData : Points) + { + if (!IsValid(PointData)) + continue; + + PointData->InstanceIndex = NewPoints.Num(); + PointData->PositionParentParm = nullptr; + PointData->ValueParentParm = nullptr; + PointData->InterpolationParentParm = nullptr; + NewPoints.Add(PointData); + + // We don't need more than NumInstances points in the array + if (NewPoints.Num() == NumInstances) + break; + } + + // Loop over InParameters and look for children of this ramp + int32 CurrentInstanceIndex = 0; + const int32 NumParameters = InParameters.Num(); + for (int32 Index = InStartParamIndex; Index < NumParameters; ++Index) + { + UHoudiniParameter* const Param = InParameters[Index]; + if (!IsValid(Param)) + continue; + + if (!Param->GetIsChildOfMultiParm() || Param->GetParentParmId() != ParmId) + continue; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType != EHoudiniParameterType::Float && ParamType != EHoudiniParameterType::IntChoice) + continue; + + // Ensure we have a valid point object at the current index + UHoudiniParameterRampFloatPoint* Point = nullptr; + if (!NewPoints.IsValidIndex(CurrentInstanceIndex)) + { + Point = NewObject(this, FName(), this->GetMaskedFlags(RF_PropagateToSubObjects)); + Point->InstanceIndex = CurrentInstanceIndex; + NewPoints.Add(Point); + } + else + { + Point = NewPoints[CurrentInstanceIndex]; + } + + if (ParamType == EHoudiniParameterType::Float) + { + UHoudiniParameterFloat* FloatParameter = Cast(Param); + if (FloatParameter) + { + //*****Float Parameter (position)*****// + if (!Point->PositionParentParm) + { + if (FloatParameter->GetNumberOfValues() <= 0) + continue; + // Set the float ramp point's position parent parm, and value + Point->PositionParentParm = FloatParameter; + Point->SetPosition(FloatParameter->GetValuesPtr()[0]); + } + //*****Float Parameter (value)*****// + else + { + if (FloatParameter->GetNumberOfValues() <= 0) + continue;; + Point->ValueParentParm = FloatParameter; + Point->SetValue(FloatParameter->GetValuesPtr()[0]); + } + } + } + //*****Choice parameter (Interpolation)*****// + else if (ParamType == EHoudiniParameterType::IntChoice) + { + UHoudiniParameterChoice* ChoiceParameter = Cast(Param); + if (ChoiceParameter) + { + Point->InterpolationParentParm = ChoiceParameter; + Point->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValueIndex())); + CurrentInstanceIndex++; + } + } + } + + //*****All ramp points have been parsed, finish!*****// + if (NewPoints.Num() == NumInstances) + { + NewPoints.Sort([](const UHoudiniParameterRampFloatPoint& P1, const UHoudiniParameterRampFloatPoint& P2) { + return P1.Position < P2.Position; + }); + + Points = MoveTemp(NewPoints); + + // Not caching, points are synced, update cached points + if (!bCaching) + { + const int32 NumPoints = Points.Num(); + CachedPoints.SetNumZeroed(NumPoints); + + for (int32 i = 0; i < NumPoints; ++i) + { + UHoudiniParameterRampFloatPoint* const FromPoint = Points[i]; + UHoudiniParameterRampFloatPoint* ToPoint = CachedPoints[i]; + + // Nothing we can do/copy if FromPoint is null/pending kill + if (!IsValid(FromPoint)) + continue; + + if (!IsValid(ToPoint)) + { + ToPoint = FromPoint->DuplicateAndCopyState(this, RF_NoFlags, GetMaskedFlags(RF_PropagateToSubObjects)); + CachedPoints[i] = ToPoint; + } + else + { + ToPoint->CopyStateFrom(FromPoint, true); + } + } + } + + SetDefaultValues(); + + return true; + } + + return false; +} + +UHoudiniParameterRampColor::UHoudiniParameterRampColor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), + bCaching(false), + NumDefaultPoints(-1) +{ + ParmType = EHoudiniParameterType::ColorRamp; +} + UHoudiniParameterRampColor * UHoudiniParameterRampColor::Create( @@ -586,6 +736,142 @@ void UHoudiniParameterRampColor::RemapParameters(const TMap& InParameters, const int32 InStartParamIndex) +{ + // Copy existing points to NewPoints + TArray NewPoints; + const int32 NumInstances = GetInstanceCount(); + NewPoints.Reserve(NumInstances); + for (UHoudiniParameterRampColorPoint* const PointData : Points) + { + if (!IsValid(PointData)) + continue; + + PointData->InstanceIndex = NewPoints.Num(); + PointData->PositionParentParm = nullptr; + PointData->ValueParentParm = nullptr; + PointData->InterpolationParentParm = nullptr; + NewPoints.Add(PointData); + + // We don't need more than NumInstances points in the array + if (NewPoints.Num() == NumInstances) + break; + } + + // Loop over InParameters and look for children of this ramp + int32 CurrentInstanceIndex = 0; + const int32 NumParameters = InParameters.Num(); + for (int32 Index = InStartParamIndex; Index < NumParameters; ++Index) + { + UHoudiniParameter* const Param = InParameters[Index]; + if (!IsValid(Param)) + continue; + + if (!Param->GetIsChildOfMultiParm() || Param->GetParentParmId() != ParmId) + continue; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType != EHoudiniParameterType::Float && ParamType != EHoudiniParameterType::Color && + ParamType != EHoudiniParameterType::IntChoice) + continue; + + // Ensure we have a valid point object at the current index + UHoudiniParameterRampColorPoint* Point = nullptr; + if (!NewPoints.IsValidIndex(CurrentInstanceIndex)) + { + Point = NewObject(this, FName(), this->GetMaskedFlags(RF_PropagateToSubObjects)); + Point->InstanceIndex = CurrentInstanceIndex; + NewPoints.Add(Point); + } + else + { + Point = NewPoints[CurrentInstanceIndex]; + } + + if (ParamType == EHoudiniParameterType::Float) + { + UHoudiniParameterFloat* FloatParameter = Cast(Param); + if (FloatParameter) + { + //*****Float Parameter (position)*****// + if (!Point->PositionParentParm) + { + if (FloatParameter->GetNumberOfValues() <= 0) + continue; + // Set the float ramp point's position parent parm, and value + Point->PositionParentParm = FloatParameter; + Point->SetPosition(FloatParameter->GetValuesPtr()[0]); + } + } + } + else if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParameter = Cast(Param); + if (ColorParameter) + { + //*****Color Parameter (value)*****// + Point->ValueParentParm = ColorParameter; + Point->SetValue(ColorParameter->GetColorValue()); + } + } + //*****Choice parameter (Interpolation)*****// + else if (ParamType == EHoudiniParameterType::IntChoice) + { + UHoudiniParameterChoice* ChoiceParameter = Cast(Param); + if (ChoiceParameter) + { + Point->InterpolationParentParm = ChoiceParameter; + Point->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValueIndex())); + CurrentInstanceIndex++; + } + } + } + + //*****All ramp points have been parsed, finish!*****// + if (NewPoints.Num() == NumInstances) + { + NewPoints.Sort([](const UHoudiniParameterRampColorPoint& P1, const UHoudiniParameterRampColorPoint& P2) { + return P1.Position < P2.Position; + }); + + Points = MoveTemp(NewPoints); + + // Not caching, points are synced, update cached points + if (!bCaching) + { + const int32 NumPoints = Points.Num(); + CachedPoints.SetNumZeroed(NumPoints); + + for (int32 i = 0; i < NumPoints; ++i) + { + UHoudiniParameterRampColorPoint* const FromPoint = Points[i]; + UHoudiniParameterRampColorPoint* ToPoint = CachedPoints[i]; + + // Nothing we can do/copy if FromPoint is null/pending kill + if (!IsValid(FromPoint)) + continue; + + if (!IsValid(ToPoint)) + { + ToPoint = FromPoint->DuplicateAndCopyState(this, RF_NoFlags, GetMaskedFlags(RF_PropagateToSubObjects)); + CachedPoints[i] = ToPoint; + } + else + { + ToPoint->CopyStateFrom(FromPoint, true); + } + } + } + + SetDefaultValues(); + + return true; + } + + return false; +} + bool UHoudiniParameterRampColor::IsDefault() const { diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h index 90d938a8a..197a87034 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -207,7 +207,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColorPoint : public UObject UCLASS() class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloat : public UHoudiniParameterMultiParm { - GENERATED_BODY() + GENERATED_UCLASS_BODY() public: @@ -233,6 +233,15 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloat : public UHoudiniParam void CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); void CreateDeleteEvent(const int32 &InDeleteIndex); + + /** + * Update/populates the Points array from InParameters. + * @param InParameters An array of parameters containing this ramp multiparm's instances (the parameters for each + * of its points). + * @param InStartParamIndex The index in InParameters where this ramp multiparm's child parameters start. + * @return true if we found enough parameters to build a number of points == NumInstances(). + */ + bool UpdatePointsArray(const TArray& InParameters, const int32 InStartParamIndex); UPROPERTY() TArray Points; @@ -268,7 +277,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloat : public UHoudiniParam UCLASS() class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColor : public UHoudiniParameterMultiParm { - GENERATED_BODY() + GENERATED_UCLASS_BODY() public: @@ -281,6 +290,15 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColor : public UHoudiniParam virtual void RemapParameters(const TMap& ParameterMapping) override; + /** + * Update/populates the Points array from InParameters. + * @param InParameters An array of parameters containing this ramp multiparm's instances (the parameters for each + * of its points). + * @param InStartParamIndex The index in InParameters where this ramp multiparm's child parameters start. + * @return true if we found enough parameters to build a number of points == NumInstances(). + */ + bool UpdatePointsArray(const TArray& InParameters, const int32 InStartParamIndex); + UPROPERTY(Instanced) TArray Points; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp index c109c5ba6..9fe3a8db1 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h index 3394999aa..f408b88dc 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp index 646120f95..98dec4210 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -79,7 +79,7 @@ FString UHoudiniParameterString::GetAssetReference(UObject* InObject) { // Get the asset reference string for a given UObject - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return FString(); // Start by getting the Object's full name diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h index 0fac749d3..1f4c13bdc 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp index 37c2e21c5..5c7e1d205 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h index 0a5a60569..63209a9bf 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp index 1d5e7bc32..e54063122 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,7 +25,9 @@ */ #include "HoudiniPluginSerializationVersion.h" + #include "Serialization/CustomVersion.h" +#include "Misc/Guid.h" const FGuid FHoudiniCustomSerializationVersion::GUID( 0x1AB9CECC, 0x6913, 0x4875, 0x203d51fb ); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h index de1b6fbf9..e765a93ad 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,6 +26,7 @@ #pragma once +#include "Misc/Guid.h" // Deprecated per-class versions used to load old files // diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp index 9a8327060..61341bb2e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,6 +36,23 @@ #define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FHoudiniStaticMeshGenerationProperties::FHoudiniStaticMeshGenerationProperties() + : bGeneratedDoubleSidedGeometry(false) + , GeneratedPhysMaterial(nullptr) + , GeneratedCollisionTraceFlag(CTF_UseDefault) + , GeneratedLightMapResolution(64) + , GeneratedWalkableSlopeOverride() + , GeneratedLightMapCoordinateIndex(1) + , bGeneratedUseMaximumStreamingTexelRatio(false) + , GeneratedStreamingDistanceMultiplier(1.0f) + , GeneratedFoliageDefaultSettings(nullptr) + , GeneratedAssetUserData() +{ + DefaultBodyInstance.SetCollisionProfileName("BlockAll"); +} + + UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & ObjectInitializer ) : Super( ObjectInitializer ) { @@ -55,6 +72,7 @@ UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & Obj // Instantiating options. bShowMultiAssetDialog = true; + bPreferHdaMemoryCopyOverHdaSourceFile = false; // Cooking options. bPauseCookingOnStart = false; @@ -68,6 +86,7 @@ UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & Obj // Custom Houdini location. bUseCustomHoudiniLocation = false; CustomHoudiniLocation.Path = TEXT(""); + HoudiniExecutable = HRSHE_Houdini; // Arguments for HAPI_Initialize CookingThreadStackSize = -1; @@ -90,11 +109,45 @@ UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & Obj bEnableProxyStaticMeshRefinementOnPreSaveWorld = true; bEnableProxyStaticMeshRefinementOnPreBeginPIE = true; + // Generated StaticMesh settings. + bDoubleSidedGeometry = false; + PhysMaterial = nullptr; + DefaultBodyInstance.SetCollisionProfileName("BlockAll"); + CollisionTraceFlag = CTF_UseDefault; + LightMapResolution = 32; + LightMapCoordinateIndex = 1; + bUseMaximumStreamingTexelRatio = false; + StreamingDistanceMultiplier = 1.0f; + GeneratedDistanceFieldResolutionScale = 0.0f; + + // Static Mesh build settings. + bUseFullPrecisionUVs = false; + SrcLightmapIndex = 0; + DstLightmapIndex = 1; + MinLightmapResolution = 64; + bRemoveDegenerates = true; + GenerateLightmapUVsFlag = HRSRF_OnlyIfMissing; + RecomputeNormalsFlag = HRSRF_OnlyIfMissing; + RecomputeTangentsFlag = HRSRF_OnlyIfMissing; + bUseMikkTSpace = true; + bBuildAdjacencyBuffer = true; // v1 default false + + bComputeWeightedNormals = false; + bBuildReversedIndexBuffer = true; + bUseHighPrecisionTangentBasis = false; + bGenerateDistanceFieldAsIfTwoSided = false; + bSupportFaceRemap = false; + //BuildScale3D = FVector(1.0f, 1.0f, 1.0f); + DistanceFieldResolutionScale = 2.0f; // ue default is 1.0 + bPDGAsyncCommandletImportEnabled = false; // Legacy settings bEnableBackwardCompatibility = true; bAutomaticLegacyHDARebuild = false; + + // Curve inputs and editable output curves + bAddRotAndScaleAttributesOnCurves = false; } UHoudiniRuntimeSettings::~UHoudiniRuntimeSettings() diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h index 84476cd9d..dd55e6c60 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,9 +29,13 @@ #include "CoreMinimal.h" #include "UObject/Object.h" #include "Engine/EngineTypes.h" +#include "Engine/AssetUserData.h" +#include "PhysicsEngine/BodyInstance.h" #include "HoudiniRuntimeSettings.generated.h" +class UFoliageType_InstancedStaticMesh; + UENUM() enum EHoudiniRuntimeSettingsSessionType { @@ -50,6 +54,94 @@ enum EHoudiniRuntimeSettingsSessionType HRSST_MAX }; + +UENUM() +enum EHoudiniRuntimeSettingsRecomputeFlag +{ + // Recompute always. + HRSRF_Always UMETA(DisplayName = "Always"), + + // Recompute only if missing. + HRSRF_OnlyIfMissing UMETA(DisplayName = "Only if missing"), + + // Do not recompute. + HRSRF_Never UMETA(DisplayName = "Never"), + + HRSRF_MAX, +}; + +UENUM() +enum EHoudiniExecutableType +{ + // Houdini + HRSHE_Houdini UMETA(DisplayName = "Houdini"), + + // Houdini FX + HRSHE_HoudiniFX UMETA(DisplayName = "Houdini FX"), + + // Houdini Core + HRSHE_HoudiniCore UMETA(DisplayName = "Houdini Core"), + + // Houdini Indie + HRSHE_HoudiniIndie UMETA(DisplayName = "Houdini Indie"), +}; + +USTRUCT(BlueprintType) +struct HOUDINIENGINERUNTIME_API FHoudiniStaticMeshGenerationProperties +{ + GENERATED_USTRUCT_BODY() + + // Constructor + FHoudiniStaticMeshGenerationProperties(); + + public: + + /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Double Sided Geometry")) + uint32 bGeneratedDoubleSidedGeometry : 1; + + /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Simple Collision Physical Material")) + UPhysicalMaterial * GeneratedPhysMaterial; + + /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (FullyExpand = "true")) + struct FBodyInstance DefaultBodyInstance; + + /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Collision Complexity")) + TEnumAsByte GeneratedCollisionTraceFlag; + + /** Resolution of lightmap. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) + int32 GeneratedLightMapResolution; + + /** Custom walkable slope setting for generated mesh's body. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Walkable Slope Override")) + FWalkableSlopeOverride GeneratedWalkableSlopeOverride; + + /** The light map coordinate index. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Light map coordinate index")) + int32 GeneratedLightMapCoordinateIndex; + + /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) + uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; + + /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Streaming Distance Multiplier")) + float GeneratedStreamingDistanceMultiplier; + + /** Default settings when using this mesh for instanced foliage. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Foliage Default Settings")) + UFoliageType_InstancedStaticMesh* GeneratedFoliageDefaultSettings = nullptr; + + /** Array of user data stored with the asset. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Asset User Data")) + TArray GeneratedAssetUserData; +}; + + UCLASS(config = Engine, defaultconfig) class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject { @@ -83,7 +175,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject public: //------------------------------------------------------------------------------------------------------------- - // Session options. + // Session options. //------------------------------------------------------------------------------------------------------------- UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) TEnumAsByte SessionType; @@ -129,12 +221,19 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject //------------------------------------------------------------------------------------------------------------- // Whether to ask user to select an asset when instantiating an HDA with multiple assets inside. If disabled, will always instantiate first asset. - // TODO: PORT THE DIALOG!! UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) bool bShowMultiAssetDialog; + // When enabled, the plugin will always instantiate the memory copy of a HDA stored in the .uasset file + // instead of using the latest version of the HDA file itself. + // This helps ensuring consistency between users when using HDAs, but will not work with expanded HDAs. + // When disabled, the plugin will always instantiate the latest version of the source HDA file if it is + // available, and will fallback to the memory copy if the source file cannot be found + UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) + bool bPreferHdaMemoryCopyOverHdaSourceFile; + //------------------------------------------------------------------------------------------------------------- - // Cooking options. + // Cooking options. //------------------------------------------------------------------------------------------------------------- // Whether houdini engine cooking is paused or not upon initializing the plugin @@ -154,7 +253,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject FString DefaultBakeFolder; //------------------------------------------------------------------------------------------------------------- - // Parameter options. + // Parameter options. //------------------------------------------------------------------------------------------------------------- /* Deprecated! @@ -170,25 +269,33 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject // If true, generated Landscapes will be marshalled using default unreal scaling. // Generated landscape will loose a lot of precision on the Z axis but will use the same transforms // as Unreal's default landscape - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Use default Unreal scaling.")) bool MarshallingLandscapesUseDefaultUnrealScaling; + // If true, generated Landscapes will be using full precision for their ZAxis, // allowing for more precision but preventing them from being sculpted higher/lower than their min/max. - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Use full resolution for data conversion.")) bool MarshallingLandscapesUseFullResolution; + // If true, the min/max values used to convert heightfields to landscape will be forced values // This is usefull when importing multiple landscapes from different HDAs - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Force Min/Max values for data conversion")) bool MarshallingLandscapesForceMinMaxValues; + // The minimum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Forced min value")) float MarshallingLandscapesForcedMinValue; + // The maximum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Forced max value")) float MarshallingLandscapesForcedMaxValue; + // If this is enabled, additional rot & scale attributes are added on curve inputs + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Curves - Add rot & scale attributes on curve inputs")) + bool bAddRotAndScaleAttributesOnCurves; + // Default resolution used when converting Unreal Spline Components to Houdini Curves (step in cm between control points, 0 only send the control points) - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Curves - Default spline resolution (cm)")) float MarshallingSplineResolution; //------------------------------------------------------------------------------------------------------------- @@ -219,6 +326,139 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes On PIE", EditCondition = "bEnableProxyStaticMesh")) bool bEnableProxyStaticMeshRefinementOnPreBeginPIE; + //------------------------------------------------------------------------------------------------------------- + // Generated StaticMesh settings. + //------------------------------------------------------------------------------------------------------------- + + /// If true, the physics triangle mesh will use double sided faces for new Houdini Assets when doing scene queries. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Double Sided Geometry")) + uint32 bDoubleSidedGeometry : 1; + + /// Physical material to use for simple collision of new Houdini Assets. Encodes information about density, friction etc. + UPROPERTY(EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Simple Collision Physical Material")) + UPhysicalMaterial * PhysMaterial; + + /// Default properties of the body instance + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (FullyExpand = "true")) + struct FBodyInstance DefaultBodyInstance; + + /// Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate for new Houdini Assets. + UPROPERTY(GlobalConfig, VisibleDefaultsOnly, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Collision Complexity")) + TEnumAsByte CollisionTraceFlag; + + /// Resolution of lightmap for baked lighting. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) + int32 LightMapResolution; + + /// Bias multiplier for Light Propagation Volume lighting for new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, BlueprintReadOnly, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) + float LpvBiasMultiplier; + + /// Default Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) + float GeneratedDistanceFieldResolutionScale; + + /// Custom walkable slope setting for bodies of new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Walkable Slope Override")) + FWalkableSlopeOverride WalkableSlopeOverride; + + /// The UV coordinate index of lightmap + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Light map coordinate index")) + int32 LightMapCoordinateIndex; + + /// True if mesh should use a less-conservative method of mip LOD texture factor computation for new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) + uint32 bUseMaximumStreamingTexelRatio : 1; + + /// Allows artists to adjust the distance where textures using UV 0 are streamed in/out for new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Streaming Distance Multiplier")) + float StreamingDistanceMultiplier; + + /// Default settings when using new Houdini Asset mesh for instanced foliage. + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Foliage Default Settings")) + UFoliageType_InstancedStaticMesh * FoliageDefaultSettings; + + /// Array of user data stored with the new Houdini Asset. + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Asset User Data")) + TArray AssetUserData; + + //------------------------------------------------------------------------------------------------------------- + // Static Mesh build settings. + //------------------------------------------------------------------------------------------------------------- + + // If true, UVs will be stored at full floating point precision. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + bool bUseFullPrecisionUVs; + + // Source UV set for generated lightmap. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Source Lightmap Index")) + int32 SrcLightmapIndex; + + // Destination UV set for generated lightmap. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Destination Lightmap Index")) + int32 DstLightmapIndex; + + // Target lightmap resolution to for generated lightmap. Determines the padding between UV shells in a packed lightmap. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + int32 MinLightmapResolution; + + // If true, degenerate triangles will be removed. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + bool bRemoveDegenerates; + + // Lightmap UV generation + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Generate Lightmap UVs")) + TEnumAsByte GenerateLightmapUVsFlag; + + // Normals generation + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Recompute Normals")) + TEnumAsByte RecomputeNormalsFlag; + + // Tangents generation + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Recompute Tangents")) + TEnumAsByte RecomputeTangentsFlag; + + // If true, recomputed tangents and normals will be calculated using MikkT Space. This method does require properly laid out UVs though otherwise you'll get a degenerate tangent warning + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Generate Using MikkT Space")) + bool bUseMikkTSpace; + + // Required for PNT tessellation but can be slow. Recommend disabling for larger meshes. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + bool bBuildAdjacencyBuffer; + + // If true, we will use the surface area and the corner angle of the triangle as a ratio when computing the normals. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") + uint8 bComputeWeightedNormals : 1; + + // Required to optimize mesh in mirrored transform. Double index buffer size. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") + uint8 bBuildReversedIndexBuffer : 1; + + // If true, Tangents will be stored at 16 bit vs 8 bit precision. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") + uint8 bUseHighPrecisionTangentBasis : 1; + + // Scale to apply to the mesh when allocating the distance field volume texture. + // The default scale is 1, which is assuming that the mesh will be placed unscaled in the world. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") + float DistanceFieldResolutionScale; + + // Whether to generate the distance field treating every triangle hit as a front face. + // When enabled prevents the distance field from being discarded due to the mesh being open, but also lowers Distance Field AO quality. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Two-Sided Distance Field Generation")) + uint8 bGenerateDistanceFieldAsIfTwoSided : 1; + + // Enable the Physical Material Mask + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Enable Physical Material Mask")) + uint8 bSupportFaceRemap : 1; + + //------------------------------------------------------------------------------------------------------------- + // PDG Commandlet import + //------------------------------------------------------------------------------------------------------------- + // Is the PDG commandlet enabled? + UPROPERTY(GlobalConfig, EditAnywhere, Category = "PDG Settings", Meta = (DisplayName = "Async Importer Enabled")) + bool bPDGAsyncCommandletImportEnabled; + //------------------------------------------------------------------------------------------------------------- // Legacy //------------------------------------------------------------------------------------------------------------- @@ -241,6 +481,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Custom Houdini location")) FDirectoryPath CustomHoudiniLocation; + // Select the Houdini executable to be used when opening session sync or opening hip files + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Houdini Executable")) + TEnumAsByte HoudiniExecutable; + //------------------------------------------------------------------------------------------------------------- // HAPI_Initialize //------------------------------------------------------------------------------------------------------------- @@ -267,11 +511,4 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject // Sets HOUDINI_AUDIO_DSO_PATH UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) FString AudioDsoSearchPath; - - //------------------------------------------------------------------------------------------------------------- - // PDG Commandlet import - //------------------------------------------------------------------------------------------------------------- - // Is the PDG commandlet enabled? - UPROPERTY(GlobalConfig, EditAnywhere, Category = "PDG Settings", Meta=(DisplayName="Async Importer Enabled")) - bool bPDGAsyncCommandletImportEnabled; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp index 1d71bd834..91960b22e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -381,55 +381,22 @@ void UHoudiniSplineComponent::PostLoad() { Super::PostLoad(); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::PostLoad()] Component: %s"), *GetPathName()); - } TStructOnScope UHoudiniSplineComponent::GetComponentInstanceData() const { - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::GetComponentInstanceData()] Component: %s"), *GetPathName()); TStructOnScope ComponentInstanceData = MakeStructOnScope(this); FHoudiniSplineComponentInstanceData* InstanceData = ComponentInstanceData.Cast(); - - // NOTE: We need to capture these properties here before the component gets torn down - // since the Spline visualizer changed values on the instance directly and is not present on the - // template yet. - /*InstanceData->CurvePoints = CurvePoints; - InstanceData->DisplayPoints = DisplayPoints; - InstanceData->DisplayPointIndexDivider = DisplayPointIndexDivider; - -#if WITH_EDITOR_DATA - InstanceData->EditedControlPointsIndexes = EditedControlPointsIndexes; -#endif*/ - return ComponentInstanceData; } void -UHoudiniSplineComponent::ApplyComponentInstanceData(FHoudiniSplineComponentInstanceData* ComponentInstanceData, - const bool bPostUCS) +UHoudiniSplineComponent::ApplyComponentInstanceData( + FHoudiniSplineComponentInstanceData* ComponentInstanceData, const bool bPostUCS) { - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] Component: %s"), *GetPathName()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] Component: %p"), this); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] IsVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] bHiddenInGame: %d"), bHiddenInGame); - check(ComponentInstanceData); - - if (!bPostUCS) - { - //bHasChanged = ComponentInstanceData->bHasChanged; - //bNeedsToTriggerUpdate = ComponentInstanceData->bNeedsToTriggerUpdate; - /*CurvePoints = ComponentInstanceData->CurvePoints; - DisplayPoints = ComponentInstanceData->DisplayPoints; - DisplayPointIndexDivider = ComponentInstanceData->DisplayPointIndexDivider; - -#if WITH_EDITOR_DATA - EditedControlPointsIndexes = ComponentInstanceData->EditedControlPointsIndexes; -#endif*/ - } } void @@ -438,20 +405,9 @@ UHoudiniSplineComponent::CopyPropertiesFrom(UObject* FromObject) // Capture properties that we want to preserve during copy const int32 PrevNodeId = NodeId; - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] BEFORE - IsVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] BEFORE - bHiddenInGame: %d"), bHiddenInGame); - UActorComponent* FromComponent = Cast(FromObject); check(FromComponent); - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - //Params.bDoDelta = false; // Perform a deep copy - //Params.bClearReferences = false; - //UEngine::CopyPropertiesForUnrelatedObjects(FromComponent, this, Params); - - /*const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )(EditorUtilities::ECopyOptions::Default); - FHoudiniEngineRuntimeUtils::CopyComponentProperties(FromComponent, this, ComponentCopyOptions);*/ - UHoudiniSplineComponent* FromSplineComponent = Cast(FromObject); if (FromSplineComponent) { @@ -472,15 +428,10 @@ UHoudiniSplineComponent::CopyPropertiesFrom(UObject* FromObject) bIsEditableOutputCurve = FromSplineComponent->bIsEditableOutputCurve; bCookOnCurveChanged = FromSplineComponent->bCookOnCurveChanged; - - bHasChanged = FromSplineComponent->bHasChanged; bNeedsToTriggerUpdate = FromSplineComponent->bNeedsToTriggerUpdate; } - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] AFTER - IsVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] AFTER - bHiddenInGame: %d"), bHiddenInGame); - // Restore properties that we want to preserve NodeId = PrevNodeId; } @@ -495,9 +446,6 @@ UHoudiniSplineComponent::OnUnregister() void UHoudiniSplineComponent::OnComponentCreated() { - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] Component: %s"), *GetPathName()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] bVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] bHiddenInGame: %d"), bHiddenInGame); Super::OnComponentCreated(); } @@ -509,7 +457,10 @@ UHoudiniSplineComponent::OnComponentDestroyed(bool bDestroyingHierarchy) if (IsInputCurve()) { // This component can't just come out of nowhere and decide to delete an input object! - // We have rules and regulations for this sort of thing. Protocols to follow, forms to fill out, in triplicate! + // We have rules and regulations for this sort of thing. Protocols to follow, forms to fill out, in triplicate! + // The reason we can't delete these nodes here is because when we're using this component in a Blueprint, + // components will go through multiple reconstructions and destructions and in multiple worlds as well, causing + // the nodes in the Houdini session to disappear when we expect them to still be there. // InputObject->MarkPendingKill(); @@ -529,22 +480,7 @@ UHoudiniSplineComponent::PostEditUndo() bPostUndo = true; - // if (bIsInputCurve) - // { - // UHoudiniInputObject * CurrentInputObject = GetInputObject(); - // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - // return; - // - // CurrentInputObject->MarkChanged(true); - // } - // - // if (bIsEditableOutputCurve) - // { - // MarkChanged(true); - // } - MarkChanged(true); - } #endif @@ -586,22 +522,6 @@ bool UHoudiniSplineComponent::NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; - - // if (bIsInputCurve) - // { - // UHoudiniInputObject * CurrentInputObject = GetInputObject(); - // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - // return false; - // - // return CurrentInputObject->NeedsToTriggerUpdate(); - // } - // - // if (bIsEditableOutputCurve) - // { - // return bNeedsToTriggerUpdate; - // } - // - // return false; } void UHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate) @@ -612,34 +532,6 @@ void UHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& NeedsToTrigger void UHoudiniSplineComponent::SetCurveType(const EHoudiniCurveType & NewCurveType) { CurveType = NewCurveType; -#if WITH_EDITOR - //FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(this, TEXT("CurveType")); -#endif -} - -void -UHoudiniSplineComponent::MarkInputObjectChanged() -{ - // if (bIsInputCurve) - // { - // UHoudiniInputObject * CurrentInputObject = GetInputObject(); - // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - // return; - // - // if (HasChanged()) - // CurrentInputObject->MarkChanged(true); - // } - // - // if (bIsEditableOutputCurve) - // { - // if (HasChanged()) - // MarkChanged(true); - // } - - // NOTE: This component should be trying to push ANY state changes to Input or Output objects. This - // component should strictly be a data container. Input / Output objects that reference this component should - // be polling this component's state. - MarkChanged(true); } bool UHoudiniSplineComponent::HasChanged() const @@ -653,29 +545,14 @@ void UHoudiniSplineComponent::MarkChanged(const bool& Changed) bNeedsToTriggerUpdate = Changed; } -// UHoudiniAssetComponent* -// UHoudiniSplineComponent::GetParentHAC() -// { -// UHoudiniAssetComponent* ParentHAC = nullptr; -// if (bIsInputCurve) -// { -// if (!InputObject) -// return nullptr; -// -// UHoudiniInput* Input = Cast(InputObject->GetOuter()); -// if (!Input) -// return nullptr; -// -// ParentHAC = Cast(Input->GetOuter()); -// } -// else -// { -// // may do something else if this is not an input curve instead of returning Null. -// } -// -// return ParentHAC; -// -// } +void UHoudiniSplineComponent::MarkInputNodesAsPendingKill() +{ + // InputObject->MarkPendingKill(); + if(NodeId > -1) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); + + SetNodeId(-1); // Set nodeId to invalid for reconstruct on re-do +} FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData() { diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h index b777fb90f..3a05d0319 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -76,22 +76,18 @@ class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, void EditPointAtindex(const FTransform& NewPoint, const int32& Index); - // UHoudiniAssetComponent* GetParentHAC(); - void MarkModified(const bool & InModified) { bHasChanged = InModified; }; // To set the offset of default position of houdini curve void SetOffset(const float& Offset); - UE_DEPRECATED(4.25, "Use MarkChanged() instead") - // This component should not be aware of whether it is being referenced by any input - // or output objects. - void MarkInputObjectChanged(); - bool HasChanged() const; void MarkChanged(const bool& Changed); + // Mark the associated Houdini nodes for pending kill + void MarkInputNodesAsPendingKill(); + FORCEINLINE FString& GetHoudiniSplineName() { return HoudiniSplineName; } @@ -102,12 +98,6 @@ class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, void SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate); - // FORCEINLINE - // UHoudiniInputObject* GetInputObject() const { return InputObject; } - - // FORCEINLINE - // void SetInputObject(UHoudiniInputObject* NewInputObject) { InputObject = NewInputObject; } - FORCEINLINE EHoudiniCurveType GetCurveType() const { return CurveType; } @@ -229,7 +219,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, #endif protected: - /** Corresponding geo part object. **/ + // Corresponding geo part object. FHoudiniGeoPartObject HoudiniGeoPartObject; private: @@ -246,9 +236,6 @@ class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, UPROPERTY() bool bIsEditableOutputCurve; - // UPROPERTY() - // UHoudiniInputObject * InputObject; - // Corresponds to the Curve NodeId in Houdini UPROPERTY(Transient, DuplicateTransient) int32 NodeId; @@ -257,7 +244,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, FString PartName; }; -/** Used to store HoudiniAssetComponent data during BP reconstruction */ +// Used to store HoudiniAssetComponent data during BP reconstruction USTRUCT() struct FHoudiniSplineComponentInstanceData : public FActorComponentInstanceData { @@ -275,13 +262,6 @@ struct FHoudiniSplineComponentInstanceData : public FActorComponentInstanceData CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); } - // Persist all the required properties for being able to recook the HoudiniAsset from its existing state. - /*UPROPERTY() - bool bHasChanged; - - UPROPERTY() - bool bNeedsToTriggerUpdate;*/ - UPROPERTY() TArray CurvePoints; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp index c51f98505..4a97f9ef0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,6 +25,10 @@ */ #include "HoudiniStaticMesh.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "Async/ParallelFor.h" +#include "MeshUtilitiesCommon.h" UHoudiniStaticMesh::UHoudiniStaticMesh(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) @@ -32,7 +36,7 @@ UHoudiniStaticMesh::UHoudiniStaticMesh(const FObjectInitializer& ObjectInitializ bHasNormals = false; bHasTangents = false; bHasColors = false; - NumUVLayers = false; + NumUVLayers = 0; bHasPerFaceMaterials = false; } @@ -218,6 +222,124 @@ void UHoudiniStaticMesh::SetStaticMaterial(uint32 InMaterialIndex, const FStatic StaticMaterials[InMaterialIndex] = InStaticMaterial; } +void UHoudiniStaticMesh::CalculateNormals(bool bInComputeWeightedNormals) +{ + const int32 NumVertexInstances = GetNumVertexInstances(); + + // Pre-allocate space in the vertex instance normals array + VertexInstanceNormals.SetNum(NumVertexInstances); + + const int32 NumTriangles = GetNumTriangles(); + const int32 NumVertices = GetNumVertices(); + + // Setup a vertex normal array + TArray VertexNormals; + VertexNormals.SetNum(NumVertices); + + // Zero all entries in VertexNormals + // for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) + ParallelFor(NumVertices, [&VertexNormals](int32 VertexIndex) + { + VertexNormals[VertexIndex] = FVector::ZeroVector; + }); + + // Calculate face normals and sum them for each vertex that shares the triangle + // for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex) + ParallelFor(NumTriangles, [this, &VertexNormals, bInComputeWeightedNormals](int32 TriangleIndex) + { + const FIntVector& TriangleVertexIndices = TriangleIndices[TriangleIndex]; + + if (!VertexPositions.IsValidIndex(TriangleVertexIndices[0]) || + !VertexPositions.IsValidIndex(TriangleVertexIndices[1]) || + !VertexPositions.IsValidIndex(TriangleVertexIndices[2])) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexPositions index out of range %d, %d, %d, Num %d"), + TriangleVertexIndices[0], TriangleVertexIndices[1], TriangleVertexIndices[2], VertexPositions.Num()); + return; + } + + const FVector& V0 = VertexPositions[TriangleVertexIndices[0]]; + const FVector& V1 = VertexPositions[TriangleVertexIndices[1]]; + const FVector& V2 = VertexPositions[TriangleVertexIndices[2]]; + + FVector TriangleNormal = FVector::CrossProduct(V2 - V0, V1 - V0); + float Area = TriangleNormal.Size(); + TriangleNormal /= Area; + Area /= 2.0f; + + const float Weight[3] = { + bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V0, V1, V2) : 1.0f, + bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V1, V2, V0) : 1.0f, + bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V2, V0, V1) : 1.0f, + }; + + for (int CornerIndex = 0; CornerIndex < 3; ++CornerIndex) + { + const FVector WeightedNormal = TriangleNormal * Weight[CornerIndex]; + if (!WeightedNormal.IsNearlyZero(SMALL_NUMBER) && !WeightedNormal.ContainsNaN()) + { + if (!VertexNormals.IsValidIndex(TriangleVertexIndices[CornerIndex])) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexNormal index out of range %d, Num %d"), + TriangleVertexIndices[CornerIndex], VertexNormals.Num()); + continue; + } + VertexNormals[TriangleVertexIndices[CornerIndex]] += WeightedNormal; + } + } + }); + + // Normalize the vertex normals + // for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) + ParallelFor(NumVertices, [&VertexNormals](int32 VertexIndex) + { + VertexNormals[VertexIndex].Normalize(); + }); + + // Copy vertex normals to vertex instance normals + // for (int32 VertexInstanceIndex = 0; VertexInstanceIndex < NumVertexInstances; ++VertexInstanceIndex) + ParallelFor(NumVertexInstances, [this, &VertexNormals](int32 VertexInstanceIndex) + { + const int32 TriangleIndex = VertexInstanceIndex / 3; + const int32 CornerIndex = VertexInstanceIndex % 3; + const FIntVector& TriangleVertexIndices = TriangleIndices[TriangleIndex]; + if (!VertexNormals.IsValidIndex(TriangleVertexIndices[CornerIndex])) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexNormals index out of range %d, Num %d"), + TriangleVertexIndices[CornerIndex], VertexNormals.Num()); + return; + } + VertexInstanceNormals[VertexInstanceIndex] = VertexNormals[TriangleVertexIndices[CornerIndex]]; + }); + + bHasNormals = true; +} + +void UHoudiniStaticMesh::CalculateTangents(bool bInComputeWeightedNormals) +{ + const int32 NumVertexInstances = GetNumVertexInstances(); + + VertexInstanceUTangents.SetNum(NumVertexInstances); + VertexInstanceVTangents.SetNum(NumVertexInstances); + + // Calculate normals first if we don't have any + if (!HasNormals() || VertexInstanceNormals.Num() != NumVertexInstances) + CalculateNormals(bInComputeWeightedNormals); + + // for (int32 VertexInstanceIndex = 0; VertexInstanceIndex < NumVertexInstances; ++VertexInstanceIndex) + ParallelFor(NumVertexInstances, [this](int32 VertexInstanceIndex) + { + const FVector& Normal = VertexInstanceNormals[VertexInstanceIndex]; + Normal.FindBestAxisVectors( + VertexInstanceUTangents[VertexInstanceIndex], VertexInstanceVTangents[VertexInstanceIndex]); + }); + + bHasTangents = true; +} + void UHoudiniStaticMesh::Optimize() { VertexPositions.Shrink(); @@ -273,6 +395,45 @@ int32 UHoudiniStaticMesh::GetMaterialIndex(FName InMaterialSlotName) const return -1; } +bool UHoudiniStaticMesh::IsValid(bool bInSkipVertexIndicesCheck) const +{ + // Validate the number of vertices, indices and triangles. This is basically the same function as FRawMesh::IsValid() + const int32 NumVertices = GetNumVertices(); + const int32 NumVertexInstances = GetNumVertexInstances(); + const int32 NumTriangles = GetNumTriangles(); + + auto ValidateAttributeArraySize = [](int32 InArrayNum, int32 InExpectedSize) + { + return InArrayNum == 0 || InArrayNum == InExpectedSize; + }; + + bool bValid = NumVertices > 0 + && NumVertexInstances > 0 + && NumTriangles > 0 + && (NumVertexInstances / 3) == NumTriangles + && ValidateAttributeArraySize(MaterialIDsPerTriangle.Num(), NumTriangles) + && ValidateAttributeArraySize(VertexInstanceNormals.Num(), NumVertexInstances) + && ValidateAttributeArraySize(VertexInstanceUTangents.Num(), NumVertexInstances) + && ValidateAttributeArraySize(VertexInstanceVTangents.Num(), NumVertexInstances) + && ValidateAttributeArraySize(VertexInstanceColors.Num(), NumVertexInstances) + && NumUVLayers >= 0 + && VertexInstanceUVs.Num() == NumUVLayers * NumVertexInstances; + + if (!bInSkipVertexIndicesCheck) + { + int32 TriangleIndex = 0; + while (bValid && TriangleIndex < NumTriangles) + { + bValid = bValid && (TriangleIndices[TriangleIndex].X < NumVertices); + bValid = bValid && (TriangleIndices[TriangleIndex].Y < NumVertices); + bValid = bValid && (TriangleIndices[TriangleIndex].Z < NumVertices); + TriangleIndex++; + } + } + + return bValid; +} + void UHoudiniStaticMesh::Serialize(FArchive &InArchive) { Super::Serialize(InArchive); @@ -301,3 +462,4 @@ void UHoudiniStaticMesh::Serialize(FArchive &InArchive) MaterialIDsPerTriangle.Shrink(); MaterialIDsPerTriangle.BulkSerialize(InArchive); } + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h index c5e9b37ae..6f81b93f1 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -124,8 +124,31 @@ class HOUDINIENGINERUNTIME_API UHoudiniStaticMesh : public UObject UFUNCTION() uint32 AddStaticMaterial(const FStaticMaterial& InStaticMaterial) { return StaticMaterials.Add(InStaticMaterial); } - // Meant to be called after the mesh data arrays are populated. - // Currently only calls Shrink on the arrays + /** Calculate the normals of the mesh by calculating the face normal of each triangle (if a triangle has vertices + * V0, V1, V2, get the vector perpendicular to the face Pf = (V2 - V0) x (V1 - V0). To calculate the + * vertex normal for V0 sum and then normalize all its shared face normals. If bInComputeWeightedNormals is true + * then the weight of each face normal that contributes to V0's normal is the area of the face multiplied by the V0 + * corner angle of that face. If bInComputeWeightedNormals is false then the weight is 1. + * + * @param bInComputeWeightedNormals Whether or not to use weighted normal calculation. Defaults to false. + */ + UFUNCTION() + void CalculateNormals(bool bInComputeWeightedNormals=false); + + /** + * Calculate tangents from the normals. Calculates normals first via CalculateNormals() if the mesh does not yet + * have normals. + * + * @param bInComputeWeightedNormals Whether or not to use weighted normal calculation if CalculateNormals() is + * called. Defaults to false. + */ + UFUNCTION() + void CalculateTangents(bool bInComputeWeightedNormals=false); + + /** + * Meant to be called after the mesh data arrays are populated. + * Currently only calls Shrink on the arrays + */ UFUNCTION() void Optimize(); @@ -159,12 +182,20 @@ class HOUDINIENGINERUNTIME_API UHoudiniStaticMesh : public UObject UFUNCTION() const TArray& GetStaticMaterials() const { return StaticMaterials; } + TArray& GetStaticMaterials() { return StaticMaterials; } + UFUNCTION() UMaterialInterface* GetMaterial(int32 InMaterialIndex); UFUNCTION() int32 GetMaterialIndex(FName InMaterialSlotName) const; + // Checks if the mesh is valid by checking face, vertex and attribute (normals etc) counts. + // If bSkipVertexIndicesCheck is true, then we don't loop over all triangle vertex indices to + // check if each index is valid (< NumVertices) + UFUNCTION() + bool IsValid(bool bInSkipVertexIndicesCheck=false) const; + // Custom serialization: we use TArray::BulkSerialize to speed up array serialization virtual void Serialize(FArchive &InArchive) override; @@ -224,3 +255,4 @@ class HOUDINIENGINERUNTIME_API UHoudiniStaticMesh : public UObject UPROPERTY() TArray StaticMaterials; }; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp index 61a907df9..3bbcb3526 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -93,11 +93,19 @@ FPrimitiveSceneProxy* UHoudiniStaticMeshComponent::CreateSceneProxy() FBoxSphereBounds UHoudiniStaticMeshComponent::CalcBounds(const FTransform& InLocalToWorld) const { - FBox LocalBoundingBox = LocalBounds; - FBoxSphereBounds Ret(LocalBoundingBox.TransformBy(InLocalToWorld)); - Ret.BoxExtent *= BoundsScale; - Ret.SphereRadius *= BoundsScale; - return Ret; + if (Mesh) + { + // mesh bounds + FBoxSphereBounds NewBounds = LocalBounds.TransformBy(InLocalToWorld); + NewBounds.BoxExtent *= BoundsScale; + NewBounds.SphereRadius *= BoundsScale; + + return NewBounds; + } + else + { + return FBoxSphereBounds(InLocalToWorld.GetLocation(), FVector::ZeroVector, 0.f); + } } #if WITH_EDITOR @@ -138,11 +146,11 @@ void UHoudiniStaticMeshComponent::NotifyMeshUpdated() LocalBounds.Init(); } + UpdateBounds(); + #if WITH_EDITORONLY_DATA UpdateSpriteComponent(); #endif - - UpdateBounds(); } #if WITH_EDITORONLY_DATA @@ -150,7 +158,8 @@ void UHoudiniStaticMeshComponent::UpdateSpriteComponent() { if (SpriteComponent) { - SpriteComponent->SetRelativeLocation(FVector(0, 0, LocalBounds.GetExtent().Z)); + const FBoxSphereBounds B = Bounds.TransformBy(GetComponentTransform().ToInverseMatrixWithScale()); + SpriteComponent->SetRelativeLocation(B.Origin + FVector(0, 0, B.BoxExtent.Size())); SpriteComponent->SetVisibility(bHoudiniIconVisible); } } @@ -211,3 +220,4 @@ UMaterialInterface* UHoudiniStaticMeshComponent::GetMaterial(int32 MaterialIndex return Mesh ? Mesh->GetMaterial(MaterialIndex) : nullptr; } } + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h index 663beb3ff..e428c9d4e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp index 6a20ff8b6..9ba27f3e1 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -131,6 +131,9 @@ FHoudiniStaticMeshSceneProxy::FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshCom , FeatureLevel(InFeatureLevel) , Component(InComponent) , MaterialRelevance(InComponent ? InComponent->GetMaterialRelevance(InFeatureLevel) : FMaterialRelevance()) +#if STATICMESH_ENABLE_DEBUG_RENDERING + , Owner(InComponent ? InComponent->GetOwner() : nullptr) +#endif { } @@ -224,7 +227,8 @@ void FHoudiniStaticMeshSceneProxy::Build() void FHoudiniStaticMeshSceneProxy::GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const { - const bool bRenderAsWireframe = (AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe); + const FEngineShowFlags EngineShowFlags = ViewFamily.EngineShowFlags; + const bool bRenderAsWireframe = (AllowDebugViewmodes() && EngineShowFlags.Wireframe); // Set up the wireframe material FMaterialRenderProxy *WireframeMaterialProxy = nullptr; @@ -238,7 +242,7 @@ void FHoudiniStaticMeshSceneProxy::GetDynamicMeshElements(const TArray Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,6 +36,7 @@ #include "Rendering/PositionVertexBuffer.h" #include "Rendering/StaticMeshVertexBuffer.h" #include "DynamicMeshBuilder.h" +#include "StaticMeshResources.h" #include "HoudiniStaticMeshComponent.h" @@ -165,4 +166,8 @@ class FHoudiniStaticMeshSceneProxy : public FPrimitiveSceneProxy FMaterialRelevance MaterialRelevance; +private: +#if STATICMESH_ENABLE_DEBUG_RENDERING + AActor* Owner; +#endif }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineProcessor.h b/Source/HoudiniEngineRuntime/Private/HoudiniTranslatorTypes.cpp similarity index 66% rename from Source/HoudiniEngine/Private/HoudiniEngineProcessor.h rename to Source/HoudiniEngineRuntime/Private/HoudiniTranslatorTypes.cpp index 05b5eb527..b6b669d80 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineProcessor.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniTranslatorTypes.cpp @@ -1,5 +1,5 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. +/* +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,4 +24,32 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#pragma once \ No newline at end of file +#include "HoudiniTranslatorTypes.h" + +FHoudiniLandscapeTileSizeInfo::FHoudiniLandscapeTileSizeInfo() + : bIsCached(false) + , UnrealSizeX(-1) + , UnrealSizeY(-1) + , NumSectionsPerComponent(-1) + , NumQuadsPerSection(-1) +{ + +} + +FHoudiniLandscapeExtent::FHoudiniLandscapeExtent() + : bIsCached(false) + , MinX(0), MaxX(0), MinY(0), MaxY(0) + , ExtentsX(0) + , ExtentsY(0) +{ + +} + +FHoudiniLandscapeReferenceLocation::FHoudiniLandscapeReferenceLocation() + : bIsCached(false) + , SectionCoordX(0) + , SectionCoordY(0) + , TileLocationX(0.f) + , TileLocationY(0.f) +{ +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniTranslatorTypes.h b/Source/HoudiniEngineRuntime/Private/HoudiniTranslatorTypes.h new file mode 100644 index 000000000..a32aca0db --- /dev/null +++ b/Source/HoudiniEngineRuntime/Private/HoudiniTranslatorTypes.h @@ -0,0 +1,77 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineRuntimePrivatePCH.h" + +class ALandscape; + +// Used to cache data to use as a reference point during landscape construction +// The very first tile will calculate landscape +struct HOUDINIENGINERUNTIME_API FHoudiniLandscapeTileSizeInfo +{ + FHoudiniLandscapeTileSizeInfo(); + bool bIsCached; + + // Tile sizes + int32 UnrealSizeX; + int32 UnrealSizeY; + int32 NumSectionsPerComponent; + int32 NumQuadsPerSection; +}; + +// Used to cache the extent of the landscape so that it doesn't have to be recalculated +// for each landscape tile. +// The very first tile will calculate landscape +struct HOUDINIENGINERUNTIME_API FHoudiniLandscapeExtent +{ + FHoudiniLandscapeExtent(); + bool bIsCached; + + // Landscape extents (in quads) + int32 MinX, MaxX, MinY, MaxY; + int32 ExtentsX; + int32 ExtentsY; +}; + +// Used to cache data to use as a reference point during landscape construction +// The very first tile will calculate a reference point as well as a component-space location. +// Every subsequent tile can then derive a component-space location from this location. +struct HOUDINIENGINERUNTIME_API FHoudiniLandscapeReferenceLocation +{ + FHoudiniLandscapeReferenceLocation(); + + bool bIsCached; + // Absolute section base coordinate for the cached tile. + int32 SectionCoordX; + int32 SectionCoordY; + // Scaled location for the reference tile. + float TileLocationX; + float TileLocationY; + // Transform of the main landscape actor. + FTransform MainTransform; +}; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutputTypes.h b/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.cpp similarity index 73% rename from Source/HoudiniEngineRuntime/Private/HoudiniOutputTypes.h rename to Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.cpp index 5b6ac4591..fbb77433f 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutputTypes.h +++ b/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.cpp @@ -24,7 +24,15 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#pragma once +#include "IHoudiniAssetStateEvents.h" -#include "CoreMinimal.h" +void +IHoudiniAssetStateEvents::HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) +{ + if (InFromState == InToState) + return; + FOnHoudiniAssetStateChange& StateChangeDelegate = GetOnHoudiniAssetStateChangeDelegate(); + if (StateChangeDelegate.IsBound()) + StateChangeDelegate.Broadcast(InHoudiniAssetContext, InFromState, InToState); +} diff --git a/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.h b/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.h new file mode 100644 index 000000000..48c4f9b49 --- /dev/null +++ b/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.h @@ -0,0 +1,57 @@ +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" + +#include "HoudiniAssetStateTypes.h" + +#include "IHoudiniAssetStateEvents.generated.h" + +// Delegate for when EHoudiniAssetState changes from InFromState to InToState on an instantiated Houdini Asset (InHoudiniAssetContext). +DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnHoudiniAssetStateChange, UObject*, const EHoudiniAssetState, const EHoudiniAssetState); + +UINTERFACE() +class HOUDINIENGINERUNTIME_API UHoudiniAssetStateEvents : public UInterface +{ + GENERATED_BODY() +}; + + +/** + * EHoudiniAssetState events: event handlers for when a Houdini Asset changes state. + */ +class HOUDINIENGINERUNTIME_API IHoudiniAssetStateEvents +{ + GENERATED_BODY() + +public: + virtual void HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState); + + virtual FOnHoudiniAssetStateChange& GetOnHoudiniAssetStateChangeDelegate() = 0; +}; diff --git a/Source/HoudiniEngineRuntime/Private/Tests/HoudiniRuntimeTests.cpp b/Source/HoudiniEngineRuntime/Private/Tests/HoudiniRuntimeTests.cpp new file mode 100644 index 000000000..cb4d59173 --- /dev/null +++ b/Source/HoudiniEngineRuntime/Private/Tests/HoudiniRuntimeTests.cpp @@ -0,0 +1,17 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#if WITH_DEV_AUTOMATION_TESTS + +#include "HoudiniRuntimeTests.h" +#include "HoudiniEngineRuntime.h" +#include "Misc/AutomationTest.h" + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(HoudiniRuntimeTestAutomation, "Houdini.Runtime.TestAutomation", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter) + +bool HoudiniRuntimeTestAutomation::RunTest(const FString & Parameters) +{ + + return true; +} + +#endif \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/Tests/HoudiniRuntimeTests.h b/Source/HoudiniEngineRuntime/Private/Tests/HoudiniRuntimeTests.h new file mode 100644 index 000000000..a42240fbd --- /dev/null +++ b/Source/HoudiniEngineRuntime/Private/Tests/HoudiniRuntimeTests.h @@ -0,0 +1,7 @@ +#pragma once + +#if WITH_DEV_AUTOMATION_TESTS + +#include "CoreMinimal.h" + +#endif diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h b/Source/HoudiniEngineRuntime/Public/HoudiniAsset.h similarity index 97% rename from Source/HoudiniEngineRuntime/Private/HoudiniAsset.h rename to Source/HoudiniEngineRuntime/Public/HoudiniAsset.h index 508867722..284c497cb 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h +++ b/Source/HoudiniEngineRuntime/Public/HoudiniAsset.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,6 +36,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniAsset : public UObject { GENERATED_UCLASS_BODY() + friend class FHoudiniEditorEquivalenceUtils; + public: // UOBject functions diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h b/Source/HoudiniEngineRuntime/Public/HoudiniAssetActor.h similarity index 92% rename from Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h rename to Source/HoudiniEngineRuntime/Public/HoudiniAssetActor.h index 6736c8fd2..709c6c247 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h +++ b/Source/HoudiniEngineRuntime/Public/HoudiniAssetActor.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2018> Side Effects Software Inc. +* Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,14 +26,13 @@ #pragma once -#include "HoudiniAssetComponent.h" - #include "UObject/ObjectMacros.h" #include "Components/ActorComponent.h" #include "GameFramework/Actor.h" #include "HoudiniAssetActor.generated.h" +class UHoudiniAssetComponent; class UHoudiniPDGAssetLink; UCLASS(hidecategories = (Input), ConversionRoot, meta = (ChildCanTick), Blueprintable) @@ -53,7 +52,7 @@ class HOUDINIENGINERUNTIME_API AHoudiniAssetActor : public AActor bool IsUsedForPreview() const; // Gets the Houdini PDG asset link associated with this actor, if it has one. - UHoudiniPDGAssetLink* GetPDGAssetLink() const { return IsValid(HoudiniAssetComponent) ? HoudiniAssetComponent->GetPDGAssetLink() : nullptr; } + UHoudiniPDGAssetLink* GetPDGAssetLink() const; #if WITH_EDITOR diff --git a/Source/HoudiniEngineRuntime/Public/HoudiniEngineRuntimeCommon.h b/Source/HoudiniEngineRuntime/Public/HoudiniEngineRuntimeCommon.h new file mode 100644 index 000000000..68d77c6a2 --- /dev/null +++ b/Source/HoudiniEngineRuntime/Public/HoudiniEngineRuntimeCommon.h @@ -0,0 +1,167 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineRuntimeCommon.generated.h" + +UENUM() +enum class EHoudiniRampInterpolationType : int8 +{ + InValid = -1, + + CONSTANT = 0, + LINEAR = 1, + CATMULL_ROM = 2, + MONOTONE_CUBIC = 3, + BEZIER = 4, + BSPLINE = 5, + HERMITE = 6 +}; + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EHoudiniEngineBakeOption : uint8 +{ + ToActor, + ToBlueprint, + ToFoliage, + ToWorldOutliner, +}; +#endif + +UENUM() +enum class EHoudiniLandscapeOutputBakeType : uint8 +{ + Detachment, + BakeToImage, + BakeToWorld, + InValid, +}; + +UENUM() +enum class EHoudiniInputType : uint8 +{ + Invalid, + + Geometry, + Curve, + Asset, + Landscape, + World, + Skeletal +}; + +UENUM() +enum class EHoudiniOutputType : uint8 +{ + Invalid, + + Mesh, + Instancer, + Landscape, + Curve, + Skeletal +}; + +UENUM() +enum class EHoudiniCurveType : int8 +{ + Invalid = -1, + + Polygon = 0, + Nurbs = 1, + Bezier = 2, + Points = 3 +}; + +UENUM() +enum class EHoudiniCurveMethod : int8 +{ + Invalid = -1, + + CVs = 0, + Breakpoints = 1, + Freehand = 2 +}; + +UENUM() +enum class EHoudiniLandscapeExportType : uint8 +{ + Heightfield, + Mesh, + Points +}; + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EPDGBakeSelectionOption : uint8 +{ + All, + SelectedNetwork, + SelectedNode +}; +#endif + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EPDGBakePackageReplaceModeOption : uint8 +{ + CreateNewAssets, + ReplaceExistingAssets +}; +#endif + +// When attempting to refine proxy mesh outputs it is a possible that a cook is needed. +// This enum defines the possible return values on a request to refine proxies. +UENUM() +enum class EHoudiniProxyRefineResult : uint8 +{ + Invalid, + + // Refinement (or cook if needed) failed + Failed, + // Refinement completed successfully + Success, + // Refinement was skipped, either it was not necessary or the operation was cancelled by the user + Skipped +}; + +// When attempting to refine proxy mesh outputs it is a possible that a cook is needed. +// This enum defines the possible return values on a request to refine proxies. +UENUM() +enum class EHoudiniProxyRefineRequestResult : uint8 +{ + Invalid, + + // No refinement is needed + None, + // A cook is needed, refinement will commence automatically after the cook + PendingCooks, + // Successfully refined + Refined +}; +