2954 lines
88 KiB
C++
2954 lines
88 KiB
C++
/*
|
|
* 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 "HoudiniAssetComponent.h"
|
|
|
|
#include "HoudiniEngineRuntimePrivatePCH.h"
|
|
|
|
#include "HoudiniAsset.h"
|
|
#include "HoudiniAssetActor.h"
|
|
#include "HoudiniInput.h"
|
|
#include "HoudiniOutput.h"
|
|
#include "HoudiniParameter.h"
|
|
#include "HoudiniParameterButton.h"
|
|
#include "HoudiniParameterButtonStrip.h"
|
|
#include "HoudiniParameterOperatorPath.h"
|
|
#include "HoudiniHandleComponent.h"
|
|
#include "HoudiniPDGAssetLink.h"
|
|
#include "HoudiniEngineRuntime.h"
|
|
#include "HoudiniStaticMeshComponent.h"
|
|
#include "HoudiniCompatibilityHelpers.h"
|
|
|
|
#include "Engine/StaticMesh.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "TimerManager.h"
|
|
#include "Landscape.h"
|
|
#include "Components/HierarchicalInstancedStaticMeshComponent.h"
|
|
#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<UActorComponent *> ReregisterComponents; \
|
|
TArray<USceneComponent *> LocalAttachChildren;\
|
|
GetChildrenComponents(true, LocalAttachChildren); \
|
|
for (TArray<USceneComponent *>::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) \
|
|
{ \
|
|
COMPONENT_CLASS * Component = Cast<COMPONENT_CLASS>(*Iter); \
|
|
if (Component) \
|
|
{ \
|
|
Component->PROPERTY = PROPERTY; \
|
|
ReregisterComponents.Add(Component); \
|
|
} \
|
|
} \
|
|
\
|
|
if (ReregisterComponents.Num() > 0) \
|
|
{ \
|
|
FMultiComponentReregisterContext MultiComponentReregisterContext(ReregisterComponents); \
|
|
} \
|
|
} \
|
|
while(0)
|
|
|
|
|
|
void
|
|
UHoudiniAssetComponent::Serialize(FArchive& Ar)
|
|
{
|
|
int64 InitialOffset = Ar.Tell();
|
|
Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID);
|
|
|
|
bool bLegacyComponent = false;
|
|
if (Ar.IsLoading())
|
|
{
|
|
int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID);
|
|
if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE)
|
|
{
|
|
bLegacyComponent = true;
|
|
}
|
|
}
|
|
|
|
if (bLegacyComponent)
|
|
{
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault<UHoudiniRuntimeSettings>();
|
|
bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility;
|
|
|
|
// Legacy serialization
|
|
// Either try to convert or skip depending the setting value
|
|
if (bEnableBackwardCompatibility)
|
|
{
|
|
// Attemp to convert the v1 object to v2
|
|
HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : converting v1 object to v2."));
|
|
|
|
Super::Serialize(Ar);
|
|
// Deserialize the legacy data, we'll do the actual conversion in PostLoad()
|
|
// After everything has been deserialized
|
|
Version1CompatibilityHAC = NewObject<UHoudiniAssetComponent_V1>(this);
|
|
Version1CompatibilityHAC->Serialize(Ar);
|
|
}
|
|
else
|
|
{
|
|
// Skip the v1 object
|
|
HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : serialization will be skipped."));
|
|
|
|
Super::Serialize(Ar);
|
|
|
|
// Skip v1 Serialized data
|
|
if (FLinker* Linker = Ar.GetLinker())
|
|
{
|
|
int32 const ExportIndex = this->GetLinkerIndex();
|
|
FObjectExport& Export = Linker->ExportMap[ExportIndex];
|
|
Ar.Seek(InitialOffset + Export.SerialSize);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Normal v2 serialization
|
|
Super::Serialize(Ar);
|
|
}
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::ConvertLegacyData()
|
|
{
|
|
if (!Version1CompatibilityHAC || Version1CompatibilityHAC->IsPendingKill())
|
|
return false;
|
|
|
|
// Set the Houdini Asset
|
|
if (!Version1CompatibilityHAC->HoudiniAsset || Version1CompatibilityHAC->HoudiniAsset->IsPendingKill())
|
|
return false;
|
|
|
|
HoudiniAsset = Version1CompatibilityHAC->HoudiniAsset;
|
|
|
|
// Convert all parameters
|
|
for (auto& LegacyParmPair : Version1CompatibilityHAC->Parameters)
|
|
{
|
|
if (!LegacyParmPair.Value)
|
|
continue;
|
|
|
|
UHoudiniParameter* Parm = LegacyParmPair.Value->ConvertLegacyData(this);
|
|
LegacyParmPair.Value->CopyLegacyParameterData(Parm);
|
|
Parameters.Add(Parm);
|
|
}
|
|
|
|
// Convert all inputs
|
|
for (auto& LegacyInput : Version1CompatibilityHAC->Inputs)
|
|
{
|
|
// Convert v1 input to v2
|
|
UHoudiniInput* Input = LegacyInput->ConvertLegacyInput(this);
|
|
|
|
Inputs.Add(Input);
|
|
}
|
|
|
|
// Lambdas for finding/creating outputs from an HGPO
|
|
auto FindOrCreateOutput = [&](FHoudiniGeoPartObject& InNewHGPO, bool& bNew)
|
|
{
|
|
UHoudiniOutput* NewOutput = nullptr;
|
|
|
|
// See if we can add to an existing output
|
|
UHoudiniOutput** FoundOutput = nullptr;
|
|
FoundOutput = Outputs.FindByPredicate(
|
|
[InNewHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(InNewHGPO) : false; });
|
|
|
|
if (FoundOutput && *FoundOutput && !(*FoundOutput)->IsPendingKill())
|
|
{
|
|
// FoundOutput is valid, add to it
|
|
NewOutput = *FoundOutput;
|
|
bNew = false;
|
|
}
|
|
else
|
|
{
|
|
// Create a new output object
|
|
NewOutput = NewObject<UHoudiniOutput>(
|
|
this, UHoudiniOutput::StaticClass(), NAME_None, RF_NoFlags);
|
|
bNew = true;
|
|
}
|
|
|
|
return NewOutput;
|
|
};
|
|
|
|
|
|
// Convert all outputs
|
|
// Start by handling the Static Meshes
|
|
for (auto& LegacySM : Version1CompatibilityHAC->StaticMeshes)
|
|
{
|
|
// Convert the legacy HGPO to a v2 HGPO
|
|
FHoudiniGeoPartObject NewHGPO = LegacySM.Key.ConvertLegacyData();
|
|
|
|
|
|
bool bCreatedNew = false;
|
|
UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew);
|
|
if (!NewOutput || NewOutput->IsPendingKill())
|
|
continue;
|
|
|
|
// Add the HGPO if we've just created it
|
|
if (bCreatedNew)
|
|
{
|
|
NewOutput->AddNewHGPO(NewHGPO);
|
|
// Mark if the HoudiniOutput is editable
|
|
NewOutput->SetIsEditableNode(NewHGPO.bIsEditable);
|
|
}
|
|
|
|
// Build a new output object identifier
|
|
FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]);
|
|
Identifier.bLoaded = true;
|
|
Identifier.PartName = NewHGPO.PartName;
|
|
|
|
// Build/Update the output object
|
|
FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier);
|
|
OutputObj.OutputObject = LegacySM.Value;
|
|
OutputObj.OutputComponent = nullptr;
|
|
OutputObj.ProxyObject = nullptr;
|
|
OutputObj.ProxyComponent = nullptr;
|
|
OutputObj.bProxyIsCurrent = false;
|
|
|
|
// Handle the SMC for this SM / HGPO
|
|
if (LegacySM.Value && !LegacySM.Value->IsPendingKill())
|
|
{
|
|
UStaticMeshComponent** FoundSMC = Version1CompatibilityHAC->StaticMeshComponents.Find(LegacySM.Value);
|
|
if (FoundSMC && *FoundSMC && !(*FoundSMC)->IsPendingKill())
|
|
OutputObj.OutputComponent = *FoundSMC;
|
|
}
|
|
|
|
// Add to the outputs
|
|
Outputs.AddUnique(NewOutput);
|
|
|
|
//NewOutput->StaleCount;
|
|
//NewOutput->bLandscapeWorldComposition;
|
|
//NewOutput->HoudiniCreatedSocketActors;
|
|
//NewOutput->HoudiniAttachedSocketActors;
|
|
//NewOutput->bHasEditableNodeBuilt - false;
|
|
}
|
|
|
|
// ... then Landscapes
|
|
for (auto& LegacyLandscape : Version1CompatibilityHAC->LandscapeComponents)
|
|
{
|
|
// Convert the legacy HGPO to a v2 HGPO
|
|
FHoudiniGeoPartObject NewHGPO = LegacyLandscape.Key.ConvertLegacyData();
|
|
|
|
bool bCreatedNew = false;
|
|
UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew);
|
|
if (!NewOutput || NewOutput->IsPendingKill())
|
|
continue;
|
|
|
|
// Add the HGPO if we've just created it
|
|
if (bCreatedNew)
|
|
{
|
|
NewOutput->AddNewHGPO(NewHGPO);
|
|
// Mark if the HoudiniOutput is editable
|
|
NewOutput->SetIsEditableNode(NewHGPO.bIsEditable);
|
|
}
|
|
|
|
// Build a new output object identifier
|
|
FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]);
|
|
Identifier.bLoaded = true;
|
|
Identifier.PartName = NewHGPO.PartName;
|
|
|
|
// Build/Update the output object
|
|
FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier);
|
|
|
|
// We need to create a LandscapePtr wrapper for the landscaope
|
|
UHoudiniLandscapePtr* LandscapePtr = NewObject<UHoudiniLandscapePtr>(NewOutput);
|
|
LandscapePtr->SetSoftPtr(LegacyLandscape.Value.IsValid() ? LegacyLandscape.Value.Get() : nullptr);
|
|
|
|
OutputObj.OutputObject = LandscapePtr;
|
|
OutputObj.OutputComponent = nullptr;
|
|
OutputObj.ProxyObject = nullptr;
|
|
OutputObj.ProxyComponent = nullptr;
|
|
OutputObj.bProxyIsCurrent = false;
|
|
|
|
// Add to the outputs
|
|
Outputs.AddUnique(NewOutput);
|
|
}
|
|
|
|
// ... instancers
|
|
for (auto& LegacyInstanceIn : Version1CompatibilityHAC->InstanceInputs)
|
|
{
|
|
if (!LegacyInstanceIn || LegacyInstanceIn->IsPendingKill())
|
|
continue;
|
|
|
|
FHoudiniGeoPartObject InstancerHGPO = LegacyInstanceIn->HoudiniGeoPartObject.ConvertLegacyData();
|
|
|
|
// Prepare this output object's output identifier
|
|
FHoudiniOutputObjectIdentifier OutputIdentifier;
|
|
OutputIdentifier.ObjectId = InstancerHGPO.ObjectId;
|
|
OutputIdentifier.GeoId = InstancerHGPO.GeoId;
|
|
OutputIdentifier.PartId = InstancerHGPO.PartId;
|
|
OutputIdentifier.PartName = InstancerHGPO.PartName;
|
|
|
|
EHoudiniInstancerType InstancerType = EHoudiniInstancerType::ObjectInstancer;
|
|
if (LegacyInstanceIn->Flags.bIsPackedPrimitiveInstancer)
|
|
InstancerType = EHoudiniInstancerType::PackedPrimitive;
|
|
else if (LegacyInstanceIn->Flags.bAttributeInstancerOverride)
|
|
InstancerType = EHoudiniInstancerType::AttributeInstancer;
|
|
else if (LegacyInstanceIn->Flags.bIsAttributeInstancer)
|
|
InstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer;
|
|
else if (LegacyInstanceIn->ObjectToInstanceId >= 0)
|
|
InstancerType = EHoudiniInstancerType::ObjectInstancer;
|
|
|
|
InstancerHGPO.InstancerType = InstancerType;
|
|
|
|
//bool bIsMSIC = LegacyInstanceIn->Flags.bIsSplitMeshInstancer;
|
|
|
|
bool bCreatedNew = false;
|
|
UHoudiniOutput* NewOutput = FindOrCreateOutput(InstancerHGPO, bCreatedNew);
|
|
if (!NewOutput || NewOutput->IsPendingKill())
|
|
continue;
|
|
|
|
// Add the HGPO if we've just created it
|
|
if (bCreatedNew)
|
|
{
|
|
NewOutput->AddNewHGPO(InstancerHGPO);
|
|
}
|
|
|
|
// Get the output's instanced outputs
|
|
TMap<FHoudiniOutputObjectIdentifier, FHoudiniInstancedOutput>& InstancedOutputs = NewOutput->GetInstancedOutputs();
|
|
|
|
int32 InstFieldIdx = 0;
|
|
for (auto& LegacyInstanceInputField : LegacyInstanceIn->InstanceInputFields)
|
|
{
|
|
FHoudiniGeoPartObject InstInputFieldHGPO = LegacyInstanceInputField->HoudiniGeoPartObject.ConvertLegacyData();
|
|
|
|
// Create an instanced output for this object
|
|
FHoudiniInstancedOutput NewInstOut;
|
|
NewInstOut.OriginalObject = LegacyInstanceInputField->OriginalObject;
|
|
NewInstOut.OriginalObjectIndex = -1;
|
|
NewInstOut.OriginalTransforms = LegacyInstanceInputField->InstancedTransforms;
|
|
|
|
for (auto& InstObj : LegacyInstanceInputField->InstancedObjects)
|
|
NewInstOut.VariationObjects.Add(InstObj);
|
|
|
|
int32 NumVar = LegacyInstanceInputField->RotationOffsets.Num();
|
|
for (int32 Idx = 0; Idx < NumVar; Idx++)
|
|
{
|
|
FTransform TransOffset;
|
|
TransOffset.SetLocation(FVector::ZeroVector);
|
|
if (LegacyInstanceInputField->RotationOffsets.IsValidIndex(Idx))
|
|
TransOffset.SetRotation(LegacyInstanceInputField->RotationOffsets[Idx].Quaternion());
|
|
|
|
if (LegacyInstanceInputField->ScaleOffsets.IsValidIndex(Idx))
|
|
TransOffset.SetScale3D(LegacyInstanceInputField->ScaleOffsets[Idx]);
|
|
|
|
NewInstOut.VariationTransformOffsets.Add(TransOffset);
|
|
}
|
|
|
|
// Build an identifier for the instance output
|
|
FHoudiniOutputObjectIdentifier Identifier;
|
|
Identifier.ObjectId = InstInputFieldHGPO.ObjectId;
|
|
Identifier.GeoId = InstInputFieldHGPO.GeoId;
|
|
Identifier.PartId = InstInputFieldHGPO.PartId;
|
|
Identifier.PartName = InstInputFieldHGPO.PartName;
|
|
Identifier.SplitIdentifier = FString::FromInt(InstFieldIdx);
|
|
Identifier.bLoaded = true;
|
|
|
|
// Add the instance output to the outputs
|
|
InstancedOutputs.Add(Identifier, NewInstOut);
|
|
|
|
// Now create an Output object for each variation
|
|
int32 VarIdx = 0;
|
|
for (auto& LegacyComp : LegacyInstanceInputField->InstancerComponents)
|
|
{
|
|
// Build a new output object identifier for this variation
|
|
FHoudiniOutputObjectIdentifier VarIdentifier;
|
|
VarIdentifier.ObjectId = InstInputFieldHGPO.ObjectId;
|
|
VarIdentifier.GeoId = InstInputFieldHGPO.GeoId;
|
|
VarIdentifier.PartId = InstInputFieldHGPO.PartId;
|
|
VarIdentifier.PartName = InstInputFieldHGPO.PartName;
|
|
// Update the split identifier for this object
|
|
// We use both the original object index and the variation index: ORIG_VAR
|
|
VarIdentifier.SplitIdentifier =
|
|
FString::FromInt(InstFieldIdx) + TEXT("_") + FString::FromInt(VarIdx);
|
|
VarIdentifier.bLoaded = true;
|
|
|
|
// Build/Update the output object
|
|
FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(VarIdentifier);
|
|
|
|
OutputObj.OutputObject = nullptr;
|
|
OutputObj.OutputComponent = LegacyComp;
|
|
OutputObj.ProxyObject = nullptr;
|
|
OutputObj.ProxyComponent = nullptr;
|
|
OutputObj.bProxyIsCurrent = false;
|
|
|
|
VarIdx++;
|
|
}
|
|
|
|
// ???
|
|
//LegacyInstanceInputField->VariationTransformsArray;
|
|
//LegacyInstanceInputField->InstanceColorOverride;
|
|
//LegacyInstanceInputField->VariationInstanceColorOverrideArray;
|
|
|
|
// Index of the variation used for each transform
|
|
//NewInstOut.TransformVariationIndices;
|
|
//NewInstOut.bUniformScaleLocked = false;
|
|
|
|
InstFieldIdx++;
|
|
}
|
|
|
|
// Add to the outputs
|
|
Outputs.AddUnique(NewOutput);
|
|
}
|
|
|
|
// ... then Spline Components (for Curve IN)
|
|
for (auto& LegacyCurve : Version1CompatibilityHAC->SplineComponents)
|
|
{
|
|
UHoudiniSplineComponent* CurSplineComp = LegacyCurve.Value;
|
|
if (!CurSplineComp || CurSplineComp->IsPendingKill())
|
|
continue;
|
|
|
|
// TODO: Needed?
|
|
// Attach the spline to the HAC
|
|
CurSplineComp->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform);
|
|
|
|
// Editable curve? / Should create an output for it!
|
|
if (CurSplineComp->IsEditableOutputCurve())
|
|
{
|
|
FHoudiniGeoPartObject CurHGPO = LegacyCurve.Key.ConvertLegacyData();
|
|
|
|
// Look for an output for that HGPO
|
|
bool bCreatedNew = false;
|
|
UHoudiniOutput* NewOutput = FindOrCreateOutput(CurHGPO, bCreatedNew);
|
|
if (!NewOutput || NewOutput->IsPendingKill())
|
|
continue;
|
|
|
|
// Add the HGPO if we've just created it
|
|
if (bCreatedNew)
|
|
{
|
|
NewOutput->AddNewHGPO(CurHGPO);
|
|
}
|
|
|
|
// Build an output object id for the editable curve output
|
|
FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier;
|
|
EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId;
|
|
EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId;
|
|
EditableSplineComponentIdentifier.PartId = CurHGPO.PartId;
|
|
EditableSplineComponentIdentifier.PartName = CurHGPO.PartName;
|
|
|
|
TMap<FHoudiniOutputObjectIdentifier, FHoudiniOutputObject>& OutputObjects = NewOutput->GetOutputObjects();
|
|
FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier);
|
|
FoundOutputObject.OutputComponent = CurSplineComp;
|
|
|
|
//CurSplineComp->SetHasEditableNodeBuilt(true);
|
|
CurSplineComp->SetIsInputCurve(false);
|
|
}
|
|
else
|
|
{
|
|
// Input!
|
|
// Conversion of the inputs should have done the job already
|
|
CurSplineComp->SetIsInputCurve(true);
|
|
}
|
|
}
|
|
|
|
// ... Handles
|
|
for (auto& LegacyHandle : Version1CompatibilityHAC->HandleComponents)
|
|
{
|
|
// TODO: Handles!!
|
|
UHoudiniHandleComponent* NewHandle = nullptr;
|
|
HandleComponents.Add(NewHandle);
|
|
}
|
|
|
|
// ... Materials
|
|
UHoudiniAssetComponentMaterials_V1* LegacyMaterials = Version1CompatibilityHAC->HoudiniAssetComponentMaterials;
|
|
if(LegacyMaterials && !LegacyMaterials->IsPendingKill())
|
|
{
|
|
// Assignements: Apply to all outputs since they're not tied to an HGPO...
|
|
for (auto& CurOutput : Outputs)
|
|
{
|
|
TMap<FString, UMaterialInterface*>& CurrAssign = CurOutput->GetAssignementMaterials();
|
|
for (auto& LegacyMaterial : LegacyMaterials->Assignments)
|
|
{
|
|
CurrAssign.Add(LegacyMaterial.Key, LegacyMaterial.Value);
|
|
}
|
|
}
|
|
|
|
// Replacements
|
|
// Try to find the output matching the HGPO
|
|
for (auto& LegacyMaterial : LegacyMaterials->Replacements)
|
|
{
|
|
// Convert the legacy HGPO to a v2 HGPO
|
|
FHoudiniGeoPartObject NewHGPO = LegacyMaterial.Key.ConvertLegacyData();
|
|
|
|
TMap<FString, UMaterialInterface*>& LegacyReplacement = LegacyMaterial.Value;
|
|
|
|
bool bCreatedNew = false;
|
|
UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew);
|
|
if (!NewOutput || NewOutput->IsPendingKill())
|
|
continue;
|
|
|
|
if (bCreatedNew)
|
|
continue;
|
|
|
|
TMap<FString, UMaterialInterface*>& CurReplacement = NewOutput->GetReplacementMaterials();
|
|
for (auto& CurLegacyReplacement : LegacyReplacement)
|
|
{
|
|
CurReplacement.Add(CurLegacyReplacement.Key, CurLegacyReplacement.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ... Bake Name overrides
|
|
for (auto& LegacyBakeNameOverride : Version1CompatibilityHAC->BakeNameOverrides)
|
|
{
|
|
// In Outputs?
|
|
}
|
|
|
|
// ... then Downstream asset connections (due to Asset inputs)
|
|
for (auto& LegacyDownstreamHAC : Version1CompatibilityHAC->DownstreamAssetConnections)
|
|
{
|
|
//TSet<UHoudiniAssetComponent*> DownstreamHoudiniAssets;
|
|
}
|
|
|
|
// Then convert all remaing flags and properties
|
|
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();
|
|
|
|
ComponentGUID = Version1CompatibilityHAC->ComponentGUID;
|
|
|
|
bEnableCooking = Version1CompatibilityHAC->bEnableCooking;
|
|
bUploadTransformsToHoudiniEngine = Version1CompatibilityHAC->bUploadTransformsToHoudiniEngine;
|
|
bCookOnTransformChange = Version1CompatibilityHAC->bTransformChangeTriggersCooks;
|
|
bCookOnParameterChange = true;
|
|
//Version1CompatibilityHAC->bCookingTriggersDownstreamCooks;
|
|
bCookOnAssetInputCook = true;
|
|
bOutputless = false;
|
|
bOutputTemplateGeos = false;
|
|
bUseOutputNodes = false;
|
|
bFullyLoaded = Version1CompatibilityHAC->bFullyLoaded;
|
|
|
|
//bContainsHoudiniLogoGeometry = Version1CompatibilityHAC->bContainsHoudiniLogoGeometry;
|
|
//bIsNativeComponent = Version1CompatibilityHAC->bIsNativeComponent;
|
|
//bIsPreviewComponent = Version1CompatibilityHAC->bIsPreviewComponent;
|
|
//bLoadedComponent = Version1CompatibilityHAC->bLoadedComponent;
|
|
//bIsPlayModeActive_Unused = Version1CompatibilityHAC->bIsPlayModeActive_Unused;
|
|
//Version1CompatibilityHAC->bTimeCookInPlaymode_Unused;
|
|
//Version1CompatibilityHAC->bUseHoudiniMaterials;
|
|
|
|
//Version1CompatibilityHAC->GeneratedGeometryScaleFactor;
|
|
//Version1CompatibilityHAC->TransformScaleFactor;
|
|
//Version1CompatibilityHAC->PresetBuffer;
|
|
//Version1CompatibilityHAC->DefaultPresetBuffer;
|
|
//Version1CompatibilityHAC->ParameterByName;
|
|
|
|
// Now that we're done, update all the output's types
|
|
for (auto& CurOutput : Outputs)
|
|
{
|
|
CurOutput->UpdateOutputType();
|
|
}
|
|
|
|
//
|
|
// Clean up the legacy HAC
|
|
//
|
|
|
|
Version1CompatibilityHAC->Parameters.Empty();
|
|
Version1CompatibilityHAC->Inputs.Empty();
|
|
Version1CompatibilityHAC->StaticMeshes.Empty();
|
|
Version1CompatibilityHAC->LandscapeComponents.Empty();
|
|
Version1CompatibilityHAC->InstanceInputs.Empty();
|
|
Version1CompatibilityHAC->SplineComponents.Empty();
|
|
Version1CompatibilityHAC->HandleComponents.Empty();
|
|
//Version1CompatibilityHAC->HoudiniAssetComponentMaterials.Empty();
|
|
Version1CompatibilityHAC->BakeNameOverrides.Empty();
|
|
Version1CompatibilityHAC->DownstreamAssetConnections.Empty();
|
|
Version1CompatibilityHAC->MarkPendingKill();
|
|
Version1CompatibilityHAC = nullptr;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
HoudiniAsset = nullptr;
|
|
bCookOnParameterChange = true;
|
|
bUploadTransformsToHoudiniEngine = true;
|
|
bCookOnTransformChange = false;
|
|
//bUseNativeHoudiniMaterials = true;
|
|
bCookOnAssetInputCook = true;
|
|
|
|
AssetId = -1;
|
|
AssetState = EHoudiniAssetState::NewHDA;
|
|
AssetStateResult = EHoudiniAssetStateResult::None;
|
|
AssetCookCount = 0;
|
|
|
|
SubAssetIndex = -1;
|
|
|
|
// Make an invalid GUID, since we do not have any cooking requests.
|
|
HapiGUID.Invalidate();
|
|
|
|
HapiAssetName = FString();
|
|
|
|
// Create unique component GUID.
|
|
ComponentGUID = FGuid::NewGuid();
|
|
|
|
bUploadTransformsToHoudiniEngine = true;
|
|
|
|
bHasBeenLoaded = false;
|
|
bHasBeenDuplicated = false;
|
|
bPendingDelete = false;
|
|
bRecookRequested = false;
|
|
bRebuildRequested = false;
|
|
bEnableCooking = true;
|
|
bForceNeedUpdate = false;
|
|
bLastCookSuccess = false;
|
|
bBlueprintStructureModified = false;
|
|
bBlueprintModified = false;
|
|
|
|
//bEditorPropertiesNeedFullUpdate = true;
|
|
|
|
// Folder used for cooking, the value is initialized by Output Translator
|
|
// TemporaryCookFolder.Path = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER;
|
|
|
|
// Folder used for baking this asset's outputs, the value is initialized by Output Translator
|
|
// BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER;
|
|
|
|
bHasComponentTransformChanged = false;
|
|
|
|
bFullyLoaded = false;
|
|
|
|
bOutputless = false;
|
|
bOutputTemplateGeos = false;
|
|
bUseOutputNodes = false;
|
|
|
|
PDGAssetLink = nullptr;
|
|
|
|
StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh;
|
|
|
|
bOverrideGlobalProxyStaticMeshSettings = false;
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
if (HoudiniRuntimeSettings)
|
|
{
|
|
bEnableProxyStaticMeshOverride = HoudiniRuntimeSettings->bEnableProxyStaticMesh;
|
|
bEnableProxyStaticMeshRefinementByTimerOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer;
|
|
ProxyMeshAutoRefineTimeoutSecondsOverride = HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds;
|
|
bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld;
|
|
bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE;
|
|
}
|
|
else
|
|
{
|
|
bEnableProxyStaticMeshOverride = false;
|
|
bEnableProxyStaticMeshRefinementByTimerOverride = true;
|
|
ProxyMeshAutoRefineTimeoutSecondsOverride = 10.0f;
|
|
bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = true;
|
|
bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = true;
|
|
}
|
|
|
|
bNoProxyMeshNextCookRequested = false;
|
|
bBakeAfterNextCook = false;
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
bGenerateMenuExpanded = true;
|
|
bBakeMenuExpanded = true;
|
|
bAssetOptionMenuExpanded = true;
|
|
bHelpAndDebugMenuExpanded = true;
|
|
|
|
HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor;
|
|
|
|
bRemoveOutputAfterBake = false;
|
|
bRecenterBakedActors = false;
|
|
bReplacePreviousBake = false;
|
|
#endif
|
|
|
|
//
|
|
// Set component properties.
|
|
//
|
|
|
|
Mobility = EComponentMobility::Static;
|
|
|
|
SetGenerateOverlapEvents(false);
|
|
|
|
// Similar to UMeshComponent.
|
|
CastShadow = true;
|
|
bUseAsOccluder = true;
|
|
bCanEverAffectNavigation = true;
|
|
|
|
// This component requires render update.
|
|
bNeverNeedsRenderUpdate = false;
|
|
|
|
Bounds = FBox(ForceInitToZero);
|
|
|
|
LastTickTime = 0.0;
|
|
|
|
// Initialize the default SM Build settings with the plugin's settings default values
|
|
StaticMeshBuildSettings = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings();
|
|
}
|
|
|
|
UHoudiniAssetComponent::~UHoudiniAssetComponent()
|
|
{
|
|
// Unregister ourself so our houdini node can be delete.
|
|
|
|
// This gets called in UnRegisterHoudiniComponent, with appropriate checks. Don't call it here.
|
|
//FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true);
|
|
|
|
FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this);
|
|
}
|
|
|
|
void UHoudiniAssetComponent::PostInitProperties()
|
|
{
|
|
Super::PostInitProperties();
|
|
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault<UHoudiniRuntimeSettings>();
|
|
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);
|
|
}
|
|
|
|
UHoudiniAsset *
|
|
UHoudiniAssetComponent::GetHoudiniAsset() const
|
|
{
|
|
return HoudiniAsset;
|
|
}
|
|
|
|
FString
|
|
UHoudiniAssetComponent::GetDisplayName() const
|
|
{
|
|
return GetOwner() ? GetOwner()->GetName() : GetName();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::GetOutputs(TArray<UHoudiniOutput*>& OutOutputs) const
|
|
{
|
|
for (UHoudiniOutput* Output : Outputs)
|
|
{
|
|
OutOutputs.Add(Output);
|
|
}
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::IsProxyStaticMeshEnabled() const
|
|
{
|
|
if (bOverrideGlobalProxyStaticMeshSettings)
|
|
{
|
|
return bEnableProxyStaticMeshOverride;
|
|
}
|
|
else
|
|
{
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
if (HoudiniRuntimeSettings)
|
|
{
|
|
return HoudiniRuntimeSettings->bEnableProxyStaticMesh;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::IsProxyStaticMeshRefinementByTimerEnabled() const
|
|
{
|
|
if (bOverrideGlobalProxyStaticMeshSettings)
|
|
{
|
|
return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride;
|
|
}
|
|
else
|
|
{
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
if (HoudiniRuntimeSettings)
|
|
{
|
|
return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
float
|
|
UHoudiniAssetComponent::GetProxyMeshAutoRefineTimeoutSeconds() const
|
|
{
|
|
if (bOverrideGlobalProxyStaticMeshSettings)
|
|
{
|
|
return ProxyMeshAutoRefineTimeoutSecondsOverride;
|
|
}
|
|
else
|
|
{
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
if (HoudiniRuntimeSettings)
|
|
{
|
|
return HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds;
|
|
}
|
|
else
|
|
{
|
|
return 5.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const
|
|
{
|
|
if (bOverrideGlobalProxyStaticMeshSettings)
|
|
{
|
|
return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride;
|
|
}
|
|
else
|
|
{
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
if (HoudiniRuntimeSettings)
|
|
{
|
|
return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const
|
|
{
|
|
if (bOverrideGlobalProxyStaticMeshSettings)
|
|
{
|
|
return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride;
|
|
}
|
|
else
|
|
{
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
if (HoudiniRuntimeSettings)
|
|
{
|
|
return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
UHoudiniAssetComponent::SetHoudiniAsset(UHoudiniAsset * InHoudiniAsset)
|
|
{
|
|
// Check the asset validity
|
|
if (!InHoudiniAsset || InHoudiniAsset->IsPendingKill())
|
|
return;
|
|
|
|
// If it is the same asset, do nothing.
|
|
if ( InHoudiniAsset == HoudiniAsset )
|
|
return;
|
|
|
|
HoudiniAsset = InHoudiniAsset;
|
|
}
|
|
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnHoudiniAssetChanged()
|
|
{
|
|
// TODO: clear input/params/outputs?
|
|
Parameters.Empty();
|
|
|
|
// The asset has been changed, mark us as needing to be reinstantiated
|
|
MarkAsNeedInstantiation();
|
|
|
|
// Force an update on the next tick
|
|
bForceNeedUpdate = true;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::NeedUpdateParameters() const
|
|
{
|
|
// This is being split into a separate function to that it can
|
|
// be called separately for component templates.
|
|
|
|
if (!bCookOnParameterChange)
|
|
return false;
|
|
|
|
// Go through all our parameters, return true if they have been updated
|
|
for (auto CurrentParm : Parameters)
|
|
{
|
|
if (!CurrentParm || CurrentParm->IsPendingKill())
|
|
continue;
|
|
|
|
if (!CurrentParm->HasChanged())
|
|
continue;
|
|
|
|
// See if the parameter doesn't require an update
|
|
// (because it has failed to upload previously or has been loaded)
|
|
if (!CurrentParm->NeedsToTriggerUpdate())
|
|
continue;
|
|
|
|
HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateParameters()] Parameters need update for component: %s"), *(GetPathName()));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::NeedUpdateInputs() const
|
|
{
|
|
// Go through all our inputs, return true if they have been updated
|
|
for (auto CurrentInput : Inputs)
|
|
{
|
|
if (!CurrentInput || CurrentInput->IsPendingKill())
|
|
continue;
|
|
|
|
if (!CurrentInput->HasChanged())
|
|
continue;
|
|
|
|
// See if the input doesn't require an update
|
|
// (because it has failed to upload previously or has been loaded)
|
|
if (!CurrentInput->NeedsToTriggerUpdate())
|
|
continue;
|
|
|
|
HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateInputs()] Inputs need update for component: %s"), *(GetPathName()));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::HasPreviousBakeOutput() const
|
|
{
|
|
// Look for any bake output objects in the output array
|
|
for (const UHoudiniOutput* Output : Outputs)
|
|
{
|
|
if (!IsValid(Output))
|
|
continue;
|
|
|
|
if (BakedOutputs.Num() == 0)
|
|
return false;
|
|
|
|
for (const FHoudiniBakedOutput& BakedOutput : BakedOutputs)
|
|
{
|
|
if (BakedOutput.BakedOutputObjects.Num() > 0)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::NeedUpdate() const
|
|
{
|
|
if (AssetState != DebugLastAssetState)
|
|
{
|
|
DebugLastAssetState = AssetState;
|
|
}
|
|
|
|
// 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 false;
|
|
|
|
// We must have a valid asset
|
|
if (!HoudiniAsset || HoudiniAsset->IsPendingKill())
|
|
return false;
|
|
|
|
if (bForceNeedUpdate)
|
|
return true;
|
|
|
|
// If we don't want to cook on parameter/input change dont bother looking for updates
|
|
if (!bCookOnParameterChange && !bRecookRequested && !bRebuildRequested)
|
|
return false;
|
|
|
|
// Check if the HAC's transform has changed and transform triggers cook is enabled
|
|
if (bCookOnTransformChange && bHasComponentTransformChanged)
|
|
return true;
|
|
|
|
if (NeedUpdateParameters())
|
|
return true;
|
|
|
|
if (NeedUpdateInputs())
|
|
return true;
|
|
|
|
// Go through all outputs, filter the editable nodes. Return true if they have been updated.
|
|
for (auto CurrentOutput : Outputs)
|
|
{
|
|
if (!CurrentOutput || CurrentOutput->IsPendingKill())
|
|
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<FHoudiniOutputObjectIdentifier, FHoudiniOutputObject>& OutputObjects = CurrentOutput->GetOutputObjects();
|
|
for (auto& NextPair : OutputObjects)
|
|
{
|
|
// For now, only editable curves can trigger update
|
|
UHoudiniSplineComponent* HoudiniSplineComponent = Cast<UHoudiniSplineComponent>(NextPair.Value.OutputComponent);
|
|
if (!HoudiniSplineComponent)
|
|
continue;
|
|
|
|
// Output curves cant trigger an update!
|
|
if (HoudiniSplineComponent->bIsOutputCurve)
|
|
continue;
|
|
|
|
if (HoudiniSplineComponent->NeedsToTriggerUpdate())
|
|
return true;
|
|
}
|
|
}
|
|
|
|
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 (!CurrentParm || CurrentParm->IsPendingKill())
|
|
continue;
|
|
|
|
// Prevent the parm from triggering an update
|
|
CurrentParm->SetNeedsToTriggerUpdate(false);
|
|
}
|
|
|
|
// Same with inputs
|
|
for (auto CurrentInput : Inputs)
|
|
{
|
|
if (!CurrentInput || CurrentInput->IsPendingKill())
|
|
continue;
|
|
|
|
// Prevent the input from triggering an update
|
|
CurrentInput->SetNeedsToTriggerUpdate(false);
|
|
}
|
|
|
|
// Go through all outputs, filter the editable nodes.
|
|
for (auto CurrentOutput : Outputs)
|
|
{
|
|
if (!CurrentOutput || CurrentOutput->IsPendingKill())
|
|
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<FHoudiniOutputObjectIdentifier, FHoudiniOutputObject>& OutputObjects = CurrentOutput->GetOutputObjects();
|
|
for (auto& NextPair : OutputObjects)
|
|
{
|
|
// For now, only editable curves can trigger update
|
|
UHoudiniSplineComponent* HoudiniSplineComponent = Cast<UHoudiniSplineComponent>(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
|
|
{
|
|
// Go through all outputs
|
|
for (auto CurrentOutput : Outputs)
|
|
{
|
|
if (!CurrentOutput || CurrentOutput->IsPendingKill())
|
|
continue;
|
|
|
|
for (const auto& InstOutput : CurrentOutput->GetInstancedOutputs())
|
|
{
|
|
if (InstOutput.Value.bChanged)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UHoudiniAssetComponent::NeedBlueprintStructureUpdate() const
|
|
{
|
|
// TODO: Add similar flags to inputs, parametsr
|
|
return bBlueprintStructureModified;
|
|
}
|
|
|
|
bool UHoudiniAssetComponent::NeedBlueprintUpdate() const
|
|
{
|
|
// TODO: Add similar flags to inputs, parametsr
|
|
return bBlueprintModified;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::NotifyCookedToDownstreamAssets()
|
|
{
|
|
// Before notifying, clean up our downstream assets
|
|
// - check that they are still valid
|
|
// - check that we are still connected to one of its asset input
|
|
// - check that the asset as the CookOnAssetInputCook trigger enabled
|
|
TArray<UHoudiniAssetComponent*> DownstreamToDelete;
|
|
for(auto& CurrentDownstreamHAC : DownstreamHoudiniAssets)
|
|
{
|
|
// 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())
|
|
{
|
|
// Go through the HAC's input
|
|
for (auto& CurrentDownstreamInput : CurrentDownstreamHAC->Inputs)
|
|
{
|
|
if (!CurrentDownstreamInput || CurrentDownstreamInput->IsPendingKill())
|
|
continue;
|
|
|
|
EHoudiniInputType CurrentDownstreamInputType = CurrentDownstreamInput->GetInputType();
|
|
if (CurrentDownstreamInputType != EHoudiniInputType::Asset
|
|
&& CurrentDownstreamInputType != EHoudiniInputType::World)
|
|
continue;
|
|
|
|
if (!CurrentDownstreamInput->ContainsInputObject(this, CurrentDownstreamInputType))
|
|
continue;
|
|
|
|
if (CurrentDownstreamHAC->bCookOnAssetInputCook)
|
|
{
|
|
// Mark that HAC's input has changed
|
|
CurrentDownstreamInput->MarkChanged(true);
|
|
}
|
|
bRemoveDownstream = false;
|
|
}
|
|
}
|
|
|
|
if (bRemoveDownstream)
|
|
{
|
|
DownstreamToDelete.Add(CurrentDownstreamHAC);
|
|
}
|
|
}
|
|
|
|
for (auto ToDelete : DownstreamToDelete)
|
|
{
|
|
DownstreamHoudiniAssets.Remove(ToDelete);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::NeedsToWaitForInputHoudiniAssets()
|
|
{
|
|
for (auto& CurrentInput : Inputs)
|
|
{
|
|
if (!CurrentInput || CurrentInput->IsPendingKill())
|
|
continue;
|
|
|
|
EHoudiniInputType CurrentInputType = CurrentInput->GetInputType();
|
|
|
|
if(CurrentInputType != EHoudiniInputType::Asset && CurrentInputType != EHoudiniInputType::World)
|
|
continue;
|
|
|
|
TArray<UHoudiniInputObject*>* ObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInputType);
|
|
if (!ObjectArray)
|
|
continue;
|
|
|
|
for (auto& CurrentInputObject : (*ObjectArray))
|
|
{
|
|
// Get the input HDA
|
|
UHoudiniAssetComponent* InputHAC = CurrentInputObject
|
|
? Cast<UHoudiniAssetComponent>(CurrentInputObject->GetObject())
|
|
: nullptr;
|
|
|
|
if (!InputHAC)
|
|
continue;
|
|
|
|
// If the input HDA needs to be instantiated, force him to instantiate
|
|
// if the input HDA is in any other state than None, we need to wait for him
|
|
// to finish whatever it's doing
|
|
if (InputHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation)
|
|
{
|
|
// Tell the input HAC to instantiate
|
|
InputHAC->SetAssetState(EHoudiniAssetState::PreInstantiation);
|
|
|
|
// We need to wait
|
|
return true;
|
|
}
|
|
else if (InputHAC->GetAssetState() != EHoudiniAssetState::None)
|
|
{
|
|
// We need to wait
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::BeginDestroy()
|
|
{
|
|
if (CanDeleteHoudiniNodes())
|
|
{
|
|
}
|
|
|
|
// Gets called through UnRegisterHoudiniComponent().
|
|
//FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true);
|
|
|
|
// Unregister ourself so our houdini node can be deleted
|
|
FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this);
|
|
|
|
Super::BeginDestroy();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::MarkAsNeedCook()
|
|
{
|
|
// Force the asset state to NeedCook
|
|
//AssetCookCount = 0;
|
|
bHasBeenLoaded = true;
|
|
bPendingDelete = false;
|
|
bRecookRequested = true;
|
|
bRebuildRequested = false;
|
|
|
|
//bEditorPropertiesNeedFullUpdate = true;
|
|
|
|
// We need to mark all our parameters as changed/trigger update
|
|
for (auto CurrentParam : Parameters)
|
|
{
|
|
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<UHoudiniParameterButton>() || CurrentParam->IsA<UHoudiniParameterButtonStrip>())
|
|
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<UHoudiniSplineComponent>(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)
|
|
{
|
|
if (!IsValid(CurrentInput))
|
|
continue;
|
|
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<UHoudiniInputObject*>* 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
|
|
ClearRefineMeshesTimer();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::MarkAsNeedRebuild()
|
|
{
|
|
// Invalidate the asset ID
|
|
//AssetId = -1;
|
|
|
|
// Force the asset state to NeedRebuild
|
|
SetAssetState(EHoudiniAssetState::NeedRebuild);
|
|
AssetStateResult = EHoudiniAssetStateResult::None;
|
|
|
|
// Reset some of the asset's flag
|
|
//AssetCookCount = 0;
|
|
bHasBeenLoaded = true;
|
|
bPendingDelete = false;
|
|
bRecookRequested = false;
|
|
bRebuildRequested = true;
|
|
bFullyLoaded = false;
|
|
|
|
//bEditorPropertiesNeedFullUpdate = true;
|
|
|
|
// We need to mark all our parameters as changed/trigger update
|
|
for (auto CurrentParam : Parameters)
|
|
{
|
|
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<UHoudiniParameterButton>() || CurrentParam->IsA<UHoudiniParameterButtonStrip>())
|
|
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<UHoudiniSplineComponent>(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)
|
|
{
|
|
if (!IsValid(CurrentInput))
|
|
continue;
|
|
CurrentInput->MarkChanged(true);
|
|
CurrentInput->SetNeedsToTriggerUpdate(true);
|
|
CurrentInput->MarkDataUploadNeeded(true);
|
|
}
|
|
|
|
// Clear the static mesh bake timer
|
|
ClearRefineMeshesTimer();
|
|
}
|
|
|
|
// Marks the asset as needing to be instantiated
|
|
void
|
|
UHoudiniAssetComponent::MarkAsNeedInstantiation()
|
|
{
|
|
// Invalidate the asset ID
|
|
AssetId = -1;
|
|
|
|
if (Parameters.Num() <= 0 && Inputs.Num() <= 0 && Outputs.Num() <= 0)
|
|
{
|
|
// The asset has no parameters or inputs.
|
|
// This likely indicates it has never cooked/been instantiated.
|
|
// Set its state to NewHDA to force its instantiation
|
|
// so that we can have its parameters/input interface
|
|
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
|
|
SetAssetState(EHoudiniAssetState::NeedInstantiation);
|
|
}
|
|
|
|
AssetStateResult = EHoudiniAssetStateResult::None;
|
|
|
|
// Reset some of the asset's flag
|
|
AssetCookCount = 0;
|
|
bHasBeenLoaded = true;
|
|
bPendingDelete = false;
|
|
bRecookRequested = false;
|
|
bRebuildRequested = false;
|
|
bFullyLoaded = false;
|
|
|
|
//bEditorPropertiesNeedFullUpdate = true;
|
|
|
|
// We need to mark all our parameters as changed/not triggering update
|
|
for (auto CurrentParam : Parameters)
|
|
{
|
|
if (CurrentParam)
|
|
{
|
|
CurrentParam->MarkChanged(true);
|
|
CurrentParam->SetNeedsToTriggerUpdate(false);
|
|
}
|
|
}
|
|
|
|
// We need to mark all our inputs as changed/not triggering update
|
|
for (auto CurrentInput : Inputs)
|
|
{
|
|
if (CurrentInput)
|
|
{
|
|
CurrentInput->MarkChanged(true);
|
|
CurrentInput->SetNeedsToTriggerUpdate(false);
|
|
CurrentInput->MarkDataUploadNeeded(true);
|
|
}
|
|
}
|
|
|
|
/*if (!CanInstantiateAsset())
|
|
{
|
|
AssetState = EHoudiniAssetState::None;
|
|
AssetStateResult = EHoudiniAssetStateResult::None;
|
|
}*/
|
|
|
|
// Clear the static mesh bake timer
|
|
ClearRefineMeshesTimer();
|
|
}
|
|
|
|
void UHoudiniAssetComponent::MarkAsBlueprintStructureModified()
|
|
{
|
|
bBlueprintStructureModified = true;
|
|
}
|
|
|
|
void UHoudiniAssetComponent::MarkAsBlueprintModified()
|
|
{
|
|
bBlueprintModified = true;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault<UHoudiniRuntimeSettings>();
|
|
bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility;
|
|
bool bAutomaticLegacyHDARebuild = HoudiniRuntimeSettings->bAutomaticLegacyHDARebuild;
|
|
|
|
// Legacy serialization: either try to convert or skip depending the setting value
|
|
if (bEnableBackwardCompatibility && Version1CompatibilityHAC != nullptr)
|
|
{
|
|
// If we have deserialized legacy v1 data, attempt to convert it now
|
|
ConvertLegacyData();
|
|
|
|
if (bAutomaticLegacyHDARebuild)
|
|
MarkAsNeedRebuild();
|
|
else
|
|
MarkAsNeedInstantiation();
|
|
}
|
|
else
|
|
{
|
|
// Normal v2 objet, mark as need instantiation
|
|
MarkAsNeedInstantiation();
|
|
}
|
|
|
|
// Component has been loaded, not duplicated
|
|
bHasBeenDuplicated = false;
|
|
|
|
// We need to register ourself
|
|
RegisterHoudiniComponent(this);
|
|
|
|
// 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
|
|
UHoudiniAssetComponent::PostEditImport()
|
|
{
|
|
Super::PostEditImport();
|
|
|
|
MarkAsNeedInstantiation();
|
|
|
|
// Component has been duplicated, not loaded
|
|
// We do need the loaded flag to reapply parameters, inputs
|
|
// and properly update some of the output objects
|
|
bHasBeenDuplicated = true;
|
|
|
|
//RemoveAllAttachedComponents();
|
|
|
|
AssetState = EHoudiniAssetState::PreInstantiation;
|
|
AssetStateResult = EHoudiniAssetStateResult::None;
|
|
|
|
// TODO?
|
|
// REGISTER?
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::UpdatePostDuplicate()
|
|
{
|
|
// TODO:
|
|
// - Keep the output objects/components (remove duplicatetransient on the output object uproperties)
|
|
// - Duplicate created objects (ie SM) and materials
|
|
// - Update the output components to use these instead
|
|
// This should remove the need for a cook on duplicate
|
|
|
|
// For now, we simply clean some of the HAC's component manually
|
|
const TArray<USceneComponent*> Children = GetAttachChildren();
|
|
|
|
for (auto & NextChild : Children)
|
|
{
|
|
if (!NextChild || NextChild->IsPendingKill())
|
|
continue;
|
|
|
|
USceneComponent * ComponentToRemove = nullptr;
|
|
if (NextChild->IsA<UStaticMeshComponent>())
|
|
{
|
|
ComponentToRemove = NextChild;
|
|
}
|
|
else if (NextChild->IsA<UHoudiniStaticMeshComponent>())
|
|
{
|
|
ComponentToRemove = NextChild;
|
|
}
|
|
/* do not destroy attached duplicated editable curves, they are needed to restore editable curves
|
|
else if (NextChild->IsA<UHoudiniSplineComponent>())
|
|
{
|
|
// Remove duplicated editable curve output's Houdini Spline Component, since they will be re-built at duplication.
|
|
UHoudiniSplineComponent * HoudiniSplineComponent = Cast<UHoudiniSplineComponent>(NextChild);
|
|
if (HoudiniSplineComponent && HoudiniSplineComponent->IsEditableOutputCurve())
|
|
ComponentToRemove = NextChild;
|
|
}
|
|
*/
|
|
if (ComponentToRemove)
|
|
{
|
|
ComponentToRemove->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform);
|
|
ComponentToRemove->UnregisterComponent();
|
|
ComponentToRemove->DestroyComponent();
|
|
}
|
|
}
|
|
|
|
// if there is an associated PDG asset link, call its UpdatePostDuplicate to cleanup references to
|
|
// to the original instance's PDG output actors
|
|
if (IsValid(PDGAssetLink))
|
|
{
|
|
PDGAssetLink->UpdatePostDuplicate();
|
|
}
|
|
|
|
SetHasBeenDuplicated(false);
|
|
}
|
|
|
|
bool UHoudiniAssetComponent::IsInputTypeSupported(EHoudiniInputType InType) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool UHoudiniAssetComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::IsPreview() const
|
|
{
|
|
return bCachedIsPreview;
|
|
}
|
|
|
|
bool UHoudiniAssetComponent::IsValidComponent() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void UHoudiniAssetComponent::OnFullyLoaded()
|
|
{
|
|
bFullyLoaded = true;
|
|
}
|
|
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnComponentCreated()
|
|
{
|
|
// This event will only be fired for native Actor and native Component.
|
|
Super::OnComponentCreated();
|
|
|
|
if (!GetOwner() || !GetOwner()->GetWorld())
|
|
return;
|
|
|
|
/*
|
|
if (StaticMeshes.Num() == 0)
|
|
{
|
|
// Create Houdini logo static mesh and component for it.
|
|
CreateStaticMeshHoudiniLogoResource(StaticMeshes);
|
|
}
|
|
|
|
// Create replacement material object.
|
|
if (!HoudiniAssetComponentMaterials)
|
|
{
|
|
HoudiniAssetComponentMaterials =
|
|
NewObject< UHoudiniAssetComponentMaterials >(
|
|
this, UHoudiniAssetComponentMaterials::StaticClass(), NAME_None, RF_Public | RF_Transactional);
|
|
}
|
|
*/
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy)
|
|
{
|
|
|
|
if (CanDeleteHoudiniNodes())
|
|
{
|
|
}
|
|
|
|
// Unregister ourself so our houdini node can be deleted
|
|
FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this);
|
|
|
|
HoudiniAsset = nullptr;
|
|
|
|
// Clear Parameters
|
|
for (UHoudiniParameter*& CurrentParm : Parameters)
|
|
{
|
|
if (CurrentParm && !CurrentParm->IsPendingKill())
|
|
{
|
|
CurrentParm->ConditionalBeginDestroy();
|
|
}
|
|
else if (GetWorld() != NULL && GetWorld()->WorldType != EWorldType::PIE)
|
|
{
|
|
// TODO unneeded log?
|
|
// Avoid spamming that error when leaving PIE mode
|
|
HOUDINI_LOG_WARNING(TEXT("%s: null parameter when clearing"), GetOwner() ? *(GetOwner()->GetName()) : *GetName());
|
|
}
|
|
|
|
CurrentParm = nullptr;
|
|
}
|
|
|
|
Parameters.Empty();
|
|
|
|
// Clear Inputs
|
|
for (UHoudiniInput*& CurrentInput : Inputs)
|
|
{
|
|
if (!CurrentInput || CurrentInput->IsPendingKill())
|
|
continue;
|
|
|
|
if (CurrentInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad))
|
|
continue;
|
|
|
|
// Destroy connected Houdini asset.
|
|
CurrentInput->ConditionalBeginDestroy();
|
|
CurrentInput = nullptr;
|
|
}
|
|
|
|
Inputs.Empty();
|
|
|
|
// Clear Output
|
|
for (UHoudiniOutput*& CurrentOutput : Outputs)
|
|
{
|
|
if (!CurrentOutput || CurrentOutput->IsPendingKill())
|
|
continue;
|
|
|
|
if (CurrentOutput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad))
|
|
continue;
|
|
|
|
// Destroy all Houdini created socket actors.
|
|
TArray<AActor*> & CurCreatedSocketActors = CurrentOutput->GetHoudiniCreatedSocketActors();
|
|
for (auto & CurCreatedActor : CurCreatedSocketActors)
|
|
{
|
|
if (!CurCreatedActor || CurCreatedActor->IsPendingKill())
|
|
continue;
|
|
|
|
CurCreatedActor->Destroy();
|
|
}
|
|
CurCreatedSocketActors.Empty();
|
|
|
|
// Detach all Houdini attached socket actors
|
|
TArray<AActor*> & CurAttachedSocketActors = CurrentOutput->GetHoudiniAttachedSocketActors();
|
|
for (auto & CurAttachedSocketActor : CurAttachedSocketActors)
|
|
{
|
|
if (!CurAttachedSocketActor || CurAttachedSocketActor->IsPendingKill())
|
|
continue;
|
|
|
|
CurAttachedSocketActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform);
|
|
}
|
|
CurAttachedSocketActors.Empty();
|
|
|
|
#if WITH_EDITOR
|
|
// Clean up foliages instances
|
|
for (auto& CurrentOutputObject : CurrentOutput->GetOutputObjects())
|
|
{
|
|
// Foliage instancers store a HISMC in the components
|
|
UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast<UHierarchicalInstancedStaticMeshComponent>(CurrentOutputObject.Value.OutputComponent);
|
|
if (!FoliageHISMC)
|
|
continue;
|
|
|
|
UStaticMesh* FoliageSM = FoliageHISMC->GetStaticMesh();
|
|
if (!FoliageSM || FoliageSM->IsPendingKill())
|
|
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<AInstancedFoliageActor>(FoliageHISMC->GetOwner());
|
|
if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill())
|
|
continue;
|
|
|
|
UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM);
|
|
if (!FoliageType || FoliageType->IsPendingKill())
|
|
continue;
|
|
|
|
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)
|
|
{
|
|
// If the component still has instances left after the cleanup,
|
|
// make sure that we dont delete it, as the leftover instances are likely hand-placed
|
|
CurrentOutputObject.Value.OutputComponent = nullptr;
|
|
}
|
|
else
|
|
{
|
|
// Remove the foliage type if it doesn't have any more instances
|
|
InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
CurrentOutput->Clear();
|
|
// Destroy connected Houdini asset.
|
|
CurrentOutput->ConditionalBeginDestroy();
|
|
CurrentOutput = nullptr;
|
|
}
|
|
|
|
Outputs.Empty();
|
|
|
|
//FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true);
|
|
// Unregister ourself so our houdini node can be delete.
|
|
FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this);
|
|
|
|
// Clear the static mesh bake timer
|
|
ClearRefineMeshesTimer();
|
|
|
|
|
|
// Clear all TOP data and temporary geo/objects from the PDG asset link (if valid)
|
|
if (IsValid(PDGAssetLink))
|
|
{
|
|
#if WITH_EDITOR
|
|
const UWorld* const World = GetWorld();
|
|
if (IsValid(World))
|
|
{
|
|
// Only do this for editor worlds, only interactively (not during engine shutdown or garbage collection)
|
|
if (World->WorldType == EWorldType::Editor && GIsRunning && !GIsGarbageCollecting)
|
|
{
|
|
// In case we are recording a transaction (undo, for example) notify that the object will be
|
|
// modified.
|
|
PDGAssetLink->Modify();
|
|
PDGAssetLink->ClearAllTOPData();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
Super::OnComponentDestroyed(bDestroyingHierarchy);
|
|
}
|
|
|
|
void UHoudiniAssetComponent::RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent)
|
|
{
|
|
// Registration of this component is wrapped in this virtual function to allow
|
|
// derived classed to override this behaviour.
|
|
FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(InComponent);
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnRegister()
|
|
{
|
|
Super::OnRegister();
|
|
|
|
// NOTE: Wait until HoudiniEngineTick() before deciding to mark this object as fully loaded
|
|
// since preview components need to wait for component templates to finish their initialization
|
|
// before being able to perform state transfers.
|
|
|
|
/*
|
|
// We need to recreate render states for loaded components.
|
|
if (bLoadedComponent)
|
|
{
|
|
// Static meshes.
|
|
for (TMap< UStaticMesh *, UStaticMeshComponent * >::TIterator Iter(StaticMeshComponents); Iter; ++Iter)
|
|
{
|
|
UStaticMeshComponent * StaticMeshComponent = Iter.Value();
|
|
if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill())
|
|
{
|
|
// Recreate render state.
|
|
StaticMeshComponent->RecreateRenderState_Concurrent();
|
|
|
|
// Need to recreate physics state.
|
|
StaticMeshComponent->RecreatePhysicsState();
|
|
}
|
|
}
|
|
|
|
// Instanced static meshes.
|
|
for (auto& InstanceInput : InstanceInputs)
|
|
{
|
|
if (!InstanceInput || InstanceInput->IsPendingKill())
|
|
continue;
|
|
|
|
// Recreate render state.
|
|
InstanceInput->RecreateRenderStates();
|
|
|
|
// Recreate physics state.
|
|
InstanceInput->RecreatePhysicsStates();
|
|
}
|
|
}
|
|
*/
|
|
|
|
// Let TickInitialization() take care of manipulating the bFullyLoaded state.
|
|
//bFullyLoaded = true;
|
|
|
|
//// If we're constructing editable components in the SCS editor, set the component instance corresponding to this node for editing purposes
|
|
//
|
|
//USimpleConstructionScript* SCS = GetSCS();
|
|
//if (SCS == nullptr)
|
|
//{
|
|
// bFullyLoaded = true;
|
|
//}
|
|
//else
|
|
//{
|
|
// if(SCS->IsConstructingEditorComponents())
|
|
// {
|
|
// // We're not fully loaded yet. We're expecting
|
|
// }
|
|
// else
|
|
// {
|
|
// bFullyLoaded = true;
|
|
// }
|
|
//}
|
|
|
|
}
|
|
|
|
UHoudiniParameter*
|
|
UHoudiniAssetComponent::FindMatchingParameter(UHoudiniParameter* InOtherParam)
|
|
{
|
|
if (!InOtherParam || InOtherParam->IsPendingKill())
|
|
return nullptr;
|
|
|
|
for (auto CurrentParam : Parameters)
|
|
{
|
|
if (!CurrentParam || CurrentParam->IsPendingKill())
|
|
continue;
|
|
|
|
if (CurrentParam->Matches(*InOtherParam))
|
|
return CurrentParam;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
UHoudiniInput*
|
|
UHoudiniAssetComponent::FindMatchingInput(UHoudiniInput* InOtherInput)
|
|
{
|
|
if (!InOtherInput || InOtherInput->IsPendingKill())
|
|
return nullptr;
|
|
|
|
for (auto CurrentInput : Inputs)
|
|
{
|
|
if (!CurrentInput || CurrentInput->IsPendingKill())
|
|
continue;
|
|
|
|
if (CurrentInput->Matches(*InOtherInput))
|
|
return CurrentInput;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
UHoudiniHandleComponent*
|
|
UHoudiniAssetComponent::FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle)
|
|
{
|
|
if (!InOtherHandle || InOtherHandle->IsPendingKill())
|
|
return nullptr;
|
|
|
|
for (auto CurrentHandle : HandleComponents)
|
|
{
|
|
if (!CurrentHandle || CurrentHandle->IsPendingKill())
|
|
continue;
|
|
|
|
if (CurrentHandle->Matches(*InOtherHandle))
|
|
return CurrentHandle;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
UHoudiniParameter*
|
|
UHoudiniAssetComponent::FindParameterByName(const FString& InParamName)
|
|
{
|
|
for (auto CurrentParam : Parameters)
|
|
{
|
|
if (!CurrentParam || CurrentParam->IsPendingKill())
|
|
continue;
|
|
|
|
if (CurrentParam->GetParameterName().Equals(InParamName))
|
|
return CurrentParam;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnChildAttached(USceneComponent* ChildComponent)
|
|
{
|
|
Super::OnChildAttached(ChildComponent);
|
|
|
|
// ... Do corresponding things for other houdini component types.
|
|
// ...
|
|
}
|
|
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport)
|
|
{
|
|
Super::OnUpdateTransform(UpdateTransformFlags, Teleport);
|
|
|
|
SetHasComponentTransformChanged(true);
|
|
}
|
|
|
|
void UHoudiniAssetComponent::HoudiniEngineTick()
|
|
{
|
|
if (!IsFullyLoaded())
|
|
{
|
|
OnFullyLoaded();
|
|
}
|
|
}
|
|
|
|
|
|
#if WITH_EDITOR
|
|
void
|
|
UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent)
|
|
{
|
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
|
|
FProperty * Property = PropertyChangedEvent.MemberProperty;
|
|
if (!Property)
|
|
return;
|
|
|
|
FName PropertyName = Property->GetFName();
|
|
|
|
// Changing the Houdini Asset?
|
|
if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, HoudiniAsset))
|
|
{
|
|
OnHoudiniAssetChanged();
|
|
}
|
|
else if (PropertyName == GetRelativeLocationPropertyName()
|
|
|| PropertyName == GetRelativeRotationPropertyName()
|
|
|| PropertyName == GetRelativeScale3DPropertyName())
|
|
{
|
|
SetHasComponentTransformChanged(true);
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bOverrideGlobalProxyStaticMeshSettings)
|
|
|| PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bEnableProxyStaticMeshRefinementByTimerOverride)
|
|
|| PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, ProxyMeshAutoRefineTimeoutSecondsOverride))
|
|
{
|
|
ClearRefineMeshesTimer();
|
|
// Reset the timer
|
|
// SetRefineMeshesTimer will check the relevant settings and only set the timer if enabled via settings
|
|
SetRefineMeshesTimer();
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, Mobility))
|
|
{
|
|
// Changed GetAttachChildren to 'GetAllDescendants' due to HoudiniMeshSplitInstanceComponent
|
|
// not propagating property changes to their own child StaticMeshComponents.
|
|
TArray< USceneComponent * > LocalAttachChildren;
|
|
GetChildrenComponents(true, LocalAttachChildren);
|
|
|
|
// Mobility was changed, we need to update it for all attached components as well.
|
|
for (TArray< USceneComponent * >::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter)
|
|
{
|
|
USceneComponent * SceneComponent = *Iter;
|
|
SceneComponent->SetMobility(Mobility);
|
|
}
|
|
}
|
|
else if (PropertyName == TEXT("bVisible"))
|
|
{
|
|
// Visibility has changed, propagate it to children.
|
|
SetVisibility(IsVisible(), true);
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bHiddenInGame))
|
|
{
|
|
// Visibility has changed, propagate it to children.
|
|
SetHiddenInGame(bHiddenInGame, true);
|
|
}
|
|
else
|
|
{
|
|
// TODO:
|
|
// Propagate properties (mobility/visibility etc.. to children 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<UStaticMesh>(Pair.Value.OutputObject);
|
|
if (!StaticMesh || StaticMesh->IsPendingKill())
|
|
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<UActorComponent*> ReregisterComponents;
|
|
{
|
|
TArray<USceneComponent *> LocalAttachChildren;
|
|
GetChildrenComponents(true, LocalAttachChildren);
|
|
for (TArray< USceneComponent * >::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter)
|
|
{
|
|
UStaticMeshComponent * Component = Cast< UStaticMeshComponent >(*Iter);
|
|
if (!Component || Component->IsPendingKill())
|
|
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
|
|
|
|
|
|
#if WITH_EDITOR
|
|
void
|
|
UHoudiniAssetComponent::PostEditUndo()
|
|
{
|
|
Super::PostEditUndo();
|
|
|
|
if (!IsPendingKill())
|
|
{
|
|
// Make sure we are registered with the HER singleton
|
|
// We could be undoing a HoudiniActor delete
|
|
if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(this))
|
|
{
|
|
MarkAsNeedInstantiation();
|
|
|
|
// Component has been loaded, not duplicated
|
|
bHasBeenDuplicated = false;
|
|
|
|
RegisterHoudiniComponent(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
#if WITH_EDITOR
|
|
void
|
|
UHoudiniAssetComponent::OnActorMoved(AActor* Actor)
|
|
{
|
|
if (GetOwner() != Actor)
|
|
return;
|
|
|
|
SetHasComponentTransformChanged(true);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
UHoudiniAssetComponent::SetHasComponentTransformChanged(const bool& InHasChanged)
|
|
{
|
|
// Only update the value if we're fully loaded
|
|
// This avoid triggering a recook when loading a level
|
|
if(bFullyLoaded)
|
|
bHasComponentTransformChanged = InHasChanged;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink)
|
|
{
|
|
// Check the object validity
|
|
if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill())
|
|
return;
|
|
|
|
// If it is the same object, do nothing.
|
|
if (InPDGAssetLink == PDGAssetLink)
|
|
return;
|
|
|
|
PDGAssetLink = InPDGAssetLink;
|
|
}
|
|
|
|
|
|
FBoxSphereBounds
|
|
UHoudiniAssetComponent::CalcBounds(const FTransform & LocalToWorld) const
|
|
{
|
|
FBoxSphereBounds LocalBounds;
|
|
FBox BoundingBox = GetAssetBounds(nullptr, false);
|
|
if (BoundingBox.GetExtent() == FVector::ZeroVector)
|
|
BoundingBox.ExpandBy(1.0f);
|
|
|
|
LocalBounds = FBoxSphereBounds(BoundingBox);
|
|
// fix for offset bounds - maintain local bounds origin
|
|
LocalBounds.TransformBy(LocalToWorld);
|
|
|
|
const auto & LocalAttachedChildren = GetAttachChildren();
|
|
for (int32 Idx = 0; Idx < LocalAttachedChildren.Num(); ++Idx)
|
|
{
|
|
if (!LocalAttachedChildren[Idx])
|
|
continue;
|
|
|
|
FBoxSphereBounds ChildBounds = LocalAttachedChildren[Idx]->CalcBounds(LocalToWorld);
|
|
if (!ChildBounds.ContainsNaN())
|
|
LocalBounds = LocalBounds + ChildBounds;
|
|
}
|
|
|
|
return LocalBounds;
|
|
}
|
|
|
|
|
|
FBox
|
|
UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const
|
|
{
|
|
FBox BoxBounds(ForceInitToZero);
|
|
|
|
// Query the bounds for all output objects
|
|
for (auto & CurOutput : Outputs)
|
|
{
|
|
if (!CurOutput || CurOutput->IsPendingKill())
|
|
continue;
|
|
|
|
BoxBounds += CurOutput->GetBounds();
|
|
}
|
|
|
|
// Query the bounds for all our inputs
|
|
for (auto & CurInput : Inputs)
|
|
{
|
|
if (!CurInput || CurInput->IsPendingKill())
|
|
continue;
|
|
|
|
BoxBounds += CurInput->GetBounds();
|
|
}
|
|
|
|
// Query the bounds for all input parameters
|
|
for (auto & CurParam : Parameters)
|
|
{
|
|
if (!CurParam || CurParam->IsPendingKill())
|
|
continue;
|
|
|
|
if (CurParam->GetParameterType() != EHoudiniParameterType::Input)
|
|
continue;
|
|
|
|
UHoudiniParameterOperatorPath* InputParam = Cast<UHoudiniParameterOperatorPath>(CurParam);
|
|
if (!CurParam || CurParam->IsPendingKill())
|
|
continue;
|
|
|
|
if (!InputParam->HoudiniInput.IsValid())
|
|
continue;
|
|
|
|
BoxBounds += InputParam->HoudiniInput.Get()->GetBounds();
|
|
}
|
|
|
|
// Query the bounds for all our Houdini handles
|
|
for (auto & CurHandleComp : HandleComponents)
|
|
{
|
|
if (!CurHandleComp || CurHandleComp->IsPendingKill())
|
|
continue;
|
|
|
|
BoxBounds += CurHandleComp->GetBounds();
|
|
}
|
|
|
|
// Also scan all our decendants for SMC bounds not just top-level children
|
|
// ( split mesh instances' mesh bounds were not gathered proiperly )
|
|
TArray<USceneComponent*> LocalAttachedChildren;
|
|
LocalAttachedChildren.Reserve(16);
|
|
GetChildrenComponents(true, LocalAttachedChildren);
|
|
for (int32 Idx = 0; Idx < LocalAttachedChildren.Num(); ++Idx)
|
|
{
|
|
if (!LocalAttachedChildren[Idx])
|
|
continue;
|
|
|
|
USceneComponent * pChild = LocalAttachedChildren[Idx];
|
|
if (UStaticMeshComponent * StaticMeshComponent = Cast<UStaticMeshComponent>(pChild))
|
|
{
|
|
if (!StaticMeshComponent || StaticMeshComponent->IsPendingKill())
|
|
continue;
|
|
|
|
FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox();
|
|
if (StaticMeshBounds.IsValid)
|
|
BoxBounds += StaticMeshBounds;
|
|
}
|
|
}
|
|
|
|
// If nothing was found, init with the asset's location
|
|
if (BoxBounds.GetVolume() == 0.0f)
|
|
BoxBounds += GetComponentLocation();
|
|
|
|
return BoxBounds;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::ClearRefineMeshesTimer()
|
|
{
|
|
UWorld *World = GetWorld();
|
|
if (!World)
|
|
{
|
|
//HOUDINI_LOG_ERROR(TEXT("Cannot ClearRefineMeshesTimer, World is nullptr!"));
|
|
return;
|
|
}
|
|
|
|
World->GetTimerManager().ClearTimer(RefineMeshesTimer);
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SetRefineMeshesTimer()
|
|
{
|
|
UWorld *World = GetWorld();
|
|
if (!World)
|
|
{
|
|
HOUDINI_LOG_ERROR(TEXT("Cannot SetRefineMeshesTimer, World is nullptr!"));
|
|
return;
|
|
}
|
|
|
|
// Check if timer-based proxy mesh refinement is enable for this component
|
|
const bool bEnableTimer = IsProxyStaticMeshRefinementByTimerEnabled();
|
|
const float TimeSeconds = GetProxyMeshAutoRefineTimeoutSeconds();
|
|
if (bEnableTimer)
|
|
{
|
|
World->GetTimerManager().SetTimer(RefineMeshesTimer, this, &UHoudiniAssetComponent::OnRefineMeshesTimerFired, 1.0f, false, TimeSeconds);
|
|
}
|
|
else
|
|
{
|
|
World->GetTimerManager().ClearTimer(RefineMeshesTimer);
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnRefineMeshesTimerFired()
|
|
{
|
|
HOUDINI_LOG_MESSAGE(TEXT("UHoudiniAssetComponent::OnRefineMeshesTimerFired()"));
|
|
if (OnRefineMeshesTimerDelegate.IsBound())
|
|
{
|
|
OnRefineMeshesTimerDelegate.Broadcast(this);
|
|
}
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::HasAnyCurrentProxyOutput() const
|
|
{
|
|
for (const UHoudiniOutput *Output : Outputs)
|
|
{
|
|
if (Output->HasAnyCurrentProxy())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::HasAnyProxyOutput() const
|
|
{
|
|
for (const UHoudiniOutput *Output : Outputs)
|
|
{
|
|
if (Output->HasAnyProxy())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::HasAnyOutputComponent() const
|
|
{
|
|
for (UHoudiniOutput *Output : Outputs)
|
|
{
|
|
for(auto& CurrentOutputObject : Output->GetOutputObjects())
|
|
{
|
|
if(CurrentOutputObject.Value.OutputComponent)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::HasOutputObject(UObject* InOutputObjectToFind) const
|
|
{
|
|
for (const auto& CurOutput : Outputs)
|
|
{
|
|
for (const auto& CurOutputObject : CurOutput->GetOutputObjects())
|
|
{
|
|
if (CurOutputObject.Value.OutputObject == InOutputObjectToFind)
|
|
return true;
|
|
else if (CurOutputObject.Value.OutputComponent == InOutputObjectToFind)
|
|
return true;
|
|
else if (CurOutputObject.Value.ProxyObject == InOutputObjectToFind)
|
|
return true;
|
|
else if (CurOutputObject.Value.ProxyComponent == InOutputObjectToFind)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const
|
|
{
|
|
// Get the state of the asset and check if it is pre-cook, cooked, pending delete/rebuild or invalid
|
|
bOutNeedsRebuildOrDelete = false;
|
|
bOutInvalidState = false;
|
|
switch (AssetState)
|
|
{
|
|
case EHoudiniAssetState::NewHDA:
|
|
case EHoudiniAssetState::NeedInstantiation:
|
|
case EHoudiniAssetState::PreInstantiation:
|
|
case EHoudiniAssetState::Instantiating:
|
|
case EHoudiniAssetState::PreCook:
|
|
case EHoudiniAssetState::Cooking:
|
|
case EHoudiniAssetState::PostCook:
|
|
case EHoudiniAssetState::PreProcess:
|
|
case EHoudiniAssetState::Processing:
|
|
return false;
|
|
break;
|
|
case EHoudiniAssetState::None:
|
|
return true;
|
|
break;
|
|
case EHoudiniAssetState::NeedRebuild:
|
|
case EHoudiniAssetState::NeedDelete:
|
|
case EHoudiniAssetState::Deleting:
|
|
bOutNeedsRebuildOrDelete = true;
|
|
break;
|
|
default:
|
|
bOutInvalidState = true;
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SetInputPresets(const TMap<UObject*, int32>& InPresets)
|
|
{
|
|
// Set the input preset for this HAC
|
|
#if WITH_EDITOR
|
|
InputPresets = InPresets;
|
|
#endif
|
|
}
|
|
|
|
|
|
void
|
|
UHoudiniAssetComponent::ApplyInputPresets()
|
|
{
|
|
if (InputPresets.Num() <= 0)
|
|
return;
|
|
|
|
#if WITH_EDITOR
|
|
// Ignore inputs that have been preset to curve
|
|
TArray<UHoudiniInput*> InputArray;
|
|
for (auto CurrentInput : Inputs)
|
|
{
|
|
if (!CurrentInput || CurrentInput->IsPendingKill())
|
|
continue;
|
|
|
|
if (CurrentInput->GetInputType() != EHoudiniInputType::Curve)
|
|
InputArray.Add(CurrentInput);
|
|
}
|
|
|
|
// Try to apply the supplied Object to the Input
|
|
for (TMap< UObject*, int32 >::TIterator IterToolPreset(InputPresets); IterToolPreset; ++IterToolPreset)
|
|
{
|
|
UObject * Object = IterToolPreset.Key();
|
|
if (!Object || Object->IsPendingKill())
|
|
continue;
|
|
|
|
int32 InputNumber = IterToolPreset.Value();
|
|
if (!InputArray.IsValidIndex(InputNumber))
|
|
continue;
|
|
|
|
// If the object is a landscape, add a new landscape input
|
|
if (Object->IsA<ALandscapeProxy>())
|
|
{
|
|
// selecting a landscape
|
|
int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Landscape);
|
|
if (InsertNum == 0)
|
|
{
|
|
// Landscape inputs only support one object!
|
|
InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Landscape, InsertNum, Object);
|
|
}
|
|
}
|
|
|
|
// If the object is an actor, add a new world input
|
|
if (Object->IsA<AActor>())
|
|
{
|
|
// selecting an actor
|
|
int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::World);
|
|
InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::World, InsertNum, Object);
|
|
}
|
|
|
|
// If the object is a static mesh, add a new geometry input (TODO: or BP ? )
|
|
if (Object->IsA<UStaticMesh>())
|
|
{
|
|
// selecting a Staticn Mesh
|
|
int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Geometry);
|
|
InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Geometry, InsertNum, Object);
|
|
}
|
|
|
|
if (Object->IsA<AHoudiniAssetActor>())
|
|
{
|
|
// selecting a Houdini Asset
|
|
int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Asset);
|
|
if (InsertNum == 0)
|
|
{
|
|
// Assert inputs only support one object!
|
|
InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Asset, InsertNum, Object);
|
|
}
|
|
}
|
|
}
|
|
|
|
// The input objects have been set, now change the input type
|
|
bool bBPStructureModified = false;
|
|
for (auto CurrentInput : Inputs)
|
|
{
|
|
int32 NumGeo = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry);
|
|
int32 NumAsset = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Asset);
|
|
int32 NumWorld = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::World);
|
|
int32 NumLandscape = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Landscape);
|
|
|
|
EHoudiniInputType NewInputType = EHoudiniInputType::Invalid;
|
|
if (NumLandscape > 0 && NumLandscape >= NumGeo && NumLandscape >= NumAsset && NumLandscape >= NumWorld)
|
|
NewInputType = EHoudiniInputType::Landscape;
|
|
else if (NumWorld > 0 && NumWorld >= NumGeo && NumWorld >= NumAsset && NumWorld >= NumLandscape)
|
|
NewInputType = EHoudiniInputType::World;
|
|
else if (NumAsset > 0 && NumAsset >= NumGeo && NumAsset >= NumWorld && NumAsset >= NumLandscape)
|
|
NewInputType = EHoudiniInputType::Asset;
|
|
else if (NumGeo > 0 && NumGeo >= NumAsset && NumGeo >= NumWorld && NumGeo >= NumLandscape)
|
|
NewInputType = EHoudiniInputType::Geometry;
|
|
|
|
if (NewInputType == EHoudiniInputType::Invalid)
|
|
continue;
|
|
|
|
// Change the input type, unless if it was preset to a different type and we have object for the preset type
|
|
if (CurrentInput->GetInputType() == EHoudiniInputType::Geometry && NewInputType != EHoudiniInputType::Geometry)
|
|
{
|
|
CurrentInput->SetInputType(NewInputType, bBPStructureModified);
|
|
}
|
|
else
|
|
{
|
|
// Input type was preset, only change if that type is empty
|
|
if(CurrentInput->GetNumberOfInputObjects() <= 0)
|
|
CurrentInput->SetInputType(NewInputType, bBPStructureModified);
|
|
}
|
|
}
|
|
if (bBPStructureModified)
|
|
{
|
|
MarkAsBlueprintStructureModified();
|
|
}
|
|
#endif
|
|
|
|
// Discard the tool presets after their first setup
|
|
InputPresets.Empty();
|
|
}
|
|
|
|
|
|
bool
|
|
UHoudiniAssetComponent::IsComponentValid() const
|
|
{
|
|
if (!IsValidLowLevel())
|
|
return false;
|
|
|
|
if (IsTemplate())
|
|
return false;
|
|
|
|
if (IsPendingKillOrUnreachable())
|
|
return false;
|
|
|
|
if (!GetOuter()) //|| !GetOuter()->GetLevel() )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::IsInstantiatingOrCooking() const
|
|
{
|
|
return HapiGUID.IsValid();
|
|
}
|
|
|
|
|
|
void
|
|
UHoudiniAssetComponent::SetStaticMeshGenerationProperties(UStaticMesh* InStaticMesh) const
|
|
{
|
|
#if WITH_EDITOR
|
|
if (!InStaticMesh)
|
|
return;
|
|
|
|
// Make sure static mesh has a new lighting guid.
|
|
InStaticMesh->LightingGuid = FGuid::NewGuid();
|
|
InStaticMesh->LODGroup = NAME_None;
|
|
|
|
// Set resolution of lightmap.
|
|
InStaticMesh->LightMapResolution = StaticMeshGenerationProperties.GeneratedLightMapResolution;
|
|
|
|
// Set the global light map coordinate index if it looks valid
|
|
if (InStaticMesh->RenderData.IsValid() && InStaticMesh->RenderData->LODResources.Num() > 0)
|
|
{
|
|
int32 NumUVs = InStaticMesh->RenderData->LODResources[0].GetNumTexCoords();
|
|
if (NumUVs > StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex)
|
|
{
|
|
InStaticMesh->LightMapCoordinateIndex = 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->BodySetup)
|
|
InStaticMesh->CreateBodySetup();
|
|
|
|
UBodySetup* BodySetup = InStaticMesh->BodySetup;
|
|
if (!InStaticMesh->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<USceneComponent *> LocalAttachChildren;
|
|
GetChildrenComponents(true, LocalAttachChildren);
|
|
for (TArray<USceneComponent *>::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<size_t>(&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);
|
|
}
|