diff --git a/.gitignore b/.gitignore index be8512ee..3ce93335 100644 --- a/.gitignore +++ b/.gitignore @@ -15,11 +15,7 @@ Saved *.suo *.xcodeproj *.xcworkspace -<<<<<<< HEAD -*BuiltData.uasset -/Imgs/ Content/Blueprints/UI/Imgs/shit/screen* -======= *BuiltData.uasset ->>>>>>> 75122678f025a258a283030fe5cab391858f2e60 Temp +!Plugins \ No newline at end of file diff --git a/Content/AssetPacks/Au_Bedroom_03/SM_MERGED_SM_fDress_C_20.uasset b/Content/AssetPacks/Au_Bedroom_03/SM_MERGED_SM_fDress_C_20.uasset index 8f02ad4d..3035d204 100644 Binary files a/Content/AssetPacks/Au_Bedroom_03/SM_MERGED_SM_fDress_C_20.uasset and b/Content/AssetPacks/Au_Bedroom_03/SM_MERGED_SM_fDress_C_20.uasset differ diff --git a/Content/AssetPacks/Book/Materials/MI_Magazine_open_3.uasset b/Content/AssetPacks/Book/Materials/MI_Magazine_open_3.uasset new file mode 100644 index 00000000..35610844 Binary files /dev/null and b/Content/AssetPacks/Book/Materials/MI_Magazine_open_3.uasset differ diff --git a/Content/AssetPacks/Book/SM_Book_9.uasset b/Content/AssetPacks/Book/SM_Book_9.uasset new file mode 100644 index 00000000..8e96f58c Binary files /dev/null and b/Content/AssetPacks/Book/SM_Book_9.uasset differ diff --git a/Content/AssetPacks/Book/SM_Magazine_open4.uasset b/Content/AssetPacks/Book/SM_Magazine_open4.uasset new file mode 100644 index 00000000..346e61b8 Binary files /dev/null and b/Content/AssetPacks/Book/SM_Magazine_open4.uasset differ diff --git a/Content/AssetPacks/Book/Textures/T_Magazines_open_A.uasset b/Content/AssetPacks/Book/Textures/T_Magazines_open_A.uasset new file mode 100644 index 00000000..d44ae075 Binary files /dev/null and b/Content/AssetPacks/Book/Textures/T_Magazines_open_A.uasset differ diff --git a/Content/AssetPacks/Kitchen/proceduralKitchen.uasset b/Content/AssetPacks/Kitchen/proceduralKitchen.uasset new file mode 100644 index 00000000..9eb3c333 Binary files /dev/null and b/Content/AssetPacks/Kitchen/proceduralKitchen.uasset differ diff --git a/Content/AssetPacks/ModernFurnitureVol1/Materials/MI_Red_plastic_DAW_Armchair.uasset b/Content/AssetPacks/ModernFurnitureVol1/Materials/MI_Red_plastic_DAW_Armchair.uasset new file mode 100644 index 00000000..1b1b1047 Binary files /dev/null and b/Content/AssetPacks/ModernFurnitureVol1/Materials/MI_Red_plastic_DAW_Armchair.uasset differ diff --git a/Content/AssetPacks/ModernFurnitureVol1/Materials/MI_leather_RobinArmchair.uasset b/Content/AssetPacks/ModernFurnitureVol1/Materials/MI_leather_RobinArmchair.uasset index 7f24b5b8..eaa6210c 100644 Binary files a/Content/AssetPacks/ModernFurnitureVol1/Materials/MI_leather_RobinArmchair.uasset and b/Content/AssetPacks/ModernFurnitureVol1/Materials/MI_leather_RobinArmchair.uasset differ diff --git a/Content/AssetPacks/ModernFurnitureVol1/Materials/MI_wood_12_ChesterChair.uasset b/Content/AssetPacks/ModernFurnitureVol1/Materials/MI_wood_12_ChesterChair.uasset new file mode 100644 index 00000000..0d715fb8 Binary files /dev/null and b/Content/AssetPacks/ModernFurnitureVol1/Materials/MI_wood_12_ChesterChair.uasset differ diff --git a/Content/AssetPacks/ModernFurnitureVol1/Materials/MI_wood_Daw_armchair2.uasset b/Content/AssetPacks/ModernFurnitureVol1/Materials/MI_wood_Daw_armchair2.uasset new file mode 100644 index 00000000..1208cbe1 Binary files /dev/null and b/Content/AssetPacks/ModernFurnitureVol1/Materials/MI_wood_Daw_armchair2.uasset differ diff --git a/Content/AssetPacks/ModernFurnitureVol1/Meshes/Dining_chair/SM_Chester_Chair.uasset b/Content/AssetPacks/ModernFurnitureVol1/Meshes/Dining_chair/SM_Chester_Chair.uasset new file mode 100644 index 00000000..37ac5f65 Binary files /dev/null and b/Content/AssetPacks/ModernFurnitureVol1/Meshes/Dining_chair/SM_Chester_Chair.uasset differ diff --git a/Content/AssetPacks/ModernFurnitureVol1/Meshes/Dining_chair/SM_Eames_Dining_Chair.uasset b/Content/AssetPacks/ModernFurnitureVol1/Meshes/Dining_chair/SM_Eames_Dining_Chair.uasset new file mode 100644 index 00000000..f5c9da2a Binary files /dev/null and b/Content/AssetPacks/ModernFurnitureVol1/Meshes/Dining_chair/SM_Eames_Dining_Chair.uasset differ diff --git a/Content/AssetPacks/ModernFurnitureVol1/Meshes/Dining_chair/SM_RMS2_Diningchair.uasset b/Content/AssetPacks/ModernFurnitureVol1/Meshes/Dining_chair/SM_RMS2_Diningchair.uasset index 5cbad024..326d39bb 100644 Binary files a/Content/AssetPacks/ModernFurnitureVol1/Meshes/Dining_chair/SM_RMS2_Diningchair.uasset and b/Content/AssetPacks/ModernFurnitureVol1/Meshes/Dining_chair/SM_RMS2_Diningchair.uasset differ diff --git a/Content/AssetPacks/ModernFurnitureVol1/Textures/Normal/T_ArmChairDaw_N.uasset b/Content/AssetPacks/ModernFurnitureVol1/Textures/Normal/T_ArmChairDaw_N.uasset new file mode 100644 index 00000000..f5f79af0 Binary files /dev/null and b/Content/AssetPacks/ModernFurnitureVol1/Textures/Normal/T_ArmChairDaw_N.uasset differ diff --git a/Content/AssetPacks/ModernFurnitureVol1/Textures/Normal/T_Eames_LCM_Chair_AO.uasset b/Content/AssetPacks/ModernFurnitureVol1/Textures/Normal/T_Eames_LCM_Chair_AO.uasset new file mode 100644 index 00000000..22a133c9 Binary files /dev/null and b/Content/AssetPacks/ModernFurnitureVol1/Textures/Normal/T_Eames_LCM_Chair_AO.uasset differ diff --git a/Content/AssetPacks/ModernKitchen/Materials/PotCover_M.uasset b/Content/AssetPacks/ModernKitchen/Materials/PotCover_M.uasset new file mode 100644 index 00000000..f4b658f9 Binary files /dev/null and b/Content/AssetPacks/ModernKitchen/Materials/PotCover_M.uasset differ diff --git a/Content/AssetPacks/ModernKitchen/Materials/StainlessSteelPolished.uasset b/Content/AssetPacks/ModernKitchen/Materials/StainlessSteelPolished.uasset new file mode 100644 index 00000000..49444745 Binary files /dev/null and b/Content/AssetPacks/ModernKitchen/Materials/StainlessSteelPolished.uasset differ diff --git a/Content/AssetPacks/ModernKitchen/StaticMeshes/Pot_Lid.uasset b/Content/AssetPacks/ModernKitchen/StaticMeshes/Pot_Lid.uasset new file mode 100644 index 00000000..a306d167 Binary files /dev/null and b/Content/AssetPacks/ModernKitchen/StaticMeshes/Pot_Lid.uasset differ diff --git a/Content/AssetPacks/ModernKitchen/Textures/Stovetop_StainlessSteelPolished_BaseColor.uasset b/Content/AssetPacks/ModernKitchen/Textures/Stovetop_StainlessSteelPolished_BaseColor.uasset new file mode 100644 index 00000000..8fe89115 Binary files /dev/null and b/Content/AssetPacks/ModernKitchen/Textures/Stovetop_StainlessSteelPolished_BaseColor.uasset differ diff --git a/Content/AssetPacks/ModernKitchen/Textures/Stovetop_StainlessSteelPolished_Normal.uasset b/Content/AssetPacks/ModernKitchen/Textures/Stovetop_StainlessSteelPolished_Normal.uasset new file mode 100644 index 00000000..4ff1d230 Binary files /dev/null and b/Content/AssetPacks/ModernKitchen/Textures/Stovetop_StainlessSteelPolished_Normal.uasset differ diff --git a/Content/AssetPacks/ModernKitchen/Textures/Stovetop_StainlessSteelPolished_RoughnessAOMetallic.uasset b/Content/AssetPacks/ModernKitchen/Textures/Stovetop_StainlessSteelPolished_RoughnessAOMetallic.uasset new file mode 100644 index 00000000..f7ee0bef Binary files /dev/null and b/Content/AssetPacks/ModernKitchen/Textures/Stovetop_StainlessSteelPolished_RoughnessAOMetallic.uasset differ diff --git a/Content/AssetPacks/NewFlat/Meshes/SM_Boca_Poof_01.uasset b/Content/AssetPacks/NewFlat/Meshes/SM_Boca_Poof_01.uasset index 5412eac2..23fd66b6 100644 Binary files a/Content/AssetPacks/NewFlat/Meshes/SM_Boca_Poof_01.uasset and b/Content/AssetPacks/NewFlat/Meshes/SM_Boca_Poof_01.uasset differ diff --git a/Content/AssetPacks/NewFlat/Meshes/SM_Carpet_DH.uasset b/Content/AssetPacks/NewFlat/Meshes/SM_Carpet_DH.uasset index d7e76cf4..43c1e0af 100644 Binary files a/Content/AssetPacks/NewFlat/Meshes/SM_Carpet_DH.uasset and b/Content/AssetPacks/NewFlat/Meshes/SM_Carpet_DH.uasset differ diff --git a/Content/AssetPacks/NewFlat/Meshes/SM_Chandeliers.uasset b/Content/AssetPacks/NewFlat/Meshes/SM_Chandeliers.uasset index 867fa6e4..079aee50 100644 Binary files a/Content/AssetPacks/NewFlat/Meshes/SM_Chandeliers.uasset and b/Content/AssetPacks/NewFlat/Meshes/SM_Chandeliers.uasset differ diff --git a/Content/AssetPacks/NewFlat/Meshes/SM_Cupboard_01.uasset b/Content/AssetPacks/NewFlat/Meshes/SM_Cupboard_01.uasset index d2c9a48a..fa3c1544 100644 Binary files a/Content/AssetPacks/NewFlat/Meshes/SM_Cupboard_01.uasset and b/Content/AssetPacks/NewFlat/Meshes/SM_Cupboard_01.uasset differ diff --git a/Content/AssetPacks/NewFlat/Meshes/SM_SVEVA_Tav.uasset b/Content/AssetPacks/NewFlat/Meshes/SM_SVEVA_Tav.uasset index f1f4ecd1..a687f1ff 100644 Binary files a/Content/AssetPacks/NewFlat/Meshes/SM_SVEVA_Tav.uasset and b/Content/AssetPacks/NewFlat/Meshes/SM_SVEVA_Tav.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Materials/M_Chairclothpattern_2.uasset b/Content/AssetPacks/Nordicapartment/Materials/M_Chairclothpattern_2.uasset new file mode 100644 index 00000000..c7dbdeba Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/Materials/M_Chairclothpattern_2.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Materials/M_Decorations02.uasset b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations02.uasset new file mode 100644 index 00000000..bfbdf99c Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations02.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08a.uasset b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08a.uasset new file mode 100644 index 00000000..e2ac787e Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08a.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08b.uasset b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08b.uasset new file mode 100644 index 00000000..bffdfe6c Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08b.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08c.uasset b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08c.uasset new file mode 100644 index 00000000..5c9abc22 Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08c.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08d.uasset b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08d.uasset new file mode 100644 index 00000000..ed5c9987 Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08d.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08e.uasset b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08e.uasset new file mode 100644 index 00000000..4caa5435 Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08e.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08f.uasset b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08f.uasset new file mode 100644 index 00000000..39f81122 Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08f.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08g.uasset b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08g.uasset new file mode 100644 index 00000000..4f7005b9 Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/Materials/M_Decorations08g.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Materials/M_pantalla_shade.uasset b/Content/AssetPacks/Nordicapartment/Materials/M_pantalla_shade.uasset index a3504d3f..244d4323 100644 Binary files a/Content/AssetPacks/Nordicapartment/Materials/M_pantalla_shade.uasset and b/Content/AssetPacks/Nordicapartment/Materials/M_pantalla_shade.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Meshes/SM_Decorations16.uasset b/Content/AssetPacks/Nordicapartment/Meshes/SM_Decorations16.uasset index e31d5d74..74f52d66 100644 Binary files a/Content/AssetPacks/Nordicapartment/Meshes/SM_Decorations16.uasset and b/Content/AssetPacks/Nordicapartment/Meshes/SM_Decorations16.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Meshes/SM_Decorations50.uasset b/Content/AssetPacks/Nordicapartment/Meshes/SM_Decorations50.uasset index f2f8af24..c20dab95 100644 Binary files a/Content/AssetPacks/Nordicapartment/Meshes/SM_Decorations50.uasset and b/Content/AssetPacks/Nordicapartment/Meshes/SM_Decorations50.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Meshes/SM_Decorations8.uasset b/Content/AssetPacks/Nordicapartment/Meshes/SM_Decorations8.uasset new file mode 100644 index 00000000..9cb7dc0b Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/Meshes/SM_Decorations8.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Meshes/SM_Painting07.uasset b/Content/AssetPacks/Nordicapartment/Meshes/SM_Painting07.uasset index 9f17fa5f..da68bd71 100644 Binary files a/Content/AssetPacks/Nordicapartment/Meshes/SM_Painting07.uasset and b/Content/AssetPacks/Nordicapartment/Meshes/SM_Painting07.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Meshes/SM_TVset01.uasset b/Content/AssetPacks/Nordicapartment/Meshes/SM_TVset01.uasset index a752513e..854d8754 100644 Binary files a/Content/AssetPacks/Nordicapartment/Meshes/SM_TVset01.uasset and b/Content/AssetPacks/Nordicapartment/Meshes/SM_TVset01.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Meshes/SM_carpet.uasset b/Content/AssetPacks/Nordicapartment/Meshes/SM_carpet.uasset index e8649817..5b19bd14 100644 Binary files a/Content/AssetPacks/Nordicapartment/Meshes/SM_carpet.uasset and b/Content/AssetPacks/Nordicapartment/Meshes/SM_carpet.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Meshes/SM_floorlamp.uasset b/Content/AssetPacks/Nordicapartment/Meshes/SM_floorlamp.uasset index 6ab5147d..b9d72517 100644 Binary files a/Content/AssetPacks/Nordicapartment/Meshes/SM_floorlamp.uasset and b/Content/AssetPacks/Nordicapartment/Meshes/SM_floorlamp.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Meshes/SM_shelf01.uasset b/Content/AssetPacks/Nordicapartment/Meshes/SM_shelf01.uasset index 0b156027..c0e9edc0 100644 Binary files a/Content/AssetPacks/Nordicapartment/Meshes/SM_shelf01.uasset and b/Content/AssetPacks/Nordicapartment/Meshes/SM_shelf01.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Meshes/SM_succulentplants.uasset b/Content/AssetPacks/Nordicapartment/Meshes/SM_succulentplants.uasset index 85de46f0..f446cbb4 100644 Binary files a/Content/AssetPacks/Nordicapartment/Meshes/SM_succulentplants.uasset and b/Content/AssetPacks/Nordicapartment/Meshes/SM_succulentplants.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Meshes/SM_tableware5.uasset b/Content/AssetPacks/Nordicapartment/Meshes/SM_tableware5.uasset index 9e72be0e..f4d54ef1 100644 Binary files a/Content/AssetPacks/Nordicapartment/Meshes/SM_tableware5.uasset and b/Content/AssetPacks/Nordicapartment/Meshes/SM_tableware5.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_Decorations1.uasset b/Content/AssetPacks/Nordicapartment/SM_Decorations1.uasset index 7c6094b9..b74f75ab 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_Decorations1.uasset and b/Content/AssetPacks/Nordicapartment/SM_Decorations1.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_Decorations12.uasset b/Content/AssetPacks/Nordicapartment/SM_Decorations12.uasset index cd8fdbdb..e7ca125c 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_Decorations12.uasset and b/Content/AssetPacks/Nordicapartment/SM_Decorations12.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_Decorations20.uasset b/Content/AssetPacks/Nordicapartment/SM_Decorations20.uasset new file mode 100644 index 00000000..caaa5e8a Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/SM_Decorations20.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_Decorations28.uasset b/Content/AssetPacks/Nordicapartment/SM_Decorations28.uasset new file mode 100644 index 00000000..60fa39b1 Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/SM_Decorations28.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_Decorations29.uasset b/Content/AssetPacks/Nordicapartment/SM_Decorations29.uasset index 1bb10cd9..595a0c79 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_Decorations29.uasset and b/Content/AssetPacks/Nordicapartment/SM_Decorations29.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_Decorations30.uasset b/Content/AssetPacks/Nordicapartment/SM_Decorations30.uasset index a060fae5..9ecaa609 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_Decorations30.uasset and b/Content/AssetPacks/Nordicapartment/SM_Decorations30.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_Decorations42.uasset b/Content/AssetPacks/Nordicapartment/SM_Decorations42.uasset index 7e347a24..4f22eda5 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_Decorations42.uasset and b/Content/AssetPacks/Nordicapartment/SM_Decorations42.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_Decorations48.uasset b/Content/AssetPacks/Nordicapartment/SM_Decorations48.uasset index 6a9a61ef..554e7597 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_Decorations48.uasset and b/Content/AssetPacks/Nordicapartment/SM_Decorations48.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_Decorations49.uasset b/Content/AssetPacks/Nordicapartment/SM_Decorations49.uasset index 12a6e240..c91f2527 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_Decorations49.uasset and b/Content/AssetPacks/Nordicapartment/SM_Decorations49.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_Decorations50.uasset b/Content/AssetPacks/Nordicapartment/SM_Decorations50.uasset index 7c9d1716..53473ba9 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_Decorations50.uasset and b/Content/AssetPacks/Nordicapartment/SM_Decorations50.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_Decorations7.uasset b/Content/AssetPacks/Nordicapartment/SM_Decorations7.uasset index 611f4096..aee260dd 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_Decorations7.uasset and b/Content/AssetPacks/Nordicapartment/SM_Decorations7.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_Footstool.uasset b/Content/AssetPacks/Nordicapartment/SM_Footstool.uasset index 79e14f24..d96e37de 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_Footstool.uasset and b/Content/AssetPacks/Nordicapartment/SM_Footstool.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations10.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations10.uasset index c9799f10..1a9e366e 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations10.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations10.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations17.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations17.uasset index efc02f2b..2fc2a8f0 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations17.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations17.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations21.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations21.uasset index 94b8080f..757bf7fc 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations21.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations21.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations22.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations22.uasset index 419b27cd..a7f77138 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations22.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations22.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations30.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations30.uasset index 9a2b0e37..fa339f1a 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations30.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations30.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations35.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations35.uasset index 86bbf6ca..e63687db 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations35.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations35.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations40.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations40.uasset index d6010a09..a7dcdb46 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations40.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Decorations40.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Diningchair15.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Diningchair15.uasset index 2f68b887..16369b46 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Diningchair15.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_Diningchair15.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_chandelier02.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_chandelier02.uasset index 4f175a29..b8e69758 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_chandelier02.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_chandelier02.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_coffeetable01.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_coffeetable01.uasset index a4f04e03..eacb580f 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_coffeetable01.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_coffeetable01.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_coffeetable04.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_coffeetable04.uasset index 4681bb02..087cf685 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_coffeetable04.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_coffeetable04.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_plant02b.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_plant02b.uasset index db3eff85..bdab6ec0 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_plant02b.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_plant02b.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_plants_09c.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_plants_09c.uasset index a6a15fce..4dd24bfb 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_plants_09c.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_plants_09c.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_sofabake002.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_sofabake002.uasset index d8205c95..98f7289e 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_sofabake002.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_sofabake002.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware10.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware10.uasset index 5c66b283..3c81b383 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware10.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware10.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware14.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware14.uasset index f1bc164c..6137f26a 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware14.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware14.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware18.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware18.uasset index c9253dd0..be56b102 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware18.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware18.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware2.uasset b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware2.uasset index c6ce08c6..608b004a 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware2.uasset and b/Content/AssetPacks/Nordicapartment/SM_MERGED_SM_tableware2.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_carpet.uasset b/Content/AssetPacks/Nordicapartment/SM_carpet.uasset index 1f43cca1..75e6a3c4 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_carpet.uasset and b/Content/AssetPacks/Nordicapartment/SM_carpet.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_succulentplants.uasset b/Content/AssetPacks/Nordicapartment/SM_succulentplants.uasset index 54eea587..7c8ec94d 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_succulentplants.uasset and b/Content/AssetPacks/Nordicapartment/SM_succulentplants.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/SM_tableware6.uasset b/Content/AssetPacks/Nordicapartment/SM_tableware6.uasset index 7bcf1d69..e2d0f905 100644 Binary files a/Content/AssetPacks/Nordicapartment/SM_tableware6.uasset and b/Content/AssetPacks/Nordicapartment/SM_tableware6.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Textures/T_mug1_D.uasset b/Content/AssetPacks/Nordicapartment/Textures/T_mug1_D.uasset new file mode 100644 index 00000000..f3e02524 Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/Textures/T_mug1_D.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Textures/T_wrip_paper_line_1_D.uasset b/Content/AssetPacks/Nordicapartment/Textures/T_wrip_paper_line_1_D.uasset new file mode 100644 index 00000000..95a101eb Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/Textures/T_wrip_paper_line_1_D.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Textures/T_wrip_paper_line_2_D.uasset b/Content/AssetPacks/Nordicapartment/Textures/T_wrip_paper_line_2_D.uasset new file mode 100644 index 00000000..e4e234b0 Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/Textures/T_wrip_paper_line_2_D.uasset differ diff --git a/Content/AssetPacks/Nordicapartment/Textures/T_wrip_paper_line_3_D.uasset b/Content/AssetPacks/Nordicapartment/Textures/T_wrip_paper_line_3_D.uasset new file mode 100644 index 00000000..b0c345df Binary files /dev/null and b/Content/AssetPacks/Nordicapartment/Textures/T_wrip_paper_line_3_D.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Materials/M_Master_Mat.uasset b/Content/AssetPacks/ThreeDee/Materials/M_Master_Mat.uasset index f5bc292b..3206851a 100644 Binary files a/Content/AssetPacks/ThreeDee/Materials/M_Master_Mat.uasset and b/Content/AssetPacks/ThreeDee/Materials/M_Master_Mat.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Materials/Props/MI_Peppermill.uasset b/Content/AssetPacks/ThreeDee/Materials/Props/MI_Peppermill.uasset new file mode 100644 index 00000000..06bc0fac Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Materials/Props/MI_Peppermill.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_1.uasset b/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_1.uasset new file mode 100644 index 00000000..5dbf5cae Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_1.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_2.uasset b/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_2.uasset new file mode 100644 index 00000000..4c0d1171 Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_2.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_3.uasset b/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_3.uasset new file mode 100644 index 00000000..73ba9f57 Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_3.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_4.uasset b/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_4.uasset new file mode 100644 index 00000000..ec6d698a Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_4.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_5.uasset b/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_5.uasset new file mode 100644 index 00000000..d986e0e7 Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Materials/Props/MI_SoftBook_5.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Materials/Tileable/MI_Fabric_Curtains.uasset b/Content/AssetPacks/ThreeDee/Materials/Tileable/MI_Fabric_Curtains.uasset new file mode 100644 index 00000000..a1516644 Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Materials/Tileable/MI_Fabric_Curtains.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Materials/Tileable/MI_Metal_Dull.uasset b/Content/AssetPacks/ThreeDee/Materials/Tileable/MI_Metal_Dull.uasset index 186bce8c..865c0bdf 100644 Binary files a/Content/AssetPacks/ThreeDee/Materials/Tileable/MI_Metal_Dull.uasset and b/Content/AssetPacks/ThreeDee/Materials/Tileable/MI_Metal_Dull.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Meshes/Furniture/Beds/SM_Bed_Malm.uasset b/Content/AssetPacks/ThreeDee/Meshes/Furniture/Beds/SM_Bed_Malm.uasset index fc8f3004..1509994b 100644 Binary files a/Content/AssetPacks/ThreeDee/Meshes/Furniture/Beds/SM_Bed_Malm.uasset and b/Content/AssetPacks/ThreeDee/Meshes/Furniture/Beds/SM_Bed_Malm.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Meshes/Furniture/Books/SM_Books_Softcover.uasset b/Content/AssetPacks/ThreeDee/Meshes/Furniture/Books/SM_Books_Softcover.uasset new file mode 100644 index 00000000..45dce5fd Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Meshes/Furniture/Books/SM_Books_Softcover.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Meshes/Furniture/Deco/SM_Bottle.uasset b/Content/AssetPacks/ThreeDee/Meshes/Furniture/Deco/SM_Bottle.uasset new file mode 100644 index 00000000..7f6d895d Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Meshes/Furniture/Deco/SM_Bottle.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Meshes/Furniture/Deco/SM_Salt_Pepper_Mill.uasset b/Content/AssetPacks/ThreeDee/Meshes/Furniture/Deco/SM_Salt_Pepper_Mill.uasset new file mode 100644 index 00000000..75467d4b Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Meshes/Furniture/Deco/SM_Salt_Pepper_Mill.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Meshes/Furniture/Shelfs/SM_shelf_billy.uasset b/Content/AssetPacks/ThreeDee/Meshes/Furniture/Shelfs/SM_shelf_billy.uasset index 37c7a6e8..f9fddac6 100644 Binary files a/Content/AssetPacks/ThreeDee/Meshes/Furniture/Shelfs/SM_shelf_billy.uasset and b/Content/AssetPacks/ThreeDee/Meshes/Furniture/Shelfs/SM_shelf_billy.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Meshes/Furniture/Tables/SM_office_table.uasset b/Content/AssetPacks/ThreeDee/Meshes/Furniture/Tables/SM_office_table.uasset index 5fb6cfe0..89353cd0 100644 Binary files a/Content/AssetPacks/ThreeDee/Meshes/Furniture/Tables/SM_office_table.uasset and b/Content/AssetPacks/ThreeDee/Meshes/Furniture/Tables/SM_office_table.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Textures/Props/T_Peppermill_A.uasset b/Content/AssetPacks/ThreeDee/Textures/Props/T_Peppermill_A.uasset new file mode 100644 index 00000000..8050d7e0 Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Textures/Props/T_Peppermill_A.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Textures/Props/T_Peppermill_Masks.uasset b/Content/AssetPacks/ThreeDee/Textures/Props/T_Peppermill_Masks.uasset new file mode 100644 index 00000000..24814e94 Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Textures/Props/T_Peppermill_Masks.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Textures/Props/T_Peppermill_N.uasset b/Content/AssetPacks/ThreeDee/Textures/Props/T_Peppermill_N.uasset new file mode 100644 index 00000000..bd714928 Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Textures/Props/T_Peppermill_N.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook1_A.uasset b/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook1_A.uasset new file mode 100644 index 00000000..1c94e32a Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook1_A.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook2_A.uasset b/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook2_A.uasset new file mode 100644 index 00000000..aa5a92ed Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook2_A.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook3_A.uasset b/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook3_A.uasset new file mode 100644 index 00000000..94af697d Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook3_A.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook4_A.uasset b/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook4_A.uasset new file mode 100644 index 00000000..ca4468df Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook4_A.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook5_A.uasset b/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook5_A.uasset new file mode 100644 index 00000000..d9cb2630 Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Textures/Props/T_SoftBook5_A.uasset differ diff --git a/Content/AssetPacks/ThreeDee/Textures/Tileable/T_FabricPlain_A.uasset b/Content/AssetPacks/ThreeDee/Textures/Tileable/T_FabricPlain_A.uasset new file mode 100644 index 00000000..ad488f27 Binary files /dev/null and b/Content/AssetPacks/ThreeDee/Textures/Tileable/T_FabricPlain_A.uasset differ diff --git a/Content/AssetPacks/dViz_Plant_Pack/Material/Instance_Material/MI_dvizTree_01_WP_04_Inst.uasset b/Content/AssetPacks/dViz_Plant_Pack/Material/Instance_Material/MI_dvizTree_01_WP_04_Inst.uasset new file mode 100644 index 00000000..2ebf6c10 Binary files /dev/null and b/Content/AssetPacks/dViz_Plant_Pack/Material/Instance_Material/MI_dvizTree_01_WP_04_Inst.uasset differ diff --git a/Content/AssetPacks/dViz_Plant_Pack/SM_MERGED_SM_dvizTree_WP_04_Var05_68.uasset b/Content/AssetPacks/dViz_Plant_Pack/SM_MERGED_SM_dvizTree_WP_04_Var05_68.uasset new file mode 100644 index 00000000..5b5ab55c Binary files /dev/null and b/Content/AssetPacks/dViz_Plant_Pack/SM_MERGED_SM_dvizTree_WP_04_Var05_68.uasset differ diff --git a/Content/AssetPacks/dViz_Plant_Pack/Textures/2k/T_dvizTree_WP_01_Var04_BC.uasset b/Content/AssetPacks/dViz_Plant_Pack/Textures/2k/T_dvizTree_WP_01_Var04_BC.uasset new file mode 100644 index 00000000..37835ba4 Binary files /dev/null and b/Content/AssetPacks/dViz_Plant_Pack/Textures/2k/T_dvizTree_WP_01_Var04_BC.uasset differ diff --git a/Content/AssetPacks/dViz_Plant_Pack/Textures/2k/T_dvizTree_WP_01_Var04_M.uasset b/Content/AssetPacks/dViz_Plant_Pack/Textures/2k/T_dvizTree_WP_01_Var04_M.uasset new file mode 100644 index 00000000..ccf9590f Binary files /dev/null and b/Content/AssetPacks/dViz_Plant_Pack/Textures/2k/T_dvizTree_WP_01_Var04_M.uasset differ diff --git a/Content/AssetPacks/dViz_Plant_Pack/Textures/2k/T_dvizTree_WP_01_Var04_N.uasset b/Content/AssetPacks/dViz_Plant_Pack/Textures/2k/T_dvizTree_WP_01_Var04_N.uasset new file mode 100644 index 00000000..57359937 Binary files /dev/null and b/Content/AssetPacks/dViz_Plant_Pack/Textures/2k/T_dvizTree_WP_01_Var04_N.uasset differ diff --git a/Content/AssetPacks/playroom/Shtora1.uasset b/Content/AssetPacks/playroom/Shtora1.uasset index a368a5b5..27659db3 100644 Binary files a/Content/AssetPacks/playroom/Shtora1.uasset and b/Content/AssetPacks/playroom/Shtora1.uasset differ diff --git a/Content/AssetPacks/playroom/Shtora2.uasset b/Content/AssetPacks/playroom/Shtora2.uasset index d05e2e96..02ab71fa 100644 Binary files a/Content/AssetPacks/playroom/Shtora2.uasset and b/Content/AssetPacks/playroom/Shtora2.uasset differ diff --git a/Content/AssetPacks/wardrobe/SM_Chair_01.uasset b/Content/AssetPacks/wardrobe/SM_Chair_01.uasset new file mode 100644 index 00000000..ee8b02a4 Binary files /dev/null and b/Content/AssetPacks/wardrobe/SM_Chair_01.uasset differ diff --git a/Content/AssetPacks/wardrobe/SM_Chandelier.uasset b/Content/AssetPacks/wardrobe/SM_Chandelier.uasset index 87719c2c..14591d42 100644 Binary files a/Content/AssetPacks/wardrobe/SM_Chandelier.uasset and b/Content/AssetPacks/wardrobe/SM_Chandelier.uasset differ diff --git a/Content/AssetPacks/wardrobe/SM_Cupboard302.uasset b/Content/AssetPacks/wardrobe/SM_Cupboard302.uasset index f7fc956b..c4778738 100644 Binary files a/Content/AssetPacks/wardrobe/SM_Cupboard302.uasset and b/Content/AssetPacks/wardrobe/SM_Cupboard302.uasset differ diff --git a/Content/AssetPacks/wardrobe/SM_Lamp_01.uasset b/Content/AssetPacks/wardrobe/SM_Lamp_01.uasset index ba1d359a..b7aff2e1 100644 Binary files a/Content/AssetPacks/wardrobe/SM_Lamp_01.uasset and b/Content/AssetPacks/wardrobe/SM_Lamp_01.uasset differ diff --git a/Content/AssetPacks/wardrobe/XIAOMI_LAMP_650_MM.uasset b/Content/AssetPacks/wardrobe/XIAOMI_LAMP_650_MM.uasset index 701fc020..c7c347b2 100644 Binary files a/Content/AssetPacks/wardrobe/XIAOMI_LAMP_650_MM.uasset and b/Content/AssetPacks/wardrobe/XIAOMI_LAMP_650_MM.uasset differ diff --git a/Content/AssetPacks/wardrobe/mesh253.uasset b/Content/AssetPacks/wardrobe/mesh253.uasset index 14bd6f73..b6bd6b41 100644 Binary files a/Content/AssetPacks/wardrobe/mesh253.uasset and b/Content/AssetPacks/wardrobe/mesh253.uasset differ diff --git a/Content/AssetPacks/xoio_berlinflat/Materials/wood_chair.uasset b/Content/AssetPacks/xoio_berlinflat/Materials/wood_chair.uasset index 34868679..5b6e43d8 100644 Binary files a/Content/AssetPacks/xoio_berlinflat/Materials/wood_chair.uasset and b/Content/AssetPacks/xoio_berlinflat/Materials/wood_chair.uasset differ diff --git a/Content/AssetPacks/xoio_berlinflat/Table.uasset b/Content/AssetPacks/xoio_berlinflat/Table.uasset index bdf03a1f..3ea41a76 100644 Binary files a/Content/AssetPacks/xoio_berlinflat/Table.uasset and b/Content/AssetPacks/xoio_berlinflat/Table.uasset differ diff --git a/Content/AssetPacks/xoio_berlinflat/designchair_woodmetal.uasset b/Content/AssetPacks/xoio_berlinflat/designchair_woodmetal.uasset new file mode 100644 index 00000000..2c7cbb38 Binary files /dev/null and b/Content/AssetPacks/xoio_berlinflat/designchair_woodmetal.uasset differ diff --git a/Content/Assets/Living_room/Bad/SM_Bad_SM_Bad.uasset b/Content/Assets/Living_room/Bad/SM_Bad_SM_Bad.uasset index 74f06ea7..0f8c9df7 100644 Binary files a/Content/Assets/Living_room/Bad/SM_Bad_SM_Bad.uasset and b/Content/Assets/Living_room/Bad/SM_Bad_SM_Bad.uasset differ diff --git a/Content/Assets/Living_room/Nightstand/SM_Nightstand_SM_Nightstand.uasset b/Content/Assets/Living_room/Nightstand/SM_Nightstand_SM_Nightstand.uasset index 2131b15a..ee52fb99 100644 Binary files a/Content/Assets/Living_room/Nightstand/SM_Nightstand_SM_Nightstand.uasset and b/Content/Assets/Living_room/Nightstand/SM_Nightstand_SM_Nightstand.uasset differ diff --git a/Content/Assets/Living_room/TV_Shelf/SM_TV_Shelf_one_mesh_Shelf_TV_low.uasset b/Content/Assets/Living_room/TV_Shelf/SM_TV_Shelf_one_mesh_Shelf_TV_low.uasset index 917b7cb0..f10021ff 100644 Binary files a/Content/Assets/Living_room/TV_Shelf/SM_TV_Shelf_one_mesh_Shelf_TV_low.uasset and b/Content/Assets/Living_room/TV_Shelf/SM_TV_Shelf_one_mesh_Shelf_TV_low.uasset differ diff --git a/Content/Blueprints/Player/WalkingPawn.uasset b/Content/Blueprints/Player/WalkingPawn.uasset index 7314e1e5..bf146322 100644 Binary files a/Content/Blueprints/Player/WalkingPawn.uasset and b/Content/Blueprints/Player/WalkingPawn.uasset differ diff --git a/Content/Blueprints/UI/Actors/FlatPoint.uasset b/Content/Blueprints/UI/Actors/FlatPoint.uasset index 3158a81d..8fe3ffd0 100644 Binary files a/Content/Blueprints/UI/Actors/FlatPoint.uasset and b/Content/Blueprints/UI/Actors/FlatPoint.uasset differ diff --git a/Content/Instances/Glaas/MI_Glass_interior.uasset b/Content/Instances/Glaas/MI_Glass_interior.uasset index ea1ccf96..d499050d 100644 Binary files a/Content/Instances/Glaas/MI_Glass_interior.uasset and b/Content/Instances/Glaas/MI_Glass_interior.uasset differ diff --git a/Content/Kitchen_Maps/Bake_Cabinet/T_bakeCabinet_BaseColor.uasset b/Content/Kitchen_Maps/Bake_Cabinet/T_bakeCabinet_BaseColor.uasset new file mode 100644 index 00000000..6fdc2a00 Binary files /dev/null and b/Content/Kitchen_Maps/Bake_Cabinet/T_bakeCabinet_BaseColor.uasset differ diff --git a/Content/Kitchen_Maps/Bake_Cabinet/T_bakeCabinet_Normal.uasset b/Content/Kitchen_Maps/Bake_Cabinet/T_bakeCabinet_Normal.uasset new file mode 100644 index 00000000..becf78b9 Binary files /dev/null and b/Content/Kitchen_Maps/Bake_Cabinet/T_bakeCabinet_Normal.uasset differ diff --git a/Content/Kitchen_Maps/Bake_Cabinet/T_bakeCabinet_OcclusionRoughnessMetallic.uasset b/Content/Kitchen_Maps/Bake_Cabinet/T_bakeCabinet_OcclusionRoughnessMetallic.uasset new file mode 100644 index 00000000..71bb5164 Binary files /dev/null and b/Content/Kitchen_Maps/Bake_Cabinet/T_bakeCabinet_OcclusionRoughnessMetallic.uasset differ diff --git a/Content/Kitchen_Maps/Crane/T_crane_DefaultMaterial_BaseColor.uasset b/Content/Kitchen_Maps/Crane/T_crane_DefaultMaterial_BaseColor.uasset new file mode 100644 index 00000000..d677efce Binary files /dev/null and b/Content/Kitchen_Maps/Crane/T_crane_DefaultMaterial_BaseColor.uasset differ diff --git a/Content/Kitchen_Maps/Crane/T_crane_DefaultMaterial_Normal.uasset b/Content/Kitchen_Maps/Crane/T_crane_DefaultMaterial_Normal.uasset new file mode 100644 index 00000000..89a1efb2 Binary files /dev/null and b/Content/Kitchen_Maps/Crane/T_crane_DefaultMaterial_Normal.uasset differ diff --git a/Content/Kitchen_Maps/Crane/T_crane_DefaultMaterial_OcclusionRoughnessMetallic.uasset b/Content/Kitchen_Maps/Crane/T_crane_DefaultMaterial_OcclusionRoughnessMetallic.uasset new file mode 100644 index 00000000..c9dcf629 Binary files /dev/null and b/Content/Kitchen_Maps/Crane/T_crane_DefaultMaterial_OcclusionRoughnessMetallic.uasset differ diff --git a/Content/Kitchen_Maps/Fasade/T_Fasade_Black_basecolor.uasset b/Content/Kitchen_Maps/Fasade/T_Fasade_Black_basecolor.uasset new file mode 100644 index 00000000..9f8b2220 Binary files /dev/null and b/Content/Kitchen_Maps/Fasade/T_Fasade_Black_basecolor.uasset differ diff --git a/Content/Kitchen_Maps/Fasade/T_Fasade_Grey_basecolor.uasset b/Content/Kitchen_Maps/Fasade/T_Fasade_Grey_basecolor.uasset new file mode 100644 index 00000000..4d8ff9f1 Binary files /dev/null and b/Content/Kitchen_Maps/Fasade/T_Fasade_Grey_basecolor.uasset differ diff --git a/Content/Kitchen_Maps/Fasade/T_Fasade_White_basecolor.uasset b/Content/Kitchen_Maps/Fasade/T_Fasade_White_basecolor.uasset new file mode 100644 index 00000000..f6c21960 Binary files /dev/null and b/Content/Kitchen_Maps/Fasade/T_Fasade_White_basecolor.uasset differ diff --git a/Content/Kitchen_Maps/Fasade/T_Fasade_normal.uasset b/Content/Kitchen_Maps/Fasade/T_Fasade_normal.uasset new file mode 100644 index 00000000..ac56a9b6 Binary files /dev/null and b/Content/Kitchen_Maps/Fasade/T_Fasade_normal.uasset differ diff --git a/Content/Kitchen_Maps/Fasade/T_Fasade_occlusion_roughness_metalic.uasset b/Content/Kitchen_Maps/Fasade/T_Fasade_occlusion_roughness_metalic.uasset new file mode 100644 index 00000000..00c34442 Binary files /dev/null and b/Content/Kitchen_Maps/Fasade/T_Fasade_occlusion_roughness_metalic.uasset differ diff --git a/Content/Kitchen_Maps/Handle/T_Handles_basecolor.uasset b/Content/Kitchen_Maps/Handle/T_Handles_basecolor.uasset new file mode 100644 index 00000000..43d59412 Binary files /dev/null and b/Content/Kitchen_Maps/Handle/T_Handles_basecolor.uasset differ diff --git a/Content/Kitchen_Maps/Handle/T_Handles_normal.uasset b/Content/Kitchen_Maps/Handle/T_Handles_normal.uasset new file mode 100644 index 00000000..adfa0d77 Binary files /dev/null and b/Content/Kitchen_Maps/Handle/T_Handles_normal.uasset differ diff --git a/Content/Kitchen_Maps/Handle/T_Handles_occlusion_roughness_metalic.uasset b/Content/Kitchen_Maps/Handle/T_Handles_occlusion_roughness_metalic.uasset new file mode 100644 index 00000000..333f680f Binary files /dev/null and b/Content/Kitchen_Maps/Handle/T_Handles_occlusion_roughness_metalic.uasset differ diff --git a/Content/Kitchen_Maps/Heat_Surface/T_heatSurf_BaseColor.uasset b/Content/Kitchen_Maps/Heat_Surface/T_heatSurf_BaseColor.uasset new file mode 100644 index 00000000..a8a39414 Binary files /dev/null and b/Content/Kitchen_Maps/Heat_Surface/T_heatSurf_BaseColor.uasset differ diff --git a/Content/Kitchen_Maps/Heat_Surface/T_heatSurf_Normal.uasset b/Content/Kitchen_Maps/Heat_Surface/T_heatSurf_Normal.uasset new file mode 100644 index 00000000..083cfcd1 Binary files /dev/null and b/Content/Kitchen_Maps/Heat_Surface/T_heatSurf_Normal.uasset differ diff --git a/Content/Kitchen_Maps/Heat_Surface/T_heatSurf_OcclusionRoughnessMetallic.uasset b/Content/Kitchen_Maps/Heat_Surface/T_heatSurf_OcclusionRoughnessMetallic.uasset new file mode 100644 index 00000000..61683550 Binary files /dev/null and b/Content/Kitchen_Maps/Heat_Surface/T_heatSurf_OcclusionRoughnessMetallic.uasset differ diff --git a/Content/Kitchen_Maps/Injector/T_injector_BaseColor.uasset b/Content/Kitchen_Maps/Injector/T_injector_BaseColor.uasset new file mode 100644 index 00000000..7b3f378b Binary files /dev/null and b/Content/Kitchen_Maps/Injector/T_injector_BaseColor.uasset differ diff --git a/Content/Kitchen_Maps/Injector/T_injector_Emissive.uasset b/Content/Kitchen_Maps/Injector/T_injector_Emissive.uasset new file mode 100644 index 00000000..74bfe99e Binary files /dev/null and b/Content/Kitchen_Maps/Injector/T_injector_Emissive.uasset differ diff --git a/Content/Kitchen_Maps/Injector/T_injector_Normal.uasset b/Content/Kitchen_Maps/Injector/T_injector_Normal.uasset new file mode 100644 index 00000000..295411d0 Binary files /dev/null and b/Content/Kitchen_Maps/Injector/T_injector_Normal.uasset differ diff --git a/Content/Kitchen_Maps/Injector/T_injector_OcclusionRoughnessMetallic.uasset b/Content/Kitchen_Maps/Injector/T_injector_OcclusionRoughnessMetallic.uasset new file mode 100644 index 00000000..86defc90 Binary files /dev/null and b/Content/Kitchen_Maps/Injector/T_injector_OcclusionRoughnessMetallic.uasset differ diff --git a/Content/Kitchen_Maps/Reservoir/T_reservoir_BaseColor.uasset b/Content/Kitchen_Maps/Reservoir/T_reservoir_BaseColor.uasset new file mode 100644 index 00000000..4e6e8489 Binary files /dev/null and b/Content/Kitchen_Maps/Reservoir/T_reservoir_BaseColor.uasset differ diff --git a/Content/Kitchen_Maps/Reservoir/T_reservoir_Normal.uasset b/Content/Kitchen_Maps/Reservoir/T_reservoir_Normal.uasset new file mode 100644 index 00000000..95e9283c Binary files /dev/null and b/Content/Kitchen_Maps/Reservoir/T_reservoir_Normal.uasset differ diff --git a/Content/Kitchen_Maps/Reservoir/T_reservoir_OcclusionRoughnessMetallic.uasset b/Content/Kitchen_Maps/Reservoir/T_reservoir_OcclusionRoughnessMetallic.uasset new file mode 100644 index 00000000..7c8d9a98 Binary files /dev/null and b/Content/Kitchen_Maps/Reservoir/T_reservoir_OcclusionRoughnessMetallic.uasset differ diff --git a/Content/Kitchen_Maps/Table_Top_Granite/T_Table_Top_Granite_basecolor.uasset b/Content/Kitchen_Maps/Table_Top_Granite/T_Table_Top_Granite_basecolor.uasset new file mode 100644 index 00000000..b07aa396 Binary files /dev/null and b/Content/Kitchen_Maps/Table_Top_Granite/T_Table_Top_Granite_basecolor.uasset differ diff --git a/Content/Kitchen_Maps/Table_Top_Granite/T_Table_Top_Granite_normal.uasset b/Content/Kitchen_Maps/Table_Top_Granite/T_Table_Top_Granite_normal.uasset new file mode 100644 index 00000000..bd4a880a Binary files /dev/null and b/Content/Kitchen_Maps/Table_Top_Granite/T_Table_Top_Granite_normal.uasset differ diff --git a/Content/Kitchen_Maps/Table_Top_Granite/T_Table_Top_Granite_occlusion_roughness_metalic.uasset b/Content/Kitchen_Maps/Table_Top_Granite/T_Table_Top_Granite_occlusion_roughness_metalic.uasset new file mode 100644 index 00000000..87e41afa Binary files /dev/null and b/Content/Kitchen_Maps/Table_Top_Granite/T_Table_Top_Granite_occlusion_roughness_metalic.uasset differ diff --git a/Content/Kitchen_Maps/Table_Top_Marble/T_Table_Top_Marble_basecolor.uasset b/Content/Kitchen_Maps/Table_Top_Marble/T_Table_Top_Marble_basecolor.uasset new file mode 100644 index 00000000..5d84da92 Binary files /dev/null and b/Content/Kitchen_Maps/Table_Top_Marble/T_Table_Top_Marble_basecolor.uasset differ diff --git a/Content/Kitchen_Maps/Table_Top_Marble/T_Table_Top_Marble_normal.uasset b/Content/Kitchen_Maps/Table_Top_Marble/T_Table_Top_Marble_normal.uasset new file mode 100644 index 00000000..dfebc1b1 Binary files /dev/null and b/Content/Kitchen_Maps/Table_Top_Marble/T_Table_Top_Marble_normal.uasset differ diff --git a/Content/Kitchen_Maps/Table_Top_Marble/T_Table_Top_Marble_occlusion_roughness_metalic.uasset b/Content/Kitchen_Maps/Table_Top_Marble/T_Table_Top_Marble_occlusion_roughness_metalic.uasset new file mode 100644 index 00000000..e1a4c155 Binary files /dev/null and b/Content/Kitchen_Maps/Table_Top_Marble/T_Table_Top_Marble_occlusion_roughness_metalic.uasset differ diff --git a/Content/Kitchen_Maps/Table_Top_Wood/T_Table_Top_Wood_basecolor.uasset b/Content/Kitchen_Maps/Table_Top_Wood/T_Table_Top_Wood_basecolor.uasset new file mode 100644 index 00000000..4f10f83c Binary files /dev/null and b/Content/Kitchen_Maps/Table_Top_Wood/T_Table_Top_Wood_basecolor.uasset differ diff --git a/Content/Kitchen_Maps/Table_Top_Wood/T_Table_Top_Wood_normal.uasset b/Content/Kitchen_Maps/Table_Top_Wood/T_Table_Top_Wood_normal.uasset new file mode 100644 index 00000000..c41c6c04 Binary files /dev/null and b/Content/Kitchen_Maps/Table_Top_Wood/T_Table_Top_Wood_normal.uasset differ diff --git a/Content/Kitchen_Maps/Table_Top_Wood/T_Table_Top_Wood_occlusion_roughness_metalic.uasset b/Content/Kitchen_Maps/Table_Top_Wood/T_Table_Top_Wood_occlusion_roughness_metalic.uasset new file mode 100644 index 00000000..0505c03c Binary files /dev/null and b/Content/Kitchen_Maps/Table_Top_Wood/T_Table_Top_Wood_occlusion_roughness_metalic.uasset differ diff --git a/Content/Kitchen_Materials/M_Bake_Cabinet.uasset b/Content/Kitchen_Materials/M_Bake_Cabinet.uasset new file mode 100644 index 00000000..052b0b2d Binary files /dev/null and b/Content/Kitchen_Materials/M_Bake_Cabinet.uasset differ diff --git a/Content/Kitchen_Materials/M_Crane.uasset b/Content/Kitchen_Materials/M_Crane.uasset new file mode 100644 index 00000000..65681f80 Binary files /dev/null and b/Content/Kitchen_Materials/M_Crane.uasset differ diff --git a/Content/Kitchen_Materials/M_Fasade_Black.uasset b/Content/Kitchen_Materials/M_Fasade_Black.uasset new file mode 100644 index 00000000..b20335ec Binary files /dev/null and b/Content/Kitchen_Materials/M_Fasade_Black.uasset differ diff --git a/Content/Kitchen_Materials/M_Fasade_Grey.uasset b/Content/Kitchen_Materials/M_Fasade_Grey.uasset new file mode 100644 index 00000000..149e7b75 Binary files /dev/null and b/Content/Kitchen_Materials/M_Fasade_Grey.uasset differ diff --git a/Content/Kitchen_Materials/M_Fasade_White.uasset b/Content/Kitchen_Materials/M_Fasade_White.uasset new file mode 100644 index 00000000..efbd6771 Binary files /dev/null and b/Content/Kitchen_Materials/M_Fasade_White.uasset differ diff --git a/Content/Kitchen_Materials/M_Handle.uasset b/Content/Kitchen_Materials/M_Handle.uasset new file mode 100644 index 00000000..c20e6705 Binary files /dev/null and b/Content/Kitchen_Materials/M_Handle.uasset differ diff --git a/Content/Kitchen_Materials/M_Heat_Surface.uasset b/Content/Kitchen_Materials/M_Heat_Surface.uasset new file mode 100644 index 00000000..a0bb642b Binary files /dev/null and b/Content/Kitchen_Materials/M_Heat_Surface.uasset differ diff --git a/Content/Kitchen_Materials/M_Injector.uasset b/Content/Kitchen_Materials/M_Injector.uasset new file mode 100644 index 00000000..fb12e959 Binary files /dev/null and b/Content/Kitchen_Materials/M_Injector.uasset differ diff --git a/Content/Kitchen_Materials/M_Reservoir.uasset b/Content/Kitchen_Materials/M_Reservoir.uasset new file mode 100644 index 00000000..c629b3f7 Binary files /dev/null and b/Content/Kitchen_Materials/M_Reservoir.uasset differ diff --git a/Content/Kitchen_Materials/M_Table_Top_Granite.uasset b/Content/Kitchen_Materials/M_Table_Top_Granite.uasset new file mode 100644 index 00000000..08343e28 Binary files /dev/null and b/Content/Kitchen_Materials/M_Table_Top_Granite.uasset differ diff --git a/Content/Kitchen_Materials/M_Table_Top_Marble.uasset b/Content/Kitchen_Materials/M_Table_Top_Marble.uasset new file mode 100644 index 00000000..d9765a95 Binary files /dev/null and b/Content/Kitchen_Materials/M_Table_Top_Marble.uasset differ diff --git a/Content/Kitchen_Materials/M_Table_Top_Wood.uasset b/Content/Kitchen_Materials/M_Table_Top_Wood.uasset new file mode 100644 index 00000000..9cb2b339 Binary files /dev/null and b/Content/Kitchen_Materials/M_Table_Top_Wood.uasset differ diff --git a/Content/Maps/House1/H1Flat0.umap b/Content/Maps/House1/H1Flat0.umap deleted file mode 100644 index d517e8c7..00000000 Binary files a/Content/Maps/House1/H1Flat0.umap and /dev/null differ diff --git a/Content/Maps/House1/H1Flat1.umap b/Content/Maps/House1/H1Flat1.umap deleted file mode 100644 index 9f036293..00000000 Binary files a/Content/Maps/House1/H1Flat1.umap and /dev/null differ diff --git a/Content/Maps/House1/H1Flat10.umap b/Content/Maps/House1/H1Flat10.umap deleted file mode 100644 index cae33b47..00000000 Binary files a/Content/Maps/House1/H1Flat10.umap and /dev/null differ diff --git a/Content/Maps/House1/H1Flat11.umap b/Content/Maps/House1/H1Flat11.umap deleted file mode 100644 index 2b93179b..00000000 Binary files a/Content/Maps/House1/H1Flat11.umap and /dev/null differ diff --git a/Content/Maps/House1/H1Flat12.umap b/Content/Maps/House1/H1Flat12.umap deleted file mode 100644 index f071b0d9..00000000 Binary files a/Content/Maps/House1/H1Flat12.umap and /dev/null differ diff --git a/Content/Maps/House1/H1Flat13.umap b/Content/Maps/House1/H1Flat13.umap deleted file mode 100644 index c8bfab71..00000000 Binary files a/Content/Maps/House1/H1Flat13.umap and /dev/null differ diff --git a/Content/Maps/House1/H1Flat2.umap b/Content/Maps/House1/H1Flat2.umap deleted file mode 100644 index 8be5d6a9..00000000 Binary files a/Content/Maps/House1/H1Flat2.umap and /dev/null differ diff --git a/Content/Maps/House1/H1Flat3.umap b/Content/Maps/House1/H1Flat3.umap deleted file mode 100644 index 6fbaf884..00000000 Binary files a/Content/Maps/House1/H1Flat3.umap and /dev/null differ diff --git a/Content/Maps/House1/H1Flat4.umap b/Content/Maps/House1/H1Flat4.umap deleted file mode 100644 index 000b47f4..00000000 Binary files a/Content/Maps/House1/H1Flat4.umap and /dev/null differ diff --git a/Content/Maps/House1/H1Flat5.umap b/Content/Maps/House1/H1Flat5.umap deleted file mode 100644 index afe6929f..00000000 Binary files a/Content/Maps/House1/H1Flat5.umap and /dev/null differ diff --git a/Content/Maps/House1/H1Flat6.umap b/Content/Maps/House1/H1Flat6.umap deleted file mode 100644 index 4929b930..00000000 Binary files a/Content/Maps/House1/H1Flat6.umap and /dev/null differ diff --git a/Content/Maps/House1/H1Flat7.umap b/Content/Maps/House1/H1Flat7.umap deleted file mode 100644 index 7aa70840..00000000 Binary files a/Content/Maps/House1/H1Flat7.umap and /dev/null differ diff --git a/Content/Maps/House1/H1Flat8.umap b/Content/Maps/House1/H1Flat8.umap deleted file mode 100644 index 4b0633c1..00000000 Binary files a/Content/Maps/House1/H1Flat8.umap and /dev/null differ diff --git a/Content/Maps/House1/H1Flat9.umap b/Content/Maps/House1/H1Flat9.umap deleted file mode 100644 index 5df7e657..00000000 Binary files a/Content/Maps/House1/H1Flat9.umap and /dev/null differ diff --git a/Content/Maps/House1/H1Floor1.umap b/Content/Maps/House1/H1Floor1.umap deleted file mode 100644 index bba3d5ed..00000000 Binary files a/Content/Maps/House1/H1Floor1.umap and /dev/null differ diff --git a/Content/Maps/House4/H4Flat0.umap b/Content/Maps/House4/H4Flat0.umap deleted file mode 100644 index 569f4c84..00000000 Binary files a/Content/Maps/House4/H4Flat0.umap and /dev/null differ diff --git a/Content/Maps/House4/H4Flat1.umap b/Content/Maps/House4/H4Flat1.umap deleted file mode 100644 index f00801f1..00000000 Binary files a/Content/Maps/House4/H4Flat1.umap and /dev/null differ diff --git a/Content/Maps/House4/H4Flat2.umap b/Content/Maps/House4/H4Flat2.umap deleted file mode 100644 index 61630092..00000000 Binary files a/Content/Maps/House4/H4Flat2.umap and /dev/null differ diff --git a/Content/Maps/House4/H4Flat3.umap b/Content/Maps/House4/H4Flat3.umap deleted file mode 100644 index 0b32d4aa..00000000 Binary files a/Content/Maps/House4/H4Flat3.umap and /dev/null differ diff --git a/Content/Maps/House4/H4Flat4.umap b/Content/Maps/House4/H4Flat4.umap deleted file mode 100644 index 224a7cfc..00000000 Binary files a/Content/Maps/House4/H4Flat4.umap and /dev/null differ diff --git a/Content/Maps/House4/H4Floor3.umap b/Content/Maps/House4/H4Floor3.umap deleted file mode 100644 index e3bdf2ca..00000000 Binary files a/Content/Maps/House4/H4Floor3.umap and /dev/null differ diff --git a/Content/Maps/S01/S01F03.umap b/Content/Maps/S01/S01F03.umap index aba3fcb2..2c48a208 100644 Binary files a/Content/Maps/S01/S01F03.umap and b/Content/Maps/S01/S01F03.umap differ diff --git a/Content/Maps/S01/S01F14.umap b/Content/Maps/S01/S01F14.umap index 476a724e..693159f1 100644 Binary files a/Content/Maps/S01/S01F14.umap and b/Content/Maps/S01/S01F14.umap differ diff --git a/Content/Maps/S01/S01F15.umap b/Content/Maps/S01/S01F15.umap index cf1c1923..30e37a4d 100644 Binary files a/Content/Maps/S01/S01F15.umap and b/Content/Maps/S01/S01F15.umap differ diff --git a/Content/Maps/S01/stuff.umap b/Content/Maps/S01/stuff.umap new file mode 100644 index 00000000..6a127872 Binary files /dev/null and b/Content/Maps/S01/stuff.umap differ diff --git a/Content/Maps/S04/S04F03/S04F03.umap b/Content/Maps/S04/S04F03/S04F03.umap index 336f7038..8f0397dc 100644 Binary files a/Content/Maps/S04/S04F03/S04F03.umap and b/Content/Maps/S04/S04F03/S04F03.umap differ diff --git a/Content/Maps/S04/S04F03/S04F03A01.umap b/Content/Maps/S04/S04F03/S04F03A01.umap index bb8c83e2..05538eba 100644 Binary files a/Content/Maps/S04/S04F03/S04F03A01.umap and b/Content/Maps/S04/S04F03/S04F03A01.umap differ diff --git a/Content/Maps/S04/S04F03/S04F03A02.umap b/Content/Maps/S04/S04F03/S04F03A02.umap index abc42b77..aa6316bb 100644 Binary files a/Content/Maps/S04/S04F03/S04F03A02.umap and b/Content/Maps/S04/S04F03/S04F03A02.umap differ diff --git a/Content/Maps/S04/S04F03/S04F03A03.umap b/Content/Maps/S04/S04F03/S04F03A03.umap index 43a3b684..d767fc03 100644 Binary files a/Content/Maps/S04/S04F03/S04F03A03.umap and b/Content/Maps/S04/S04F03/S04F03A03.umap differ diff --git a/Content/Maps/S04/S04F03/S04F03A04.umap b/Content/Maps/S04/S04F03/S04F03A04.umap index 8e7ff357..5434020c 100644 Binary files a/Content/Maps/S04/S04F03/S04F03A04.umap and b/Content/Maps/S04/S04F03/S04F03A04.umap differ diff --git a/Content/Maps/S04/S04F03/S04F03A05.umap b/Content/Maps/S04/S04F03/S04F03A05.umap index 24cfe34a..7209bd01 100644 Binary files a/Content/Maps/S04/S04F03/S04F03A05.umap and b/Content/Maps/S04/S04F03/S04F03A05.umap differ diff --git a/Content/Maps/S04/S04F03/S04F04.umap b/Content/Maps/S04/S04F03/S04F04.umap new file mode 100644 index 00000000..cb39a595 Binary files /dev/null and b/Content/Maps/S04/S04F03/S04F04.umap differ diff --git a/Content/Materials/Instances/MI_Shower_1.uasset b/Content/Materials/Instances/MI_Shower_1.uasset new file mode 100644 index 00000000..986b4d8b Binary files /dev/null and b/Content/Materials/Instances/MI_Shower_1.uasset differ diff --git a/Content/Materials/Instances/MI_Shower_2_1.uasset b/Content/Materials/Instances/MI_Shower_2_1.uasset new file mode 100644 index 00000000..eb347583 Binary files /dev/null and b/Content/Materials/Instances/MI_Shower_2_1.uasset differ diff --git a/Content/Materials/Instances/MI_Shower_2_2.uasset b/Content/Materials/Instances/MI_Shower_2_2.uasset new file mode 100644 index 00000000..75f5e05f Binary files /dev/null and b/Content/Materials/Instances/MI_Shower_2_2.uasset differ diff --git a/Content/Materials/Masters/MM_Substence.uasset b/Content/Materials/Masters/MM_Substence.uasset new file mode 100644 index 00000000..c04b4392 Binary files /dev/null and b/Content/Materials/Masters/MM_Substence.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A00.uasset b/Content/Meshes/Danis/S01/SM_S01F04A00.uasset index 31d46989..1dcd80e6 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A00.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A00.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A01.uasset b/Content/Meshes/Danis/S01/SM_S01F04A01.uasset index ee018cb3..f5b3b097 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A01.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A01.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A02.uasset b/Content/Meshes/Danis/S01/SM_S01F04A02.uasset index 0f525804..94030d7d 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A02.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A02.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A03.uasset b/Content/Meshes/Danis/S01/SM_S01F04A03.uasset index 29a975e7..9f208129 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A03.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A03.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A04.uasset b/Content/Meshes/Danis/S01/SM_S01F04A04.uasset index 3190dc28..1b4982e7 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A04.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A04.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A05.uasset b/Content/Meshes/Danis/S01/SM_S01F04A05.uasset index d5c53f41..858e042c 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A05.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A05.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A06.uasset b/Content/Meshes/Danis/S01/SM_S01F04A06.uasset index c878e8de..d1ba7562 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A06.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A06.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A07.uasset b/Content/Meshes/Danis/S01/SM_S01F04A07.uasset index 1ae3178b..04213dec 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A07.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A07.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A08.uasset b/Content/Meshes/Danis/S01/SM_S01F04A08.uasset index d4667d1e..79ff4f4d 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A08.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A08.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A09.uasset b/Content/Meshes/Danis/S01/SM_S01F04A09.uasset index 9f4e5c88..a6a44aa8 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A09.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A09.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A10.uasset b/Content/Meshes/Danis/S01/SM_S01F04A10.uasset index 11c79e42..0484f9bc 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A10.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A10.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A11.uasset b/Content/Meshes/Danis/S01/SM_S01F04A11.uasset index f378084c..7e1fb100 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A11.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A11.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A12.uasset b/Content/Meshes/Danis/S01/SM_S01F04A12.uasset index c8df2d1c..3fdf4aee 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A12.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A12.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A13.uasset b/Content/Meshes/Danis/S01/SM_S01F04A13.uasset index d251429d..b9b4cd6d 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A13.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A13.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04A14.uasset b/Content/Meshes/Danis/S01/SM_S01F04A14.uasset index 061c2021..52c8e9bb 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04A14.uasset and b/Content/Meshes/Danis/S01/SM_S01F04A14.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F04_WallsCap.uasset b/Content/Meshes/Danis/S01/SM_S01F04_WallsCap.uasset index d367ffe0..1f1618a8 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F04_WallsCap.uasset and b/Content/Meshes/Danis/S01/SM_S01F04_WallsCap.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F14A00.uasset b/Content/Meshes/Danis/S01/SM_S01F14A00.uasset index 397c127f..6833e180 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F14A00.uasset and b/Content/Meshes/Danis/S01/SM_S01F14A00.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F14A01.uasset b/Content/Meshes/Danis/S01/SM_S01F14A01.uasset index 06db7b37..6bbf263c 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F14A01.uasset and b/Content/Meshes/Danis/S01/SM_S01F14A01.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F14A02.uasset b/Content/Meshes/Danis/S01/SM_S01F14A02.uasset index ed34cb71..79c272b1 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F14A02.uasset and b/Content/Meshes/Danis/S01/SM_S01F14A02.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F14A03.uasset b/Content/Meshes/Danis/S01/SM_S01F14A03.uasset index e7cf0619..07e350df 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F14A03.uasset and b/Content/Meshes/Danis/S01/SM_S01F14A03.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F14A04.uasset b/Content/Meshes/Danis/S01/SM_S01F14A04.uasset index 83465410..4957a031 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F14A04.uasset and b/Content/Meshes/Danis/S01/SM_S01F14A04.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F14A05.uasset b/Content/Meshes/Danis/S01/SM_S01F14A05.uasset index de690616..a536ceae 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F14A05.uasset and b/Content/Meshes/Danis/S01/SM_S01F14A05.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F14A06.uasset b/Content/Meshes/Danis/S01/SM_S01F14A06.uasset index 691d899f..06330c22 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F14A06.uasset and b/Content/Meshes/Danis/S01/SM_S01F14A06.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F14A07.uasset b/Content/Meshes/Danis/S01/SM_S01F14A07.uasset index 1e0db7c4..3dad32bd 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F14A07.uasset and b/Content/Meshes/Danis/S01/SM_S01F14A07.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F14A08.uasset b/Content/Meshes/Danis/S01/SM_S01F14A08.uasset index 38cdb543..8595dfdf 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F14A08.uasset and b/Content/Meshes/Danis/S01/SM_S01F14A08.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F14A09.uasset b/Content/Meshes/Danis/S01/SM_S01F14A09.uasset index 7435b587..09d82214 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F14A09.uasset and b/Content/Meshes/Danis/S01/SM_S01F14A09.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F14A10.uasset b/Content/Meshes/Danis/S01/SM_S01F14A10.uasset index fa61f489..9392e1ca 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F14A10.uasset and b/Content/Meshes/Danis/S01/SM_S01F14A10.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F14A11.uasset b/Content/Meshes/Danis/S01/SM_S01F14A11.uasset index 770ccd4d..2b044003 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F14A11.uasset and b/Content/Meshes/Danis/S01/SM_S01F14A11.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F14A12.uasset b/Content/Meshes/Danis/S01/SM_S01F14A12.uasset index 063f4cba..26f10253 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F14A12.uasset and b/Content/Meshes/Danis/S01/SM_S01F14A12.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F14_WallsCap.uasset b/Content/Meshes/Danis/S01/SM_S01F14_WallsCap.uasset index 5f62b52c..d112aae9 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F14_WallsCap.uasset and b/Content/Meshes/Danis/S01/SM_S01F14_WallsCap.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15A00.uasset b/Content/Meshes/Danis/S01/SM_S01F15A00.uasset index 7dd25ef8..29f83fc0 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15A00.uasset and b/Content/Meshes/Danis/S01/SM_S01F15A00.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15A01.uasset b/Content/Meshes/Danis/S01/SM_S01F15A01.uasset index 2e15d455..5b38152b 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15A01.uasset and b/Content/Meshes/Danis/S01/SM_S01F15A01.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15A02.uasset b/Content/Meshes/Danis/S01/SM_S01F15A02.uasset index 8a70ce65..c19378d9 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15A02.uasset and b/Content/Meshes/Danis/S01/SM_S01F15A02.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15A03.uasset b/Content/Meshes/Danis/S01/SM_S01F15A03.uasset index 9b728fa5..d81fe5e5 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15A03.uasset and b/Content/Meshes/Danis/S01/SM_S01F15A03.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15A04.uasset b/Content/Meshes/Danis/S01/SM_S01F15A04.uasset index d8ed3a57..62f852dd 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15A04.uasset and b/Content/Meshes/Danis/S01/SM_S01F15A04.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15A05.uasset b/Content/Meshes/Danis/S01/SM_S01F15A05.uasset index 6463003c..2a4da6af 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15A05.uasset and b/Content/Meshes/Danis/S01/SM_S01F15A05.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15A06.uasset b/Content/Meshes/Danis/S01/SM_S01F15A06.uasset index 24a688bd..980ea006 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15A06.uasset and b/Content/Meshes/Danis/S01/SM_S01F15A06.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15A07.uasset b/Content/Meshes/Danis/S01/SM_S01F15A07.uasset index 1fc22437..641dc137 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15A07.uasset and b/Content/Meshes/Danis/S01/SM_S01F15A07.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15A08.uasset b/Content/Meshes/Danis/S01/SM_S01F15A08.uasset index 8b2ded0b..9effc1a1 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15A08.uasset and b/Content/Meshes/Danis/S01/SM_S01F15A08.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15A09.uasset b/Content/Meshes/Danis/S01/SM_S01F15A09.uasset index 3ff0958d..9963c07d 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15A09.uasset and b/Content/Meshes/Danis/S01/SM_S01F15A09.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15A10.uasset b/Content/Meshes/Danis/S01/SM_S01F15A10.uasset index 700fa07f..d32bd621 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15A10.uasset and b/Content/Meshes/Danis/S01/SM_S01F15A10.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15A11.uasset b/Content/Meshes/Danis/S01/SM_S01F15A11.uasset index 71010650..5081e7ec 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15A11.uasset and b/Content/Meshes/Danis/S01/SM_S01F15A11.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15A12.uasset b/Content/Meshes/Danis/S01/SM_S01F15A12.uasset index 2dbb2cd3..853a8530 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15A12.uasset and b/Content/Meshes/Danis/S01/SM_S01F15A12.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15A13.uasset b/Content/Meshes/Danis/S01/SM_S01F15A13.uasset index 3edfc67d..72c03623 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15A13.uasset and b/Content/Meshes/Danis/S01/SM_S01F15A13.uasset differ diff --git a/Content/Meshes/Danis/S01/SM_S01F15_WallsCap.uasset b/Content/Meshes/Danis/S01/SM_S01F15_WallsCap.uasset index 50f5e0fd..5ad1b11d 100644 Binary files a/Content/Meshes/Danis/S01/SM_S01F15_WallsCap.uasset and b/Content/Meshes/Danis/S01/SM_S01F15_WallsCap.uasset differ diff --git a/Content/Shower/SM_Shower_90x120_shower01_low.uasset b/Content/Shower/SM_Shower_90x120_shower01_low.uasset new file mode 100644 index 00000000..8e702bd2 Binary files /dev/null and b/Content/Shower/SM_Shower_90x120_shower01_low.uasset differ diff --git a/Content/Shower/SM_Shower_90x120_shower02_low.uasset b/Content/Shower/SM_Shower_90x120_shower02_low.uasset new file mode 100644 index 00000000..d4af5b29 Binary files /dev/null and b/Content/Shower/SM_Shower_90x120_shower02_low.uasset differ diff --git a/Content/Shower/SM_Shower_90x120_shower03_low.uasset b/Content/Shower/SM_Shower_90x120_shower03_low.uasset new file mode 100644 index 00000000..1da87988 Binary files /dev/null and b/Content/Shower/SM_Shower_90x120_shower03_low.uasset differ diff --git a/Content/Shower/SM_Shower_90x90_Shower01_low.uasset b/Content/Shower/SM_Shower_90x90_Shower01_low.uasset new file mode 100644 index 00000000..ddd673dc Binary files /dev/null and b/Content/Shower/SM_Shower_90x90_Shower01_low.uasset differ diff --git a/Content/Textures/T_MacroVariation.uasset b/Content/Textures/T_MacroVariation.uasset index 2413c21a..c7392162 100644 Binary files a/Content/Textures/T_MacroVariation.uasset and b/Content/Textures/T_MacroVariation.uasset differ diff --git a/Content/Textures/T_Shower_1_BC.uasset b/Content/Textures/T_Shower_1_BC.uasset new file mode 100644 index 00000000..d48a2e4e Binary files /dev/null and b/Content/Textures/T_Shower_1_BC.uasset differ diff --git a/Content/Textures/T_Shower_1_N.uasset b/Content/Textures/T_Shower_1_N.uasset new file mode 100644 index 00000000..16a1c0d4 Binary files /dev/null and b/Content/Textures/T_Shower_1_N.uasset differ diff --git a/Content/Textures/T_Shower_1_ORM.uasset b/Content/Textures/T_Shower_1_ORM.uasset new file mode 100644 index 00000000..3c20d3fd Binary files /dev/null and b/Content/Textures/T_Shower_1_ORM.uasset differ diff --git a/Content/Textures/T_Shower_2_1_BC.uasset b/Content/Textures/T_Shower_2_1_BC.uasset new file mode 100644 index 00000000..2093336f Binary files /dev/null and b/Content/Textures/T_Shower_2_1_BC.uasset differ diff --git a/Content/Textures/T_Shower_2_1_N.uasset b/Content/Textures/T_Shower_2_1_N.uasset new file mode 100644 index 00000000..ba57437e Binary files /dev/null and b/Content/Textures/T_Shower_2_1_N.uasset differ diff --git a/Content/Textures/T_Shower_2_1_ORM.uasset b/Content/Textures/T_Shower_2_1_ORM.uasset new file mode 100644 index 00000000..c66362b2 Binary files /dev/null and b/Content/Textures/T_Shower_2_1_ORM.uasset differ diff --git a/Content/Textures/T_Shower_2_2_BC.uasset b/Content/Textures/T_Shower_2_2_BC.uasset new file mode 100644 index 00000000..8e7692c5 Binary files /dev/null and b/Content/Textures/T_Shower_2_2_BC.uasset differ diff --git a/Content/Textures/T_Shower_2_2_N.uasset b/Content/Textures/T_Shower_2_2_N.uasset new file mode 100644 index 00000000..179cc514 Binary files /dev/null and b/Content/Textures/T_Shower_2_2_N.uasset differ diff --git a/Content/Textures/T_Shower_2_2_ORM.uasset b/Content/Textures/T_Shower_2_2_ORM.uasset new file mode 100644 index 00000000..d28107a5 Binary files /dev/null and b/Content/Textures/T_Shower_2_2_ORM.uasset differ diff --git a/Content/content/AssetPacks/bedroom/SM_Tubes_radiatori_SM_Tubes_radiatori_3.uasset b/Content/content/AssetPacks/bedroom/SM_Tubes_radiatori_SM_Tubes_radiatori_3.uasset new file mode 100644 index 00000000..3ec24ace Binary files /dev/null and b/Content/content/AssetPacks/bedroom/SM_Tubes_radiatori_SM_Tubes_radiatori_3.uasset differ diff --git a/Imgs/Custom Size – 3.png b/Imgs/Custom Size – 3.png new file mode 100644 index 00000000..855bfc35 Binary files /dev/null and b/Imgs/Custom Size – 3.png differ diff --git a/Imgs/all_txt.png b/Imgs/all_txt.png new file mode 100644 index 00000000..c390d6b4 Binary files /dev/null and b/Imgs/all_txt.png differ diff --git a/Imgs/bkg_btn_bottom_active.png b/Imgs/bkg_btn_bottom_active.png new file mode 100644 index 00000000..c661fb26 Binary files /dev/null and b/Imgs/bkg_btn_bottom_active.png differ diff --git a/Imgs/bkg_btn_bottom_normal.png b/Imgs/bkg_btn_bottom_normal.png new file mode 100644 index 00000000..d8a603dd Binary files /dev/null and b/Imgs/bkg_btn_bottom_normal.png differ diff --git a/Imgs/bkg_btn_whitel.png b/Imgs/bkg_btn_whitel.png new file mode 100644 index 00000000..a92ce0d6 Binary files /dev/null and b/Imgs/bkg_btn_whitel.png differ diff --git a/Imgs/bkg_logo_graff-1.png b/Imgs/bkg_logo_graff-1.png new file mode 100644 index 00000000..7481b64a Binary files /dev/null and b/Imgs/bkg_logo_graff-1.png differ diff --git a/Imgs/bkg_map_big.png b/Imgs/bkg_map_big.png new file mode 100644 index 00000000..9a7af023 Binary files /dev/null and b/Imgs/bkg_map_big.png differ diff --git a/Imgs/btn_back.png b/Imgs/btn_back.png new file mode 100644 index 00000000..f40e7a7d Binary files /dev/null and b/Imgs/btn_back.png differ diff --git a/Imgs/btn_bottom_active.png b/Imgs/btn_bottom_active.png new file mode 100644 index 00000000..e4fb7182 Binary files /dev/null and b/Imgs/btn_bottom_active.png differ diff --git a/Imgs/btn_bottom_normal.png b/Imgs/btn_bottom_normal.png new file mode 100644 index 00000000..d0413683 Binary files /dev/null and b/Imgs/btn_bottom_normal.png differ diff --git a/Imgs/btn_close.png b/Imgs/btn_close.png new file mode 100644 index 00000000..b381945b Binary files /dev/null and b/Imgs/btn_close.png differ diff --git a/Imgs/btn_close_window_info.png b/Imgs/btn_close_window_info.png new file mode 100644 index 00000000..1e117919 Binary files /dev/null and b/Imgs/btn_close_window_info.png differ diff --git a/Imgs/btn_day_y_like_active.png b/Imgs/btn_day_y_like_active.png new file mode 100644 index 00000000..359d4fa5 Binary files /dev/null and b/Imgs/btn_day_y_like_active.png differ diff --git a/Imgs/btn_day_y_like_normal.png b/Imgs/btn_day_y_like_normal.png new file mode 100644 index 00000000..ef664aac Binary files /dev/null and b/Imgs/btn_day_y_like_normal.png differ diff --git a/Imgs/btn_dom.png b/Imgs/btn_dom.png new file mode 100644 index 00000000..1b434a46 Binary files /dev/null and b/Imgs/btn_dom.png differ diff --git a/Imgs/btn_floor.png b/Imgs/btn_floor.png new file mode 100644 index 00000000..9677f383 Binary files /dev/null and b/Imgs/btn_floor.png differ diff --git a/Imgs/btn_floor_lock.png b/Imgs/btn_floor_lock.png new file mode 100644 index 00000000..6eea338c Binary files /dev/null and b/Imgs/btn_floor_lock.png differ diff --git a/Imgs/btn_objects.png b/Imgs/btn_objects.png new file mode 100644 index 00000000..549c21be Binary files /dev/null and b/Imgs/btn_objects.png differ diff --git a/Imgs/btn_right_active.png b/Imgs/btn_right_active.png new file mode 100644 index 00000000..455198fb Binary files /dev/null and b/Imgs/btn_right_active.png differ diff --git a/Imgs/btn_right_normal.png b/Imgs/btn_right_normal.png new file mode 100644 index 00000000..371a477a Binary files /dev/null and b/Imgs/btn_right_normal.png differ diff --git a/Imgs/color_dop.png b/Imgs/color_dop.png new file mode 100644 index 00000000..e5283c42 Binary files /dev/null and b/Imgs/color_dop.png differ diff --git a/Imgs/color_main.png b/Imgs/color_main.png new file mode 100644 index 00000000..caa4126c Binary files /dev/null and b/Imgs/color_main.png differ diff --git a/Imgs/color_select.png b/Imgs/color_select.png new file mode 100644 index 00000000..30dddc56 Binary files /dev/null and b/Imgs/color_select.png differ diff --git a/Imgs/compass.png b/Imgs/compass.png new file mode 100644 index 00000000..d62ea365 Binary files /dev/null and b/Imgs/compass.png differ diff --git a/Imgs/graff_info.png b/Imgs/graff_info.png new file mode 100644 index 00000000..cf0719f7 Binary files /dev/null and b/Imgs/graff_info.png differ diff --git a/Imgs/hint_floor.png b/Imgs/hint_floor.png new file mode 100644 index 00000000..abb8f687 Binary files /dev/null and b/Imgs/hint_floor.png differ diff --git a/Imgs/hint_vtur.png b/Imgs/hint_vtur.png new file mode 100644 index 00000000..6d06e458 Binary files /dev/null and b/Imgs/hint_vtur.png differ diff --git a/Imgs/hint_vtur_icon.png b/Imgs/hint_vtur_icon.png new file mode 100644 index 00000000..cd85b0d2 Binary files /dev/null and b/Imgs/hint_vtur_icon.png differ diff --git a/Imgs/icon_3d_normal.png b/Imgs/icon_3d_normal.png new file mode 100644 index 00000000..9e1d83ad Binary files /dev/null and b/Imgs/icon_3d_normal.png differ diff --git a/Imgs/icon_5.png b/Imgs/icon_5.png new file mode 100644 index 00000000..a40d59ce Binary files /dev/null and b/Imgs/icon_5.png differ diff --git a/Imgs/icon_DS.png b/Imgs/icon_DS.png new file mode 100644 index 00000000..504b3abd Binary files /dev/null and b/Imgs/icon_DS.png differ diff --git a/Imgs/icon_Wildberries.png b/Imgs/icon_Wildberries.png new file mode 100644 index 00000000..9c8d402b Binary files /dev/null and b/Imgs/icon_Wildberries.png differ diff --git a/Imgs/icon_back.png b/Imgs/icon_back.png new file mode 100644 index 00000000..e00f8085 Binary files /dev/null and b/Imgs/icon_back.png differ diff --git a/Imgs/icon_brusnika.png b/Imgs/icon_brusnika.png new file mode 100644 index 00000000..3d1f0fe3 Binary files /dev/null and b/Imgs/icon_brusnika.png differ diff --git a/Imgs/icon_camera_normal.png b/Imgs/icon_camera_normal.png new file mode 100644 index 00000000..0900260b Binary files /dev/null and b/Imgs/icon_camera_normal.png differ diff --git a/Imgs/icon_day_normal.png b/Imgs/icon_day_normal.png new file mode 100644 index 00000000..7b3d51a4 Binary files /dev/null and b/Imgs/icon_day_normal.png differ diff --git a/Imgs/icon_info_normal.png b/Imgs/icon_info_normal.png new file mode 100644 index 00000000..4de163c5 Binary files /dev/null and b/Imgs/icon_info_normal.png differ diff --git a/Imgs/icon_infra_normal.png b/Imgs/icon_infra_normal.png new file mode 100644 index 00000000..7c4d75e1 Binary files /dev/null and b/Imgs/icon_infra_normal.png differ diff --git a/Imgs/icon_like_normal.png b/Imgs/icon_like_normal.png new file mode 100644 index 00000000..d7fbed07 Binary files /dev/null and b/Imgs/icon_like_normal.png differ diff --git a/Imgs/icon_lock_active.png b/Imgs/icon_lock_active.png new file mode 100644 index 00000000..31d86e26 Binary files /dev/null and b/Imgs/icon_lock_active.png differ diff --git a/Imgs/icon_lock_normal.png b/Imgs/icon_lock_normal.png new file mode 100644 index 00000000..3132a898 Binary files /dev/null and b/Imgs/icon_lock_normal.png differ diff --git a/Imgs/icon_magnit.png b/Imgs/icon_magnit.png new file mode 100644 index 00000000..5ac9dd1b Binary files /dev/null and b/Imgs/icon_magnit.png differ diff --git a/Imgs/icon_menu_normal.png b/Imgs/icon_menu_normal.png new file mode 100644 index 00000000..02c425fb Binary files /dev/null and b/Imgs/icon_menu_normal.png differ diff --git a/Imgs/icon_metro.png b/Imgs/icon_metro.png new file mode 100644 index 00000000..e23c9930 Binary files /dev/null and b/Imgs/icon_metro.png differ diff --git a/Imgs/icon_monetka.png b/Imgs/icon_monetka.png new file mode 100644 index 00000000..0600807d Binary files /dev/null and b/Imgs/icon_monetka.png differ diff --git a/Imgs/icon_navigator_orange.png b/Imgs/icon_navigator_orange.png new file mode 100644 index 00000000..13243925 Binary files /dev/null and b/Imgs/icon_navigator_orange.png differ diff --git a/Imgs/icon_photo_normal-1.png b/Imgs/icon_photo_normal-1.png new file mode 100644 index 00000000..82654133 Binary files /dev/null and b/Imgs/icon_photo_normal-1.png differ diff --git a/Imgs/icon_photo_normal.png b/Imgs/icon_photo_normal.png new file mode 100644 index 00000000..8e5afdd4 Binary files /dev/null and b/Imgs/icon_photo_normal.png differ diff --git a/Imgs/icon_pickpoint.png b/Imgs/icon_pickpoint.png new file mode 100644 index 00000000..04c8da35 Binary files /dev/null and b/Imgs/icon_pickpoint.png differ diff --git a/Imgs/icon_play_normal.png b/Imgs/icon_play_normal.png new file mode 100644 index 00000000..779d926d Binary files /dev/null and b/Imgs/icon_play_normal.png differ diff --git a/Imgs/icon_sber.png b/Imgs/icon_sber.png new file mode 100644 index 00000000..d7011ed2 Binary files /dev/null and b/Imgs/icon_sber.png differ diff --git a/Imgs/icon_school.png b/Imgs/icon_school.png new file mode 100644 index 00000000..3d6a52b9 Binary files /dev/null and b/Imgs/icon_school.png differ diff --git a/Imgs/icon_transport.png b/Imgs/icon_transport.png new file mode 100644 index 00000000..67c064ce Binary files /dev/null and b/Imgs/icon_transport.png differ diff --git a/Imgs/icon_view.png b/Imgs/icon_view.png new file mode 100644 index 00000000..ff5c5d5e Binary files /dev/null and b/Imgs/icon_view.png differ diff --git a/Imgs/logo_4you_active.png b/Imgs/logo_4you_active.png new file mode 100644 index 00000000..ce6c471c Binary files /dev/null and b/Imgs/logo_4you_active.png differ diff --git a/Imgs/logo_4you_normal.png b/Imgs/logo_4you_normal.png new file mode 100644 index 00000000..15e00ab2 Binary files /dev/null and b/Imgs/logo_4you_normal.png differ diff --git a/Imgs/logo_graff.png b/Imgs/logo_graff.png new file mode 100644 index 00000000..673b2bca Binary files /dev/null and b/Imgs/logo_graff.png differ diff --git a/Imgs/nearMarket/icon_alcomarket.png b/Imgs/nearMarket/icon_alcomarket.png new file mode 100644 index 00000000..4917bbc2 Binary files /dev/null and b/Imgs/nearMarket/icon_alcomarket.png differ diff --git a/Imgs/nearMarket/icon_apteka.png b/Imgs/nearMarket/icon_apteka.png new file mode 100644 index 00000000..acaa4539 Binary files /dev/null and b/Imgs/nearMarket/icon_apteka.png differ diff --git a/Imgs/nearMarket/icon_barber.png b/Imgs/nearMarket/icon_barber.png new file mode 100644 index 00000000..c766a3f2 Binary files /dev/null and b/Imgs/nearMarket/icon_barber.png differ diff --git a/Imgs/nearMarket/icon_bytovye.png b/Imgs/nearMarket/icon_bytovye.png new file mode 100644 index 00000000..e3289fe5 Binary files /dev/null and b/Imgs/nearMarket/icon_bytovye.png differ diff --git a/Imgs/nearMarket/icon_cosmetic.png b/Imgs/nearMarket/icon_cosmetic.png new file mode 100644 index 00000000..32571de9 Binary files /dev/null and b/Imgs/nearMarket/icon_cosmetic.png differ diff --git a/Imgs/nearMarket/icon_detskaya_ploshadka.png b/Imgs/nearMarket/icon_detskaya_ploshadka.png new file mode 100644 index 00000000..4a3eaffb Binary files /dev/null and b/Imgs/nearMarket/icon_detskaya_ploshadka.png differ diff --git a/Imgs/nearMarket/icon_parking.png b/Imgs/nearMarket/icon_parking.png new file mode 100644 index 00000000..4ea89a58 Binary files /dev/null and b/Imgs/nearMarket/icon_parking.png differ diff --git a/Imgs/nearMarket/icon_salon_krasoty.png b/Imgs/nearMarket/icon_salon_krasoty.png new file mode 100644 index 00000000..7304b087 Binary files /dev/null and b/Imgs/nearMarket/icon_salon_krasoty.png differ diff --git a/Imgs/nearMarket/icon_sport.png b/Imgs/nearMarket/icon_sport.png new file mode 100644 index 00000000..dae05510 Binary files /dev/null and b/Imgs/nearMarket/icon_sport.png differ diff --git a/Imgs/nearMarket/icon_supermarket.png b/Imgs/nearMarket/icon_supermarket.png new file mode 100644 index 00000000..22a18766 Binary files /dev/null and b/Imgs/nearMarket/icon_supermarket.png differ diff --git a/Imgs/nearMarket/icon_zona_otdyha.png b/Imgs/nearMarket/icon_zona_otdyha.png new file mode 100644 index 00000000..15b2b868 Binary files /dev/null and b/Imgs/nearMarket/icon_zona_otdyha.png differ diff --git a/Imgs/nearMarket/icon_zoomag.png b/Imgs/nearMarket/icon_zoomag.png new file mode 100644 index 00000000..f292c254 Binary files /dev/null and b/Imgs/nearMarket/icon_zoomag.png differ diff --git a/Imgs/newInterface/bkg_btns.png b/Imgs/newInterface/bkg_btns.png new file mode 100644 index 00000000..dcfdc617 Binary files /dev/null and b/Imgs/newInterface/bkg_btns.png differ diff --git a/Imgs/newInterface/bkg_compass.png b/Imgs/newInterface/bkg_compass.png new file mode 100644 index 00000000..a4b70c28 Binary files /dev/null and b/Imgs/newInterface/bkg_compass.png differ diff --git a/Imgs/newInterface/bkg_logo.png b/Imgs/newInterface/bkg_logo.png new file mode 100644 index 00000000..58d8cc7b Binary files /dev/null and b/Imgs/newInterface/bkg_logo.png differ diff --git a/Imgs/newInterface/btn_day_night_active.png b/Imgs/newInterface/btn_day_night_active.png new file mode 100644 index 00000000..aaa77042 Binary files /dev/null and b/Imgs/newInterface/btn_day_night_active.png differ diff --git a/Imgs/newInterface/btn_day_night_normal.png b/Imgs/newInterface/btn_day_night_normal.png new file mode 100644 index 00000000..5c301072 Binary files /dev/null and b/Imgs/newInterface/btn_day_night_normal.png differ diff --git a/Imgs/newInterface/btn_graff_normal.png b/Imgs/newInterface/btn_graff_normal.png new file mode 100644 index 00000000..3bbc76f6 Binary files /dev/null and b/Imgs/newInterface/btn_graff_normal.png differ diff --git a/Imgs/newInterface/btn_home_back.png b/Imgs/newInterface/btn_home_back.png new file mode 100644 index 00000000..b0e11438 Binary files /dev/null and b/Imgs/newInterface/btn_home_back.png differ diff --git a/Imgs/newInterface/btn_left_active.png b/Imgs/newInterface/btn_left_active.png new file mode 100644 index 00000000..4e563861 Binary files /dev/null and b/Imgs/newInterface/btn_left_active.png differ diff --git a/Imgs/newInterface/btn_left_blocked.png b/Imgs/newInterface/btn_left_blocked.png new file mode 100644 index 00000000..095cad7e Binary files /dev/null and b/Imgs/newInterface/btn_left_blocked.png differ diff --git a/Imgs/newInterface/btn_left_normal.png b/Imgs/newInterface/btn_left_normal.png new file mode 100644 index 00000000..e0f3b296 Binary files /dev/null and b/Imgs/newInterface/btn_left_normal.png differ diff --git a/Imgs/newInterface/btn_logo_active.png b/Imgs/newInterface/btn_logo_active.png new file mode 100644 index 00000000..3ea931d8 Binary files /dev/null and b/Imgs/newInterface/btn_logo_active.png differ diff --git a/Imgs/newInterface/btn_logo_normal.png b/Imgs/newInterface/btn_logo_normal.png new file mode 100644 index 00000000..81a596a3 Binary files /dev/null and b/Imgs/newInterface/btn_logo_normal.png differ diff --git a/Imgs/newInterface/decor_bottom.png b/Imgs/newInterface/decor_bottom.png new file mode 100644 index 00000000..59a8fa44 Binary files /dev/null and b/Imgs/newInterface/decor_bottom.png differ diff --git a/Imgs/newInterface/decor_top.png b/Imgs/newInterface/decor_top.png new file mode 100644 index 00000000..8ce8f962 Binary files /dev/null and b/Imgs/newInterface/decor_top.png differ diff --git a/Imgs/newInterface/decor_topline.png b/Imgs/newInterface/decor_topline.png new file mode 100644 index 00000000..8ce1b7a1 Binary files /dev/null and b/Imgs/newInterface/decor_topline.png differ diff --git a/Imgs/newInterface/elements/bkg_anketa.png b/Imgs/newInterface/elements/bkg_anketa.png new file mode 100644 index 00000000..7f8ee293 Binary files /dev/null and b/Imgs/newInterface/elements/bkg_anketa.png differ diff --git a/Imgs/newInterface/elements/bkg_anketa_zagolovok_v1.png b/Imgs/newInterface/elements/bkg_anketa_zagolovok_v1.png new file mode 100644 index 00000000..aed5081a Binary files /dev/null and b/Imgs/newInterface/elements/bkg_anketa_zagolovok_v1.png differ diff --git a/Imgs/newInterface/elements/bkg_anketa_zagolovok_v2.png b/Imgs/newInterface/elements/bkg_anketa_zagolovok_v2.png new file mode 100644 index 00000000..385ad1c0 Binary files /dev/null and b/Imgs/newInterface/elements/bkg_anketa_zagolovok_v2.png differ diff --git a/Imgs/newInterface/elements/bkg_hint.png b/Imgs/newInterface/elements/bkg_hint.png new file mode 100644 index 00000000..4725a571 Binary files /dev/null and b/Imgs/newInterface/elements/bkg_hint.png differ diff --git a/Imgs/newInterface/elements/bkg_map_infoflat.png b/Imgs/newInterface/elements/bkg_map_infoflat.png new file mode 100644 index 00000000..9e494483 Binary files /dev/null and b/Imgs/newInterface/elements/bkg_map_infoflat.png differ diff --git a/Imgs/newInterface/elements/bkg_vybor.png b/Imgs/newInterface/elements/bkg_vybor.png new file mode 100644 index 00000000..52830b77 Binary files /dev/null and b/Imgs/newInterface/elements/bkg_vybor.png differ diff --git a/Imgs/newInterface/elements/bkg_zagolovok.png b/Imgs/newInterface/elements/bkg_zagolovok.png new file mode 100644 index 00000000..b5ea07ab Binary files /dev/null and b/Imgs/newInterface/elements/bkg_zagolovok.png differ diff --git a/Imgs/newInterface/elements/bkgs.png b/Imgs/newInterface/elements/bkgs.png new file mode 100644 index 00000000..cd368947 Binary files /dev/null and b/Imgs/newInterface/elements/bkgs.png differ diff --git a/Imgs/newInterface/elements/btn_check_active.png b/Imgs/newInterface/elements/btn_check_active.png new file mode 100644 index 00000000..80f29396 Binary files /dev/null and b/Imgs/newInterface/elements/btn_check_active.png differ diff --git a/Imgs/newInterface/elements/btn_check_normal.png b/Imgs/newInterface/elements/btn_check_normal.png new file mode 100644 index 00000000..7bab092e Binary files /dev/null and b/Imgs/newInterface/elements/btn_check_normal.png differ diff --git a/Imgs/newInterface/elements/btn_close_filter.png b/Imgs/newInterface/elements/btn_close_filter.png new file mode 100644 index 00000000..ba424804 Binary files /dev/null and b/Imgs/newInterface/elements/btn_close_filter.png differ diff --git a/Imgs/newInterface/elements/btn_close_window.png b/Imgs/newInterface/elements/btn_close_window.png new file mode 100644 index 00000000..6c41470c Binary files /dev/null and b/Imgs/newInterface/elements/btn_close_window.png differ diff --git a/Imgs/newInterface/elements/btn_like_broni.png b/Imgs/newInterface/elements/btn_like_broni.png new file mode 100644 index 00000000..8af76cca Binary files /dev/null and b/Imgs/newInterface/elements/btn_like_broni.png differ diff --git a/Imgs/newInterface/elements/btn_n_home_active.png b/Imgs/newInterface/elements/btn_n_home_active.png new file mode 100644 index 00000000..e973bd07 Binary files /dev/null and b/Imgs/newInterface/elements/btn_n_home_active.png differ diff --git a/Imgs/newInterface/elements/btn_n_home_normal.png b/Imgs/newInterface/elements/btn_n_home_normal.png new file mode 100644 index 00000000..9eae3587 Binary files /dev/null and b/Imgs/newInterface/elements/btn_n_home_normal.png differ diff --git a/Imgs/newInterface/elements/btn_perehod.png b/Imgs/newInterface/elements/btn_perehod.png new file mode 100644 index 00000000..bee94941 Binary files /dev/null and b/Imgs/newInterface/elements/btn_perehod.png differ diff --git a/Imgs/newInterface/elements/dvizhok.png b/Imgs/newInterface/elements/dvizhok.png new file mode 100644 index 00000000..a6f47814 Binary files /dev/null and b/Imgs/newInterface/elements/dvizhok.png differ diff --git a/Imgs/newInterface/elements/graff_info.png b/Imgs/newInterface/elements/graff_info.png new file mode 100644 index 00000000..6d842394 Binary files /dev/null and b/Imgs/newInterface/elements/graff_info.png differ diff --git a/Imgs/newInterface/elements/hint_active_v1.png b/Imgs/newInterface/elements/hint_active_v1.png new file mode 100644 index 00000000..286cd0ea Binary files /dev/null and b/Imgs/newInterface/elements/hint_active_v1.png differ diff --git a/Imgs/newInterface/elements/hint_active_v2.png b/Imgs/newInterface/elements/hint_active_v2.png new file mode 100644 index 00000000..74b72ddc Binary files /dev/null and b/Imgs/newInterface/elements/hint_active_v2.png differ diff --git a/Imgs/newInterface/elements/hint_normal_v1.png b/Imgs/newInterface/elements/hint_normal_v1.png new file mode 100644 index 00000000..2ad29dec Binary files /dev/null and b/Imgs/newInterface/elements/hint_normal_v1.png differ diff --git a/Imgs/newInterface/elements/hint_normal_v2.png b/Imgs/newInterface/elements/hint_normal_v2.png new file mode 100644 index 00000000..f90b52ff Binary files /dev/null and b/Imgs/newInterface/elements/hint_normal_v2.png differ diff --git a/Imgs/newInterface/elements/icon_zapros.png b/Imgs/newInterface/elements/icon_zapros.png new file mode 100644 index 00000000..3e45a191 Binary files /dev/null and b/Imgs/newInterface/elements/icon_zapros.png differ diff --git a/Imgs/newInterface/elements/line_dvizhok.png b/Imgs/newInterface/elements/line_dvizhok.png new file mode 100644 index 00000000..bbcd9363 Binary files /dev/null and b/Imgs/newInterface/elements/line_dvizhok.png differ diff --git a/Imgs/newInterface/elements/radio_btn_active.png b/Imgs/newInterface/elements/radio_btn_active.png new file mode 100644 index 00000000..0c14d86d Binary files /dev/null and b/Imgs/newInterface/elements/radio_btn_active.png differ diff --git a/Imgs/newInterface/elements/radio_btn_normal.png b/Imgs/newInterface/elements/radio_btn_normal.png new file mode 100644 index 00000000..01698a73 Binary files /dev/null and b/Imgs/newInterface/elements/radio_btn_normal.png differ diff --git a/Imgs/newInterface/elements/radio_btn_text_active.png b/Imgs/newInterface/elements/radio_btn_text_active.png new file mode 100644 index 00000000..f4977f12 Binary files /dev/null and b/Imgs/newInterface/elements/radio_btn_text_active.png differ diff --git a/Imgs/newInterface/hint_home_day.png b/Imgs/newInterface/hint_home_day.png new file mode 100644 index 00000000..73e8884f Binary files /dev/null and b/Imgs/newInterface/hint_home_day.png differ diff --git a/Imgs/newInterface/hint_home_night.png b/Imgs/newInterface/hint_home_night.png new file mode 100644 index 00000000..5dbe30cc Binary files /dev/null and b/Imgs/newInterface/hint_home_night.png differ diff --git a/Imgs/newInterface/hint_street.png b/Imgs/newInterface/hint_street.png new file mode 100644 index 00000000..44078d0a Binary files /dev/null and b/Imgs/newInterface/hint_street.png differ diff --git a/Imgs/newInterface/hint_window.png b/Imgs/newInterface/hint_window.png new file mode 100644 index 00000000..5c6e657a Binary files /dev/null and b/Imgs/newInterface/hint_window.png differ diff --git a/Imgs/newInterface/icon_autoprez_normal.png b/Imgs/newInterface/icon_autoprez_normal.png new file mode 100644 index 00000000..813aafa4 Binary files /dev/null and b/Imgs/newInterface/icon_autoprez_normal.png differ diff --git a/Imgs/newInterface/icon_back.png b/Imgs/newInterface/icon_back.png new file mode 100644 index 00000000..208d2a9f Binary files /dev/null and b/Imgs/newInterface/icon_back.png differ diff --git a/Imgs/newInterface/icon_day.png b/Imgs/newInterface/icon_day.png new file mode 100644 index 00000000..672d756b Binary files /dev/null and b/Imgs/newInterface/icon_day.png differ diff --git a/Imgs/newInterface/icon_hint_street.png b/Imgs/newInterface/icon_hint_street.png new file mode 100644 index 00000000..d21f52da Binary files /dev/null and b/Imgs/newInterface/icon_hint_street.png differ diff --git a/Imgs/newInterface/icon_hint_window.png b/Imgs/newInterface/icon_hint_window.png new file mode 100644 index 00000000..773b17be Binary files /dev/null and b/Imgs/newInterface/icon_hint_window.png differ diff --git a/Imgs/newInterface/icon_home.png b/Imgs/newInterface/icon_home.png new file mode 100644 index 00000000..895ee17b Binary files /dev/null and b/Imgs/newInterface/icon_home.png differ diff --git a/Imgs/newInterface/icon_info_normal.png b/Imgs/newInterface/icon_info_normal.png new file mode 100644 index 00000000..a183e2d4 Binary files /dev/null and b/Imgs/newInterface/icon_info_normal.png differ diff --git a/Imgs/newInterface/icon_infra_normal.png b/Imgs/newInterface/icon_infra_normal.png new file mode 100644 index 00000000..adbf48fd Binary files /dev/null and b/Imgs/newInterface/icon_infra_normal.png differ diff --git a/Imgs/newInterface/icon_like_normal.png b/Imgs/newInterface/icon_like_normal.png new file mode 100644 index 00000000..87467ab7 Binary files /dev/null and b/Imgs/newInterface/icon_like_normal.png differ diff --git a/Imgs/newInterface/icon_night.png b/Imgs/newInterface/icon_night.png new file mode 100644 index 00000000..74d8c6ae Binary files /dev/null and b/Imgs/newInterface/icon_night.png differ diff --git a/Imgs/newInterface/icon_photo_normal.png b/Imgs/newInterface/icon_photo_normal.png new file mode 100644 index 00000000..c3c44de7 Binary files /dev/null and b/Imgs/newInterface/icon_photo_normal.png differ diff --git a/Imgs/newInterface/icon_video_normal.png b/Imgs/newInterface/icon_video_normal.png new file mode 100644 index 00000000..2e22c4f5 Binary files /dev/null and b/Imgs/newInterface/icon_video_normal.png differ diff --git a/Imgs/newInterface/icon_virtrur_normal.png b/Imgs/newInterface/icon_virtrur_normal.png new file mode 100644 index 00000000..71f8417f Binary files /dev/null and b/Imgs/newInterface/icon_virtrur_normal.png differ diff --git a/Imgs/newInterface/icon_vybor_normal.png b/Imgs/newInterface/icon_vybor_normal.png new file mode 100644 index 00000000..5e3c9715 Binary files /dev/null and b/Imgs/newInterface/icon_vybor_normal.png differ diff --git a/Imgs/newInterface/icons/balcony.png b/Imgs/newInterface/icons/balcony.png new file mode 100644 index 00000000..c08e623e Binary files /dev/null and b/Imgs/newInterface/icons/balcony.png differ diff --git a/Imgs/newInterface/icons/bathtub.png b/Imgs/newInterface/icons/bathtub.png new file mode 100644 index 00000000..67c90a59 Binary files /dev/null and b/Imgs/newInterface/icons/bathtub.png differ diff --git a/Imgs/newInterface/icons/bedroom.png b/Imgs/newInterface/icons/bedroom.png new file mode 100644 index 00000000..47458f8c Binary files /dev/null and b/Imgs/newInterface/icons/bedroom.png differ diff --git a/Imgs/newInterface/icons/icon_alcomarket.png b/Imgs/newInterface/icons/icon_alcomarket.png new file mode 100644 index 00000000..4917bbc2 Binary files /dev/null and b/Imgs/newInterface/icons/icon_alcomarket.png differ diff --git a/Imgs/newInterface/icons/icon_apteka.png b/Imgs/newInterface/icons/icon_apteka.png new file mode 100644 index 00000000..acaa4539 Binary files /dev/null and b/Imgs/newInterface/icons/icon_apteka.png differ diff --git a/Imgs/newInterface/icons/icon_barber.png b/Imgs/newInterface/icons/icon_barber.png new file mode 100644 index 00000000..c766a3f2 Binary files /dev/null and b/Imgs/newInterface/icons/icon_barber.png differ diff --git a/Imgs/newInterface/icons/icon_broni.png b/Imgs/newInterface/icons/icon_broni.png new file mode 100644 index 00000000..5d07570b Binary files /dev/null and b/Imgs/newInterface/icons/icon_broni.png differ diff --git a/Imgs/newInterface/icons/icon_bytovye.png b/Imgs/newInterface/icons/icon_bytovye.png new file mode 100644 index 00000000..e3289fe5 Binary files /dev/null and b/Imgs/newInterface/icons/icon_bytovye.png differ diff --git a/Imgs/newInterface/icons/icon_cosmetic.png b/Imgs/newInterface/icons/icon_cosmetic.png new file mode 100644 index 00000000..27b73c86 Binary files /dev/null and b/Imgs/newInterface/icons/icon_cosmetic.png differ diff --git a/Imgs/newInterface/icons/icon_detskaya_ploshadka.png b/Imgs/newInterface/icons/icon_detskaya_ploshadka.png new file mode 100644 index 00000000..7e066a7f Binary files /dev/null and b/Imgs/newInterface/icons/icon_detskaya_ploshadka.png differ diff --git a/Imgs/newInterface/icons/icon_family_vybor.png b/Imgs/newInterface/icons/icon_family_vybor.png new file mode 100644 index 00000000..9d8cbc13 Binary files /dev/null and b/Imgs/newInterface/icons/icon_family_vybor.png differ diff --git a/Imgs/newInterface/icons/icon_filter_vybor.png b/Imgs/newInterface/icons/icon_filter_vybor.png new file mode 100644 index 00000000..df55721c Binary files /dev/null and b/Imgs/newInterface/icons/icon_filter_vybor.png differ diff --git a/Imgs/newInterface/icons/icon_flat.png b/Imgs/newInterface/icons/icon_flat.png new file mode 100644 index 00000000..e3f2263f Binary files /dev/null and b/Imgs/newInterface/icons/icon_flat.png differ diff --git a/Imgs/newInterface/icons/icon_floor_info.png b/Imgs/newInterface/icons/icon_floor_info.png new file mode 100644 index 00000000..70351757 Binary files /dev/null and b/Imgs/newInterface/icons/icon_floor_info.png differ diff --git a/Imgs/newInterface/icons/icon_floor_vybor.png b/Imgs/newInterface/icons/icon_floor_vybor.png new file mode 100644 index 00000000..edfbc308 Binary files /dev/null and b/Imgs/newInterface/icons/icon_floor_vybor.png differ diff --git a/Imgs/newInterface/icons/icon_home_info.png b/Imgs/newInterface/icons/icon_home_info.png new file mode 100644 index 00000000..7e535493 Binary files /dev/null and b/Imgs/newInterface/icons/icon_home_info.png differ diff --git a/Imgs/newInterface/icons/icon_komnat_info.png b/Imgs/newInterface/icons/icon_komnat_info.png new file mode 100644 index 00000000..f49ed93d Binary files /dev/null and b/Imgs/newInterface/icons/icon_komnat_info.png differ diff --git a/Imgs/newInterface/icons/icon_komnat_vybor.png b/Imgs/newInterface/icons/icon_komnat_vybor.png new file mode 100644 index 00000000..a252cc7b Binary files /dev/null and b/Imgs/newInterface/icons/icon_komnat_vybor.png differ diff --git a/Imgs/newInterface/icons/icon_like.png b/Imgs/newInterface/icons/icon_like.png new file mode 100644 index 00000000..6be3175e Binary files /dev/null and b/Imgs/newInterface/icons/icon_like.png differ diff --git a/Imgs/newInterface/icons/icon_n_home_vybor.png b/Imgs/newInterface/icons/icon_n_home_vybor.png new file mode 100644 index 00000000..07ea5406 Binary files /dev/null and b/Imgs/newInterface/icons/icon_n_home_vybor.png differ diff --git a/Imgs/newInterface/icons/icon_parking.png b/Imgs/newInterface/icons/icon_parking.png new file mode 100644 index 00000000..4ea89a58 Binary files /dev/null and b/Imgs/newInterface/icons/icon_parking.png differ diff --git a/Imgs/newInterface/icons/icon_perehod.png b/Imgs/newInterface/icons/icon_perehod.png new file mode 100644 index 00000000..48add747 Binary files /dev/null and b/Imgs/newInterface/icons/icon_perehod.png differ diff --git a/Imgs/newInterface/icons/icon_plan_info.png b/Imgs/newInterface/icons/icon_plan_info.png new file mode 100644 index 00000000..6054e662 Binary files /dev/null and b/Imgs/newInterface/icons/icon_plan_info.png differ diff --git a/Imgs/newInterface/icons/icon_planirovka_vybor.png b/Imgs/newInterface/icons/icon_planirovka_vybor.png new file mode 100644 index 00000000..5e3c9715 Binary files /dev/null and b/Imgs/newInterface/icons/icon_planirovka_vybor.png differ diff --git a/Imgs/newInterface/icons/icon_razvernuti.png b/Imgs/newInterface/icons/icon_razvernuti.png new file mode 100644 index 00000000..f821e52f Binary files /dev/null and b/Imgs/newInterface/icons/icon_razvernuti.png differ diff --git a/Imgs/newInterface/icons/icon_s_info.png b/Imgs/newInterface/icons/icon_s_info.png new file mode 100644 index 00000000..8b60b003 Binary files /dev/null and b/Imgs/newInterface/icons/icon_s_info.png differ diff --git a/Imgs/newInterface/icons/icon_s_vybor.png b/Imgs/newInterface/icons/icon_s_vybor.png new file mode 100644 index 00000000..59afc4aa Binary files /dev/null and b/Imgs/newInterface/icons/icon_s_vybor.png differ diff --git a/Imgs/newInterface/icons/icon_salon_krasoty.png b/Imgs/newInterface/icons/icon_salon_krasoty.png new file mode 100644 index 00000000..67454f8e Binary files /dev/null and b/Imgs/newInterface/icons/icon_salon_krasoty.png differ diff --git a/Imgs/newInterface/icons/icon_sport.png b/Imgs/newInterface/icons/icon_sport.png new file mode 100644 index 00000000..f3aa4098 Binary files /dev/null and b/Imgs/newInterface/icons/icon_sport.png differ diff --git a/Imgs/newInterface/icons/icon_srok_sdachi_info.png b/Imgs/newInterface/icons/icon_srok_sdachi_info.png new file mode 100644 index 00000000..a0e87016 Binary files /dev/null and b/Imgs/newInterface/icons/icon_srok_sdachi_info.png differ diff --git a/Imgs/newInterface/icons/icon_supermarket.png b/Imgs/newInterface/icons/icon_supermarket.png new file mode 100644 index 00000000..22a18766 Binary files /dev/null and b/Imgs/newInterface/icons/icon_supermarket.png differ diff --git a/Imgs/newInterface/icons/icon_zapros.png b/Imgs/newInterface/icons/icon_zapros.png new file mode 100644 index 00000000..3e45a191 Binary files /dev/null and b/Imgs/newInterface/icons/icon_zapros.png differ diff --git a/Imgs/newInterface/icons/icon_zona_otdyha.png b/Imgs/newInterface/icons/icon_zona_otdyha.png new file mode 100644 index 00000000..15b2b868 Binary files /dev/null and b/Imgs/newInterface/icons/icon_zona_otdyha.png differ diff --git a/Imgs/newInterface/icons/icon_zoomag.png b/Imgs/newInterface/icons/icon_zoomag.png new file mode 100644 index 00000000..bd737e3b Binary files /dev/null and b/Imgs/newInterface/icons/icon_zoomag.png differ diff --git a/Imgs/newInterface/icons/kitchen.png b/Imgs/newInterface/icons/kitchen.png new file mode 100644 index 00000000..51d6cfaa Binary files /dev/null and b/Imgs/newInterface/icons/kitchen.png differ diff --git a/Imgs/newInterface/icons/living-room.png b/Imgs/newInterface/icons/living-room.png new file mode 100644 index 00000000..ceff5ff0 Binary files /dev/null and b/Imgs/newInterface/icons/living-room.png differ diff --git a/Imgs/newInterface/icons/wardrobe.png b/Imgs/newInterface/icons/wardrobe.png new file mode 100644 index 00000000..81279639 Binary files /dev/null and b/Imgs/newInterface/icons/wardrobe.png differ diff --git a/Imgs/newInterface/logo_graff.png b/Imgs/newInterface/logo_graff.png new file mode 100644 index 00000000..4429dd8b Binary files /dev/null and b/Imgs/newInterface/logo_graff.png differ diff --git a/Imgs/newInterface/txt_compass.png b/Imgs/newInterface/txt_compass.png new file mode 100644 index 00000000..995b3fb2 Binary files /dev/null and b/Imgs/newInterface/txt_compass.png differ diff --git a/Imgs/shit/Custom Size – 14.png b/Imgs/shit/Custom Size – 14.png new file mode 100644 index 00000000..8058842b Binary files /dev/null and b/Imgs/shit/Custom Size – 14.png differ diff --git a/Imgs/shit/Custom Size – 2.png b/Imgs/shit/Custom Size – 2.png new file mode 100644 index 00000000..f351e26f Binary files /dev/null and b/Imgs/shit/Custom Size – 2.png differ diff --git a/Imgs/shit/Custom Size – 8.jpg b/Imgs/shit/Custom Size – 8.jpg new file mode 100644 index 00000000..b4b0ce9f Binary files /dev/null and b/Imgs/shit/Custom Size – 8.jpg differ diff --git a/Imgs/shit/Custom Size –1.png b/Imgs/shit/Custom Size –1.png new file mode 100644 index 00000000..f00aa749 Binary files /dev/null and b/Imgs/shit/Custom Size –1.png differ diff --git a/Imgs/shit/Custom Size12.jpg b/Imgs/shit/Custom Size12.jpg new file mode 100644 index 00000000..306f1fe2 Binary files /dev/null and b/Imgs/shit/Custom Size12.jpg differ diff --git a/Imgs/shit/Custom Size5.jpg b/Imgs/shit/Custom Size5.jpg new file mode 100644 index 00000000..7973b6c7 Binary files /dev/null and b/Imgs/shit/Custom Size5.jpg differ diff --git a/Imgs/window_info.png b/Imgs/window_info.png new file mode 100644 index 00000000..94f67016 Binary files /dev/null and b/Imgs/window_info.png differ diff --git a/Imgs/window_map_big.png b/Imgs/window_map_big.png new file mode 100644 index 00000000..74195052 Binary files /dev/null and b/Imgs/window_map_big.png differ diff --git a/Onejsky4U.uproject b/Onejsky4U.uproject index aa539779..eb535ed4 100644 --- a/Onejsky4U.uproject +++ b/Onejsky4U.uproject @@ -257,6 +257,10 @@ { "Name": "WebBrowserWidget", "Enabled": true + }, + { + "Name": "HoudiniEngine", + "Enabled": true } ] } \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Content/Icons/icon_houdini_logo_128.png b/Plugins/Runtime/HoudiniEngine/Content/Icons/icon_houdini_logo_128.png new file mode 100644 index 00000000..d2c92dc5 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Icons/icon_houdini_logo_128.png differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Icons/icon_houdini_logo_16.png b/Plugins/Runtime/HoudiniEngine/Content/Icons/icon_houdini_logo_16.png new file mode 100644 index 00000000..316a8181 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Icons/icon_houdini_logo_16.png differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Icons/icon_houdini_logo_40.png b/Plugins/Runtime/HoudiniEngine/Content/Icons/icon_houdini_logo_40.png new file mode 100644 index 00000000..8d6b2b9d Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Icons/icon_houdini_logo_40.png differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/MaterialFunctions/MF_VAT_Fluid.uasset b/Plugins/Runtime/HoudiniEngine/Content/MaterialFunctions/MF_VAT_Fluid.uasset new file mode 100644 index 00000000..aaf670e1 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/MaterialFunctions/MF_VAT_Fluid.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/MaterialFunctions/MF_VAT_Rigid.uasset b/Plugins/Runtime/HoudiniEngine/Content/MaterialFunctions/MF_VAT_Rigid.uasset new file mode 100644 index 00000000..2c8766be Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/MaterialFunctions/MF_VAT_Rigid.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/MaterialFunctions/MF_VAT_Soft.uasset b/Plugins/Runtime/HoudiniEngine/Content/MaterialFunctions/MF_VAT_Soft.uasset new file mode 100644 index 00000000..c436a8fd Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/MaterialFunctions/MF_VAT_Soft.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/MaterialFunctions/MF_VAT_Sprite.uasset b/Plugins/Runtime/HoudiniEngine/Content/MaterialFunctions/MF_VAT_Sprite.uasset new file mode 100644 index 00000000..97388727 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/MaterialFunctions/MF_VAT_Sprite.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Materials/M_VAT_Fluid.uasset b/Plugins/Runtime/HoudiniEngine/Content/Materials/M_VAT_Fluid.uasset new file mode 100644 index 00000000..0efdcce4 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Materials/M_VAT_Fluid.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Materials/M_VAT_Rigid.uasset b/Plugins/Runtime/HoudiniEngine/Content/Materials/M_VAT_Rigid.uasset new file mode 100644 index 00000000..76600731 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Materials/M_VAT_Rigid.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Materials/M_VAT_Soft.uasset b/Plugins/Runtime/HoudiniEngine/Content/Materials/M_VAT_Soft.uasset new file mode 100644 index 00000000..aa075655 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Materials/M_VAT_Soft.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Materials/M_VAT_Sprite.uasset b/Plugins/Runtime/HoudiniEngine/Content/Materials/M_VAT_Sprite.uasset new file mode 100644 index 00000000..0809fc5c Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Materials/M_VAT_Sprite.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Test/InputEcho.uasset b/Plugins/Runtime/HoudiniEngine/Content/Test/InputEcho.uasset new file mode 100644 index 00000000..ebdb4698 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Test/InputEcho.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Test/TestBox.uasset b/Plugins/Runtime/HoudiniEngine/Content/Test/TestBox.uasset new file mode 100644 index 00000000..c45e5dba Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Test/TestBox.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Test/TestParams.uasset b/Plugins/Runtime/HoudiniEngine/Content/Test/TestParams.uasset new file mode 100644 index 00000000..fe54b4e9 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Test/TestParams.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Test/TestPolyReduce.uasset b/Plugins/Runtime/HoudiniEngine/Content/Test/TestPolyReduce.uasset new file mode 100644 index 00000000..337db4da Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Test/TestPolyReduce.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Test/hda/InputEcho.hda b/Plugins/Runtime/HoudiniEngine/Content/Test/hda/InputEcho.hda new file mode 100644 index 00000000..ac376b36 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Test/hda/InputEcho.hda differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Test/hda/TestBox.hda b/Plugins/Runtime/HoudiniEngine/Content/Test/hda/TestBox.hda new file mode 100644 index 00000000..a4f56421 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Test/hda/TestBox.hda differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Test/hda/TestParams.hda b/Plugins/Runtime/HoudiniEngine/Content/Test/hda/TestParams.hda new file mode 100644 index 00000000..93d66064 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Test/hda/TestParams.hda differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Test/hda/TestPolyReduce.hda b/Plugins/Runtime/HoudiniEngine/Content/Test/hda/TestPolyReduce.hda new file mode 100644 index 00000000..a3d573c3 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Test/hda/TestPolyReduce.hda differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_boolean.hda b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_boolean.hda new file mode 100644 index 00000000..0471e145 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_boolean.hda differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_boolean.png b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_boolean.png new file mode 100644 index 00000000..951d5761 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_boolean.png differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_boolean.uasset b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_boolean.uasset new file mode 100644 index 00000000..e1e4140b Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_boolean.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_curve_instancer.hda b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_curve_instancer.hda new file mode 100644 index 00000000..0dc27356 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_curve_instancer.hda differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_curve_instancer.png b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_curve_instancer.png new file mode 100644 index 00000000..976035a1 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_curve_instancer.png differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_curve_instancer.uasset b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_curve_instancer.uasset new file mode 100644 index 00000000..5ad30b79 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_curve_instancer.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_polyreduce.hda b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_polyreduce.hda new file mode 100644 index 00000000..d2f56c59 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_polyreduce.hda differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_polyreduce.png b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_polyreduce.png new file mode 100644 index 00000000..d2d7d9a3 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_polyreduce.png differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_polyreduce.uasset b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_polyreduce.uasset new file mode 100644 index 00000000..aca7446f Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Tools/he_sop_polyreduce.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Tools/rock_generator.hda b/Plugins/Runtime/HoudiniEngine/Content/Tools/rock_generator.hda new file mode 100644 index 00000000..5750a10b Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Tools/rock_generator.hda differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Tools/rock_generator.png b/Plugins/Runtime/HoudiniEngine/Content/Tools/rock_generator.png new file mode 100644 index 00000000..42b5f781 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Tools/rock_generator.png differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Tools/rock_generator.uasset b/Plugins/Runtime/HoudiniEngine/Content/Tools/rock_generator.uasset new file mode 100644 index 00000000..a8591631 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Tools/rock_generator.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/Tools/rock_generator_40.png b/Plugins/Runtime/HoudiniEngine/Content/Tools/rock_generator_40.png new file mode 100644 index 00000000..42c54412 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/Tools/rock_generator_40.png differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/houdini_bgeo_import.uasset b/Plugins/Runtime/HoudiniEngine/Content/houdini_bgeo_import.uasset new file mode 100644 index 00000000..1c126c3d Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/houdini_bgeo_import.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/houdini_default_material.uasset b/Plugins/Runtime/HoudiniEngine/Content/houdini_default_material.uasset new file mode 100644 index 00000000..f5d62286 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/houdini_default_material.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/Content/houdini_logo.uasset b/Plugins/Runtime/HoudiniEngine/Content/houdini_logo.uasset new file mode 100644 index 00000000..742ed4af Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Content/houdini_logo.uasset differ diff --git a/Plugins/Runtime/HoudiniEngine/HoudiniEngine.uplugin b/Plugins/Runtime/HoudiniEngine/HoudiniEngine.uplugin new file mode 100644 index 00000000..b47d798f --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/HoudiniEngine.uplugin @@ -0,0 +1,28 @@ +{ + "FileVersion" : 3, + "FriendlyName" : "Houdini Engine", + "Version" : 18050351, + "VersionName" : "18.5.351", + "CreatedBy" : "Side Effects Software Inc.", + "CreatedByURL" : "http://www.sidefx.com", + "DocsURL" : "http://www.sidefx.com/docs/unreal/", + "Description" : "Houdini Engine for Unreal Engine.", + "Category" : "Rendering", + "EnabledByDefault" : true, + + "Modules" : + [ + { + "Name" : "HoudiniEngineRuntime", + "Type" : "Runtime" + }, + { + "Name" : "HoudiniEngineEditor", + "Type" : "Editor", + "LoadingPhase" : "PostEngineInit" + } + ], + + "CanContainContent" : true, + "Installed": true +} diff --git a/Plugins/Runtime/HoudiniEngine/README.md b/Plugins/Runtime/HoudiniEngine/README.md new file mode 100644 index 00000000..cd4740b4 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/README.md @@ -0,0 +1,32 @@ +# Houdini Engine for Unreal +Houdini Engine for Unreal Engine is a plug-in that allows integration of Houdini technology into Unreal. + +This plug-in brings Houdini's powerful and flexible procedural workflow into Unreal Engine through Houdini Digital Assets. Artists can interactively adjust asset parameters inside the editor and use Unreal geometries as asset inputs. Houdini's procedural engine will then "cook" the asset and the results will be available in the editor without the need for baking. + +You can use the [Houdini Engine for UE4 Forum](http://www.sidefx.com/forum/51/) on the SideFX website, or join the [Think Procedural](https://discord.gg/b8U5Hdy) discord server to ask questions, discuss new features, and share your ideas. + +For more information: + +* [Houdini Engine for Unreal](https://www.sidefx.com/products/houdini-engine/ue4-plug-in/) +* [Documentation](http://www.sidefx.com/docs/unreal/) +* [FAQ](https://www.sidefx.com/faq/houdini-engine-faq/) + +For support and reporting bugs: + +* [Houdini Engine for UE4 Forum](http://www.sidefx.com/forum/51/) +* [Bug Submission](https://www.sidefx.com/bugs/submit/) + +You can see the latest updates and bug fixes made to the plugin in the [Daily Changelog](https://www.sidefx.com/changelog/?journal=16.5&categories=52&body=&version=&build_0=&build_1=&show_versions=on&show_compatibility=on&items_per_page=100). + +# Installing from Source +01. Get the UE4 source code from: https://github.com/EpicGames/UnrealEngine/releases +01. Within the UE4 source, navigate to `Engine/Plugins/Runtime`, and clone this repo into a folder named `HoudiniEngine`. +01. Download and install the correct build of 64-bit Houdini. To get the build number, look at the header of `Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs`, under `Houdini Version`. +01. Generate the UE4 Project Files (by running `GenerateProjectFiles`) and build Unreal, either in x64 `Debug Editor` or x64 `Development Editor`. +01. When starting the Unreal Engine editor, go to Plug-ins menu and make sure to enable the `HoudiniEngine` plug-in (it's in `Rendering` section). Restart UE4 if you had to enable it. +01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. +01. Once you have an HDA in the `Content Browser` you should be able to drag it into Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters in the `Details` pane. Modifying any of the parameters will force the asset to recook and possibly update its geometry. + +*The source code on this github is only automatically updated when a change in the plugin's source code is made. This could lead to the plugin's build.cs files to reference a daily build of Houdini that is not available anymore. If this happens, you simply need to update the version number in the HoudiniEngineRuntime.build.cs and HoudiniEngineEditor.build.cs files to match the latest daily build's version number.* + +*The Houdini Engine for Unreal is not officially supported on Linux, but the plug-in can still be compiled from sources. Since setting up the Houdini environment is required for the session (HARS process) to be created successfully, you need to call "source houdini_setup" in a shell from the installed Houdini directory that matches the plug-in prior to launching the Unreal Editor from that shell.* diff --git a/Plugins/Runtime/HoudiniEngine/Resources/Icon128.png b/Plugins/Runtime/HoudiniEngine/Resources/Icon128.png new file mode 100644 index 00000000..d2c92dc5 Binary files /dev/null and b/Plugins/Runtime/HoudiniEngine/Resources/Icon128.png differ diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs new file mode 100644 index 00000000..e0cd7295 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs @@ -0,0 +1,287 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Produced by: + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + * COMMENTS: + * This file is generated. Do not modify directly. + */ + +/* + + Houdini Version: 18.5.351 + Houdini Engine Version: 3.5.0 + Unreal Version: 4.25.0 + +*/ + +using UnrealBuildTool; +using System; +using System.IO; + +public class HoudiniEngineEditor : ModuleRules +{ + private string GetHFSPath() + { + string HoudiniVersion = "18.5.351"; + bool bIsRelease = true; + string HFSPath = "C:/cygwin/home/prisms/builder-new/Nightly18.5.351CMake/dev/hfs"; + string RegistryPath = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Side Effects Software"; + + if ( !bIsRelease ) + { + // Only use the preset build folder + System.Console.WriteLine("Using stamped HFSPath:" + HFSPath); + return HFSPath; + } + + // Look for the Houdini install folder for this platform + PlatformID buildPlatformId = Environment.OSVersion.Platform; + if (buildPlatformId == PlatformID.Win32NT) + { + // Look for the HEngine install path in the registry + string HEngineRegistry = RegistryPath + string.Format(@"\Houdini Engine {0}", HoudiniVersion); + string HPath = Microsoft.Win32.Registry.GetValue(HEngineRegistry, "InstallPath", null) as string; + if ( HPath != null ) + { + if ( Directory.Exists( HPath ) ) + return HPath; + } + + // If we couldn't find the Houdini Engine registry path, try the default one + string DefaultHPath = "C:/Program Files/Side Effects Software/Houdini Engine " + HoudiniVersion; + if ( DefaultHPath != HPath ) + { + if ( Directory.Exists( DefaultHPath ) ) + return DefaultHPath; + } + + // Look for the Houdini registry install path for the version the plug-in was compiled for + string HoudiniRegistry = RegistryPath + string.Format(@"\Houdini {0}", HoudiniVersion); + HPath = Microsoft.Win32.Registry.GetValue(HoudiniRegistry, "InstallPath", null) as string; + if ( HPath != null ) + { + if ( Directory.Exists( HPath ) ) + return HPath; + } + + // If we couldn't find the Houdini registry path, try the default one + DefaultHPath = "C:/Program Files/Side Effects Software/Houdini " + HoudiniVersion; + if ( DefaultHPath != HPath ) + { + if ( Directory.Exists( DefaultHPath ) ) + return DefaultHPath; + } + + // See if the preset build HFS exists + if ( Directory.Exists( HFSPath ) ) + return HFSPath; + + // We couldn't find the exact version the plug-in was built for, we can still try with the active version in the registry + string ActiveHEngine = Microsoft.Win32.Registry.GetValue( RegistryPath, "ActiveEngineVersion", null ) as string; + if ( ActiveHEngine != null ) + { + // See if the latest active HEngine version has the proper major/minor version + if ( ActiveHEngine.Substring( 0, 4 ) == HoudiniVersion.Substring( 0, 4 ) ) + { + HEngineRegistry = RegistryPath + string.Format(@"\Houdini Engine {0}", ActiveHEngine); + HPath = Microsoft.Win32.Registry.GetValue( HEngineRegistry, "InstallPath", null ) as string; + if ( HPath != null ) + { + if ( Directory.Exists( HPath ) ) + return HPath; + } + } + } + + // Active HEngine version didn't match, so try with the active Houdini version + string ActiveHoudini = Microsoft.Win32.Registry.GetValue( RegistryPath, "ActiveVersion", null ) as string; + if ( ActiveHoudini != null ) + { + // See if the latest active Houdini version has the proper major/minor version + if ( ActiveHoudini.Substring( 0, 4 ) == HoudiniVersion.Substring( 0, 4 ) ) + { + HoudiniRegistry = RegistryPath + string.Format(@"\Houdini {0}", ActiveHoudini); + HPath = Microsoft.Win32.Registry.GetValue( HoudiniRegistry, "InstallPath", null ) as string; + if ( HPath != null ) + { + if ( Directory.Exists( HPath ) ) + return HPath; + } + } + } + } + else if ( buildPlatformId == PlatformID.MacOSX || + (buildPlatformId == PlatformID.Unix && File.Exists("/System/Library/CoreServices/SystemVersion.plist"))) + { + // Check for Houdini installation. + string HPath = "/Applications/Houdini/Houdini" + HoudiniVersion + "/Frameworks/Houdini.framework/Versions/Current/Resources"; + if ( Directory.Exists( HPath ) ) + return HPath; + + HPath = "/Users/Shared/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Resources"; + if (Directory.Exists(HPath)) + return HPath; + + if ( Directory.Exists( HFSPath ) ) + return HFSPath; + } + else if ( buildPlatformId == PlatformID.Unix ) + { + HFSPath = System.Environment.GetEnvironmentVariable( "HFS" ); + if ( Directory.Exists( HFSPath ) ) + { + System.Console.WriteLine("Unix using $HFS: " + HFSPath); + return HFSPath; + } + } + else + { + System.Console.WriteLine( string.Format( "Building on an unknown environment!" ) ); + } + + string Err = string.Format("Houdini Engine : Please install Houdini or Houdini Engine {0}", HoudiniVersion); + System.Console.WriteLine(Err); + + return ""; + } + + public HoudiniEngineEditor( ReadOnlyTargetRules Target ) : base( Target ) + { + bPrecompile = true; + PCHUsage = PCHUsageMode.NoSharedPCHs; + PrivatePCHHeaderFile = "Private/HoudiniEngineEditorPrivatePCH.h"; + + // Check if we are compiling on unsupported platforms. + if ( Target.Platform != UnrealTargetPlatform.Win64 && + Target.Platform != UnrealTargetPlatform.Mac && + Target.Platform != UnrealTargetPlatform.Linux ) + { + string Err = string.Format( "Houdini Engine Editor: Compiling for unsupported platform." ); + System.Console.WriteLine( Err ); + throw new BuildException( Err ); + } + + // Find HFS + string HFSPath = GetHFSPath(); + HFSPath = HFSPath.Replace("\\", "/"); + if( HFSPath != "" ) + { + PlatformID buildPlatformId = Environment.OSVersion.Platform; + if ( buildPlatformId == PlatformID.Win32NT ) + { + PublicDefinitions.Add("HOUDINI_ENGINE_HFS_PATH_DEFINE=" + HFSPath); + } + } + + // Find the HAPI include directory + string HAPIIncludePath = HFSPath + "/toolkit/include/HAPI"; + if (!Directory.Exists(HAPIIncludePath)) + { + // Try the custom include path as well in case the toolkit path doesn't exist yet. + HAPIIncludePath = HFSPath + "/custom/houdini/include/HAPI"; + + if (!Directory.Exists(HAPIIncludePath)) + { + System.Console.WriteLine(string.Format("Couldnt find the HAPI include folder!")); + HAPIIncludePath = ""; + } + } + + if (HAPIIncludePath != "") + PublicIncludePaths.Add(HAPIIncludePath); + + // Get the plugin path + string PluginPath = Path.Combine( ModuleDirectory, "../../" ); + PluginPath = Utils.MakePathRelativeTo(PluginPath, Target.RelativeEnginePath); + + PublicIncludePaths.AddRange( + new string[] { + Path.Combine(ModuleDirectory, "Public") + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + "HoudiniEngineEditor/Private", + "HoudiniEngineRuntime/Private" + } + ); + + PrivateIncludePathModuleNames.AddRange( + new string[] { + "PlacementMode" + } + ); + + // Add common dependencies. + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "HoudiniEngineRuntime", + "Slate", + "SlateCore", + "Landscape" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "AppFramework", + "AssetTools", + "ContentBrowser", + "DesktopWidgets", + "EditorStyle", + "EditorWidgets", + "Engine", + "InputCore", + "LevelEditor", + "MainFrame", + "Projects", + "PropertyEditor", + "RHI", + "RawMesh", + "RenderCore", + "TargetPlatform", + "UnrealEd", + "ApplicationCore", + "CurveEditor", + "Json", + "SceneOutliner" + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + "PlacementMode", + } + ); + } +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp new file mode 100644 index 00000000..9aa5fa7e --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Produced by: + * Mykola Konyk + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + */ + +#include "HoudiniAssetActorFactory.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAsset.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetActorFactory::UHoudiniAssetActorFactory( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{ + DisplayName = LOCTEXT( "HoudiniAssetDisplayName", "Houdini Engine Asset" ); + NewActorClass = AHoudiniAssetActor::StaticClass(); +} + +bool +UHoudiniAssetActorFactory::CanCreateActorFrom( const FAssetData & AssetData, FText & OutErrorMsg ) +{ + if ( !AssetData.IsValid() || !AssetData.GetClass()->IsChildOf(UHoudiniAsset::StaticClass() ) ) + { + OutErrorMsg = NSLOCTEXT( "CanCreateActor", "NoHoudiniAsset", "A valid Houdini Engine asset must be specified." ); + return false; + } + + return true; +} + +UObject * +UHoudiniAssetActorFactory::GetAssetFromActorInstance( AActor * Instance ) +{ + check( Instance->IsA( NewActorClass ) ); + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >( Instance ); + + check( HoudiniAssetActor->HoudiniAssetComponent ); + return HoudiniAssetActor->GetHoudiniAssetComponent()->HoudiniAsset; +} + +void +UHoudiniAssetActorFactory::PostSpawnActor( UObject * Asset, AActor * NewActor ) +{ + HOUDINI_LOG_MESSAGE( TEXT( "PostSpawnActor %s, supplied Asset = 0x%0.8p" ), *NewActor->GetName(), Asset ); + + UHoudiniAsset * HoudiniAsset = CastChecked(Asset); + if ( HoudiniAsset ) + { + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >( NewActor ); + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + check( HoudiniAssetComponent ); + + // Mark this component as native. + HoudiniAssetComponent->SetNative( true ); + + HoudiniAssetComponent->UnregisterComponent(); + HoudiniAssetComponent->SetHoudiniAsset( HoudiniAsset ); + HoudiniAssetComponent->RegisterComponent(); + } +} + +void +UHoudiniAssetActorFactory::PostCreateBlueprint( UObject * Asset, AActor * CDO ) +{ + HOUDINI_LOG_MESSAGE( TEXT( "PostCreateBlueprint, supplied Asset = 0x%0.8p" ), Asset ); + + UHoudiniAsset * HoudiniAsset = CastChecked(Asset); + if ( HoudiniAsset ) + { + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >( CDO ); + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + HoudiniAssetComponent->HoudiniAsset = HoudiniAsset; + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h new file mode 100644 index 00000000..346e2253 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Produced by: + * Mykola Konyk + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + */ + +#pragma once +#include "ActorFactories/ActorFactory.h" +#include "HoudiniAssetActorFactory.generated.h" + + +class FText; +class AActor; +class UObject; + +struct FAssetData; + + +UCLASS( config = Editor ) +class UHoudiniAssetActorFactory : public UActorFactory +{ + GENERATED_UCLASS_BODY() + + /** UActorFactory methods. **/ + public: + + /** Return true if Actor can be created from a given asset. **/ + virtual bool CanCreateActorFrom( const FAssetData & AssetData, FText & OutErrorMsg ) override; + + /** Given an instance of an actor pertaining to this factory, find the asset that should be used to create a new actor. **/ + virtual UObject * GetAssetFromActorInstance( AActor * Instance ) override; + + /** Subclasses may implement this to modify the actor after it has been spawned. **/ + virtual void PostSpawnActor( UObject * Asset, AActor * NewActor ) override; + + /** Override this in derived factory classes if needed. This is called after a blueprint is created by this factory to **/ + /** update the blueprint's CDO properties with state from the asset for this factory. **/ + virtual void PostCreateBlueprint( UObject * Asset, AActor * CDO ) override; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp new file mode 100644 index 00000000..9d3be9c6 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Produced by: + * Mykola Konyk + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + */ + +#include "HoudiniAssetBroker.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAsset.h" + +FHoudiniAssetBroker::~FHoudiniAssetBroker() +{ + +} + +UClass * +FHoudiniAssetBroker::GetSupportedAssetClass() +{ + return UHoudiniAsset::StaticClass(); +} + +bool +FHoudiniAssetBroker::AssignAssetToComponent( UActorComponent * InComponent, UObject * InAsset ) +{ + if ( UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >( InComponent ) ) + { + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >( InAsset ); + + if ( HoudiniAsset || !InAsset ) + { + HoudiniAssetComponent->SetHoudiniAsset( HoudiniAsset ); + return true; + } + } + + return false; +} + +UObject * +FHoudiniAssetBroker::GetAssetFromComponent( UActorComponent * InComponent ) +{ + if ( UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >( InComponent ) ) + { + return HoudiniAssetComponent->GetHoudiniAsset(); + } + + return nullptr; +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h new file mode 100644 index 00000000..75356b92 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Produced by: + * Mykola Konyk + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + */ + +#pragma once + +#include "ComponentAssetBroker.h" + +class UObject; +class UActorComponent; + + +class FHoudiniAssetBroker : public IComponentAssetBroker +{ + public: + + virtual ~FHoudiniAssetBroker(); + + /** IComponentAssetBroker methods. **/ + public: + + /** Reports the asset class this broker knows how to handle. **/ + UClass * GetSupportedAssetClass() override; + + /** Assign the assigned asset to the supplied component. **/ + bool AssignAssetToComponent( UActorComponent * InComponent, UObject * InAsset ) override; + + /** Get the currently assigned asset from the component. **/ + UObject * GetAssetFromComponent( UActorComponent * InComponent ) override; +}; + diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp new file mode 100644 index 00000000..0466b335 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp @@ -0,0 +1,2331 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include "HoudiniAssetComponentDetails.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetLogWidget.h" +#include "HoudiniEngineString.h" +#include "HoudiniParameterDetails.h" +#include "HoudiniAssetInput.h" +#include "HoudiniAssetInstanceInput.h" + +#include "Components/InstancedStaticMeshComponent.h" +#include "ContentBrowserModule.h" +#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" +#include "DetailWidgetRow.h" +#include "Editor.h" +#include "IContentBrowserSingleton.h" +#include "Landscape.h" +#include "SAssetDropTarget.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Images/SImage.h" +#include "Framework/Application/SlateApplication.h" +#include "Materials/Material.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "Internationalization/Internationalization.h" +#include "DetailLayoutBuilder.h" +#include "IDetailGroup.h" + + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +uint32 +GetTypeHash( TPair< UStaticMesh *, int32 > Pair ) +{ + return PointerHash( Pair.Key, Pair.Value ); +} + +uint32 +GetTypeHash( TPair< ALandscape *, int32 > Pair ) +{ + return PointerHash(Pair.Key, Pair.Value); +} + +uint32 +GetTypeHash(TPair< ALandscapeProxy *, int32 > Pair) +{ + return PointerHash(Pair.Key, Pair.Value); +} + +TSharedRef< IDetailCustomization > +FHoudiniAssetComponentDetails::MakeInstance() +{ + return MakeShareable( new FHoudiniAssetComponentDetails ); +} + +FHoudiniAssetComponentDetails::FHoudiniAssetComponentDetails() +{ + +} + + +FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() +{ + +} + +void +FHoudiniAssetComponentDetails::CustomizeDetails( IDetailLayoutBuilder & DetailBuilder ) +{ + // Get all components which are being customized. + TArray< TWeakObjectPtr< UObject > > ObjectsCustomized; + DetailBuilder.GetObjectsBeingCustomized( ObjectsCustomized ); + + // See if we need to enable baking option. + for ( int32 i = 0; i < ObjectsCustomized.Num(); ++i ) + { + if ( ObjectsCustomized[ i ].IsValid() ) + { + UObject * Object = ObjectsCustomized[ i ].Get(); + if( Object ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >( Object ); + HoudiniAssetComponents.Add( HoudiniAssetComponent ); + } + } + } + + // Create Houdini parameters. + { + IDetailCategoryBuilder & DetailCategoryBuilder = + DetailBuilder.EditCategory( "HoudiniParameters", FText::GetEmpty(), ECategoryPriority::Important ); + + // If we are running Houdini Engine Indie license, we need to display special label. + if ( FHoudiniEngineUtils::IsLicenseHoudiniEngineIndie() ) + { + FText ParameterLabelText = + FText::FromString( TEXT( "Houdini Engine Indie - For Limited Commercial Use Only" ) ); + + FSlateFontInfo LargeDetailsFont = IDetailLayoutBuilder::GetDetailFontBold(); + LargeDetailsFont.Size += 2; + + FSlateColor LabelColor = FLinearColor( 1.0f, 1.0f, 0.0f, 1.0f ); + + DetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ) + [ + SNew( STextBlock ) + .Text( ParameterLabelText ) + .ToolTipText( ParameterLabelText ) + .Font( LargeDetailsFont ) + .Justification( ETextJustify::Center ) + .ColorAndOpacity( LabelColor ) + ]; + + DetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ) + [ + SNew( SVerticalBox ) + +SVerticalBox::Slot() + .Padding( 0, 0, 5, 0 ) + [ + SNew( SSeparator ) + .Thickness( 2.0f ) + ] + ]; + } + + for ( TArray< UHoudiniAssetComponent * >::TIterator + IterComponents( HoudiniAssetComponents ); IterComponents; ++IterComponents ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + continue; + + for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator + IterParams( HoudiniAssetComponent->Parameters ); IterParams; ++IterParams ) + { + // We only want to create root parameters here, they will recursively create child parameters. + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill() || HoudiniAssetParameter->IsChildParameter()) + continue; + + // ensure the parameter is properly owned by a HAC + const UHoudiniAssetComponent* Owner = HoudiniAssetParameter->GetHoudiniAssetComponent(); + if (!Owner || Owner->IsPendingKill()) + continue; + + FHoudiniParameterDetails::CreateWidget( DetailCategoryBuilder, HoudiniAssetParameter ); + } + } + } + + // Create Houdini Inputs. + { + IDetailCategoryBuilder & DetailCategoryBuilder = DetailBuilder.EditCategory( + "HoudiniInputs", FText::GetEmpty(), ECategoryPriority::Important ); + for ( TArray< UHoudiniAssetComponent * >::TIterator + IterComponents( HoudiniAssetComponents ); IterComponents; ++IterComponents ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + continue; + + for ( TArray< UHoudiniAssetInput * >::TIterator + IterInputs( HoudiniAssetComponent->Inputs ); IterInputs; ++IterInputs ) + { + UHoudiniAssetInput * HoudiniAssetInput = *IterInputs; + if( HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill() ) + { + FHoudiniParameterDetails::CreateWidget( DetailCategoryBuilder, HoudiniAssetInput ); + } + } + } + } + + // Create Houdini Instanced Inputs category. + { + IDetailCategoryBuilder & DetailCategoryBuilder = DetailBuilder.EditCategory( + "HoudiniInstancedInputs", FText::GetEmpty(), ECategoryPriority::Important ); + for ( TArray< UHoudiniAssetComponent * >::TIterator + IterComponents( HoudiniAssetComponents ); IterComponents; ++IterComponents ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + continue; + + for ( auto& InstanceInput : HoudiniAssetComponent->InstanceInputs ) + { + if ( InstanceInput && !InstanceInput->IsPendingKill() ) + { + FHoudiniParameterDetails::CreateWidget( DetailCategoryBuilder, InstanceInput ); + } + } + } + } + + // Create Houdini Asset category. + { + IDetailCategoryBuilder & DetailCategoryBuilder = + DetailBuilder.EditCategory( "HoudiniAsset", FText::GetEmpty(), ECategoryPriority::Important ); + CreateHoudiniAssetWidget( DetailCategoryBuilder ); + } + + // Create category for generated static meshes and their materials. + { + IDetailCategoryBuilder & DetailCategoryBuilder = + DetailBuilder.EditCategory( "HoudiniGeneratedMeshes", FText::GetEmpty(), ECategoryPriority::Important ); + CreateStaticMeshAndMaterialWidgets( DetailCategoryBuilder ); + } + + // Create Houdini Generated Static mesh settings category. + DetailBuilder.EditCategory( "HoudiniGeneratedStaticMeshSettings", FText::GetEmpty(), ECategoryPriority::Important ); +} + +void +FHoudiniAssetComponentDetails::CreateStaticMeshAndMaterialWidgets( IDetailCategoryBuilder & DetailCategoryBuilder ) +{ + StaticMeshThumbnailBorders.Empty(); + LandscapeThumbnailBorders.Empty(); + MaterialInterfaceComboButtons.Empty(); + MaterialInterfaceThumbnailBorders.Empty(); + + // Get thumbnail pool for this builder. + IDetailLayoutBuilder & DetailLayoutBuilder = DetailCategoryBuilder.GetParentLayout(); + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + int32 NumberOfGeneratedMeshes = 0; + for ( TArray< UHoudiniAssetComponent * >::TIterator + IterComponents( HoudiniAssetComponents ); IterComponents; ++IterComponents) + { + int32 MeshIdx = 0; + UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + continue; + + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator + IterMeshes( HoudiniAssetComponent->StaticMeshes ); IterMeshes; ++IterMeshes ) + { + UStaticMesh * StaticMesh = IterMeshes.Value(); + FHoudiniGeoPartObject & HoudiniGeoPartObject = IterMeshes.Key(); + + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + continue; + + NumberOfGeneratedMeshes++; + + FString Label = HoudiniAssetComponent->GetBakingBaseName( HoudiniGeoPartObject); + + // Create thumbnail for this mesh. + TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = + MakeShareable( new FAssetThumbnail( StaticMesh, 64, 64, AssetThumbnailPool ) ); + + TSharedPtr< SBorder > StaticMeshThumbnailBorder; + TSharedRef< SVerticalBox > VerticalBox = SNew( SVerticalBox ); + + IDetailGroup& StaticMeshGrp = DetailCategoryBuilder.AddGroup(FName(*Label), FText::FromString(Label)); + + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "BakeBaseName", "Bake Name" ) ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + ] + .ValueContent() + .MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ) + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + .FillWidth( 1 ) + [ + SNew( SEditableTextBox ) + .Text( FText::FromString(Label) ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + .OnTextCommitted( this, &FHoudiniAssetComponentDetails::OnBakeNameCommited, HoudiniAssetComponent, HoudiniGeoPartObject ) + .ToolTipText( LOCTEXT( "BakeNameTip", "The base name of the baked asset") ) + ] + +SHorizontalBox::Slot() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "RevertNameOverride", "Revert bake name override" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked( this, &FHoudiniAssetComponentDetails::OnRemoveBakingBaseNameOverride, HoudiniAssetComponent, HoudiniGeoPartObject ) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ] + ]; + + FString MeshLabel = TEXT( "Static Mesh" ); + if( HoudiniGeoPartObject.bHasCollisionBeenAdded ) + { + int32 NumColliders = 1; + if ( StaticMesh->BodySetup && !StaticMesh->BodySetup->IsPendingKill() ) + NumColliders = StaticMesh->BodySetup->AggGeom.GetElementCount(); + + MeshLabel += TEXT( "\n(") + FString::FromInt( NumColliders ) + TEXT(" Simple Collider" ); + if ( NumColliders > 1 ) + MeshLabel += TEXT("s"); + MeshLabel += TEXT(")"); + } + else if( HoudiniGeoPartObject.bIsRenderCollidable ) + { + MeshLabel += TEXT( "\n(Rendered Complex Collider)" ); + } + else if( HoudiniGeoPartObject.bIsCollidable ) + { + MeshLabel += TEXT( "\n(Invisible Complex Collider)" ); + } + + if ( StaticMesh->GetNumLODs() > 1 ) + MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->GetNumLODs() ) + TEXT(" LODs)"); + + if ( StaticMesh->Sockets.Num() > 0 ) + MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->Sockets.Num() ) + TEXT(" sockets)"); + + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew( STextBlock ) + .Text( FText::FromString(MeshLabel) ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + VerticalBox + ]; + + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 0.0f, 0.0f, 2.0f, 0.0f ) + .AutoWidth() + [ + SAssignNew( StaticMeshThumbnailBorder, SBorder ) + .Padding( 5.0f ) + .BorderImage( this, &FHoudiniAssetComponentDetails::GetStaticMeshThumbnailBorder, StaticMesh ) + .OnMouseDoubleClick( this, &FHoudiniAssetComponentDetails::OnThumbnailDoubleClick, (UObject *) StaticMesh ) + [ + SNew( SBox ) + .WidthOverride( 64 ) + .HeightOverride( 64 ) + .ToolTipText( FText::FromString( StaticMesh->GetPathName() ) ) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ] + +SHorizontalBox::Slot() + .FillWidth( 1.0f ) + .Padding( 0.0f, 4.0f, 4.0f, 4.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SVerticalBox ) + +SVerticalBox::Slot() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .MaxWidth( 80.0f ) + [ + SNew( SButton ) + .VAlign( VAlign_Center ) + .HAlign( HAlign_Center ) + .Text( LOCTEXT( "Bake", "Bake" ) ) + .OnClicked( this, &FHoudiniAssetComponentDetails::OnBakeStaticMesh, StaticMesh, HoudiniAssetComponent ) + .ToolTipText( LOCTEXT( "HoudiniStaticMeshBakeButton", "Bake this generated static mesh" ) ) + ] + ] + ] + ]; + + // Store thumbnail for this mesh. + StaticMeshThumbnailBorders.Add( StaticMesh, StaticMeshThumbnailBorder ); + + // We need to add material box for each material present in this static mesh. + auto & StaticMeshMaterials = StaticMesh->StaticMaterials; + for ( int32 MaterialIdx = 0; MaterialIdx < StaticMeshMaterials.Num(); ++MaterialIdx ) + { + UMaterialInterface * MaterialInterface = StaticMeshMaterials[ MaterialIdx ].MaterialInterface; + TSharedPtr< SBorder > MaterialThumbnailBorder; + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + + FString MaterialName, MaterialPathName; + if ( MaterialInterface && !MaterialInterface->IsPendingKill() + && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill() ) + { + MaterialName = MaterialInterface->GetName(); + MaterialPathName = MaterialInterface->GetPathName(); + } + else + { + MaterialInterface = nullptr; + MaterialName = TEXT("Material (invalid)") + FString::FromInt( MaterialIdx ) ; + MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); + } + + // Create thumbnail for this material. + TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = + MakeShareable( new FAssetThumbnail( MaterialInterface, 64, 64, AssetThumbnailPool ) ); + + VerticalBox->AddSlot().Padding( 0, 2 ) + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop( this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceDraggedOver ) + .OnAssetDropped( + this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceDropped, + StaticMesh, &HoudiniGeoPartObject, MaterialIdx ) + [ + SAssignNew( HorizontalBox, SHorizontalBox ) + ] + ]; + + HorizontalBox->AddSlot().Padding( 0.0f, 0.0f, 2.0f, 0.0f ).AutoWidth() + [ + SAssignNew( MaterialThumbnailBorder, SBorder ) + .Padding( 5.0f ) + .BorderImage( + this, &FHoudiniAssetComponentDetails::GetMaterialInterfaceThumbnailBorder, StaticMesh, MaterialIdx ) + .OnMouseDoubleClick( + this, &FHoudiniAssetComponentDetails::OnThumbnailDoubleClick, (UObject *) MaterialInterface ) + [ + SNew( SBox ) + .WidthOverride( 64 ) + .HeightOverride( 64 ) + .ToolTipText( FText::FromString( MaterialPathName ) ) + [ + MaterialInterfaceThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + // Store thumbnail for this mesh and material index. + { + TPairInitializer< UStaticMesh *, int32 > Pair( StaticMesh, MaterialIdx ); + MaterialInterfaceThumbnailBorders.Add( Pair, MaterialThumbnailBorder ); + } + + TSharedPtr< SComboButton > AssetComboButton; + TSharedPtr< SHorizontalBox > ButtonBox; + + HorizontalBox->AddSlot() + .FillWidth( 1.0f ) + .Padding( 0.0f, 4.0f, 4.0f, 4.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SVerticalBox ) + +SVerticalBox::Slot() + .HAlign( HAlign_Fill ) + [ + SAssignNew( ButtonBox, SHorizontalBox ) + +SHorizontalBox::Slot() + [ + SAssignNew( AssetComboButton, SComboButton ) + //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) + .ButtonStyle( FEditorStyle::Get(), "PropertyEditor.AssetComboStyle" ) + .ForegroundColor( FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity" ) ) + .OnGetMenuContent( this, &FHoudiniAssetComponentDetails::OnGetMaterialInterfaceMenuContent, + MaterialInterface, StaticMesh, &HoudiniGeoPartObject, MaterialIdx ) + .ContentPadding( 2.0f ) + .ButtonContent() + [ + SNew( STextBlock ) + .TextStyle( FEditorStyle::Get(), "PropertyEditor.AssetClass" ) + .Font( FEditorStyle::GetFontStyle( FName( TEXT( "PropertyWindow.NormalFont" ) ) ) ) + .Text( FText::FromString( MaterialName ) ) + ] + ] + ] + ]; + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add( TEXT( "Asset" ), FText::FromString( MaterialName ) ); + FText MaterialTooltip = FText::Format( + LOCTEXT( "BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser" ), Args ); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceBrowse, MaterialInterface ), + TAttribute< FText >( MaterialTooltip ) ) + ]; + + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "ResetToBaseMaterial", "Reset to base material" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked( + this, &FHoudiniAssetComponentDetails::OnResetMaterialInterfaceClicked, + StaticMesh, &HoudiniGeoPartObject, MaterialIdx ) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ]; + + // Store combo button for this mesh and index. + { + TPairInitializer< UStaticMesh *, int32 > Pair( StaticMesh, MaterialIdx ); + MaterialInterfaceComboButtons.Add( Pair, AssetComboButton ); + } + } + + MeshIdx++; + } + + // Do the same for the Landscape components + for (TMap< FHoudiniGeoPartObject, TWeakObjectPtr >::TIterator + IterLandscapes(HoudiniAssetComponent->LandscapeComponents); IterLandscapes; ++IterLandscapes) + { + ALandscapeProxy * Landscape = IterLandscapes.Value().Get(); + FHoudiniGeoPartObject & HoudiniGeoPartObject = IterLandscapes.Key(); + + if (!Landscape || !Landscape->IsValidLowLevel() ) + continue; + + NumberOfGeneratedMeshes++; + + FString Label = TEXT(""); + if (HoudiniGeoPartObject.HasCustomName()) + Label = HoudiniGeoPartObject.PartName; + else + Label = Landscape->GetName(); + + // Create thumbnail for this landscape. + TSharedPtr< FAssetThumbnail > LandscapeThumbnail = + MakeShareable(new FAssetThumbnail(Landscape, 64, 64, AssetThumbnailPool)); + + TSharedPtr< SBorder > LandscapeThumbnailBorder; + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + IDetailGroup& LandscapeGrp = DetailCategoryBuilder.AddGroup(FName(*Label), FText::FromString(Label)); + LandscapeGrp.AddWidgetRow() + .NameContent() + [ + SNew(SSpacer) + .Size(FVector2D(250, 64)) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + VerticalBox + ]; + + VerticalBox->AddSlot().Padding(0, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .AutoWidth() + [ + SAssignNew(LandscapeThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage(this, &FHoudiniAssetComponentDetails::GetLandscapeThumbnailBorder, Landscape) + .OnMouseDoubleClick(this, &FHoudiniAssetComponentDetails::OnThumbnailDoubleClick, (UObject *)Landscape) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(Landscape->GetPathName())) + [ + LandscapeThumbnail->MakeThumbnailWidget() + ] + ] + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .MaxWidth(80.0f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("Bake", "Bake")) + .OnClicked(this, &FHoudiniAssetComponentDetails::OnBakeLandscape, Landscape, HoudiniAssetComponent) + .ToolTipText(LOCTEXT("HoudiniLandscapeBakeButton", "Bake this landscape")) + ] + ] + ] + ]; + + // Store thumbnail for this landscape. + LandscapeThumbnailBorders.Add(Landscape, LandscapeThumbnailBorder); + + // We need to add material box for each the landscape and landscape hole materials + for (int32 MaterialIdx = 0; MaterialIdx < 2; ++MaterialIdx) + { + UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); + TSharedPtr< SBorder > MaterialThumbnailBorder; + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + + FString MaterialName, MaterialPathName; + if (MaterialInterface) + { + MaterialName = MaterialInterface->GetName(); + MaterialPathName = MaterialInterface->GetPathName(); + } + + // Create thumbnail for this material. + TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = + MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(STextBlock) + .Text(MaterialIdx == 0 ? LOCTEXT("LandscapeMaterial", "Landscape Material") : LOCTEXT("LandscapeHoleMaterial", "Landscape Hole Material")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + VerticalBox->AddSlot().Padding(0, 2) + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop( this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceDraggedOver ) + .OnAssetDropped( + this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceDropped, + Landscape, &HoudiniGeoPartObject, MaterialIdx) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + [ + SAssignNew(MaterialThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage( + this, &FHoudiniAssetComponentDetails::GetMaterialInterfaceThumbnailBorder, Landscape, MaterialIdx) + .OnMouseDoubleClick( + this, &FHoudiniAssetComponentDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(MaterialPathName)) + [ + MaterialInterfaceThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + // Store thumbnail for this mesh and material index. + { + TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); + LandscapeMaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); + } + + TSharedPtr< SComboButton > AssetComboButton; + TSharedPtr< SHorizontalBox > ButtonBox; + + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .HAlign(HAlign_Fill) + [ + SAssignNew(ButtonBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SAssignNew(AssetComboButton, SComboButton) + //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .OnGetMenuContent(this, &FHoudiniAssetComponentDetails::OnGetMaterialInterfaceMenuContent, + MaterialInterface, Landscape, &HoudiniGeoPartObject, MaterialIdx) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(MaterialName)) + ] + ] + ] + ]; + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + FText MaterialTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceBrowse, MaterialInterface ), + TAttribute< FText >( MaterialTooltip ) ) + ]; + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked( + this, &FHoudiniAssetComponentDetails::OnResetMaterialInterfaceClicked, + Landscape, &HoudiniGeoPartObject, MaterialIdx ) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + + // Store combo button for this mesh and index. + { + TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); + LandscapeMaterialInterfaceComboButtons.Add(Pair, AssetComboButton); + } + } + + MeshIdx++; + } + } + + if (NumberOfGeneratedMeshes > 1) + { + // Add the BakeAll button + TSharedRef< SHorizontalBox > HorizontalButtonBox = SNew(SHorizontalBox); + DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .Padding(0, 2.0f, 0, 0) + .FillHeight(1.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(HorizontalButtonBox, SHorizontalBox) + ] + ]; + + HorizontalButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked(this, &FHoudiniAssetComponentDetails::OnBakeAllGeneratedMeshes) + .Text(LOCTEXT("BakeHoudiniActor", "Bake All")) + .ToolTipText(LOCTEXT("BakeHoudiniActorToolTip", "Bake all generated meshes")) + ]; + } +} + +void +FHoudiniAssetComponentDetails::CreateHoudiniAssetWidget( IDetailCategoryBuilder & DetailCategoryBuilder ) +{ + // Reset Houdini asset related widgets. + HoudiniAssetComboButton.Reset(); + HoudiniAssetThumbnailBorder.Reset(); + + // Get thumbnail pool for this builder. + IDetailLayoutBuilder& DetailLayoutBuilder = DetailCategoryBuilder.GetParentLayout(); + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + FSlateFontInfo NormalFont = FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")); + UHoudiniAsset * HoudiniAsset = nullptr; + UHoudiniAssetComponent * HoudiniAssetComponent = nullptr; + FString HoudiniAssetPathName = TEXT( "" ); + FString HoudiniAssetName = TEXT( "" ); + + if ( HoudiniAssetComponents.Num() > 0 ) + { + HoudiniAssetComponent = HoudiniAssetComponents[ 0 ]; + HoudiniAsset = HoudiniAssetComponent->HoudiniAsset; + + if ( HoudiniAsset ) + { + HoudiniAssetPathName = HoudiniAsset->GetPathName(); + HoudiniAssetName = HoudiniAsset->GetName(); + } + } + + // Create thumbnail for this Houdini asset. + TSharedPtr< FAssetThumbnail > HoudiniAssetThumbnail = + MakeShareable(new FAssetThumbnail(HoudiniAsset, 64, 64, AssetThumbnailPool)); + + IDetailGroup& OptionsGroup = DetailCategoryBuilder.AddGroup(TEXT("Options"), LOCTEXT("Options", "Asset Options")); + OptionsGroup.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniDigitalAsset", "Houdini Digital Asset")) + .Font(NormalFont) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop( this, &FHoudiniAssetComponentDetails::OnHoudiniAssetDraggedOver ) + .OnAssetDropped( this, &FHoudiniAssetComponentDetails::OnHoudiniAssetDropped ) + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 0.0f, 0.0f, 2.0f, 0.0f ) + .AutoWidth() + [ + SAssignNew( HoudiniAssetThumbnailBorder, SBorder ) + .Padding( 5.0f ) + .BorderImage( this, &FHoudiniAssetComponentDetails::GetHoudiniAssetThumbnailBorder ) + [ + SNew( SBox ) + .WidthOverride( 64 ) + .HeightOverride( 64 ) + .ToolTipText( FText::FromString( HoudiniAssetPathName ) ) + [ + HoudiniAssetThumbnail->MakeThumbnailWidget() + ] + ] + ] + +SHorizontalBox::Slot() + .FillWidth(1.f) + .Padding( 0.0f, 4.0f, 4.0f, 4.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SVerticalBox ) + +SVerticalBox::Slot() + .HAlign( HAlign_Fill ) + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + [ + SAssignNew( HoudiniAssetComboButton, SComboButton ) + .ButtonStyle( FEditorStyle::Get(), "PropertyEditor.AssetComboStyle" ) + .ForegroundColor( FEditorStyle::GetColor( "PropertyEditor.AssetName.ColorAndOpacity" ) ) + .OnGetMenuContent( this, &FHoudiniAssetComponentDetails::OnGetHoudiniAssetMenuContent ) + .ContentPadding( 2.0f ) + .ButtonContent() + [ + SNew( STextBlock ) + .TextStyle( FEditorStyle::Get(), "PropertyEditor.AssetClass" ) + .Font(NormalFont) + .Text( FText::FromString( HoudiniAssetName ) ) + ] + ] + +SHorizontalBox::Slot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( this, &FHoudiniAssetComponentDetails::OnHoudiniAssetBrowse ), + TAttribute< FText >( FText::FromString( HoudiniAssetName ) ) ) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "ResetToBaseHoudiniAsset", "Reset Houdini Asset" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked( this, &FHoudiniAssetComponentDetails::OnResetHoudiniAssetClicked ) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ] // horizontal buttons next to thumbnail box + ] + ] // horizontal asset chooser box + ] + ]; + + auto AddOptionRow = [&](const FText& NameText, FOnCheckStateChanged OnCheckStateChanged, TAttribute IsCheckedAttr) + { + OptionsGroup.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(NameText) + .Font(NormalFont) + ] + .ValueContent() + [ + SNew( SCheckBox ) + .OnCheckStateChanged( OnCheckStateChanged ) + .IsChecked( IsCheckedAttr ) + ]; + }; + + AddOptionRow( + LOCTEXT("HoudiniEnableCookingOnParamChange", "Enable Cooking on Parameter Change"), + FOnCheckStateChanged::CreateSP(this, &FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingCooking, HoudiniAssetComponent), + TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::IsCheckedComponentSettingCooking, HoudiniAssetComponent))); + AddOptionRow( + LOCTEXT("HoudiniUploadTransformsToHoudiniEngine", "Upload Transforms to Houdini Engine"), + FOnCheckStateChanged::CreateSP(this, &FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingUploadTransform, HoudiniAssetComponent), + TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::IsCheckedComponentSettingUploadTransform, HoudiniAssetComponent))); + AddOptionRow( + LOCTEXT("HoudiniTransformChangeTriggersCooks", "Transform Change Triggers Cooks"), + FOnCheckStateChanged::CreateSP(this, &FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingTransformCooking, HoudiniAssetComponent), + TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::IsCheckedComponentSettingTransformCooking, HoudiniAssetComponent))); + AddOptionRow( + LOCTEXT("HoudiniUseHoudiniMaterials", "Use Native Houdini Materials"), + FOnCheckStateChanged::CreateSP(this, &FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingUseHoudiniMaterials, HoudiniAssetComponent), + TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::IsCheckedComponentSettingUseHoudiniMaterials, HoudiniAssetComponent))); + AddOptionRow( + LOCTEXT("HoudiniCookingTriggersDownstreamCooks", "Cooking Triggers Downstream Cooks"), + FOnCheckStateChanged::CreateSP(this, &FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingCookingTriggersDownstreamCooks, HoudiniAssetComponent), + TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::IsCheckedComponentSettingCookingTriggersDownstreamCooks, HoudiniAssetComponent))); + + auto ActionButtonSlot = [&](const FText& InText, const FText& InToolTipText, FOnClicked InOnClicked) -> SHorizontalBox::FSlot& + { + return SHorizontalBox::Slot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + .HAlign( HAlign_Center ) + [ + SNew( SButton ) + .VAlign( VAlign_Center ) + .HAlign( HAlign_Center ) + .OnClicked(InOnClicked) + .Text(InText) + .ToolTipText(InToolTipText) + ]; + }; + + IDetailGroup& CookGroup = DetailCategoryBuilder.AddGroup(TEXT("Cooking"), LOCTEXT("CookingActions", "Cooking Actions")); + CookGroup.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + +ActionButtonSlot( + LOCTEXT("RecookHoudiniActor", "Recook Asset"), + LOCTEXT("RecookHoudiniActorToolTip", "Recooks the outputs of the Houdini asset"), + FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnRecookAsset)) + +ActionButtonSlot( + LOCTEXT("RebuildHoudiniActor", "Rebuild Asset"), + LOCTEXT("RebuildHoudiniActorToolTip", "Deletes and then re-creates and re-cooks the Houdini asset"), + FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnRebuildAsset)) + +ActionButtonSlot( + LOCTEXT("ResetHoudiniActor", "Reset Parameters"), + LOCTEXT("ResetHoudiniActorToolTip", "Resets parameters to their default values"), + FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnResetAsset)) + ]; + + // Cook folder widget + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + FPathPickerConfig CookPathPickerConfig; + CookPathPickerConfig.OnPathSelected = FOnPathSelected::CreateSP(this, &FHoudiniAssetComponentDetails::OnCookFolderSelected); + CookPathPickerConfig.bAllowContextMenu = false; + CookPathPickerConfig.bAllowClassesFolder = true; + + CookGroup.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("CookFolder", "Temporary Cook Folder")) + .Font(NormalFont) + ] + .ValueContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SEditableText) + .IsReadOnly(true) + .Text(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::GetTempCookFolderText))) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::GetTempCookFolderText))) + ] + + SHorizontalBox::Slot() + [ + SNew(SComboButton) + .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") + .ToolTipText(LOCTEXT("ChooseACookFolder", "Choose a temporary cook folder for this asset.")) + .ContentPadding(2.0f) + .ForegroundColor(FSlateColor::UseForeground()) + .IsFocusable(false) + .MenuContent() + [ + SNew(SBox) + .WidthOverride(300.0f) + .HeightOverride(300.0f) + [ + ContentBrowserModule.Get().CreatePathPicker(CookPathPickerConfig) + ] + ] + ] + ]; + + IDetailGroup& BakeGroup = DetailCategoryBuilder.AddGroup(TEXT("Baking"), LOCTEXT("Baking", "Baking")); + TSharedPtr< SButton > BakeToInputButton, BakeToFoliageButton, ReplaceWithFoliageButton; + + BakeGroup.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + ActionButtonSlot( + LOCTEXT("BakeBlueprintHoudiniActor", "Bake Blueprint"), + LOCTEXT("BakeBlueprintHoudiniActorToolTip", "Bakes to a new Blueprint"), + FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnBakeBlueprint)) + + + ActionButtonSlot( + LOCTEXT("BakeToActors", "Bake to Actors"), + LOCTEXT("BakeToActorsTooltip", "Bakes each output and creates new individual Actors"), + FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnBakeToActors)) + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SAssignNew(BakeToFoliageButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked(this, &FHoudiniAssetComponentDetails::OnBakeToFoliage) + .Text(LOCTEXT("BakeToFoliage", "Bake to Foliage")) + .ToolTipText(LOCTEXT("BakeToFoliageTooltip", "Bakes instanced static meshes to foliage actor.\nNote: The asset must be outputing at least one instancer. Only instanced static meshes will be baked to foliage.")) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SAssignNew(BakeToInputButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked(this, &FHoudiniAssetComponentDetails::OnBakeToInput) + .Text(LOCTEXT("BakeToInput", "Bake to Outliner Input")) + .ToolTipText(LOCTEXT("BakeToInputTooltip", "Bakes single static mesh and sets it on the first outliner input actor and then disconnects it.\nNote: There must be one static mesh outliner input and one generated mesh.")) + ] + ]; + + BakeGroup.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + ActionButtonSlot( + LOCTEXT("BakeReplaceBlueprintHoudiniActor", "Replace With Blueprint"), + LOCTEXT("BakeReplaceBlueprintHoudiniActorToolTip", "Bakes to a new Blueprint and replaces this Actor"), + FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnBakeBlueprintReplace)) + + + ActionButtonSlot( + LOCTEXT("BakeReplaceActors", "Replace with Actors"), + LOCTEXT("BakeReplaceActorsTooltip", "Replace this Actors with individual actors baked from each output."), + FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnBakeToActorsReplace)) + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SAssignNew(ReplaceWithFoliageButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked(this, &FHoudiniAssetComponentDetails::OnBakeToFoliageReplace) + .Text(LOCTEXT("BakeReplaceFoliage", "Replace with Foliage")) + .ToolTipText(LOCTEXT("BakeReplaceFoliageTooltip", "Bakes instanced static meshes to the foliage actor and deletes this Actor.\nNote: The asset must be outputing at least one instancer. Only instanced static meshes will be baked to foliage.")) + ] + ]; + + + + // Enable disable the bake to input/foliage buttons + BakeToInputButton->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda([=] { + return FHoudiniEngineBakeUtils::CanComponentBakeToOutlinerInput(HoudiniAssetComponent); + }))); + + BakeToFoliageButton->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda([=] { + return FHoudiniEngineBakeUtils::CanComponentBakeToFoliage(HoudiniAssetComponent); + }))); + + ReplaceWithFoliageButton->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda([=] { + return FHoudiniEngineBakeUtils::CanComponentBakeToFoliage(HoudiniAssetComponent); + }))); + + + // + // Bake folder widget + // + FPathPickerConfig BakePathPickerConfig; + BakePathPickerConfig.OnPathSelected = FOnPathSelected::CreateSP( this, &FHoudiniAssetComponentDetails::OnBakeFolderSelected ); + BakePathPickerConfig.bAllowContextMenu = false; + BakePathPickerConfig.bAllowClassesFolder = true; + + BakeGroup.AddWidgetRow() + .NameContent() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "BakeFolder", "Bake Folder" ) ) + .Font( NormalFont ) + ] + .ValueContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SEditableText) + .IsReadOnly(true) + .Text( TAttribute::Create( TAttribute::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::GetBakeFolderText ) ) ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + .ToolTipText( TAttribute::Create( TAttribute::FGetter::CreateSP( this, &FHoudiniAssetComponentDetails::GetBakeFolderText ) ) ) + ] + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SComboButton) + .ButtonStyle( FEditorStyle::Get(), "HoverHintOnly" ) + .ToolTipText( LOCTEXT( "ChooseABakeFolder", "Choose a baking output folder") ) + .ContentPadding( 2.0f ) + .ForegroundColor( FSlateColor::UseForeground() ) + .IsFocusable( false ) + .MenuContent() + [ + SNew(SBox) + .WidthOverride(300.0f) + .HeightOverride(300.0f) + [ + ContentBrowserModule.Get().CreatePathPicker(BakePathPickerConfig) + ] + ] + ] + ]; + + // + // Help widget + // + IDetailGroup& HelpGroup = DetailCategoryBuilder.AddGroup(TEXT("Help"), LOCTEXT("Help", "Help and Debugging")); + HelpGroup.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + +ActionButtonSlot( + LOCTEXT("FetchCookLogHoudiniActor", "Fetch Cook Log"), + LOCTEXT("FetchCookLogHoudiniActorToolTip", "Fetches the cook log from Houdini"), + FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnFetchCookLog)) + +ActionButtonSlot( + LOCTEXT("FetchAssetHelpHoudiniActor", "Asset Help"), + LOCTEXT("FetchAssetHelpHoudiniActorToolTip", "Displays the asset's Help text"), + FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnFetchAssetHelp, HoudiniAssetComponent)) + ]; +} + +const FSlateBrush * +FHoudiniAssetComponentDetails::GetStaticMeshThumbnailBorder( UStaticMesh * StaticMesh ) const +{ + TSharedPtr< SBorder > ThumbnailBorder = StaticMeshThumbnailBorders[ StaticMesh ]; + if ( ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered() ) + return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailLight" ); + else + return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailShadow" ); +} + +const FSlateBrush * +FHoudiniAssetComponentDetails::GetLandscapeThumbnailBorder(ALandscapeProxy * Landscape ) const +{ + TSharedPtr< SBorder > ThumbnailBorder = LandscapeThumbnailBorders[ Landscape ]; + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} + +const FSlateBrush * +FHoudiniAssetComponentDetails::GetMaterialInterfaceThumbnailBorder( UStaticMesh * StaticMesh, int32 MaterialIdx ) const +{ + if ( !StaticMesh ) + return nullptr; + + TPairInitializer< UStaticMesh *, int32 > Pair( StaticMesh, MaterialIdx ); + TSharedPtr< SBorder > ThumbnailBorder = MaterialInterfaceThumbnailBorders[ Pair ]; + + if ( ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered() ) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailShadow" ); +} + +const FSlateBrush * +FHoudiniAssetComponentDetails::GetMaterialInterfaceThumbnailBorder(ALandscapeProxy * Landscape, int32 MaterialIdx ) const +{ + if ( !Landscape ) + return nullptr; + + TPairInitializer< ALandscapeProxy *, int32 > Pair( Landscape, MaterialIdx ); + TSharedPtr< SBorder > ThumbnailBorder = LandscapeMaterialInterfaceThumbnailBorders[ Pair ]; + + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailLight" ); + else + return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailShadow" ); +} + +FReply +FHoudiniAssetComponentDetails::OnThumbnailDoubleClick( + const FGeometry & InMyGeometry, + const FPointerEvent & InMouseEvent, UObject * Object ) +{ + if ( Object && GEditor ) + GEditor->EditObject( Object ); + + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnBakeStaticMesh( UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent ) +{ + if ( HoudiniAssetComponent && StaticMesh && !HoudiniAssetComponent->IsPendingKill() && !StaticMesh->IsPendingKill() ) + { + // We need to locate corresponding geo part object in component. + const FHoudiniGeoPartObject& HoudiniGeoPartObject = HoudiniAssetComponent->LocateGeoPartObject( StaticMesh ); + + (void) FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( + StaticMesh, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::ReplaceExisitingAssets ); + } + + return FReply::Handled(); +} + +void +FHoudiniAssetComponentDetails::OnBakeNameCommited( const FText& NewText, ETextCommit::Type CommitType, + UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject HoudiniGeoPartObject ) +{ + if( ensure(HoudiniAssetComponent) ) + { + HoudiniAssetComponent->SetBakingBaseNameOverride( HoudiniGeoPartObject, NewText.ToString() ); + } + + // The group label has to be updated + if( HoudiniAssetComponent ) + HoudiniAssetComponent->UpdateEditorProperties( false ); +} + +FReply +FHoudiniAssetComponentDetails::OnRemoveBakingBaseNameOverride( UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject GeoPartObject ) +{ + if( HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill() ) + { + if ( HoudiniAssetComponent->RemoveBakingBaseNameOverride( GeoPartObject ) ) + HoudiniAssetComponent->UpdateEditorProperties( false ); + } + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnBakeLandscape(ALandscapeProxy * Landscape, UHoudiniAssetComponent * HoudiniAssetComponent) +{ + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() + || !Landscape || Landscape->IsPendingKill() ) + return FReply::Handled(); + + bool bNeedToUpdateProperties = FHoudiniEngineBakeUtils::BakeLandscape( HoudiniAssetComponent, Landscape ); + + // Modify the component GUID to avoid overwriting the layers + if ( bNeedToUpdateProperties ) + { + HoudiniAssetComponent->ComponentGUID = FGuid::NewGuid(); + HoudiniAssetComponent->UpdateEditorProperties(false); + } + + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnBakeAllGeneratedMeshes() +{ + if ( HoudiniAssetComponents.Num() > 0 ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ]; + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + return FReply::Handled(); + + for( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator + Iter(HoudiniAssetComponent->StaticMeshes); Iter; ++Iter ) + { + FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key(); + UStaticMesh * StaticMesh = Iter.Value(); + (void) OnBakeStaticMesh( StaticMesh, HoudiniAssetComponent ); + } + + for (TMap< FHoudiniGeoPartObject, TWeakObjectPtr >::TIterator + IterLandscapes(HoudiniAssetComponent->LandscapeComponents); IterLandscapes; ++IterLandscapes) + { + ALandscapeProxy * Landscape = IterLandscapes.Value().Get(); + if ( !Landscape || !Landscape->IsValidLowLevel() ) + continue; + + (void) OnBakeLandscape(Landscape, HoudiniAssetComponent); + } + } + + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnRecookAsset() +{ + if ( HoudiniAssetComponents.Num() > 0 ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ]; + if ( HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill() ) + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnRebuildAsset() +{ + if ( HoudiniAssetComponents.Num() > 0 ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ]; + if ( HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill() ) + HoudiniAssetComponent->StartTaskAssetRebuildManual(); + } + + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnResetAsset() +{ + if ( HoudiniAssetComponents.Num() > 0 ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ]; + if ( HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill() ) + HoudiniAssetComponent->StartTaskAssetResetManual(); + } + + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnBakeBlueprint() +{ + if ( HoudiniAssetComponents.Num() > 0 ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ]; + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + return FReply::Handled(); + + // If component is not cooking or instancing, we can bake blueprint. + if ( !HoudiniAssetComponent->IsInstantiatingOrCooking() ) + FHoudiniEngineBakeUtils::BakeBlueprint( HoudiniAssetComponent ); + } + + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnBakeBlueprintReplace() +{ + if ( HoudiniAssetComponents.Num() > 0 ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ]; + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + return FReply::Handled(); + + // If component is not cooking or instancing, we can bake blueprint. + if ( !HoudiniAssetComponent->IsInstantiatingOrCooking() ) + FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithBlueprint( HoudiniAssetComponent ); + } + + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnBakeToActors() +{ + if ( HoudiniAssetComponents.Num() > 0 ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ]; + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + return FReply::Handled(); + + // If component is not cooking or instancing, we can bake. + if ( !HoudiniAssetComponent->IsInstantiatingOrCooking() ) + { + FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( HoudiniAssetComponent, true ); + } + } + + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnBakeToActorsReplace() +{ + if (HoudiniAssetComponents.Num() > 0) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[0]; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return FReply::Handled(); + + // If component is not cooking or instancing, we can bake. + if (!HoudiniAssetComponent->IsInstantiatingOrCooking()) + { + FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(HoudiniAssetComponent, false); + } + } + + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnBakeToInput() +{ + if ( HoudiniAssetComponents.Num() > 0 ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ]; + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + return FReply::Handled(); + + // If component is not cooking or instancing, we can bake. + if ( !HoudiniAssetComponent->IsInstantiatingOrCooking() ) + { + FHoudiniEngineBakeUtils::BakeHoudiniActorToOutlinerInput( HoudiniAssetComponent ); + } + } + + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnBakeToFoliage() +{ + if ( HoudiniAssetComponents.Num() > 0 ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[0]; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return FReply::Handled(); + + // If component is not cooking or instancing, we can bake. + if (!HoudiniAssetComponent->IsInstantiatingOrCooking()) + { + FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(HoudiniAssetComponent); + } + } + + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnBakeToFoliageReplace() +{ + if (HoudiniAssetComponents.Num() > 0) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[0]; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return FReply::Handled(); + + // If component is not cooking or instancing, we can bake. + if (!HoudiniAssetComponent->IsInstantiatingOrCooking()) + { + FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithFoliage(HoudiniAssetComponent); + } + } + + return FReply::Handled(); +} + +void +FHoudiniAssetComponentDetails::OnBakeFolderSelected( const FString& Folder ) +{ + if( HoudiniAssetComponents.Num() && HoudiniAssetComponents[ 0 ] && !HoudiniAssetComponents[0]->IsPendingKill() ) + { + HoudiniAssetComponents[ 0 ]->SetBakeFolder( Folder ); + } +} + +FText +FHoudiniAssetComponentDetails::GetBakeFolderText() const +{ + FText BakeFolderText; + if( HoudiniAssetComponents.Num() && HoudiniAssetComponents[ 0 ] && !HoudiniAssetComponents[0]->IsPendingKill() ) + { + BakeFolderText = HoudiniAssetComponents[ 0 ]->GetBakeFolder(); + } + return BakeFolderText; +} + +void +FHoudiniAssetComponentDetails::OnCookFolderSelected(const FString& Folder) +{ + if (HoudiniAssetComponents.Num() && HoudiniAssetComponents[0] && !HoudiniAssetComponents[0]->IsPendingKill()) + { + HoudiniAssetComponents[0]->SetTempCookFolder(Folder); + } +} + +FText +FHoudiniAssetComponentDetails::GetTempCookFolderText() const +{ + FText TempCookFolderText; + if (HoudiniAssetComponents.Num() && HoudiniAssetComponents[0] && !HoudiniAssetComponents[0]->IsPendingKill() ) + { + TempCookFolderText = HoudiniAssetComponents[0]->GetTempCookFolder(); + } + return TempCookFolderText; +} + +FReply +FHoudiniAssetComponentDetails::OnFetchCookLog() +{ + TSharedPtr< SWindow > ParentWindow; + + FString CookLog; + + // Get fetch cook status. + FString CookResult = FHoudiniEngineUtils::GetCookResult(); + if (!CookResult.IsEmpty()) + CookLog += TEXT("Cook Results:\n") + CookResult + TEXT("\n\n"); + + // Add the cook state + FString CookState = FHoudiniEngineUtils::GetCookState(); + if ( !CookState.IsEmpty()) + CookLog += TEXT("Cook State:\n") + CookState + TEXT("\n\n"); + + // Error Description + FString Error = FHoudiniEngineUtils::GetErrorDescription(); + if (!Error.IsEmpty()) + CookLog += TEXT("Error Description:\n") + Error + TEXT("\n\n"); + + // Iterates on all the selected HAC and get their node errors + for (auto& HAC : HoudiniAssetComponents) + { + if (!HAC || HAC->IsPendingKill()) + continue; + + // Get the node errors, warnings and messages + FString NodeErrors = FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(HAC->GetAssetId()); + if (!NodeErrors.IsEmpty()) + CookLog += NodeErrors; + } + + // Check if the main frame is loaded. When using the old main frame it may not be. + if ( FModuleManager::Get().IsModuleLoaded( "MainFrame" ) ) + { + IMainFrameModule & MainFrame = FModuleManager::LoadModuleChecked( "MainFrame" ); + ParentWindow = MainFrame.GetParentWindow(); + } + + if (CookLog.IsEmpty()) + { + // See if a failed HAPI initialization / invalid session is preventing us from getting the cook log + if (!FHoudiniApi::IsHAPIInitialized()) + { + CookLog += TEXT("\n\nThe Houdini Engine API Library (HAPI) has not been initialized properly.\n\n"); + } + else + { + const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) + { + CookLog += TEXT("\n\nThe current Houdini Engine Session is not valid.\n\n"); + } + else if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) + { + CookLog += TEXT("\n\nThe current Houdini Engine Session has not been initialized properly.\n\n"); + } + } + + if (!CookLog.IsEmpty()) + { + CookLog += TEXT("Please try to restart the current Houdini Engine session via File > Restart Houdini Engine Session.\n\n"); + } + else + { + CookLog = TEXT("\n\nThe cook log is empty...\n\n"); + } + } + + if ( ParentWindow.IsValid() ) + { + TSharedPtr< SHoudiniAssetLogWidget > HoudiniAssetCookLog; + + TSharedRef< SWindow > Window = + SNew( SWindow ) + .Title( LOCTEXT( "WindowTitle", "Houdini Cook Log" ) ) + .ClientSize( FVector2D( 640, 480 ) ); + + Window->SetContent( + SAssignNew( HoudiniAssetCookLog, SHoudiniAssetLogWidget ) + .LogText(CookLog) ); + + FSlateApplication::Get().AddModalWindow( Window, ParentWindow, false ); + } + + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnFetchAssetHelp( UHoudiniAssetComponent * HoudiniAssetComponent ) +{ + if ( HoudiniAssetComponent ) + { + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HAPI_NodeId AssetId = HoudiniAssetComponent->GetAssetId(); + + if ( FHoudiniEngineUtils::IsValidNodeId( AssetId ) ) + { + auto result = FHoudiniApi::GetAssetInfo( FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ); + if ( result == HAPI_RESULT_SUCCESS ) + { + FString HelpLogString = TEXT( "" ); + FHoudiniEngineString HoudiniEngineString( AssetInfo.helpTextSH ); + if ( HoudiniEngineString.ToFString( HelpLogString ) ) + { + if ( HelpLogString.IsEmpty() ) + HelpLogString = TEXT( "No Asset Help Found" ); + + TSharedPtr< SWindow > ParentWindow; + + // Check if the main frame is loaded. When using the old main frame it may not be. + if ( FModuleManager::Get().IsModuleLoaded( "MainFrame" ) ) + { + IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked< IMainFrameModule >( "MainFrame" ); + ParentWindow = MainFrame.GetParentWindow(); + } + + if ( ParentWindow.IsValid() ) + { + TSharedPtr< SHoudiniAssetLogWidget > HoudiniAssetHelpLog; + + TSharedRef< SWindow > Window = + SNew( SWindow ) + .Title( LOCTEXT( "WindowTitle", "Houdini Asset Help" ) ) + .ClientSize( FVector2D( 640, 480 ) ); + + Window->SetContent( + SAssignNew( HoudiniAssetHelpLog, SHoudiniAssetLogWidget ) + .LogText( HelpLogString ) ); + + FSlateApplication::Get().AddModalWindow( Window, ParentWindow, false ); + } + } + } + } + } + + return FReply::Handled(); +} + +bool +FHoudiniAssetComponentDetails::OnMaterialInterfaceDraggedOver( const UObject * InObject ) const +{ + return ( InObject && InObject->IsA( UMaterialInterface::StaticClass() ) ); +} + +void +FHoudiniAssetComponentDetails::OnMaterialInterfaceDropped( + UObject * InObject, UStaticMesh * StaticMesh, + FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx ) +{ + UMaterialInterface * MaterialInterface = Cast< UMaterialInterface >( InObject ); + if ( !MaterialInterface || MaterialInterface->IsPendingKill() ) + return; + + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + return; + + if ( !StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx) ) + return; + + bool bViewportNeedsUpdate = false; + + // Replace material on component using this static mesh. + for ( TArray< UHoudiniAssetComponent * >::TIterator + IterComponents( HoudiniAssetComponents ); IterComponents; ++IterComponents ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + continue; + + // Retrieve material interface which is being replaced. + UMaterialInterface * OldMaterialInterface = StaticMesh->StaticMaterials[ MaterialIdx ].MaterialInterface; + + if ( OldMaterialInterface == MaterialInterface ) + continue; + + // Record replaced material. + const bool bReplaceSuccessful = HoudiniAssetComponent->ReplaceMaterial( + *HoudiniGeoPartObject, MaterialInterface, OldMaterialInterface, MaterialIdx ); + + bool bMaterialReplaced = false; + if ( bReplaceSuccessful ) + { + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_EDITOR ), + LOCTEXT( "HoudiniMaterialReplacement", "Houdini Material Replacement" ), HoudiniAssetComponent ); + + // Replace material on static mesh. + StaticMesh->Modify(); + StaticMesh->StaticMaterials[ MaterialIdx ].MaterialInterface = MaterialInterface; + + UStaticMeshComponent * StaticMeshComponent = + HoudiniAssetComponent->LocateStaticMeshComponent( StaticMesh ); + if ( StaticMeshComponent && !StaticMeshComponent->IsPendingKill() ) + { + StaticMeshComponent->Modify(); + StaticMeshComponent->SetMaterial( MaterialIdx, MaterialInterface ); + + bMaterialReplaced = true; + } + + TArray< UInstancedStaticMeshComponent * > InstancedStaticMeshComponents; + if ( HoudiniAssetComponent->LocateInstancedStaticMeshComponents( StaticMesh, InstancedStaticMeshComponents ) ) + { + for ( int32 Idx = 0; Idx < InstancedStaticMeshComponents.Num(); ++Idx ) + { + UInstancedStaticMeshComponent * InstancedStaticMeshComponent = InstancedStaticMeshComponents[ Idx ]; + if ( InstancedStaticMeshComponent && !InstancedStaticMeshComponent->IsPendingKill() ) + { + InstancedStaticMeshComponent->Modify(); + InstancedStaticMeshComponent->SetMaterial( MaterialIdx, MaterialInterface ); + + bMaterialReplaced = true; + } + } + } + } + + if ( bMaterialReplaced ) + { + HoudiniAssetComponent->UpdateEditorProperties( false ); + bViewportNeedsUpdate = true; + } + } + + if ( GEditor && bViewportNeedsUpdate ) + GEditor->RedrawAllViewports(); +} + +void +FHoudiniAssetComponentDetails::OnMaterialInterfaceDropped( + UObject * InObject, ALandscapeProxy * Landscape, + FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx) +{ + UMaterialInterface * MaterialInterface = Cast< UMaterialInterface >( InObject ); + if (!MaterialInterface || MaterialInterface->IsPendingKill() ) + return; + + bool bViewportNeedsUpdate = false; + + // Replace material on component using this static mesh. + for (TArray< UHoudiniAssetComponent * >::TIterator + IterComponents(HoudiniAssetComponents); IterComponents; ++IterComponents) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + continue; + + TWeakObjectPtr* FoundLandscapePtr = HoudiniAssetComponent->LandscapeComponents.Find( *HoudiniGeoPartObject ); + if ( !FoundLandscapePtr || !FoundLandscapePtr->IsValid() ) + continue; + + ALandscapeProxy* FoundLandscape = FoundLandscapePtr->Get(); + if (!FoundLandscape || !FoundLandscape->IsValidLowLevel()) + continue; + + if ( FoundLandscape != Landscape ) + continue; + + // Retrieve the material interface which is being replaced. + UMaterialInterface * OldMaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); + if ( OldMaterialInterface == MaterialInterface ) + continue; + + // Record replaced material. + const bool bReplaceSuccessful = HoudiniAssetComponent->ReplaceMaterial( + *HoudiniGeoPartObject, MaterialInterface, OldMaterialInterface, MaterialIdx ); + + if ( !bReplaceSuccessful ) + continue; + + { + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_EDITOR ), + LOCTEXT( "HoudiniMaterialReplacement", "Houdini Material Replacement" ), HoudiniAssetComponent ); + + // Replace material on static mesh. + Landscape->Modify(); + + if ( MaterialIdx == 0 ) + Landscape->LandscapeMaterial = MaterialInterface; + else + Landscape->LandscapeHoleMaterial = MaterialInterface; + + //Landscape->UpdateAllComponentMaterialInstances(); + + // As UpdateAllComponentMaterialInstances() is not accessible to us, we'll try to access the Material's UProperty + // to trigger a fake Property change event that will call the Update function... + FProperty* FoundProperty = FindFProperty< FProperty >( Landscape->GetClass(), ( MaterialIdx == 0 ) ? TEXT( "LandscapeMaterial" ) : TEXT( "LandscapeHoleMaterial" ) ); + if ( FoundProperty ) + { + FPropertyChangedEvent PropChanged( FoundProperty, EPropertyChangeType::ValueSet ); + Landscape->PostEditChangeProperty( PropChanged ); + } + else + { + // The only way to update the material for now is to recook/recreate the landscape... + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + } + + HoudiniAssetComponent->UpdateEditorProperties( false ); + bViewportNeedsUpdate = true; + } + + if ( GEditor && bViewportNeedsUpdate ) + GEditor->RedrawAllViewports(); +} + +TSharedRef< SWidget > +FHoudiniAssetComponentDetails::OnGetMaterialInterfaceMenuContent( + UMaterialInterface * MaterialInterface, + UStaticMesh * StaticMesh, FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx ) +{ + TArray< const UClass * > AllowedClasses; + AllowedClasses.Add( UMaterialInterface::StaticClass() ); + + TArray< UFactory * > NewAssetFactories; + + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData( MaterialInterface ), true, AllowedClasses, + NewAssetFactories, OnShouldFilterMaterialInterface, + FOnAssetSelected::CreateSP( + this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceSelected, + StaticMesh, HoudiniGeoPartObject, MaterialIdx ), + FSimpleDelegate::CreateSP( this, &FHoudiniAssetComponentDetails::CloseMaterialInterfaceComboButton ) ); +} + +TSharedRef< SWidget > +FHoudiniAssetComponentDetails::OnGetMaterialInterfaceMenuContent( + UMaterialInterface * MaterialInterface, + ALandscapeProxy * Landscape, FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx) +{ + TArray< const UClass * > AllowedClasses; + AllowedClasses.Add( UMaterialInterface::StaticClass() ); + + TArray< UFactory * > NewAssetFactories; + + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(MaterialInterface), true, AllowedClasses, + NewAssetFactories, OnShouldFilterMaterialInterface, + FOnAssetSelected::CreateSP( + this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceSelected, + Landscape, HoudiniGeoPartObject, MaterialIdx ), + FSimpleDelegate::CreateSP( this, &FHoudiniAssetComponentDetails::CloseMaterialInterfaceComboButton ) ); +} + +void +FHoudiniAssetComponentDetails::OnMaterialInterfaceSelected( + const FAssetData & AssetData, UStaticMesh * StaticMesh, + FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx ) +{ + TPairInitializer< UStaticMesh *, int32 > Pair( StaticMesh, MaterialIdx ); + TSharedPtr< SComboButton > AssetComboButton = MaterialInterfaceComboButtons[ Pair ]; + if ( AssetComboButton.IsValid() ) + { + AssetComboButton->SetIsOpen( false ); + + UObject * Object = AssetData.GetAsset(); + OnMaterialInterfaceDropped( Object, StaticMesh, HoudiniGeoPartObject, MaterialIdx ); + } +} + +void +FHoudiniAssetComponentDetails::OnMaterialInterfaceSelected( + const FAssetData & AssetData, ALandscapeProxy* Landscape, + FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx ) +{ + TPairInitializer< ALandscapeProxy *, int32 > Pair( Landscape, MaterialIdx ); + TSharedPtr< SComboButton > AssetComboButton = LandscapeMaterialInterfaceComboButtons[ Pair ]; + if ( AssetComboButton.IsValid() ) + { + AssetComboButton->SetIsOpen( false ); + + UObject * Object = AssetData.GetAsset(); + OnMaterialInterfaceDropped( Object, Landscape, HoudiniGeoPartObject, MaterialIdx ); + } +} + +void +FHoudiniAssetComponentDetails::CloseMaterialInterfaceComboButton() +{ + +} + +void +FHoudiniAssetComponentDetails::OnMaterialInterfaceBrowse( UMaterialInterface * MaterialInterface ) +{ + if ( GEditor ) + { + TArray< UObject * > Objects; + Objects.Add( MaterialInterface ); + GEditor->SyncBrowserToObjects( Objects ); + } +} + +FReply +FHoudiniAssetComponentDetails::OnResetMaterialInterfaceClicked( + UStaticMesh * StaticMesh, + FHoudiniGeoPartObject * HoudiniGeoPartObject, + int32 MaterialIdx ) +{ + bool bViewportNeedsUpdate = false; + + for ( TArray< UHoudiniAssetComponent * >::TIterator + IterComponents( HoudiniAssetComponents ); IterComponents; ++IterComponents ) + { + // Retrieve material interface which is being replaced. + UMaterialInterface * MaterialInterface = StaticMesh->StaticMaterials[ MaterialIdx ].MaterialInterface; + UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + + UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; + if ( !HoudiniAssetComponent ) + continue; + + bool bMaterialRestored = false; + FString MaterialShopName; + if ( !HoudiniAssetComponent->GetReplacementMaterialShopName( *HoudiniGeoPartObject, MaterialInterface, MaterialShopName ) ) + { + // This material was not replaced so there's no need to reset it + continue; + } + + // Remove the replacement + HoudiniAssetComponent->RemoveReplacementMaterial( *HoudiniGeoPartObject, MaterialShopName ); + + // Try to find the original assignment, if not, we'll use the default material + UMaterialInterface * AssignedMaterial = HoudiniAssetComponent->GetAssignmentMaterial(MaterialShopName); + if ( AssignedMaterial ) + MaterialInterfaceReplacement = AssignedMaterial; + + // Replace material on static mesh. + StaticMesh->Modify(); + StaticMesh->StaticMaterials[ MaterialIdx ].MaterialInterface = MaterialInterfaceReplacement; + + UStaticMeshComponent * StaticMeshComponent = HoudiniAssetComponent->LocateStaticMeshComponent( StaticMesh ); + if ( StaticMeshComponent ) + { + StaticMeshComponent->Modify(); + StaticMeshComponent->SetMaterial( MaterialIdx, MaterialInterfaceReplacement ); + + bMaterialRestored = true; + } + + TArray< UInstancedStaticMeshComponent * > InstancedStaticMeshComponents; + if ( HoudiniAssetComponent->LocateInstancedStaticMeshComponents( StaticMesh, InstancedStaticMeshComponents ) ) + { + for ( int32 Idx = 0; Idx < InstancedStaticMeshComponents.Num(); ++Idx ) + { + UInstancedStaticMeshComponent * InstancedStaticMeshComponent = InstancedStaticMeshComponents[ Idx ]; + if ( InstancedStaticMeshComponent ) + { + InstancedStaticMeshComponent->Modify(); + InstancedStaticMeshComponent->SetMaterial( MaterialIdx, MaterialInterfaceReplacement ); + + bMaterialRestored = true; + } + } + } + + if ( bMaterialRestored ) + { + HoudiniAssetComponent->UpdateEditorProperties( false ); + bViewportNeedsUpdate = true; + } + } + + if ( GEditor && bViewportNeedsUpdate ) + { + GEditor->RedrawAllViewports(); + } + + return FReply::Handled(); +} + +FReply +FHoudiniAssetComponentDetails::OnResetMaterialInterfaceClicked( + ALandscapeProxy * Landscape, FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx) +{ + bool bViewportNeedsUpdate = false; + + for ( TArray< UHoudiniAssetComponent * >::TIterator + IterComponents( HoudiniAssetComponents ); IterComponents; ++IterComponents ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; + if ( !HoudiniAssetComponent ) + continue; + + TWeakObjectPtr* FoundLandscapePtr = HoudiniAssetComponent->LandscapeComponents.Find( *HoudiniGeoPartObject ); + if ( !FoundLandscapePtr ) + continue; + + ALandscapeProxy* FoundLandscape = FoundLandscapePtr->Get(); + if ( !FoundLandscape || !FoundLandscape->IsValidLowLevel() ) + continue; + + if ( FoundLandscape != Landscape ) + continue; + + // Retrieve the material interface which is being replaced. + UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); + UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + + bool bMaterialRestored = false; + FString MaterialShopName; + if ( !HoudiniAssetComponent->GetReplacementMaterialShopName( *HoudiniGeoPartObject, MaterialInterface, MaterialShopName ) ) + { + // This material was not replaced so there's no need to reset it + continue; + } + + // Remove the replacement + HoudiniAssetComponent->RemoveReplacementMaterial( *HoudiniGeoPartObject, MaterialShopName ); + + // Try to find the original assignment, if not, we'll use the default material + UMaterialInterface * AssignedMaterial = HoudiniAssetComponent->GetAssignmentMaterial( MaterialShopName ); + if ( AssignedMaterial ) + MaterialInterfaceReplacement = AssignedMaterial; + + // Replace material on the landscape + Landscape->Modify(); + + if ( MaterialIdx == 0 ) + Landscape->LandscapeMaterial = MaterialInterfaceReplacement; + else + Landscape->LandscapeHoleMaterial = MaterialInterfaceReplacement; + + //Landscape->UpdateAllComponentMaterialInstances(); + + // As UpdateAllComponentMaterialInstances() is not accessible to us, we'll try to access the Material's UProperty + // to trigger a fake Property change event that will call the Update function... + FProperty* FoundProperty = FindFProperty< FProperty >( Landscape->GetClass(), ( MaterialIdx == 0 ) ? TEXT( "LandscapeMaterial" ) : TEXT( "LandscapeHoleMaterial" ) ); + if ( FoundProperty ) + { + FPropertyChangedEvent PropChanged( FoundProperty, EPropertyChangeType::ValueSet ); + Landscape->PostEditChangeProperty( PropChanged ); + } + else + { + // The only way to update the material for now is to recook/recreate the landscape... + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + + HoudiniAssetComponent->UpdateEditorProperties( false ); + bViewportNeedsUpdate = true; + } + + if ( GEditor && bViewportNeedsUpdate ) + { + GEditor->RedrawAllViewports(); + } + + return FReply::Handled(); +} + +void +FHoudiniAssetComponentDetails::OnHoudiniAssetDropped( UObject * InObject ) +{ + if ( InObject ) + { + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >( InObject ); + if ( HoudiniAsset && HoudiniAssetComponents.Num() > 0 ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ]; + + // Assign asset to component. + HoudiniAssetComponent->SetHoudiniAsset( HoudiniAsset ); + } + } +} + +bool +FHoudiniAssetComponentDetails::OnHoudiniAssetDraggedOver( const UObject * InObject ) const +{ + return ( InObject && InObject->IsA( UHoudiniAsset::StaticClass() ) ); +} + +const FSlateBrush * +FHoudiniAssetComponentDetails::GetHoudiniAssetThumbnailBorder() const +{ + if ( HoudiniAssetThumbnailBorder.IsValid() && HoudiniAssetThumbnailBorder->IsHovered() ) + return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailLight" ); + else + return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailShadow" ); +} + +TSharedRef< SWidget > +FHoudiniAssetComponentDetails::OnGetHoudiniAssetMenuContent() +{ + TArray< const UClass * > AllowedClasses; + AllowedClasses.Add( UHoudiniAsset::StaticClass() ); + + TArray< UFactory * > NewAssetFactories; + + UHoudiniAsset * HoudiniAsset = nullptr; + if ( HoudiniAssetComponents.Num() > 0 ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ]; + HoudiniAsset = HoudiniAssetComponent->HoudiniAsset; + } + + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData( HoudiniAsset ), true, + AllowedClasses, NewAssetFactories, OnShouldFilterHoudiniAsset, + FOnAssetSelected::CreateSP( this, &FHoudiniAssetComponentDetails::OnHoudiniAssetSelected ), + FSimpleDelegate::CreateSP( this, &FHoudiniAssetComponentDetails::CloseHoudiniAssetComboButton ) ); +} + +void +FHoudiniAssetComponentDetails::OnHoudiniAssetSelected( const FAssetData & AssetData ) +{ + if ( HoudiniAssetComboButton.IsValid() ) + { + HoudiniAssetComboButton->SetIsOpen( false ); + + UObject * Object = AssetData.GetAsset(); + OnHoudiniAssetDropped( Object ); + } +} + +void +FHoudiniAssetComponentDetails::CloseHoudiniAssetComboButton() +{ + +} + +void +FHoudiniAssetComponentDetails::OnHoudiniAssetBrowse() +{ + if ( GEditor ) + { + UHoudiniAsset * HoudiniAsset = nullptr; + if ( HoudiniAssetComponents.Num() > 0 ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ]; + HoudiniAsset = HoudiniAssetComponent->HoudiniAsset; + + if( HoudiniAsset ) + { + TArray< UObject * > Objects; + Objects.Add(HoudiniAsset); + GEditor->SyncBrowserToObjects( Objects ); + } + } + } +} + +FReply +FHoudiniAssetComponentDetails::OnResetHoudiniAssetClicked() +{ + if ( HoudiniAssetComponents.Num() > 0 ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ]; + HoudiniAssetComponent->SetHoudiniAsset( nullptr ); + } + + return FReply::Handled(); +} + +ECheckBoxState +FHoudiniAssetComponentDetails::IsCheckedComponentSettingCooking( UHoudiniAssetComponent * HoudiniAssetComponent ) const +{ + if ( HoudiniAssetComponent && HoudiniAssetComponent->bEnableCooking ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +ECheckBoxState +FHoudiniAssetComponentDetails::IsCheckedComponentSettingUploadTransform( + UHoudiniAssetComponent * HoudiniAssetComponent ) const +{ + if ( HoudiniAssetComponent && HoudiniAssetComponent->bUploadTransformsToHoudiniEngine ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +ECheckBoxState +FHoudiniAssetComponentDetails::IsCheckedComponentSettingTransformCooking( + UHoudiniAssetComponent * HoudiniAssetComponent ) const +{ + if ( HoudiniAssetComponent && HoudiniAssetComponent->bTransformChangeTriggersCooks ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +ECheckBoxState +FHoudiniAssetComponentDetails::IsCheckedComponentSettingUseHoudiniMaterials( + UHoudiniAssetComponent * HoudiniAssetComponent ) const +{ + if ( HoudiniAssetComponent && HoudiniAssetComponent->bUseHoudiniMaterials ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +ECheckBoxState +FHoudiniAssetComponentDetails::IsCheckedComponentSettingCookingTriggersDownstreamCooks( + UHoudiniAssetComponent * HoudiniAssetComponent ) const +{ + if ( HoudiniAssetComponent && HoudiniAssetComponent->bCookingTriggersDownstreamCooks ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingCooking( + ECheckBoxState NewState, + UHoudiniAssetComponent * HoudiniAssetComponent ) +{ + if ( HoudiniAssetComponent ) + HoudiniAssetComponent->bEnableCooking = ( NewState == ECheckBoxState::Checked ); +} + +void +FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingUploadTransform( + ECheckBoxState NewState, + UHoudiniAssetComponent * HoudiniAssetComponent ) +{ + if ( HoudiniAssetComponent ) + HoudiniAssetComponent->bUploadTransformsToHoudiniEngine = ( NewState == ECheckBoxState::Checked ); +} + +void +FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingTransformCooking( + ECheckBoxState NewState, + UHoudiniAssetComponent * HoudiniAssetComponent ) +{ + if ( HoudiniAssetComponent ) + HoudiniAssetComponent->bTransformChangeTriggersCooks = ( NewState == ECheckBoxState::Checked ); +} + +void +FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingUseHoudiniMaterials( + ECheckBoxState NewState, + UHoudiniAssetComponent * HoudiniAssetComponent ) +{ + if ( HoudiniAssetComponent ) + HoudiniAssetComponent->bUseHoudiniMaterials = ( NewState == ECheckBoxState::Checked ); +} + +void +FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingCookingTriggersDownstreamCooks( + ECheckBoxState NewState, + UHoudiniAssetComponent* HoudiniAssetComponent ) +{ + if ( HoudiniAssetComponent ) + HoudiniAssetComponent->bCookingTriggersDownstreamCooks = ( NewState == ECheckBoxState::Checked ); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h new file mode 100644 index 00000000..2cd26e95 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h @@ -0,0 +1,299 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Produced by: + * Mykola Konyk + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + */ + +#pragma once + +#include "CoreMinimal.h" +#include "HoudiniGeoPartObject.h" +#include "DetailCategoryBuilder.h" +#include "IDetailCustomization.h" +#include "ContentBrowserDelegates.h" +#include "Materials/MaterialInterface.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Input/SComboButton.h" + + +struct FGeometry; +struct FSlateBrush; +struct FPointerEvent; +struct FAssetData; + +class UStaticMesh; +class IDetailLayoutBuilder; +class UHoudiniAssetComponent; +class ALandscape; +class ALandscapeProxy; + + +/** Hashing function for our pair. **/ +uint32 GetTypeHash( TPair< UStaticMesh *, int32 > Pair ); +uint32 GetTypeHash( TPair< ALandscape *, int32 > Pair ); +uint32 GetTypeHash(TPair< ALandscapeProxy *, int32 > Pair); + +class FHoudiniAssetComponentDetails : public IDetailCustomization +{ + friend class FHoudiniParameterDetails; + + public: + + /** Constructor. **/ + FHoudiniAssetComponentDetails(); + + /** Destructor. **/ + virtual ~FHoudiniAssetComponentDetails(); + + /** IDetailCustomization methods. **/ + public: + + virtual void CustomizeDetails( IDetailLayoutBuilder & DetailBuilder ) override; + + public: + + /** Create an instance of this detail layout class. **/ + static TSharedRef< IDetailCustomization > MakeInstance(); + + private: + + /** Helper method used to create widgets for generated static meshes. **/ + void CreateStaticMeshAndMaterialWidgets( IDetailCategoryBuilder & DetailCategoryBuilder ); + + /** Helper method used to create widget for Houdini asset. **/ + void CreateHoudiniAssetWidget( IDetailCategoryBuilder & DetailCategoryBuilder ); + + /** Gets the border brush to show around thumbnails, changes when the user hovers on it. **/ + const FSlateBrush * GetStaticMeshThumbnailBorder( UStaticMesh * StaticMesh ) const; + const FSlateBrush * GetLandscapeThumbnailBorder(ALandscapeProxy * Landscape ) const; + const FSlateBrush * GetMaterialInterfaceThumbnailBorder( UStaticMesh * StaticMesh, int32 MaterialIdx ) const; + const FSlateBrush * GetMaterialInterfaceThumbnailBorder(ALandscapeProxy * Landscape, int32 MaterialIdx ) const; + + /** Handler for when static mesh thumbnail is double clicked. We open editor in this case. **/ + FReply OnThumbnailDoubleClick( + const FGeometry & InMyGeometry, const FPointerEvent & InMouseEvent, UObject * Object ); + + /** Handler for bake individual static mesh action. **/ + FReply OnBakeStaticMesh( UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent ); + + /** Handler for changing the mesh bake name */ + void OnBakeNameCommited( const FText&, ETextCommit::Type, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject ); + + FReply OnRemoveBakingBaseNameOverride( UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject ); + + /** Handler for baking an individual Landscape. **/ + FReply OnBakeLandscape(ALandscapeProxy * Landscape, UHoudiniAssetComponent * HoudiniAssetComponent ); + + /** Handler for bake all static meshes action. **/ + FReply OnBakeAllGeneratedMeshes(); + + /** Handler for recook action. **/ + FReply OnRecookAsset(); + + /** Handler for rebuild action. **/ + FReply OnRebuildAsset(); + + /** Handler for reset action. **/ + FReply OnResetAsset(); + + /** Handler for fetch log action. **/ + FReply OnFetchCookLog(); + + /** Handler for bake blueprint action. **/ + FReply OnBakeBlueprint(); + + /** Handler for the bake and replace with blueprint action. **/ + FReply OnBakeBlueprintReplace(); + + /** Handler for bake to actors action **/ + FReply OnBakeToActors(); + + /** Handler for the bake and replace with actors action **/ + FReply OnBakeToActorsReplace(); + + /** Handler for bake to outliner input action **/ + FReply OnBakeToInput(); + + /** Handler for bake to foliage action **/ + FReply OnBakeToFoliage(); + + /** Handler for bake and replace with foliage action **/ + FReply OnBakeToFoliageReplace(); + + /** Handler for change the bake folder button */ + void OnBakeFolderSelected( const FString& Folder ); + + /** Get the text for display of baking folder */ + FText GetBakeFolderText() const; + + /** Handler for changing the temporary cook folder */ + void OnCookFolderSelected(const FString& Folder); + + /** Get the text for displaying the temporary cook folder */ + FText GetTempCookFolderText() const; + + /** Handler for fetch asset help action. **/ + FReply OnFetchAssetHelp( UHoudiniAssetComponent * HoudiniAssetComponent ); + + /** Delegate used to detect if valid object has been dragged and dropped. **/ + bool OnMaterialInterfaceDraggedOver( const UObject * InObject ) const; + + /** Delegate used when valid material has been drag and dropped. **/ + void OnMaterialInterfaceDropped( + UObject * InObject, UStaticMesh * StaticMesh, + FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx ); + void OnMaterialInterfaceDropped( + UObject * InObject, ALandscapeProxy * Landscape, + FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx ); + + /** Construct drop down menu content for material. **/ + TSharedRef< SWidget > OnGetMaterialInterfaceMenuContent( + UMaterialInterface * MaterialInterface, UStaticMesh * StaticMesh, + FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx ); + TSharedRef< SWidget > OnGetMaterialInterfaceMenuContent( + UMaterialInterface * MaterialInterface, ALandscapeProxy * Landscape, + FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx ); + + /** Delegate for handling selection in content browser. **/ + void OnMaterialInterfaceSelected( + const FAssetData & AssetData, UStaticMesh * StaticMesh, + FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx ); + void OnMaterialInterfaceSelected( + const FAssetData & AssetData, ALandscapeProxy * Landscape, + FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx ); + + /** Closes the combo button. **/ + void CloseMaterialInterfaceComboButton(); + + /** Browse to material interface. **/ + void OnMaterialInterfaceBrowse( UMaterialInterface * MaterialInterface ); + + /** Handler for reset material interface button. **/ + FReply OnResetMaterialInterfaceClicked( + UStaticMesh * StaticMesh, FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx ); + FReply OnResetMaterialInterfaceClicked( + ALandscapeProxy * Landscape, FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx ); + + /** Delegate used when Houdini asset has been drag and dropped. **/ + void OnHoudiniAssetDropped( UObject * InObject ); + + /** Delegate used to detect if valid object has been dragged and dropped. **/ + bool OnHoudiniAssetDraggedOver( const UObject * InObject ) const; + + /** Gets the border brush to show around thumbnails, changes when the user hovers on it. **/ + const FSlateBrush * GetHoudiniAssetThumbnailBorder() const; + + /** Construct drop down menu content for Houdini asset. **/ + TSharedRef< SWidget > OnGetHoudiniAssetMenuContent(); + + /** Delegate for handling selection in content browser. **/ + void OnHoudiniAssetSelected( const FAssetData & AssetData ); + + /** Closes the combo button. **/ + void CloseHoudiniAssetComboButton(); + + /** Browse to Houdini asset. **/ + void OnHoudiniAssetBrowse(); + + /** Handler for reset Houdini asset button. **/ + FReply OnResetHoudiniAssetClicked(); + + /** Checks whether checkbox is checked. **/ + ECheckBoxState IsCheckedComponentSettingCooking( + UHoudiniAssetComponent * HoudiniAssetComponent ) const; + + ECheckBoxState IsCheckedComponentSettingUploadTransform( + UHoudiniAssetComponent * HoudiniAssetComponent ) const; + + ECheckBoxState IsCheckedComponentSettingTransformCooking( + UHoudiniAssetComponent * HoudiniAssetComponent ) const; + + ECheckBoxState IsCheckedComponentSettingUseHoudiniMaterials( + UHoudiniAssetComponent * HoudiniAssetComponent ) const; + + ECheckBoxState IsCheckedComponentSettingCookingTriggersDownstreamCooks( + UHoudiniAssetComponent * HoudiniAssetComponent ) const; + + /** Handle change in Checkbox. **/ + void CheckStateChangedComponentSettingCooking( + ECheckBoxState NewState, UHoudiniAssetComponent * HoudiniAssetComponent ); + + void CheckStateChangedComponentSettingUploadTransform( + ECheckBoxState NewState, + UHoudiniAssetComponent * HoudiniAssetComponent ); + + void CheckStateChangedComponentSettingTransformCooking( + ECheckBoxState NewState, + UHoudiniAssetComponent * HoudiniAssetComponent ); + + void CheckStateChangedComponentSettingUseHoudiniMaterials( + ECheckBoxState NewState, + UHoudiniAssetComponent * HoudiniAssetComponent ); + + void CheckStateChangedComponentSettingCookingTriggersDownstreamCooks( + ECheckBoxState NewState, + UHoudiniAssetComponent * HoudiniAssetComponent ); + + private: + + /** Components which are being customized. **/ + TArray< UHoudiniAssetComponent * > HoudiniAssetComponents; + + /** Map of static meshes and corresponding thumbnail borders. **/ + TMap< UStaticMesh *, TSharedPtr< SBorder > > StaticMeshThumbnailBorders; + + /** Map of static meshes / material indices to combo elements. **/ + TMap< TPair< UStaticMesh *, int32 >, TSharedPtr > MaterialInterfaceComboButtons; + + /** Map of static meshes / material indices to thumbnail borders. **/ + TMap< TPair< UStaticMesh *, int32 >, TSharedPtr< SBorder > > MaterialInterfaceThumbnailBorders; + + /** Map of Landscapes and corresponding thumbnail borders. **/ + TMap< ALandscapeProxy *, TSharedPtr< SBorder > > LandscapeThumbnailBorders; + + /** Map of Landscapes / material indices to combo elements. **/ + TMap< TPair< ALandscapeProxy *, int32 >, TSharedPtr > LandscapeMaterialInterfaceComboButtons; + + /** Map of Landscapes / material indices to thumbnail borders. **/ + TMap< TPair< ALandscapeProxy *, int32 >, TSharedPtr< SBorder > > LandscapeMaterialInterfaceThumbnailBorders; + + /** Delegate for filtering material interfaces. **/ + FOnShouldFilterAsset OnShouldFilterMaterialInterface; + + /** Thumbnail border used by Houdini asset. **/ + TSharedPtr< SBorder > HoudiniAssetThumbnailBorder; + + /** Combo element used by Houdini asset. **/ + TSharedPtr< SComboButton > HoudiniAssetComboButton; + + /** Delegate for filtering Houdini assets. **/ + FOnShouldFilterAsset OnShouldFilterHoudiniAsset; + + /** Whether baking option is enabled. **/ + bool bEnableBaking; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp new file mode 100644 index 00000000..db47035d --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp @@ -0,0 +1,216 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Produced by: + * Mykola Konyk + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + */ + +#include "HoudiniAssetFactory.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniAsset.h" +#include "EditorFramework/AssetImportData.h" +#include "Misc/FileHelper.h" + +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetFactory::UHoudiniAssetFactory( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{ + // This factory is responsible for manufacturing HoudiniEngine assets. + SupportedClass = UHoudiniAsset::StaticClass(); + + // This factory does not manufacture new objects from scratch. + bCreateNew = false; + + // This factory will open the editor for each new object. + bEditAfterNew = false; + + // This factory will import objects from files. + bEditorImport = true; + + // Factory does not import objects from text. + bText = false; + + // Add supported formats. + Formats.Add( TEXT( "otl;Houdini Engine Asset" ) ); + Formats.Add( TEXT( "otllc;Houdini Engine Limited Commercial Asset" ) ); + Formats.Add( TEXT( "otlnc;Houdini Engine Non-Commercial Asset" ) ); + Formats.Add( TEXT( "hda;Houdini Engine Asset" ) ); + Formats.Add( TEXT( "hdalc;Houdini Engine Limited Commercial Asset" ) ); + Formats.Add( TEXT( "hdanc;Houdini Engine Non-Commercial Asset" ) ); + Formats.Add( TEXT( "hdalibrary;Houdini Engine Expanded Asset" ) ); +} + +bool +UHoudiniAssetFactory::DoesSupportClass( UClass * Class ) +{ + return Class == SupportedClass; +} + +FText +UHoudiniAssetFactory::GetDisplayName() const +{ + return LOCTEXT( "HoudiniAssetFactoryDescription", "Houdini Engine Asset" ); +} + +UObject * +UHoudiniAssetFactory::FactoryCreateBinary( + UClass * InClass, UObject* InParent, FName InName, EObjectFlags Flags, + UObject * Context, const TCHAR * Type, const uint8 *& Buffer, + const uint8 * BufferEnd, FFeedbackContext * Warn ) +{ + // Broadcast notification that a new asset is being imported. + GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, InName, Type); + + // Create a new asset. + UHoudiniAsset * HoudiniAsset = NewObject< UHoudiniAsset >( InParent, InName, Flags ); + HoudiniAsset->CreateAsset( Buffer, BufferEnd, UFactory::GetCurrentFilename() ); + + // Create reimport information. + UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; + if ( !AssetImportData ) + { + AssetImportData = NewObject< UAssetImportData >( HoudiniAsset, UAssetImportData::StaticClass() ); + HoudiniAsset->AssetImportData = AssetImportData; + } + + AssetImportData->Update( UFactory::GetCurrentFilename() ); + + // Broadcast notification that the new asset has been imported. + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, HoudiniAsset); + + return HoudiniAsset; +} + +bool +UHoudiniAssetFactory::CanReimport( UObject * Obj, TArray< FString > & OutFilenames ) +{ + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >( Obj ); + if ( HoudiniAsset ) + { + UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; + if ( AssetImportData ) + OutFilenames.Add( AssetImportData->GetFirstFilename() ); + else + OutFilenames.Add( TEXT( "" ) ); + + return true; + } + + return false; +} + +void +UHoudiniAssetFactory::SetReimportPaths( UObject * Obj, const TArray< FString > & NewReimportPaths ) +{ + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >( Obj ); + if ( HoudiniAsset && ( 1 == NewReimportPaths.Num() ) ) + HoudiniAsset->AssetImportData->UpdateFilenameOnly( NewReimportPaths[ 0 ] ); +} + +EReimportResult::Type +UHoudiniAssetFactory::Reimport( UObject * Obj ) +{ + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >( Obj ); + if ( HoudiniAsset && HoudiniAsset->AssetImportData ) + { + // Make sure file is valid and exists. + const FString & Filename = HoudiniAsset->AssetImportData->GetFirstFilename(); + + if ( !Filename.Len() || IFileManager::Get().FileSize( *Filename ) == INDEX_NONE ) + return EReimportResult::Failed; + + if ( UFactory::StaticImportObject( + HoudiniAsset->GetClass(), HoudiniAsset->GetOuter(), *HoudiniAsset->GetName(), + RF_Public | RF_Standalone, *Filename, NULL, this ) ) + { + HOUDINI_LOG_MESSAGE( TEXT( "Houdini Asset reimported successfully." ) ); + + if ( HoudiniAsset->GetOuter() ) + HoudiniAsset->GetOuter()->MarkPackageDirty(); + else + HoudiniAsset->MarkPackageDirty(); + + return EReimportResult::Succeeded; + } + } + + HOUDINI_LOG_MESSAGE( TEXT( "Houdini Asset reimport has failed." ) ); + return EReimportResult::Failed; +} + + + +UObject* +UHoudiniAssetFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) +{ + // "houdini.hdalibrary" files (expanded hda / hda folder) need a special treatment, + // but ".hda" files can be loaded normally + FString FileExtension = FPaths::GetExtension( Filename ); + if ( FileExtension.Compare( TEXT( "hdalibrary" ), ESearchCase::IgnoreCase ) != 0 ) + return Super::FactoryCreateFile( InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled ); + + // Make sure the file name is sections.list + FString NameOfFile = FPaths::GetBaseFilename( Filename ); + if ( NameOfFile.Compare( TEXT( "houdini" ), ESearchCase::IgnoreCase ) != 0 ) + { + HOUDINI_LOG_ERROR( TEXT( "Failed to load file '%s'. File is not a valid extended HDA." ), *Filename ); + return nullptr; + } + + // Make sure that the proper .list file is loaded + FString PathToFile = FPaths::GetPath( Filename ); + if ( PathToFile.Find( TEXT( ".hda" ) ) != ( PathToFile.Len() - 4 ) ) + { + HOUDINI_LOG_ERROR( TEXT( "Failed to load file '%s'. File is not a valid extended HDA." ), *Filename ); + return nullptr; + } + + FString NewFilename = PathToFile; + FString NewFileNameNoHDA = FPaths::GetBaseFilename( PathToFile ); + FName NewIname = FName( *NewFileNameNoHDA ); + FString NewFileExtension = FPaths::GetExtension( NewFilename ); + + // load as binary + TArray< uint8 > Data; + if ( !FFileHelper::LoadFileToArray( Data, *Filename ) ) + { + HOUDINI_LOG_ERROR( TEXT( "Failed to load file '%s' to array" ), *Filename ); + return nullptr; + } + + Data.Add( 0 ); + ParseParms( Parms ); + const uint8* Ptr = &Data[ 0 ]; + + return FactoryCreateBinary( InClass, InParent, NewIname, Flags, nullptr, *NewFileExtension, Ptr, Ptr + Data.Num() - 1, Warn ); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h new file mode 100644 index 00000000..c84ec279 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Produced by: + * Mykola Konyk + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + */ + +#pragma once +#include "EditorReimportHandler.h" +#if WITH_EDITOR +#include "Factories/Factory.h" +#endif +#include "HoudiniAssetFactory.generated.h" + +class UClass; +class UObject; +class FFeedbackContext; + +UCLASS( config = Editor ) +class UHoudiniAssetFactory : public UFactory, public FReimportHandler +{ + GENERATED_UCLASS_BODY() + + /** UFactory methods. **/ + private: + + virtual bool DoesSupportClass( UClass * Class ) override; + virtual FText GetDisplayName() const override; + virtual UObject * FactoryCreateBinary( + UClass * InClass, UObject * InParent, FName InName, EObjectFlags Flags, + UObject * Context, const TCHAR * Type, const uint8 *& Buffer, const uint8 * BufferEnd, + FFeedbackContext * Warn ) override; + + /** FReimportHandler methods. **/ + public: + + virtual bool CanReimport( UObject * Obj, TArray< FString > & OutFilenames ) override; + virtual void SetReimportPaths( UObject * Obj, const TArray< FString > & NewReimportPaths ) override; + virtual EReimportResult::Type Reimport( UObject * Obj ) override; + + virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) override; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetLogWidget.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetLogWidget.cpp new file mode 100644 index 00000000..1dc1743b --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetLogWidget.cpp @@ -0,0 +1,59 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Damian Campeanu, Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniAssetLogWidget.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Widgets/Input/SMultiLineEditableTextBox.h" +#include "EditorStyleSet.h" + +void +SHoudiniAssetLogWidget::Construct( const FArguments & InArgs ) +{ + this->ChildSlot + [ + SNew( SBorder ) + .BorderImage( FEditorStyle::GetBrush( TEXT( "Menu.Background" ) ) ) + .Content() + [ + SNew( SScrollBox ) + + SScrollBox::Slot() + [ + SNew( SMultiLineEditableTextBox ) + .Text( FText::FromString( InArgs._LogText ) ) + .AutoWrapText( true ) + .IsReadOnly( true ) + ] + ] + ]; +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetLogWidget.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetLogWidget.h new file mode 100644 index 00000000..5b2025ef --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetLogWidget.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Produced by: + * Mykola Konyk + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + */ + +#pragma once +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" + + +class SHoudiniAssetLogWidget : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS( SHoudiniAssetLogWidget ) + : _LogText( TEXT( "" ) ) + {} + + SLATE_ARGUMENT( FString, LogText ) + SLATE_END_ARGS() + + /** Widget construct. **/ + void Construct( const FArguments & InArgs ); +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetTypeActions.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetTypeActions.cpp new file mode 100644 index 00000000..79a76734 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetTypeActions.cpp @@ -0,0 +1,371 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Produced by: + * Mykola Konyk + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + */ + +#include "HoudiniAssetTypeActions.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngine.h" +#include "SHoudiniToolPalette.h" +#include "ThumbnailRendering/SceneThumbnailInfo.h" +#include "EditorFramework/AssetImportData.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "EditorReimportHandler.h" +#include "HAL/FileManager.h" + +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FText +FHoudiniAssetTypeActions::GetName() const +{ + return LOCTEXT( "HoudiniAssetTypeActions", "HoudiniAsset" ); +} + +FColor +FHoudiniAssetTypeActions::GetTypeColor() const +{ + return FColor( 255, 165, 0 ); +} + +UClass * +FHoudiniAssetTypeActions::GetSupportedClass() const +{ + return UHoudiniAsset::StaticClass(); +} + +uint32 +FHoudiniAssetTypeActions::GetCategories() +{ + return EAssetTypeCategories::Misc; +} + +UThumbnailInfo * +FHoudiniAssetTypeActions::GetThumbnailInfo( UObject * Asset ) const +{ + if (!Asset || Asset->IsPendingKill()) + return nullptr; + + UHoudiniAsset * HoudiniAsset = CastChecked< UHoudiniAsset >( Asset ); + UThumbnailInfo * ThumbnailInfo = HoudiniAsset->ThumbnailInfo; + if ( !ThumbnailInfo ) + { + // If we have no thumbnail information, construct it. + ThumbnailInfo = NewObject< USceneThumbnailInfo >( HoudiniAsset, USceneThumbnailInfo::StaticClass() ); + HoudiniAsset->ThumbnailInfo = ThumbnailInfo; + } + + return ThumbnailInfo; +} + +bool +FHoudiniAssetTypeActions::HasActions( const TArray< UObject * > & InObjects ) const +{ + return true; +} + +void +FHoudiniAssetTypeActions::GetActions( const TArray< UObject * > & InObjects, class FMenuBuilder & MenuBuilder ) +{ + bool ValidObjects = false; + TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets; + if ( InObjects.Num() > 0 ) + { + HoudiniAssets = GetTypedWeakObjectPtrs< UHoudiniAsset >( InObjects ); + ValidObjects = true; + } + + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT( "HoudiniAssetTypeActions", "HoudiniAsset_Reimport", "Reimport" ), + NSLOCTEXT( "HoudiniAssetTypeActions", "HoudiniAsset_ReimportTooltip", "Reimport selected Houdini Assets." ), + FSlateIcon( StyleSetName, "HoudiniEngine.HoudiniEngineLogo" ), + FUIAction( + FExecuteAction::CreateSP( this, &FHoudiniAssetTypeActions::ExecuteReimport, HoudiniAssets ), + FCanExecuteAction::CreateLambda( [=] { return ValidObjects; } ) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAll", "Rebuild All Instances"), + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAllTooltip", "Reimports and rebuild all instances of the selected Houdini Assets."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FHoudiniAssetTypeActions::ExecuteRebuildAllInstances, HoudiniAssets), + FCanExecuteAction::CreateLambda( [=] { return ValidObjects; } ) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT( "HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorer", "Find Source"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorerTooltip", + "Opens explorer at the location of this asset." ), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo" ), + FUIAction( + FExecuteAction::CreateSP( this, &FHoudiniAssetTypeActions::ExecuteFindInExplorer, HoudiniAssets ), + FCanExecuteAction::CreateLambda( [=] { return ValidObjects; } ) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudini", "Open in Houdini"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudiniTooltip", + "Opens the selected asset in Houdini."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FHoudiniAssetTypeActions::ExecuteOpenInHoudini, HoudiniAssets), + FCanExecuteAction::CreateLambda( [=] { return ( HoudiniAssets.Num() == 1 ); } ) + ) + ); + + MenuBuilder.AddMenuSeparator(); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingle", "Apply to the current selection (single input)"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingleTooltip", + "Applies the selected asset to the current world selection. All the selected object will be assigned to the first input."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FHoudiniAssetTypeActions::ExecuteApplyOpSingle, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT( "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMulti", "Apply to the current selection (multiple inputs )"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMultiTooltip", + "Applies the selected asset to the current world selection. Each selected object will be assigned to its own input (one object per input)."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP( this, &FHoudiniAssetTypeActions::ExecuteApplyOpMulti, HoudiniAssets ), + FCanExecuteAction::CreateLambda( [=] { return (HoudiniAssets.Num() == 1); } ) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatch", "Batch Apply to the current selection"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatchTooltip", + "Batch apply the selected asset to the current world selection. An instance of the selected Houdini asset will be created for each selected object."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP( this, &FHoudiniAssetTypeActions::ExecuteApplyBatch, HoudiniAssets ), + FCanExecuteAction::CreateLambda( [=] { return (HoudiniAssets.Num() == 1); } ) + ) + ); +} + +TSharedRef +FHoudiniAssetTypeActions::AddLevelEditorMenuExtenders(TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets) +{ + FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); + TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); + + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + TSharedRef Extender = MakeShareable(new FExtender); + Extender->AddMenuExtension( + "ActorAsset", + EExtensionHook::After, + LevelEditorCommandBindings, + FMenuExtensionDelegate::CreateLambda( [ this, HoudiniAssets, StyleSetName ]( FMenuBuilder& MenuBuilder ) + { + MenuBuilder.AddMenuEntry( + NSLOCTEXT( "HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorer", "Find Source HDA" ), + NSLOCTEXT( "HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorerTooltip", "Opens an explorer at the location of this actor's source HDA file." ), + FSlateIcon( StyleSetName, "HoudiniEngine.HoudiniEngineLogo" ), + FUIAction( + FExecuteAction::CreateSP( this, &FHoudiniAssetTypeActions::ExecuteFindInExplorer, HoudiniAssets ), + FCanExecuteAction::CreateLambda([=] { return ( HoudiniAssets.Num() > 0 ); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT( "HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudini", "Open HDA in Houdini" ), + NSLOCTEXT( "HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudiniTooltip", "Opens the selected asset's source HDA file in Houdini." ), + FSlateIcon( StyleSetName, "HoudiniEngine.HoudiniEngineLogo" ), + FUIAction( + FExecuteAction::CreateSP( this, &FHoudiniAssetTypeActions::ExecuteOpenInHoudini, HoudiniAssets ), + FCanExecuteAction::CreateLambda([=] { return ( HoudiniAssets.Num() == 1 ); } ) + ) + ); + } ) + ); + + return Extender; +} + +void +FHoudiniAssetTypeActions::ExecuteReimport( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets ) +{ + for ( auto ObjIt = HoudiniAssets.CreateConstIterator(); ObjIt; ++ObjIt ) + { + UHoudiniAsset * HoudiniAsset = ( *ObjIt ).Get(); + if ( HoudiniAsset ) + FReimportManager::Instance()->Reimport( HoudiniAsset, true ); + } +} + +void +FHoudiniAssetTypeActions::ExecuteRebuildAllInstances(TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets) +{ + // Reimports and then rebuild all instances of the asset + for (auto ObjIt = HoudiniAssets.CreateConstIterator(); ObjIt; ++ObjIt) + { + UHoudiniAsset * HoudiniAsset = ( *ObjIt ).Get(); + if ( !HoudiniAsset ) + continue; + + // Reimports the asset + FReimportManager::Instance()->Reimport( HoudiniAsset, true ); + + // Rebuilds all instances of that asset in the scene + for ( TObjectIterator Itr; Itr; ++Itr ) + { + UHoudiniAssetComponent * Component = *Itr; + if ( Component && ( Component->GetHoudiniAsset() == HoudiniAsset ) ) + { + Component->StartTaskAssetRebuildManual(); + } + } + } +} + +void +FHoudiniAssetTypeActions::ExecuteFindInExplorer( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets ) +{ + for ( auto ObjIt = HoudiniAssets.CreateConstIterator(); ObjIt; ++ObjIt ) + { + UHoudiniAsset * HoudiniAsset = ( *ObjIt ).Get(); + if ( HoudiniAsset && HoudiniAsset->AssetImportData ) + { + const FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); + if ( SourceFilePath.Len() && IFileManager::Get().FileSize( *SourceFilePath ) != INDEX_NONE ) + return FPlatformProcess::ExploreFolder( *SourceFilePath ); + } + } +} + +void +FHoudiniAssetTypeActions::ExecuteOpenInHoudini( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets ) +{ + if ( HoudiniAssets.Num() != 1 ) + return; + + UHoudiniAsset * HoudiniAsset = HoudiniAssets[0].Get(); + if ( !HoudiniAsset || !( HoudiniAsset->AssetImportData ) ) + return; + + FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); + if ( !SourceFilePath.Len() || IFileManager::Get().FileSize(*SourceFilePath) == INDEX_NONE ) + return; + + if ( !FPaths::FileExists( SourceFilePath ) ) + return; + + // We'll need to modify the file name for expanded .hda + FString FileExtension = FPaths::GetExtension( SourceFilePath ); + if ( FileExtension.Compare( TEXT( "hdalibrary" ), ESearchCase::IgnoreCase ) == 0 ) + { + // the .hda directory is what we're actually interested in loading + SourceFilePath = FPaths::GetPath( SourceFilePath ); + } + + if ( FPaths::IsRelative( SourceFilePath ) ) + FPaths::ConvertRelativePathToFull(SourceFilePath); + + // Then open the HDA file in Houdini + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + FString HoudiniLocation = LibHAPILocation + "/hview"; + + FPlatformProcess::CreateProc( + HoudiniLocation.GetCharArray().GetData(), + SourceFilePath.GetCharArray().GetData(), + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr ); +} + +void +FHoudiniAssetTypeActions::ExecuteApplyOpSingle( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets ) +{ + return ExecuteApplyAssetToSelection( HoudiniAssets, EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE ); +} + +void +FHoudiniAssetTypeActions::ExecuteApplyOpMulti( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets ) +{ + return ExecuteApplyAssetToSelection( HoudiniAssets, EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI ); +} +void +FHoudiniAssetTypeActions::ExecuteApplyBatch( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets ) +{ + return ExecuteApplyAssetToSelection( HoudiniAssets, EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH ); +} + +void +FHoudiniAssetTypeActions::ExecuteApplyAssetToSelection( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets, EHoudiniToolType Type ) +{ + if ( HoudiniAssets.Num() != 1 ) + return; + + UHoudiniAsset * HoudiniAsset = HoudiniAssets[ 0 ].Get(); + if ( !HoudiniAsset || !( HoudiniAsset->AssetImportData ) ) + return; + + // Creating a temporary tool for the selected asset + TSoftObjectPtr HoudiniAssetPtr( HoudiniAsset ); + FHoudiniTool HoudiniTool( + HoudiniAssetPtr, + FText::FromString( HoudiniAsset->GetName() ), + Type, + EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY, + FText(), + NULL, + FString(), + false, + FFilePath(), + FHoudiniToolDirectory(), + FString() ); + + SHoudiniToolPalette::InstantiateHoudiniTool( &HoudiniTool ); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetTypeActions.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetTypeActions.h new file mode 100644 index 00000000..667a7f51 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniAssetTypeActions.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Produced by: + * Mykola Konyk + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + */ + +#pragma once + +#include "AssetTypeActions_Base.h" + +class UClass; +class UObject; +class UHoudiniAsset; +class UThumbnailInfo; + +enum class EHoudiniToolType: uint8; + +class FHoudiniAssetTypeActions : public FAssetTypeActions_Base +{ + /** FAssetTypeActions_Base methods. **/ + public: + + virtual FText GetName() const override; + virtual FColor GetTypeColor() const override; + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + virtual UThumbnailInfo * GetThumbnailInfo( UObject * Asset) const override; + virtual bool HasActions(const TArray< UObject * > & InObjects) const override; + virtual void GetActions(const TArray< UObject * > & InObjects, class FMenuBuilder & MenuBuilder ) override; + + TSharedRef AddLevelEditorMenuExtenders( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets ); + + protected: + + /** Handler for reimport option. **/ + void ExecuteReimport( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets ); + + /** Handler for rebuild all option **/ + void ExecuteRebuildAllInstances( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets ); + + /** Handler for find in explorer option */ + void ExecuteFindInExplorer( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets ); + + /** Handler for the open in Houdini option */ + void ExecuteOpenInHoudini( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets ); + + /** Handler to apply the current hda to the current world selection (single input) */ + void ExecuteApplyOpSingle( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets ); + + /** Handler to apply the current hda to the current world selection (multi input) */ + void ExecuteApplyOpMulti( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets ); + + /** Handler to batch apply the current hda to the current world selection */ + void ExecuteApplyBatch( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets ); + + void ExecuteApplyAssetToSelection( TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets, EHoudiniToolType Type ); +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp new file mode 100644 index 00000000..e1310e45 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp @@ -0,0 +1,2134 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniEngineEditor.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniAsset.h" +#include "HoudiniEngine.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniHandleComponentVisualizer.h" +#include "HoudiniHandleComponent.h" +#include "HoudiniSplineComponentVisualizer.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniAssetComponentDetails.h" +#include "HoudiniRuntimeSettingsDetails.h" +#include "HoudiniAssetTypeActions.h" +#include "HoudiniAssetBroker.h" +#include "HoudiniAssetActorFactory.h" +#include "HoudiniEngineBakeUtils.h" + +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" +#include "Engine/ObjectLibrary.h" +#include "EditorDirectories.h" +#include "Styling/SlateStyleRegistry.h" +#include "SHoudiniToolPalette.h" +#include "IPlacementModeModule.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "AssetRegistryModule.h" +#include "Engine/Selection.h" +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" +#include "SlateOptMacros.h" +#include "HAL/FileManager.h" +#include "HAL/PlatformFilemanager.h" +#include "Dom/JsonObject.h" +#include "Misc/FileHelper.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" +#include "Interfaces/IPluginManager.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define TTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) +#define OTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) + +TSharedPtr FHoudiniEngineStyle::StyleSet = nullptr; + +TSharedPtr +FHoudiniEngineStyle::Get() +{ + return StyleSet; +} + +FName +FHoudiniEngineStyle::GetStyleSetName() +{ + static FName HoudiniStyleName(TEXT("HoudiniEngineStyle")); + return HoudiniStyleName; +} + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +void +FHoudiniEngineStyle::Initialize() +{ + // Only register the StyleSet once + if ( StyleSet.IsValid() ) + return; + + StyleSet = MakeShareable( new FSlateStyleSet( GetStyleSetName() ) ); + StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate")); + StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); + + // Note, these sizes are in Slate Units. + // Slate Units do NOT have to map to pixels. + const FVector2D Icon5x16(5.0f, 16.0f); + const FVector2D Icon8x4(8.0f, 4.0f); + const FVector2D Icon8x8(8.0f, 8.0f); + const FVector2D Icon10x10(10.0f, 10.0f); + const FVector2D Icon12x12(12.0f, 12.0f); + const FVector2D Icon12x16(12.0f, 16.0f); + const FVector2D Icon14x14(14.0f, 14.0f); + const FVector2D Icon16x16(16.0f, 16.0f); + const FVector2D Icon20x20(20.0f, 20.0f); + const FVector2D Icon22x22(22.0f, 22.0f); + const FVector2D Icon24x24(24.0f, 24.0f); + const FVector2D Icon25x25(25.0f, 25.0f); + const FVector2D Icon32x32(32.0f, 32.0f); + const FVector2D Icon40x40(40.0f, 40.0f); + const FVector2D Icon64x64(64.0f, 64.0f); + const FVector2D Icon36x24(36.0f, 24.0f); + const FVector2D Icon128x128(128.0f, 128.0f); + + static FString IconsDir = FHoudiniEngineEditor::GetHoudiniEnginePluginDir() / TEXT("Content/Icons/"); + StyleSet->Set( + "HoudiniEngine.HoudiniEngineLogo", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set( + "ClassIcon.HoudiniAssetActor", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set( + "HoudiniEngine.HoudiniEngineLogo40", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_40.png"), Icon40x40)); + + StyleSet->Set( + "ClassIcon.HoudiniAsset", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set( + "ClassThumbnail.HoudiniAsset", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_128.png"), Icon64x64)); + + //FSlateImageBrush* HoudiniLogo16 = new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16); + StyleSet->Set("HoudiniEngine.SaveHIPFile", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine.ReportBug", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine.OpenInHoudini", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine.CleanUpTempFolder", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine.BakeAllAssets", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine.PauseAssetCooking", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine.RestartSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + // We need some colors from Editor Style & this is the only way to do this at the moment + const FSlateColor DefaultForeground = FEditorStyle::GetSlateColor("DefaultForeground"); + const FSlateColor InvertedForeground = FEditorStyle::GetSlateColor("InvertedForeground"); + const FSlateColor SelectorColor = FEditorStyle::GetSlateColor("SelectorColor"); + const FSlateColor SelectionColor = FEditorStyle::GetSlateColor("SelectionColor"); + const FSlateColor SelectionColor_Inactive = FEditorStyle::GetSlateColor("SelectionColor_Inactive"); + + const FTableRowStyle &NormalTableRowStyle = FEditorStyle::Get().GetWidgetStyle("TableView.Row"); + StyleSet->Set( + "HoudiniEngine.TableRow", FTableRowStyle( NormalTableRowStyle) + .SetEvenRowBackgroundBrush( FSlateNoResource() ) + .SetEvenRowBackgroundHoveredBrush( IMAGE_BRUSH( "Common/Selection", Icon8x8, FLinearColor( 1.0f, 1.0f, 1.0f, 0.1f ) ) ) + .SetOddRowBackgroundBrush( FSlateNoResource() ) + .SetOddRowBackgroundHoveredBrush( IMAGE_BRUSH( "Common/Selection", Icon8x8, FLinearColor( 1.0f, 1.0f, 1.0f, 0.1f ) ) ) + .SetSelectorFocusedBrush( BORDER_BRUSH( "Common/Selector", FMargin( 4.f / 16.f ), SelectorColor ) ) + .SetActiveBrush( IMAGE_BRUSH( "Common/Selection", Icon8x8, SelectionColor ) ) + .SetActiveHoveredBrush( IMAGE_BRUSH( "Common/Selection", Icon8x8, SelectionColor ) ) + .SetInactiveBrush( IMAGE_BRUSH( "Common/Selection", Icon8x8, SelectionColor_Inactive ) ) + .SetInactiveHoveredBrush( IMAGE_BRUSH( "Common/Selection", Icon8x8, SelectionColor_Inactive ) ) + .SetTextColor( DefaultForeground ) + .SetSelectedTextColor( InvertedForeground ) + ); + + // Normal Text + const FTextBlockStyle& NormalText = FEditorStyle::Get().GetWidgetStyle("NormalText"); + StyleSet->Set( + "HoudiniEngine.ThumbnailText", FTextBlockStyle(NormalText) + .SetFont(TTF_CORE_FONT("Fonts/Roboto-Regular", 9)) + .SetColorAndOpacity(FSlateColor::UseForeground()) + .SetShadowOffset(FVector2D::ZeroVector) + .SetShadowColorAndOpacity(FLinearColor::Black) + .SetHighlightColor(FLinearColor(0.02f, 0.3f, 0.0f)) + .SetHighlightShape(BOX_BRUSH("Common/TextBlockHighlightShape", FMargin(3.f / 8.f))) + ); + + StyleSet->Set("HoudiniEngine.ThumbnailShadow", new BOX_BRUSH("ContentBrowser/ThumbnailShadow", FMargin(4.0f / 64.0f))); + StyleSet->Set("HoudiniEngine.ThumbnailBackground", new IMAGE_BRUSH("Common/ClassBackground_64x", FVector2D(64.f, 64.f), FLinearColor(0.75f, 0.75f, 0.75f, 1.0f))); + + // Register Slate style. + FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); +}; + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +#undef IMAGE_BRUSH +#undef BOX_BRUSH +#undef BORDER_BRUSH +#undef TTF_FONT +#undef TTF_CORE_FONT +#undef OTF_FONT +#undef OTF_CORE_FONT + +void +FHoudiniEngineStyle::Shutdown() +{ + if (StyleSet.IsValid()) + { + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); + ensure(StyleSet.IsUnique()); + StyleSet.Reset(); + } +} + + +const FName +FHoudiniEngineEditor::HoudiniEngineEditorAppIdentifier = FName( TEXT( "HoudiniEngineEditorApp" ) ); + +IMPLEMENT_MODULE( FHoudiniEngineEditor, HoudiniEngineEditor ); +DEFINE_LOG_CATEGORY( LogHoudiniEngineEditor ); + +FHoudiniEngineEditor * +FHoudiniEngineEditor::HoudiniEngineEditorInstance = nullptr; + +FHoudiniEngineEditor & +FHoudiniEngineEditor::Get() +{ + return *HoudiniEngineEditorInstance; +} + +bool +FHoudiniEngineEditor::IsInitialized() +{ + return FHoudiniEngineEditor::HoudiniEngineEditorInstance != nullptr; +} + +FHoudiniEngineEditor::FHoudiniEngineEditor() + : CurrentHoudiniToolDirIndex(-1), + LastHoudiniAssetComponentUndoObject( nullptr ) +{} + +void +FHoudiniEngineEditor::StartupModule() +{ + HOUDINI_LOG_MESSAGE( TEXT( "Starting the Houdini Engine Editor module." ) ); + + // Create style set. + FHoudiniEngineStyle::Initialize(); + + // Register asset type actions. + RegisterAssetTypeActions(); + + // Register asset brokers. + RegisterAssetBrokers(); + + // Register component visualizers. + RegisterComponentVisualizers(); + + // Register detail presenters. + RegisterDetails(); + + // Register actor factories. + RegisterActorFactories(); + + // Extends the file menu. + ExtendMenu(); + + // Extend the World Outliner Menu + AddLevelViewportMenuExtender(); + + // Adds the custom console commands + RegisterConsoleCommands(); + + // Register global undo / redo callbacks. + RegisterForUndo(); + +#ifdef HOUDINI_MODE + // Register editor modes + RegisterModes(); +#endif + + RegisterPlacementModeExtensions(); + + // Store the instance. + FHoudiniEngineEditor::HoudiniEngineEditorInstance = this; + + HOUDINI_LOG_MESSAGE( TEXT("Houdini Engine Editor module startup complete." ) ); +} + +void +FHoudiniEngineEditor::ShutdownModule() +{ + HOUDINI_LOG_MESSAGE( TEXT( "Shutting down the Houdini Engine Editor module." ) ); + + // Remove the level viewport Menu extender% + RemoveLevelViewportMenuExtender(); + + // Unregister asset type actions. + UnregisterAssetTypeActions(); + + // Unregister asset brokers. + UnregisterAssetBrokers(); + + // Unregister detail presenters. + UnregisterDetails(); + + // Unregister our component visualizers. + UnregisterComponentVisualizers(); + + // Unregister global undo / redo callbacks. + UnregisterForUndo(); + +#ifdef HOUDINI_MODE + UnregisterModes(); +#endif + + UnregisterPlacementModeExtensions(); + + // Unregister the styleset + FHoudiniEngineStyle::Shutdown(); + + HOUDINI_LOG_MESSAGE( TEXT("Houdini Engine Editor module shutdown complete." ) ); +} + +void +FHoudiniEngineEditor::RegisterComponentVisualizers() +{ + if ( GUnrealEd ) + { + if ( !SplineComponentVisualizer.IsValid() ) + { + SplineComponentVisualizer = MakeShareable( new FHoudiniSplineComponentVisualizer ); + + GUnrealEd->RegisterComponentVisualizer( + UHoudiniSplineComponent::StaticClass()->GetFName(), + SplineComponentVisualizer + ); + + SplineComponentVisualizer->OnRegister(); + } + + if ( !HandleComponentVisualizer.IsValid() ) + { + HandleComponentVisualizer = MakeShareable( new FHoudiniHandleComponentVisualizer ); + + GUnrealEd->RegisterComponentVisualizer( + UHoudiniHandleComponent::StaticClass()->GetFName(), + HandleComponentVisualizer + ); + + HandleComponentVisualizer->OnRegister(); + } + } +} + +void +FHoudiniEngineEditor::UnregisterComponentVisualizers() +{ + if ( GUnrealEd ) + { + if ( SplineComponentVisualizer.IsValid() ) + GUnrealEd->UnregisterComponentVisualizer( UHoudiniSplineComponent::StaticClass()->GetFName() ); + + if ( HandleComponentVisualizer.IsValid() ) + GUnrealEd->UnregisterComponentVisualizer( UHoudiniHandleComponent::StaticClass()->GetFName() ); + } +} + +void +FHoudiniEngineEditor::RegisterDetails() +{ + FPropertyEditorModule & PropertyModule = FModuleManager::LoadModuleChecked< FPropertyEditorModule >( "PropertyEditor" ); + + // Register details presenter for our component type and runtime settings. + PropertyModule.RegisterCustomClassLayout( + TEXT( "HoudiniAssetComponent" ), + FOnGetDetailCustomizationInstance::CreateStatic( &FHoudiniAssetComponentDetails::MakeInstance ) ); + PropertyModule.RegisterCustomClassLayout( + TEXT( "HoudiniRuntimeSettings" ), + FOnGetDetailCustomizationInstance::CreateStatic( &FHoudiniRuntimeSettingsDetails::MakeInstance ) ); +} + +void +FHoudiniEngineEditor::UnregisterDetails() +{ + if ( FModuleManager::Get().IsModuleLoaded( "PropertyEditor" ) ) + { + FPropertyEditorModule & PropertyModule = + FModuleManager::LoadModuleChecked( "PropertyEditor" ); + + PropertyModule.UnregisterCustomClassLayout( TEXT( "HoudiniAssetComponent" ) ); + PropertyModule.UnregisterCustomClassLayout( TEXT( "HoudiniRuntimeSettings" ) ); + } +} + +void +FHoudiniEngineEditor::RegisterAssetTypeActions() +{ + // Create and register asset type actions for Houdini asset. + IAssetTools& AssetTools = FModuleManager::LoadModuleChecked< FAssetToolsModule >( "AssetTools" ).Get(); + RegisterAssetTypeAction( AssetTools, MakeShareable( new FHoudiniAssetTypeActions() ) ); +} + +void +FHoudiniEngineEditor::UnregisterAssetTypeActions() +{ + // Unregister asset type actions we have previously registered. + if ( FModuleManager::Get().IsModuleLoaded( "AssetTools" ) ) + { + IAssetTools & AssetTools = FModuleManager::GetModuleChecked< FAssetToolsModule >( "AssetTools" ).Get(); + + for ( int32 Index = 0; Index < AssetTypeActions.Num(); ++Index ) + AssetTools.UnregisterAssetTypeActions( AssetTypeActions[ Index ].ToSharedRef() ); + + AssetTypeActions.Empty(); + } +} + +void +FHoudiniEngineEditor::RegisterAssetTypeAction( IAssetTools & AssetTools, TSharedRef< IAssetTypeActions > Action ) +{ + AssetTools.RegisterAssetTypeActions( Action ); + AssetTypeActions.Add( Action ); +} + +void +FHoudiniEngineEditor::RegisterAssetBrokers() +{ + // Create and register broker for Houdini asset. + HoudiniAssetBroker = MakeShareable( new FHoudiniAssetBroker() ); + FComponentAssetBrokerage::RegisterBroker( HoudiniAssetBroker, UHoudiniAssetComponent::StaticClass(), true, true ); +} + +void +FHoudiniEngineEditor::UnregisterAssetBrokers() +{ + if ( UObjectInitialized() ) + { + // Unregister broker. + FComponentAssetBrokerage::UnregisterBroker( HoudiniAssetBroker ); + } +} + +void +FHoudiniEngineEditor::RegisterActorFactories() +{ + if ( GEditor ) + { + UHoudiniAssetActorFactory * HoudiniAssetActorFactory = + NewObject< UHoudiniAssetActorFactory >( GetTransientPackage(), UHoudiniAssetActorFactory::StaticClass() ); + + GEditor->ActorFactories.Add( HoudiniAssetActorFactory ); + } +} + +void +FHoudiniEngineEditor::ExtendMenu() +{ + if ( !IsRunningCommandlet() ) + { + // We need to add/bind the UI Commands to their functions first + BindMenuCommands(); + + // Extend main menu, we will add Houdini section. + MainMenuExtender = MakeShareable( new FExtender ); + MainMenuExtender->AddMenuExtension( + "FileLoadAndSave", EExtensionHook::After, HEngineCommands, + FMenuExtensionDelegate::CreateRaw( this, &FHoudiniEngineEditor::AddHoudiniMenuExtension ) ); + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked< FLevelEditorModule >( "LevelEditor" ); + LevelEditorModule.GetMenuExtensibilityManager()->AddExtender( MainMenuExtender ); + } +} + +void +FHoudiniEngineEditor::AddHoudiniMenuExtension( FMenuBuilder & MenuBuilder ) +{ + MenuBuilder.BeginSection( "Houdini", LOCTEXT( "HoudiniLabel", "Houdini Engine" ) ); + + MenuBuilder.AddMenuEntry( FHoudiniEngineCommands::Get().OpenInHoudini ); + MenuBuilder.AddMenuEntry( FHoudiniEngineCommands::Get().SaveHIPFile ); + MenuBuilder.AddMenuEntry( FHoudiniEngineCommands::Get().ReportBug ); + MenuBuilder.AddMenuEntry( FHoudiniEngineCommands::Get().CleanUpTempFolder ); + MenuBuilder.AddMenuEntry( FHoudiniEngineCommands::Get().BakeAllAssets ); + MenuBuilder.AddMenuEntry( FHoudiniEngineCommands::Get().PauseAssetCooking ); + MenuBuilder.AddMenuEntry( FHoudiniEngineCommands::Get().RestartSession); + + MenuBuilder.EndSection(); +} + +void +FHoudiniEngineEditor::BindMenuCommands() +{ + HEngineCommands = MakeShareable( new FUICommandList ); + + FHoudiniEngineCommands::Register(); + const FHoudiniEngineCommands& Commands = FHoudiniEngineCommands::Get(); + + HEngineCommands->MapAction( + Commands.OpenInHoudini, + FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::OpenInHoudini ), + FCanExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CanOpenInHoudini ) ); + + HEngineCommands->MapAction( + Commands.SaveHIPFile, + FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::SaveHIPFile ), + FCanExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CanSaveHIPFile ) ); + + HEngineCommands->MapAction( + Commands.ReportBug, + FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::ReportBug ), + FCanExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CanReportBug ) ); + + HEngineCommands->MapAction( + Commands.CleanUpTempFolder, + FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CleanUpTempFolder ), + FCanExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CanCleanUpTempFolder ) ); + + HEngineCommands->MapAction( + Commands.BakeAllAssets, + FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::BakeAllAssets ), + FCanExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CanBakeAllAssets ) ); + + HEngineCommands->MapAction( + Commands.PauseAssetCooking, + FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::PauseAssetCooking ), + FCanExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CanPauseAssetCooking ), + FIsActionChecked::CreateRaw( this, &FHoudiniEngineEditor::IsAssetCookingPaused ) ); + + HEngineCommands->MapAction( + Commands.RestartSession, + FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::RestartSession), + FCanExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CanRestartSession ) ); + + // Non menu command (used for shortcuts only) + HEngineCommands->MapAction( + Commands.CookSelec, + FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::RecookSelection ), + FCanExecuteAction() ); + + HEngineCommands->MapAction( + Commands.RebuildSelec, + FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::RebuildSelection ), + FCanExecuteAction() ); + + HEngineCommands->MapAction( + Commands.BakeSelec, + FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::BakeSelection ), + FCanExecuteAction() ); + + // Append the command to the editor module + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked< FLevelEditorModule >("LevelEditor"); + LevelEditorModule.GetGlobalLevelEditorActions()->Append(HEngineCommands.ToSharedRef()); +} + +void +FHoudiniEngineEditor::RegisterConsoleCommands() +{ + // Register corresponding console commands + static FAutoConsoleCommand CCmdOpen = FAutoConsoleCommand( + TEXT("Houdini.Open"), + TEXT("Open the scene in Houdini."), + FConsoleCommandDelegate::CreateRaw( this, &FHoudiniEngineEditor::OpenInHoudini ) ); + + static FAutoConsoleCommand CCmdSave = FAutoConsoleCommand( + TEXT("Houdini.Save"), + TEXT("Save the current Houdini scene to a hip file."), + FConsoleCommandDelegate::CreateRaw(this, &FHoudiniEngineEditor::SaveHIPFile ) ); + + static FAutoConsoleCommand CCmdBake = FAutoConsoleCommand( + TEXT("Houdini.BakeAll"), + TEXT("Bakes and replaces with blueprints all Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateRaw(this, &FHoudiniEngineEditor::BakeAllAssets ) ); + + static FAutoConsoleCommand CCmdClean = FAutoConsoleCommand( + TEXT("Houdini.Clean"), + TEXT("Cleans up unused/unreferenced Houdini Engine temporary files."), + FConsoleCommandDelegate::CreateRaw(this, &FHoudiniEngineEditor::CleanUpTempFolder ) ); + + static FAutoConsoleCommand CCmdPause = FAutoConsoleCommand( + TEXT( "Houdini.Pause"), + TEXT( "Pauses Houdini Engine Asset cooking." ), + FConsoleCommandDelegate::CreateRaw( this, &FHoudiniEngineEditor::PauseAssetCooking ) ); + + // Additional console only commands + static FAutoConsoleCommand CCmdCookAll = FAutoConsoleCommand( + TEXT("Houdini.CookAll"), + TEXT("Re-cooks all Houdini Engine Asset Actors in the current level."), + FConsoleCommandDelegate::CreateRaw( this, &FHoudiniEngineEditor::RecookAllAssets ) ); + + static FAutoConsoleCommand CCmdRebuildAll = FAutoConsoleCommand( + TEXT("Houdini.RebuildAll"), + TEXT("Rebuilds all Houdini Engine Asset Actors in the current level."), + FConsoleCommandDelegate::CreateRaw( this, &FHoudiniEngineEditor::RebuildAllAssets ) ); + + static FAutoConsoleCommand CCmdCookSelec = FAutoConsoleCommand( + TEXT("Houdini.Cook"), + TEXT("Re-cooks selected Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateRaw( this, &FHoudiniEngineEditor::RecookSelection ) ); + + static FAutoConsoleCommand CCmdRebuildSelec = FAutoConsoleCommand( + TEXT("Houdini.Rebuild"), + TEXT("Rebuilds selected Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateRaw( this, &FHoudiniEngineEditor::RebuildSelection ) ); + + static FAutoConsoleCommand CCmdBakeSelec = FAutoConsoleCommand( + TEXT("Houdini.Bake"), + TEXT("Bakes and replaces with blueprints selected Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateRaw( this, &FHoudiniEngineEditor::BakeSelection ) ); + + static FAutoConsoleCommand CCmdRestartSession = FAutoConsoleCommand( + TEXT("Houdini.RestartSession"), + TEXT("Restart the current Houdini Session."), + FConsoleCommandDelegate::CreateRaw(this, &FHoudiniEngineEditor::RestartSession ) ); +} + +bool +FHoudiniEngineEditor::CanSaveHIPFile() const +{ + return FHoudiniEngine::IsInitialized(); +} + +void +FHoudiniEngineEditor::SaveHIPFile() +{ + IDesktopPlatform * DesktopPlatform = FDesktopPlatformModule::Get(); + if ( DesktopPlatform && FHoudiniEngineUtils::IsInitialized() ) + { + TArray< FString > SaveFilenames; + bool bSaved = false; + void * ParentWindowWindowHandle = NULL; + + IMainFrameModule & MainFrameModule = FModuleManager::LoadModuleChecked< IMainFrameModule >( TEXT( "MainFrame" ) ); + const TSharedPtr< SWindow > & MainFrameParentWindow = MainFrameModule.GetParentWindow(); + if ( MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid() ) + ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle(); + + bSaved = DesktopPlatform->SaveFileDialog( + ParentWindowWindowHandle, + NSLOCTEXT( "SaveHIPFile", "SaveHIPFile", "Saves a .hip file of the current Houdini scene." ).ToString(), + *( FEditorDirectories::Get().GetLastDirectory( ELastDirectory::GENERIC_EXPORT ) ), + TEXT( "" ), + TEXT( "Houdini HIP file|*.hip" ), + EFileDialogFlags::None, + SaveFilenames ); + + if ( bSaved && SaveFilenames.Num() ) + { + // Add a slate notification + FString Notification = TEXT("Saving internal Houdini scene..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Saved Houdini scene to %s"), *SaveFilenames[ 0 ] ); + + // Get first path. + std::string HIPPathConverted( TCHAR_TO_UTF8(*SaveFilenames[0]) ); + + // Save HIP file through Engine. + FHoudiniApi::SaveHIPFile( FHoudiniEngine::Get().GetSession(), HIPPathConverted.c_str(), false ); + } + } +} + +bool +FHoudiniEngineEditor::CanOpenInHoudini() const +{ + return FHoudiniEngine::IsInitialized(); +} + +void +FHoudiniEngineEditor::OpenInHoudini() +{ + if ( !FHoudiniEngine::IsInitialized() ) + return; + + // First, saves the current scene as a hip file + // Creates a proper temporary file name + FString UserTempPath = FPaths::CreateTempFilename( + FPlatformProcess::UserTempDir(), + TEXT( "HoudiniEngine" ), TEXT( ".hip" ) ); + + // Save HIP file through Engine. + std::string TempPathConverted( TCHAR_TO_UTF8( *UserTempPath ) ); + FHoudiniApi::SaveHIPFile( + FHoudiniEngine::Get().GetSession(), + TempPathConverted.c_str(), false); + + if ( !FPaths::FileExists( UserTempPath ) ) + return; + + // Add a slate notification + FString Notification = TEXT( "Opening scene in Houdini..." ); + FHoudiniEngineUtils::CreateSlateNotification( Notification ); + + // ... and a log message + HOUDINI_LOG_MESSAGE( TEXT("Opened scene in Houdini.") ); + + // Add quotes to the path to avoid issues with spaces + UserTempPath = TEXT("\"") + UserTempPath + TEXT("\""); + // Then open the hip file in Houdini + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); + FPlatformProcess::CreateProc( + *HoudiniLocation, + *UserTempPath, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr ); + + // Unfortunately, LaunchFileInDefaultExternalApplication doesn't seem to be working properly + //FPlatformProcess::LaunchFileInDefaultExternalApplication( UserTempPath.GetCharArray().GetData(), nullptr, ELaunchVerb::Open ); +} + +void +FHoudiniEngineEditor::ReportBug() +{ + FPlatformProcess::LaunchURL( HAPI_UNREAL_BUG_REPORT_URL, nullptr, nullptr ); +} + +bool +FHoudiniEngineEditor::CanReportBug() const +{ + return FHoudiniEngine::IsInitialized(); +} + +void +FHoudiniEngineEditor::RegisterForUndo() +{ + if ( GUnrealEd ) + GUnrealEd->RegisterForUndo( this ); +} + +void +FHoudiniEngineEditor::UnregisterForUndo() +{ + if ( GUnrealEd ) + GUnrealEd->UnregisterForUndo( this ); +} + +/** Registers placement mode extensions. */ +void +FHoudiniEngineEditor::RegisterPlacementModeExtensions() +{ + // Load custom houdini tools + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + check( HoudiniRuntimeSettings ); + + if ( HoudiniRuntimeSettings->bHidePlacementModeHoudiniTools ) + return; + + FPlacementCategoryInfo Info( + LOCTEXT( "HoudiniCategoryName", "Houdini Engine" ), + "HoudiniEngine", + TEXT( "PMHoudiniEngine" ), + 25 + ); + Info.CustomGenerator = []() -> TSharedRef { return SNew( SHoudiniToolPalette ); }; + + IPlacementModeModule::Get().RegisterPlacementCategory( Info ); +} + +FString +FHoudiniEngineEditor::GetDefaultHoudiniToolIcon() +{ + return FHoudiniEngineEditor::GetHoudiniEnginePluginDir() / TEXT("Content/Icons/icon_houdini_logo_40.png"); +} + +FString +FHoudiniEngineEditor::GetHoudiniEnginePluginDir() +{ + FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); + if ( FPaths::DirectoryExists(EnginePluginDir) ) + return EnginePluginDir; + + FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); + if ( FPaths::DirectoryExists(ProjectPluginDir) ) + return ProjectPluginDir; + + TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); + FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; + if ( FPaths::DirectoryExists(PluginBaseDir) ) + return PluginBaseDir; + + HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); + + return EnginePluginDir; +} + +void +FHoudiniEngineEditor::UnregisterPlacementModeExtensions() +{ + if ( IPlacementModeModule::IsAvailable() ) + { + IPlacementModeModule::Get().UnregisterPlacementCategory( "HoudiniEngine" ); + } + + HoudiniTools.Empty(); +} + + + +bool +FHoudiniEngineEditor::MatchesContext(const FTransactionContext& InContext, const TArray>& TransactionObjects) const +{ + if (InContext.Context == TEXT(HOUDINI_MODULE_EDITOR) || InContext.Context == TEXT(HOUDINI_MODULE_RUNTIME)) + { + // Check if we care about the undo/redo + for (const TPair& TransactionObjectPair : TransactionObjects) + { + LastHoudiniAssetComponentUndoObject = Cast< UHoudiniAssetComponent >(TransactionObjectPair.Key); + if ( LastHoudiniAssetComponentUndoObject ) + return true; + } + } + + LastHoudiniAssetComponentUndoObject = nullptr; + return false; +} + +void +FHoudiniEngineEditor::PostUndo( bool bSuccess ) +{ + if ( LastHoudiniAssetComponentUndoObject && bSuccess ) + { + LastHoudiniAssetComponentUndoObject->UpdateEditorProperties( false ); + LastHoudiniAssetComponentUndoObject = nullptr; + } +} + +void +FHoudiniEngineEditor::PostRedo( bool bSuccess ) +{ + if ( LastHoudiniAssetComponentUndoObject && bSuccess ) + { + LastHoudiniAssetComponentUndoObject->UpdateEditorProperties( false ); + LastHoudiniAssetComponentUndoObject = nullptr; + } +} + +void +FHoudiniEngineEditor::CleanUpTempFolder() +{ + // Add a slate notification + FString Notification = TEXT("Cleaning up Houdini Engine temporary folder"); + FHoudiniEngineUtils::CreateSlateNotification( Notification ); + + // Get Runtime settings to get the Temp Cook Folder + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!HoudiniRuntimeSettings) + return; + + FString TempCookFolder = HoudiniRuntimeSettings->TemporaryCookFolder.ToString(); + + // The Asset registry will help us finding if the content of the asset is referenced + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + + int32 DeletedCount = 0; + bool bDidDeleteAsset = true; + while ( bDidDeleteAsset ) + { + // To correctly clean the temp folder, we need to iterate multiple times, because some of the temp assets + // might be referenced by other temp assets.. (ie Textures are referenced by Materials) + // We'll stop looking for assets to delete when no deletion occured. + bDidDeleteAsset = false; + + // The Object library will list all UObjects found in the TempFolder + auto ObjectLibrary = UObjectLibrary::CreateLibrary( UObject::StaticClass(), false, true ); + ObjectLibrary->LoadAssetDataFromPath( TempCookFolder ); + + // Get all the assets found in the TEMP folder + TArray AssetDataList; + ObjectLibrary->GetAssetDataList( AssetDataList ); + + // All the assets we're going to delete + TArray AssetDataToDelete; + + for ( FAssetData Data : AssetDataList ) + { + UPackage* CurrentPackage = Data.GetPackage(); + if ( !CurrentPackage || CurrentPackage->IsPendingKill() ) + continue; + + // Do not try to delete the package if it's referenced anywhere + TArray ReferenceNames; + AssetRegistryModule.Get().GetReferencers(CurrentPackage->GetFName(), ReferenceNames, EAssetRegistryDependencyType::All ); + if (ReferenceNames.Num() > 0) + continue; + + bool bAssetDataSafeToDelete = true; + TArray AssetsInPackage; + AssetRegistryModule.Get().GetAssetsByPackageName( CurrentPackage->GetFName(), AssetsInPackage ); + for ( const auto& AssetInfo : AssetsInPackage ) + { + // Check if the objects contained in the package are referenced by something that won't be garbage collected (*including* the undo buffer) + UObject* AssetInPackage = AssetInfo.GetAsset(); + if (!AssetInPackage || AssetInPackage->IsPendingKill()) + continue; + + FReferencerInformationList ReferencesIncludingUndo; + bool bReferencedInMemoryOrUndoStack = IsReferenced( AssetInPackage, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, &ReferencesIncludingUndo ); + if ( !bReferencedInMemoryOrUndoStack ) + continue; + + // We do have external references, check if the external references are in our ObjectToDelete list + // If they are, we can delete the asset because its references are going to be deleted as well. + for ( auto ExtRef : ReferencesIncludingUndo.ExternalReferences ) + { + UObject* Outer = ExtRef.Referencer->GetOuter(); + if (!Outer || Outer->IsPendingKill()) + continue; + + bool bOuterFound = false; + for ( auto DataToDelete : AssetDataToDelete ) + { + if ( DataToDelete.GetPackage() == Outer ) + { + bOuterFound = true; + break; + } + else if ( DataToDelete.GetAsset() == Outer ) + { + bOuterFound = true; + break; + } + } + + // We have at least one reference that's not going to be deleted, we have to keep the asset + if ( !bOuterFound ) + { + bAssetDataSafeToDelete = false; + break; + } + } + } + + if ( bAssetDataSafeToDelete ) + AssetDataToDelete.Add( Data ); + } + + // Nothing to delete + if ( AssetDataToDelete.Num() <= 0 ) + break; + + int32 CurrentDeleted = ObjectTools::DeleteAssets( AssetDataToDelete, false ); + /* + // DO NOT FORCE DELETE!! + // This will actually cause more problems than it solves + if ( CurrentDeleted <= 0 ) + { + // Normal deletion failed... Try to force delete the objects? + TArray ObjectsToDelete; + for (int i = 0; i < AssetDataToDelete.Num(); i++) + { + const FAssetData& AssetData = AssetDataToDelete[i]; + UObject *ObjectToDelete = AssetData.GetAsset(); + // Assets can be loaded even when their underlying type/class no longer exists... + if ( ObjectToDelete && !ObjectToDelete->IsPendingKill() ) + { + ObjectsToDelete.Add(ObjectToDelete); + } + } + + CurrentDeleted = ObjectTools::ForceDeleteObjects(ObjectsToDelete, false); + } + */ + + if ( CurrentDeleted > 0 ) + { + DeletedCount += CurrentDeleted; + bDidDeleteAsset = true; + } + } + + // Add a slate notification + Notification = TEXT("Deleted ") + FString::FromInt( DeletedCount ) + TEXT(" temporary files."); + FHoudiniEngineUtils::CreateSlateNotification( Notification ); + + // ... and a log message + HOUDINI_LOG_MESSAGE( TEXT("Deleted %d temporary files."), DeletedCount ); +} + +bool +FHoudiniEngineEditor::CanCleanUpTempFolder() const +{ + return FHoudiniEngine::IsInitialized(); +} + +void +FHoudiniEngineEditor::BakeAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Baking all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification( Notification ); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 BakedCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + { + HOUDINI_LOG_ERROR( TEXT( "Failed to bake a Houdini Asset in the scene! - Invalid Houdini Asset Component" ) ); + continue; + } + + if ( !HoudiniAssetComponent->IsComponentValid() ) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + if ( AssetName != "Default__HoudiniAssetActor" ) + HOUDINI_LOG_ERROR( TEXT( "Failed to bake a Houdini Asset in the scene! - %s is invalid" ), *AssetName ); + continue; + } + + // If component is not cooking or instancing, we can bake blueprint. + if ( HoudiniAssetComponent->IsInstantiatingOrCooking() ) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is actively instantiating or cooking"), *AssetName); + continue; + } + + bool bSuccess = false; + bool BakeToBlueprints = true; + if (BakeToBlueprints) + { + if (FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithBlueprint(HoudiniAssetComponent)) + bSuccess = true; + } + else + { + if (FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(HoudiniAssetComponent, false)) + bSuccess = true; + } + + if ( bSuccess ) + BakedCount++; + } + + // Add a slate notification + Notification = TEXT("Baked ") + FString::FromInt( BakedCount ) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification( Notification ); + + // ... and a log message + HOUDINI_LOG_MESSAGE( TEXT("Baked all %d Houdini assets in the current level."), BakedCount ); +} + +bool +FHoudiniEngineEditor::CanBakeAllAssets() const +{ + if ( !FHoudiniEngine::IsInitialized() ) + return false; + + return true; +} + +void +FHoudiniEngineEditor::PauseAssetCooking() +{ + // Revert the global flag + bool CurrentEnableCookingGlobal = !FHoudiniEngine::Get().GetEnableCookingGlobal(); + FHoudiniEngine::Get().SetEnableCookingGlobal( CurrentEnableCookingGlobal ); + + // Add a slate notification + FString Notification = TEXT("Houdini Engine cooking paused"); + if ( CurrentEnableCookingGlobal ) + Notification = TEXT("Houdini Engine cooking resumed"); + FHoudiniEngineUtils::CreateSlateNotification( Notification ); + + // ... and a log message + if ( CurrentEnableCookingGlobal ) + HOUDINI_LOG_MESSAGE( TEXT("Houdini Engine cooking resumed.") ); + else + HOUDINI_LOG_MESSAGE( TEXT("Houdini Engine cooking paused.") ); + + if ( !CurrentEnableCookingGlobal ) + return; + + // If we are unpausing, tick each asset component to "update" them + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsValidLowLevel()) + { + HOUDINI_LOG_ERROR( TEXT("Failed to cook a Houdini Asset in the scene!") ); + continue; + } + + HoudiniAssetComponent->StartHoudiniTicking(); + } +} + +bool +FHoudiniEngineEditor::CanPauseAssetCooking() +{ + if ( !FHoudiniEngine::IsInitialized() ) + return false; + + return true; +} + +bool +FHoudiniEngineEditor::IsAssetCookingPaused() +{ + return !FHoudiniEngine::Get().GetEnableCookingGlobal(); +} + +void +FHoudiniEngineEditor::RecookSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditor::GetWorldSelection( WorldSelection, true ); + if ( SelectedHoudiniAssets <= 0 ) + { + HOUDINI_LOG_MESSAGE( TEXT( "No Houdini Assets selected in the world outliner" ) ); + return; + } + + // Add a slate notification + FString Notification = TEXT("Cooking selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification( Notification ); + + // Iterates over the selection and cook the assets if they're in a valid state + int32 CookedCount = 0; + for ( int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++ ) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast( WorldSelection[ Idx ] ); + if ( !HoudiniAssetActor || HoudiniAssetActor->IsPendingKill() ) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsComponentValid() ) + continue; + + HoudiniAssetComponent->StartTaskAssetCookingManual(); + CookedCount++; + } + + // Add a slate notification + Notification = TEXT("Re-cooked ") + FString::FromInt( CookedCount ) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE( TEXT("Re-cooked %d selected Houdini assets."), CookedCount ); +} + +void +FHoudiniEngineEditor::RecookAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Cooking all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 CookedCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsComponentValid()) + continue; + + HoudiniAssetComponent->StartTaskAssetCookingManual(); + CookedCount++; + } + + // Add a slate notification + Notification = TEXT("Re-cooked ") + FString::FromInt( CookedCount ) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification( Notification ); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Re-cooked %d Houdini assets in the current level."), CookedCount ); +} + +void +FHoudiniEngineEditor::RebuildAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Re-building all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 RebuiltCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsComponentValid() ) + continue; + + HoudiniAssetComponent->StartTaskAssetRebuildManual(); + RebuiltCount++; + } + + // Add a slate notification + Notification = TEXT("Rebuilt ") + FString::FromInt( RebuiltCount ) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification( Notification ); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d Houdini assets in the current level."), RebuiltCount ); +} + +void +FHoudiniEngineEditor::RebuildSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditor::GetWorldSelection( WorldSelection, true ); + if ( SelectedHoudiniAssets <= 0 ) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Rebuilding selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification( Notification ); + + // Iterates over the selection and rebuilds the assets if they're in a valid state + int32 RebuiltCount = 0; + for ( int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++ ) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast( WorldSelection[ Idx ] ); + if ( !HoudiniAssetActor || HoudiniAssetActor->IsPendingKill() ) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsComponentValid() ) + continue; + + HoudiniAssetComponent->StartTaskAssetRebuildManual(); + RebuiltCount++; + } + + // Add a slate notification + Notification = TEXT("Rebuilt ") + FString::FromInt( RebuiltCount ) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification( Notification ); + + // ... and a log message + HOUDINI_LOG_MESSAGE( TEXT("Rebuilt %d selected Houdini assets."), RebuiltCount ); +} + +void +FHoudiniEngineEditor::BakeSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditor::GetWorldSelection( WorldSelection, true ); + if ( SelectedHoudiniAssets <= 0 ) + { + HOUDINI_LOG_MESSAGE( TEXT("No Houdini Assets selected in the world outliner") ); + return; + } + + // Add a slate notification + FString Notification = TEXT("Baking selected Houdini Asset Actors in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification( Notification ); + + // Iterates over the selection and rebuilds the assets if they're in a valid state + int32 BakedCount = 0; + for ( int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++ ) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast( WorldSelection[ Idx ] ); + if ( !HoudiniAssetActor || HoudiniAssetActor->IsPendingKill() ) + continue; + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + { + HOUDINI_LOG_ERROR( TEXT("Failed to export a Houdini Asset in the scene!") ); + continue; + } + + if ( !HoudiniAssetComponent->IsComponentValid() ) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + HOUDINI_LOG_ERROR(TEXT("Failed to export Houdini Asset: %s in the scene!"), *AssetName); + continue; + } + + // If component is not cooking or instancing, we can bake blueprint. + if ( !HoudiniAssetComponent->IsInstantiatingOrCooking() ) + { + if ( FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithBlueprint( HoudiniAssetComponent ) ) + BakedCount++; + } + } + + // Add a slate notification + Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification( Notification ); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); +} + +// Recentre HoudiniAsset actors' pivots to their input / cooked static-mesh average centre. +void FHoudiniEngineEditor::RecentreSelection() +{ +#if WITH_EDITOR + //Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditor::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Recentering selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and cook the assets if they're in a valid state + int32 RecentreCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || !HoudiniAssetComponent->IsComponentValid()) + continue; + + // Get the average centre of all the created Static Meshes + FVector AverageBoundsCentre = FVector::ZeroVector; + int32 NumBounds = 0; + const FVector CurrentLocation = HoudiniAssetComponent->GetComponentLocation(); + { + //Check Static Meshes + TArray StaticMeshes; + StaticMeshes.Reserve(16); + HoudiniAssetComponent->GetAllUsedStaticMeshes(StaticMeshes); + + //Get average centre of all the static meshes. + for (const UStaticMesh* pMesh : StaticMeshes) + { + if (!pMesh) + continue; + + //to world space + AverageBoundsCentre += (pMesh->GetBounds().Origin + CurrentLocation); + NumBounds++; + } + } + + //Check Inputs + if (0 == NumBounds) + { + const TArray< UHoudiniAssetInput* >& AssetInputs = HoudiniAssetComponent->GetInputs(); + for (const UHoudiniAssetInput* pInput : AssetInputs) + { + if (!pInput || pInput->IsPendingKill()) + continue; + + // to world space + FBox Bounds = pInput->GetInputBounds(CurrentLocation); + if (Bounds.IsValid) + { + AverageBoundsCentre += Bounds.GetCenter(); + NumBounds++; + } + } + } + + //if we have more than one, get the average centre + if (NumBounds > 1) + { + AverageBoundsCentre /= (float)NumBounds; + } + + //if we need to move... + float fDist = FVector::DistSquared(CurrentLocation, AverageBoundsCentre); + if (NumBounds && fDist > 1.0f) + { + // Move actor to average centre and recook + // This will refresh the static mesh under the HoudiniAssestComponent ( undoing the translation ). + HoudiniAssetActor->SetActorLocation(AverageBoundsCentre, false, nullptr, ETeleportType::TeleportPhysics); + + // Recook now the houdini-static-mesh has a new origin + HoudiniAssetComponent->StartTaskAssetCookingManual(); + RecentreCount++; + } + } + + if (RecentreCount) + { + // UE4 Editor doesn't refresh the translation-handles until they are re-selected, confusing the user, deselect the objects. + GEditor->SelectNone(true, false); + } + + // Add a slate notification + Notification = TEXT("Re-centred ") + FString::FromInt(RecentreCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Re-centred %d selected Houdini assets."), RecentreCount); + +#endif //WITH_EDITOR +} + +int32 +FHoudiniEngineEditor::GetContentBrowserSelection( TArray< UObject* >& ContentBrowserSelection ) +{ + ContentBrowserSelection.Empty(); + + // Get the current Content browser selection + FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked< FContentBrowserModule >( "ContentBrowser" ); + TArray SelectedAssets; + ContentBrowserModule.Get().GetSelectedAssets( SelectedAssets ); + + for( int32 n = 0; n < SelectedAssets.Num(); n++ ) + { + // Get the current object + UObject * Object = SelectedAssets[ n ].GetAsset(); + if ( !Object || Object->IsPendingKill() ) + continue; + + // Only static meshes are supported + if ( Object->GetClass() != UStaticMesh::StaticClass() ) + continue; + + ContentBrowserSelection.Add( Object ); + } + + return ContentBrowserSelection.Num(); +} + +int32 +FHoudiniEngineEditor::GetWorldSelection( TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly ) +{ + WorldSelection.Empty(); + + // Get the current editor selection + if ( GEditor ) + { + USelection* SelectedActors = GEditor->GetSelectedActors(); + if ( SelectedActors && !SelectedActors->IsPendingKill() ) + { + for ( FSelectionIterator It( *SelectedActors ); It; ++It ) + { + AActor * Actor = Cast< AActor >( *It ); + if ( !Actor && Actor->IsPendingKill() ) + continue; + + // Ignore the SkySphere? + FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); + if ( ClassName == TEXT( "BP_Sky_Sphere_C" ) ) + continue; + + // We're normally only selecting actors with StaticMeshComponents and SplineComponents + // Heightfields? Filter here or later? also allow HoudiniAssets? + WorldSelection.Add( Actor ); + } + } + + } + + // If we only want Houdini Actors... + if ( bHoudiniAssetActorsOnly ) + { + // ... remove all but them + for ( int32 Idx = WorldSelection.Num() - 1; Idx >= 0; Idx-- ) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast( WorldSelection[ Idx ] ); + if ( !HoudiniAssetActor || HoudiniAssetActor->IsPendingKill() ) + WorldSelection.RemoveAt( Idx ); + } + } + + return WorldSelection.Num(); +} + +bool +FHoudiniEngineEditor::CanRestartSession() const +{ + return true; +} + +void +FHoudiniEngineEditor::RestartSession() +{ + // Add a slate notification + FString Notification = TEXT("Restarting Current Houdini Session"); + FHoudiniEngineUtils::CreateSlateNotification( Notification ); + + // Restart the current Houdini Engine Session + bool bSuccess = FHoudiniEngine::Get().RestartSession(); + + // Notify all the HoudiniAssetComponent that they need to reinstantiate themselves in the new session. + for ( TObjectIterator Itr; Itr; ++Itr ) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + continue; + + HoudiniAssetComponent->NotifyAssetNeedsToBeReinstantiated(); + } + + // Add a slate notification + if ( bSuccess ) + Notification = TEXT("Houdini Session successfully restarted."); + else + Notification = TEXT("Failed to restart the current Houdini Session."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + //HOUDINI_LOG_MESSAGE( *Notification ); +} + +void +FHoudiniEngineEditor::AddLevelViewportMenuExtender() +{ + FLevelEditorModule& LevelEditorModule = FModuleManager::Get().LoadModuleChecked( "LevelEditor" ); + auto& MenuExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders(); + + MenuExtenders.Add( FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw( this, &FHoudiniEngineEditor::GetLevelViewportContextMenuExtender ) ); + LevelViewportExtenderHandle = MenuExtenders.Last().GetHandle(); +} + +void +FHoudiniEngineEditor::RemoveLevelViewportMenuExtender() +{ + if ( LevelViewportExtenderHandle.IsValid() ) + { + FLevelEditorModule* LevelEditorModule = FModuleManager::Get().GetModulePtr("LevelEditor"); + if ( LevelEditorModule ) + { + typedef FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors DelegateType; + LevelEditorModule->GetAllLevelViewportContextMenuExtenders().RemoveAll( + [=]( const DelegateType& In ) { return In.GetHandle() == LevelViewportExtenderHandle; } ); + } + } +} + +TSharedRef +FHoudiniEngineEditor::GetLevelViewportContextMenuExtender( const TSharedRef CommandList, const TArray InActors ) +{ + TSharedRef Extender = MakeShareable(new FExtender); + + // Build an array of the HoudiniAssets corresponding to the selected actors + TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets; + TArray< TWeakObjectPtr< AHoudiniAssetActor > > HoudiniAssetActors; + for ( auto CurrentActor : InActors ) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast( CurrentActor ); + if ( !HoudiniAssetActor || HoudiniAssetActor->IsPendingKill() ) + continue; + + HoudiniAssetActors.Add( HoudiniAssetActor ); + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + continue; + + HoudiniAssets.AddUnique( HoudiniAssetComponent->GetHoudiniAsset() ); + } + + if ( HoudiniAssets.Num() > 0 ) + { + // Add the Asset menu extension + if ( AssetTypeActions.Num() > 0 ) + { + // Add the menu extensions via our HoudiniAssetTypeActions + FHoudiniAssetTypeActions * HATA = static_cast( AssetTypeActions[0].Get() ); + if ( HATA ) + Extender = HATA->AddLevelEditorMenuExtenders( HoudiniAssets ); + } + } + + if ( HoudiniAssetActors.Num() > 0 ) + { + // Add some actor menu extensions + FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked( TEXT("LevelEditor") ); + TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); + Extender->AddMenuExtension( + "ActorControl", + EExtensionHook::After, + LevelEditorCommandBindings, + FMenuExtensionDelegate::CreateLambda( [ this, HoudiniAssetActors ]( FMenuBuilder& MenuBuilder ) + { + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recentre", "Recentre selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecentreTooltip", "Recentres the selected Houdini Asset Actors pivots to their input/cooked static mesh average centre."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateRaw(this, &FHoudiniEngineEditor::RecentreSelection), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recook", "Recook selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecookTooltip", "Forces a recook on the selected Houdini Asset Actors."), + FSlateIcon( FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateRaw(this, &FHoudiniEngineEditor::RecookSelection ), + FCanExecuteAction::CreateLambda([=] { return ( HoudiniAssetActors.Num() > 0 ); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Rebuild", "Rebuild selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RebuildTooltip", "Rebuilds selected Houdini Asset Actors in the current level."), + FSlateIcon( FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateRaw(this, &FHoudiniEngineEditor::RebuildSelection ), + FCanExecuteAction::CreateLambda([=] { return ( HoudiniAssetActors.Num() > 0 ); }) + ) + ); + } ) + ); + } + + return Extender; +} + +void +FHoudiniEngineEditor::GetAllHoudiniToolDirectories( TArray& HoudiniToolsDirectoryArray ) const +{ + HoudiniToolsDirectoryArray.Empty(); + + // Read the default tools from the $HFS/engine/tool folder + FDirectoryPath DefaultToolPath; + FString HFSPath = FPaths::GetPath(FHoudiniEngine::Get().GetLibHAPILocation()); + FString DefaultPath = FPaths::Combine(HFSPath, TEXT("engine")); + DefaultPath = FPaths::Combine(DefaultPath, TEXT("tools")); + + FHoudiniToolDirectory ToolDir; + ToolDir.Name = TEXT("Default"); + ToolDir.Path.Path = DefaultPath; + ToolDir.ContentDirID = TEXT("Default"); + + HoudiniToolsDirectoryArray.Add( ToolDir ); + + // Append all the custom tools directory from the runtime settings + UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetMutableDefault< UHoudiniRuntimeSettings >(); + if ( !HoudiniRuntimeSettings ) + return; + + // We have to make sure all Custom tool dir have a ContentDirID + bool NeedToSave = false; + for (int32 n = 0; n < HoudiniRuntimeSettings->CustomHoudiniToolsLocation.Num(); n++ ) + { + if (!HoudiniRuntimeSettings->CustomHoudiniToolsLocation[n].ContentDirID.IsEmpty()) + continue; + + // Generate a new Directory ID for that directory + HoudiniRuntimeSettings->CustomHoudiniToolsLocation[n].ContentDirID = ObjectTools::SanitizeObjectName( + HoudiniRuntimeSettings->CustomHoudiniToolsLocation[n].Name + TEXT(" ") + FGuid::NewGuid().ToString() ); + + NeedToSave = true; + } + + if ( NeedToSave ) + HoudiniRuntimeSettings->SaveConfig(); + + // Add all the custom tool paths + HoudiniToolsDirectoryArray.Append( HoudiniRuntimeSettings->CustomHoudiniToolsLocation ); +} + +void +FHoudiniEngineEditor::GetHoudiniToolDirectories(const int32& SelectedDirIndex, TArray& HoudiniToolsDirectoryArray) const +{ + HoudiniToolsDirectoryArray.Empty(); + + // Get All the houdini tool directories + GetAllHoudiniToolDirectories(HoudiniToolsDirectoryArray); + + // Only keep the selected one + // -1 indicates we want all directories + if ( SelectedDirIndex >= 0 ) + { + for (int32 n = HoudiniToolsDirectoryArray.Num() - 1; n >= 0; n--) + { + if (n != CurrentHoudiniToolDirIndex) + HoudiniToolsDirectoryArray.RemoveAt(n); + } + } +} + +void +FHoudiniEngineEditor::UpdateHoudiniToolList(const int32 SelectedDir/*=-1*/) +{ + CurrentHoudiniToolDirIndex = SelectedDir; + + // Clean up the existing tool list + HoudiniTools.Empty(); + + // Get All the houdini tool directories + TArray HoudiniToolsDirectoryArray; + GetHoudiniToolDirectories( SelectedDir, HoudiniToolsDirectoryArray ); + + // Add the tools for all the directories + for ( int32 n = 0; n < HoudiniToolsDirectoryArray.Num(); n++ ) + { + FHoudiniToolDirectory CurrentDir = HoudiniToolsDirectoryArray[n]; + bool isDefault = ( (n == 0) && (CurrentHoudiniToolDirIndex <= 0) ); + UpdateHoudiniToolList( CurrentDir, isDefault ); + } +} + +void +FHoudiniEngineEditor::UpdateHoudiniToolList(const FHoudiniToolDirectory& HoudiniToolsDirectory, const bool& isDefault) +{ + FString ToolDirPath = HoudiniToolsDirectory.Path.Path; + if ( ToolDirPath.IsEmpty() ) + return; + + // We need to either load or create a new HoudiniAsset from the tool's HDA + FString ToolPath = TEXT("/Game/HoudiniEngine/Tools/"); + ToolPath = FPaths::Combine(ToolPath, HoudiniToolsDirectory.ContentDirID ); + ToolPath = ObjectTools::SanitizeObjectPath(ToolPath); + + // List all the json files in the current directory + TArray JSONFiles; + IFileManager::Get().FindFiles(JSONFiles, *ToolDirPath, TEXT(".json")); + for ( const FString& CurrentJsonFile : JSONFiles ) + { + FString CurrentToolName; + EHoudiniToolType CurrentToolType; + EHoudiniToolSelectionType CurrentToolSelectionType; + FString CurrentToolToolTip; + FFilePath CurrentToolIconPath; + FFilePath CurrentToolAssetPath; + FString CurrentToolHelpURL; + + // Extract the Tool info from the JSON file + FString CurrentJsonFilePath = ToolDirPath / CurrentJsonFile; + if ( !GetHoudiniToolDescriptionFromJSON( + CurrentJsonFilePath, CurrentToolName, CurrentToolType, CurrentToolSelectionType, + CurrentToolToolTip, CurrentToolIconPath, CurrentToolAssetPath, CurrentToolHelpURL)) + continue; + + FText ToolName = FText::FromString(CurrentToolName); + FText ToolTip = FText::FromString(CurrentToolToolTip); + + FString IconPath = FPaths::ConvertRelativePathToFull(CurrentToolIconPath.FilePath); + const FSlateBrush* CustomIconBrush = nullptr; + if (FPaths::FileExists(IconPath)) + { + FName BrushName = *IconPath; + CustomIconBrush = new FSlateDynamicImageBrush(BrushName, FVector2D(40.f, 40.f)); + } + else + { + CustomIconBrush = FHoudiniEngineStyle::Get()->GetBrush(TEXT("HoudiniEngine.HoudiniEngineLogo40")); + } + + // Construct the asset's ref + FString BaseToolName = ObjectTools::SanitizeObjectName( FPaths::GetBaseFilename( CurrentToolAssetPath.FilePath ) ); + FString ToolAssetRef = TEXT("HoudiniAsset'") + FPaths::Combine(ToolPath, BaseToolName) + TEXT(".") + BaseToolName + TEXT("'"); + TSoftObjectPtr HoudiniAsset(ToolAssetRef); + + // See if the HDA needs to be imported in Unreal, or just loaded + bool NeedsImport = false; + if ( !HoudiniAsset.IsValid() ) + { + NeedsImport = true; + + // Try to load the asset + UHoudiniAsset* LoadedAsset = HoudiniAsset.LoadSynchronous(); + if ( LoadedAsset && !LoadedAsset->IsPendingKill() ) + NeedsImport = !HoudiniAsset.IsValid(); + } + + if ( NeedsImport ) + { + // Automatically import the HDA and create a uasset for it + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); + TArray FileNames; + FileNames.Add(CurrentToolAssetPath.FilePath); + + UAutomatedAssetImportData* ImportData = NewObject(); + ImportData->bReplaceExisting = true; + ImportData->bSkipReadOnly = true; + ImportData->Filenames = FileNames; + ImportData->DestinationPath = ToolPath; + TArray AssetArray = AssetToolsModule.Get().ImportAssetsAutomated(ImportData); + if ( AssetArray.Num() <= 0 ) + continue; + + HoudiniAsset = Cast< UHoudiniAsset >(AssetArray[0]); + if (!HoudiniAsset || HoudiniAsset->IsPendingKill() ) + continue; + + // Try to save the newly created package + UPackage* Pckg = Cast( HoudiniAsset->GetOuter() ); + if (Pckg && !Pckg->IsPendingKill() && Pckg->IsDirty() ) + { + Pckg->FullyLoad(); + UPackage::SavePackage( + Pckg, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, + *FPackageName::LongPackageNameToFilename( Pckg->GetName(), FPackageName::GetAssetPackageExtension() ) ); + } + } + + // Add the tool to the tool list + HoudiniTools.Add( MakeShareable( new FHoudiniTool( + HoudiniAsset, ToolName, CurrentToolType, CurrentToolSelectionType, ToolTip, CustomIconBrush, CurrentToolHelpURL, isDefault, CurrentToolAssetPath, HoudiniToolsDirectory, CurrentJsonFile ) ) ); + } +} + +bool +FHoudiniEngineEditor::GetHoudiniToolDescriptionFromJSON(const FString& JsonFilePath, + FString& OutName, EHoudiniToolType& OutType, EHoudiniToolSelectionType& OutSelectionType, + FString& OutToolTip, FFilePath& OutIconPath, FFilePath& OutAssetPath, FString& OutHelpURL ) +{ + if ( JsonFilePath.IsEmpty() ) + return false; + + // Read the file as a string + FString FileContents; + if ( !FFileHelper::LoadFileToString( FileContents, *JsonFilePath ) ) + { + HOUDINI_LOG_ERROR( TEXT("Failed to import Houdini Tool .json file: %s"), *JsonFilePath); + return false; + } + + // Parse it as JSON + TSharedPtr JSONObject; + TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( FileContents ); + if (!FJsonSerializer::Deserialize(Reader, JSONObject) || !JSONObject.IsValid()) + { + HOUDINI_LOG_ERROR( TEXT("Invalid json in Houdini Tool .json file: %s"), *JsonFilePath); + return false; + } + + // First, check that the tool is compatible with UE4 + bool IsCompatible = true; + if (JSONObject->HasField(TEXT("target"))) + { + IsCompatible = false; + TArray >TargetArray = JSONObject->GetArrayField("target"); + for (TSharedPtr TargetValue : TargetArray) + { + if ( !TargetValue.IsValid() ) + continue; + + // Check the target array field contains either "all" or "unreal" + FString TargetString = TargetValue->AsString(); + if ( TargetString.Equals( TEXT("all"), ESearchCase::IgnoreCase ) + || TargetString.Equals(TEXT("unreal"), ESearchCase::IgnoreCase) ) + { + IsCompatible = true; + break; + } + } + } + + if ( !IsCompatible ) + { + // The tool is not compatible with unreal, skip it + HOUDINI_LOG_MESSAGE( TEXT("Skipped Houdini Tool due to invalid target in JSON file: %s"), *JsonFilePath ); + return false; + } + + // Then, we need to make sure the json file has a correponding hda + // Start by looking at the assetPath field + FString HDAFilePath = FString(); + if ( JSONObject->HasField( TEXT("assetPath") ) ) + { + // If the json has the assetPath field, read it from there + HDAFilePath = JSONObject->GetStringField(TEXT("assetPath")); + if (!FPaths::FileExists(HDAFilePath)) + HDAFilePath = FString(); + } + + if (HDAFilePath.IsEmpty()) + { + // Look for an hda file with the same name as the json file + HDAFilePath = JsonFilePath.Replace(TEXT(".json"), TEXT(".hda")); + if (!FPaths::FileExists(HDAFilePath)) + { + // Try .hdalc + HDAFilePath = JsonFilePath.Replace(TEXT(".json"), TEXT(".hdalc")); + if (!FPaths::FileExists(HDAFilePath)) + { + // Try .hdanc + HDAFilePath = JsonFilePath.Replace(TEXT(".json"), TEXT(".hdanc")); + if (!FPaths::FileExists(HDAFilePath)) + HDAFilePath = FString(); + } + } + } + + if ( HDAFilePath.IsEmpty() ) + { + HOUDINI_LOG_ERROR(TEXT("Could not find the hda for the Houdini Tool .json file: %s"), *JsonFilePath); + return false; + } + + // Create a new asset. + OutAssetPath = FFilePath{ HDAFilePath }; + + // Read the tool name + OutName = FPaths::GetBaseFilename(JsonFilePath); + if ( JSONObject->HasField(TEXT("name") ) ) + OutName = JSONObject->GetStringField(TEXT("name")); + + // Read the tool type + OutType = EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE; + if ( JSONObject->HasField( TEXT("toolType") ) ) + { + FString ToolType = JSONObject->GetStringField(TEXT("toolType")); + if ( ToolType.Equals( TEXT("GENERATOR"), ESearchCase::IgnoreCase ) ) + OutType = EHoudiniToolType::HTOOLTYPE_GENERATOR; + else if (ToolType.Equals( TEXT("OPERATOR_SINGLE"), ESearchCase::IgnoreCase ) ) + OutType = EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE; + else if (ToolType.Equals(TEXT("OPERATOR_MULTI"), ESearchCase::IgnoreCase)) + OutType = EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI; + else if (ToolType.Equals(TEXT("BATCH"), ESearchCase::IgnoreCase)) + OutType = EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH; + } + + // Read the tooltip + OutToolTip = FString(); + if ( JSONObject->HasField( TEXT("toolTip") ) ) + { + OutToolTip = JSONObject->GetStringField(TEXT("toolTip")); + } + + // Read the help url + OutHelpURL = FString(); + if (JSONObject->HasField(TEXT("helpURL"))) + { + OutHelpURL = JSONObject->GetStringField(TEXT("helpURL")); + } + + // Read the icon path + FString IconPath = FString(); + if (JSONObject->HasField(TEXT("iconPath"))) + { + // If the json has the iconPath field, read it from there + IconPath = JSONObject->GetStringField(TEXT("iconPath")); + if (!FPaths::FileExists(IconPath)) + IconPath = FString(); + } + + if (IconPath.IsEmpty()) + { + // Look for a png file with the same name as the json file + IconPath = JsonFilePath.Replace(TEXT(".json"), TEXT(".png")); + if (!FPaths::FileExists(IconPath)) + { + IconPath = GetDefaultHoudiniToolIcon(); + } + } + + OutIconPath = FFilePath{ IconPath }; + + // Read the selection types + FString SelectionType = TEXT("All"); + if ( JSONObject->HasField(TEXT("UE_SelectionType")) ) + SelectionType = JSONObject->GetStringField( TEXT("UE_SelectionType") ); + + if ( SelectionType.Equals( TEXT("CB"), ESearchCase::IgnoreCase ) ) + OutSelectionType = EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY; + else if ( SelectionType.Equals( TEXT("World"), ESearchCase::IgnoreCase ) ) + OutSelectionType = EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY; + else + OutSelectionType = EHoudiniToolSelectionType::HTOOL_SELECTION_ALL; + + // TODO: Handle Tags in the JSON? + + return true; +} + +bool +FHoudiniEngineEditor::WriteJSONFromHoudiniTool(const FHoudiniTool& Tool) +{ + // Start by building a JSON object from the tool + TSharedPtr JSONObject = MakeShareable(new FJsonObject); + + // Mark the target as unreal only + TArray< TSharedPtr > TargetValue; + TargetValue.Add(MakeShareable(new FJsonValueString(TEXT("unreal")))); + JSONObject->SetArrayField(TEXT("target"), TargetValue ); + + // Write the asset Path + if (!Tool.SourceAssetPath.FilePath.IsEmpty()) + { + // Only write the assetPath if it's different from the default one (same path as JSON) + if (FPaths::GetBaseFilename(Tool.SourceAssetPath.FilePath) != (FPaths::GetBaseFilename(Tool.JSONFile)) + && (FPaths::GetPath(Tool.SourceAssetPath.FilePath) != Tool.ToolDirectory.Path.Path)) + { + JSONObject->SetStringField(TEXT("assetPath"), FPaths::ConvertRelativePathToFull(Tool.SourceAssetPath.FilePath)); + } + } + + // The Tool Name + if ( !Tool.Name.IsEmpty() ) + JSONObject->SetStringField(TEXT("name"), Tool.Name.ToString()); + + // Tooltype + FString ToolType = TEXT("GENERATOR"); + if ( Tool.Type == EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE) + ToolType = TEXT("OPERATOR_SINGLE"); + else if (Tool.Type == EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI) + ToolType = TEXT("OPERATOR_MULTI"); + else if ( Tool.Type == EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH) + ToolType = TEXT("BATCH"); + + JSONObject->SetStringField(TEXT("toolType"), ToolType); + + // Tooltip + if ( !Tool.ToolTipText.IsEmpty() ) + JSONObject->SetStringField(TEXT("toolTip"), Tool.ToolTipText.ToString()); + + // Help URL + if ( !Tool.HelpURL.IsEmpty() ) + JSONObject->SetStringField(TEXT("helpURL"), Tool.HelpURL); + + // IconPath + if ( Tool.Icon ) + { + FString IconPath = Tool.Icon->GetResourceName().ToString(); + JSONObject->SetStringField(TEXT("iconPath"), IconPath); + } + + // Selection Type + FString SelectionType = TEXT("All"); + if (Tool.SelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY) + SelectionType = TEXT("CB"); + else if (Tool.SelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY) + SelectionType = TEXT("World"); + JSONObject->SetStringField(TEXT("UE_SelectionType"), SelectionType); + + FString ToolJSONFilePath = Tool.ToolDirectory.Path.Path / Tool.JSONFile; + + // Output the JSON to a String + FString OutputString; + TSharedRef< TJsonWriter<> > Writer = TJsonWriterFactory<>::Create(&OutputString); + FJsonSerializer::Serialize(JSONObject.ToSharedRef(), Writer); + + // Then write the output string to the json file itself + FString SaveDirectory = Tool.ToolDirectory.Path.Path; + FString FileName = Tool.JSONFile; + + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + // Returns true if the directory existed or has been created + if ( PlatformFile.CreateDirectoryTree(*SaveDirectory) ) + { + FString AbsoluteFilePath = SaveDirectory / FileName; + return FFileHelper::SaveStringToFile(OutputString, *AbsoluteFilePath); + } + + return false; +} + +bool +FHoudiniEngineEditor::FindHoudiniTool( const FHoudiniTool& Tool, int32& FoundIndex, bool& IsDefault ) +{ + // Return -1 if we cant find the Tool + FoundIndex = -1; + + for ( int32 Idx = 0; Idx < HoudiniTools.Num(); Idx++ ) + { + TSharedPtr Current = HoudiniTools[ Idx ]; + if ( !Current.IsValid() ) + continue; + + if ( Current->HoudiniAsset != Tool.HoudiniAsset ) + continue; + + if ( Current->Type != Tool.Type ) + continue; + + if ( Current->JSONFile != Tool.JSONFile ) + continue; + + if (Current->ToolDirectory != Tool.ToolDirectory) + continue; + + // We found the Houdini Tool + IsDefault = Current->DefaultTool; + FoundIndex = Idx; + + return true; + } + + return false; +} + +bool +FHoudiniEngineEditor::FindHoudiniToolInHoudiniSettings( const FHoudiniTool& Tool, int32& FoundIndex ) +{ + // Return -1 if we cant find the Tool + FoundIndex = -1; + + // Remove the tool from the runtime settings + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if ( !HoudiniRuntimeSettings ) + return false; + + // TODO: FIX ME! + /* + for ( int32 Idx = 0; Idx < HoudiniRuntimeSettings->CustomHoudiniTools.Num(); Idx++ ) + { + FHoudiniToolDescription CurrentDesc = HoudiniRuntimeSettings->CustomHoudiniTools[ Idx ]; + + if ( CurrentDesc.HoudiniAsset != Tool.HoudiniAsset ) + continue; + + if ( CurrentDesc.Type != Tool.Type ) + continue; + + FoundIndex = Idx; + return true; + } + */ + + return false; +} + +void +FHoudiniEngineCommands::RegisterCommands() +{ + UI_COMMAND( OpenInHoudini, "Open scene in Houdini", "Opens the current Houdini scene in Houdini.", EUserInterfaceActionType::Button, FInputChord( EKeys::O, EModifierKey::Control | EModifierKey::Alt) ); + + UI_COMMAND( SaveHIPFile, "Save Houdini scene (HIP)", "Saves a .hip file of the current Houdini scene.", EUserInterfaceActionType::Button, FInputChord() ); + UI_COMMAND( ReportBug, "Report a plugin bug", "Report a bug for Houdini Engine plugin.", EUserInterfaceActionType::Button, FInputChord() ); + + UI_COMMAND( CleanUpTempFolder, "Clean Houdini Engine Temp Folder", "Deletes the unused temporary files in the Temporary Cook Folder.", EUserInterfaceActionType::Button, FInputChord() ); + UI_COMMAND( BakeAllAssets, "Bake And Replace All Houdini Assets", "Bakes and replaces with blueprints all Houdini Assets in the scene.", EUserInterfaceActionType::Button, FInputChord() ); + UI_COMMAND( PauseAssetCooking, "Pause Houdini Engine Cooking", "When activated, prevents Houdini Engine from cooking assets until unpaused.", EUserInterfaceActionType::Check, FInputChord( EKeys::P, EModifierKey::Control | EModifierKey::Alt ) ); + + UI_COMMAND( CookSelec, "Recook Selection", "Recooks selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord( EKeys::C, EModifierKey::Control | EModifierKey::Alt ) ); + UI_COMMAND( RebuildSelec, "Rebuild Selection", "Rebuilds selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord( EKeys::R, EModifierKey::Control | EModifierKey::Alt ) ); + UI_COMMAND( BakeSelec, "Bake Selection", "Bakes and replaces with blueprints selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord( EKeys::B, EModifierKey::Control | EModifierKey::Alt ) ); + + UI_COMMAND( RestartSession, "Restart the Houdini Engine Session", "Restarts the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h new file mode 100644 index 00000000..032625b8 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h @@ -0,0 +1,407 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once +#include "IHoudiniEngineEditor.h" +#include "ComponentVisualizer.h" +#include "Styling/SlateStyle.h" +#include "EditorUndoClient.h" +#include "Framework/MultiBox/MultiBoxExtender.h" +#include "HoudiniRuntimeSettings.h" +#include "Framework/Commands/Commands.h" +#include "HAPI.h" + + +class IAssetTools; +class IAssetTypeActions; +class IComponentAssetBroker; +class UHoudiniAssetComponent; +class FMenuBuilder; + +struct FSlateBrush; +struct FHoudiniToolDescription; + +struct FHoudiniTool +{ + FHoudiniTool() + : HoudiniAsset( nullptr) + , Name() + , ToolTipText() + , Icon() + , HelpURL() + , Type(EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE) + , DefaultTool(false) + , SelectionType(EHoudiniToolSelectionType::HTOOL_SELECTION_ALL) + , SourceAssetPath() + , ToolDirectory() + , JSONFile() + { + } + + FHoudiniTool( + TSoftObjectPtr < class UHoudiniAsset > InHoudiniAsset, const FText& InName, + const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelType, + const FText& InToolTipText, const FSlateBrush* InIcon, const FString& InHelpURL, + const bool& isDefault, const FFilePath& InAssetPath, const FHoudiniToolDirectory& InToolDirectory, + const FString& InJSONFile ) + : HoudiniAsset( InHoudiniAsset ) + , Name( InName ) + , ToolTipText( InToolTipText ) + , Icon( InIcon ) + , HelpURL( InHelpURL ) + , Type( InType ) + , DefaultTool( isDefault ) + , SelectionType( InSelType ) + , SourceAssetPath( InAssetPath ) + , ToolDirectory( InToolDirectory ) + , JSONFile( InJSONFile ) + { + } + + /** The Houdini Asset used by the tool **/ + TSoftObjectPtr < class UHoudiniAsset > HoudiniAsset; + + /** The name to be displayed */ + FText Name; + + /** The name to be displayed */ + FText ToolTipText; + + /** The icon to be displayed */ + const FSlateBrush* Icon; + + /** The help URL for this tool */ + FString HelpURL; + + /** The type of tool, this will change how the asset handles the current selection **/ + EHoudiniToolType Type; + + /** Indicate this is one of the default tools **/ + bool DefaultTool; + + /** Indicate what the tool should consider for selection **/ + EHoudiniToolSelectionType SelectionType; + + /** Path to the Asset used **/ + FFilePath SourceAssetPath; + + /** Directory containing the tool **/ + FHoudiniToolDirectory ToolDirectory; + + /** Name of the JSON containing the tool's description **/ + FString JSONFile; + + /** Returns the file path to the JSOn file containing the tool's description **/ + FString GetJSonFilePath() { return ToolDirectory.Path.Path / JSONFile; }; +}; + +class FHoudiniEngineStyle +{ +public: + static void Initialize(); + static void Shutdown(); + static TSharedPtr Get(); + static FName GetStyleSetName(); + +private: + //static FString InContent(const FString &RelativePath, const ANSICHAR *Extension); + + static TSharedPtr StyleSet; +}; + +class FHoudiniEngineEditor : public IHoudiniEngineEditor, public FEditorUndoClient +{ + public: + FHoudiniEngineEditor(); + + /** IModuleInterface methods. **/ + public: + + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + /** IHoudiniEngineEditor methods. **/ + public: + + virtual void RegisterComponentVisualizers() override; + virtual void UnregisterComponentVisualizers() override; + virtual void RegisterDetails() override; + virtual void UnregisterDetails() override; + virtual void RegisterAssetTypeActions() override; + virtual void UnregisterAssetTypeActions() override; + virtual void RegisterAssetBrokers() override; + virtual void UnregisterAssetBrokers() override; + virtual void RegisterActorFactories() override; + virtual void ExtendMenu() override; + virtual void RegisterForUndo() override; + virtual void UnregisterForUndo() override; + virtual void RegisterPlacementModeExtensions() override; + virtual void UnregisterPlacementModeExtensions() override; + + /** FEditorUndoClient methods. **/ + public: + + virtual bool MatchesContext(const FTransactionContext& InContext, const TArray>& TransactionObjects) const override; + virtual void PostUndo( bool bSuccess ) override; + virtual void PostRedo( bool bSuccess ) override; + + public: + + /** App identifier string. **/ + static const FName HoudiniEngineEditorAppIdentifier; + + /** Selected Houdini Tool Dir **/ + int32 CurrentHoudiniToolDirIndex; + + public: + + /** Return singleton instance of Houdini Engine Editor, used internally. **/ + static FHoudiniEngineEditor & Get(); + + /** Return true if singleton instance has been created. **/ + static bool IsInitialized(); + + public: + + /** Menu action called to save a HIP file. **/ + void SaveHIPFile(); + + /** Helper delegate used to determine if HIP file save can be executed. **/ + bool CanSaveHIPFile() const; + + /** Menu action called to report a bug. **/ + void ReportBug(); + + /** Helper delegate used to determine if report a bug can be executed. **/ + bool CanReportBug() const; + + /** Menu action called to open the current scene in Houdini. **/ + void OpenInHoudini(); + + /** Helper delegate used to determine if open in Houdini can be executed. **/ + bool CanOpenInHoudini() const; + + /** Menu action called to clean up all unused files in the cook temp folder **/ + void CleanUpTempFolder(); + + /** Helper delegate used to determine if Clean up temp can be executed. **/ + bool CanCleanUpTempFolder() const; + + /** Menu action to bake/replace all current Houdini Assets with blueprints **/ + void BakeAllAssets(); + + /** Helper function for baking/replacing the current select Houdini Assets with blueprints **/ + void BakeSelection(); + + /** Helper delegate used to determine if BakeAllAssets can be executed. **/ + bool CanBakeAllAssets() const; + + /** Helper function for restarting the current Houdini Engine session. **/ + void RestartSession(); + + /** Helper delegate used to determine if RestartSession can be executed. **/ + bool CanRestartSession() const; + + /** Returns the plugin's directory **/ + static FString GetHoudiniEnginePluginDir(); + + /** Returns the Default Icon to be used by Houdini Tools**/ + static FString GetDefaultHoudiniToolIcon(); + + /** Returns the HoudiniTools currently available for the shelf **/ + const TArray< TSharedPtr >& GetHoudiniTools() { return HoudiniTools; } + + /** Reads the Houdini Tool Description from a JSON file **/ + bool GetHoudiniToolDescriptionFromJSON( + const FString& JsonFilePath, + FString& OutName, EHoudiniToolType& OutType, EHoudiniToolSelectionType& OutSelectionType, + FString& OutToolTip, FFilePath& OutIconPath, FFilePath& OutAssetPath, FString& OutHelpURL ); + + bool WriteJSONFromHoudiniTool(const FHoudiniTool& Tool); + + /** Returns the HoudiniTools array **/ + TArray< TSharedPtr >* GetHoudiniToolsForWrite() { return &HoudiniTools; } + + /** Menu action to pause cooking for all Houdini Assets **/ + void PauseAssetCooking(); + + /** Helper delegate used to determine if PauseAssetCooking can be executed. **/ + bool CanPauseAssetCooking(); + + /** Helper delegate used to get the current state of PauseAssetCooking. **/ + bool IsAssetCookingPaused(); + + /** Helper function for recooking all assets in the current level **/ + void RecookAllAssets(); + + /** Helper function for rebuilding all assets in the current level **/ + void RebuildAllAssets(); + + /** Helper function for recooking selected assets **/ + void RecookSelection(); + + /** Helper function for rebuilding selected assets **/ + void RebuildSelection(); + + /** Helper function for rebuilding selected assets **/ + void RecentreSelection(); + + /** Helper function for accessing the current CB selection **/ + static int32 GetContentBrowserSelection( TArray< UObject* >& ContentBrowserSelection ); + + /** Helper function for accessing the current world selection **/ + static int32 GetWorldSelection( TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly = false ); + + /** Helper function for retrieving an HoudiniTool in the Editor list **/ + bool FindHoudiniTool( const FHoudiniTool& Tool, int32& FoundIndex, bool& IsDefault ); + + /** Helper function for retrieving an HoudiniTool in the Houdini Runtime Settings list **/ + bool FindHoudiniToolInHoudiniSettings( const FHoudiniTool& Tool, int32& FoundIndex ); + + /** Rebuild the editor's Houdini Tool list **/ + void UpdateHoudiniToolList(int32 SelectedDir = -1); + + /** Rebuild the editor's Houdini Tool list for a directory **/ + void UpdateHoudiniToolList(const FHoudiniToolDirectory& HoudiniToolsDirectory, const bool& isDefault ); + + /** Return all the directories where we should look for houdini tools**/ + void GetAllHoudiniToolDirectories(TArray& HoudiniToolsDirectoryArray) const; + + /** Return the directories where we should look for houdini tools**/ + void GetHoudiniToolDirectories(const int32& SelectedIndex, TArray& HoudiniToolsDirectoryArray) const; + + protected: + + /** Register AssetType action. **/ + void RegisterAssetTypeAction( IAssetTools & AssetTools, TSharedRef< IAssetTypeActions > Action ); + + /** Binds the menu extension's UICommands to their corresponding functions **/ + void BindMenuCommands(); + + /** Add menu extension for our module. **/ + void AddHoudiniMenuExtension( FMenuBuilder & MenuBuilder ); + + /** Add the default Houdini Tools to the Houdini Engine Shelft tool **/ + void AddDefaultHoudiniToolToArray( TArray< FHoudiniToolDescription >& ToolArray ); + + /** Adds the custom Houdini Engine console commands **/ + void RegisterConsoleCommands(); + + /** Adds the custom Houdini Engine commands to the world outliner context menu **/ + void AddLevelViewportMenuExtender(); + + /** Removes the custom Houdini Engine commands from the world outliner context menu **/ + void RemoveLevelViewportMenuExtender(); + + /** Return all the custom Houdini Engine commands for the world outliner context menu **/ + TSharedRef GetLevelViewportContextMenuExtender( + const TSharedRef CommandList, const TArray InActors ); + + private: + + /** Singleton instance of Houdini Engine Editor. **/ + static FHoudiniEngineEditor * HoudiniEngineEditorInstance; + + private: + + /** AssetType actions associated with Houdini asset. **/ + TArray< TSharedPtr< IAssetTypeActions > > AssetTypeActions; + + /** Visualizer for our spline component. **/ + TSharedPtr< FComponentVisualizer > HandleComponentVisualizer; + + /** Visualizer for our spline component. **/ + TSharedPtr< FComponentVisualizer > SplineComponentVisualizer; + + /** Broker associated with Houdini asset. **/ + TSharedPtr< IComponentAssetBroker > HoudiniAssetBroker; + + /** The extender to pass to the level editor to extend it's window menu. **/ + TSharedPtr< FExtender > MainMenuExtender; + + /** Stored last used Houdini component which was involved in undo. **/ + mutable UHoudiniAssetComponent * LastHoudiniAssetComponentUndoObject; + + TArray< TSharedPtr > HoudiniTools; + + TSharedPtr HEngineCommands; + + FDelegateHandle LevelViewportExtenderHandle; +}; + + +/** +* Class containing commands for Houdini Engine actions +*/ +class FHoudiniEngineCommands : public TCommands +{ +public: + FHoudiniEngineCommands() + : TCommands + ( + TEXT("HoudiniEngine"), // Context name for fast lookup + NSLOCTEXT("Contexts", "HoudiniEngine", "Houdini Engine Plugin"), // Localized context name for displaying + NAME_None, // Parent context name. + FHoudiniEngineStyle::GetStyleSetName() // Icon Style Set + ) + { + } + + // TCommand<> interface + virtual void RegisterCommands() override; + + /** Menu action called to save a HIP file. **/ + TSharedPtr SaveHIPFile; + + /** Menu action called to report a bug. **/ + TSharedPtr ReportBug; + + /** Menu action called to open the current scene in Houdini. **/ + TSharedPtr OpenInHoudini; + + /** Menu action called to clean up all unused files in the cook temp folder **/ + TSharedPtr CleanUpTempFolder; + + /** Menu action to bake/replace all current Houdini Assets with blueprints **/ + TSharedPtr BakeAllAssets; + + /** Menu action to pause cooking for all Houdini Assets **/ + TSharedPtr PauseAssetCooking; + + /** UI Action to recook the current world selection **/ + TSharedPtr CookSelec; + + /** UI Action to rebuild the current world selection **/ + TSharedPtr RebuildSelec; + + /** UI Action to bake and replace the current world selection **/ + TSharedPtr BakeSelec; + + /** UI Action to restart the current Houdini Engine Session **/ + TSharedPtr RestartSession; + + /** UI Action to recentre the current selection **/ + TSharedPtr RecentreSelec; + +}; + diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.h new file mode 100644 index 00000000..719bc07b --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.h @@ -0,0 +1,32 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#pragma once diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h new file mode 100644 index 00000000..6b440275 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h @@ -0,0 +1,43 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Damian Campeanu, Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#pragma once + + +#define HOUDINI_ENGINE_EDITOR +#include "HoudiniEngineRuntimePrivatePCH.h" + + /** Houdini Engine Editor Module Localization. **/ +#include "HoudiniEngineEditorLocalization.h" + + /** URL used for bug reporting. **/ +#define HAPI_UNREAL_BUG_REPORT_URL \ + TEXT("https://www.sidefx.com/bugs/submit/") diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp new file mode 100644 index 00000000..6b31b944 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp @@ -0,0 +1,228 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniHandleComponentVisualizer.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngineEditor.h" +#include "EditorViewportClient.h" +#include "Framework/Commands/Commands.h" +#include "EditorStyleSet.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_HIT_PROXY( HHoudiniHandleVisProxy, HComponentVisProxy ); + +HHoudiniHandleVisProxy::HHoudiniHandleVisProxy( const UActorComponent * InComponent ) + : HComponentVisProxy( InComponent, HPP_Wireframe ) +{} + +FHoudiniHandleComponentVisualizerCommands::FHoudiniHandleComponentVisualizerCommands() + : TCommands< FHoudiniHandleComponentVisualizerCommands >( + "HoudiniHandleComponentVisualizer", + LOCTEXT( "HoudiniHandleComponentVisualizer", "Houdini Handle Component Visualizer" ), + NAME_None, + FEditorStyle::GetStyleSetName() ) +{} + +void +FHoudiniHandleComponentVisualizerCommands::RegisterCommands() +{} + +FHoudiniHandleComponentVisualizer::FHoudiniHandleComponentVisualizer() + : FComponentVisualizer() + , EditedComponent( nullptr ) + , bEditing( false ) +{ + FHoudiniHandleComponentVisualizerCommands::Register(); + VisualizerActions = MakeShareable( new FUICommandList ); +} + +FHoudiniHandleComponentVisualizer::~FHoudiniHandleComponentVisualizer() +{ + FHoudiniHandleComponentVisualizerCommands::Unregister(); +} + +bool +FHoudiniHandleComponentVisualizer::HandleInputKey( FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event ) +{ + if( EditedComponent ) + { + if( Key == EKeys::LeftMouseButton && Event == IE_Released ) + { + if( GEditor ) + GEditor->RedrawLevelEditingViewports( true ); + + EditedComponent->UpdateTransformParameters(); + } + } + return false; +} + +void +FHoudiniHandleComponentVisualizer::DrawVisualization( + const UActorComponent * Component, const FSceneView * View, + FPrimitiveDrawInterface * PDI ) +{ + const UHoudiniHandleComponent * HandleComponent = Cast< const UHoudiniHandleComponent >( Component ); + if ( !HandleComponent ) + return; + + bool IsActive = EditedComponent != nullptr; + + static const FLinearColor ActiveColor( 1.f, 0, 1.f); + static const FLinearColor InactiveColor( 0.2f, 0.2f, 0.2f, 0.2f ); + + // Draw point and set hit box for it. + PDI->SetHitProxy( new HHoudiniHandleVisProxy( HandleComponent ) ); + { + static const float GrabHandleSize = 24.0f; + PDI->DrawPoint( HandleComponent->GetComponentTransform().GetLocation(), IsActive ? ActiveColor : InactiveColor, GrabHandleSize, SDPG_Foreground ); + } + + if( HandleComponent->HandleType == EHoudiniHandleType::Bounder ) + { + // draw the scale box + FTransform BoxTransform = HandleComponent->GetComponentTransform(); + const float BoxRad = 50.f; + const FBox Box( FVector( -BoxRad, -BoxRad, -BoxRad ), FVector( BoxRad, BoxRad, BoxRad ) ); + DrawWireBox( PDI, BoxTransform.ToMatrixWithScale(), Box, IsActive ? ActiveColor : InactiveColor, SDPG_Foreground ); + } + PDI->SetHitProxy( nullptr ); +} + +bool +FHoudiniHandleComponentVisualizer::VisProxyHandleClick( + FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click ) +{ + bEditing = false; + + bAllowTranslate = false; + bAllowRotation = false; + bAllowScale = false; + + if ( VisProxy && VisProxy->Component.IsValid() ) + { + const UHoudiniHandleComponent * Component = + CastChecked< const UHoudiniHandleComponent >( VisProxy->Component.Get() ); + + EditedComponent = const_cast< UHoudiniHandleComponent * >( Component ); + + if ( Component ) + { + if ( VisProxy->IsA( HHoudiniHandleVisProxy::StaticGetType() ) ) + bEditing = true; + + bAllowTranslate = + Component->XformParms[ UHoudiniHandleComponent::EXformParameter::TX ].AssetParameter || + Component->XformParms[ UHoudiniHandleComponent::EXformParameter::TY ].AssetParameter || + Component->XformParms[ UHoudiniHandleComponent::EXformParameter::TZ ].AssetParameter; + + bAllowRotation = + Component->XformParms[ UHoudiniHandleComponent::EXformParameter::RX ].AssetParameter || + Component->XformParms[ UHoudiniHandleComponent::EXformParameter::RY ].AssetParameter || + Component->XformParms[ UHoudiniHandleComponent::EXformParameter::RZ ].AssetParameter; + + bAllowScale = + Component->XformParms[ UHoudiniHandleComponent::EXformParameter::SX ].AssetParameter || + Component->XformParms[ UHoudiniHandleComponent::EXformParameter::SY ].AssetParameter || + Component->XformParms[ UHoudiniHandleComponent::EXformParameter::SZ ].AssetParameter; + } + } + + return bEditing; +} + +void +FHoudiniHandleComponentVisualizer::EndEditing() +{ + EditedComponent = nullptr; +} + +bool +FHoudiniHandleComponentVisualizer::GetWidgetLocation( + const FEditorViewportClient * ViewportClient, + FVector & OutLocation ) const +{ + if ( EditedComponent ) + { + OutLocation = EditedComponent->GetComponentTransform().GetLocation(); + return true; + } + + return false; +} + +bool +FHoudiniHandleComponentVisualizer::GetCustomInputCoordinateSystem( + const FEditorViewportClient * ViewportClient, + FMatrix & OutMatrix ) const +{ + if ( EditedComponent && ViewportClient->GetWidgetMode() == FWidget::WM_Scale ) + { + OutMatrix = FRotationMatrix::Make( EditedComponent->GetComponentTransform().GetRotation() ); + return true; + } + else + { + return false; + } +} + +bool +FHoudiniHandleComponentVisualizer::HandleInputDelta( + FEditorViewportClient * ViewportClient, FViewport * Viewport, + FVector& DeltaTranslate, FRotator & DeltaRotate, FVector & DeltaScale ) +{ + if ( !EditedComponent ) + return false; + + if ( !DeltaTranslate.IsZero() && bAllowTranslate ) + { + EditedComponent->SetWorldLocation( EditedComponent->GetComponentTransform().GetLocation() + DeltaTranslate ); + } + + if ( !DeltaRotate.IsZero() && bAllowRotation ) + { + EditedComponent->SetWorldRotation( DeltaRotate.Quaternion() * EditedComponent->GetComponentTransform().GetRotation() ); + } + + if ( !DeltaScale.IsZero() && bAllowScale ) + { + EditedComponent->SetWorldScale3D( EditedComponent->GetComponentTransform().GetScale3D() + DeltaScale ); + } + + return true; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h new file mode 100644 index 00000000..48c5821c --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h @@ -0,0 +1,115 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#pragma once + +#include "HoudiniHandleComponent.h" +#include "ComponentVisualizer.h" +#include "Framework/Commands/Commands.h" +#include "Framework/Commands/UICommandList.h" + +/** Base class for clickable editing proxies. **/ +struct HHoudiniHandleVisProxy : public HComponentVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniHandleVisProxy( const UActorComponent * InComponent ); +}; + +/** Define commands for our component visualizer */ +class FHoudiniHandleComponentVisualizerCommands : public TCommands< FHoudiniHandleComponentVisualizerCommands > +{ + public: + + /** Constructor. **/ + FHoudiniHandleComponentVisualizerCommands(); + + /** Register commands. **/ + virtual void RegisterCommands() override; + + public: + + /** Command for adding a control point. **/ + TSharedPtr< FUICommandInfo > CommandAddControlPoint; + + /** Command for deleting a control point. **/ + TSharedPtr< FUICommandInfo > CommandDeleteControlPoint; +}; + + +/** Our handle visualizer. **/ +class FHoudiniHandleComponentVisualizer : public FComponentVisualizer +{ + public: + + FHoudiniHandleComponentVisualizer(); + virtual ~FHoudiniHandleComponentVisualizer(); + + /** FComponentVisualizer methods. **/ + public: + + /** Draw visualization for the given component. **/ + virtual void DrawVisualization( + const UActorComponent * Component, const FSceneView * View, + FPrimitiveDrawInterface * PDI) override; + + /** Handle a click on a registered hit box. **/ + virtual bool VisProxyHandleClick( + FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click ) override; + + /** Called when editing is no longer being performed. **/ + virtual void EndEditing() override; + + /** Returns location of a gizmo widget. **/ + virtual bool GetWidgetLocation( + const FEditorViewportClient *, FVector & OutLocation) const override; + + virtual bool GetCustomInputCoordinateSystem( + const FEditorViewportClient * ViewportClient, FMatrix & OutMatrix) const override; + + /** Handle input change. **/ + virtual bool HandleInputDelta( + FEditorViewportClient *, FViewport *, FVector & DeltaTranslate, + FRotator & DeltaRotate, FVector & DeltaScale) override; + + virtual bool HandleInputKey( FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event ) override; + + protected: + /** Visualizer actions. **/ + TSharedPtr< FUICommandList > VisualizerActions; + + /** Houdini component which is being edited. **/ + UHoudiniHandleComponent * EditedComponent; + + /** Is set to true if we are editing. **/ + uint32 bEditing : 1; + uint32 bAllowTranslate : 1; + uint32 bAllowRotation : 1; + uint32 bAllowScale : 1; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp new file mode 100644 index 00000000..43d51bd0 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp @@ -0,0 +1,3300 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniParameterDetails.h" + +#include "HoudiniApi.h" +#include "HoudiniAssetComponentDetails.h" +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetInput.h" +#include "HoudiniAssetInstanceInput.h" +#include "HoudiniAssetInstanceInputField.h" +#include "HoudiniAssetParameterButton.h" +#include "HoudiniAssetParameterChoice.h" +#include "HoudiniAssetParameterColor.h" +#include "HoudiniAssetParameterFile.h" +#include "HoudiniAssetParameterFolder.h" +#include "HoudiniAssetParameterFolderList.h" +#include "HoudiniAssetParameterFloat.h" +#include "HoudiniAssetParameterInt.h" +#include "HoudiniAssetParameterLabel.h" +#include "HoudiniAssetParameterMultiparm.h" +#include "HoudiniAssetParameterRamp.h" +#include "HoudiniAssetParameterSeparator.h" +#include "HoudiniAssetParameterString.h" +#include "HoudiniAssetParameterToggle.h" +#include "HoudiniRuntimeSettings.h" +#include "SNewFilePathPicker.h" + +#include "Editor/CurveEditor/Public/CurveEditorSettings.h" +#include "DetailLayoutBuilder.h" +#include "Editor/SceneOutliner/Public/SceneOutlinerModule.h" +#include "Editor/SceneOutliner/Public/SceneOutlinerPublicTypes.h" +#include "Editor/UnrealEd/Public/AssetThumbnail.h" +#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" +#include "Editor/PropertyEditor/Private/PropertyNode.h" +#include "Editor/PropertyEditor/Private/SDetailsViewBase.h" +#include "EditorDirectories.h" +#include "Engine/Selection.h" +#include "Engine/SkeletalMesh.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Internationalization/Internationalization.h" +#include "IDetailGroup.h" +#include "Particles/ParticleSystemComponent.h" +#include "SCurveEditor.h" +#include "SAssetDropTarget.h" +#include "Sound/SoundBase.h" +#include "Math/UnitConversion.h" +#include "Math/NumericLimits.h" +#include "Misc/Optional.h" +#include "Widgets/Colors/SColorPicker.h" +#include "Widgets/Colors/SColorBlock.h" +#include "Widgets/Input/NumericUnitTypeInterface.inl" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Input/SRotatorInputBox.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "EngineUtils.h" + + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +void +FHoudiniParameterDetails::CreateNameWidget( UHoudiniAssetParameter* InParam, FDetailWidgetRow & Row, bool WithLabel ) +{ + if ( !InParam || InParam->IsPendingKill() ) + return; + + FText ParameterLabelText = FText::FromString( InParam->GetParameterLabel() ); + const FText & FinalParameterLabelText = WithLabel ? ParameterLabelText : FText::GetEmpty(); + + FText ParameterTooltip = GetParameterTooltip( InParam ); + if ( InParam->bIsChildOfMultiparm && InParam->ParentParameter && !InParam->ParentParameter->IsPendingKill() ) + { + TSharedRef< SHorizontalBox > HorizontalBox = SNew( SHorizontalBox ); + + // We have to make sure the ParentParameter is a multiparm, as folders will cause issues here + // ( we want to call RemoveMultiParmInstance or AddMultiParmInstance on the parent multiparm, not just the parent) + UHoudiniAssetParameter * ParentMultiparm = InParam->ParentParameter; + while ( ParentMultiparm && !ParentMultiparm->IsPendingKill() && !ParentMultiparm->bIsMultiparm ) + ParentMultiparm = ParentMultiparm->ParentParameter; + + // Failed to find the multiparm parent, better have the original parent than nullptr + if ( !ParentMultiparm || ParentMultiparm->IsPendingKill() ) + ParentMultiparm = InParam->ParentParameter; + + TSharedRef< SWidget > ClearButton = PropertyCustomizationHelpers::MakeClearButton( + FSimpleDelegate::CreateUObject( + (UHoudiniAssetParameterMultiparm *)ParentMultiparm, + &UHoudiniAssetParameterMultiparm::RemoveMultiparmInstance, + InParam->MultiparmInstanceIndex ), + LOCTEXT( "RemoveMultiparmInstanceToolTip", "Remove" ) ); + TSharedRef< SWidget > AddButton = PropertyCustomizationHelpers::MakeAddButton( + FSimpleDelegate::CreateUObject( + (UHoudiniAssetParameterMultiparm *)ParentMultiparm, + &UHoudiniAssetParameterMultiparm::AddMultiparmInstance, + InParam->MultiparmInstanceIndex ), + LOCTEXT( "InsertBeforeMultiparmInstanceToolTip", "Insert Before" ) ); + + if ( InParam->ChildIndex != 0 ) + { + AddButton.Get().SetVisibility( EVisibility::Hidden ); + ClearButton.Get().SetVisibility( EVisibility::Hidden ); + } + + // Adding eventual padding for nested multiparams + UHoudiniAssetParameter* currentParentParameter = ParentMultiparm; + while ( currentParentParameter && currentParentParameter->bIsChildOfMultiparm ) + { + if ( currentParentParameter->bIsMultiparm ) + HorizontalBox->AddSlot().MaxWidth( 16.0f ); + + currentParentParameter = currentParentParameter->ParentParameter; + } + + HorizontalBox->AddSlot().AutoWidth().Padding( 2.0f, 0.0f ) + [ + ClearButton + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding( 0.0f, 0.0f ) + [ + AddButton + ]; + + HorizontalBox->AddSlot().Padding( 2, 5, 5, 2 ) + [ + SNew( STextBlock ) + .Text( FinalParameterLabelText ) + .ToolTipText( WithLabel ? ParameterTooltip : ParameterLabelText ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ]; + + Row.NameWidget.Widget = HorizontalBox; + } + else + { + Row.NameWidget.Widget = + SNew( STextBlock ) + .Text( FinalParameterLabelText ) + .ToolTipText( ParameterTooltip ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ); + } +} + +FText +FHoudiniParameterDetails::GetParameterTooltip( UHoudiniAssetParameter* InParam ) +{ + if ( !InParam ) + return FText(); + + /* + FString Tooltip = InParam->GetParameterLabel() + TEXT(" (") + InParam->GetParameterName() + TEXT(")"); + if ( !InParam->GetParameterHelp().IsEmpty() ) + Tooltip += TEXT ( ":\n") + InParam->GetParameterHelp(); + + return FText::FromString( Tooltip ); + */ + + if ( !InParam->GetParameterHelp().IsEmpty() ) + return FText::FromString( InParam->GetParameterHelp() ); + else + return FText::FromString( InParam->GetParameterLabel() + TEXT( " (" ) + InParam->GetParameterName() + TEXT( ")" ) ); +} + +void +FHoudiniParameterDetails::CreateWidget( IDetailCategoryBuilder & LocalDetailCategoryBuilder, UHoudiniAssetParameter* InParam ) +{ + if( !InParam || InParam->IsPendingKill() ) + return; + + if ( auto ParamFloat = Cast( InParam ) ) + { + CreateWidgetFloat( LocalDetailCategoryBuilder, *ParamFloat ); + } + else if ( auto ParamFolder = Cast( InParam ) ) + { + CreateWidgetFolder( LocalDetailCategoryBuilder, *ParamFolder ); + } + else if ( auto ParamFolderList = Cast( InParam ) ) + { + CreateWidgetFolderList( LocalDetailCategoryBuilder, *ParamFolderList ); + } + // Test Ramp before Multiparm! + else if ( auto ParamRamp = Cast( InParam ) ) + { + CreateWidgetRamp( LocalDetailCategoryBuilder, *ParamRamp ); + } + else if ( auto ParamMultiparm = Cast( InParam ) ) + { + CreateWidgetMultiparm( LocalDetailCategoryBuilder, *ParamMultiparm ); + } + else if ( auto ParamButton = Cast( InParam ) ) + { + CreateWidgetButton( LocalDetailCategoryBuilder, *ParamButton ); + } + else if ( auto ParamChoice = Cast( InParam ) ) + { + CreateWidgetChoice( LocalDetailCategoryBuilder, *ParamChoice ); + } + else if ( auto ParamColor = Cast( InParam ) ) + { + CreateWidgetColor( LocalDetailCategoryBuilder, *ParamColor ); + } + else if ( auto ParamToggle = Cast( InParam ) ) + { + CreateWidgetToggle( LocalDetailCategoryBuilder, *ParamToggle ); + } + else if ( auto ParamInput = Cast( InParam ) ) + { + CreateWidgetInput( LocalDetailCategoryBuilder, *ParamInput ); + } + else if ( auto ParamInt = Cast( InParam ) ) + { + CreateWidgetInt( LocalDetailCategoryBuilder, *ParamInt ); + } + else if ( auto ParamInstanceInput = Cast( InParam ) ) + { + CreateWidgetInstanceInput( LocalDetailCategoryBuilder, *ParamInstanceInput ); + } + else if ( auto ParamLabel = Cast( InParam ) ) + { + CreateWidgetLabel( LocalDetailCategoryBuilder, *ParamLabel ); + } + else if ( auto ParamString = Cast( InParam ) ) + { + CreateWidgetString( LocalDetailCategoryBuilder, *ParamString ); + } + else if ( auto ParamSeparator = Cast( InParam ) ) + { + CreateWidgetSeparator( LocalDetailCategoryBuilder, *ParamSeparator ); + } + else if ( auto ParamFile = Cast( InParam ) ) + { + CreateWidgetFile( LocalDetailCategoryBuilder, *ParamFile ); + } + else + { + check( false ); + } +} + +void +FHoudiniParameterDetails::CreateWidget( TSharedPtr< SVerticalBox > VerticalBox, class UHoudiniAssetParameter* InParam ) +{ + check( InParam ); + + if ( auto ParamChoice = Cast( InParam ) ) + { + CreateWidgetChoice( VerticalBox, *ParamChoice ); + } + else if ( auto ParamToggle = Cast( InParam ) ) + { + CreateWidgetToggle( VerticalBox, *ParamToggle ); + } + else + { + check( false ); + } +} + +void +FHoudiniParameterDetails::CreateWidgetFile( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterFile& InParam ) +{ + FDetailWidgetRow& Row = LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + + // Create the standard parameter name widget. + CreateNameWidget( &InParam, Row, true ); + + TSharedRef VerticalBox = SNew( SVerticalBox ); + + FString FileTypeWidgetFilter = TEXT( "All files (*.*)|*.*" ); + if ( !InParam.Filters.IsEmpty() ) + FileTypeWidgetFilter = FString::Printf( TEXT( "%s files (*.%s)|*.%s" ), *InParam.Filters, *InParam.Filters, *InParam.Filters ); + + FString BrowseWidgetDirectory = FEditorDirectories::Get().GetLastDirectory( ELastDirectory::GENERIC_OPEN ); + + for ( int32 Idx = 0; Idx < InParam.GetTupleSize(); ++Idx ) + { + FString FileWidgetPath = InParam.Values[Idx]; + FString FileWidgetBrowsePath = BrowseWidgetDirectory; + + if ( !FileWidgetPath.IsEmpty() ) + { + FString FileWidgetDirPath = FPaths::GetPath( FileWidgetPath ); + if ( !FileWidgetDirPath.IsEmpty() ) + FileWidgetBrowsePath = FileWidgetDirPath; + } + + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ) + [ + SNew( SNewFilePathPicker ) + .BrowseButtonImage( FEditorStyle::GetBrush( "PropertyWindow.Button_Ellipsis" ) ) + .BrowseButtonStyle( FEditorStyle::Get(), "HoverHintOnly" ) + .BrowseButtonToolTip( LOCTEXT( "FileButtonToolTipText", "Choose a file" ) ) + .BrowseDirectory( FileWidgetBrowsePath ) + .BrowseTitle( LOCTEXT( "PropertyEditorTitle", "File picker..." ) ) + .FilePath( FileWidgetPath ) + .FileTypeFilter( FileTypeWidgetFilter ) + .OnPathPicked( FOnPathPicked::CreateUObject( + &InParam, &UHoudiniAssetParameterFile::HandleFilePathPickerPathPicked, Idx ) ) + .IsNewFile( !InParam.IsReadOnly ) + ]; + + } + + Row.ValueWidget.Widget = VerticalBox; + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); + + Row.ValueWidget.Widget->SetEnabled( !InParam.bIsDisabled ); +} + +void +FHoudiniParameterDetails::CreateWidgetFolder( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterFolder& InParam ) +{ + if ( InParam.ParentParameter && !InParam.ParentParameter->IsPendingKill() && InParam.ParentParameter->IsActiveChildParameter( &InParam ) ) + { + // Recursively create all child parameters. + for ( UHoudiniAssetParameter * ChildParam : InParam.ChildParameters ) + { + if ( ChildParam && !ChildParam->IsPendingKill() ) + FHoudiniParameterDetails::CreateWidget(LocalDetailCategoryBuilder, ChildParam); + } + + } +} + +void +FHoudiniParameterDetails::CreateWidgetFolderList( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterFolderList& InParam ) +{ + TWeakObjectPtr MyParam( &InParam ); + TSharedRef< SHorizontalBox > HorizontalBox = SNew( SHorizontalBox ); + + LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ]; + + for ( int32 ParameterIdx = 0; ParameterIdx < InParam.ChildParameters.Num(); ++ParameterIdx ) + { + UHoudiniAssetParameter * HoudiniAssetParameterChild = InParam.ChildParameters[ ParameterIdx ]; + if ( !HoudiniAssetParameterChild || HoudiniAssetParameterChild->IsPendingKill() ) + continue; + + if ( HoudiniAssetParameterChild->IsA( UHoudiniAssetParameterFolder::StaticClass() ) ) + { + FText ParameterLabelText = FText::FromString( HoudiniAssetParameterChild->GetParameterLabel() ); + FText ParameterToolTip = GetParameterTooltip( HoudiniAssetParameterChild ); + + HorizontalBox->AddSlot().Padding( 0, 2, 0, 2 ) + [ + SNew( SButton ) + .VAlign( VAlign_Center ) + .HAlign( HAlign_Center ) + .Text( ParameterLabelText ) + .ToolTipText( ParameterToolTip ) + .OnClicked( FOnClicked::CreateLambda( [=]() { + if ( MyParam.IsValid() ) + { + MyParam->ActiveChildParameter = ParameterIdx; + MyParam->OnParamStateChanged(); + } + return FReply::Handled(); + })) + ]; + } + } + + // Recursively create all child parameters. + for (UHoudiniAssetParameter * ChildParam : InParam.ChildParameters) + { + if ( ChildParam && !ChildParam->IsPendingKill() ) + FHoudiniParameterDetails::CreateWidget(LocalDetailCategoryBuilder, ChildParam); + } + + if ( InParam.ChildParameters.Num() > 1 ) + { + TSharedPtr< STextBlock > TextBlock; + + LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ) + [ + SAssignNew( TextBlock, STextBlock ) + .Text( FText::GetEmpty() ) + .ToolTipText( FText::GetEmpty() ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + .WrapTextAt( HAPI_UNREAL_DESIRED_ROW_FULL_WIDGET_WIDTH ) + ]; + + if ( TextBlock.IsValid() ) + TextBlock->SetEnabled( !InParam.bIsDisabled ); + } +} + +void +FHoudiniParameterDetails::CreateWidgetMultiparm( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterMultiparm& InParam ) +{ + FDetailWidgetRow & Row = LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + + // Create the standard parameter name widget. + CreateNameWidget( &InParam, Row, true ); + + TSharedRef< SHorizontalBox > HorizontalBox = SNew( SHorizontalBox ); + + TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; + + HorizontalBox->AddSlot().Padding( 2, 2, 5, 2 ) + [ + SAssignNew( NumericEntryBox, SNumericEntryBox< int32 > ) + .AllowSpin( true ) + + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + + .Value( TAttribute< TOptional< int32 > >::Create( TAttribute< TOptional< int32 > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetParameterMultiparm::GetValue ) ) ) + .OnValueChanged( SNumericEntryBox::FOnValueChanged::CreateUObject( + &InParam, &UHoudiniAssetParameterMultiparm::SetValue ) ) + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding( 2.0f, 0.0f ) + [ + PropertyCustomizationHelpers::MakeAddButton( FSimpleDelegate::CreateUObject( + &InParam, &UHoudiniAssetParameterMultiparm::AddElement, true, true ), + LOCTEXT( "AddAnotherMultiparmInstanceToolTip", "Add Another Instance" ) ) + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding( 2.0f, 0.0f ) + [ + PropertyCustomizationHelpers::MakeRemoveButton( FSimpleDelegate::CreateUObject( + &InParam, &UHoudiniAssetParameterMultiparm::RemoveElement, true, true ), + LOCTEXT( "RemoveLastMultiparmInstanceToolTip", "Remove Last Instance" ) ) + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding( 2.0f, 0.0f ) + [ + PropertyCustomizationHelpers::MakeEmptyButton( FSimpleDelegate::CreateUObject( + &InParam, &UHoudiniAssetParameterMultiparm::SetValue, 0 ), + LOCTEXT( "ClearAllMultiparmInstanesToolTip", "Clear All Instances" ) ) + ]; + + if ( NumericEntryBox.IsValid() ) + NumericEntryBox->SetEnabled( !InParam.bIsDisabled ); + + Row.ValueWidget.Widget = HorizontalBox; + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); + + // Recursively create all child parameters. + for ( UHoudiniAssetParameter * ChildParam : InParam.ChildParameters ) + if ( ChildParam && !ChildParam->IsPendingKill() ) + FHoudiniParameterDetails::CreateWidget( LocalDetailCategoryBuilder, ChildParam ); +} + +/** We need to inherit from curve editor in order to get subscription to mouse events. **/ +class SHoudiniAssetParameterRampCurveEditor : public SCurveEditor +{ +public: + + SLATE_BEGIN_ARGS( SHoudiniAssetParameterRampCurveEditor ) + : _ViewMinInput( 0.0f ) + , _ViewMaxInput( 10.0f ) + , _ViewMinOutput( 0.0f ) + , _ViewMaxOutput( 1.0f ) + , _InputSnap( 0.1f ) + , _OutputSnap( 0.05f ) + , _InputSnappingEnabled( false ) + , _OutputSnappingEnabled( false ) + , _ShowTimeInFrames( false ) + , _TimelineLength( 5.0f ) + , _DesiredSize( FVector2D::ZeroVector ) + , _DrawCurve( true ) + , _HideUI( true ) + , _AllowZoomOutput( true ) + , _AlwaysDisplayColorCurves( false ) + , _ZoomToFitVertical( true ) + , _ZoomToFitHorizontal( true ) + , _ShowZoomButtons( true ) + , _XAxisName() + , _YAxisName() + , _ShowInputGridNumbers( true ) + , _ShowOutputGridNumbers( true ) + , _ShowCurveSelector( true ) + , _GridColor( FLinearColor( 0.0f, 0.0f, 0.0f, 0.3f ) ) + { + _Clipping = EWidgetClipping::ClipToBounds; + } + + SLATE_ATTRIBUTE(float, ViewMinInput) + SLATE_ATTRIBUTE(float, ViewMaxInput) + SLATE_ATTRIBUTE(TOptional, DataMinInput) + SLATE_ATTRIBUTE(TOptional, DataMaxInput) + SLATE_ATTRIBUTE(float, ViewMinOutput) + SLATE_ATTRIBUTE(float, ViewMaxOutput) + SLATE_ATTRIBUTE(float, InputSnap) + SLATE_ATTRIBUTE(float, OutputSnap) + SLATE_ATTRIBUTE(bool, InputSnappingEnabled) + SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) + SLATE_ATTRIBUTE(bool, ShowTimeInFrames) + SLATE_ATTRIBUTE(float, TimelineLength) + SLATE_ATTRIBUTE(FVector2D, DesiredSize) + SLATE_ATTRIBUTE(bool, AreCurvesVisible) + SLATE_ARGUMENT(bool, DrawCurve) + SLATE_ARGUMENT(bool, HideUI) + SLATE_ARGUMENT(bool, AllowZoomOutput) + SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) + SLATE_ARGUMENT(bool, ZoomToFitVertical) + SLATE_ARGUMENT(bool, ZoomToFitHorizontal) + SLATE_ARGUMENT(bool, ShowZoomButtons) + SLATE_ARGUMENT(TOptional, XAxisName) + SLATE_ARGUMENT(TOptional, YAxisName) + SLATE_ARGUMENT(bool, ShowInputGridNumbers) + SLATE_ARGUMENT(bool, ShowOutputGridNumbers) + SLATE_ARGUMENT(bool, ShowCurveSelector) + SLATE_ARGUMENT(FLinearColor, GridColor) + SLATE_EVENT( FOnSetInputViewRange, OnSetInputViewRange ) + SLATE_EVENT( FOnSetOutputViewRange, OnSetOutputViewRange ) + SLATE_EVENT( FOnSetAreCurvesVisible, OnSetAreCurvesVisible ) + SLATE_EVENT( FSimpleDelegate, OnCreateAsset ) + SLATE_END_ARGS() + +public: + + /** Widget construction. **/ + void Construct( const FArguments & InArgs ); + +protected: + + /** Handle mouse up events. **/ + virtual FReply OnMouseButtonUp( const FGeometry & MyGeometry, const FPointerEvent & MouseEvent ) override; + +public: + + /** Set parent ramp parameter. **/ + void SetParentRampParameter( UHoudiniAssetParameterRamp * InHoudiniAssetParameterRamp ) + { + HoudiniAssetParameterRamp = InHoudiniAssetParameterRamp; + } + +protected: + + /** Parent ramp parameter. **/ + TWeakObjectPtr HoudiniAssetParameterRamp; +}; + +void +SHoudiniAssetParameterRampCurveEditor::Construct( const FArguments & InArgs ) +{ + SCurveEditor::Construct( SCurveEditor::FArguments() + .ViewMinInput( InArgs._ViewMinInput ) + .ViewMaxInput( InArgs._ViewMaxInput ) + .ViewMinOutput( InArgs._ViewMinOutput ) + .ViewMaxOutput( InArgs._ViewMaxOutput ) + .XAxisName( InArgs._XAxisName ) + .YAxisName( InArgs._YAxisName ) + .HideUI( InArgs._HideUI ) + .DrawCurve( InArgs._DrawCurve ) + .TimelineLength( InArgs._TimelineLength ) + .AllowZoomOutput( InArgs._AllowZoomOutput ) + .ShowInputGridNumbers( InArgs._ShowInputGridNumbers ) + .ShowOutputGridNumbers( InArgs._ShowOutputGridNumbers ) + .ShowZoomButtons( InArgs._ShowZoomButtons ) + .ZoomToFitHorizontal( InArgs._ZoomToFitHorizontal ) + .ZoomToFitVertical( InArgs._ZoomToFitVertical ) + ); + + HoudiniAssetParameterRamp = nullptr; + + UCurveEditorSettings * CurveEditorSettings = GetSettings(); + if ( CurveEditorSettings ) + { + //CurveEditorSettings->SetCurveVisibility( ECurveEditorCurveVisibility::AllCurves ); + CurveEditorSettings->SetTangentVisibility( ECurveEditorTangentVisibility::NoTangents ); + } +} + +FReply +SHoudiniAssetParameterRampCurveEditor::OnMouseButtonUp( + const FGeometry & MyGeometry, + const FPointerEvent & MouseEvent ) +{ + FReply Reply = SCurveEditor::OnMouseButtonUp( MyGeometry, MouseEvent ); + + if ( HoudiniAssetParameterRamp.IsValid() ) + HoudiniAssetParameterRamp->OnCurveEditingFinished(); + + return Reply; +} + +void +FHoudiniParameterDetails::CreateWidgetRamp( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterRamp& InParam ) +{ + TWeakObjectPtr MyParam( &InParam ); + FDetailWidgetRow & Row = LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + + // Create the standard parameter name widget. + CreateNameWidget( &InParam, Row, true ); + + TSharedRef< SHorizontalBox > HorizontalBox = SNew( SHorizontalBox ); + + FString CurveAxisTextX = TEXT( "" ); + FString CurveAxisTextY = TEXT( "" ); + UClass * CurveClass = nullptr; + + if ( InParam.bIsFloatRamp ) + { + CurveAxisTextX = TEXT( HAPI_UNREAL_RAMP_FLOAT_AXIS_X ); + CurveAxisTextY = TEXT( HAPI_UNREAL_RAMP_FLOAT_AXIS_Y ); + CurveClass = UHoudiniAssetParameterRampCurveFloat::StaticClass(); + } + else + { + CurveAxisTextX = TEXT( HAPI_UNREAL_RAMP_COLOR_AXIS_X ); + CurveAxisTextY = TEXT( HAPI_UNREAL_RAMP_COLOR_AXIS_Y ); + CurveClass = UHoudiniAssetParameterRampCurveColor::StaticClass(); + } + + HorizontalBox->AddSlot().Padding( 2, 2, 5, 2 ) + [ + SNew( SBorder ) + .VAlign( VAlign_Fill ) + [ + SAssignNew( InParam.CurveEditor, SHoudiniAssetParameterRampCurveEditor ) + .ViewMinInput( 0.0f ) + .ViewMaxInput( 1.0f ) + .HideUI( true ) + .DrawCurve( true ) + .ViewMinInput( 0.0f ) + .ViewMaxInput( 1.0f ) + .ViewMinOutput( 0.0f ) + .ViewMaxOutput( 1.0f ) + .TimelineLength( 1.0f ) + .AllowZoomOutput( false ) + .ShowInputGridNumbers( false ) + .ShowOutputGridNumbers( false ) + .ShowZoomButtons( false ) + .ZoomToFitHorizontal( false ) + .ZoomToFitVertical( false ) + .XAxisName( CurveAxisTextX ) + .YAxisName( CurveAxisTextY ) + .ShowCurveSelector( false ) + ] + ]; + + // Set callback for curve editor events. + TSharedPtr< SHoudiniAssetParameterRampCurveEditor > HoudiniRampEditor = StaticCastSharedPtr(InParam.CurveEditor ); + if ( HoudiniRampEditor.IsValid() ) + HoudiniRampEditor->SetParentRampParameter( &InParam ); + + if ( InParam.bIsFloatRamp ) + { + if ( !InParam.HoudiniAssetParameterRampCurveFloat + && (InParam.PrimaryObject && !InParam.PrimaryObject->IsPendingKill() ) ) + { + InParam.HoudiniAssetParameterRampCurveFloat = Cast< UHoudiniAssetParameterRampCurveFloat >( + NewObject< UHoudiniAssetParameterRampCurveFloat >( + InParam.PrimaryObject, UHoudiniAssetParameterRampCurveFloat::StaticClass(), + NAME_None, RF_Transactional | RF_Public ) ); + + InParam.HoudiniAssetParameterRampCurveFloat->SetParentRampParameter( &InParam ); + } + + // Set curve values. + InParam.GenerateCurvePoints(); + + // Set the curve that is being edited. + InParam.CurveEditor->SetCurveOwner( InParam.HoudiniAssetParameterRampCurveFloat, true ); + } + else + { + if ( !InParam.HoudiniAssetParameterRampCurveColor + && ( InParam.PrimaryObject && !InParam.PrimaryObject->IsPendingKill() ) ) + { + InParam.HoudiniAssetParameterRampCurveColor = Cast< UHoudiniAssetParameterRampCurveColor >( + NewObject< UHoudiniAssetParameterRampCurveColor >( + InParam.PrimaryObject, UHoudiniAssetParameterRampCurveColor::StaticClass(), + NAME_None, RF_Transactional | RF_Public ) ); + + InParam.HoudiniAssetParameterRampCurveColor->SetParentRampParameter( &InParam ); + } + + // Set curve values. + InParam.GenerateCurvePoints(); + + // Set the curve that is being edited. + InParam.CurveEditor->SetCurveOwner( InParam.HoudiniAssetParameterRampCurveColor, true ); + } + + Row.ValueWidget.Widget = HorizontalBox; + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); + + // Recursively create all child parameters. + for ( UHoudiniAssetParameter * ChildParam : InParam.ChildParameters ) + if ( ChildParam && !ChildParam->IsPendingKill() ) + FHoudiniParameterDetails::CreateWidget( LocalDetailCategoryBuilder, ChildParam ); +} + +void +FHoudiniParameterDetails::CreateWidgetButton( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterButton& InParam ) +{ + TWeakObjectPtr MyParam( &InParam ); + FDetailWidgetRow& Row = LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + + // Create the standard parameter name widget. + CreateNameWidget( &InParam, Row, true ); + + FText ParameterLabelText = FText::FromString( InParam.GetParameterLabel() ); + FText ParameterTooltip = GetParameterTooltip( &InParam ); + + TSharedRef< SHorizontalBox > HorizontalBox = SNew( SHorizontalBox ); + TSharedPtr< SButton > Button; + + HorizontalBox->AddSlot().Padding( 1, 2, 4, 2 ) + [ + SAssignNew( Button, SButton ) + .VAlign( VAlign_Center ) + .HAlign( HAlign_Center ) + .Text( ParameterLabelText ) + .ToolTipText( ParameterTooltip ) + .OnClicked( FOnClicked::CreateLambda( [=]() { + if ( MyParam.IsValid() ) + { + // There's no undo operation for button. + MyParam->MarkChanged(); + } + return FReply::Handled(); + })) + ]; + + if ( Button.IsValid() ) + Button->SetEnabled( !InParam.bIsDisabled ); + + Row.ValueWidget.Widget = HorizontalBox; + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); + +} + +void +FHoudiniParameterDetails::CreateWidgetChoice( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterChoice& InParam ) +{ + FDetailWidgetRow & Row = LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + + // Create the standard parameter name widget. + CreateNameWidget( &InParam, Row, true ); + + TSharedRef< SHorizontalBox > HorizontalBox = SNew( SHorizontalBox ); + TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBox; + + TWeakObjectPtr MyParam( &InParam ); + + HorizontalBox->AddSlot().Padding( 2, 2, 5, 2 ) + [ + SAssignNew( ComboBox, SComboBox< TSharedPtr< FString > > ) + .OptionsSource( &InParam.StringChoiceLabels ) + .InitiallySelectedItem( InParam.StringChoiceLabels[InParam.CurrentValue] ) + .OnGenerateWidget( SComboBox< TSharedPtr< FString > >::FOnGenerateWidget::CreateLambda( + []( TSharedPtr< FString > ChoiceEntry ) { + FText ChoiceEntryText = FText::FromString( *ChoiceEntry ); + return SNew( STextBlock ) + .Text( ChoiceEntryText ) + .ToolTipText( ChoiceEntryText ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ); + })) + .OnSelectionChanged( SComboBox< TSharedPtr< FString > >::FOnSelectionChanged::CreateLambda( + [=]( TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType ) { + if ( !NewChoice.IsValid() || !MyParam.IsValid() ) + return; + MyParam->OnChoiceChange( NewChoice ); + })) + [ + SNew( STextBlock ) + .Text( TAttribute< FText >::Create( TAttribute< FText >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetParameterChoice::HandleChoiceContentText ) ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ]; + + if ( ComboBox.IsValid() ) + ComboBox->SetEnabled( !InParam.bIsDisabled ); + + Row.ValueWidget.Widget = HorizontalBox; + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); +} + +void +FHoudiniParameterDetails::CreateWidgetChoice( TSharedPtr< SVerticalBox > VerticalBox, class UHoudiniAssetParameterChoice& InParam ) +{ + FText ParameterLabelText = FText::FromString( InParam.GetParameterLabel() ); + FText ParameterTooltip = GetParameterTooltip( &InParam ); + + TWeakObjectPtr MyParam( &InParam ); + + VerticalBox->AddSlot().Padding( 2, 2, 2, 2 ) + [ + SNew( SHorizontalBox ) + + SHorizontalBox::Slot().MaxWidth( 80 ).Padding( 7, 1, 0, 0 ).VAlign( VAlign_Center ) + [ + SNew( STextBlock ) + .Text( ParameterLabelText ) + .ToolTipText( ParameterTooltip ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + [ + SNew( SComboBox< TSharedPtr< FString > > ) + .OptionsSource( &InParam.StringChoiceLabels ) + .InitiallySelectedItem( InParam.StringChoiceLabels[InParam.CurrentValue] ) + .OnGenerateWidget( SComboBox< TSharedPtr< FString > >::FOnGenerateWidget::CreateLambda( + []( TSharedPtr< FString > ChoiceEntry ) { + FText ChoiceEntryText = FText::FromString( *ChoiceEntry ); + return SNew( STextBlock ) + .Text( ChoiceEntryText ) + .ToolTipText( ChoiceEntryText ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ); + })) + .OnSelectionChanged( SComboBox< TSharedPtr< FString > >::FOnSelectionChanged::CreateLambda( + [=]( TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType ) { + if ( !NewChoice.IsValid() || !MyParam.IsValid() ) + return; + MyParam->OnChoiceChange( NewChoice ); + })) + [ + SNew( STextBlock ) + .Text( TAttribute< FText >::Create( TAttribute< FText >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetParameterChoice::HandleChoiceContentText ) ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ] + ]; +} + +void +FHoudiniParameterDetails::CreateWidgetColor( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterColor& InParam ) +{ + TWeakObjectPtr MyParam( &InParam ); + + FDetailWidgetRow & Row = LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + + // Create the standard parameter name widget. + CreateNameWidget( &InParam, Row, true ); + + TSharedPtr< SColorBlock > ColorBlock; + TSharedRef< SVerticalBox > VerticalBox = SNew( SVerticalBox ); + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ) + [ + SAssignNew( ColorBlock, SColorBlock ) + .Color( TAttribute< FLinearColor >::Create( TAttribute< FLinearColor >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetParameterColor::GetColor ) ) ) + .OnMouseButtonDown( FPointerEventHandler::CreateLambda( + [=]( const FGeometry & MyGeometry, const FPointerEvent & MouseEvent ) + { + if ( MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton || !MyParam.IsValid() ) + return FReply::Unhandled(); + + FColorPickerArgs PickerArgs; + PickerArgs.ParentWidget = ColorBlock; + PickerArgs.bUseAlpha = true; + PickerArgs.DisplayGamma = TAttribute< float >::Create( + TAttribute< float >::FGetter::CreateUObject( GEngine, &UEngine::GetDisplayGamma ) ); + PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateUObject( + MyParam.Get(), &UHoudiniAssetParameterColor::OnPaintColorChanged, true, true ); + PickerArgs.InitialColorOverride = MyParam->GetColor(); + PickerArgs.bOnlyRefreshOnOk = true; + OpenColorPicker( PickerArgs ); + return FReply::Handled(); + } )) + ]; + + if ( ColorBlock.IsValid() ) + ColorBlock->SetEnabled( !InParam.bIsDisabled ); + + Row.ValueWidget.Widget = VerticalBox; + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); +} + +void +FHoudiniParameterDetails::CreateWidgetToggle( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterToggle& InParam ) +{ + FDetailWidgetRow & Row = LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + FText ParameterLabelText = FText::FromString( InParam.GetParameterLabel() ); + + // Create the standard parameter name widget. + CreateNameWidget( &InParam, Row, false ); + + TSharedRef< SVerticalBox > VerticalBox = SNew( SVerticalBox ); + + for ( int32 Idx = 0; Idx < InParam.GetTupleSize(); ++Idx ) + { + TSharedPtr< SCheckBox > CheckBox; + + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ) + [ + SAssignNew( CheckBox, SCheckBox ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetParameterToggle::CheckStateChanged, Idx ) ) + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetParameterToggle::IsChecked, Idx ) ) ) + .Content() + [ + SNew( STextBlock ) + .Text( ParameterLabelText ) + .ToolTipText( GetParameterTooltip( &InParam ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ]; + + if ( CheckBox.IsValid() ) + CheckBox->SetEnabled( !InParam.bIsDisabled ); + } + + Row.ValueWidget.Widget = VerticalBox; + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); +} + +void +FHoudiniParameterDetails::CreateWidgetToggle( TSharedPtr< SVerticalBox > VerticalBox, class UHoudiniAssetParameterToggle& InParam ) +{ + FText ParameterLabelText = FText::FromString( InParam.GetParameterLabel() ); + FText ParameterTooltip = GetParameterTooltip( &InParam ); + + for ( int32 Idx = 0; Idx < InParam.GetTupleSize(); ++Idx ) + { + VerticalBox->AddSlot().Padding( 0, 2, 0, 2 ) + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot().MaxWidth( 8 ) + [ + SNew( STextBlock ) + .Text( FText::GetEmpty() ) + .ToolTipText( ParameterLabelText ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + +SHorizontalBox::Slot() + [ + SNew( SCheckBox ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetParameterToggle::CheckStateChanged, Idx ) ) + .IsChecked(TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetParameterToggle::IsChecked, Idx ) ) ) + .Content() + [ + SNew( STextBlock ) + .Text( ParameterLabelText ) + .ToolTipText( ParameterTooltip ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ] + ]; + } +} + + +void +FHoudiniParameterDetails::CreateWidgetFloat( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterFloat& InParam ) +{ + TWeakObjectPtr MyParam( &InParam ); + + /** Should we swap Y and Z fields (only relevant for Vector3) */ + bool SwappedAxis3Vector = false; + if ( auto Settings = UHoudiniRuntimeSettings::StaticClass()->GetDefaultObject() ) + { + SwappedAxis3Vector = InParam.GetTupleSize() == 3 && Settings->ImportAxis == HRSAI_Unreal; + } + + if ( SwappedAxis3Vector ) + { + // Ignore the swapping if that parameter has the noswap tag + if ( InParam.NoSwap ) + SwappedAxis3Vector = false; + } + + FDetailWidgetRow & Row = LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + + // Create the standard parameter name widget. + CreateNameWidget( &InParam, Row, true ); + + // Helper function to find a unit from a string (name or abbreviation) + auto ParmUnit = FUnitConversion::UnitFromString( *InParam.ValueUnit ); + + EUnit Unit = EUnit::Unspecified; + if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) + Unit = ParmUnit.GetValue(); + + TSharedPtr> paramTypeInterface; + paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); + + if ( InParam.GetTupleSize() == 3 ) + { + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X(TAttribute< TOptional< float > >::Create(TAttribute< TOptional< float > >::FGetter::CreateUObject(&InParam, &UHoudiniAssetParameterFloat::GetValue, 0))) + .Y(TAttribute< TOptional< float > >::Create(TAttribute< TOptional< float > >::FGetter::CreateUObject(&InParam, &UHoudiniAssetParameterFloat::GetValue, SwappedAxis3Vector ? 2 : 1))) + .Z(TAttribute< TOptional< float > >::Create(TAttribute< TOptional< float > >::FGetter::CreateUObject(&InParam, &UHoudiniAssetParameterFloat::GetValue, SwappedAxis3Vector ? 1 : 2))) + .OnXCommitted(FOnFloatValueCommitted::CreateLambda( + [=](float Val, ETextCommit::Type TextCommitType) { + MyParam->SetValue(Val, 0, true, true); + })) + .OnYCommitted(FOnFloatValueCommitted::CreateLambda( + [=](float Val, ETextCommit::Type TextCommitType) { + MyParam->SetValue(Val, SwappedAxis3Vector ? 2 : 1, true, true); + })) + .OnZCommitted(FOnFloatValueCommitted::CreateLambda( + [=](float Val, ETextCommit::Type TextCommitType) { + MyParam->SetValue(Val, SwappedAxis3Vector ? 1 : 2, true, true); + })) + .TypeInterface(paramTypeInterface) + ] + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("ResetToBase", "Reset to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked(FOnClicked::CreateUObject( + &InParam, &UHoudiniAssetParameter::OnRevertParmToDefault, -1)) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + Row.ValueWidget.Widget = VerticalBox; + } + else + { + TSharedRef< SVerticalBox > VerticalBox = SNew( SVerticalBox ); + + for ( int32 Idx = 0; Idx < InParam.GetTupleSize(); ++Idx ) + { + TSharedPtr< SNumericEntryBox< float > > NumericEntryBox; + + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SAssignNew( NumericEntryBox, SNumericEntryBox< float > ) + .AllowSpin( true ) + + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + + .MinValue( InParam.ValueMin ) + .MaxValue( InParam.ValueMax ) + + .MinSliderValue( InParam.ValueUIMin ) + .MaxSliderValue( InParam.ValueUIMax ) + + .Value( TAttribute< TOptional< float > >::Create( TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetParameterFloat::GetValue, Idx ) ) ) + .OnValueChanged( SNumericEntryBox< float >::FOnValueChanged::CreateLambda( + [=]( float Val ) { + MyParam->SetValue( Val, Idx, false, false ); + } ) ) + .OnValueCommitted( SNumericEntryBox< float >::FOnValueCommitted::CreateLambda( + [=]( float Val, ETextCommit::Type TextCommitType ) { + MyParam->SetValue( Val, Idx, true, true ); + } ) ) + .OnBeginSliderMovement( FSimpleDelegate::CreateUObject( + &InParam, &UHoudiniAssetParameterFloat::OnSliderMovingBegin, Idx ) ) + .OnEndSliderMovement( SNumericEntryBox< float >::FOnValueChanged::CreateUObject( + &InParam, &UHoudiniAssetParameterFloat::OnSliderMovingFinish, Idx ) ) + + .SliderExponent( 1.0f ) + .TypeInterface( paramTypeInterface ) + ] + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("ResetToBase", "Reset to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked(FOnClicked::CreateUObject( + &InParam, &UHoudiniAssetParameter::OnRevertParmToDefault, Idx)) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + } + + Row.ValueWidget.Widget = VerticalBox; + } + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); + Row.ValueWidget.Widget->SetEnabled( !InParam.bIsDisabled ); +} + +void +FHoudiniParameterDetails::CreateWidgetInt( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterInt& InParam ) +{ + TWeakObjectPtr MyParam( &InParam ); + FDetailWidgetRow & Row = LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + + // Create the standard parameter name widget. + CreateNameWidget( &InParam, Row, true ); + + TSharedRef< SVerticalBox > VerticalBox = SNew( SVerticalBox ); + + // Helper function to find a unit from a string (name or abbreviation) + auto ParmUnit = FUnitConversion::UnitFromString( *InParam.ValueUnit ); + + EUnit Unit = EUnit::Unspecified; + if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) + Unit = ParmUnit.GetValue(); + + TSharedPtr> paramTypeInterface; + paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); + + for ( int32 Idx = 0; Idx < InParam.GetTupleSize(); ++Idx ) + { + TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; + + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SAssignNew( NumericEntryBox, SNumericEntryBox< int32 > ) + .AllowSpin( true ) + + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + + .MinValue( InParam.ValueMin ) + .MaxValue( InParam.ValueMax ) + + .MinSliderValue( InParam.ValueUIMin ) + .MaxSliderValue( InParam.ValueUIMax ) + + .Value( TAttribute< TOptional< int32 > >::Create( + TAttribute< TOptional< int32 > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetParameterInt::GetValue, Idx ) ) ) + .OnValueChanged( SNumericEntryBox< int32 >::FOnValueChanged::CreateLambda( + [=]( int32 Val ) { + MyParam->SetValue( Val, Idx, false, false ); + } ) ) + .OnValueCommitted( SNumericEntryBox< int32 >::FOnValueCommitted::CreateLambda( + [=]( float Val, ETextCommit::Type TextCommitType ) { + MyParam->SetValue( Val, Idx, true, true ); + } ) ) + .OnBeginSliderMovement( FSimpleDelegate::CreateUObject( + &InParam, &UHoudiniAssetParameterInt::OnSliderMovingBegin, Idx ) ) + .OnEndSliderMovement( SNumericEntryBox< int32 >::FOnValueChanged::CreateUObject( + &InParam, &UHoudiniAssetParameterInt::OnSliderMovingFinish, Idx ) ) + + .SliderExponent( 1.0f ) + .TypeInterface(paramTypeInterface) + ] + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("ResetToBase", "Reset to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked(FOnClicked::CreateUObject( + &InParam, &UHoudiniAssetParameter::OnRevertParmToDefault, Idx)) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + if ( NumericEntryBox.IsValid() ) + NumericEntryBox->SetEnabled( !InParam.bIsDisabled ); + } + + Row.ValueWidget.Widget = VerticalBox; + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); +} + +void +FHoudiniParameterDetails::CreateWidgetInstanceInput( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetInstanceInput& InParam ) +{ + TWeakObjectPtr MyParam(&InParam); + if (!MyParam.IsValid()) + return; + + // Get thumbnail pool for this builder. + IDetailLayoutBuilder & DetailLayoutBuilder = LocalDetailCategoryBuilder.GetParentLayout(); + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // Classes allowed by instanced inputs. + const TArray< const UClass * > AllowedClasses = { + UStaticMesh::StaticClass(), AActor::StaticClass(), UBlueprint::StaticClass(), + USoundBase::StaticClass(), UParticleSystem::StaticClass(), USkeletalMesh::StaticClass() }; + + const int32 FieldCount = InParam.InstanceInputFields.Num(); + for ( int32 FieldIdx = 0; FieldIdx < FieldCount; ++FieldIdx ) + { + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField = InParam.InstanceInputFields[ FieldIdx ]; + if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) + continue; + + const int32 VariationCount = HoudiniAssetInstanceInputField->InstanceVariationCount(); + for( int32 VariationIdx = 0; VariationIdx < VariationCount; VariationIdx++ ) + { + UObject * InstancedObject = HoudiniAssetInstanceInputField->GetInstanceVariation( VariationIdx ); + if ( !InstancedObject || InstancedObject->IsPendingKill() ) + { + HOUDINI_LOG_WARNING( TEXT("Null Object found for instance variation %d"), VariationIdx ); + continue; + } + + // Create thumbnail for this object. + TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = + MakeShareable( new FAssetThumbnail( InstancedObject, 64, 64, AssetThumbnailPool ) ); + TSharedRef< SVerticalBox > PickerVerticalBox = SNew( SVerticalBox ); + TSharedPtr< SHorizontalBox > PickerHorizontalBox = nullptr; + TSharedPtr< SBorder > StaticMeshThumbnailBorder; + + FString FieldLabel = InParam.GetFieldLabel(FieldIdx, VariationIdx); + + IDetailGroup& DetailGroup = LocalDetailCategoryBuilder.AddGroup(FName(*FieldLabel), FText::FromString(FieldLabel)); + DetailGroup.AddWidgetRow() + .NameContent() + [ + SNew(SSpacer) + .Size(FVector2D(250, 64)) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + PickerVerticalBox + ]; + TWeakObjectPtr InputFieldPtr( HoudiniAssetInstanceInputField ); + PickerVerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop( SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( + [=]( const UObject* Obj ) { + for ( auto Klass : AllowedClasses ) + { + if ( Obj && Obj->IsA( Klass ) ) + return true; + } + return false; + }) + ) + .OnAssetDropped( SAssetDropTarget::FOnAssetDropped::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::OnStaticMeshDropped, HoudiniAssetInstanceInputField, FieldIdx, VariationIdx ) ) + [ + SAssignNew( PickerHorizontalBox, SHorizontalBox ) + ] + ]; + + PickerHorizontalBox->AddSlot().Padding( 0.0f, 0.0f, 2.0f, 0.0f ).AutoWidth() + [ + SAssignNew( StaticMeshThumbnailBorder, SBorder ) + .Padding( 5.0f ) + .OnMouseDoubleClick( FPointerEventHandler::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::OnThumbnailDoubleClick, InstancedObject ) ) + [ + SNew( SBox ) + .WidthOverride( 64 ) + .HeightOverride( 64 ) + .ToolTipText( FText::FromString( InstancedObject->GetPathName() ) ) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + StaticMeshThumbnailBorder->SetBorderImage( TAttribute< const FSlateBrush * >::Create( + TAttribute< const FSlateBrush * >::FGetter::CreateLambda([=]() { + + if ( StaticMeshThumbnailBorder.IsValid() && StaticMeshThumbnailBorder->IsHovered() ) + return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailLight" ); + else + return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailShadow" ); + } ) ) ); + + PickerHorizontalBox->AddSlot().AutoWidth().Padding( 0.0f, 28.0f, 0.0f, 28.0f ) + [ + PropertyCustomizationHelpers::MakeAddButton( + FSimpleDelegate::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::OnAddInstanceVariation, + HoudiniAssetInstanceInputField, VariationIdx ), + LOCTEXT("AddAnotherInstanceToolTip", "Add Another Instance" ) ) + ]; + + PickerHorizontalBox->AddSlot().AutoWidth().Padding( 2.0f, 28.0f, 4.0f, 28.0f ) + [ + PropertyCustomizationHelpers::MakeRemoveButton( + FSimpleDelegate::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::OnRemoveInstanceVariation, + HoudiniAssetInstanceInputField, VariationIdx ), + LOCTEXT("RemoveLastInstanceToolTip", "Remove Last Instance")) + ]; + + TSharedPtr< SComboButton > AssetComboButton; + TSharedPtr< SHorizontalBox > ButtonBox; + + PickerHorizontalBox->AddSlot() + .FillWidth( 10.0f ) + .Padding( 0.0f, 4.0f, 4.0f, 4.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SVerticalBox ) + +SVerticalBox::Slot() + .HAlign( HAlign_Fill ) + [ + SAssignNew( ButtonBox, SHorizontalBox ) + +SHorizontalBox::Slot() + [ + SAssignNew( AssetComboButton, SComboButton ) + //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) + .ButtonStyle( FEditorStyle::Get(), "PropertyEditor.AssetComboStyle" ) + .ForegroundColor( FEditorStyle::GetColor( "PropertyEditor.AssetName.ColorAndOpacity" ) ) + .OnMenuOpenChanged( FOnIsOpenChanged::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::ChangedStaticMeshComboButton, + HoudiniAssetInstanceInputField, FieldIdx, VariationIdx ) ) + .ContentPadding( 2.0f ) + .ButtonContent() + [ + SNew( STextBlock ) + .TextStyle( FEditorStyle::Get(), "PropertyEditor.AssetClass" ) + .Font( FEditorStyle::GetFontStyle( FName( TEXT( "PropertyWindow.NormalFont" ) ) ) ) + .Text( FText::FromString(InstancedObject->GetName() ) ) + ] + ] + ] + ]; + + // Create asset picker for this combo button. + { + TArray< UFactory * > NewAssetFactories; + TSharedRef< SWidget > PropertyMenuAssetPicker = + PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData( InstancedObject ), true, + AllowedClasses, NewAssetFactories, FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda( [=]( const FAssetData& AssetData ) { + if ( AssetComboButton.IsValid() && MyParam.IsValid() && InputFieldPtr.IsValid() ) + { + AssetComboButton->SetIsOpen( false ); + UObject * Object = AssetData.GetAsset(); + MyParam->OnStaticMeshDropped( Object, InputFieldPtr.Get(), FieldIdx, VariationIdx ); + } + }), + FSimpleDelegate::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::CloseStaticMeshComboButton, + HoudiniAssetInstanceInputField, FieldIdx, VariationIdx ) ); + + AssetComboButton->SetMenuContent( PropertyMenuAssetPicker ); + } + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add( TEXT( "Asset" ), FText::FromString( InstancedObject->GetName() ) ); + FText StaticMeshTooltip = + FText::Format( LOCTEXT( "BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser" ), Args ); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateUObject( &InParam, &UHoudiniAssetInstanceInput::OnInstancedObjectBrowse, InstancedObject ), + TAttribute< FText >( StaticMeshTooltip ) ) + ]; + + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "ResetToBase", "Reset to default static mesh" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked( FOnClicked::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::OnResetStaticMeshClicked, + HoudiniAssetInstanceInputField, FieldIdx, VariationIdx ) ) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ]; + + TSharedRef< SVerticalBox > OffsetVerticalBox = SNew( SVerticalBox ); + FText LabelRotationText = LOCTEXT( "HoudiniRotationOffset", "Rotation Offset" ); + DetailGroup.AddWidgetRow() + .NameContent() + [ + SNew( STextBlock ) + .Text( LabelRotationText ) + .ToolTipText( LabelRotationText ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .ValueContent() + .MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH - 17 ) + [ + SNew( SHorizontalBox ) + + SHorizontalBox::Slot().MaxWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH - 17 ) + [ + SNew( SRotatorInputBox ) + .AllowSpin( true ) + .bColorAxisLabels( true ) + .Roll( TAttribute< TOptional< float > >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::GetRotationRoll, HoudiniAssetInstanceInputField, VariationIdx ) ) ) + .Pitch( TAttribute< TOptional< float> >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::GetRotationPitch, HoudiniAssetInstanceInputField, VariationIdx ) ) ) + .Yaw( TAttribute >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::GetRotationYaw, HoudiniAssetInstanceInputField, VariationIdx ) ) ) + .OnRollChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::SetRotationRoll, HoudiniAssetInstanceInputField, VariationIdx ) ) + .OnPitchChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::SetRotationPitch, HoudiniAssetInstanceInputField, VariationIdx ) ) + .OnYawChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::SetRotationYaw, HoudiniAssetInstanceInputField, VariationIdx ) ) + ] + ]; + + FText LabelScaleText = LOCTEXT( "HoudiniScaleOffset", "Scale Offset" ); + + DetailGroup.AddWidgetRow() + .NameContent() + [ + SNew( STextBlock ) + .Text( LabelScaleText ) + .ToolTipText( LabelScaleText ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .ValueContent() + .MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ) + [ + SNew( SHorizontalBox ) + + SHorizontalBox::Slot().MaxWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ) + [ + SNew( SVectorInputBox ) + .bColorAxisLabels( true ) + .X( TAttribute< TOptional< float > >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::GetScaleX, HoudiniAssetInstanceInputField, VariationIdx ) ) ) + .Y( TAttribute< TOptional< float > >::Create( + TAttribute< TOptional< float> >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::GetScaleY, HoudiniAssetInstanceInputField, VariationIdx ) ) ) + .Z( TAttribute< TOptional< float> >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::GetScaleZ, HoudiniAssetInstanceInputField, VariationIdx ) ) ) + .OnXChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::SetScaleX, HoudiniAssetInstanceInputField, VariationIdx ) ) + .OnYChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::SetScaleY, HoudiniAssetInstanceInputField, VariationIdx ) ) + .OnZChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::SetScaleZ, HoudiniAssetInstanceInputField, VariationIdx ) ) + ] + + SHorizontalBox::Slot().AutoWidth() + [ + // Add a checkbox to toggle between preserving the ratio of x,y,z components of scale when a value is entered + SNew( SCheckBox ) + .Style( FEditorStyle::Get(), "TransparentCheckBox" ) + .ToolTipText( LOCTEXT( "PreserveScaleToolTip", "When locked, scales uniformly based on the current xyz scale values so the object maintains its shape in each direction when scaled" ) ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateLambda( [=]( ECheckBoxState NewState ) { + if ( MyParam.IsValid() && InputFieldPtr.IsValid() ) + MyParam->CheckStateChanged( NewState == ECheckBoxState::Checked, InputFieldPtr.Get(), VariationIdx ); + })) + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute::FGetter::CreateLambda( [=]() { + if ( InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly( VariationIdx ) ) + return ECheckBoxState::Checked; + return ECheckBoxState::Unchecked; + }))) + [ + SNew( SImage ) + .Image( TAttribute::Create( + TAttribute::FGetter::CreateLambda( [=]() { + if ( InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly( VariationIdx ) ) + { + return FEditorStyle::GetBrush( TEXT( "GenericLock" ) ); + } + return FEditorStyle::GetBrush( TEXT( "GenericUnlock" ) ); + }))) + .ColorAndOpacity( FSlateColor::UseForeground() ) + ] + ] + ]; + } + } +} + +FMenuBuilder +FHoudiniParameterDetails::Helper_CreateCustomActorPickerWidget( UHoudiniAssetInput& InParam, const TAttribute& HeadingText, const bool& bShowCurrentSelectionSection ) +{ + // Custom Actor Picker showing only the desired Actor types. + // Note: Code stolen from SPropertyMenuActorPicker.cpp + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateUObject( &InParam, &UHoudiniAssetInput::OnShouldFilterActor ); + + //FHoudiniEngineEditor & HoudiniEngineEditor = FHoudiniEngineEditor::Get(); + //TSharedPtr< ISlateStyle > StyleSet = GEditor->GetSlateStyle(); + + FMenuBuilder MenuBuilder( true, NULL ); + + if ( bShowCurrentSelectionSection ) + { + MenuBuilder.BeginSection( NAME_None, LOCTEXT( "CurrentActorOperationsHeader", "Current Selection" ) ); + { + MenuBuilder.AddMenuEntry( + TAttribute::Create( TAttribute::FGetter::CreateUObject( &InParam, &UHoudiniAssetInput::GetCurrentSelectionText ) ), + TAttribute::Create( TAttribute::FGetter::CreateUObject( &InParam, &UHoudiniAssetInput::GetCurrentSelectionText ) ), + FSlateIcon(),//StyleSet->GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo")), + FUIAction(), + NAME_None, + EUserInterfaceActionType::Button, + NAME_None ); + } + MenuBuilder.EndSection(); + } + + MenuBuilder.BeginSection( NAME_None, HeadingText ); + { + + + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize( 350.0f, 200.0f ); + TSharedRef< SWidget > MenuWidget = + SNew( SBox ) + .WidthOverride( SceneOutlinerWindowSize.X ) + .HeightOverride( SceneOutlinerWindowSize.Y ) + [ + SNew( SBorder ) + .BorderImage( FEditorStyle::GetBrush( "Menu.Background" ) ) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateUObject( + &InParam, &UHoudiniAssetInput::OnActorSelected ) ) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true ); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +/** Create a single geometry widget for the given input object */ +void FHoudiniParameterDetails::Helper_CreateGeometryWidget( + UHoudiniAssetInput& InParam, int32 AtIndex, UObject* InputObject, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, TSharedRef< SVerticalBox > VerticalBox ) +{ + TWeakObjectPtr MyParam( &InParam ); + + // Create thumbnail for this static mesh. + TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = MakeShareable( + new FAssetThumbnail( InputObject, 64, 64, AssetThumbnailPool ) ); + + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + // Drop Target: Static Mesh + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop( SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( + []( const UObject* InObject ) + { return InObject && ( InObject->IsA< UStaticMesh >() || InObject->IsA< USkeletalMesh >() ); } + ) ) + .OnAssetDropped( SAssetDropTarget::FOnAssetDropped::CreateUObject( + &InParam, &UHoudiniAssetInput::OnStaticMeshDropped, AtIndex ) ) + [ + SAssignNew( HorizontalBox, SHorizontalBox ) + ] + ]; + + // Thumbnail : Static Mesh + FText ParameterLabelText = FText::FromString( InParam.GetParameterLabel() ); + //FText ParameterTooltip = GetParameterTooltip( &InParam, true ); + TSharedPtr< SBorder > StaticMeshThumbnailBorder; + + HorizontalBox->AddSlot().Padding( 0.0f, 0.0f, 2.0f, 0.0f ).AutoWidth() + [ + SAssignNew( StaticMeshThumbnailBorder, SBorder ) + .Padding( 5.0f ) + .OnMouseDoubleClick( FPointerEventHandler::CreateUObject( &InParam, &UHoudiniAssetInput::OnThumbnailDoubleClick, AtIndex ) ) + [ + SNew( SBox ) + .WidthOverride( 64 ) + .HeightOverride( 64 ) + .ToolTipText( ParameterLabelText ) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + StaticMeshThumbnailBorder->SetBorderImage( TAttribute< const FSlateBrush * >::Create( + TAttribute< const FSlateBrush * >::FGetter::CreateLambda( [StaticMeshThumbnailBorder]() + { + if ( StaticMeshThumbnailBorder.IsValid() && StaticMeshThumbnailBorder->IsHovered() ) + return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailLight" ); + else + return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailShadow" ); + } + ) ) ); + + FText MeshNameText = FText::GetEmpty(); + if ( InputObject ) + MeshNameText = FText::FromString( InputObject->GetName() ); + + // ComboBox : Static Mesh + TSharedPtr< SComboButton > StaticMeshComboButton; + + TSharedPtr< SHorizontalBox > ButtonBox; + HorizontalBox->AddSlot() + .FillWidth( 1.0f ) + .Padding( 0.0f, 4.0f, 4.0f, 4.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SVerticalBox ) + + SVerticalBox::Slot() + .HAlign( HAlign_Fill ) + [ + SAssignNew( ButtonBox, SHorizontalBox ) + + SHorizontalBox::Slot() + [ + SAssignNew( StaticMeshComboButton, SComboButton ) + .ButtonStyle( FEditorStyle::Get(), "PropertyEditor.AssetComboStyle" ) + .ForegroundColor( FEditorStyle::GetColor( "PropertyEditor.AssetName.ColorAndOpacity" ) ) + .ContentPadding( 2.0f ) + .ButtonContent() + [ + SNew( STextBlock ) + .TextStyle( FEditorStyle::Get(), "PropertyEditor.AssetClass" ) + .Font( FEditorStyle::GetFontStyle( FName( TEXT( "PropertyWindow.NormalFont" ) ) ) ) + .Text( MeshNameText ) + ] + ] + ] + ]; + + StaticMeshComboButton->SetOnGetMenuContent( FOnGetContent::CreateLambda( + [ MyParam, AtIndex, StaticMeshComboButton ]() + { + TArray< const UClass * > AllowedClasses; + AllowedClasses.Add( UStaticMesh::StaticClass() ); + AllowedClasses.Add( USkeletalMesh::StaticClass() ); + + TArray< UFactory * > NewAssetFactories; + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData( MyParam->GetInputObject( AtIndex ) ), + true, + AllowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda( [MyParam, AtIndex, StaticMeshComboButton]( const FAssetData & AssetData ) { + if ( StaticMeshComboButton.IsValid() ) + { + StaticMeshComboButton->SetIsOpen( false ); + + UObject * Object = AssetData.GetAsset(); + MyParam->OnStaticMeshDropped( Object, AtIndex ); + } + } ), + FSimpleDelegate::CreateLambda( []() {} ) ); + } ) ); + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add( TEXT( "Asset" ), MeshNameText ); + FText StaticMeshTooltip = FText::Format( + LOCTEXT( "BrowseToSpecificAssetInContentBrowser", + "Browse to '{Asset}' in Content Browser" ), Args ); + + // Button : Browse Static Mesh + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateUObject( &InParam, &UHoudiniAssetInput::OnStaticMeshBrowse, AtIndex ), + TAttribute< FText >( StaticMeshTooltip ) ) + ]; + + // ButtonBox : Reset + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "ResetToBase", "Reset to default static mesh" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked( FOnClicked::CreateUObject( &InParam, &UHoudiniAssetInput::OnResetStaticMeshClicked, AtIndex ) ) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ]; + + ButtonBox->AddSlot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeInsertDeleteDuplicateButton( + FExecuteAction::CreateLambda( [ MyParam, AtIndex ]() + { + MyParam->OnInsertGeo( AtIndex ); + } + ), + FExecuteAction::CreateLambda( [MyParam, AtIndex ]() + { + MyParam->OnDeleteGeo( AtIndex ); + } + ), + FExecuteAction::CreateLambda( [ MyParam, AtIndex ]() + { + MyParam->OnDuplicateGeo( AtIndex ); + } + ) ) + ]; + + { + TSharedPtr ExpanderArrow; + TSharedPtr ExpanderImage; + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( ExpanderArrow, SButton ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ClickMethod( EButtonClickMethod::MouseDown ) + .Visibility( EVisibility::Visible ) + .OnClicked( FOnClicked::CreateUObject(&InParam, &UHoudiniAssetInput::OnExpandInputTransform, AtIndex ) ) + [ + SAssignNew( ExpanderImage, SImage ) + .ColorAndOpacity( FSlateColor::UseForeground() ) + ] + ] + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( LOCTEXT("GeoInputTransform", "Transform Offset") ) + .ToolTipText( LOCTEXT( "GeoInputTransformTooltip", "Transform offset used for correction before sending the asset to Houdini" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ]; + // Set delegate for image + ExpanderImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda( [=]() { + FName ResourceName; + if ( MyParam->TransformUIExpanded[ AtIndex ] ) + { + if ( ExpanderArrow->IsHovered() ) + ResourceName = "TreeArrow_Expanded_Hovered"; + else + ResourceName = "TreeArrow_Expanded"; + } + else + { + if ( ExpanderArrow->IsHovered() ) + ResourceName = "TreeArrow_Collapsed_Hovered"; + else + ResourceName = "TreeArrow_Collapsed"; + } + return FEditorStyle::GetBrush( ResourceName ); + } ) ) ); + } + + // TRANSFORM + if ( InParam.TransformUIExpanded[ AtIndex ] ) + { + // Position + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew(STextBlock) + .Text( LOCTEXT("GeoInputTranslate", "T") ) + .ToolTipText( LOCTEXT( "GeoInputTranslateTooltip", "Translate" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot().MaxWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ) + [ + SNew( SVectorInputBox ) + .bColorAxisLabels( true ) + .X( TAttribute< TOptional< float > >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetPositionX, AtIndex ) ) ) + .Y( TAttribute< TOptional< float > >::Create( + TAttribute< TOptional< float> >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetPositionY, AtIndex ) ) ) + .Z( TAttribute< TOptional< float> >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetPositionZ, AtIndex ) ) ) + .OnXChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetPositionX, AtIndex ) ) + .OnYChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetPositionY, AtIndex ) ) + .OnZChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetPositionZ, AtIndex ) ) + ] + ]; + + // Rotation + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text( LOCTEXT("GeoInputRotate", "R") ) + .ToolTipText( LOCTEXT( "GeoInputRotateTooltip", "Rotate" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot().MaxWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ) + [ + SNew( SRotatorInputBox ) + .AllowSpin( true ) + .bColorAxisLabels( true ) + .Roll( TAttribute< TOptional< float > >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetRotationRoll, AtIndex ) ) ) + .Pitch( TAttribute< TOptional< float> >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetRotationPitch, AtIndex) ) ) + .Yaw( TAttribute >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetRotationYaw, AtIndex) ) ) + .OnRollChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetRotationRoll, AtIndex) ) + .OnPitchChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetRotationPitch, AtIndex) ) + .OnYawChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetRotationYaw, AtIndex) ) + ] + ]; + + // Scale + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "GeoInputScale", "S" ) ) + .ToolTipText( LOCTEXT( "GeoInputScaleTooltip", "Scale" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot().MaxWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ) + [ + SNew( SVectorInputBox ) + .bColorAxisLabels( true ) + .X( TAttribute< TOptional< float > >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetScaleX, AtIndex ) ) ) + .Y( TAttribute< TOptional< float > >::Create( + TAttribute< TOptional< float> >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetScaleY, AtIndex ) ) ) + .Z( TAttribute< TOptional< float> >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetScaleZ, AtIndex ) ) ) + .OnXChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetScaleX, AtIndex ) ) + .OnYChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetScaleY, AtIndex ) ) + .OnZChanged( FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetScaleZ, AtIndex ) ) + ] + /* + + SHorizontalBox::Slot().AutoWidth() + [ + // Add a checkbox to toggle between preserving the ratio of x,y,z components of scale when a value is entered + SNew( SCheckBox ) + .Style( FEditorStyle::Get(), "TransparentCheckBox" ) + .ToolTipText( LOCTEXT( "PreserveScaleToolTip", "When locked, scales uniformly based on the current xyz scale values so the object maintains its shape in each direction when scaled" ) ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + this, &UHoudiniAssetInput::CheckStateChanged, AtIndex ) ) + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute::FGetter::CreateUObject( + this, &UHoudiniAssetInput::IsChecked, AtIndex ) ) ) + [ + SNew( SImage ) + .Image( TAttribute::Create( + TAttribute::FGetter::CreateUObject( + this, &UHoudiniAssetInput::GetPreserveScaleRatioImage, AtIndex ) ) ) + .ColorAndOpacity( FSlateColor::UseForeground() ) + ] + ] + */ + ]; + } +} + +/** Create a single skeleton widget for the given input object */ +void FHoudiniParameterDetails::Helper_CreateSkeletonWidget( + UHoudiniAssetInput& InParam, int32 AtIndex, UObject* InputObject, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, TSharedRef< SVerticalBox > VerticalBox ) +{ + TWeakObjectPtr MyParam( &InParam ); + + // Create thumbnail for this skeleton mesh. + TSharedPtr< FAssetThumbnail > SkeletalMeshThumbnail = MakeShareable( + new FAssetThumbnail( InputObject, 64, 64, AssetThumbnailPool ) ); + + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + // Drop Target: Skeletal Mesh + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop( SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( + [] ( const UObject* InObject ) + { + return InObject && InObject->IsA< USkeletalMesh >(); + } + ) ) + .OnAssetDropped( SAssetDropTarget::FOnAssetDropped::CreateUObject( + &InParam, &UHoudiniAssetInput::OnSkeletalMeshDropped, AtIndex ) ) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + // Thumbnail : Skeletal Mesh + FText ParameterLabelText = FText::FromString( InParam.GetParameterLabel() ); + //FText ParameterTooltip = GetParameterTooltip( &InParam, true ); + TSharedPtr< SBorder > SkeletalMeshThumbnailBorder; + + HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + [ + SAssignNew( SkeletalMeshThumbnailBorder, SBorder ) + .Padding(5.0f) + .OnMouseDoubleClick(FPointerEventHandler::CreateUObject(&InParam, &UHoudiniAssetInput::OnThumbnailDoubleClick, AtIndex)) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(ParameterLabelText) + [ + SkeletalMeshThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + SkeletalMeshThumbnailBorder->SetBorderImage( TAttribute< const FSlateBrush * >::Create( + TAttribute< const FSlateBrush * >::FGetter::CreateLambda( [ SkeletalMeshThumbnailBorder ]() + { + if ( SkeletalMeshThumbnailBorder.IsValid() && SkeletalMeshThumbnailBorder->IsHovered() ) + return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailLight" ); + else + return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailShadow" ); + } ) ) ); + + FText MeshNameText = FText::GetEmpty(); + if ( InputObject ) + MeshNameText = FText::FromString( InputObject->GetName() ); + + // ComboBox : Skeletal Mesh + TSharedPtr< SComboButton > SkeletalMeshComboButton; + + TSharedPtr< SHorizontalBox > ButtonBox; + HorizontalBox->AddSlot() + .FillWidth( 1.0f ) + .Padding( 0.0f, 4.0f, 4.0f, 4.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SVerticalBox ) + + SVerticalBox::Slot() + .HAlign( HAlign_Fill ) + [ + SAssignNew( ButtonBox, SHorizontalBox ) + + SHorizontalBox::Slot() + [ + SAssignNew(SkeletalMeshComboButton, SComboButton ) + .ButtonStyle( FEditorStyle::Get(), "PropertyEditor.AssetComboStyle" ) + .ForegroundColor( FEditorStyle::GetColor( "PropertyEditor.AssetName.ColorAndOpacity" ) ) + .ContentPadding( 2.0f ) + .ButtonContent() + [ + SNew( STextBlock ) + .TextStyle( FEditorStyle::Get(), "PropertyEditor.AssetClass" ) + .Font( FEditorStyle::GetFontStyle( FName( TEXT( "PropertyWindow.NormalFont" ) ) ) ) + .Text( MeshNameText ) + ] + ] + ] + ]; + + SkeletalMeshComboButton->SetOnGetMenuContent( FOnGetContent::CreateLambda( + [ MyParam, AtIndex, SkeletalMeshComboButton ]() + { + TArray< const UClass * > AllowedClasses; + AllowedClasses.Add(USkeletalMesh::StaticClass()); + + TArray< UFactory * > NewAssetFactories; + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData( MyParam->GetInputObject( AtIndex ) ), + true, + AllowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda( + [ MyParam, AtIndex, SkeletalMeshComboButton]( const FAssetData & AssetData ) + { + if (SkeletalMeshComboButton.IsValid()) + { + SkeletalMeshComboButton->SetIsOpen(false); + + UObject * Object = AssetData.GetAsset(); + MyParam->OnSkeletalMeshDropped(Object, AtIndex); + } + } ), + FSimpleDelegate::CreateLambda( [](){}) + ); + } ) ); + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add( TEXT("Asset"), MeshNameText ); + FText SkeletalMeshTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", + "Browse to '{Asset}' in Content Browser"), Args); + + // Button : Browse Skeletal Mesh + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateUObject( &InParam, &UHoudiniAssetInput::OnStaticMeshBrowse, AtIndex ), + TAttribute< FText >( SkeletalMeshTooltip ) ) + ]; + + // ButtonBox : Reset + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "ResetToBase", "Reset to default skeletal mesh" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked( FOnClicked::CreateUObject( &InParam, &UHoudiniAssetInput::OnResetSkeletalMeshClicked, AtIndex ) ) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ]; + + ButtonBox->AddSlot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeInsertDeleteDuplicateButton( + FExecuteAction::CreateLambda( [ MyParam, AtIndex ](){ MyParam->OnInsertGeo( AtIndex ); } ), + FExecuteAction::CreateLambda( [ MyParam, AtIndex ](){ MyParam->OnDeleteGeo( AtIndex ); } ), + FExecuteAction::CreateLambda( [ MyParam, AtIndex ](){ MyParam->OnDuplicateGeo( AtIndex ); } ) ) + ]; + + /* + // TRANSFORM + { + TSharedPtr ExpanderArrow; + TSharedPtr ExpanderImage; + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( ExpanderArrow, SButton ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ClickMethod( EButtonClickMethod::MouseDown ) + .Visibility( EVisibility::Visible ) + .OnClicked( FOnClicked::CreateUObject( &InParam, &UHoudiniAssetInput::OnExpandInputTransform, AtIndex ) ) + [ + SAssignNew( ExpanderImage, SImage ) + .ColorAndOpacity( FSlateColor::UseForeground() ) + ] + ] + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( LOCTEXT("GeoInputTransform", "Transform Offset" ) ) + .ToolTipText( LOCTEXT( "GeoInputTransformTooltip", "Transform offset used for correction before sending the asset to Houdini" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ]; + + // Set delegate for image + ExpanderImage->SetImage( TAttribute::Create( + TAttribute::FGetter::CreateLambda( [=]() + { + FName ResourceName; + if ( MyParam->TransformUIExpanded[ AtIndex ] ) + { + if ( ExpanderArrow->IsHovered() ) + ResourceName = "TreeArrow_Expanded_Hovered"; + else + ResourceName = "TreeArrow_Expanded"; + } + else + { + if ( ExpanderArrow->IsHovered() ) + ResourceName = "TreeArrow_Collapsed_Hovered"; + else + ResourceName = "TreeArrow_Collapsed"; + } + return FEditorStyle::GetBrush( ResourceName ); + } ) ) ); + } + + if ( InParam.TransformUIExpanded[ AtIndex ] ) + { + // Position + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "GeoInputTranslate", "T" ) ) + .ToolTipText( LOCTEXT( "GeoInputTranslateTooltip", "Translate" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot().MaxWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ) + [ + SNew( SVectorInputBox ) + .bColorAxisLabels( true ) + .X( TAttribute< TOptional< float > >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( &InParam, &UHoudiniAssetInput::GetPositionX, AtIndex ) ) ) + .Y(TAttribute< TOptional< float > >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( &InParam, &UHoudiniAssetInput::GetPositionY, AtIndex ) ) ) + .Z(TAttribute< TOptional< float > >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( &InParam, &UHoudiniAssetInput::GetPositionZ, AtIndex ) ) ) + .OnXChanged( FOnFloatValueChanged::CreateUObject( &InParam, &UHoudiniAssetInput::SetPositionX, AtIndex ) ) + .OnYChanged(FOnFloatValueChanged::CreateUObject( &InParam, &UHoudiniAssetInput::SetPositionY, AtIndex ) ) + .OnZChanged(FOnFloatValueChanged::CreateUObject( &InParam, &UHoudiniAssetInput::SetPositionZ, AtIndex ) ) + ] + ]; + + // Rotation + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "GeoInputRotate", "R" ) ) + .ToolTipText(LOCTEXT("GeoInputRotateTooltip", "Rotate") ) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SRotatorInputBox) + .AllowSpin(true) + .bColorAxisLabels(true) + .Roll(TAttribute< TOptional< float > >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetRotationRoll, AtIndex))) + .Pitch(TAttribute< TOptional< float> >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetRotationPitch, AtIndex))) + .Yaw(TAttribute >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetRotationYaw, AtIndex))) + .OnRollChanged(FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetRotationRoll, AtIndex)) + .OnPitchChanged(FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetRotationPitch, AtIndex)) + .OnYawChanged(FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetRotationYaw, AtIndex)) + ] + ]; + + // Scale + VerticalBox->AddSlot().Padding(0, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("GeoInputScale", "S")) + .ToolTipText(LOCTEXT("GeoInputScaleTooltip", "Scale")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X(TAttribute< TOptional< float > >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetScaleX, AtIndex))) + .Y(TAttribute< TOptional< float > >::Create( + TAttribute< TOptional< float> >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetScaleY, AtIndex))) + .Z(TAttribute< TOptional< float> >::Create( + TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetScaleZ, AtIndex))) + .OnXChanged(FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetScaleX, AtIndex)) + .OnYChanged(FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetScaleY, AtIndex)) + .OnZChanged(FOnFloatValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetScaleZ, AtIndex)) + ] + ]; + } + */ +} + +FReply +FHoudiniParameterDetails::Helper_OnButtonClickSelectActors( + TWeakObjectPtr InParam, + FName DetailsPanelName) +{ + return Helper_OnButtonClickSelectActors( InParam, DetailsPanelName, false ); +} + +FReply +FHoudiniParameterDetails::Helper_OnButtonClickUseSelectionAsBoundSelector( + TWeakObjectPtr InParam, + FName DetailsPanelName ) +{ + return Helper_OnButtonClickSelectActors(InParam, DetailsPanelName, true); +} + +FReply +FHoudiniParameterDetails::Helper_OnButtonClickSelectActors( + TWeakObjectPtr InParam, + FName DetailsPanelName, + const bool& bUseWorldInAsWorldSelector ) +{ + if ( !InParam.IsValid() ) + return FReply::Handled(); + + // There's no undo operation for button. + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >( "PropertyEditor" ); + + // Locate the details panel. + TSharedPtr< IDetailsView > DetailsView = PropertyModule.FindDetailView( DetailsPanelName ); + if ( !DetailsView.IsValid() ) + return FReply::Handled(); + + class SLocalDetailsView : public SDetailsViewBase + { + public: + void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } + void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } + }; + auto * LocalDetailsView = static_cast< SLocalDetailsView * >( DetailsView.Get() ); + + if ( !DetailsView->IsLocked() ) + { + LocalDetailsView->LockDetailsView(); + check( DetailsView->IsLocked() ); + + // Force refresh of details view. + InParam->OnParamStateChanged(); + + // Select the previously chosen input Actors from the World Outliner. + GEditor->SelectNone( false, true ); + for ( auto & OutlinerMesh : InParam->InputOutlinerMeshArray ) + { + if ( OutlinerMesh.ActorPtr.IsValid() ) + GEditor->SelectActor( OutlinerMesh.ActorPtr.Get(), true, true ); + } + + return FReply::Handled(); + } + + if ( !GEditor || !GEditor->GetSelectedObjects() ) + return FReply::Handled(); + + // If details panel is locked, locate selected actors and check if this component belongs to one of them. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini World Outliner Input Change" ), + InParam->PrimaryObject ); + InParam->Modify(); + + InParam->bStaticMeshChanged = true; + + // Delete all assets and reset the array. + // TODO: Make this process a little more efficient. + InParam->DisconnectAndDestroyInputAsset(); + InParam->InputOutlinerMeshArray.Empty(); + + USelection * SelectedActors = GEditor->GetSelectedActors(); + if ( !SelectedActors ) + return FReply::Handled(); + + if ( bUseWorldInAsWorldSelector ) + { + // Build an array of the current selection's bounds + TArray AllBBox; + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor * CurrentActor = Cast< AActor >(*It); + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + AllBBox.Add(CurrentActor->GetComponentsBoundingBox(true)); + } + + UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); + for (TActorIterator ActorItr(editorWorld); ActorItr; ++ActorItr) + { + AActor *CurrentActor = *ActorItr; + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + // Check that actor is currently not selected + if (CurrentActor->IsSelected()) + continue; + + // Ignore the SkySpheres? + FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); + if (ClassName.Contains("BP_Sky_Sphere")) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if ( InParam->GetHoudiniAssetComponent() && (CurrentActor == InParam->GetHoudiniAssetComponent()->GetOwner()) ) + continue; + + FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); + for (auto InBounds : AllBBox) + { + // Check if both actor's bounds intersects + if (!ActorBounds.Intersect(InBounds)) + continue; + + // Current actors intersect with our selection's bounds, update the input with it + InParam->UpdateInputOulinerArrayFromActor(CurrentActor, false); + break; + } + } + } + else + { + // If the builder brush is selected, first deselect it. + for ( FSelectionIterator It(*SelectedActors); It; ++It ) + { + AActor * Actor = Cast< AActor >(*It); + if (!Actor) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (InParam->GetHoudiniAssetComponent() && (Actor == InParam->GetHoudiniAssetComponent()->GetOwner())) + continue; + + InParam->UpdateInputOulinerArrayFromActor(Actor, false); + } + } + + InParam->MarkChanged(); + + AActor* HoudiniAssetActor = InParam->GetHoudiniAssetComponent()->GetOwner(); + + if ( DetailsView->IsLocked() ) + { + LocalDetailsView->UnlockDetailsView(); + check( !DetailsView->IsLocked() ); + + TArray< UObject * > DummySelectedActors; + DummySelectedActors.Add( HoudiniAssetActor ); + + // Reset selected actor to itself, force refresh and override the lock. + DetailsView->SetObjects( DummySelectedActors, true, true ); + } + + // Reselect the Asset Actor. If we don't do this, our Asset parameters will stop + // refreshing and the user will be very confused. It is also resetting the state + // of the selection before the input actor selection process was started. + GEditor->SelectNone( false, true ); + GEditor->SelectActor( HoudiniAssetActor, true, true ); + + // Update parameter layout. + InParam->OnParamStateChanged(); + + // Start or stop the tick timer to check if the selected Actors have been transformed. + if ( InParam->InputOutlinerMeshArray.Num() > 0 ) + InParam->StartWorldOutlinerTicking(); + else if ( InParam->InputOutlinerMeshArray.Num() <= 0 ) + InParam->StopWorldOutlinerTicking(); + + return FReply::Handled(); +} + +void +FHoudiniParameterDetails::CreateWidgetInput( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetInput& InParam ) +{ + TWeakObjectPtr MyParam( &InParam ); + + // Get thumbnail pool for this builder. + IDetailLayoutBuilder & DetailLayoutBuilder = LocalDetailCategoryBuilder.GetParentLayout(); + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + FDetailWidgetRow & Row = LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + FText ParameterLabelText = FText::FromString( InParam.GetParameterLabel() ); + FText ParameterTooltip = GetParameterTooltip( &InParam ); + Row.NameWidget.Widget = + SNew( STextBlock ) + .Text( ParameterLabelText ) + .ToolTipText( ParameterTooltip ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ); + + TSharedRef< SVerticalBox > VerticalBox = SNew( SVerticalBox ); + + if ( InParam.StringChoiceLabels.Num() > 0 ) + { + // ComboBox : Input Type + TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBoxInputType; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + //SNew(SComboBox >) + SAssignNew( ComboBoxInputType, SComboBox< TSharedPtr< FString > > ) + .OptionsSource( &InParam.StringChoiceLabels ) + .InitiallySelectedItem( InParam.StringChoiceLabels[ InParam.ChoiceIndex ] ) + .OnGenerateWidget( SComboBox< TSharedPtr< FString > >::FOnGenerateWidget::CreateLambda( + []( TSharedPtr< FString > ChoiceEntry ) { + FText ChoiceEntryText = FText::FromString( *ChoiceEntry ); + return SNew( STextBlock ) + .Text( ChoiceEntryText ) + .ToolTipText( ChoiceEntryText ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ); + })) + .OnSelectionChanged( SComboBox< TSharedPtr< FString > >::FOnSelectionChanged::CreateLambda( + [=]( TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType ) { + if ( !NewChoice.IsValid() || !MyParam.IsValid() ) + return; + MyParam->OnChoiceChange( NewChoice ); + })) + [ + SNew( STextBlock ) + .Text( TAttribute< FText >::Create( TAttribute< FText >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::HandleChoiceContentText ) ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ]; + + //ComboBoxInputType->SetSelectedItem( InParam.StringChoiceLabels[ InParam.ChoiceIndex ] ); + } + + // Checkbox : Keep World Transform + { + TSharedPtr< SCheckBox > CheckBoxTranformType; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxTranformType, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("KeepWorldTransformCheckbox", "Keep World Transform")) + .ToolTipText(LOCTEXT("KeepWorldTransformCheckboxTip", "Set this Input's object_merge Transform Type to INTO_THIS_OBJECT. If unchecked, it will be set to NONE.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked(TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedKeepWorldTransform))) + .OnCheckStateChanged(FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedKeepWorldTransform)) + ]; + + // the checkbox is read only for geo inputs + if ( InParam.ChoiceIndex == EHoudiniAssetInputType::GeometryInput ) + CheckBoxTranformType->SetEnabled( false ); + + // Checkbox is read only if the input is an object-path parameter + //if ( bIsObjectPathParameter ) + // CheckBoxTranformType->SetEnabled(false); + } + + // Checkbox Pack before merging + if ( InParam.ChoiceIndex == EHoudiniAssetInputType::GeometryInput + || InParam.ChoiceIndex == EHoudiniAssetInputType::WorldInput ) + { + TSharedPtr< SCheckBox > CheckBoxPackBeforeMerge; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SAssignNew( CheckBoxPackBeforeMerge, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "PackBeforeMergeCheckbox", "Pack Geometry before merging" ) ) + .ToolTipText( LOCTEXT( "PackBeforeMergeCheckboxTip", "Pack each separate piece of geometry before merging them into the input." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedPackBeforeMerge ) ) ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedPackBeforeMerge ) ) + ]; + } + + // Checkboxes Export LODs and Export Sockets + if ( InParam.ChoiceIndex == EHoudiniAssetInputType::GeometryInput + || InParam.ChoiceIndex == EHoudiniAssetInputType::WorldInput ) + { + TSharedPtr< SCheckBox > CheckBoxExportAllLODs; + TSharedPtr< SCheckBox > CheckBoxExportSockets; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( CheckBoxExportAllLODs, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "ExportAllLOD", "Export LODs" ) ) + .ToolTipText( LOCTEXT( "ExportAllLODCheckboxTip", "If enabled, all LOD Meshes in this static mesh will be sent to Houdini." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedExportAllLODs ) ) ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedExportAllLODs ) ) + ] + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( CheckBoxExportSockets, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "ExportSockets", "Export Sockets" ) ) + .ToolTipText( LOCTEXT( "ExportSocketsTip", "If enabled, all Mesh Sockets in this static mesh will be sent to Houdini." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedExportSockets ) ) ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedExportSockets ) ) + ] + ]; + } + + if ( InParam.ChoiceIndex == EHoudiniAssetInputType::GeometryInput ) + { + const int32 NumInputs = InParam.InputObjects.Num(); + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( FText::Format( LOCTEXT( "NumArrayItemsFmt", "{0} elements" ), FText::AsNumber( NumInputs ) ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton( FSimpleDelegate::CreateUObject( &InParam, &UHoudiniAssetInput::OnAddToInputObjects ), LOCTEXT( "AddInput", "Adds a Geometry Input" ), true ) + ] + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeEmptyButton( FSimpleDelegate::CreateUObject( &InParam, &UHoudiniAssetInput::OnEmptyInputObjects ), LOCTEXT( "EmptyInputs", "Removes All Inputs" ), true ) + ] + ]; + + for ( int32 Ix = 0; Ix < NumInputs; Ix++ ) + { + UObject* InputObject = InParam.GetInputObject( Ix ); + Helper_CreateGeometryWidget( InParam, Ix, InputObject, AssetThumbnailPool, VerticalBox ); + } + } + else if ( InParam.ChoiceIndex == EHoudiniAssetInputType::AssetInput ) + { + // ActorPicker : Houdini Asset + FMenuBuilder MenuBuilder = Helper_CreateCustomActorPickerWidget( InParam, LOCTEXT( "AssetInputSelectableActors", "Houdini Asset Actors" ), true ); + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + } + else if ( InParam.ChoiceIndex == EHoudiniAssetInputType::CurveInput ) + { + // Go through all input curve parameters and build their widgets recursively. + for ( TMap< FString, UHoudiniAssetParameter * >::TIterator + IterParams( InParam.InputCurveParameters ); IterParams; ++IterParams ) + { + // Note: Only choice and toggle supported atm + if ( UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value() ) + FHoudiniParameterDetails::CreateWidget( VerticalBox, HoudiniAssetParameter ); + } + } + else if ( InParam.ChoiceIndex == EHoudiniAssetInputType::LandscapeInput ) + { + // CheckBox : Update Input Landscape Data + { + TSharedPtr< SCheckBox > CheckBoxUpdateInput; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew( CheckBoxUpdateInput, SCheckBox ) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeUpdateInputCheckbox", "Update Input Landscape Data")) + .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, the input landscape's data will be updated instead of creating a new Landscape Actor.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked(TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedUpdateInputLandscape ) ) ) + .OnCheckStateChanged(FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedUpdateInputLandscape ) ) + ]; + + // Only enable if we're exporting as HF + CheckBoxUpdateInput->SetEnabled(static_cast(InParam.bLandscapeExportAsHeightfield)); + } + + // ActorPicker : Landscape + FMenuBuilder MenuBuilder = Helper_CreateCustomActorPickerWidget( InParam, LOCTEXT( "LandscapeInputSelectableActors", "Landscapes" ), true ); + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + + // Checkboxes : Export Landscape As + // Heightfield Mesh Points + { + // Label + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAs", "Export Landscape As")) + .ToolTipText(LOCTEXT("LandscapeExportAsTooltip", "Choose the type of data you want the landscape to be exported to:\n * Heightfield\n * Mesh\n * Points")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + // + TSharedPtr ButtonOptionsPanel; + VerticalBox->AddSlot().Padding(5, 2, 5, 2).AutoHeight() + [ + SAssignNew( ButtonOptionsPanel, SUniformGridPanel ) + ]; + + // Heightfield + FText HeightfieldTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a heighfield."); + ButtonOptionsPanel->AddSlot(0, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.Start") + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedExportAsHeightfield ) ) ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedExportAsHeightfield ) ) + .ToolTipText( HeightfieldTooltip ) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("ClassIcon.LandscapeComponent")) + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew(STextBlock) + .Text( LOCTEXT( "LandscapeExportAsHeightfieldCheckbox", "Heightfield" ) ) + .ToolTipText( HeightfieldTooltip ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + ] + ] + ]; + + // Mesh + FText MeshTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a heighfield."); + ButtonOptionsPanel->AddSlot(1, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedExportAsMesh ) ) ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedExportAsMesh ) ) + .ToolTipText( MeshTooltip ) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush( "ClassIcon.StaticMeshComponent" ) ) + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew( STextBlock ) + .Text( LOCTEXT( "LandscapeExportAsMeshCheckbox", "Mesh" ) ) + .ToolTipText( MeshTooltip ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + ] + ] + ]; + + // Points + FText PointsTooltip = LOCTEXT( "LandscapeExportAsPointsTooltip", "If enabled, the landscape will be exported to Houdini as points." ); + ButtonOptionsPanel->AddSlot(2, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.End") + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedExportAsPoints ) ) ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedExportAsPoints ) ) + .ToolTipText( PointsTooltip ) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("Mobility.Static")) + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew(STextBlock) + .Text( LOCTEXT( "LandscapeExportAsPointsCheckbox", "Points" ) ) + .ToolTipText( PointsTooltip ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + ] + ] + ]; + } + + // CheckBox : Export selected components only + { + TSharedPtr< SCheckBox > CheckBoxExportSelected; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SAssignNew( CheckBoxExportSelected, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "LandscapeSelectedCheckbox", "Export Selected Landscape Components Only" ) ) + .ToolTipText( LOCTEXT( "LandscapeSelectedTooltip", "If enabled, only the selected Landscape Components will be exported." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont") ) ) + ] + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedExportOnlySelected) ) ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedExportOnlySelected ) ) + ]; + } + + // Checkboxes auto select components + { + TSharedPtr< SCheckBox > CheckBoxAutoSelectComponents; + VerticalBox->AddSlot().Padding(10, 2, 5, 2).AutoHeight() + [ + SAssignNew( CheckBoxAutoSelectComponents, SCheckBox ) + .Content() + [ + SNew(STextBlock) + .Text( LOCTEXT( "AutoSelectComponentCheckbox", "Auto-select component in asset bounds" ) ) + .ToolTipText( LOCTEXT( "AutoSelectComponentCheckboxTooltip", "If enabled, when no Landscape components are curremtly selected, the one within the asset's bounding box will be exported." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont") ) ) + ] + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedAutoSelectLandscape ) ) ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedAutoSelectLandscape) ) + ]; + + // Enable only when exporting selection + // or when exporting heighfield (for now) + if ( !InParam.bLandscapeExportSelectionOnly ) + CheckBoxAutoSelectComponents->SetEnabled( false ); + } + + // The following checkbox are only added when not in heightfield mode + if ( !InParam.bLandscapeExportAsHeightfield ) + { + // Checkbox : Export materials + { + TSharedPtr< SCheckBox > CheckBoxExportMaterials; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SAssignNew( CheckBoxExportMaterials, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "LandscapeMaterialsCheckbox", "Export Landscape Materials" ) ) + .ToolTipText( LOCTEXT( "LandscapeMaterialsTooltip", "If enabled, the landscape materials will be exported with it." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedExportMaterials ) ) ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedExportMaterials ) ) + ]; + + // Disable when exporting heightfields + if ( InParam.bLandscapeExportAsHeightfield ) + CheckBoxExportMaterials->SetEnabled( false ); + } + + // Checkbox : Export Tile UVs + { + TSharedPtr< SCheckBox > CheckBoxExportTileUVs; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SAssignNew( CheckBoxExportTileUVs, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "LandscapeTileUVsCheckbox", "Export Landscape Tile UVs" ) ) + .ToolTipText( LOCTEXT( "LandscapeTileUVsTooltip", "If enabled, UVs will be exported separately for each Landscape tile." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked(TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedExportTileUVs ) ) ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedExportTileUVs ) ) + ]; + + // Disable when exporting heightfields + if ( InParam.bLandscapeExportAsHeightfield ) + CheckBoxExportTileUVs->SetEnabled( false ); + } + + // Checkbox : Export normalized UVs + { + TSharedPtr< SCheckBox > CheckBoxExportNormalizedUVs; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SAssignNew( CheckBoxExportNormalizedUVs, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "LandscapeNormalizedUVsCheckbox", "Export Landscape Normalized UVs" ) ) + .ToolTipText( LOCTEXT( "LandscapeNormalizedUVsTooltip", "If enabled, landscape UVs will be exported in [0, 1]." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedExportNormalizedUVs ) ) ) + .OnCheckStateChanged(FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedExportNormalizedUVs ) ) + ]; + + // Disable when exporting heightfields + if ( InParam.bLandscapeExportAsHeightfield ) + CheckBoxExportNormalizedUVs->SetEnabled( false ); + } + + // Checkbox : Export lighting + { + TSharedPtr< SCheckBox > CheckBoxExportLighting; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SAssignNew( CheckBoxExportLighting, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "LandscapeLightingCheckbox", "Export Landscape Lighting" ) ) + .ToolTipText( LOCTEXT( "LandscapeLightingTooltip", "If enabled, lightmap information will be exported with the landscape." ) ) + .Font(FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedExportLighting ) ) ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedExportLighting ) ) + ]; + + // Disable when exporting heightfields + if ( InParam.bLandscapeExportAsHeightfield ) + CheckBoxExportLighting->SetEnabled( false ); + } + + /* + // UNUSED + // Checkbox : Export landscape curves + { + TSharedPtr< SCheckBox > CheckBoxExportCurves; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SAssignNew( CheckBoxExportCurves, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "LandscapeCurvesCheckbox", "Export Landscape Curves" ) ) + .ToolTipText( LOCTEXT( "LandscapeCurvesTooltip", "If enabled, Landscape curves will be exported." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute< ECheckBoxState >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsCheckedExportCurves ) ) ) + .OnCheckStateChanged( FOnCheckStateChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::CheckStateChangedExportCurves ) ) + ]; + + // Disable curves until we have them implemented. + if ( CheckBoxExportCurves.IsValid() ) + CheckBoxExportCurves->SetEnabled( false ); + } + */ + } + + // Button : Recommit + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1, 2, 4, 2 ) + [ + SNew( SButton ) + .VAlign( VAlign_Center ) + .HAlign( HAlign_Center ) + .Text( LOCTEXT( "LandscapeInputRecommit", "Recommit Landscape" ) ) + .ToolTipText( LOCTEXT( "LandscapeInputRecommitTooltip", "Recommits the Landscape to Houdini." ) ) + .OnClicked( FOnClicked::CreateUObject( &InParam, &UHoudiniAssetInput::OnButtonClickRecommit ) ) + ] + ]; + } + else if ( InParam.ChoiceIndex == EHoudiniAssetInputType::WorldInput ) + { + // Button : Start Selection / Use current selection + refresh + { + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >( "PropertyEditor" ); + + // Get the details view + bool bDetailsLocked = false; + FName DetailsPanelName = "LevelEditorSelectionDetails"; + + const IDetailsView* DetailsView = DetailLayoutBuilder.GetDetailsView(); + if ( DetailsView ) + { + DetailsPanelName = DetailsView->GetIdentifier(); + if ( DetailsView->IsLocked() ) + bDetailsLocked = true; + } + + auto ButtonLabel = LOCTEXT( "WorldInputStartSelection", "Start Selection (Locks Details Panel)" ); + if ( bDetailsLocked ) + ButtonLabel = LOCTEXT( "WorldInputUseCurrentSelection", "Use Current Selection (Unlocks Details Panel)" ); + + auto ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); + if (bDetailsLocked) + ButtonTooltip = LOCTEXT("WorldInputUseCurrentSelectionTooltip", "Fill the asset's input with the current selection and unlocks the Details Panel."); + + FOnClicked OnSelectActors = FOnClicked::CreateStatic( &FHoudiniParameterDetails::Helper_OnButtonClickSelectActors, MyParam, DetailsPanelName ); + + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SAssignNew( HorizontalBox, SHorizontalBox ) + + SHorizontalBox::Slot() + [ + SNew( SButton ) + .VAlign( VAlign_Center ) + .HAlign( HAlign_Center ) + .Text( ButtonLabel ) + .ToolTipText( ButtonTooltip ) + .OnClicked( OnSelectActors ) + ] + ]; + + // Button : Use current selection as Bound selector + FOnClicked OnSelectBounds = FOnClicked::CreateStatic(&FHoudiniParameterDetails::Helper_OnButtonClickUseSelectionAsBoundSelector, MyParam, DetailsPanelName ); + if ( bDetailsLocked ) + { + ButtonLabel = LOCTEXT("WorldInputUseSelectionAsBoundSelector", "Use Selection as Bound Selector (Unlocks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputUseSelectionAsBoundSelectorTooltip", "Fill the asset's input with all the actors contains in the current's selection bound, and unlocks the Details Panel."); + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SAssignNew( HorizontalBox, SHorizontalBox ) + + SHorizontalBox::Slot() + [ + SNew( SButton ) + .VAlign( VAlign_Center ) + .HAlign( HAlign_Center ) + .Text( ButtonLabel ) + .ToolTipText( ButtonTooltip ) + .OnClicked( OnSelectBounds ) + ] + ]; + } + } + + + // ActorPicker : World Outliner + { + FMenuBuilder MenuBuilder = Helper_CreateCustomActorPickerWidget( InParam, LOCTEXT( "WorldInputSelectedActors", "Currently Selected Actors" ), false ); + + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + } + + { + // Spline Resolution + TSharedPtr< SNumericEntryBox< float > > NumericEntryBox; + int32 Idx = 0; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(LOCTEXT("SplineRes", "Unreal Spline Resolution")) + .ToolTipText(LOCTEXT("SplineResTooltip", "Resolution used when marshalling the Unreal Splines to HoudiniEngine.\n(step in cm betweem control points)\nSet this to 0 to only export the control points.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SNumericEntryBox< float >) + .AllowSpin(true) + + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + + .MinValue(-1.0f) + .MaxValue(1000.0f) + .MinSliderValue(0.0f) + .MaxSliderValue(1000.0f) + + .Value(TAttribute< TOptional< float > >::Create(TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetSplineResolutionValue) ) ) + .OnValueChanged(SNumericEntryBox< float >::FOnValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetSplineResolutionValue) ) + .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsSplineResolutionEnabled))) + + .SliderExponent(1.0f) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("SplineResToDefault", "Reset to default value.")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked(FOnClicked::CreateUObject(&InParam, &UHoudiniAssetInput::OnResetSplineResolutionClicked)) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + } + } + + if ( InParam.ChoiceIndex == EHoudiniAssetInputType::SkeletonInput ) + { + const int32 NumInputs = InParam.SkeletonInputObjects.Num(); + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( FText::Format( LOCTEXT( "NumArrayItemsFmt", "{0} elements" ), FText::AsNumber( NumInputs ) ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton( FSimpleDelegate::CreateUObject( &InParam, &UHoudiniAssetInput::OnAddToSkeletonInputObjects ), LOCTEXT( "AddSkeletonInput", "Adds a Skeleton Input" ), true ) + ] + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeEmptyButton( FSimpleDelegate::CreateUObject( &InParam, &UHoudiniAssetInput::OnEmptySkeletonInputObjects ), LOCTEXT( "EmptySkeletonInputs", "Removes All Inputs" ), true ) + ] + ]; + + for ( int32 Ix = 0; Ix < NumInputs; Ix++ ) + { + UObject* InputObject = InParam.GetSkeletonInputObject( Ix ); + Helper_CreateSkeletonWidget( InParam, Ix, InputObject, AssetThumbnailPool, VerticalBox ); + } + } + + Row.ValueWidget.Widget = VerticalBox; + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); +} + +void +FHoudiniParameterDetails::CreateWidgetLabel( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterLabel& InParam ) +{ + TSharedPtr< STextBlock > TextBlock; + FText ParameterLabelText = FText::FromString( InParam.GetParameterLabel() ); + FText ParameterTooltip = GetParameterTooltip( &InParam ); + + LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ) + [ + SAssignNew( TextBlock, STextBlock ) + .Text( ParameterLabelText ) + .ToolTipText( ParameterTooltip ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + .WrapTextAt( HAPI_UNREAL_DESIRED_ROW_FULL_WIDGET_WIDTH ) + .Justification( ETextJustify::Center ) + ]; + + if ( TextBlock.IsValid() ) + TextBlock->SetEnabled( !InParam.bIsDisabled ); +} + +void +FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterString& InParam ) +{ + FDetailWidgetRow & Row = LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + + // Create the standard parameter name widget. + CreateNameWidget( &InParam, Row, true ); + + TSharedRef< SVerticalBox > VerticalBox = SNew( SVerticalBox ); + + for ( int32 Idx = 0; Idx < InParam.GetTupleSize(); ++Idx ) + { + TSharedPtr< SEditableTextBox > EditableTextBox; + + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ) + [ + SAssignNew( EditableTextBox, SEditableTextBox ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + .Text( FText::FromString( InParam.Values[Idx] ) ) + .OnTextCommitted( FOnTextCommitted::CreateUObject( + &InParam, &UHoudiniAssetParameterString::SetValueCommitted, Idx ) ) + ]; + + if ( EditableTextBox.IsValid() ) + EditableTextBox->SetEnabled( !InParam.bIsDisabled ); + } + + Row.ValueWidget.Widget = VerticalBox; + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); +} + +void +FHoudiniParameterDetails::CreateWidgetSeparator( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterSeparator& InParam ) +{ + TSharedPtr< SSeparator > Separator; + + LocalDetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ) + [ + SNew( SVerticalBox ) + +SVerticalBox::Slot() + .Padding( 0, 0, 5, 0 ) + [ + SAssignNew( Separator, SSeparator ) + .Thickness( 2.0f ) + ] + ]; + + if ( Separator.IsValid() ) + Separator->SetEnabled( !InParam.bIsDisabled ); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h new file mode 100644 index 00000000..e62d1eac --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h @@ -0,0 +1,82 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "DetailCategoryBuilder.h" +#include "IDetailCustomization.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Widgets/Input/NumericTypeInterface.h" + +class FHoudiniParameterDetails : public TSharedFromThis, public TNumericUnitTypeInterface, public TNumericUnitTypeInterface +{ + +public: + + static void CreateNameWidget( class UHoudiniAssetParameter* InParam, FDetailWidgetRow & Row, bool bLabel ); + + static void CreateWidget( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameter* InParam ); + static void CreateWidgetFile( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterFile& InParam ); + static void CreateWidgetFolder( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterFolder& InParam ); + static void CreateWidgetFolderList( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterFolderList& InParam ); + static void CreateWidgetMultiparm( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterMultiparm& InParam ); + static void CreateWidgetRamp( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterRamp& InParam ); + static void CreateWidgetButton( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterButton& InParam ); + static void CreateWidgetChoice( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterChoice& InParam ); + static void CreateWidgetColor( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterColor& InParam ); + static void CreateWidgetToggle( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterToggle& InParam ); + static void CreateWidgetFloat( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterFloat& InParam ); + static void CreateWidgetInt( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterInt& InParam ); + static void CreateWidgetInstanceInput( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetInstanceInput& InParam ); + static void CreateWidgetInput( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetInput& InParam ); + static void CreateWidgetLabel( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterLabel& InParam ); + static void CreateWidgetString( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterString& InParam ); + static void CreateWidgetSeparator( IDetailCategoryBuilder & LocalDetailCategoryBuilder, class UHoudiniAssetParameterSeparator& InParam ); + + static void CreateWidget( TSharedPtr< SVerticalBox > VerticalBox, class UHoudiniAssetParameter* InParam ); + static void CreateWidgetChoice( TSharedPtr< SVerticalBox > VerticalBox, class UHoudiniAssetParameterChoice& InParam ); + static void CreateWidgetToggle( TSharedPtr< SVerticalBox > VerticalBox, class UHoudiniAssetParameterToggle& InParam ); + + static FText GetParameterTooltip( UHoudiniAssetParameter* InParam ); + +private: + + static FMenuBuilder Helper_CreateCustomActorPickerWidget( + UHoudiniAssetInput& InParam, const TAttribute& HeadingText, const bool& bShowCurrentSelectionSection ); + + static void Helper_CreateGeometryWidget( + class UHoudiniAssetInput& InParam, int32 AtIndex, UObject* InputObject, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, TSharedRef< SVerticalBox > VerticalBox ); + + static void Helper_CreateSkeletonWidget( + class UHoudiniAssetInput& InParam, int32 AtIndex, UObject* InputObject, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, TSharedRef< SVerticalBox > VerticalBox ); + + static FReply Helper_OnButtonClickSelectActors( + TWeakObjectPtr InParam, FName DetailsPanelName ); + static FReply Helper_OnButtonClickUseSelectionAsBoundSelector( + TWeakObjectPtr InParam, FName DetailsPanelName ); + static FReply Helper_OnButtonClickSelectActors( + TWeakObjectPtr InParam, FName DetailsPanelName, const bool& bUseWorldInAsWorldSelector ); + +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp new file mode 100644 index 00000000..3aa31eec --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp @@ -0,0 +1,315 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniRuntimeSettingsDetails.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngine.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "Internationalization/Internationalization.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "Widgets/Input/SNumericEntryBox.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +TSharedRef< IDetailCustomization > +FHoudiniRuntimeSettingsDetails::MakeInstance() +{ + return MakeShareable( new FHoudiniRuntimeSettingsDetails ); +} + +FHoudiniRuntimeSettingsDetails::FHoudiniRuntimeSettingsDetails() +{} + +FHoudiniRuntimeSettingsDetails::~FHoudiniRuntimeSettingsDetails() +{} + +void +FHoudiniRuntimeSettingsDetails::CustomizeDetails( IDetailLayoutBuilder & DetailBuilder ) +{ + // Create basic categories. + DetailBuilder.EditCategory( "Session", FText::GetEmpty(), ECategoryPriority::Important ); + DetailBuilder.EditCategory( "Instantiating", FText::GetEmpty(), ECategoryPriority::Important ); + DetailBuilder.EditCategory( "Cooking", FText::GetEmpty(), ECategoryPriority::Important ); + DetailBuilder.EditCategory( "Parameters", FText::GetEmpty(), ECategoryPriority::Important ); + DetailBuilder.EditCategory( "CollisionGeneration", FText::GetEmpty(), ECategoryPriority::Important ); + DetailBuilder.EditCategory( "GeometryMarshalling", FText::GetEmpty(), ECategoryPriority::Important ); + DetailBuilder.EditCategory( "GeometryScalingAndImport", FText::GetEmpty(), ECategoryPriority::Important ); + DetailBuilder.EditCategory( "GeneratedStaticMeshSettings", FText::GetEmpty(), ECategoryPriority::Important ); + DetailBuilder.EditCategory( "StaticMeshBuildSettings", FText::GetEmpty(), ECategoryPriority::Important ); + + // Create Plugin Information category. + { + static const FName InformationCategory = TEXT( "Plugin Information" ); + IDetailCategoryBuilder & InformationCategoryBuilder = DetailBuilder.EditCategory( InformationCategory ); + + // Add built Houdini version. + CreateHoudiniEntry( + LOCTEXT( "HInformationBuilt", "Built against Houdini" ), + InformationCategoryBuilder, HAPI_VERSION_HOUDINI_MAJOR, HAPI_VERSION_HOUDINI_MINOR, + HAPI_VERSION_HOUDINI_BUILD, HAPI_VERSION_HOUDINI_PATCH ); + + // Add built against Houdini Engine version. + CreateHoudiniEngineEntry( + LOCTEXT( "HEngineInformationBuilt", "Built against Houdini Engine" ), + InformationCategoryBuilder, HAPI_VERSION_HOUDINI_ENGINE_MAJOR, + HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API ); + + // Add running against Houdini version. + { + int32 RunningMajor = 0; + int32 RunningMinor = 0; + int32 RunningBuild = 0; + int32 RunningPatch = 0; + + if ( FHoudiniApi::IsHAPIInitialized() ) + { + const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); + // Retrieve version numbers for running Houdini. + FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_MAJOR, &RunningMajor ); + FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_MINOR, &RunningMinor ); + FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_BUILD, &RunningBuild ); + FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_PATCH, &RunningPatch ); + } + + CreateHoudiniEntry( + LOCTEXT( "HInformationRunning", "Running against Houdini" ), + InformationCategoryBuilder, RunningMajor, RunningMinor, RunningBuild, RunningPatch ); + } + + // Add running against Houdini Engine version. + { + int32 RunningEngineMajor = 0; + int32 RunningEngineMinor = 0; + int32 RunningEngineApi = 0; + + if ( FHoudiniApi::IsHAPIInitialized() ) + { + const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); + // Retrieve version numbers for running Houdini Engine. + FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_MAJOR, &RunningEngineMajor ); + FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_MINOR, &RunningEngineMinor ); + FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_API, &RunningEngineApi ); + } + + CreateHoudiniEngineEntry( + LOCTEXT( "HEngineInformationRunning", "Running against Houdini Engine" ), + InformationCategoryBuilder, RunningEngineMajor, RunningEngineMinor, RunningEngineApi ); + } + + // Add path of libHAPI. + { + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + if ( LibHAPILocation.IsEmpty() ) + LibHAPILocation = TEXT( "Not Found" ); + + CreateHAPILibraryPathEntry( LibHAPILocation, InformationCategoryBuilder ); + } + + // Add licensing info. + { + FString HAPILicenseType = TEXT( "" ); + if ( !FHoudiniEngineUtils::GetLicenseType( HAPILicenseType ) ) + HAPILicenseType = TEXT( "Unknown" ); + + CreateHAPILicenseEntry( HAPILicenseType, InformationCategoryBuilder ); + } + } + + DetailBuilder.EditCategory( "HoudiniLocation", FText::GetEmpty(), ECategoryPriority::Important ); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHoudiniEntry( + const FText & EntryName, + IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, + int32 VersionPatch ) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + + Row.NameWidget.Widget = + SNew( STextBlock ) + .Text( EntryName ) + .Font( IDetailLayoutBuilder::GetDetailFont() ); + + TSharedRef< SHorizontalBox > HorizontalBox = SNew( SHorizontalBox ); + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew( SNumericEntryBox< int32 > ); + HorizontalBox->AddSlot().Padding( 0, 0, 5, 0 ) + [ + SAssignNew( NumericEntryBox, SNumericEntryBox< int32 > ) + .AllowSpin( false ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + .Value( VersionMajor ) + ]; + NumericEntryBox->SetEnabled( false ); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew( SNumericEntryBox< int32 > ); + HorizontalBox->AddSlot().Padding( 0, 0, 5, 0 ) + [ + SAssignNew( NumericEntryBox, SNumericEntryBox< int32 > ) + .AllowSpin( false ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + .Value( VersionMinor ) + ]; + NumericEntryBox->SetEnabled( false ); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew( SNumericEntryBox< int32 > ); + HorizontalBox->AddSlot().Padding( 0, 0, 5, 0 ) + [ + SAssignNew( NumericEntryBox, SNumericEntryBox< int32 > ) + .AllowSpin( false ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + .Value( VersionBuild ) + ]; + NumericEntryBox->SetEnabled( false ); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew( SNumericEntryBox< int32 > ); + HorizontalBox->AddSlot().Padding( 0, 0, 5, 0 ) + [ + SAssignNew( NumericEntryBox, SNumericEntryBox< int32 > ) + .AllowSpin( false ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + .Value( VersionPatch ) + ]; + NumericEntryBox->SetEnabled( false ); + } + + Row.ValueWidget.Widget = HorizontalBox; + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH ); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHoudiniEngineEntry( + const FText & EntryName, + IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionApi) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + + Row.NameWidget.Widget = + SNew( STextBlock ) + .Text( EntryName ) + .Font( IDetailLayoutBuilder::GetDetailFont() ); + + TSharedRef< SHorizontalBox > HorizontalBox = SNew( SHorizontalBox ); + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew( SNumericEntryBox< int32 > ); + HorizontalBox->AddSlot().Padding( 0, 0, 5, 0 ) + [ + SAssignNew( NumericEntryBox, SNumericEntryBox< int32 > ) + .AllowSpin( false ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + .Value( VersionMajor ) + ]; + NumericEntryBox->SetEnabled( false ); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew( SNumericEntryBox< int32 > ); + HorizontalBox->AddSlot().Padding( 0, 0, 5, 0 ) + [ + SAssignNew( NumericEntryBox, SNumericEntryBox< int32 > ) + .AllowSpin( false ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + .Value( VersionMinor ) + ]; + NumericEntryBox->SetEnabled( false ); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew( SNumericEntryBox< int32 > ); + HorizontalBox->AddSlot().Padding( 0, 0, 5, 0 ) + [ + SAssignNew( NumericEntryBox, SNumericEntryBox< int32 > ) + .AllowSpin( false ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + .Value( VersionApi ) + ]; + NumericEntryBox->SetEnabled( false ); + } + + Row.ValueWidget.Widget = HorizontalBox; + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH ); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHAPILibraryPathEntry( + const FString & LibHAPIPath, + IDetailCategoryBuilder & DetailCategoryBuilder ) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + + FString LibHAPIName = FString::Printf( TEXT( "Location of %s" ), *FHoudiniEngineUtils::HoudiniGetLibHAPIName() ); + + Row.NameWidget.Widget = + SNew( STextBlock ) + .Text( FText::FromString( LibHAPIName ) ) + .Font( IDetailLayoutBuilder::GetDetailFont() ); + + TSharedRef TextBlock = + SNew( STextBlock ) + .Text( FText::FromString( LibHAPIPath ) ) + .Font( IDetailLayoutBuilder::GetDetailFont() ); + + TextBlock->SetEnabled( false ); + Row.ValueWidget.Widget = TextBlock; + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH ); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHAPILicenseEntry( + const FString & LibHAPILicense, + IDetailCategoryBuilder & DetailCategoryBuilder ) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow( FText::GetEmpty() ); + + FString LibHAPILicenseTypeText = TEXT( "Acquired License Type" ); + + Row.NameWidget.Widget = SNew( STextBlock ) + .Text( FText::FromString( LibHAPILicenseTypeText ) ) + .Font( IDetailLayoutBuilder::GetDetailFont() ); + + TSharedRef TextBlock = SNew( STextBlock ) + .Text( FText::FromString( LibHAPILicense ) ) + .Font( IDetailLayoutBuilder::GetDetailFont() ); + + TextBlock->SetEnabled( false ); + Row.ValueWidget.Widget = TextBlock; + Row.ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH ); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h new file mode 100644 index 00000000..152b9bfd --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h @@ -0,0 +1,76 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#pragma once + +#include "DetailCategoryBuilder.h" +#include "IDetailCustomization.h" + +class FHoudiniRuntimeSettingsDetails : public IDetailCustomization +{ + public: + + /** Constructor. **/ + FHoudiniRuntimeSettingsDetails(); + + /** Destructor. **/ + virtual ~FHoudiniRuntimeSettingsDetails(); + + /** IDetailCustomization methods. **/ + public: + + virtual void CustomizeDetails( IDetailLayoutBuilder & DetailBuilder ) override; + + public: + + /** Create an instance of this detail layout class. **/ + static TSharedRef< IDetailCustomization > MakeInstance(); + + protected: + + /** Used to create Houdini version entry. **/ + void CreateHoudiniEntry( + const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, int32 VersionPatch ); + + /** Used to create Houdini Engine version entry. **/ + void CreateHoudiniEngineEntry( + const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionApi ); + + /** Used to create libHAPI dynamic library path entry. **/ + void CreateHAPILibraryPathEntry( + const FString & LibHAPIPath, IDetailCategoryBuilder & DetailCategoryBuilder ); + + /** Used to create libHAPI license information entry. **/ + void CreateHAPILicenseEntry( + const FString & LibHAPILicense, IDetailCategoryBuilder & DetailCategoryBuilder ); +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniShelfEdMode.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniShelfEdMode.cpp new file mode 100644 index 00000000..6c714ed5 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniShelfEdMode.cpp @@ -0,0 +1,70 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniShelfEdMode.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniShelfEdModeToolkit.h" +#include "Toolkits/ToolkitManager.h" +#include "EdMode.h" +#include "EditorModeTools.h" +#include "EditorModeManager.h" + +const FEditorModeID FHoudiniShelfEdMode::EM_HoudiniShelfEdModeId = TEXT( "EM_HoudiniShelfEdMode" ); + +FHoudiniShelfEdMode::FHoudiniShelfEdMode() +{ + +} + +void +FHoudiniShelfEdMode::Enter() +{ + FEdMode::Enter(); + + if ( !Toolkit.IsValid() && UsesToolkits() ) + { + Toolkit = MakeShareable( new FHoudiniShelfEdModeToolkit ); + Toolkit->Init( Owner->GetToolkitHost() ); + } +} + +void +FHoudiniShelfEdMode::Exit() +{ + if ( Toolkit.IsValid() ) + { + FToolkitManager::Get().CloseToolkit( Toolkit.ToSharedRef() ); + Toolkit.Reset(); + } + + // Call base Exit method to ensure proper cleanup + FEdMode::Exit(); +} + +bool +FHoudiniShelfEdMode::UsesToolkits() const +{ + return true; +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniShelfEdMode.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniShelfEdMode.h new file mode 100644 index 00000000..e64e01d3 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniShelfEdMode.h @@ -0,0 +1,46 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +//#include "UnrealEd.h" +#include "Editor.h" +#include "EdMode.h" +#include "Toolkits/BaseToolkit.h" + +class FHoudiniShelfEdMode : public FEdMode +{ +public: + const static FEditorModeID EM_HoudiniShelfEdModeId; +public: + FHoudiniShelfEdMode(); + + // FEdMode interface + virtual void Enter() override; + virtual void Exit() override; + //virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override; + //virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override; + //virtual void ActorSelectionChangeNotify() override; + bool UsesToolkits() const override; + // End of FEdMode interface +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniShelfEdModeToolkit.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniShelfEdModeToolkit.cpp new file mode 100644 index 00000000..430078ae --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniShelfEdModeToolkit.cpp @@ -0,0 +1,65 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniShelfEdModeToolkit.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniShelfEdMode.h" +#include "HoudiniAttributeDataComponent.h" +#include "Engine/Selection.h" +#include "EditorModeManager.h" +#include "Widgets/Input/SButton.h" + +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FHoudiniShelfEdModeToolkit::FHoudiniShelfEdModeToolkit() +{ +} + +void +FHoudiniShelfEdModeToolkit::Init( const TSharedPtr& InitToolkitHost ) +{ + FModeToolkit::Init( InitToolkitHost ); +} + +FName +FHoudiniShelfEdModeToolkit::GetToolkitFName() const +{ + return FName( "HoudiniEditor" ); +} + +FText +FHoudiniShelfEdModeToolkit::GetBaseToolkitName() const +{ + return LOCTEXT( "ToolkitName", "Houdini Editor" ); +} + +class FEdMode* + FHoudiniShelfEdModeToolkit::GetEditorMode() const +{ + return GLevelEditorModeTools().GetActiveMode( FHoudiniShelfEdMode::EM_HoudiniShelfEdModeId ); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniShelfEdModeToolkit.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniShelfEdModeToolkit.h new file mode 100644 index 00000000..0a278a2b --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniShelfEdModeToolkit.h @@ -0,0 +1,46 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ +#pragma once + +#include "Editor/UnrealEd/Public/Toolkits/BaseToolkit.h" + + +class FHoudiniShelfEdModeToolkit : public FModeToolkit +{ +public: + + FHoudiniShelfEdModeToolkit(); + + /** FModeToolkit interface */ + virtual void Init( const TSharedPtr& InitToolkitHost ) override; + + /** IToolkit interface */ + virtual FName GetToolkitFName() const override; + virtual FText GetBaseToolkitName() const override; + virtual class FEdMode* GetEditorMode() const override; + virtual TSharedPtr GetInlineContent() const override { return ToolkitWidget; } + +private: + + TSharedPtr ToolkitWidget; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp new file mode 100644 index 00000000..87e2d50c --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp @@ -0,0 +1,786 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniSplineComponentVisualizer.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineEditor.h" +#include "EditorViewportClient.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Framework/Application/SlateApplication.h" +#include "EditorStyleSet.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_HIT_PROXY( HHoudiniSplineVisProxy, HComponentVisProxy ); +IMPLEMENT_HIT_PROXY( HHoudiniSplineControlPointVisProxy, HHoudiniSplineVisProxy ); + +HHoudiniSplineVisProxy::HHoudiniSplineVisProxy( const UActorComponent * InComponent ) + : HComponentVisProxy( InComponent, HPP_Wireframe ) +{} + +HHoudiniSplineControlPointVisProxy::HHoudiniSplineControlPointVisProxy( + const UActorComponent * InComponent, int32 InControlPointIndex ) + : HHoudiniSplineVisProxy( InComponent ) + , ControlPointIndex( InControlPointIndex ) +{} + +FHoudiniSplineComponentVisualizerCommands::FHoudiniSplineComponentVisualizerCommands() + : TCommands< FHoudiniSplineComponentVisualizerCommands >( + "HoudiniSplineComponentVisualizer", + LOCTEXT( "HoudiniSplineComponentVisualizer", "Houdini Spline Component Visualizer" ), + NAME_None, + FEditorStyle::GetStyleSetName() ) +{} + +void +FHoudiniSplineComponentVisualizerCommands::RegisterCommands() +{ + UI_COMMAND( + CommandAddControlPoint, "Add Control Point", "Add Control Point.", + EUserInterfaceActionType::Button, FInputChord() ); + + UI_COMMAND( + CommandDuplicateControlPoint, "Duplicate Control Point", "Duplicate Control Point.", + EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND( + CommandDeleteControlPoint, "Delete Control Point", "Delete Control Point.", + EUserInterfaceActionType::Button, FInputChord(EKeys::Delete) ); +} + +FHoudiniSplineComponentVisualizer::FHoudiniSplineComponentVisualizer() + : FComponentVisualizer() + , EditedHoudiniSplineComponent( nullptr ) + , bCurveEditing( false ) + , bAllowDuplication( true ) + , CachedRotation( FQuat::Identity ) + , bComponentNeedUpdate( false ) + , bCookOnlyOnMouseRelease( false ) + , bRecordTransactionOnMove( true ) +{ + FHoudiniSplineComponentVisualizerCommands::Register(); + VisualizerActions = MakeShareable( new FUICommandList ); +} + +FHoudiniSplineComponentVisualizer::~FHoudiniSplineComponentVisualizer() +{ + FHoudiniSplineComponentVisualizerCommands::Unregister(); +} + +void +FHoudiniSplineComponentVisualizer::OnRegister() +{ + const auto & Commands = FHoudiniSplineComponentVisualizerCommands::Get(); + + VisualizerActions->MapAction( + Commands.CommandAddControlPoint, + FExecuteAction::CreateSP( this, &FHoudiniSplineComponentVisualizer::OnAddControlPoint ), + FCanExecuteAction::CreateSP( this, &FHoudiniSplineComponentVisualizer::IsAddControlPointValid ) ); + + VisualizerActions->MapAction( + Commands.CommandDuplicateControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid)); + + VisualizerActions->MapAction( + Commands.CommandDeleteControlPoint, + FExecuteAction::CreateSP( this, &FHoudiniSplineComponentVisualizer::OnDeleteControlPoint ), + FCanExecuteAction::CreateSP( this, &FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid ) ); +} + +void +FHoudiniSplineComponentVisualizer::DrawVisualization( + const UActorComponent * Component, const FSceneView * View, + FPrimitiveDrawInterface * PDI ) +{ + const UHoudiniSplineComponent * HoudiniSplineComponent = Cast< const UHoudiniSplineComponent >( Component ); + + if ( HoudiniSplineComponent && HoudiniSplineComponent->IsValidCurve() && HoudiniSplineComponent->IsActive() ) + { + static const FColor ColorNormal = FColor(255, 255, 255); + static const FColor ColorFirst(0, 192, 0); + static const FColor ColorSecond(255, 159, 0); + static const FColor ColorSelected(255, 0, 0); + + static const FColor ColorNone = FColor(172, 172, 172); + static const FColor ColorNoneFirst = FColor(172, 255, 172); + static const FColor ColorNoneSecond = FColor(254, 216, 177); + + static const float GrabHandleSize = 12.0f; + static const float GrabHandleSizeNone = 12.0f;// 8.0f; + static const float GrabHandleSizeSelected = 13.0f; + + // Get component transformation. + const FTransform & HoudiniSplineComponentTransform = HoudiniSplineComponent->GetComponentTransform(); + + // Get curve points. + const TArray< FTransform > & CurvePoints = HoudiniSplineComponent->CurvePoints; + const TArray< FVector > & CurveDisplayPoints = HoudiniSplineComponent->CurveDisplayPoints; + + // Draw the curve. + FVector DisplayPointFirst; + FVector DisplayPointPrevious; + + // Dim the color if no points is selected + bool bNoPointSelected = EditedControlPointsIndexes.Num() <= 0; + float GrabHandleCurrentSize = bNoPointSelected ? GrabHandleSizeNone : GrabHandleSize; + + int32 NumDisplayPoints = CurveDisplayPoints.Num(); + for ( int32 DisplayPointIdx = 0; DisplayPointIdx < NumDisplayPoints; ++DisplayPointIdx ) + { + // Get point for this index. + const FVector & DisplayPoint = + HoudiniSplineComponentTransform.TransformPosition( CurveDisplayPoints[ DisplayPointIdx ] ); + + if ( DisplayPointIdx > 0 ) + { + // Draw line from previous point to current one. + PDI->DrawLine( DisplayPointPrevious, DisplayPoint, bNoPointSelected ? ColorNone : ColorNormal, SDPG_Foreground ); + } + else + { + DisplayPointFirst = DisplayPoint; + } + + // If this is last point and curve is closed, draw link from last to first. + if ( HoudiniSplineComponent->IsClosedCurve() && NumDisplayPoints > 1 && + DisplayPointIdx + 1 == NumDisplayPoints ) + { + PDI->DrawLine( DisplayPointFirst, DisplayPoint, bNoPointSelected ? ColorNone : ColorNormal, SDPG_Foreground ); + } + + DisplayPointPrevious = DisplayPoint; + } + + // Draw control points. + for ( int32 PointIdx = 0; PointIdx < CurvePoints.Num(); ++PointIdx ) + { + // Get point at this index. + const FVector & DisplayPoint = HoudiniSplineComponentTransform.TransformPosition( CurvePoints[ PointIdx ].GetLocation() ); + + // Draw point and set hit box for it. + PDI->SetHitProxy(new HHoudiniSplineControlPointVisProxy(HoudiniSplineComponent, PointIdx)); + + if ( ( bCurveEditing ) && ( EditedControlPointsIndexes.Contains(PointIdx))) + { + // If we are editing this control point, change its color + PDI->DrawPoint(DisplayPoint, ColorSelected, GrabHandleSizeSelected, SDPG_Foreground); + } + else + { + // Color the first two points differently to show the direction of the spline + if( PointIdx == 0 ) + PDI->DrawPoint(DisplayPoint, bNoPointSelected ? ColorNoneFirst : ColorFirst, GrabHandleCurrentSize, SDPG_Foreground); + else if (PointIdx == 1) + PDI->DrawPoint(DisplayPoint, bNoPointSelected ? ColorNoneSecond : ColorSecond, GrabHandleCurrentSize, SDPG_Foreground); + else + PDI->DrawPoint(DisplayPoint, bNoPointSelected ? ColorNone : ColorNormal, GrabHandleCurrentSize, SDPG_Foreground); + } + + PDI->SetHitProxy(nullptr); + } + } +} + +bool +FHoudiniSplineComponentVisualizer::VisProxyHandleClick( + FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click ) +{ + bCurveEditing = false; + if ( !VisProxy || !VisProxy->Component.IsValid() ) + return bCurveEditing; + + const UHoudiniSplineComponent * HoudiniSplineComponent = + CastChecked< const UHoudiniSplineComponent >( VisProxy->Component.Get() ); + + EditedHoudiniSplineComponent = const_cast(HoudiniSplineComponent); + + + if ( !HoudiniSplineComponent ) + return bCurveEditing; + + if ( !VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType()) ) + return bCurveEditing; + + HHoudiniSplineControlPointVisProxy * ControlPointProxy = (HHoudiniSplineControlPointVisProxy *) VisProxy; + if ( !ControlPointProxy ) + return bCurveEditing; + + bCurveEditing = true; + + // If we are right-clicking, we dont want to select a new point unless the selection is empty/just one point... + bool bRightClick = Click.GetKey() == EKeys::RightMouseButton; + if (bRightClick && EditedControlPointsIndexes.Num() > 1) + return bCurveEditing; + + bool bIsMultiSelecting = false; + FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys(); + if(ModifierKeys.IsControlDown()) + bIsMultiSelecting = true; + + if ( bIsMultiSelecting ) + { + // Add to multi-selection + if ( !EditedControlPointsIndexes.Contains(ControlPointProxy->ControlPointIndex) ) + EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); + else + EditedControlPointsIndexes.Remove(ControlPointProxy->ControlPointIndex); + } + else + { + // Single selection + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); + } + + CacheRotation(); + + return bCurveEditing; +} + +bool +FHoudiniSplineComponentVisualizer::HandleInputKey( + FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event ) +{ + bool bHandled = false; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + bCookOnlyOnMouseRelease = HoudiniRuntimeSettings->bCookCurvesOnMouseRelease; + + if ( Key == EKeys::LeftMouseButton && Event == IE_Released ) + { + // Updates the spline + if ( bComponentNeedUpdate ) + UpdateHoudiniComponents(); + + // Reset duplication flag on LMB release. + bAllowDuplication = true; + + // Reset the transaction flag + bRecordTransactionOnMove = true; + + // Re-cache the rotation + CacheRotation(); + } + + if ( Key == EKeys::Delete && Event == IE_Pressed ) + { + if (IsDeleteControlPointValid()) + { + OnDeleteControlPoint(); + return true; + } + } + + if ( Event == IE_Pressed ) + bHandled = VisualizerActions->ProcessCommandBindings( Key, FSlateApplication::Get().GetModifierKeys(), false ); + + return bHandled; +} + +void +FHoudiniSplineComponentVisualizer::EndEditing() +{ + EditedHoudiniSplineComponent = nullptr; + EditedControlPointsIndexes.Empty(); +} + +bool +FHoudiniSplineComponentVisualizer::GetWidgetLocation( + const FEditorViewportClient * ViewportClient, + FVector & OutLocation ) const +{ + if ( !EditedHoudiniSplineComponent || EditedControlPointsIndexes.Num() <= 0 ) + return false; + + // Get curve points. + const TArray< FTransform > & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + int32 nCurrentCPIndex = -1; + FVector LocationSum = FVector::ZeroVector; + + for (int n = 0; n < EditedControlPointsIndexes.Num(); n++) + { + nCurrentCPIndex = EditedControlPointsIndexes[n]; + if( nCurrentCPIndex < 0 || nCurrentCPIndex >= CurvePoints.Num() ) + continue; + + LocationSum += CurvePoints[nCurrentCPIndex].GetLocation(); + } + + LocationSum /= EditedControlPointsIndexes.Num(); + OutLocation = EditedHoudiniSplineComponent->GetComponentTransform().TransformPosition(LocationSum); + + return true; +} + +bool +FHoudiniSplineComponentVisualizer::GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const +{ + // Change the system only for the rotation gizmo or if in LocalSpace + if (ViewportClient->GetWidgetCoordSystemSpace() != COORD_Local && ViewportClient->GetWidgetMode() != FWidget::WM_Rotate) + return false; + + // We only orient the widget if we select one point + if (!EditedHoudiniSplineComponent || EditedControlPointsIndexes.Num() != 1) + return false; + + // Get the selected curve point + /* + int32 nCurrentCPIndex = EditedControlPointsIndexes[0]; + FTransform transf = EditedHoudiniSplineComponent->CurvePoints[nCurrentCPIndex]; + transf.SetLocation(FVector::ZeroVector); + OutMatrix = transf.ToMatrixNoScale(); + */ + + OutMatrix = FRotationMatrix::Make(CachedRotation); + + return true; +} + +bool +FHoudiniSplineComponentVisualizer::HandleInputDelta( + FEditorViewportClient * ViewportClient, FViewport * Viewport, + FVector & DeltaTranslate, FRotator & DeltaRotate, FVector & DeltaScale) +{ + if (!EditedHoudiniSplineComponent || EditedControlPointsIndexes.Num() <= 0) + return false; + + if ( ViewportClient->IsAltPressed() && bAllowDuplication ) + { + DuplicateControlPoint(); + + // Don't duplicate again until we release LMB + bAllowDuplication = false; + } + + // Get curve points. + const TArray< FTransform > & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + // Get component transformation. + const FTransform & HoudiniSplineComponentTransform = EditedHoudiniSplineComponent->GetComponentTransform(); + + + FTransform CurrentPoint = FTransform::Identity; + for ( int n = 0; n < EditedControlPointsIndexes.Num(); n++ ) + { + // Get current point from the selected points + int32 nCurrentCPIndex = EditedControlPointsIndexes[n]; + if (nCurrentCPIndex < 0 || nCurrentCPIndex >= CurvePoints.Num()) + continue; + + CurrentPoint = CurvePoints[nCurrentCPIndex]; + + // Handle change in translation. + if ( !DeltaTranslate.IsZero() ) + { + FVector PointTransformed = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint.GetLocation()); // Get Position in world space + FVector PointTransformedDelta = PointTransformed + DeltaTranslate; // apply delta in world space + PointTransformed = HoudiniSplineComponentTransform.InverseTransformPosition(PointTransformedDelta); // convert back to local + CurrentPoint.SetLocation( PointTransformed ); + } + + // Handle change in rotation. + if ( !DeltaRotate.IsZero() ) + { + FQuat NewRot = HoudiniSplineComponentTransform.GetRotation() * CurrentPoint.GetRotation(); // convert local-space rotation to world-space + NewRot = DeltaRotate.Quaternion() * NewRot; // apply world-space rotation + NewRot = HoudiniSplineComponentTransform.GetRotation().Inverse() * NewRot; // convert world-space rotation to local-space + CurrentPoint.SetRotation( NewRot ); + } + + // Handle change in scale + if ( !DeltaScale.IsZero() ) + { + FVector NewScale = CurrentPoint.GetScale3D() * ( FVector(1,1,1) + DeltaScale ); + CurrentPoint.SetScale3D( NewScale ); + } + + NotifyComponentModified(nCurrentCPIndex, CurrentPoint); + } + + if ( ( bComponentNeedUpdate ) && ( !bCookOnlyOnMouseRelease ) ) + { + // Update and cook the asset + UpdateHoudiniComponents(); + } + + return true; +} + +TSharedPtr< SWidget > +FHoudiniSplineComponentVisualizer::GenerateContextMenu() const +{ + FHoudiniEngineEditor& HoudiniEngineEditor = FHoudiniEngineEditor::Get(); + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + FMenuBuilder MenuBuilder( true, VisualizerActions ); + { + MenuBuilder.BeginSection( "CurveKeyEdit" ); + + { + if ( EditedControlPointsIndexes.Num() > 0 ) + { + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandAddControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon( StyleSetName, "HoudiniEngine.HoudiniEngineLogo" ) ); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandDuplicateControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon( StyleSetName, "HoudiniEngine.HoudiniEngineLogo" ) ); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandDeleteControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo" ) ); + } + } + + MenuBuilder.EndSection(); + } + + TSharedPtr< SWidget > MenuWidget = MenuBuilder.MakeWidget(); + return MenuWidget; +} + +void +FHoudiniSplineComponentVisualizer::UpdateHoudiniComponents() +{ + if ( EditedHoudiniSplineComponent ) + EditedHoudiniSplineComponent->UpdateHoudiniComponents(); + + bComponentNeedUpdate = false; +} + +void +FHoudiniSplineComponentVisualizer::NotifyComponentModified( int32 PointIndex, const FTransform & Point ) +{ + if ( !EditedHoudiniSplineComponent ) + return; + + UHoudiniAssetComponent * HoudiniAssetComponent = + Cast< UHoudiniAssetComponent >( EditedHoudiniSplineComponent->GetAttachParent() ); + + if ( bRecordTransactionOnMove ) + { + FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniSplineComponentChange", "Houdini Spline Component: Moving a point"), + HoudiniAssetComponent ); + EditedHoudiniSplineComponent->Modify(); + + // Do not record further transaction until the flag is reset + bRecordTransactionOnMove = false; + } + + // Update given control point. + EditedHoudiniSplineComponent->UpdatePoint( PointIndex, Point ); + + bComponentNeedUpdate = true; +} + +void +FHoudiniSplineComponentVisualizer::OnAddControlPoint() +{ + if ( !EditedHoudiniSplineComponent || EditedControlPointsIndexes.Num() < 0 ) + return; + + // Transaction for Undo/Redo + UHoudiniAssetComponent * HoudiniAssetComponent = + Cast< UHoudiniAssetComponent >(EditedHoudiniSplineComponent->GetAttachParent()); + + if (!HoudiniAssetComponent) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniSplineComponentChange", "Houdini Spline Component: Adding a control point"), + HoudiniAssetComponent); + + EditedHoudiniSplineComponent->Modify(); + + FTransform OtherPoint = FTransform::Identity; + FTransform CurrentPoint = FTransform::Identity; + int32 nCurrentCPIndex = -1; + + // We need to sort the selection to insert the new nodes properly + EditedControlPointsIndexes.Sort(); + + const TArray< FTransform > & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + TArray tNewSelection; + for (int32 n = EditedControlPointsIndexes.Num() - 1; n >= 0 ; n--) + { + // Get current point from the selected points + nCurrentCPIndex = EditedControlPointsIndexes[n]; + if (nCurrentCPIndex < 0 || nCurrentCPIndex >= CurvePoints.Num()) + continue; + + CurrentPoint = CurvePoints[nCurrentCPIndex]; + + // Select the other point + if (nCurrentCPIndex + 1 != CurvePoints.Num()) + { + OtherPoint = CurvePoints[nCurrentCPIndex + 1]; + } + else + { + if (EditedHoudiniSplineComponent->bClosedCurve) + { + OtherPoint = CurvePoints[0]; + } + else + { + OtherPoint = CurvePoints[nCurrentCPIndex - 1]; + nCurrentCPIndex--; + } + } + + FVector NewPointLocation = CurrentPoint.GetLocation() + (OtherPoint.GetLocation() - CurrentPoint.GetLocation()) / 2.0f; + FVector NewPointScale = CurrentPoint.GetScale3D() + (OtherPoint.GetScale3D() - CurrentPoint.GetScale3D()) / 2.0f; + FQuat NewPointRotation = FQuat::Slerp(CurrentPoint.GetRotation(), OtherPoint.GetRotation(), 0.5f); + + FTransform NewTransform = FTransform::Identity; + NewTransform.SetLocation(NewPointLocation); + NewTransform.SetScale3D(NewPointScale); + NewTransform.SetRotation(NewPointRotation); + + int32 NewPointIndex = AddControlPointAfter(NewTransform, nCurrentCPIndex); + + tNewSelection.Add(NewPointIndex); + } + + // Update the spline component + UpdateHoudiniComponents(); + + // Select the new points. + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes = tNewSelection; +} + +bool +FHoudiniSplineComponentVisualizer::IsAddControlPointValid() const +{ + // We can always add points. + return true; +} + +void +FHoudiniSplineComponentVisualizer::OnDeleteControlPoint() +{ + if (!EditedHoudiniSplineComponent || EditedControlPointsIndexes.Num() <= 0) + return; + + UHoudiniAssetComponent * HoudiniAssetComponent = + Cast< UHoudiniAssetComponent >(EditedHoudiniSplineComponent->GetAttachParent() ); + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_EDITOR ), + LOCTEXT( "HoudiniSplineComponentChange", "Houdini Spline Component: Removing a control point" ), + HoudiniAssetComponent); + EditedHoudiniSplineComponent->Modify(); + + // We need to sort the selection to delete the nodes properly + EditedControlPointsIndexes.Sort(); + + int32 nOffset = 0; + TArray tNewSelection; + for (int32 n = 0; n < EditedControlPointsIndexes.Num(); n++) + { + int32 nIndex = EditedControlPointsIndexes[n] - nOffset; + EditedHoudiniSplineComponent->RemovePoint(nIndex); + + if (!tNewSelection.Contains(--nIndex)) + tNewSelection.Add(nIndex); + nOffset++; + } + + // Select previous points if possible + for (int32 n = tNewSelection.Num() - 1; n >= 0; n--) + { + // get the previous point + if (tNewSelection[n] < 0) + tNewSelection[n] = 0; + + // if the new index is invalid, or has been removed, unselect it + if (tNewSelection[n] >= EditedHoudiniSplineComponent->CurvePoints.Num() || EditedControlPointsIndexes.Contains(tNewSelection[n])) + { + tNewSelection.RemoveAt(n); + } + } + + if(tNewSelection.Num() > 0) + EditedControlPointsIndexes = tNewSelection; + else + { + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes.Add(0); + } + + // cache the rotation + CacheRotation(); + + // Update the spline object + UpdateHoudiniComponents(); +} + +bool +FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid() const +{ + if ( !EditedHoudiniSplineComponent || EditedControlPointsIndexes.Num() <= 0 ) + return false; + + // We can only delete points if we have more than two points. + if ( EditedHoudiniSplineComponent->GetCurvePointCount() < 2 ) + return false; + + // We need to leave 2 points after deleting + if ( EditedHoudiniSplineComponent->GetCurvePointCount() - EditedControlPointsIndexes.Num() < 2 ) + return false; + + return true; +} + +int32 FHoudiniSplineComponentVisualizer::AddControlPointAfter( const FTransform & NewPoint, const int32& nIndex ) +{ + if ( !EditedHoudiniSplineComponent ) + return nIndex; + + const TArray< FTransform > & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + check( nIndex >= 0 && nIndex < CurvePoints.Num() ); + + int32 ControlPointIndex = nIndex + 1; + if (ControlPointIndex == CurvePoints.Num()) + EditedHoudiniSplineComponent->AddPoint( NewPoint ); + else + EditedHoudiniSplineComponent->AddPoint( ControlPointIndex, NewPoint); + + // Return the newly created point index. + return ControlPointIndex; +} + +void +FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint() +{ + // Duplicate the selected points + DuplicateControlPoint(); + + // Update the spline component + UpdateHoudiniComponents(); +} + +void +FHoudiniSplineComponentVisualizer::DuplicateControlPoint() +{ + if (!EditedHoudiniSplineComponent || EditedControlPointsIndexes.Num() <= 0) + return; + + UHoudiniAssetComponent * HoudiniAssetComponent = + Cast< UHoudiniAssetComponent >(EditedHoudiniSplineComponent->GetAttachParent()); + + if (!HoudiniAssetComponent) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniSplineComponentChange", "Houdini Spline Component: Adding a control point"), + HoudiniAssetComponent); + + EditedHoudiniSplineComponent->Modify(); + + const TArray< FTransform > & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + // + EditedControlPointsIndexes.Sort(); + + int32 nCurrentCPIndex = -1; + int32 nNewCPIndex = -1; + TArray tNewSelection; + + FTransform NewPoint = FTransform::Identity; + int nOffset = 0; + for ( int32 n = 0; n < EditedControlPointsIndexes.Num(); n++ ) + { + // Get current point from the selected points + nCurrentCPIndex = EditedControlPointsIndexes[n] + nOffset; + if (nCurrentCPIndex < 0 || nCurrentCPIndex >= CurvePoints.Num()) + continue; + + // We just add the new point on top of the existing point. + NewPoint = CurvePoints[nCurrentCPIndex]; + + // Add the new point and select it. + nNewCPIndex = AddControlPointAfter(NewPoint, nCurrentCPIndex); + + // Small hack when extending from the first point + if (nCurrentCPIndex == 0) + nNewCPIndex = 0; + + tNewSelection.Add(nNewCPIndex); + nOffset++; + } + + // Select the new points. + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes = tNewSelection; +} + +bool +FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid() const +{ + // We can only duplicate points if we have selected a point. + if (!EditedHoudiniSplineComponent || EditedControlPointsIndexes.Num() <= 0 ) + return false; + + return true; +} + + +void +FHoudiniSplineComponentVisualizer::CacheRotation() +{ + FQuat NewCachedQuat = FQuat::Identity; + if (EditedHoudiniSplineComponent && EditedControlPointsIndexes.Num() == 1) + { + if (EditedHoudiniSplineComponent->CurvePoints.IsValidIndex(EditedControlPointsIndexes[0])) + NewCachedQuat = (EditedHoudiniSplineComponent->CurvePoints[EditedControlPointsIndexes[0]]).GetRotation(); + } + + CachedRotation = NewCachedQuat; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h new file mode 100644 index 00000000..12af1a59 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h @@ -0,0 +1,178 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#pragma once + +#include "HoudiniSplineComponent.h" +#include "Framework/Commands/Commands.h" +#include "ComponentVisualizer.h" +#include "Framework/Commands/UICommandList.h" + +/** Base class for clickable spline editing proxies. **/ +struct HHoudiniSplineVisProxy : public HComponentVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniSplineVisProxy( const UActorComponent * InComponent ); +}; + +/** Proxy for a spline control point. **/ +struct HHoudiniSplineControlPointVisProxy : public HHoudiniSplineVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniSplineControlPointVisProxy( const UActorComponent * InComponent, int32 InControlPointIndex ); + + int32 ControlPointIndex; +}; + +/** Define commands for our component visualizer */ +class FHoudiniSplineComponentVisualizerCommands : public TCommands< FHoudiniSplineComponentVisualizerCommands > +{ + public: + + /** Constructor. **/ + FHoudiniSplineComponentVisualizerCommands(); + + /** Register commands. **/ + virtual void RegisterCommands() override; + + public: + + /** Command for adding a control point. **/ + TSharedPtr< FUICommandInfo > CommandAddControlPoint; + + /** Command for duplicating a control point. **/ + TSharedPtr< FUICommandInfo > CommandDuplicateControlPoint; + + /** Command for deleting a control point. **/ + TSharedPtr< FUICommandInfo > CommandDeleteControlPoint; +}; + +/** Our spline visualizer. **/ +class FHoudiniSplineComponentVisualizer : public FComponentVisualizer +{ + public: + + FHoudiniSplineComponentVisualizer(); + virtual ~FHoudiniSplineComponentVisualizer(); + + /** FComponentVisualizer methods. **/ + public: + + /** Registration of this component visualizer. **/ + virtual void OnRegister() override; + + /** Draw visualization for the given component. **/ + virtual void DrawVisualization( + const UActorComponent * Component, const FSceneView * View, + FPrimitiveDrawInterface * PDI ) override; + + /** Handle a click on a registered hit box. **/ + virtual bool VisProxyHandleClick( + FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click ) override; + + /** Handle modifier key presses and depresses such as Alt for key duplication. **/ + virtual bool HandleInputKey( + FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event ) override; + + /** Called when editing is no longer being performed. **/ + virtual void EndEditing() override; + + /** Returns location of a gizmo widget. **/ + virtual bool GetWidgetLocation( + const FEditorViewportClient * ViewportClient, FVector & OutLocation ) const override; + + /** Returns Coordinate System of a gizmo widget. **/ + virtual bool GetCustomInputCoordinateSystem( + const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const override; + + /** Handle input change. **/ + virtual bool HandleInputDelta( + FEditorViewportClient * ViewportClient, FViewport * Viewport, FVector & DeltaTranslate, + FRotator & DeltaRotate, FVector & DeltaScale ) override; + + /** Create context menu for this visualizer. **/ + virtual TSharedPtr< SWidget > GenerateContextMenu() const override; + + protected: + + /** Update owner spline component and Houdini component it is attached to. **/ + void UpdateHoudiniComponents(); + + /** Perform internal component update. **/ + void NotifyComponentModified( int32 PointIndex, const FTransform & Point ); + + /** Callbacks for Add control point action. **/ + void OnAddControlPoint(); + bool IsAddControlPointValid() const; + + /** Callbacks for Delete control point action. **/ + void OnDeleteControlPoint(); + bool IsDeleteControlPointValid() const; + + /** Callbacks for Duplicate control point action. **/ + void OnDuplicateControlPoint(); + bool IsDuplicateControlPointValid() const; + + void DuplicateControlPoint(); + int32 AddControlPointAfter( const FTransform & NewPoint, const int32& nIndex ); + + /** Store the current rotation to orient the rotation gizmo properly **/ + void CacheRotation(); + + protected: + + /** Visualizer actions. **/ + TSharedPtr< FUICommandList > VisualizerActions; + + /** Houdini component which is being edited. **/ + UHoudiniSplineComponent * EditedHoudiniSplineComponent; + + /** Is set to true if we are editing corresponding curve. **/ + bool bCurveEditing; + + /** Whether we currently allow duplication when dragging. */ + bool bAllowDuplication; + + /** Keeps index of currently selected control points, if editing is being performed. **/ + TArray EditedControlPointsIndexes; + + /** Rotation used for the gizmo widgets **/ + FQuat CachedRotation; + + /** Indicates the current selection has been modified and the parent component must be updated **/ + bool bComponentNeedUpdate; + + /** Indicates if the curves should only cook on mouse release **/ + bool bCookOnlyOnMouseRelease; + + /** Indicates wether or not a transaction should be recorded when moving a point **/ + bool bRecordTransactionOnMove; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/SHoudiniToolPalette.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/SHoudiniToolPalette.cpp new file mode 100644 index 00000000..aa1c8078 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/SHoudiniToolPalette.cpp @@ -0,0 +1,1230 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "SHoudiniToolPalette.h" + +#include "HoudiniApi.h" +#include "Editor.h" +#include "Modules/ModuleManager.h" +#include "Widgets/SBoxPanel.h" +#include "SlateOptMacros.h" +#include "Styling/CoreStyle.h" +#include "Widgets/Images/SImage.h" +#include "Styling/SlateTypes.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Layout/SScrollBorder.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Views/STableViewBase.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Views/SListView.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SComboBox.h" +#include "EditorStyleSet.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "AssetRegistryModule.h" +#include "DragAndDrop/AssetDragDropOp.h" +#include "ActorFactories/ActorFactory.h" +#include "Engine/Selection.h" +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" +#include "EditorViewportClient.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "AssetToolsModule.h" +#include "EditorFramework/AssetImportData.h" +#include "Toolkits/GlobalEditorCommonCommands.h" +#include "Landscape.h" +#include "LandscapeProxy.h" +#include "Framework/Application/SlateApplication.h" +#include "HAL/PlatformFilemanager.h" +#include "HAL/FileManager.h" +#include "Modules/ModuleManager.h" +#include "PropertyEditorModule.h" +#include "Interfaces/IMainFrameModule.h" + +#define LOCTEXT_NAMESPACE "HoudiniToolPalette" + + +UHoudiniToolProperties::UHoudiniToolProperties(const FObjectInitializer & ObjectInitializer) + :Super( ObjectInitializer ) + , Name() + , Type(EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE) + , SelectionType(EHoudiniToolSelectionType::HTOOL_SELECTION_ALL) + , ToolTip() + , IconPath(FFilePath()) + , AssetPath(FFilePath()) + , HelpURL() +{ +}; + +UHoudiniToolDirectoryProperties::UHoudiniToolDirectoryProperties(const FObjectInitializer & ObjectInitializer) + :Super(ObjectInitializer) + , CustomHoudiniToolsDirectories() +{ +}; + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION +void +SHoudiniToolPalette::Construct( const FArguments& InArgs ) +{ + FHoudiniEngineEditor& HoudiniEngineEditor = FModuleManager::GetModuleChecked("HoudiniEngineEditor"); + + UpdateHoudiniToolDirectories(); + + SAssignNew( HoudiniToolListView, SHoudiniToolListView ) + .SelectionMode( ESelectionMode::Single ) + .ListItemsSource( &HoudiniEngineEditor.GetHoudiniTools() ) + .OnGenerateRow( this, &SHoudiniToolPalette::MakeListViewWidget ) + .OnSelectionChanged( this, &SHoudiniToolPalette::OnSelectionChanged ) + .OnMouseButtonDoubleClick( this, &SHoudiniToolPalette::OnDoubleClickedListViewWidget ) + .OnContextMenuOpening( this, &SHoudiniToolPalette::ConstructHoudiniToolContextMenu ) + .ItemHeight( 35 ); + + int32 ToolDirComboSelectedIdx = HoudiniEngineEditor.CurrentHoudiniToolDirIndex + 1; + if (!HoudiniToolDirArray.IsValidIndex(ToolDirComboSelectedIdx)) + ToolDirComboSelectedIdx = 0; + + TSharedRef > > HoudiniToolDirComboBox = + SNew(SComboBox< TSharedPtr< FString > >) + .OptionsSource( &HoudiniToolDirArray) + .InitiallySelectedItem(HoudiniToolDirArray[ ToolDirComboSelectedIdx ]) + .OnGenerateWidget( SComboBox< TSharedPtr< FString > >::FOnGenerateWidget::CreateLambda( + []( TSharedPtr< FString > ChoiceEntry ) + { + FText ChoiceEntryText = FText::FromString( *ChoiceEntry ); + return SNew( STextBlock ) + .Text( ChoiceEntryText ) + .ToolTipText( ChoiceEntryText ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ); + } ) ) + .OnSelectionChanged( SComboBox< TSharedPtr< FString > >::FOnSelectionChanged::CreateLambda( + [=]( TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType ) + { + if ( !NewChoice.IsValid() ) + return; + OnDirectoryChange(*(NewChoice.Get()) ); + } ) ) + [ + SNew(STextBlock) + .Text(this, &SHoudiniToolPalette::OnGetSelectedDirText ) + .ToolTipText( this, &SHoudiniToolPalette::OnGetSelectedDirText ) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + TSharedPtr< SButton > EditButton; + FText EditButtonText = FText::FromString(TEXT("Edit")); + FText EditButtonTooltip = FText::FromString(TEXT("Add, Remove or Edit custom Houdini tool directories")); + + FText ActiveShelfText = FText::FromString(TEXT("Active Shelf:")); + FText ActiveShelfTooltip = FText::FromString(TEXT("Name of the currently selected Houdini Tool Shelf")); + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SScrollBorder, HoudiniToolListView.ToSharedRef()) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(ActiveShelfText) + .ToolTipText(ActiveShelfTooltip) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.BoldFont"))) + ] + + SHorizontalBox::Slot() + [ + HoudiniToolDirComboBox + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SAssignNew(EditButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(EditButtonText) + .ToolTipText(EditButtonTooltip) + .OnClicked(FOnClicked::CreateSP(this, &SHoudiniToolPalette::OnEditToolDirectories)) + ] + ] + ] + + + SVerticalBox::Slot() + .FillHeight(1.0f) + [ + SNew(SScrollBorder, HoudiniToolListView.ToSharedRef()) + [ + HoudiniToolListView.ToSharedRef() + ] + ] + ]; +} +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +TSharedRef< ITableRow > +SHoudiniToolPalette::MakeListViewWidget( TSharedPtr< FHoudiniTool > HoudiniTool, const TSharedRef< STableViewBase >& OwnerTable ) +{ + check( HoudiniTool.IsValid() ); + + auto HelpDefault = FEditorStyle::GetBrush( "HelpIcon" ); + auto HelpHovered = FEditorStyle::GetBrush( "HelpIcon.Hovered" ); + auto HelpPressed = FEditorStyle::GetBrush( "HelpIcon.Pressed" ); + auto DefaultTool = FHoudiniEngineStyle::Get()->GetBrush( "HoudiniEngine.HoudiniEngineLogo"); + TSharedPtr< SImage > HelpButtonImage; + TSharedPtr< SButton > HelpButton; + TSharedPtr< SImage > DefaultToolImage; + + // Building the tool's tooltip + FString HoudiniToolTip = HoudiniTool->Name.ToString() + TEXT( "\n" ); + if ( HoudiniTool->HoudiniAsset.IsValid() ) + HoudiniToolTip += HoudiniTool->HoudiniAsset.ToSoftObjectPath().ToString() /*->AssetFileName */+ TEXT( "\n\n" ); + if ( !HoudiniTool->ToolTipText.IsEmpty() ) + HoudiniToolTip += HoudiniTool->ToolTipText.ToString() + TEXT("\n\n"); + + // Adding a description of the tools behavior deriving from its type + switch ( HoudiniTool->Type ) + { + case EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE: + HoudiniToolTip += TEXT("Operator (Single):\nDouble clicking on this tool will instantiate the asset in the world.\nThe current selection will be automatically assigned to the asset's first input.\n" ); + break; + case EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI: + HoudiniToolTip += TEXT("Operator (Multiple):\nDouble clicking on this tool will instantiate the asset in the world.\nEach of the selected object will be assigned to one of the asset's input (object1 to input1, object2 to input2 etc.)\n" ); + break; + case EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH: + HoudiniToolTip += TEXT("Operator (Batch):\nDouble clicking on this tool will instantiate the asset in the world.\nAn instance of the asset will be created for each of the selected object, and the asset's first input will be set to that object.\n"); + break; + case EHoudiniToolType::HTOOLTYPE_GENERATOR: + default: + HoudiniToolTip += TEXT("Generator:\nDouble clicking on this tool will instantiate the asset in the world.\n"); + break; + } + + // Add a description from the tools selection type + if ( HoudiniTool->Type != EHoudiniToolType::HTOOLTYPE_GENERATOR ) + { + switch ( HoudiniTool->SelectionType ) + { + case EHoudiniToolSelectionType::HTOOL_SELECTION_ALL: + HoudiniToolTip += TEXT("\nBoth Content Browser and World Outliner selection will be considered.\nIf objects are selected in the content browser, the world selection will be ignored.\n"); + break; + + case EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY: + HoudiniToolTip += TEXT("\nOnly World Outliner selection will be considered.\n"); + break; + + case EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY: + HoudiniToolTip += TEXT("\nOnly Content Browser selection will be considered.\n"); + break; + } + } + + if ( HoudiniTool->Type != EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH ) + { + if ( HoudiniTool->SelectionType != EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY ) + HoudiniToolTip += TEXT( "If objects are selected in the world outliner, the asset's transform will default to the mean Transform of the select objects.\n" ); + } + else + { + if ( HoudiniTool->SelectionType != EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY ) + HoudiniToolTip += TEXT( "If objects are selected in the world outliner, each created asset will use its object transform.\n" ); + } + + FText ToolTipText = FText::FromString( HoudiniToolTip ); + + // Build the Help Tooltip + FString HelpURL = HoudiniTool->HelpURL; + bool HasHelp = HelpURL.Len() > 0; + FText HelpTip = HasHelp ? FText::Format( LOCTEXT( "OpenHelp", "Click to view tool help: {0}" ), FText::FromString( HelpURL ) ) : HoudiniTool->Name; + + // Build the "DefaultTool" tool tip + bool IsDefault = HoudiniTool->DefaultTool; + FText DefaultToolText = FText::FromString( TEXT("Default Houdini Engine for Unreal plug-in tool") ); + + TSharedRef< STableRow> > TableRowWidget = + SNew( STableRow>, OwnerTable ) + .Style( FHoudiniEngineStyle::Get(), "HoudiniEngine.TableRow" ) + .OnDragDetected( this, &SHoudiniToolPalette::OnDraggingListViewWidget ); + + TSharedPtr< SHorizontalBox > ContentBox; + TSharedRef Content = + SNew( SBorder ) + .BorderImage( FCoreStyle::Get().GetBrush( "NoBorder" ) ) + .Padding( 0 ) + .ToolTip( SNew( SToolTip ).Text( ToolTipText ) ) + .Cursor( EMouseCursor::GrabHand ) + [ SAssignNew( ContentBox, SHorizontalBox ) ]; + + // Tool Icon + ContentBox->AddSlot() + .AutoWidth() + [ + SNew( SBorder ) + .Padding( 4.0f ) + .BorderImage( FHoudiniEngineStyle::Get()->GetBrush( "HoudiniEngine.ThumbnailShadow" ) ) + [ + SNew( SBox ) + .WidthOverride( 35.0f ) + .HeightOverride( 35.0f ) + [ + SNew( SBorder ) + .BorderImage( FHoudiniEngineStyle::Get()->GetBrush( "HoudiniEngine.ThumbnailBackground" ) ) + .HAlign( HAlign_Center ) + .VAlign( VAlign_Center ) + [ + SNew( SImage ) + .Image( HoudiniTool->Icon ) + .ToolTip( SNew( SToolTip ).Text( ToolTipText ) ) + ] + ] + ] + ]; + + // Tool name + tooltip + ContentBox->AddSlot() + .HAlign( HAlign_Left ) + .VAlign( VAlign_Center ) + .FillWidth( 1.f ) + [ + SNew( STextBlock ) + .TextStyle( FHoudiniEngineStyle::Get(), "HoudiniEngine.ThumbnailText" ) + .Text( HoudiniTool->Name ) + .ToolTip( SNew( SToolTip ).Text( ToolTipText ) ) + ]; + + // Default Tool Icon + if ( IsDefault ) + { + ContentBox->AddSlot() + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( DefaultToolImage, SImage ) + .ToolTip( SNew( SToolTip ).Text( DefaultToolText ) ) + ]; + + // Houdini logo for the Default Tool + DefaultToolImage->SetImage( DefaultTool ); + } + + if ( HasHelp ) + { + ContentBox->AddSlot() + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( HelpButton, SButton ) + .ContentPadding( 0 ) + .ButtonStyle( FEditorStyle::Get(), "HelpButton" ) + .OnClicked( FOnClicked::CreateLambda( [ HelpURL ]() { + if ( HelpURL.Len() ) + FPlatformProcess::LaunchURL( *HelpURL, nullptr, nullptr ); + return FReply::Handled(); + } ) ) + .HAlign( HAlign_Center ) + .VAlign( VAlign_Center ) + .ToolTip( SNew( SToolTip ).Text( HelpTip ) ) + [ + SAssignNew( HelpButtonImage, SImage ) + .ToolTip( SNew( SToolTip ).Text( HelpTip ) ) + ] + ]; + + HelpButtonImage->SetImage( TAttribute::Create( TAttribute::FGetter::CreateLambda( [=]() + { + if ( HelpButton->IsPressed() ) + { + return HelpPressed; + } + + if ( HelpButtonImage->IsHovered() ) + { + return HelpHovered; + } + + return HelpDefault; + } ) ) ); + } + + TableRowWidget->SetContent( Content ); + + return TableRowWidget; +} + +void +SHoudiniToolPalette::OnSelectionChanged( TSharedPtr ToolType, ESelectInfo::Type SelectionType ) +{ + if ( ToolType.IsValid() ) + { + ActiveTool = ToolType; + } + else + { + ActiveTool = NULL; + } +} + +FReply +SHoudiniToolPalette::OnDraggingListViewWidget( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) +{ + if ( MouseEvent.IsMouseButtonDown( EKeys::LeftMouseButton ) ) + { + if ( ActiveTool.IsValid() ) + { + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( "AssetRegistry" ); + UObject* AssetObj = ActiveTool->HoudiniAsset.LoadSynchronous(); + if ( AssetObj ) + { + FAssetData BackingAssetData = AssetRegistryModule.Get().GetAssetByObjectPath(*AssetObj->GetPathName()); + + return FReply::Handled().BeginDragDrop( FAssetDragDropOp::New( BackingAssetData, GEditor->FindActorFactoryForActorClass( AHoudiniAssetActor::StaticClass() ) ) ); + } + } + } + + return FReply::Unhandled(); +} + +void +SHoudiniToolPalette::OnDoubleClickedListViewWidget( TSharedPtr HoudiniTool ) +{ + if ( !HoudiniTool.IsValid() ) + return; + + return InstantiateHoudiniTool( HoudiniTool.Get() ); +} + +void +SHoudiniToolPalette::InstantiateHoudiniTool( FHoudiniTool* HoudiniTool ) +{ + if ( !HoudiniTool ) + return; + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( "AssetRegistry" ); + + // Load the asset + UObject* AssetObj = HoudiniTool->HoudiniAsset.LoadSynchronous(); + if ( !AssetObj ) + return; + + // Get the asset Factory + UActorFactory* Factory = GEditor->FindActorFactoryForActorClass( AHoudiniAssetActor::StaticClass() ); + if ( !Factory ) + return; + + // Get the current Level Editor Selection + TArray WorldSelection; + int32 WorldSelectionCount = FHoudiniEngineEditor::GetWorldSelection( WorldSelection ); + + // Get the current Content browser selection + TArray ContentBrowserSelection; + int32 ContentBrowserSelectionCount = FHoudiniEngineEditor::GetContentBrowserSelection( ContentBrowserSelection ); + + // By default, Content browser selection has a priority over the world selection + bool UseCBSelection = ContentBrowserSelectionCount > 0; + if ( HoudiniTool->SelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY ) + UseCBSelection = true; + else if ( HoudiniTool->SelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY ) + UseCBSelection = false; + + // Modify the created actor's position from the current editor world selection + FTransform SpawnTransform = GetDefaulToolSpawnTransform(); + if ( WorldSelectionCount > 0 ) + { + // Get the "mean" transform of all the selected actors + SpawnTransform = GetMeanWorldSelectionTransform(); + } + + // If the current tool is a batch one, we'll need to create multiple instances of the HDA + if ( HoudiniTool->Type == EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH ) + { + // Unselect the current selection to select the created actor after + if ( GEditor ) + GEditor->SelectNone( true, true, false ); + + // An instance of the asset will be created for each selected object + for( int32 SelecIndex = 0; SelecIndex < ( UseCBSelection ? ContentBrowserSelectionCount : WorldSelectionCount ); SelecIndex++ ) + { + // Get the current object + UObject* CurrentSelectedObject = nullptr; + if ( UseCBSelection && ContentBrowserSelection.IsValidIndex( SelecIndex ) ) + CurrentSelectedObject = ContentBrowserSelection[ SelecIndex ]; + + if ( !UseCBSelection && WorldSelection.IsValidIndex( SelecIndex ) ) + CurrentSelectedObject = WorldSelection[ SelecIndex ]; + + if ( !CurrentSelectedObject ) + continue; + + // If it's an actor, use its Transform to spawn the HDA + AActor* CurrentSelectedActor = Cast( CurrentSelectedObject ); + if ( CurrentSelectedActor ) + SpawnTransform = CurrentSelectedActor->GetTransform(); + else + SpawnTransform = GetDefaulToolSpawnTransform(); + + // Create the actor for the HDA + AActor* CreatedActor = Factory->CreateActor( AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform ); + if ( !CreatedActor ) + continue; + + // Get the HoudiniAssetActor / HoudiniAssetComponent we just created + AHoudiniAssetActor* HoudiniAssetActor = ( AHoudiniAssetActor* )CreatedActor; + if ( !HoudiniAssetActor ) + continue; + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if ( !HoudiniAssetComponent ) + continue; + + // Create and set the input preset for this HDA and selected Object + TMap< UObject*, int32 > InputPreset; + InputPreset.Add( CurrentSelectedObject, 0 ); + HoudiniAssetComponent->SetHoudiniToolInputPresets( InputPreset ); + + // Select the Actor we just created + if ( GEditor && GEditor->CanSelectActor( CreatedActor, true, false ) ) + GEditor->SelectActor( CreatedActor, true, true, true ); + } + } + else + { + // We only need to create a single instance of the asset, regarding of the selection + AActor* CreatedActor = Factory->CreateActor( AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform ); + if ( !CreatedActor ) + return; + + // Generator tools don't need to preset their input + if ( HoudiniTool->Type != EHoudiniToolType::HTOOLTYPE_GENERATOR ) + { + TMap InputPresets; + AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor ? HoudiniAssetActor->GetHoudiniAssetComponent() : nullptr; + if ( HoudiniAssetComponent ) + { + // Build the preset map + int InputIndex = 0; + for ( auto CurrentObject : ( UseCBSelection ? ContentBrowserSelection : WorldSelection ) ) + { + if ( !CurrentObject ) + continue; + + if ( HoudiniTool->Type == EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI ) + { + // The selection will be applied individually to multiple inputs + // (first object to first input, second object to second input etc...) + InputPresets.Add( CurrentObject, InputIndex++ ); + } + else + { + // All the selection will be applied to the asset's first input + InputPresets.Add( CurrentObject, 0 ); + } + } + + // Set the input preset on the HoudiniAssetComponent + if ( InputPresets.Num() > 0 ) + HoudiniAssetComponent->SetHoudiniToolInputPresets( InputPresets ); + } + } + + // Select the Actor we just created + if ( GEditor->CanSelectActor( CreatedActor, true, true ) ) + { + GEditor->SelectNone( true, true, false ); + GEditor->SelectActor( CreatedActor, true, true, true ); + } + } +} + +FTransform +SHoudiniToolPalette::GetDefaulToolSpawnTransform() +{ + FTransform SpawnTransform = FTransform::Identity; + + // Get the editor viewport LookAt position to spawn the new objects + if ( GEditor && GEditor->GetActiveViewport() ) + { + FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); + if ( ViewportClient ) + { + // We need to toggle the orbit camera on to get the proper LookAtLocation to spawn our asset + ViewportClient->ToggleOrbitCamera( true ); + SpawnTransform.SetLocation( ViewportClient->GetLookAtLocation() ); + ViewportClient->ToggleOrbitCamera( false ); + } + } + + return SpawnTransform; +} + +FTransform +SHoudiniToolPalette::GetMeanWorldSelectionTransform() +{ + FTransform SpawnTransform = GetDefaulToolSpawnTransform(); + + if ( GEditor && ( GEditor->GetSelectedActorCount() > 0 ) ) + { + // Get the current Level Editor Selection + USelection* SelectedActors = GEditor->GetSelectedActors(); + + int NumAppliedTransform = 0; + for ( FSelectionIterator It( *SelectedActors ); It; ++It ) + { + AActor * Actor = Cast< AActor >( *It ); + if ( !Actor ) + continue; + + // Just Ignore the SkySphere... + FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); + if ( ClassName == TEXT( "BP_Sky_Sphere_C" ) ) + continue; + + FTransform CurrentTransform = Actor->GetTransform(); + + ALandscapeProxy* Landscape = Cast< ALandscapeProxy >( Actor ); + if ( Landscape ) + { + // We need to offset Landscape's transform in X/Y to center them properly + FVector Origin, Extent; + Actor->GetActorBounds(false, Origin, Extent); + + // Use the origin's XY Position + FVector Location = CurrentTransform.GetLocation(); + Location.X = Origin.X; + Location.Y = Origin.Y; + CurrentTransform.SetLocation( Location ); + } + + // Accumulate all the actor transforms... + if ( NumAppliedTransform == 0 ) + SpawnTransform = CurrentTransform; + else + SpawnTransform.Accumulate( CurrentTransform ); + + NumAppliedTransform++; + } + + if ( NumAppliedTransform > 0 ) + { + // "Mean" all the accumulated Transform + SpawnTransform.SetScale3D( FVector::OneVector ); + SpawnTransform.NormalizeRotation(); + + if ( NumAppliedTransform > 1 ) + SpawnTransform.SetLocation( SpawnTransform.GetLocation() / (float)NumAppliedTransform ); + } + } + + return SpawnTransform; +} + +// Builds the context menu for the selected tool +TSharedPtr +SHoudiniToolPalette::ConstructHoudiniToolContextMenu() +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked< FAssetRegistryModule >( "AssetRegistry" ); + + FMenuBuilder MenuBuilder( true, NULL ); + + TArray< UObject* > AssetArray; + if ( ActiveTool.IsValid() && !ActiveTool->HoudiniAsset.IsNull() ) + { + // Load the asset + UObject* AssetObj = ActiveTool->HoudiniAsset.LoadSynchronous(); + if( AssetObj ) + AssetArray.Add( AssetObj ); + } + + //MenuBuilder.AddMenuEntry(FGlobalEditorCommonCommands::Get().FindInContentBrowser ); + + FAssetToolsModule & AssetToolsModule = FModuleManager::GetModuleChecked< FAssetToolsModule >( "AssetTools" ); + TSharedPtr< IAssetTypeActions > EngineActions = AssetToolsModule.Get().GetAssetTypeActionsForClass( UHoudiniAsset::StaticClass() ).Pin(); + if ( EngineActions.IsValid() ) + EngineActions->GetActions( AssetArray, MenuBuilder ); + + // Add HoudiniTools actions + MenuBuilder.AddMenuSeparator(); + + // Remove Tool + MenuBuilder.AddMenuEntry( + NSLOCTEXT( "HoudiniToolsTypeActions", "HoudiniTool_Remove", "Remove Tool" ), + NSLOCTEXT( "HoudiniToolsTypeActions", "HoudiniTool_RemoveTooltip", "Remove the selected tool from the list of Houdini Tools." ), + FSlateIcon( FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo" ), + FUIAction( + FExecuteAction::CreateSP(this, &SHoudiniToolPalette::RemoveActiveTool ), + FCanExecuteAction::CreateLambda([&] { return IsActiveHoudiniToolEditable(); } ) + ) + ); + + // Edit Tool + MenuBuilder.AddMenuEntry( + NSLOCTEXT( "HoudiniToolsTypeActions", "HoudiniTool_Edit", "Edit Tool Properties" ), + NSLOCTEXT( "HoudiniToolsTypeActions", "HoudiniTool_EditTooltip", "Edit the selected tool properties." ), + FSlateIcon( FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo" ), + FUIAction( + FExecuteAction::CreateSP(this, &SHoudiniToolPalette::EditActiveHoudiniTool ), + FCanExecuteAction::CreateLambda([&] { return IsActiveHoudiniToolEditable(); } ) + ) + ); + + // Add HoudiniTools actions + MenuBuilder.AddMenuSeparator(); + + // Generate Missing Tool Descriptions + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniToolsTypeActions", "HoudiniTool_GenMissing", "Generate Missing Tool Descriptions"), + NSLOCTEXT("HoudiniToolsTypeActions", "HoudiniTool_GenMissingTooltip", "Generates the tool descriptions .json file for HDA that doesnt have one."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &SHoudiniToolPalette::GenerateMissingJSONFiles ), + FCanExecuteAction::CreateLambda([&] { return true; }) + ) + ); + + return MenuBuilder.MakeWidget(); +} + +void +SHoudiniToolPalette::EditActiveHoudiniTool() +{ + if ( !ActiveTool.IsValid() ) + return; + + if ( !IsActiveHoudiniToolEditable() ) + return; + + // Create a new Tool property object for the property dialog + FString ToolName = ActiveTool->Name.ToString(); + if ( ActiveTool->HoudiniAsset ) + ToolName += TEXT(" (") + ActiveTool->HoudiniAsset->AssetFileName + TEXT(")"); + + UHoudiniToolProperties* NewToolProperty = NewObject< UHoudiniToolProperties >( GetTransientPackage(), FName( *ToolName ) ); + NewToolProperty->AddToRoot(); + + // Set the default values for this asset + NewToolProperty->Name = ActiveTool->Name.ToString(); + NewToolProperty->Type = ActiveTool->Type; + NewToolProperty->ToolTip = ActiveTool->ToolTipText.ToString(); + FName IconResourceName = ActiveTool->Icon->GetResourceName(); + NewToolProperty->IconPath.FilePath = IconResourceName.ToString(); + NewToolProperty->HelpURL = ActiveTool->HelpURL; + NewToolProperty->AssetPath = ActiveTool->SourceAssetPath; + NewToolProperty->SelectionType = ActiveTool->SelectionType; + + TArray ActiveHoudiniTools; + ActiveHoudiniTools.Add( NewToolProperty ); + + // Create a new property editor window + TSharedRef< SWindow > Window = CreateFloatingDetailsView( ActiveHoudiniTools ); + Window->SetOnWindowClosed( FOnWindowClosed::CreateSP( this, &SHoudiniToolPalette::OnEditActiveHoudiniToolWindowClosed, ActiveHoudiniTools ) ); +} + +void +SHoudiniToolPalette::OnEditActiveHoudiniToolWindowClosed( const TSharedRef& InWindow, TArray InObjects ) +{ + // Sanity check, we can only edit one tool at a time! + if ( InObjects.Num() != 1 ) + return; + + TArray< FHoudiniTool > EditedToolArray; + for ( int32 ObjIdx = 0; ObjIdx < InObjects.Num(); ObjIdx++ ) + { + UHoudiniToolProperties* ToolProperties = Cast< UHoudiniToolProperties >( InObjects[ ObjIdx ] ); + if ( !ToolProperties ) + continue; + + FString IconPath = FPaths::ConvertRelativePathToFull( ToolProperties->IconPath.FilePath ); + const FSlateBrush* CustomIconBrush = nullptr; + if ( FPaths::FileExists( IconPath ) ) + { + FName BrushName = *IconPath; + CustomIconBrush = new FSlateDynamicImageBrush( BrushName, FVector2D( 40.f, 40.f ) ); + } + else + { + CustomIconBrush = FHoudiniEngineStyle::Get()->GetBrush( TEXT( "HoudiniEngine.HoudiniEngineLogo40" ) ); + } + + // Create a new Houdini Tool from the modified properties + FHoudiniTool EditedHoudiniTool( + ActiveTool->HoudiniAsset, + FText::FromString( ToolProperties->Name ), + ToolProperties->Type, + ToolProperties->SelectionType, + FText::FromString (ToolProperties->ToolTip ), + CustomIconBrush, + ToolProperties->HelpURL, + false, + ToolProperties->AssetPath, + ActiveTool->ToolDirectory, + ActiveTool->JSONFile); + + EditedToolArray.Add( EditedHoudiniTool ); + + // Remove the tool from Root + ToolProperties->RemoveFromRoot(); + } + + if ( EditedToolArray.Num() != 1 ) + return; + + // Add the new tools to the editor + TArray< TSharedPtr >* EditorTools = FHoudiniEngineEditor::Get().GetHoudiniToolsForWrite(); + if ( !EditorTools ) + return; + + for ( int32 Idx = 0; Idx < EditedToolArray.Num(); Idx++ ) + { + FHoudiniTool EditedTool = EditedToolArray[ Idx ]; + + // Update the editor tool list + int32 FoundIndex = -1; + bool IsDefault = false; + if ( !FHoudiniEngineEditor::Get().FindHoudiniTool( *ActiveTool, FoundIndex, IsDefault ) ) + continue; + + if ( IsDefault ) + continue; + + // Modify the Editor Tool if needed + bool bModified = false; + TSharedPtr< FHoudiniTool > EditorTool = (*EditorTools)[FoundIndex]; + + if (!EditorTool->Name.EqualTo(EditedTool.Name)) + { + EditorTool->Name = EditedTool.Name; + bModified = true; + } + + if (EditorTool->Type != EditedTool.Type) + { + EditorTool->Type = EditedTool.Type; + bModified = true; + } + + if (EditorTool->SelectionType != EditedTool.SelectionType) + { + EditorTool->SelectionType = EditedTool.SelectionType; + bModified = true; + } + + if (!EditorTool->ToolTipText.EqualTo(EditedTool.ToolTipText)) + { + EditorTool->ToolTipText = EditedTool.ToolTipText; + bModified = true; + } + + if (EditorTool->Icon && EditedTool.Icon) + { + if (EditorTool->Icon->GetResourceName().ToString() != EditedTool.Icon->GetResourceName().ToString()) + { + EditorTool->Icon = EditedTool.Icon; + bModified = true; + } + } + + if( EditorTool->HelpURL != EditedTool.HelpURL ) + { + EditorTool->HelpURL = EditedTool.HelpURL; + bModified = true; + } + + if (!FPaths::IsSamePath(EditorTool->SourceAssetPath.FilePath, EditedTool.SourceAssetPath.FilePath)) + { + EditorTool->SourceAssetPath = EditedTool.SourceAssetPath; + bModified = true; + } + + EditorTool->DefaultTool = false; + + // Write the new tool's state to JSON + if (bModified) + FHoudiniEngineEditor::Get().WriteJSONFromHoudiniTool( EditedTool ); + } + + // Call construct to refresh the shelf + SHoudiniToolPalette::FArguments Args; + Construct( Args ); +} + +void +SHoudiniToolPalette::GenerateMissingJSONFiles() +{ + // Get the current list of directories + TArray< FHoudiniToolDirectory > ToolDirectories; + FHoudiniEngineEditor::Get().GetHoudiniToolDirectories(FHoudiniEngineEditor::Get().CurrentHoudiniToolDirIndex, ToolDirectories ); + + // For all selected tool directories + for (int32 n = 0; n < ToolDirectories.Num(); n++) + { + FString ToolDirPath = ToolDirectories[n].Path.Path; + + // List all the json files in the current directory + TArray JSONFiles; + IFileManager::Get().FindFiles(JSONFiles, *ToolDirPath, TEXT(".json")); + + // List all the hda files in the current directory + TArray HDAFiles; + IFileManager::Get().FindFiles(HDAFiles, *ToolDirPath, TEXT(".hda")); + + // For each HDA, try to find a corresponding json file + // If we find one, remove the hda from the array + for ( int32 HDAIndex = HDAFiles.Num() - 1; HDAIndex >= 0; HDAIndex-- ) + { + FString HDABaseName = FPaths::GetBaseFilename( HDAFiles[ HDAIndex ] ); + for (auto CurrentJSON : JSONFiles) + { + FString JSONBaseName = FPaths::GetBaseFilename( CurrentJSON ); + if ( JSONBaseName.Equals( HDABaseName ) ) + HDAFiles.RemoveAt( HDAIndex ); + } + } + + // Generate a default JSON file for each remaining HDA + for ( auto CurrentHDA : HDAFiles ) + { + FString HDABaseName = FPaths::GetBaseFilename( CurrentHDA ); + + FHoudiniTool DefaultTool; + DefaultTool.Name = FText::FromString( HDABaseName ); + DefaultTool.ToolTipText = FText::FromString( FString() ); + DefaultTool.Icon = nullptr; + DefaultTool.HelpURL = FString(); + DefaultTool.Type = EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE; + DefaultTool.DefaultTool = false; + DefaultTool.SelectionType = EHoudiniToolSelectionType::HTOOL_SELECTION_ALL; + + DefaultTool.SourceAssetPath.FilePath = ToolDirPath / CurrentHDA; + DefaultTool.ToolDirectory = ToolDirectories[ n ]; + DefaultTool.JSONFile = HDABaseName + TEXT(".json"); + + FHoudiniEngineEditor::Get().WriteJSONFromHoudiniTool( DefaultTool ); + } + } + + // Trigger a refresh of the list + OnDirectoryChange( CurrentHoudiniToolDir ); +} + +TSharedRef +SHoudiniToolPalette::CreateFloatingDetailsView( const TArray< UObject* >& InObjects ) +{ + TSharedRef NewSlateWindow = SNew(SWindow) + .Title(NSLOCTEXT("PropertyEditor", "WindowTitle", "Houdini Tools Property Editor")) + .ClientSize(FVector2D(400, 550)); + + // If the main frame exists parent the window to it + TSharedPtr< SWindow > ParentWindow; + if ( FModuleManager::Get().IsModuleLoaded("MainFrame") ) + { + IMainFrameModule& MainFrame = FModuleManager::GetModuleChecked("MainFrame"); + ParentWindow = MainFrame.GetParentWindow(); + } + + if ( ParentWindow.IsValid() ) + { + // Parent the window to the main frame + FSlateApplication::Get().AddWindowAsNativeChild( NewSlateWindow, ParentWindow.ToSharedRef() ); + } + else + { + FSlateApplication::Get().AddWindow( NewSlateWindow ); + } + + FDetailsViewArgs Args; + Args.bHideSelectionTip = true; + Args.bLockable = false; + Args.bAllowMultipleTopLevelObjects = true; + Args.ViewIdentifier = TEXT("Houdini Tools Properties"); + Args.NameAreaSettings = FDetailsViewArgs::HideNameArea; + Args.bShowPropertyMatrixButton = false; + Args.bShowOptions = false; + Args.bShowModifiedPropertiesOption = false; + Args.bShowActorLabel = false; + + FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor"); + TSharedRef DetailView = PropertyEditorModule.CreateDetailView( Args ); + DetailView->SetObjects( InObjects ); + + NewSlateWindow->SetContent( + SNew( SBorder ) + .BorderImage( FEditorStyle::GetBrush( TEXT("PropertyWindow.WindowBorder") ) ) + [ + DetailView + ] + ); + + return NewSlateWindow; +} + +FReply +SHoudiniToolPalette::OnKeyDown( const FGeometry& InGeometry, const FKeyEvent& InKeyEvent ) +{ + if ( InKeyEvent.GetKey() == EKeys::Delete ) + { + RemoveActiveTool(); + return FReply::Handled(); + } + else if ( InKeyEvent.GetKey() == EKeys::Enter ) + { + EditActiveHoudiniTool(); + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::F5 ) + { + OnDirectoryChange( CurrentHoudiniToolDir ); + return FReply::Handled(); + } + + return FReply::Unhandled(); +} + +void +SHoudiniToolPalette::RemoveActiveTool() +{ + if ( !ActiveTool.IsValid() ) + return; + + // Remove the tool from the editor list + TArray< TSharedPtr >* EditorTools = FHoudiniEngineEditor::Get().GetHoudiniToolsForWrite(); + if ( !EditorTools ) + return; + + int32 EditorIndex = -1; + bool IsDefaultTool = false; + if ( !FHoudiniEngineEditor::Get().FindHoudiniTool( *ActiveTool, EditorIndex, IsDefaultTool ) ) + return; + + if ( IsDefaultTool ) + return; + + EditorTools->RemoveAt( EditorIndex ); + + // Delete the tool's JSON file to remove it + FString ToolJSONFilePath = ActiveTool->ToolDirectory.Path.Path / ActiveTool->JSONFile; + + if ( FPaths::FileExists(ToolJSONFilePath) ) + { + FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*ToolJSONFilePath); + } + + // Call construct to refresh the shelf + SHoudiniToolPalette::FArguments Args; + Construct( Args ); +} + +bool +SHoudiniToolPalette::IsActiveHoudiniToolEditable() +{ + int32 FoundIndex = -1; + bool IsDefault = false; + + if ( !ActiveTool.IsValid() ) + return false; + + if ( !FHoudiniEngineEditor::Get().FindHoudiniTool( *ActiveTool, FoundIndex, IsDefault ) ) + return false; + + return !IsDefault; +} + +FReply +SHoudiniToolPalette::OnEditToolDirectories() +{ + // Append all the custom tools directory from the runtime settings + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!HoudiniRuntimeSettings) + return FReply::Handled(); + + TArray NewCustomHoudiniToolDirs; + + FString CustomToolDirs = TEXT("CustomToolDir"); + UHoudiniToolDirectoryProperties* NewToolDirProperty = NewObject< UHoudiniToolDirectoryProperties >(GetTransientPackage(), FName(*CustomToolDirs)); + NewToolDirProperty->CustomHoudiniToolsDirectories = HoudiniRuntimeSettings->CustomHoudiniToolsLocation; + NewCustomHoudiniToolDirs.Add(NewToolDirProperty); + + TSharedRef< SWindow > Window = CreateFloatingDetailsView(NewCustomHoudiniToolDirs); + Window->SetOnWindowClosed(FOnWindowClosed::CreateSP(this, &SHoudiniToolPalette::OnEditToolDirectoriesWindowClosed, NewCustomHoudiniToolDirs)); + + return FReply::Handled(); +} + + +void +SHoudiniToolPalette::OnEditToolDirectoriesWindowClosed(const TSharedRef& InWindow, TArray InObjects) +{ + TArray EditedToolDirectories; + for (int32 ObjIdx = 0; ObjIdx < InObjects.Num(); ObjIdx++) + { + UHoudiniToolDirectoryProperties* ToolDirProperties = Cast< UHoudiniToolDirectoryProperties >(InObjects[ObjIdx]); + if (!ToolDirProperties) + continue; + + for ( auto CurrentToolDir : ToolDirProperties->CustomHoudiniToolsDirectories ) + { + EditedToolDirectories.AddUnique( CurrentToolDir ); + } + + // Remove the tool directory from Root + ToolDirProperties->RemoveFromRoot(); + } + + // Get the runtime settings to add the new custom tools there + UHoudiniRuntimeSettings * HoudiniRuntimeSettings = const_cast(GetDefault< UHoudiniRuntimeSettings >()); + if (!HoudiniRuntimeSettings) + return; + + // Check if there's been any modification + bool bModified = false; + if ( EditedToolDirectories.Num() != HoudiniRuntimeSettings->CustomHoudiniToolsLocation.Num() ) + { + bModified = true; + } + else + { + // Same number of directory, look for a value change + for ( int32 n = 0; n < EditedToolDirectories.Num(); n++ ) + { + if (EditedToolDirectories[n] != HoudiniRuntimeSettings->CustomHoudiniToolsLocation[n]) + { + bModified = true; + break; + } + } + } + + if ( !bModified ) + return; + + // Replace the Tool dir in the runtime settings with the edited list + HoudiniRuntimeSettings->CustomHoudiniToolsLocation.Empty(); + + for ( auto EditedToolDir : EditedToolDirectories ) + HoudiniRuntimeSettings->CustomHoudiniToolsLocation.AddUnique( EditedToolDir ); + + // Save the Settings file to keep the new tools upon restart + HoudiniRuntimeSettings->SaveConfig(); + + // Call construct to refresh the shelf + SHoudiniToolPalette::FArguments Args; + Construct(Args); +} + +bool +SHoudiniToolPalette::GetCurrentDirectoryIndex(int32& SelectedIndex ) const +{ + if ( CurrentHoudiniToolDir.IsEmpty() ) + return false; + + for (int32 n = 0; n < HoudiniToolDirArray.Num(); n++) + { + if (!HoudiniToolDirArray[n].IsValid()) + continue; + + if (!HoudiniToolDirArray[n]->Equals(CurrentHoudiniToolDir)) + continue; + + SelectedIndex = (n - 1); + /* + FHoudiniToolDirectory EditorToolDir; + FHoudiniEngineEditor::Get().GetHoudiniToolDirectories(FoundIndex, EditorToolDir); + + if ( EditorToolDir.Name.Equals(CurrentHoudiniToolDir) ) + return true; + */ + + return true; + } + + return false; +} + +void +SHoudiniToolPalette::OnDirectoryChange(const FString& NewDir) +{ + if ( NewDir.IsEmpty() ) + return; + + CurrentHoudiniToolDir = NewDir; + + int32 DirIndex = 0; + if ( GetCurrentDirectoryIndex( DirIndex ) ) + { + + FHoudiniEngineEditor::Get().UpdateHoudiniToolList( DirIndex ); + HoudiniToolListView->RequestListRefresh(); + } +} + +void +SHoudiniToolPalette::UpdateHoudiniToolDirectories() +{ + int32 ChoiceIndex = FHoudiniEngineEditor::Get().CurrentHoudiniToolDirIndex; + + // Refresh the Editor's Houdini Tool list + FHoudiniEngineEditor::Get().UpdateHoudiniToolList( ChoiceIndex ); + + // Add the directory choice lists + HoudiniToolDirArray.Empty(); + + TArray ToolDirArray; + FHoudiniEngineEditor::Get().GetAllHoudiniToolDirectories( ToolDirArray ); + + { + FString * ChoiceLabel = new FString(TEXT("All")); + HoudiniToolDirArray.Add(TSharedPtr< FString >(ChoiceLabel)); + + if (ChoiceIndex < 0 ) + CurrentHoudiniToolDir = *ChoiceLabel; + } + + { + FString * ChoiceLabel = new FString(TEXT("Default")); + HoudiniToolDirArray.Add(TSharedPtr< FString >(ChoiceLabel)); + + if ( ChoiceIndex == 0 ) + CurrentHoudiniToolDir = *ChoiceLabel; + } + + for (int32 n = 1; n < ToolDirArray.Num(); n++) + { + FString * ChoiceLabel = new FString( ToolDirArray[n].Name ); + HoudiniToolDirArray.Add(TSharedPtr< FString >(ChoiceLabel)); + + if ( ChoiceIndex == n ) + CurrentHoudiniToolDir = *ChoiceLabel; + } +} + +FText +SHoudiniToolPalette::OnGetSelectedDirText() const +{ + return FText::FromString(CurrentHoudiniToolDir); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/SHoudiniToolPalette.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/SHoudiniToolPalette.h new file mode 100644 index 00000000..4e17b011 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/SHoudiniToolPalette.h @@ -0,0 +1,183 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "HoudiniEngineEditor.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Input/Reply.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/Views/SListView.h" +#include "Misc/NotifyHook.h" +#include "SHoudiniToolPalette.generated.h" + +class IDetailsView; +class ITableRow; +class STableViewBase; +struct FSlateBrush; +enum class ECheckBoxState : uint8; + +/** The list view mode of the asset view */ +class SHoudiniToolListView : public SListView< TSharedPtr > +{ + public: + virtual bool SupportsKeyboardFocus() const override { return true; } + virtual FReply OnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override + { + return FReply::Unhandled(); + } +}; + +// Class describing the various properties for a Houdini Tool +// adding a UClass was necessary to use the PropertyEditor window +UCLASS( EditInlineNew ) +class UHoudiniToolProperties : public UObject +{ + GENERATED_UCLASS_BODY() + + public: + + /** Name of the tool */ + UPROPERTY( Category = Tool, EditAnywhere ) + FString Name; + + /** Type of the tool */ + UPROPERTY( Category = Tool, EditAnywhere ) + EHoudiniToolType Type; + + /** Selection Type of the tool */ + UPROPERTY( Category = Tool, EditAnywhere ) + EHoudiniToolSelectionType SelectionType; + + /** Tooltip shown on mouse hover */ + UPROPERTY( Category = Tool, EditAnywhere ) + FString ToolTip; + + /** Path to a custom icon */ + UPROPERTY( Category = Tool, EditAnywhere, meta = (FilePathFilter = "png") ) + FFilePath IconPath; + + /** Houdini Asset path **/ + UPROPERTY(Category = Tool, EditAnywhere, meta = (FilePathFilter = "hda")) + FFilePath AssetPath; + + /** Clicking on help icon will bring up this URL */ + UPROPERTY( Category = Tool, EditAnywhere ) + FString HelpURL; +}; + +// Class describing a Houdini Tool directory +// adding a UClass was necessary to use the PropertyEditor window +UCLASS(EditInlineNew) +class UHoudiniToolDirectoryProperties : public UObject +{ + GENERATED_UCLASS_BODY() + + public: + + /** Custom Houdini Tool Directories **/ + UPROPERTY(EditAnywhere, Category = CustomHoudiniTools) + TArray CustomHoudiniToolsDirectories; +}; + +class SHoudiniToolPalette : public SCompoundWidget, public FNotifyHook +{ +public: + SLATE_BEGIN_ARGS( SHoudiniToolPalette ) {} + SLATE_END_ARGS(); + + void Construct( const FArguments& InArgs ); + + static FTransform GetDefaulToolSpawnTransform(); + static FTransform GetMeanWorldSelectionTransform(); + + /** Instantiate the selected HoudiniTool and assigns input depending on the current selection and tool type **/ + static void InstantiateHoudiniTool( FHoudiniTool* HoudiniTool ); + + /** Handler for Key presses**/ + virtual FReply OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ); + + bool IsActiveHoudiniToolEditable(); + + FText OnGetSelectedDirText() const; + +protected: + + /** Remove the current tool from the tool list **/ + void RemoveActiveTool(); + + /** Shows a property window for editing the current Tool directory **/ + FReply OnEditToolDirectories(); + + /** Handler for closing the EditToolDirectory window **/ + void OnEditToolDirectoriesWindowClosed(const TSharedRef& InWindow, TArray InObjects); + + /** Generates default JSON files for HDA in the current Tool directory that doesnt have one **/ + void GenerateMissingJSONFiles(); + +private: + + /** Make a widget for the list view display */ + TSharedRef MakeListViewWidget( TSharedPtr BspBuilder, const TSharedRef& OwnerTable ); + + /** Returns the index of the currently selected directory in the Editor ToolDirectoryArray **/ + bool GetCurrentDirectoryIndex(int32& SelectedIndex) const; + + /** Delegate for when the list view selection changes */ + void OnSelectionChanged( TSharedPtr BspBuilder, ESelectInfo::Type SelectionType ); + + /** Delegate for when the direcory combo selection changes **/ + void OnDirectoryChange(const FString& NewChoice); + + /** Begin dragging a list widget */ + FReply OnDraggingListViewWidget( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ); + + /** Handler for double clicking on a Houdini tool **/ + void OnDoubleClickedListViewWidget( TSharedPtr ListItem ); + + /** Handler for the right click context menu on a Houdini tool **/ + TSharedPtr< SWidget > ConstructHoudiniToolContextMenu(); + + /** Shows a Property Window for editing the properties of new HoudiniTools**/ + void EditActiveHoudiniTool(); + + /** Handler for closing the AddHoudiniTools window**/ + void OnEditActiveHoudiniToolWindowClosed(const TSharedRef& InWindow, TArray InObjects); + + TSharedRef CreateFloatingDetailsView( const TArray< UObject* >& InObjects ); + + void UpdateHoudiniToolDirectories(); + + //void RenameToolFolder(const FString& SourcePath, const FString& NewName); + +private: + TSharedPtr ActiveTool; + + TArray< TSharedPtr < FString > > HoudiniToolDirArray; + + FString CurrentHoudiniToolDir; + + /** Holds the tools list view. */ + TSharedPtr HoudiniToolListView; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp new file mode 100644 index 00000000..d33d9a4b --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp @@ -0,0 +1,342 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "SNewFilePathPicker.h" + +#include "HoudiniApi.h" +#include "DesktopPlatformModule.h" +#include "Widgets/SBoxPanel.h" +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SButton.h" + + +#define LOCTEXT_NAMESPACE "SNewFilePathPicker" + + +/* SNewFilePathPicker interface + *****************************************************************************/ + +void SNewFilePathPicker::Construct( const FArguments& InArgs ) +{ + BrowseDirectory = InArgs._BrowseDirectory; + BrowseTitle = InArgs._BrowseTitle; + FilePath = InArgs._FilePath; + FileTypeFilter = InArgs._FileTypeFilter; + OnPathPicked = InArgs._OnPathPicked; + IsNewFile = InArgs._IsNewFile; + + ChildSlot + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + // file path text box + SAssignNew(TextBox, SEditableTextBox) + .Text(this, &SNewFilePathPicker::HandleTextBoxText) + .Font(InArgs._Font) + .SelectAllTextWhenFocused(true) + .ClearKeyboardFocusOnCommit(false) + .OnTextCommitted(this, &SNewFilePathPicker::HandleTextBoxTextCommitted) + .SelectAllTextOnCommit(false) + .IsReadOnly(InArgs._IsReadOnly) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(4.0f, 0.0f, 0.0f, 0.0f) + .VAlign(VAlign_Center) + [ + // browse button + SNew(SButton) + .ButtonStyle(InArgs._BrowseButtonStyle) + .ToolTipText(InArgs._BrowseButtonToolTip) + .OnClicked(this, &SNewFilePathPicker::HandleBrowseButtonClicked) + .ContentPadding(2.0f) + .ForegroundColor(FSlateColor::UseForeground()) + .IsFocusable(false) + [ + SNew(SImage) + .Image(InArgs._BrowseButtonImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ] + ]; +} + + +/* SNewFilePathPicker callbacks + *****************************************************************************/ +#if PLATFORM_WINDOWS + +#include "Windows/WindowsHWrapper.h" +#include "Windows/COMPointer.h" +//#include "Misc/Paths.h" +//#include "Misc/Guid.h" +#include "HAL/FileManager.h" +#include "Windows/AllowWindowsPlatformTypes.h" +#include +//#include +#include +//#include +//#include +//#include +//#include +#include "Windows/HideWindowsPlatformTypes.h" +//#pragma comment( lib, "version.lib" ) + +bool FileDialogShared( bool bSave, const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames, int32& OutFilterIndex ) +{ + FScopedSystemModalMode SystemModalScope; + + bool bSuccess = false; + + TComPtr FileDialog; + if ( SUCCEEDED( ::CoCreateInstance( bSave ? CLSID_FileSaveDialog : CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, bSave ? IID_IFileSaveDialog : IID_IFileOpenDialog, IID_PPV_ARGS_Helper( &FileDialog ) ) ) ) + { + if ( bSave ) + { + // Set the default "filename" + if ( !DefaultFile.IsEmpty() ) + { + FileDialog->SetFileName( *FPaths::GetCleanFilename( DefaultFile ) ); + } + DWORD dwFlags = 0; + FileDialog->GetOptions( &dwFlags ); + FileDialog->SetOptions( dwFlags & ~FOS_OVERWRITEPROMPT ); + } + else + { + // Set this up as a multi-select picker + if ( Flags & EFileDialogFlags::Multiple ) + { + DWORD dwFlags = 0; + FileDialog->GetOptions( &dwFlags ); + FileDialog->SetOptions( dwFlags | FOS_ALLOWMULTISELECT ); + } + } + + // Set up common settings + FileDialog->SetTitle( *DialogTitle ); + if ( !DefaultPath.IsEmpty() ) + { + // SHCreateItemFromParsingName requires the given path be absolute and use \ rather than / as our normalized paths do + FString DefaultWindowsPath = FPaths::ConvertRelativePathToFull( DefaultPath ); + DefaultWindowsPath.ReplaceInline( TEXT( "/" ), TEXT( "\\" ), ESearchCase::CaseSensitive ); + + TComPtr DefaultPathItem; + if ( SUCCEEDED( ::SHCreateItemFromParsingName( *DefaultWindowsPath, nullptr, IID_PPV_ARGS( &DefaultPathItem ) ) ) ) + { + FileDialog->SetFolder( DefaultPathItem ); + } + } + + // Set-up the file type filters + TArray UnformattedExtensions; + TArray FileDialogFilters; + { + // Split the given filter string (formatted as "Pair1String1|Pair1String2|Pair2String1|Pair2String2") into the Windows specific filter struct + FileTypes.ParseIntoArray( UnformattedExtensions, TEXT( "|" ), true ); + + if ( UnformattedExtensions.Num() % 2 == 0 ) + { + FileDialogFilters.Reserve( UnformattedExtensions.Num() / 2 ); + for ( int32 ExtensionIndex = 0; ExtensionIndex < UnformattedExtensions.Num();) + { + COMDLG_FILTERSPEC& NewFilterSpec = FileDialogFilters[FileDialogFilters.AddDefaulted()]; + NewFilterSpec.pszName = *UnformattedExtensions[ExtensionIndex++]; + NewFilterSpec.pszSpec = *UnformattedExtensions[ExtensionIndex++]; + } + } + } + FileDialog->SetFileTypes( FileDialogFilters.Num(), FileDialogFilters.GetData() ); + + // Show the picker + if ( SUCCEEDED( FileDialog->Show( (HWND)ParentWindowHandle ) ) ) + { + OutFilterIndex = 0; + if ( SUCCEEDED( FileDialog->GetFileTypeIndex( (UINT*)&OutFilterIndex ) ) ) + { + OutFilterIndex -= 1; // GetFileTypeIndex returns a 1-based index + } + + auto AddOutFilename = [&OutFilenames]( const FString& InFilename ) + { + FString& OutFilename = OutFilenames[OutFilenames.Add( InFilename )]; + OutFilename = IFileManager::Get().ConvertToRelativePath( *OutFilename ); + FPaths::NormalizeFilename( OutFilename ); + }; + + if ( bSave ) + { + TComPtr Result; + if ( SUCCEEDED( FileDialog->GetResult( &Result ) ) ) + { + PWSTR pFilePath = nullptr; + if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) + { + bSuccess = true; + + // Apply the selected extension if the given filename doesn't already have one + FString SaveFilePath = pFilePath; + if ( FileDialogFilters.IsValidIndex( OutFilterIndex ) ) + { + // Build a "clean" version of the selected extension (without the wildcard) + FString CleanExtension = FileDialogFilters[OutFilterIndex].pszSpec; + if ( CleanExtension == TEXT( "*.*" ) ) + { + CleanExtension.Reset(); + } + else + { + const int32 WildCardIndex = CleanExtension.Find( TEXT( "*" ) ); + if ( WildCardIndex != INDEX_NONE ) + { + CleanExtension = CleanExtension.RightChop( WildCardIndex + 1 ); + } + } + + // We need to split these before testing the extension to avoid anything within the path being treated as a file extension + FString SaveFileName = FPaths::GetCleanFilename( SaveFilePath ); + SaveFilePath = FPaths::GetPath( SaveFilePath ); + + // Apply the extension if the file name doesn't already have one + if ( FPaths::GetExtension( SaveFileName ).IsEmpty() && !CleanExtension.IsEmpty() ) + { + SaveFileName = FPaths::SetExtension( SaveFileName, CleanExtension ); + } + + SaveFilePath /= SaveFileName; + } + AddOutFilename( SaveFilePath ); + + ::CoTaskMemFree( pFilePath ); + } + } + } + else + { + IFileOpenDialog* FileOpenDialog = static_cast( FileDialog.Get() ); + + TComPtr Results; + if ( SUCCEEDED( FileOpenDialog->GetResults( &Results ) ) ) + { + DWORD NumResults = 0; + Results->GetCount( &NumResults ); + for ( DWORD ResultIndex = 0; ResultIndex < NumResults; ++ResultIndex ) + { + TComPtr Result; + if ( SUCCEEDED( Results->GetItemAt( ResultIndex, &Result ) ) ) + { + PWSTR pFilePath = nullptr; + if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) + { + bSuccess = true; + AddOutFilename( pFilePath ); + ::CoTaskMemFree( pFilePath ); + } + } + } + } + } + } + } + + return bSuccess; +} + +bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) +{ + int32 DummyFilterIndex = 0; + return FileDialogShared( true, ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames, DummyFilterIndex ); +} + +#else + +bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + return DesktopPlatform->SaveFileDialog( ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames ); +} +#endif + +FReply SNewFilePathPicker::HandleBrowseButtonClicked() +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + + if (DesktopPlatform == nullptr) + { + return FReply::Handled(); + } + + const FString DefaultPath = BrowseDirectory.IsSet() + ? BrowseDirectory.Get() + : FPaths::GetPath(FilePath.Get()); + + // show the file browse dialog + TSharedPtr ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + void* ParentWindowHandle = (ParentWindow.IsValid() && ParentWindow->GetNativeWindow().IsValid()) + ? ParentWindow->GetNativeWindow()->GetOSWindowHandle() + : nullptr; + + TArray OutFiles; + + // CG: Use SaveFileDialog instead of OpenFileDialog + if ( IsNewFile.Get() ) + { + if ( SaveFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) + { + OnPathPicked.ExecuteIfBound( OutFiles[0] ); + } + } + else + { + if ( DesktopPlatform->OpenFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) + { + OnPathPicked.ExecuteIfBound( OutFiles[0] ); + } + } + + return FReply::Handled(); +} + + +FText SNewFilePathPicker::HandleTextBoxText() const +{ + return FText::FromString(FilePath.Get()); +} + + +void SNewFilePathPicker::HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type CommitInfo ) +{ + OnPathPicked.ExecuteIfBound(NewText.ToString()); +} + + + + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h new file mode 100644 index 00000000..c4f1a42e --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h @@ -0,0 +1,141 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +// This is the same as SFilePathPicker but it uses SaveFileDialog instead of OpenFileDialog +// to allow browsing to a new path + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/Attribute.h" +#include "Fonts/SlateFontInfo.h" +#include "Input/Reply.h" +#include "Styling/SlateWidgetStyleAsset.h" +#include "Styling/ISlateStyle.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Styling/SlateTypes.h" + +class SEditableTextBox; + +/** + * Declares a delegate that is executed when a file was picked in the SFilePathPicker widget. + * + * The first parameter will contain the path to the picked file. + */ +DECLARE_DELEGATE_OneParam(FOnPathPicked, const FString& /*PickedPath*/); + + +/** + * Implements an editable text box with a browse button. + */ +class SNewFilePathPicker + : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SNewFilePathPicker) + : _BrowseButtonToolTip(NSLOCTEXT("SNewFilePathPicker", "BrowseButtonToolTip", "Choose a file from this computer")) + , _FileTypeFilter(TEXT("All files (*.*)|*.*")) + , _Font() + , _IsReadOnly(false) + , _IsNewFile(true) + { } + + /** Browse button image resource. */ + SLATE_ATTRIBUTE(const FSlateBrush*, BrowseButtonImage) + + /** Browse button visual style. */ + SLATE_STYLE_ARGUMENT(FButtonStyle, BrowseButtonStyle) + + /** Browse button tool tip text. */ + SLATE_ATTRIBUTE(FText, BrowseButtonToolTip) + + /** The directory to browse by default */ + SLATE_ATTRIBUTE(FString, BrowseDirectory) + + /** Title for the browse dialog window. */ + SLATE_ATTRIBUTE(FText, BrowseTitle) + + /** The currently selected file path. */ + SLATE_ATTRIBUTE(FString, FilePath) + + /** File type filter string. */ + SLATE_ATTRIBUTE(FString, FileTypeFilter) + + /** Font color and opacity of the path text box. */ + SLATE_ATTRIBUTE(FSlateFontInfo, Font) + + /** Whether the path text box can be modified by the user. */ + SLATE_ATTRIBUTE(bool, IsReadOnly) + + /** Whether to use the new-file dialog instead of open-file */ + SLATE_ATTRIBUTE(bool, IsNewFile) + + /** Called when a file path has been picked. */ + SLATE_EVENT(FOnPathPicked, OnPathPicked) + + SLATE_END_ARGS() + + /** + * Constructs a new widget. + * + * @param InArgs The construction arguments. + */ + void Construct( const FArguments& InArgs ); + +private: + + /** Callback for clicking the browse button. */ + FReply HandleBrowseButtonClicked( ); + + /** Callback for getting the text in the path text box. */ + FText HandleTextBoxText( ) const; + + /** Callback for committing the text in the path text box. */ + void HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type /*CommitInfo*/ ); + +private: + + /** Holds the directory path to browse by default. */ + TAttribute BrowseDirectory; + + /** Holds the title for the browse dialog window. */ + TAttribute BrowseTitle; + + /** Holds the currently selected file path. */ + TAttribute FilePath; + + /** Holds the file type filter string. */ + TAttribute FileTypeFilter; + + /** Holds the editable text box. */ + TSharedPtr TextBox; + + TAttribute IsNewFile; + +private: + + /** Holds a delegate that is executed when a file was picked. */ + FOnPathPicked OnPathPicked; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h new file mode 100644 index 00000000..e30b2d86 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#pragma once +#include "Modules/ModuleInterface.h" +#include "Styling/ISlateStyle.h" + +class IHoudiniEngineEditor : public IModuleInterface +{ +public: + /** Register and unregister component visualizers used by this module. **/ + virtual void RegisterComponentVisualizers() {} + virtual void UnregisterComponentVisualizers() {} + + /** Register and unregister detail presenters used by this module. **/ + virtual void RegisterDetails() {} + virtual void UnregisterDetails() {} + + /** Register and unregister asset type actions. **/ + virtual void RegisterAssetTypeActions() {} + virtual void UnregisterAssetTypeActions() {} + + /** Create and register / unregister asset brokers. **/ + virtual void RegisterAssetBrokers() {} + virtual void UnregisterAssetBrokers() {} + + /** Create and register actor factories. **/ + virtual void RegisterActorFactories() {} + + /** Extend menu. **/ + virtual void ExtendMenu() {} + + /** Register and unregister thumbnails. **/ + virtual void RegisterThumbnails() {} + virtual void UnregisterThumbnails() {} + + /** Register and unregister for undo/redo notifications. **/ + virtual void RegisterForUndo() {} + virtual void UnregisterForUndo() {} + + /** Create custom modes **/ + virtual void RegisterModes() {} + virtual void UnregisterModes() {} + + /** Create custom placement extensions */ + virtual void RegisterPlacementModeExtensions() {} + virtual void UnregisterPlacementModeExtensions() {} +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs new file mode 100644 index 00000000..074eebc6 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs @@ -0,0 +1,314 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Produced by: + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + * COMMENTS: + * This file is generated. Do not modify directly. + */ + +/* + + Houdini Version: 18.5.351 + Houdini Engine Version: 3.5.0 + Unreal Version: 4.25.0 + +*/ + +using UnrealBuildTool; +using System; +using System.IO; + +public class HoudiniEngineRuntime : ModuleRules +{ + private string GetHFSPath() + { + string HoudiniVersion = "18.5.351"; + bool bIsRelease = true; + string HFSPath = "C:/cygwin/home/prisms/builder-new/Nightly18.5.351CMake/dev/hfs"; + string RegistryPath = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Side Effects Software"; + string Log; + + if ( !bIsRelease ) + { + // Only use the preset build folder + System.Console.WriteLine("Using stamped HFSPath:" + HFSPath); + return HFSPath; + } + + // Look for the Houdini install folder for this platform + PlatformID buildPlatformId = Environment.OSVersion.Platform; + if (buildPlatformId == PlatformID.Win32NT) + { + // Look for the HEngine install path in the registry + string HEngineRegistry = RegistryPath + string.Format(@"\Houdini Engine {0}", HoudiniVersion); + string HPath = Microsoft.Win32.Registry.GetValue(HEngineRegistry, "InstallPath", null) as string; + if ( HPath != null ) + { + Log = string.Format("Houdini Engine : Looking for Houdini Engine {0} in {1}", HoudiniVersion, HPath ); + System.Console.WriteLine( Log ); + if ( Directory.Exists( HPath ) ) + return HPath; + } + + // If we couldn't find the Houdini Engine registry path, try the default one + string DefaultHPath = "C:/Program Files/Side Effects Software/Houdini Engine " + HoudiniVersion; + if ( DefaultHPath != HPath ) + { + Log = string.Format("Houdini Engine : Looking for Houdini Engine {0} in {1}", HoudiniVersion, DefaultHPath ); + System.Console.WriteLine( Log ); + if ( Directory.Exists( DefaultHPath ) ) + return DefaultHPath; + } + + // Look for the Houdini registry install path for the version the plug-in was compiled for + string HoudiniRegistry = RegistryPath + string.Format(@"\Houdini {0}", HoudiniVersion); + HPath = Microsoft.Win32.Registry.GetValue(HoudiniRegistry, "InstallPath", null) as string; + if ( HPath != null ) + { + Log = string.Format("Houdini Engine : Looking for Houdini {0} in {1}", HoudiniVersion, HPath ); + System.Console.WriteLine( Log ); + if ( Directory.Exists( HPath ) ) + return HPath; + } + + // If we couldn't find the Houdini registry path, try the default one + DefaultHPath = "C:/Program Files/Side Effects Software/Houdini " + HoudiniVersion; + if ( DefaultHPath != HPath ) + { + Log = string.Format("Houdini Engine : Looking for Houdini {0} in {1}", HoudiniVersion, DefaultHPath ); + System.Console.WriteLine( Log ); + if ( Directory.Exists( DefaultHPath ) ) + return DefaultHPath; + } + + // See if the preset build HFS exists + if ( Directory.Exists( HFSPath ) ) + return HFSPath; + + Log = string.Format("Houdini Engine : Failed to find Houdini {0}, will attempt to build using the latest installed version", HoudiniVersion ); + System.Console.WriteLine( Log ); + + // We couldn't find the exact version the plug-in was built for, we can still try with the active version in the registry + string ActiveHEngine = Microsoft.Win32.Registry.GetValue(RegistryPath, "ActiveEngineVersion", null) as string; + if ( ActiveHEngine != null ) + { + // See if the latest active HEngine version has the proper major/minor version + if ( ActiveHEngine.Substring(0,4) == HoudiniVersion.Substring(0,4) ) + { + Log = string.Format("Houdini Engine : Found Active Houdini Engine version: {0}", ActiveHEngine ); + System.Console.WriteLine( Log ); + + // Active version contain the patch version that we need to strip off + //string[] ActiveVersion = ActiveHEngine.Split("."); + + HEngineRegistry = RegistryPath + string.Format(@"\Houdini Engine {0}", ActiveHEngine); + HPath = Microsoft.Win32.Registry.GetValue(HEngineRegistry, "InstallPath", null) as string; + if ( HPath != null ) + { + Log = string.Format("Houdini Engine : Looking for Houdini Engine {0} in {1}", ActiveHEngine, HPath ); + System.Console.WriteLine( Log ); + if ( Directory.Exists( HPath ) ) + return HPath; + } + } + } + + // Active HEngine version didn't match, so try with the active Houdini version + string ActiveHoudini = Microsoft.Win32.Registry.GetValue(RegistryPath, "ActiveVersion", null) as string; + if ( ActiveHoudini != null ) + { + // See if the latest active Houdini version has the proper major/minor version + if ( ActiveHoudini.Substring(0,4) == HoudiniVersion.Substring(0,4) ) + { + Log = string.Format("Houdini Engine : Found Active Houdini version: {0}", ActiveHoudini ); + System.Console.WriteLine( Log ); + + HoudiniRegistry = RegistryPath + string.Format(@"\Houdini {0}", ActiveHoudini); + HPath = Microsoft.Win32.Registry.GetValue(HoudiniRegistry, "InstallPath", null) as string; + if ( HPath != null ) + { + Log = string.Format("Houdini Engine : Looking for Houdini {0} in {1}", ActiveHoudini, HPath ); + System.Console.WriteLine( Log ); + + if ( Directory.Exists( HPath ) ) + return HPath; + } + } + } + } + else if (buildPlatformId == PlatformID.MacOSX || + (buildPlatformId == PlatformID.Unix && File.Exists("/System/Library/CoreServices/SystemVersion.plist"))) + { + // Check for Houdini installation. + string HPath = "/Applications/Houdini/Houdini" + HoudiniVersion + "/Frameworks/Houdini.framework/Versions/Current/Resources"; + if ( Directory.Exists( HPath ) ) + return HPath; + + HPath = "/Users/Shared/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Resources"; + if (Directory.Exists(HPath)) + return HPath; + + if ( Directory.Exists( HFSPath ) ) + return HFSPath; + } + else if ( buildPlatformId == PlatformID.Unix ) + { + HFSPath = System.Environment.GetEnvironmentVariable("HFS"); + if ( Directory.Exists( HFSPath ) ) + { + System.Console.WriteLine("Unix using $HFS: " + HFSPath); + return HFSPath; + } + } + else + { + System.Console.WriteLine(string.Format("Building on an unknown environment!")); + } + + string Err = string.Format("Houdini Engine : Please install Houdini or Houdini Engine {0}", HoudiniVersion); + System.Console.WriteLine(Err); + + return ""; + } + + public HoudiniEngineRuntime( ReadOnlyTargetRules Target ) : base( Target ) + { + bPrecompile = true; + PCHUsage = PCHUsageMode.NoSharedPCHs; + PrivatePCHHeaderFile = "Private/HoudiniEngineRuntimePrivatePCH.h"; + + // Check if we are compiling for unsupported platforms. + if ( Target.Platform != UnrealTargetPlatform.Win64 && + Target.Platform != UnrealTargetPlatform.Mac && + Target.Platform != UnrealTargetPlatform.Linux && + Target.Platform != UnrealTargetPlatform.Switch ) + { + System.Console.WriteLine( string.Format( "Houdini Engine: Compiling for untested target platform. Please let us know how it goes!" ) ); + } + + // Find HFS + string HFSPath = GetHFSPath(); + HFSPath = HFSPath.Replace("\\", "/"); + + if( HFSPath != "" ) + { + string Log = string.Format("Houdini Engine: Found Houdini in {0}", HFSPath ); + System.Console.WriteLine( Log ); + + PlatformID buildPlatformId = Environment.OSVersion.Platform; + if (buildPlatformId == PlatformID.Win32NT) + { + PublicDefinitions.Add("HOUDINI_ENGINE_HFS_PATH_DEFINE=" + HFSPath); + } + } + + // Find the HAPI include directory + string HAPIIncludePath = HFSPath + "/toolkit/include/HAPI"; + if (!Directory.Exists(HAPIIncludePath)) + { + // Add the custom include path as well in case the toolkit path doesn't exist yet. + HAPIIncludePath = HFSPath + "/custom/houdini/include/HAPI"; + + if (!Directory.Exists(HAPIIncludePath)) + { + System.Console.WriteLine(string.Format("Couldn't find the HAPI include folder!")); + HAPIIncludePath = ""; + } + } + + if (HAPIIncludePath != "") + PublicIncludePaths.Add(HAPIIncludePath); + + PublicIncludePaths.AddRange( + new string[] { + Path.Combine(ModuleDirectory, "Public/HAPI"), + Path.Combine(ModuleDirectory, "Public") + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + "HoudiniEngineRuntime/Private" + } + ); + + // Add common dependencies. + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "RenderCore", + "InputCore", + "RHI", + "Foliage", + "Landscape" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Landscape" + } + ); + + if (Target.bBuildEditor == true) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "AppFramework", + "AssetTools", + "EditorStyle", + "EditorWidgets", + "LevelEditor", + "MainFrame", + "MeshPaint", + "Projects", + "PropertyEditor", + "RawMesh", + "Settings", + "Slate", + "SlateCore", + "TargetPlatform", + "UnrealEd", + "ApplicationCore", + "LandscapeEditor" + } + ); + } + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniApi.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniApi.cpp new file mode 100644 index 00000000..be5a9301 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniApi.cpp @@ -0,0 +1,3411 @@ +/* + * Copyright (c) <2019> Side Effects Software Inc. * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + * COMMENTS: + * This file is generated. Do not modify directly. + */ + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + + +FHoudiniApi::AddAttributeFuncPtr +FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; + +FHoudiniApi::AddGroupFuncPtr +FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; + +FHoudiniApi::AssetInfo_CreateFuncPtr +FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; + +FHoudiniApi::AssetInfo_InitFuncPtr +FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; + +FHoudiniApi::AttributeInfo_CreateFuncPtr +FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; + +FHoudiniApi::AttributeInfo_InitFuncPtr +FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; + +FHoudiniApi::BindCustomImplementationFuncPtr +FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; + +FHoudiniApi::CancelPDGCookFuncPtr +FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; + +FHoudiniApi::CheckForSpecificErrorsFuncPtr +FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; + +FHoudiniApi::CleanupFuncPtr +FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; + +FHoudiniApi::CloseSessionFuncPtr +FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; + +FHoudiniApi::CommitGeoFuncPtr +FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; + +FHoudiniApi::CommitWorkitemsFuncPtr +FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; + +FHoudiniApi::ComposeChildNodeListFuncPtr +FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; + +FHoudiniApi::ComposeNodeCookResultFuncPtr +FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; + +FHoudiniApi::ComposeObjectListFuncPtr +FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; + +FHoudiniApi::ConnectNodeInputFuncPtr +FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; + +FHoudiniApi::ConvertMatrixToEulerFuncPtr +FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; + +FHoudiniApi::ConvertMatrixToQuatFuncPtr +FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; + +FHoudiniApi::ConvertTransformFuncPtr +FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; + +FHoudiniApi::ConvertTransformEulerToMatrixFuncPtr +FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; + +FHoudiniApi::ConvertTransformQuatToMatrixFuncPtr +FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; + +FHoudiniApi::CookNodeFuncPtr +FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; + +FHoudiniApi::CookOptions_AreEqualFuncPtr +FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; + +FHoudiniApi::CookOptions_CreateFuncPtr +FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; + +FHoudiniApi::CookOptions_InitFuncPtr +FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; + +FHoudiniApi::CookPDGFuncPtr +FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; + +FHoudiniApi::CreateCustomSessionFuncPtr +FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; + +FHoudiniApi::CreateHeightfieldInputNodeFuncPtr +FHoudiniApi::CreateHeightfieldInputNode = &FHoudiniApi::CreateHeightfieldInputNodeEmptyStub; + +FHoudiniApi::CreateHeightfieldInputVolumeNodeFuncPtr +FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; + +FHoudiniApi::CreateInProcessSessionFuncPtr +FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; + +FHoudiniApi::CreateInputNodeFuncPtr +FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; + +FHoudiniApi::CreateNodeFuncPtr +FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; + +FHoudiniApi::CreateThriftNamedPipeSessionFuncPtr +FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; + +FHoudiniApi::CreateThriftSocketSessionFuncPtr +FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; + +FHoudiniApi::CreateWorkitemFuncPtr +FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; + +FHoudiniApi::CurveInfo_CreateFuncPtr +FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; + +FHoudiniApi::CurveInfo_InitFuncPtr +FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; + +FHoudiniApi::DeleteAttributeFuncPtr +FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; + +FHoudiniApi::DeleteGroupFuncPtr +FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; + +FHoudiniApi::DeleteNodeFuncPtr +FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; + +FHoudiniApi::DirtyPDGNodeFuncPtr +FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; + +FHoudiniApi::DisconnectNodeInputFuncPtr +FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; + +FHoudiniApi::DisconnectNodeOutputsAtFuncPtr +FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; + +FHoudiniApi::ExtractImageToFileFuncPtr +FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; + +FHoudiniApi::ExtractImageToMemoryFuncPtr +FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; + +FHoudiniApi::GeoInfo_CreateFuncPtr +FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; + +FHoudiniApi::GeoInfo_GetGroupCountByTypeFuncPtr +FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; + +FHoudiniApi::GeoInfo_InitFuncPtr +FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; + +FHoudiniApi::GetActiveCacheCountFuncPtr +FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; + +FHoudiniApi::GetActiveCacheNamesFuncPtr +FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; + +FHoudiniApi::GetAssetInfoFuncPtr +FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; + +FHoudiniApi::GetAttributeFloat64DataFuncPtr +FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; + +FHoudiniApi::GetAttributeFloatDataFuncPtr +FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; + +FHoudiniApi::GetAttributeInfoFuncPtr +FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; + +FHoudiniApi::GetAttributeInt64DataFuncPtr +FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; + +FHoudiniApi::GetAttributeIntDataFuncPtr +FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; + +FHoudiniApi::GetAttributeNamesFuncPtr +FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; + +FHoudiniApi::GetAttributeStringDataFuncPtr +FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; + +FHoudiniApi::GetAvailableAssetCountFuncPtr +FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; + +FHoudiniApi::GetAvailableAssetsFuncPtr +FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; + +FHoudiniApi::GetBoxInfoFuncPtr +FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; + +FHoudiniApi::GetCachePropertyFuncPtr +FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; + +FHoudiniApi::GetComposedChildNodeListFuncPtr +FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; + +FHoudiniApi::GetComposedNodeCookResultFuncPtr +FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; + +FHoudiniApi::GetComposedObjectListFuncPtr +FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; + +FHoudiniApi::GetComposedObjectTransformsFuncPtr +FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; + +FHoudiniApi::GetCookingCurrentCountFuncPtr +FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; + +FHoudiniApi::GetCookingTotalCountFuncPtr +FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; + +FHoudiniApi::GetCurveCountsFuncPtr +FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; + +FHoudiniApi::GetCurveInfoFuncPtr +FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; + +FHoudiniApi::GetCurveKnotsFuncPtr +FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; + +FHoudiniApi::GetCurveOrdersFuncPtr +FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; + +FHoudiniApi::GetDisplayGeoInfoFuncPtr +FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; + +FHoudiniApi::GetEnvIntFuncPtr +FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; + +FHoudiniApi::GetFaceCountsFuncPtr +FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; + +FHoudiniApi::GetFirstVolumeTileFuncPtr +FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; + +FHoudiniApi::GetGeoInfoFuncPtr +FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; + +FHoudiniApi::GetGeoSizeFuncPtr +FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; + +FHoudiniApi::GetGroupCountOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetGroupMembershipFuncPtr +FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; + +FHoudiniApi::GetGroupMembershipOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetGroupNamesFuncPtr +FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; + +FHoudiniApi::GetGroupNamesOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetHandleBindingInfoFuncPtr +FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; + +FHoudiniApi::GetHandleInfoFuncPtr +FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; + +FHoudiniApi::GetHeightFieldDataFuncPtr +FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; + +FHoudiniApi::GetImageFilePathFuncPtr +FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; + +FHoudiniApi::GetImageInfoFuncPtr +FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; + +FHoudiniApi::GetImageMemoryBufferFuncPtr +FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; + +FHoudiniApi::GetImagePlaneCountFuncPtr +FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; + +FHoudiniApi::GetImagePlanesFuncPtr +FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; + +FHoudiniApi::GetInstanceTransformsOnPartFuncPtr +FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; + +FHoudiniApi::GetInstancedObjectIdsFuncPtr +FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; + +FHoudiniApi::GetInstancedPartIdsFuncPtr +FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; + +FHoudiniApi::GetInstancerPartTransformsFuncPtr +FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; + +FHoudiniApi::GetManagerNodeIdFuncPtr +FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; + +FHoudiniApi::GetMaterialInfoFuncPtr +FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; + +FHoudiniApi::GetMaterialNodeIdsOnFacesFuncPtr +FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; + +FHoudiniApi::GetNextVolumeTileFuncPtr +FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; + +FHoudiniApi::GetNodeInfoFuncPtr +FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; + +FHoudiniApi::GetNodeInputNameFuncPtr +FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; + +FHoudiniApi::GetNodeOutputNameFuncPtr +FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; + +FHoudiniApi::GetNodePathFuncPtr +FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; + +FHoudiniApi::GetNumWorkitemsFuncPtr +FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; + +FHoudiniApi::GetObjectInfoFuncPtr +FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; + +FHoudiniApi::GetObjectTransformFuncPtr +FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; + +FHoudiniApi::GetPDGEventsFuncPtr +FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; + +FHoudiniApi::GetPDGGraphContextIdFuncPtr +FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; + +FHoudiniApi::GetPDGGraphContextsFuncPtr +FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; + +FHoudiniApi::GetPDGStateFuncPtr +FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; + +FHoudiniApi::GetParametersFuncPtr +FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; + +FHoudiniApi::GetParmChoiceListsFuncPtr +FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; + +FHoudiniApi::GetParmExpressionFuncPtr +FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; + +FHoudiniApi::GetParmFileFuncPtr +FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; + +FHoudiniApi::GetParmFloatValueFuncPtr +FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; + +FHoudiniApi::GetParmFloatValuesFuncPtr +FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; + +FHoudiniApi::GetParmIdFromNameFuncPtr +FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; + +FHoudiniApi::GetParmInfoFuncPtr +FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; + +FHoudiniApi::GetParmInfoFromNameFuncPtr +FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; + +FHoudiniApi::GetParmIntValueFuncPtr +FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; + +FHoudiniApi::GetParmIntValuesFuncPtr +FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; + +FHoudiniApi::GetParmNodeValueFuncPtr +FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; + +FHoudiniApi::GetParmStringValueFuncPtr +FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; + +FHoudiniApi::GetParmStringValuesFuncPtr +FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; + +FHoudiniApi::GetParmTagNameFuncPtr +FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; + +FHoudiniApi::GetParmTagValueFuncPtr +FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; + +FHoudiniApi::GetParmWithTagFuncPtr +FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; + +FHoudiniApi::GetPartInfoFuncPtr +FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; + +FHoudiniApi::GetPresetFuncPtr +FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; + +FHoudiniApi::GetPresetBufLengthFuncPtr +FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; + +FHoudiniApi::GetServerEnvIntFuncPtr +FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; + +FHoudiniApi::GetServerEnvStringFuncPtr +FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; + +FHoudiniApi::GetServerEnvVarCountFuncPtr +FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; + +FHoudiniApi::GetServerEnvVarListFuncPtr +FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; + +FHoudiniApi::GetSessionEnvIntFuncPtr +FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; + +FHoudiniApi::GetSphereInfoFuncPtr +FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; + +FHoudiniApi::GetStatusFuncPtr +FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; + +FHoudiniApi::GetStatusStringFuncPtr +FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; + +FHoudiniApi::GetStatusStringBufLengthFuncPtr +FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; + +FHoudiniApi::GetStringFuncPtr +FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; + +FHoudiniApi::GetStringBatchFuncPtr +FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; + +FHoudiniApi::GetStringBatchSizeFuncPtr +FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; + +FHoudiniApi::GetStringBufLengthFuncPtr +FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; + +FHoudiniApi::GetSupportedImageFileFormatCountFuncPtr +FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; + +FHoudiniApi::GetSupportedImageFileFormatsFuncPtr +FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; + +FHoudiniApi::GetTimeFuncPtr +FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; + +FHoudiniApi::GetTimelineOptionsFuncPtr +FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; + +FHoudiniApi::GetVertexListFuncPtr +FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; + +FHoudiniApi::GetVolumeBoundsFuncPtr +FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; + +FHoudiniApi::GetVolumeInfoFuncPtr +FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; + +FHoudiniApi::GetVolumeTileFloatDataFuncPtr +FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; + +FHoudiniApi::GetVolumeTileIntDataFuncPtr +FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; + +FHoudiniApi::GetVolumeVoxelFloatDataFuncPtr +FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; + +FHoudiniApi::GetVolumeVoxelIntDataFuncPtr +FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; + +FHoudiniApi::GetWorkitemDataLengthFuncPtr +FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; + +FHoudiniApi::GetWorkitemFloatDataFuncPtr +FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; + +FHoudiniApi::GetWorkitemInfoFuncPtr +FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; + +FHoudiniApi::GetWorkitemIntDataFuncPtr +FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; + +FHoudiniApi::GetWorkitemResultInfoFuncPtr +FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; + +FHoudiniApi::GetWorkitemStringDataFuncPtr +FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; + +FHoudiniApi::GetWorkitemsFuncPtr +FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; + +FHoudiniApi::HandleBindingInfo_CreateFuncPtr +FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; + +FHoudiniApi::HandleBindingInfo_InitFuncPtr +FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; + +FHoudiniApi::HandleInfo_CreateFuncPtr +FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; + +FHoudiniApi::HandleInfo_InitFuncPtr +FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; + +FHoudiniApi::ImageFileFormat_CreateFuncPtr +FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; + +FHoudiniApi::ImageFileFormat_InitFuncPtr +FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; + +FHoudiniApi::ImageInfo_CreateFuncPtr +FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; + +FHoudiniApi::ImageInfo_InitFuncPtr +FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; + +FHoudiniApi::InitializeFuncPtr +FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; + +FHoudiniApi::InsertMultiparmInstanceFuncPtr +FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; + +FHoudiniApi::InterruptFuncPtr +FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; + +FHoudiniApi::IsInitializedFuncPtr +FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; + +FHoudiniApi::IsNodeValidFuncPtr +FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; + +FHoudiniApi::IsSessionValidFuncPtr +FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; + +FHoudiniApi::Keyframe_CreateFuncPtr +FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; + +FHoudiniApi::Keyframe_InitFuncPtr +FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; + +FHoudiniApi::LoadAssetLibraryFromFileFuncPtr +FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; + +FHoudiniApi::LoadAssetLibraryFromMemoryFuncPtr +FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; + +FHoudiniApi::LoadGeoFromFileFuncPtr +FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; + +FHoudiniApi::LoadGeoFromMemoryFuncPtr +FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; + +FHoudiniApi::LoadHIPFileFuncPtr +FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; + +FHoudiniApi::MaterialInfo_CreateFuncPtr +FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; + +FHoudiniApi::MaterialInfo_InitFuncPtr +FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; + +FHoudiniApi::NodeInfo_CreateFuncPtr +FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; + +FHoudiniApi::NodeInfo_InitFuncPtr +FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; + +FHoudiniApi::ObjectInfo_CreateFuncPtr +FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; + +FHoudiniApi::ObjectInfo_InitFuncPtr +FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; + +FHoudiniApi::ParmChoiceInfo_CreateFuncPtr +FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; + +FHoudiniApi::ParmChoiceInfo_InitFuncPtr +FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; + +FHoudiniApi::ParmHasExpressionFuncPtr +FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; + +FHoudiniApi::ParmHasTagFuncPtr +FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; + +FHoudiniApi::ParmInfo_CreateFuncPtr +FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; + +FHoudiniApi::ParmInfo_GetFloatValueCountFuncPtr +FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; + +FHoudiniApi::ParmInfo_GetIntValueCountFuncPtr +FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; + +FHoudiniApi::ParmInfo_GetStringValueCountFuncPtr +FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; + +FHoudiniApi::ParmInfo_InitFuncPtr +FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; + +FHoudiniApi::ParmInfo_IsFloatFuncPtr +FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; + +FHoudiniApi::ParmInfo_IsIntFuncPtr +FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; + +FHoudiniApi::ParmInfo_IsNodeFuncPtr +FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; + +FHoudiniApi::ParmInfo_IsNonValueFuncPtr +FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; + +FHoudiniApi::ParmInfo_IsPathFuncPtr +FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; + +FHoudiniApi::ParmInfo_IsStringFuncPtr +FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; + +FHoudiniApi::PartInfo_CreateFuncPtr +FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; + +FHoudiniApi::PartInfo_GetAttributeCountByOwnerFuncPtr +FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; + +FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerFuncPtr +FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; + +FHoudiniApi::PartInfo_GetElementCountByGroupTypeFuncPtr +FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; + +FHoudiniApi::PartInfo_InitFuncPtr +FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; + +FHoudiniApi::PausePDGCookFuncPtr +FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; + +FHoudiniApi::PythonThreadInterpreterLockFuncPtr +FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; + +FHoudiniApi::QueryNodeInputFuncPtr +FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; + +FHoudiniApi::QueryNodeOutputConnectedCountFuncPtr +FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; + +FHoudiniApi::QueryNodeOutputConnectedNodesFuncPtr +FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; + +FHoudiniApi::RemoveCustomStringFuncPtr +FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; + +FHoudiniApi::RemoveMultiparmInstanceFuncPtr +FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; + +FHoudiniApi::RemoveParmExpressionFuncPtr +FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; + +FHoudiniApi::RenameNodeFuncPtr +FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; + +FHoudiniApi::RenderCOPToImageFuncPtr +FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; + +FHoudiniApi::RenderTextureToImageFuncPtr +FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; + +FHoudiniApi::ResetSimulationFuncPtr +FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; + +FHoudiniApi::RevertGeoFuncPtr +FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; + +FHoudiniApi::RevertParmToDefaultFuncPtr +FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; + +FHoudiniApi::RevertParmToDefaultsFuncPtr +FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; + +FHoudiniApi::SaveGeoToFileFuncPtr +FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; + +FHoudiniApi::SaveGeoToMemoryFuncPtr +FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; + +FHoudiniApi::SaveHIPFileFuncPtr +FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; + +FHoudiniApi::SetAnimCurveFuncPtr +FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; + +FHoudiniApi::SetAttributeFloat64DataFuncPtr +FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; + +FHoudiniApi::SetAttributeFloatDataFuncPtr +FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; + +FHoudiniApi::SetAttributeInt64DataFuncPtr +FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; + +FHoudiniApi::SetAttributeIntDataFuncPtr +FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; + +FHoudiniApi::SetAttributeStringDataFuncPtr +FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; + +FHoudiniApi::SetCachePropertyFuncPtr +FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; + +FHoudiniApi::SetCurveCountsFuncPtr +FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; + +FHoudiniApi::SetCurveInfoFuncPtr +FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; + +FHoudiniApi::SetCurveKnotsFuncPtr +FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; + +FHoudiniApi::SetCurveOrdersFuncPtr +FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; + +FHoudiniApi::SetCustomStringFuncPtr +FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; + +FHoudiniApi::SetFaceCountsFuncPtr +FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; + +FHoudiniApi::SetGroupMembershipFuncPtr +FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; + +FHoudiniApi::SetHeightFieldDataFuncPtr +FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; + +FHoudiniApi::SetImageInfoFuncPtr +FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; + +FHoudiniApi::SetNodeDisplayFuncPtr +FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; + +FHoudiniApi::SetObjectTransformFuncPtr +FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; + +FHoudiniApi::SetParmExpressionFuncPtr +FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; + +FHoudiniApi::SetParmFloatValueFuncPtr +FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; + +FHoudiniApi::SetParmFloatValuesFuncPtr +FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; + +FHoudiniApi::SetParmIntValueFuncPtr +FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; + +FHoudiniApi::SetParmIntValuesFuncPtr +FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; + +FHoudiniApi::SetParmNodeValueFuncPtr +FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; + +FHoudiniApi::SetParmStringValueFuncPtr +FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; + +FHoudiniApi::SetPartInfoFuncPtr +FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; + +FHoudiniApi::SetPresetFuncPtr +FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; + +FHoudiniApi::SetServerEnvIntFuncPtr +FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; + +FHoudiniApi::SetServerEnvStringFuncPtr +FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; + +FHoudiniApi::SetTimeFuncPtr +FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; + +FHoudiniApi::SetTimelineOptionsFuncPtr +FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; + +FHoudiniApi::SetTransformAnimCurveFuncPtr +FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; + +FHoudiniApi::SetVertexListFuncPtr +FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; + +FHoudiniApi::SetVolumeInfoFuncPtr +FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; + +FHoudiniApi::SetVolumeTileFloatDataFuncPtr +FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; + +FHoudiniApi::SetVolumeTileIntDataFuncPtr +FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; + +FHoudiniApi::SetVolumeVoxelFloatDataFuncPtr +FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; + +FHoudiniApi::SetVolumeVoxelIntDataFuncPtr +FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; + +FHoudiniApi::SetWorkitemFloatDataFuncPtr +FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; + +FHoudiniApi::SetWorkitemIntDataFuncPtr +FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; + +FHoudiniApi::SetWorkitemStringDataFuncPtr +FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; + +FHoudiniApi::StartThriftNamedPipeServerFuncPtr +FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; + +FHoudiniApi::StartThriftSocketServerFuncPtr +FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; + +FHoudiniApi::TimelineOptions_CreateFuncPtr +FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; + +FHoudiniApi::TimelineOptions_InitFuncPtr +FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; + +FHoudiniApi::TransformEuler_CreateFuncPtr +FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; + +FHoudiniApi::TransformEuler_InitFuncPtr +FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; + +FHoudiniApi::Transform_CreateFuncPtr +FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; + +FHoudiniApi::Transform_InitFuncPtr +FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; + +FHoudiniApi::VolumeInfo_CreateFuncPtr +FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; + +FHoudiniApi::VolumeInfo_InitFuncPtr +FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; + +FHoudiniApi::VolumeTileInfo_CreateFuncPtr +FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; + +FHoudiniApi::VolumeTileInfo_InitFuncPtr +FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; + + +void +FHoudiniApi::InitializeHAPI(void* LibraryHandle) +{ + if(!LibraryHandle) return; + + FHoudiniApi::AddAttribute = (AddAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddAttribute")); + FHoudiniApi::AddGroup = (AddGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddGroup")); + FHoudiniApi::AssetInfo_Create = (AssetInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Create")); + FHoudiniApi::AssetInfo_Init = (AssetInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Init")); + FHoudiniApi::AttributeInfo_Create = (AttributeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Create")); + FHoudiniApi::AttributeInfo_Init = (AttributeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Init")); + FHoudiniApi::BindCustomImplementation = (BindCustomImplementationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_BindCustomImplementation")); + FHoudiniApi::CancelPDGCook = (CancelPDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CancelPDGCook")); + FHoudiniApi::CheckForSpecificErrors = (CheckForSpecificErrorsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CheckForSpecificErrors")); + FHoudiniApi::Cleanup = (CleanupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Cleanup")); + FHoudiniApi::CloseSession = (CloseSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CloseSession")); + FHoudiniApi::CommitGeo = (CommitGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitGeo")); + FHoudiniApi::CommitWorkitems = (CommitWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitWorkitems")); + FHoudiniApi::ComposeChildNodeList = (ComposeChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeChildNodeList")); + FHoudiniApi::ComposeNodeCookResult = (ComposeNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeNodeCookResult")); + FHoudiniApi::ComposeObjectList = (ComposeObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeObjectList")); + FHoudiniApi::ConnectNodeInput = (ConnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConnectNodeInput")); + FHoudiniApi::ConvertMatrixToEuler = (ConvertMatrixToEulerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToEuler")); + FHoudiniApi::ConvertMatrixToQuat = (ConvertMatrixToQuatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToQuat")); + FHoudiniApi::ConvertTransform = (ConvertTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransform")); + FHoudiniApi::ConvertTransformEulerToMatrix = (ConvertTransformEulerToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformEulerToMatrix")); + FHoudiniApi::ConvertTransformQuatToMatrix = (ConvertTransformQuatToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformQuatToMatrix")); + FHoudiniApi::CookNode = (CookNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookNode")); + FHoudiniApi::CookOptions_AreEqual = (CookOptions_AreEqualFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_AreEqual")); + FHoudiniApi::CookOptions_Create = (CookOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Create")); + FHoudiniApi::CookOptions_Init = (CookOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Init")); + FHoudiniApi::CookPDG = (CookPDGFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookPDG")); + FHoudiniApi::CreateCustomSession = (CreateCustomSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateCustomSession")); + FHoudiniApi::CreateHeightfieldInputNode = (CreateHeightfieldInputNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightfieldInputNode")); + FHoudiniApi::CreateHeightfieldInputVolumeNode = (CreateHeightfieldInputVolumeNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightfieldInputVolumeNode")); + FHoudiniApi::CreateInProcessSession = (CreateInProcessSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInProcessSession")); + FHoudiniApi::CreateInputNode = (CreateInputNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInputNode")); + FHoudiniApi::CreateNode = (CreateNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateNode")); + FHoudiniApi::CreateThriftNamedPipeSession = (CreateThriftNamedPipeSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftNamedPipeSession")); + FHoudiniApi::CreateThriftSocketSession = (CreateThriftSocketSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftSocketSession")); + FHoudiniApi::CreateWorkitem = (CreateWorkitemFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateWorkitem")); + FHoudiniApi::CurveInfo_Create = (CurveInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Create")); + FHoudiniApi::CurveInfo_Init = (CurveInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Init")); + FHoudiniApi::DeleteAttribute = (DeleteAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteAttribute")); + FHoudiniApi::DeleteGroup = (DeleteGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteGroup")); + FHoudiniApi::DeleteNode = (DeleteNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteNode")); + FHoudiniApi::DirtyPDGNode = (DirtyPDGNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DirtyPDGNode")); + FHoudiniApi::DisconnectNodeInput = (DisconnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeInput")); + FHoudiniApi::DisconnectNodeOutputsAt = (DisconnectNodeOutputsAtFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeOutputsAt")); + FHoudiniApi::ExtractImageToFile = (ExtractImageToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToFile")); + FHoudiniApi::ExtractImageToMemory = (ExtractImageToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToMemory")); + FHoudiniApi::GeoInfo_Create = (GeoInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Create")); + FHoudiniApi::GeoInfo_GetGroupCountByType = (GeoInfo_GetGroupCountByTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_GetGroupCountByType")); + FHoudiniApi::GeoInfo_Init = (GeoInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Init")); + FHoudiniApi::GetActiveCacheCount = (GetActiveCacheCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheCount")); + FHoudiniApi::GetActiveCacheNames = (GetActiveCacheNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheNames")); + FHoudiniApi::GetAssetInfo = (GetAssetInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetInfo")); + FHoudiniApi::GetAttributeFloat64Data = (GetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64Data")); + FHoudiniApi::GetAttributeFloatData = (GetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatData")); + FHoudiniApi::GetAttributeInfo = (GetAttributeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInfo")); + FHoudiniApi::GetAttributeInt64Data = (GetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64Data")); + FHoudiniApi::GetAttributeIntData = (GetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntData")); + FHoudiniApi::GetAttributeNames = (GetAttributeNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeNames")); + FHoudiniApi::GetAttributeStringData = (GetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringData")); + FHoudiniApi::GetAvailableAssetCount = (GetAvailableAssetCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssetCount")); + FHoudiniApi::GetAvailableAssets = (GetAvailableAssetsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssets")); + FHoudiniApi::GetBoxInfo = (GetBoxInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetBoxInfo")); + FHoudiniApi::GetCacheProperty = (GetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCacheProperty")); + FHoudiniApi::GetComposedChildNodeList = (GetComposedChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedChildNodeList")); + FHoudiniApi::GetComposedNodeCookResult = (GetComposedNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedNodeCookResult")); + FHoudiniApi::GetComposedObjectList = (GetComposedObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectList")); + FHoudiniApi::GetComposedObjectTransforms = (GetComposedObjectTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectTransforms")); + FHoudiniApi::GetCookingCurrentCount = (GetCookingCurrentCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingCurrentCount")); + FHoudiniApi::GetCookingTotalCount = (GetCookingTotalCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingTotalCount")); + FHoudiniApi::GetCurveCounts = (GetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveCounts")); + FHoudiniApi::GetCurveInfo = (GetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveInfo")); + FHoudiniApi::GetCurveKnots = (GetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveKnots")); + FHoudiniApi::GetCurveOrders = (GetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveOrders")); + FHoudiniApi::GetDisplayGeoInfo = (GetDisplayGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetDisplayGeoInfo")); + FHoudiniApi::GetEnvInt = (GetEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetEnvInt")); + FHoudiniApi::GetFaceCounts = (GetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFaceCounts")); + FHoudiniApi::GetFirstVolumeTile = (GetFirstVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFirstVolumeTile")); + FHoudiniApi::GetGeoInfo = (GetGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoInfo")); + FHoudiniApi::GetGeoSize = (GetGeoSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoSize")); + FHoudiniApi::GetGroupCountOnPackedInstancePart = (GetGroupCountOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupCountOnPackedInstancePart")); + FHoudiniApi::GetGroupMembership = (GetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembership")); + FHoudiniApi::GetGroupMembershipOnPackedInstancePart = (GetGroupMembershipOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembershipOnPackedInstancePart")); + FHoudiniApi::GetGroupNames = (GetGroupNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNames")); + FHoudiniApi::GetGroupNamesOnPackedInstancePart = (GetGroupNamesOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNamesOnPackedInstancePart")); + FHoudiniApi::GetHandleBindingInfo = (GetHandleBindingInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleBindingInfo")); + FHoudiniApi::GetHandleInfo = (GetHandleInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleInfo")); + FHoudiniApi::GetHeightFieldData = (GetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHeightFieldData")); + FHoudiniApi::GetImageFilePath = (GetImageFilePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageFilePath")); + FHoudiniApi::GetImageInfo = (GetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageInfo")); + FHoudiniApi::GetImageMemoryBuffer = (GetImageMemoryBufferFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageMemoryBuffer")); + FHoudiniApi::GetImagePlaneCount = (GetImagePlaneCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlaneCount")); + FHoudiniApi::GetImagePlanes = (GetImagePlanesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlanes")); + FHoudiniApi::GetInstanceTransformsOnPart = (GetInstanceTransformsOnPartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstanceTransformsOnPart")); + FHoudiniApi::GetInstancedObjectIds = (GetInstancedObjectIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedObjectIds")); + FHoudiniApi::GetInstancedPartIds = (GetInstancedPartIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedPartIds")); + FHoudiniApi::GetInstancerPartTransforms = (GetInstancerPartTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancerPartTransforms")); + FHoudiniApi::GetManagerNodeId = (GetManagerNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetManagerNodeId")); + FHoudiniApi::GetMaterialInfo = (GetMaterialInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialInfo")); + FHoudiniApi::GetMaterialNodeIdsOnFaces = (GetMaterialNodeIdsOnFacesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialNodeIdsOnFaces")); + FHoudiniApi::GetNextVolumeTile = (GetNextVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNextVolumeTile")); + FHoudiniApi::GetNodeInfo = (GetNodeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInfo")); + FHoudiniApi::GetNodeInputName = (GetNodeInputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInputName")); + FHoudiniApi::GetNodeOutputName = (GetNodeOutputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeOutputName")); + FHoudiniApi::GetNodePath = (GetNodePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodePath")); + FHoudiniApi::GetNumWorkitems = (GetNumWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNumWorkitems")); + FHoudiniApi::GetObjectInfo = (GetObjectInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectInfo")); + FHoudiniApi::GetObjectTransform = (GetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectTransform")); + FHoudiniApi::GetPDGEvents = (GetPDGEventsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGEvents")); + FHoudiniApi::GetPDGGraphContextId = (GetPDGGraphContextIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContextId")); + FHoudiniApi::GetPDGGraphContexts = (GetPDGGraphContextsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContexts")); + FHoudiniApi::GetPDGState = (GetPDGStateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGState")); + FHoudiniApi::GetParameters = (GetParametersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParameters")); + FHoudiniApi::GetParmChoiceLists = (GetParmChoiceListsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmChoiceLists")); + FHoudiniApi::GetParmExpression = (GetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmExpression")); + FHoudiniApi::GetParmFile = (GetParmFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFile")); + FHoudiniApi::GetParmFloatValue = (GetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValue")); + FHoudiniApi::GetParmFloatValues = (GetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValues")); + FHoudiniApi::GetParmIdFromName = (GetParmIdFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIdFromName")); + FHoudiniApi::GetParmInfo = (GetParmInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfo")); + FHoudiniApi::GetParmInfoFromName = (GetParmInfoFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfoFromName")); + FHoudiniApi::GetParmIntValue = (GetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValue")); + FHoudiniApi::GetParmIntValues = (GetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValues")); + FHoudiniApi::GetParmNodeValue = (GetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmNodeValue")); + FHoudiniApi::GetParmStringValue = (GetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValue")); + FHoudiniApi::GetParmStringValues = (GetParmStringValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValues")); + FHoudiniApi::GetParmTagName = (GetParmTagNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagName")); + FHoudiniApi::GetParmTagValue = (GetParmTagValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagValue")); + FHoudiniApi::GetParmWithTag = (GetParmWithTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmWithTag")); + FHoudiniApi::GetPartInfo = (GetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPartInfo")); + FHoudiniApi::GetPreset = (GetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPreset")); + FHoudiniApi::GetPresetBufLength = (GetPresetBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPresetBufLength")); + FHoudiniApi::GetServerEnvInt = (GetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvInt")); + FHoudiniApi::GetServerEnvString = (GetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvString")); + FHoudiniApi::GetServerEnvVarCount = (GetServerEnvVarCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarCount")); + FHoudiniApi::GetServerEnvVarList = (GetServerEnvVarListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarList")); + FHoudiniApi::GetSessionEnvInt = (GetSessionEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionEnvInt")); + FHoudiniApi::GetSphereInfo = (GetSphereInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSphereInfo")); + FHoudiniApi::GetStatus = (GetStatusFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatus")); + FHoudiniApi::GetStatusString = (GetStatusStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusString")); + FHoudiniApi::GetStatusStringBufLength = (GetStatusStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusStringBufLength")); + FHoudiniApi::GetString = (GetStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetString")); + FHoudiniApi::GetStringBatch = (GetStringBatchFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatch")); + FHoudiniApi::GetStringBatchSize = (GetStringBatchSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatchSize")); + FHoudiniApi::GetStringBufLength = (GetStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBufLength")); + FHoudiniApi::GetSupportedImageFileFormatCount = (GetSupportedImageFileFormatCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormatCount")); + FHoudiniApi::GetSupportedImageFileFormats = (GetSupportedImageFileFormatsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormats")); + FHoudiniApi::GetTime = (GetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTime")); + FHoudiniApi::GetTimelineOptions = (GetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTimelineOptions")); + FHoudiniApi::GetVertexList = (GetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVertexList")); + FHoudiniApi::GetVolumeBounds = (GetVolumeBoundsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeBounds")); + FHoudiniApi::GetVolumeInfo = (GetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeInfo")); + FHoudiniApi::GetVolumeTileFloatData = (GetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileFloatData")); + FHoudiniApi::GetVolumeTileIntData = (GetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileIntData")); + FHoudiniApi::GetVolumeVoxelFloatData = (GetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelFloatData")); + FHoudiniApi::GetVolumeVoxelIntData = (GetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelIntData")); + FHoudiniApi::GetWorkitemDataLength = (GetWorkitemDataLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemDataLength")); + FHoudiniApi::GetWorkitemFloatData = (GetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemFloatData")); + FHoudiniApi::GetWorkitemInfo = (GetWorkitemInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemInfo")); + FHoudiniApi::GetWorkitemIntData = (GetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemIntData")); + FHoudiniApi::GetWorkitemResultInfo = (GetWorkitemResultInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemResultInfo")); + FHoudiniApi::GetWorkitemStringData = (GetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemStringData")); + FHoudiniApi::GetWorkitems = (GetWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitems")); + FHoudiniApi::HandleBindingInfo_Create = (HandleBindingInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Create")); + FHoudiniApi::HandleBindingInfo_Init = (HandleBindingInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Init")); + FHoudiniApi::HandleInfo_Create = (HandleInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Create")); + FHoudiniApi::HandleInfo_Init = (HandleInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Init")); + FHoudiniApi::ImageFileFormat_Create = (ImageFileFormat_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Create")); + FHoudiniApi::ImageFileFormat_Init = (ImageFileFormat_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Init")); + FHoudiniApi::ImageInfo_Create = (ImageInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Create")); + FHoudiniApi::ImageInfo_Init = (ImageInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Init")); + FHoudiniApi::Initialize = (InitializeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Initialize")); + FHoudiniApi::InsertMultiparmInstance = (InsertMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_InsertMultiparmInstance")); + FHoudiniApi::Interrupt = (InterruptFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Interrupt")); + FHoudiniApi::IsInitialized = (IsInitializedFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsInitialized")); + FHoudiniApi::IsNodeValid = (IsNodeValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsNodeValid")); + FHoudiniApi::IsSessionValid = (IsSessionValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsSessionValid")); + FHoudiniApi::Keyframe_Create = (Keyframe_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Create")); + FHoudiniApi::Keyframe_Init = (Keyframe_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Init")); + FHoudiniApi::LoadAssetLibraryFromFile = (LoadAssetLibraryFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromFile")); + FHoudiniApi::LoadAssetLibraryFromMemory = (LoadAssetLibraryFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromMemory")); + FHoudiniApi::LoadGeoFromFile = (LoadGeoFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromFile")); + FHoudiniApi::LoadGeoFromMemory = (LoadGeoFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromMemory")); + FHoudiniApi::LoadHIPFile = (LoadHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadHIPFile")); + FHoudiniApi::MaterialInfo_Create = (MaterialInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Create")); + FHoudiniApi::MaterialInfo_Init = (MaterialInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Init")); + FHoudiniApi::NodeInfo_Create = (NodeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Create")); + FHoudiniApi::NodeInfo_Init = (NodeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Init")); + FHoudiniApi::ObjectInfo_Create = (ObjectInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Create")); + FHoudiniApi::ObjectInfo_Init = (ObjectInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Init")); + FHoudiniApi::ParmChoiceInfo_Create = (ParmChoiceInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Create")); + FHoudiniApi::ParmChoiceInfo_Init = (ParmChoiceInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Init")); + FHoudiniApi::ParmHasExpression = (ParmHasExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasExpression")); + FHoudiniApi::ParmHasTag = (ParmHasTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasTag")); + FHoudiniApi::ParmInfo_Create = (ParmInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Create")); + FHoudiniApi::ParmInfo_GetFloatValueCount = (ParmInfo_GetFloatValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetFloatValueCount")); + FHoudiniApi::ParmInfo_GetIntValueCount = (ParmInfo_GetIntValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetIntValueCount")); + FHoudiniApi::ParmInfo_GetStringValueCount = (ParmInfo_GetStringValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetStringValueCount")); + FHoudiniApi::ParmInfo_Init = (ParmInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Init")); + FHoudiniApi::ParmInfo_IsFloat = (ParmInfo_IsFloatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsFloat")); + FHoudiniApi::ParmInfo_IsInt = (ParmInfo_IsIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsInt")); + FHoudiniApi::ParmInfo_IsNode = (ParmInfo_IsNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNode")); + FHoudiniApi::ParmInfo_IsNonValue = (ParmInfo_IsNonValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNonValue")); + FHoudiniApi::ParmInfo_IsPath = (ParmInfo_IsPathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsPath")); + FHoudiniApi::ParmInfo_IsString = (ParmInfo_IsStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsString")); + FHoudiniApi::PartInfo_Create = (PartInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Create")); + FHoudiniApi::PartInfo_GetAttributeCountByOwner = (PartInfo_GetAttributeCountByOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetAttributeCountByOwner")); + FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = (PartInfo_GetElementCountByAttributeOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByAttributeOwner")); + FHoudiniApi::PartInfo_GetElementCountByGroupType = (PartInfo_GetElementCountByGroupTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByGroupType")); + FHoudiniApi::PartInfo_Init = (PartInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Init")); + FHoudiniApi::PausePDGCook = (PausePDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PausePDGCook")); + FHoudiniApi::PythonThreadInterpreterLock = (PythonThreadInterpreterLockFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PythonThreadInterpreterLock")); + FHoudiniApi::QueryNodeInput = (QueryNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeInput")); + FHoudiniApi::QueryNodeOutputConnectedCount = (QueryNodeOutputConnectedCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedCount")); + FHoudiniApi::QueryNodeOutputConnectedNodes = (QueryNodeOutputConnectedNodesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedNodes")); + FHoudiniApi::RemoveCustomString = (RemoveCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveCustomString")); + FHoudiniApi::RemoveMultiparmInstance = (RemoveMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveMultiparmInstance")); + FHoudiniApi::RemoveParmExpression = (RemoveParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveParmExpression")); + FHoudiniApi::RenameNode = (RenameNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenameNode")); + FHoudiniApi::RenderCOPToImage = (RenderCOPToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderCOPToImage")); + FHoudiniApi::RenderTextureToImage = (RenderTextureToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderTextureToImage")); + FHoudiniApi::ResetSimulation = (ResetSimulationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ResetSimulation")); + FHoudiniApi::RevertGeo = (RevertGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertGeo")); + FHoudiniApi::RevertParmToDefault = (RevertParmToDefaultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefault")); + FHoudiniApi::RevertParmToDefaults = (RevertParmToDefaultsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefaults")); + FHoudiniApi::SaveGeoToFile = (SaveGeoToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToFile")); + FHoudiniApi::SaveGeoToMemory = (SaveGeoToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToMemory")); + FHoudiniApi::SaveHIPFile = (SaveHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveHIPFile")); + FHoudiniApi::SetAnimCurve = (SetAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAnimCurve")); + FHoudiniApi::SetAttributeFloat64Data = (SetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloat64Data")); + FHoudiniApi::SetAttributeFloatData = (SetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloatData")); + FHoudiniApi::SetAttributeInt64Data = (SetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt64Data")); + FHoudiniApi::SetAttributeIntData = (SetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeIntData")); + FHoudiniApi::SetAttributeStringData = (SetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeStringData")); + FHoudiniApi::SetCacheProperty = (SetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCacheProperty")); + FHoudiniApi::SetCurveCounts = (SetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveCounts")); + FHoudiniApi::SetCurveInfo = (SetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveInfo")); + FHoudiniApi::SetCurveKnots = (SetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveKnots")); + FHoudiniApi::SetCurveOrders = (SetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveOrders")); + FHoudiniApi::SetCustomString = (SetCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCustomString")); + FHoudiniApi::SetFaceCounts = (SetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetFaceCounts")); + FHoudiniApi::SetGroupMembership = (SetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetGroupMembership")); + FHoudiniApi::SetHeightFieldData = (SetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetHeightFieldData")); + FHoudiniApi::SetImageInfo = (SetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetImageInfo")); + FHoudiniApi::SetNodeDisplay = (SetNodeDisplayFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetNodeDisplay")); + FHoudiniApi::SetObjectTransform = (SetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetObjectTransform")); + FHoudiniApi::SetParmExpression = (SetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmExpression")); + FHoudiniApi::SetParmFloatValue = (SetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValue")); + FHoudiniApi::SetParmFloatValues = (SetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValues")); + FHoudiniApi::SetParmIntValue = (SetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValue")); + FHoudiniApi::SetParmIntValues = (SetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValues")); + FHoudiniApi::SetParmNodeValue = (SetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmNodeValue")); + FHoudiniApi::SetParmStringValue = (SetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmStringValue")); + FHoudiniApi::SetPartInfo = (SetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPartInfo")); + FHoudiniApi::SetPreset = (SetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPreset")); + FHoudiniApi::SetServerEnvInt = (SetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvInt")); + FHoudiniApi::SetServerEnvString = (SetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvString")); + FHoudiniApi::SetTime = (SetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTime")); + FHoudiniApi::SetTimelineOptions = (SetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTimelineOptions")); + FHoudiniApi::SetTransformAnimCurve = (SetTransformAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTransformAnimCurve")); + FHoudiniApi::SetVertexList = (SetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVertexList")); + FHoudiniApi::SetVolumeInfo = (SetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeInfo")); + FHoudiniApi::SetVolumeTileFloatData = (SetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileFloatData")); + FHoudiniApi::SetVolumeTileIntData = (SetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileIntData")); + FHoudiniApi::SetVolumeVoxelFloatData = (SetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelFloatData")); + FHoudiniApi::SetVolumeVoxelIntData = (SetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelIntData")); + FHoudiniApi::SetWorkitemFloatData = (SetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemFloatData")); + FHoudiniApi::SetWorkitemIntData = (SetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemIntData")); + FHoudiniApi::SetWorkitemStringData = (SetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemStringData")); + FHoudiniApi::StartThriftNamedPipeServer = (StartThriftNamedPipeServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftNamedPipeServer")); + FHoudiniApi::StartThriftSocketServer = (StartThriftSocketServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftSocketServer")); + FHoudiniApi::TimelineOptions_Create = (TimelineOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Create")); + FHoudiniApi::TimelineOptions_Init = (TimelineOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Init")); + FHoudiniApi::TransformEuler_Create = (TransformEuler_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Create")); + FHoudiniApi::TransformEuler_Init = (TransformEuler_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Init")); + FHoudiniApi::Transform_Create = (Transform_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Create")); + FHoudiniApi::Transform_Init = (Transform_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Init")); + FHoudiniApi::VolumeInfo_Create = (VolumeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Create")); + FHoudiniApi::VolumeInfo_Init = (VolumeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Init")); + FHoudiniApi::VolumeTileInfo_Create = (VolumeTileInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Create")); + FHoudiniApi::VolumeTileInfo_Init = (VolumeTileInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Init")); +} + + +void +FHoudiniApi::FinalizeHAPI() +{ + FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; + FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; + FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; + FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; + FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; + FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; + FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; + FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; + FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; + FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; + FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; + FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; + FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; + FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; + FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; + FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; + FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; + FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; + FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; + FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; + FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; + FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; + FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; + FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; + FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; + FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; + FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; + FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; + FHoudiniApi::CreateHeightfieldInputNode = &FHoudiniApi::CreateHeightfieldInputNodeEmptyStub; + FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; + FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; + FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; + FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; + FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; + FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; + FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; + FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; + FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; + FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; + FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; + FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; + FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; + FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; + FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; + FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; + FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; + FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; + FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; + FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; + FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; + FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; + FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; + FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; + FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; + FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; + FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; + FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; + FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; + FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; + FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; + FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; + FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; + FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; + FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; + FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; + FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; + FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; + FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; + FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; + FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; + FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; + FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; + FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; + FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; + FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; + FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; + FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; + FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; + FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; + FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; + FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; + FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; + FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; + FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; + FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; + FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; + FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; + FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; + FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; + FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; + FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; + FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; + FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; + FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; + FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; + FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; + FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; + FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; + FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; + FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; + FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; + FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; + FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; + FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; + FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; + FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; + FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; + FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; + FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; + FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; + FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; + FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; + FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; + FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; + FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; + FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; + FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; + FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; + FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; + FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; + FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; + FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; + FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; + FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; + FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; + FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; + FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; + FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; + FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; + FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; + FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; + FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; + FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; + FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; + FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; + FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; + FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; + FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; + FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; + FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; + FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; + FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; + FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; + FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; + FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; + FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; + FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; + FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; + FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; + FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; + FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; + FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; + FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; + FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; + FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; + FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; + FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; + FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; + FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; + FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; + FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; + FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; + FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; + FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; + FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; + FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; + FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; + FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; + FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; + FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; + FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; + FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; + FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; + FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; + FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; + FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; + FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; + FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; + FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; + FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; + FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; + FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; + FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; + FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; + FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; + FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; + FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; + FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; + FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; + FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; + FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; + FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; + FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; + FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; + FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; + FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; + FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; + FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; + FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; + FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; + FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; + FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; + FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; + FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; + FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; + FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; + FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; + FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; + FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; + FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; + FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; + FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; + FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; + FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; + FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; + FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; + FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; + FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; + FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; + FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; + FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; + FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; + FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; + FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; + FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; + FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; + FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; + FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; + FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; + FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; + FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; + FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; + FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; + FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; + FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; + FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; + FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; + FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; + FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; + FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; + FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; + FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; + FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; + FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; + FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; + FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; + FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; + FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; + FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; + FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; + FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; + FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; + FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; + FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; + FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; + FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; + FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; + FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; + FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; + FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; + FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; + FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; + FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; + FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; + FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; + FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; + FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; + FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; + FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; + FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; + FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; + FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; + FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; + FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; + FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; + FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; + FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; + FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; + FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; + FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; +} + + +bool +FHoudiniApi::IsHAPIInitialized() +{ + return ( FHoudiniApi::IsInitialized != &FHoudiniApi::IsInitializedEmptyStub ); +} + + +HAPI_Result +FHoudiniApi::AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_AssetInfo +FHoudiniApi::AssetInfo_CreateEmptyStub() +{ + return HAPI_AssetInfo(); +} + + +void +FHoudiniApi::AssetInfo_InitEmptyStub(HAPI_AssetInfo * in) +{ + return; +} + + +HAPI_AttributeInfo +FHoudiniApi::AttributeInfo_CreateEmptyStub() +{ + return HAPI_AttributeInfo(); +} + + +void +FHoudiniApi::AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CleanupEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CloseSessionEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Bool +FHoudiniApi::CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right) +{ + return HAPI_Bool(); +} + + +HAPI_CookOptions +FHoudiniApi::CookOptions_CreateEmptyStub() +{ + return HAPI_CookOptions(); +} + + +void +FHoudiniApi::CookOptions_InitEmptyStub(HAPI_CookOptions * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateHeightfieldInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateInProcessSessionEmptyStub(HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_CurveInfo +FHoudiniApi::CurveInfo_CreateEmptyStub() +{ + return HAPI_CurveInfo(); +} + + +void +FHoudiniApi::CurveInfo_InitEmptyStub(HAPI_CurveInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DisconnectNodeOutputsAtEmptyStub(const HAPI_Session *session, HAPI_NodeId node_id, int output_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_GeoInfo +FHoudiniApi::GeoInfo_CreateEmptyStub() +{ + return HAPI_GeoInfo(); +} + + +int +FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type) +{ + return -1; +} + + +void +FHoudiniApi::GeoInfo_InitEmptyStub(HAPI_GeoInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstanceTransformsOnPartEmptyStub(const HAPI_Session *session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform *transforms_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo *parm_choices_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBatchEmptyStub(const HAPI_Session * session, char * char_array, int char_array_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int* string_buffer_size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetTimeEmptyStub(const HAPI_Session * session, float * time) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char* data_name, int * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_HandleBindingInfo +FHoudiniApi::HandleBindingInfo_CreateEmptyStub() +{ + return HAPI_HandleBindingInfo(); +} + + +void +FHoudiniApi::HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in) +{ + return; +} + + +HAPI_HandleInfo +FHoudiniApi::HandleInfo_CreateEmptyStub() +{ + return HAPI_HandleInfo(); +} + + +void +FHoudiniApi::HandleInfo_InitEmptyStub(HAPI_HandleInfo * in) +{ + return; +} + + +HAPI_ImageFileFormat +FHoudiniApi::ImageFileFormat_CreateEmptyStub() +{ + return HAPI_ImageFileFormat(); +} + + +void +FHoudiniApi::ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in) +{ + return; +} + + +HAPI_ImageInfo +FHoudiniApi::ImageInfo_CreateEmptyStub() +{ + return HAPI_ImageInfo(); +} + + +void +FHoudiniApi::ImageInfo_InitEmptyStub(HAPI_ImageInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::InterruptEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsInitializedEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsSessionValidEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Keyframe +FHoudiniApi::Keyframe_CreateEmptyStub() +{ + return HAPI_Keyframe(); +} + + +void +FHoudiniApi::Keyframe_InitEmptyStub(HAPI_Keyframe * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId* library_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_MaterialInfo +FHoudiniApi::MaterialInfo_CreateEmptyStub() +{ + return HAPI_MaterialInfo(); +} + + +void +FHoudiniApi::MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in) +{ + return; +} + + +HAPI_NodeInfo +FHoudiniApi::NodeInfo_CreateEmptyStub() +{ + return HAPI_NodeInfo(); +} + + +void +FHoudiniApi::NodeInfo_InitEmptyStub(HAPI_NodeInfo * in) +{ + return; +} + + +HAPI_ObjectInfo +FHoudiniApi::ObjectInfo_CreateEmptyStub() +{ + return HAPI_ObjectInfo(); +} + + +void +FHoudiniApi::ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in) +{ + return; +} + + +HAPI_ParmChoiceInfo +FHoudiniApi::ParmChoiceInfo_CreateEmptyStub() +{ + return HAPI_ParmChoiceInfo(); +} + + +void +FHoudiniApi::ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_ParmInfo +FHoudiniApi::ParmInfo_CreateEmptyStub() +{ + return HAPI_ParmInfo(); +} + + +int +FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in) +{ + return -1; +} + + +int +FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in) +{ + return -1; +} + + +int +FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in) +{ + return -1; +} + + +void +FHoudiniApi::ParmInfo_InitEmptyStub(HAPI_ParmInfo * in) +{ + return; +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_PartInfo +FHoudiniApi::PartInfo_CreateEmptyStub() +{ + return HAPI_PartInfo(); +} + + +int +FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) +{ + return -1; +} + + +int +FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) +{ + return -1; +} + + +int +FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type) +{ + return -1; +} + + +void +FHoudiniApi::PartInfo_InitEmptyStub(HAPI_PartInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session *session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session *session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveCustomStringEmptyStub(const HAPI_Session * session, const int string_handle) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo *attr_info, const char ** data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, int *handle_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTimeEmptyStub(const HAPI_Session * session, float time) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_TimelineOptions +FHoudiniApi::TimelineOptions_CreateEmptyStub() +{ + return HAPI_TimelineOptions(); +} + + +void +FHoudiniApi::TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in) +{ + return; +} + + +HAPI_TransformEuler +FHoudiniApi::TransformEuler_CreateEmptyStub() +{ + return HAPI_TransformEuler(); +} + + +void +FHoudiniApi::TransformEuler_InitEmptyStub(HAPI_TransformEuler * in) +{ + return; +} + + +HAPI_Transform +FHoudiniApi::Transform_CreateEmptyStub() +{ + return HAPI_Transform(); +} + + +void +FHoudiniApi::Transform_InitEmptyStub(HAPI_Transform * in) +{ + return; +} + + +HAPI_VolumeInfo +FHoudiniApi::VolumeInfo_CreateEmptyStub() +{ + return HAPI_VolumeInfo(); +} + + +void +FHoudiniApi::VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in) +{ + return; +} + + +HAPI_VolumeTileInfo +FHoudiniApi::VolumeTileInfo_CreateEmptyStub() +{ + return HAPI_VolumeTileInfo(); +} + + +void +FHoudiniApi::VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in) +{ + return; +} + + diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp new file mode 100644 index 00000000..41008a0c --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp @@ -0,0 +1,198 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniAsset.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "Misc/Paths.h" +#include "HoudiniEngineUtils.h" + +const uint32 +UHoudiniAsset::PersistenceFormatVersion = 2u; + +UHoudiniAsset::UHoudiniAsset( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) + , AssetFileName( TEXT( "" ) ) + , AssetBytes( nullptr ) + , AssetBytesCount( 0 ) + , FileFormatVersion( UHoudiniAsset::PersistenceFormatVersion ) + , HoudiniAssetFlagsPacked ( 0u ) +{} + +void +UHoudiniAsset::CreateAsset( const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName ) +{ + AssetFileName = InFileName; + + // Calculate buffer size. + AssetBytesCount = BufferEnd - BufferStart; + + if ( AssetBytesCount ) + { + // Allocate buffer to store OTL raw data. + AssetBytes = static_cast< uint8 * >( FMemory::Malloc( AssetBytesCount ) ); + + if ( AssetBytes ) + { + // Copy data into a newly allocated buffer. + FMemory::Memcpy( AssetBytes, BufferStart, AssetBytesCount ); + } + } + + FString FileExtension = FPaths::GetExtension( InFileName ); + + if ( FileExtension.Equals( TEXT( "hdalc" ), ESearchCase::IgnoreCase ) || + FileExtension.Equals( TEXT( "otlc" ), ESearchCase::IgnoreCase ) ) + { + bAssetLimitedCommercial = true; + } + else if ( FileExtension.Equals( TEXT( "hdanc" ), ESearchCase::IgnoreCase ) || + FileExtension.Equals( TEXT( "otlnc" ), ESearchCase::IgnoreCase ) ) + { + bAssetNonCommercial = true; + } +} + +const uint8 * +UHoudiniAsset::GetAssetBytes() const +{ + return AssetBytes; +} + +const FString & +UHoudiniAsset::GetAssetFileName() const +{ + return AssetFileName; +} + +uint32 +UHoudiniAsset::GetAssetBytesCount() const +{ + return AssetBytesCount; +} + +bool +UHoudiniAsset::IsPreviewHoudiniLogo() const +{ + return bPreviewHoudiniLogo; +} + +void +UHoudiniAsset::FinishDestroy() +{ + // Release buffer which was used to store raw OTL data. + if ( AssetBytes ) + { + FMemory::Free( AssetBytes ); + AssetBytes = nullptr; + AssetBytesCount = 0; + } + + Super::FinishDestroy(); +} + +void +UHoudiniAsset::Serialize( FArchive & Ar ) +{ + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + // Properties will get serialized. + + // Serialize persistence format version. + Ar << FileFormatVersion; + + Ar << AssetBytesCount; + + if ( Ar.IsLoading() ) + { + // If buffer was previously used, release it. + if ( AssetBytes ) + { + FMemory::Free( AssetBytes ); + AssetBytes = nullptr; + } + + // Allocate sufficient space to read stored raw OTL data. + if ( AssetBytesCount ) + AssetBytes = static_cast< uint8 * >( FMemory::Malloc( AssetBytesCount ) ); + } + + if ( AssetBytes && AssetBytesCount ) + Ar.Serialize( AssetBytes, AssetBytesCount ); + + // Serialize flags. + Ar << HoudiniAssetFlagsPacked; + + // Serialize asset file path. + Ar << AssetFileName; +} + +void +UHoudiniAsset::GetAssetRegistryTags( TArray< FAssetRegistryTag > & OutTags ) const +{ + OutTags.Add( FAssetRegistryTag( "FileName", AssetFileName, FAssetRegistryTag::TT_Alphabetical ) ); + OutTags.Add( + FAssetRegistryTag( "FileFormatVersion", FString::FromInt( FileFormatVersion ), + FAssetRegistryTag::TT_Numerical ) ); + OutTags.Add( FAssetRegistryTag( "Bytes", FString::FromInt( AssetBytesCount ), FAssetRegistryTag::TT_Numerical ) ); + + FString AssetType = TEXT( "Full" ); + + if ( bAssetLimitedCommercial ) + AssetType = TEXT( "Limited Commercial (LC)" ); + else if( bAssetNonCommercial ) + AssetType = TEXT( "Non Commercial (NC)" ); + + OutTags.Add( FAssetRegistryTag( "Asset Type", AssetType, FAssetRegistryTag::TT_Alphabetical ) ); + + Super::GetAssetRegistryTags( OutTags ); +} + +bool +UHoudiniAsset::IsAssetLimitedCommercial() const +{ + return bAssetLimitedCommercial; +} + +bool +UHoudiniAsset::IsAssetNonCommercial() const +{ + return bAssetNonCommercial; +} + +bool +UHoudiniAsset::GetAssetNames( HAPI_AssetLibraryId & AssetLibraryId, TArray< HAPI_StringHandle > & AssetNames ) +{ + return FHoudiniEngineUtils::GetAssetNames( this, AssetLibraryId, AssetNames ); +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp new file mode 100644 index 00000000..cf213bda --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp @@ -0,0 +1,166 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Damian Campeanu, Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniAssetActor.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" + +AHoudiniAssetActor::AHoudiniAssetActor( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) + , CurrentPlayTime( 0.0f ) +{ + SetCanBeDamaged(false); + PrimaryActorTick.bCanEverTick = true; + PrimaryActorTick.bStartWithTickEnabled = true; + + // Create Houdini component and attach it to a root component. + HoudiniAssetComponent = + ObjectInitializer.CreateDefaultSubobject< UHoudiniAssetComponent >( this, TEXT("HoudiniAssetComponent" ) ); + + HoudiniAssetComponent->SetCollisionProfileName( UCollisionProfile::BlockAll_ProfileName ); + RootComponent = HoudiniAssetComponent; +} + +UHoudiniAssetComponent * +AHoudiniAssetActor::GetHoudiniAssetComponent() const +{ + return HoudiniAssetComponent; +} + +bool +AHoudiniAssetActor::IsUsedForPreview() const +{ + return HasAnyFlags( RF_Transient ); +} + +void +AHoudiniAssetActor::ResetHoudiniCurrentPlaytime() +{ + CurrentPlayTime = 0.0f; +} + +float +AHoudiniAssetActor::GetHoudiniCurrentPlaytime() const +{ + return CurrentPlayTime; +} + +#if WITH_EDITOR + +bool +AHoudiniAssetActor::ShouldImport( FString * ActorPropString, bool IsMovingLevel ) +{ + if (!ActorPropString) + return false; + + // Locate actor which is being copied in clipboard string. + AHoudiniAssetActor * CopiedActor = FHoudiniEngineUtils::LocateClipboardActor(this, *ActorPropString ); + + // We no longer need clipboard string and can empty it. This seems to avoid occasional crash bug in UE4 which + // happens on copy / paste. + //ActorPropString->Empty(); + + if( !CopiedActor || CopiedActor->IsPendingKill() ) + { + HOUDINI_LOG_WARNING( TEXT("Failed to import from copy: Duplicated actor not found") ); + return false; + } + + // Get Houdini component of an actor which is being copied. + UHoudiniAssetComponent * CopiedActorHoudiniAssetComponent = CopiedActor->HoudiniAssetComponent; + if (!CopiedActorHoudiniAssetComponent || CopiedActorHoudiniAssetComponent->IsPendingKill() ) + return false; + + HoudiniAssetComponent->OnComponentClipboardCopy( CopiedActorHoudiniAssetComponent ); + + // If actor is copied through moving, we need to copy main transform. + const FTransform & ComponentWorldTransform = CopiedActorHoudiniAssetComponent->GetComponentTransform(); + HoudiniAssetComponent->SetWorldLocationAndRotation( + ComponentWorldTransform.GetLocation(), + ComponentWorldTransform.GetRotation() ); + + // We also need to copy actor label. + const FString & CopiedActorLabel = CopiedActor->GetActorLabel(); + FActorLabelUtilities::SetActorLabelUnique( this, CopiedActorLabel ); + + return true; +} + +bool +AHoudiniAssetActor::GetReferencedContentObjects( TArray< UObject * >& Objects ) const +{ + Super::GetReferencedContentObjects( Objects ); + + if ( HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill() ) + { + UHoudiniAsset* HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset(); + if ( HoudiniAsset && !HoudiniAsset->IsPendingKill() ) + Objects.AddUnique( HoudiniAsset ); + } + + return true; +} + + +bool +AHoudiniAssetActor::ShouldExport() +{ + return true; +} + +void +AHoudiniAssetActor::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + // Some property changes need to be forwarded to the component (ie Transform) + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + return; + + FProperty * Property = PropertyChangedEvent.MemberProperty; + if ( !Property ) + return; + + if ( ( Property->GetName() == TEXT( "RelativeLocation" ) ) + || ( Property->GetName() == TEXT( "RelativeRotation" ) ) + || ( Property->GetName() == TEXT( "RelativeScale3D" ) ) ) + { + // Transform has changed + HoudiniAssetComponent->CheckedUploadTransform(); + } +} + +#endif diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h new file mode 100644 index 00000000..107b4d14 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h @@ -0,0 +1,82 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Damian Campeanu, Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#pragma once + +#include "GameFramework/Actor.h" +#include "HoudiniAssetActor.generated.h" + +class UHoudiniAssetComponent; + +UCLASS( hidecategories = (Input), ConversionRoot, meta = (ChildCanTick) ) +class HOUDINIENGINERUNTIME_API AHoudiniAssetActor : public AActor +{ + GENERATED_UCLASS_BODY() + + UPROPERTY( Category = HoudiniAssetActor, VisibleAnywhere, BlueprintReadOnly, + meta = ( ExposeFunctionCategories = "Mesh,Rendering,Physics,Components|HoudiniAsset" ) ) + UHoudiniAssetComponent * HoudiniAssetComponent; + + public: + + /** Return true if this actor is used for preview. **/ + bool IsUsedForPreview() const; + + /** Return actor component. **/ + UHoudiniAssetComponent* GetHoudiniAssetComponent() const; + + /** Reset actor's playtime. This time is used to set time and cook when in playmode. **/ + void ResetHoudiniCurrentPlaytime(); + + /** Return actor's playtime. **/ + float GetHoudiniCurrentPlaytime() const; + + /** AActor methods. **/ + public: + +#if WITH_EDITOR + + virtual bool ShouldImport( FString * ActorPropString, bool IsMovingLevel ) override; + virtual bool ShouldExport() override; + + /** Used by the "Sync to Content Browser" right-click menu option in the editor. **/ + virtual bool GetReferencedContentObjects( TArray< UObject * >& Objects ) const; + + /** Called after a property has been changed **/ + virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; + +#endif + + protected: + + /** Used to track current playtime. **/ + float CurrentPlayTime; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp new file mode 100644 index 00000000..d4e3711f --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp @@ -0,0 +1,6701 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetComponent.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineMaterialUtils.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponentMaterials.h" +#include "HoudiniAssetInstanceInput.h" +#include "HoudiniAssetInput.h" +#include "HoudiniAssetParameter.h" +#include "HoudiniAssetParameterButton.h" +#include "HoudiniAssetParameterChoice.h" +#include "HoudiniAssetParameterColor.h" +#include "HoudiniAssetParameterFloat.h" +#include "HoudiniAssetParameterFolder.h" +#include "HoudiniAssetParameterFolderList.h" +#include "HoudiniAssetParameterInt.h" +#include "HoudiniAssetParameterLabel.h" +#include "HoudiniAssetParameterMultiparm.h" +#include "HoudiniAssetParameterSeparator.h" +#include "HoudiniAssetParameterString.h" +#include "HoudiniAssetParameterFile.h" +#include "HoudiniAssetParameterToggle.h" +#include "HoudiniAssetParameterRamp.h" +#include "HoudiniHandleComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniEngineTask.h" +#include "HoudiniEngineTaskInfo.h" +#include "HoudiniAssetComponentMaterials.h" +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniEngineString.h" +#include "HoudiniAssetInstanceInputField.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniParamUtils.h" +#include "HoudiniLandscapeUtils.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Landscape.h" +#include "Logging/MessageLog.h" +#include "Misc/UObjectToken.h" +#include "LandscapeInfo.h" +#include "LandscapeLayerInfoObject.h" +#include "Materials/MaterialInstance.h" +#include "Engine/StaticMeshSocket.h" +#include "HoudiniCookHandler.h" +#include "UObject/MetaData.h" +#if WITH_EDITOR +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" +#include "Editor.h" +#include "EdMode.h" +#include "EditorModeManager.h" +#include "EditorModes.h" +#include "Editor/PropertyEditor/Public/IDetailsView.h" +#include "Editor/UnrealEd/Private/GeomFitUtils.h" +#include "Misc/MessageDialog.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Editor/UnrealEd/Public/BusyCursor.h" +#include "Editor/UnrealEd/Public/AssetThumbnail.h" +#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" +#include "Editor/PropertyEditor/Private/PropertyNode.h" +#include "Editor/PropertyEditor/Private/SDetailsViewBase.h" +#include "StaticMeshResources.h" +#include "Framework/Application/SlateApplication.h" +#include "NavigationSystem.h" +#include "FileHelpers.h" +#endif +#include "Internationalization/Internationalization.h" + + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#if WITH_EDITOR + + /** Slate widget used to pick an asset to instantiate from an HDA with multiple assets inside. **/ +class SAssetSelectionWidget : public SCompoundWidget +{ + public: + SLATE_BEGIN_ARGS( SAssetSelectionWidget ) + : _WidgetWindow(), _AvailableAssetNames() + {} + + SLATE_ARGUMENT(TSharedPtr, WidgetWindow ) + SLATE_ARGUMENT( TArray< HAPI_StringHandle >, AvailableAssetNames ) + SLATE_END_ARGS() + + public: + + SAssetSelectionWidget(); + + public: + + /** Widget construct. **/ + void Construct( const FArguments & InArgs ); + + /** Return true if cancel button has been pressed. **/ + bool IsCancelled() const; + + /** Return true if constructed widget is valid. **/ + bool IsValidWidget() const; + + /** Return selected asset name. **/ + int32 GetSelectedAssetName() const; + + protected: + + /** Called when Ok button is pressed. **/ + FReply OnButtonOk(); + + /** Called when Cancel button is pressed. **/ + FReply OnButtonCancel(); + + /** Called when user picks an asset. **/ + FReply OnButtonAssetPick( int32 AssetName ); + + protected: + + /** Parent widget window. **/ + TWeakPtr< SWindow > WidgetWindow; + + /** List of available Houdini Engine asset names. **/ + TArray< HAPI_StringHandle > AvailableAssetNames; + + /** Selected asset name. **/ + int32 SelectedAssetName; + + /** Is set to true if constructed widget is valid. **/ + bool bIsValidWidget; + + /** Is set to true if selection process has been cancelled. **/ + bool bIsCancelled; +}; + +SAssetSelectionWidget::SAssetSelectionWidget() + : SelectedAssetName( -1 ) + , bIsValidWidget( false ) + , bIsCancelled( false ) +{} + +bool +SAssetSelectionWidget::IsCancelled() const +{ + return bIsCancelled; +} + +bool +SAssetSelectionWidget::IsValidWidget() const +{ + return bIsValidWidget; +} + +int32 +SAssetSelectionWidget::GetSelectedAssetName() const +{ + return SelectedAssetName; +} + +void +SAssetSelectionWidget::Construct( const FArguments & InArgs ) +{ + WidgetWindow = InArgs._WidgetWindow; + AvailableAssetNames = InArgs._AvailableAssetNames; + + TSharedRef< SVerticalBox > VerticalBox = SNew( SVerticalBox ); + + this->ChildSlot + [ + SNew( SBorder ) + .BorderImage( FEditorStyle::GetBrush( TEXT( "Menu.Background" ) ) ) + .Content() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .HAlign( HAlign_Center ) + .VAlign( VAlign_Center ) + [ + SAssignNew( VerticalBox, SVerticalBox ) + ] + ] + ]; + + for ( int32 AssetNameIdx = 0, AssetNameNum = AvailableAssetNames.Num(); AssetNameIdx < AssetNameNum; ++AssetNameIdx ) + { + FString AssetNameString = TEXT( "" ); + HAPI_StringHandle AssetName = AvailableAssetNames[ AssetNameIdx ]; + + FHoudiniEngineString HoudiniEngineString( AssetName ); + if ( HoudiniEngineString.ToFString( AssetNameString ) ) + { + bIsValidWidget = true; + FText AssetNameStringText = FText::FromString( AssetNameString ); + + VerticalBox->AddSlot() + .HAlign( HAlign_Center ) + .AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .HAlign( HAlign_Center ) + .VAlign( VAlign_Center ) + .Padding( 2.0f, 4.0f ) + [ + SNew( SButton ) + .VAlign( VAlign_Center ) + .HAlign( HAlign_Center ) + .OnClicked( this, &SAssetSelectionWidget::OnButtonAssetPick, AssetName ) + .Text( AssetNameStringText ) + .ToolTipText( AssetNameStringText ) + ] + ]; + } + } +} + +FReply +SAssetSelectionWidget::OnButtonAssetPick( int32 AssetName ) +{ + SelectedAssetName = AssetName; + + if (TSharedPtr WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + +FReply +SAssetSelectionWidget::OnButtonOk() +{ + if (TSharedPtr WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + +FReply +SAssetSelectionWidget::OnButtonCancel() +{ + bIsCancelled = true; + + if (TSharedPtr WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + +#endif + + +// Macro to update given property on all components. +#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 ) + +bool +UHoudiniAssetComponent::bDisplayEngineNotInitialized = true; + +bool +UHoudiniAssetComponent::bDisplayEngineHapiVersionMismatch = true; + +UHoudiniAssetComponent::UHoudiniAssetComponent( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{ + HoudiniAsset = nullptr; + bManualRecookRequested = false; + PreviousTransactionHoudiniAsset = nullptr; + HoudiniAssetComponentMaterials = nullptr; +#if WITH_EDITOR + CopiedHoudiniComponent = nullptr; +#endif + AssetId = -1; + GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION; + TransformScaleFactor = HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + ImportAxis = HRSAI_Unreal; + HapiNotificationStarted = 0.0; + AssetCookCount = 0; + HoudiniAssetComponentTransientFlagsPacked = 0u; + + /** Component flags. **/ + HoudiniAssetComponentFlagsPacked = 0u; + bEnableCooking = true; + bUploadTransformsToHoudiniEngine = true; + bUseHoudiniMaterials = true; + bCookingTriggersDownstreamCooks = true; + + UObject * Object = ObjectInitializer.GetObj(); + UObject * ObjectOuter = Object->GetOuter(); + + if ( ObjectOuter->IsA( AHoudiniAssetActor::StaticClass() ) ) + bIsNativeComponent = true; + + // Set scaling information. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if ( HoudiniRuntimeSettings ) + { + GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + TransformScaleFactor = HoudiniRuntimeSettings->TransformScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + } + + // Set component properties. + Mobility = EComponentMobility::Static; + PrimaryComponentTick.bCanEverTick = true; + bTickInEditor = true; + SetGenerateOverlapEvents(false); + + // Similar to UMeshComponent. + CastShadow = true; + bUseAsOccluder = true; + bCanEverAffectNavigation = true; + + // This component requires render update. + bNeverNeedsRenderUpdate = false; + + // Initialize static mesh generation parameters. + bGeneratedDoubleSidedGeometry = false; + GeneratedPhysMaterial = nullptr; + DefaultBodyInstance.SetCollisionProfileName("BlockAll"); + GeneratedCollisionTraceFlag = CTF_UseDefault; + GeneratedLpvBiasMultiplier = 1.0f; + GeneratedLightMapResolution = 32; + GeneratedLightMapCoordinateIndex = 1; + bGeneratedUseMaximumStreamingTexelRatio = false; + GeneratedStreamingDistanceMultiplier = 1.0f; + GeneratedDistanceFieldResolutionScale = 0.0f; + + bNeedToUpdateNavigationSystem = false; + + // Make an invalid GUID, since we do not have any cooking requests. + HapiGUID.Invalidate(); + + // Create unique component GUID. + ComponentGUID = FGuid::NewGuid(); + + bEditorPropertiesNeedFullUpdate = true; + + bFullyLoaded = false; + + Bounds = FBox( ForceInitToZero ); + + DefaultPresetBuffer.Empty(); + PresetBuffer.Empty(); + Parameters.Empty(); + ParameterByName.Empty(); + BakeNameOverrides.Empty(); + DownstreamAssetConnections.Empty(); +} + +UHoudiniAssetComponent::~UHoudiniAssetComponent() +{} + +void +UHoudiniAssetComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >( InThis ); + + if ( HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill() ) + { + // Add references for all parameters. + for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator + IterParams( HoudiniAssetComponent->Parameters ); IterParams; ++IterParams ) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if ( HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill() ) + Collector.AddReferencedObject( HoudiniAssetParameter, InThis ); + } + + // Add references to all inputs. + for ( TArray< UHoudiniAssetInput * >::TIterator + IterInputs( HoudiniAssetComponent->Inputs ); IterInputs; ++IterInputs ) + { + UHoudiniAssetInput * HoudiniAssetInput = *IterInputs; + if ( HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill() ) + Collector.AddReferencedObject( HoudiniAssetInput, InThis ); + } + + // Add references to all instance inputs. + for ( auto& InstanceInput : HoudiniAssetComponent->InstanceInputs) + { + if ( !InstanceInput || InstanceInput->IsPendingKill() ) + continue; + + Collector.AddReferencedObject( InstanceInput, InThis ); + } + + // Add references to all handles. + for ( TMap< FString, UHoudiniHandleComponent * >::TIterator + IterHandles( HoudiniAssetComponent->HandleComponents ); IterHandles; ++IterHandles ) + { + UHoudiniHandleComponent * HandleComponent = IterHandles.Value(); + if ( HandleComponent && !HandleComponent->IsPendingKill() ) + Collector.AddReferencedObject( HandleComponent, InThis ); + } + + // Add references to all static meshes and corresponding geo parts. + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator + Iter( HoudiniAssetComponent->StaticMeshes ); Iter; ++Iter ) + { + UStaticMesh * StaticMesh = Iter.Value(); + if ( StaticMesh && StaticMesh->IsValidLowLevel() && !StaticMesh->IsPendingKill() ) + Collector.AddReferencedObject( StaticMesh, InThis ); + } + + // Add references to all static meshes and their static mesh components. + for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TIterator + Iter( HoudiniAssetComponent->StaticMeshComponents ); Iter; ++Iter ) + { + UStaticMesh * StaticMesh = Iter.Key(); + UStaticMeshComponent * StaticMeshComponent = Iter.Value(); + + if (!StaticMesh || !StaticMeshComponent->IsValidLowLevel() || StaticMesh->IsPendingKill()) + continue; + + Collector.AddReferencedObject( StaticMesh, InThis ); + + if (!StaticMeshComponent || !StaticMeshComponent->IsValidLowLevel() || StaticMeshComponent->IsPendingKill()) + continue; + + Collector.AddReferencedObject( StaticMeshComponent, InThis ); + } + + // Add references to all temporary cooked mesh packages + for (TMap< FHoudiniGeoPartObject, TWeakObjectPtr > ::TIterator + Iter(HoudiniAssetComponent->CookedTemporaryStaticMeshPackages); Iter; ++Iter) + { + UPackage * MeshPackage = Iter.Value().Get(); + if (MeshPackage && !MeshPackage->IsPendingKill()) + Collector.AddReferencedObject(MeshPackage, InThis); + } + + // Add references to all spline components. + for ( TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* >::TIterator + Iter( HoudiniAssetComponent->SplineComponents ); Iter; ++Iter ) + { + UHoudiniSplineComponent * HoudiniSplineComponent = Iter.Value(); + if ( !HoudiniSplineComponent || !HoudiniSplineComponent->IsValidLowLevel() || HoudiniSplineComponent->IsPendingKill() ) + continue; + + Collector.AddReferencedObject( HoudiniSplineComponent, InThis ); + } + + // Add references to all Landscape + for ( TMap< FHoudiniGeoPartObject, TWeakObjectPtr >::TIterator + Iter( HoudiniAssetComponent->LandscapeComponents ); Iter; ++Iter) + { + ALandscapeProxy * HoudiniLandscape = Iter.Value().Get(); + if ( !HoudiniLandscape || HoudiniLandscape->IsPendingKill() || !HoudiniLandscape->IsValidLowLevel() ) + continue; + + Collector.AddReferencedObject( HoudiniLandscape, InThis ); + } + + // Add references to all generated Landscape layer objects + for ( TMap< TWeakObjectPtr, FHoudiniGeoPartObject > ::TIterator + Iter( HoudiniAssetComponent->CookedTemporaryLandscapeLayers ); Iter; ++Iter ) + { + UPackage * LayerPackage = Iter.Key().Get(); + if ( LayerPackage && !LayerPackage->IsPendingKill() ) + Collector.AddReferencedObject( LayerPackage, InThis ); + } + + // Retrieve asset associated with this component and add reference to it. + // Also do not add reference if it is being referenced by preview component. + UHoudiniAsset * HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset(); + if ( HoudiniAsset && !HoudiniAsset->IsPendingKill() && !HoudiniAssetComponent->bIsPreviewComponent ) + { + // Manually add a reference to Houdini asset from this component. + Collector.AddReferencedObject( HoudiniAsset, InThis ); + } + + // Add reference to material replacements. + UHoudiniAssetComponentMaterials* HoudiniAssetComponentMaterials = HoudiniAssetComponent->HoudiniAssetComponentMaterials; + if ( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() ) + Collector.AddReferencedObject( HoudiniAssetComponent->HoudiniAssetComponentMaterials, InThis ); + + // Add references to all temporary cooked material packages + for (TMap< FString, TWeakObjectPtr > ::TIterator + Iter(HoudiniAssetComponent->CookedTemporaryPackages); Iter; ++Iter) + { + UPackage * MaterialPackage = Iter.Value().Get(); + if (MaterialPackage && !MaterialPackage->IsPendingKill()) + Collector.AddReferencedObject(MaterialPackage, InThis); + } + + } + + // Call base implementation. + Super::AddReferencedObjects( InThis, Collector ); +} + +void +UHoudiniAssetComponent::SetNative( bool InbIsNativeComponent ) +{ + bIsNativeComponent = InbIsNativeComponent; +} + +HAPI_NodeId +UHoudiniAssetComponent::GetAssetId() const +{ + return AssetId; +} + +void +UHoudiniAssetComponent::SetAssetId( HAPI_NodeId InAssetId ) +{ + AssetId = InAssetId; +} + +bool +UHoudiniAssetComponent::HasValidAssetId() const +{ + return FHoudiniEngineUtils::IsHoudiniNodeValid( AssetId ); +} + +bool +UHoudiniAssetComponent::IsComponentValid() const +{ + if( !IsValidLowLevel() ) + return false; + + if ( IsTemplate() ) + return false; + + if ( IsPendingKillOrUnreachable() ) + return false; + + if ( !GetOuter() ) //|| !GetOuter()->GetLevel() ) + return false; + + return true; +} + +UHoudiniAsset * +UHoudiniAssetComponent::GetHoudiniAsset() const +{ + return HoudiniAsset; +} + + +AHoudiniAssetActor* +UHoudiniAssetComponent::GetHoudiniAssetActorOwner() const +{ + return Cast< AHoudiniAssetActor >( GetOwner() ); +} + +void +UHoudiniAssetComponent::SetHoudiniAsset( UHoudiniAsset * InHoudiniAsset ) +{ + // If it is the same asset, do nothing. + if ( InHoudiniAsset == HoudiniAsset ) + return; + + UHoudiniAsset * Asset = nullptr; + AHoudiniAssetActor * HoudiniAssetActor = Cast< AHoudiniAssetActor >( GetOwner() ); + + HoudiniAsset = InHoudiniAsset; + + // Reset material tracking. + if ( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() ) + HoudiniAssetComponentMaterials->ResetMaterialInfo(); + + if ( !bIsNativeComponent ) + return; + +#if WITH_EDITOR + + // Houdini Asset has been changed, we need to reset corresponding HDA and relevant resources. + ResetHoudiniResources(); + +#endif + + // Clear all created parameters. + ClearParameters(); + + // Clear all inputs. + ClearInputs(); + + // Clear all instance inputs. + ClearInstanceInputs(); + + // Release all curve related resources. + ClearCurves(); + + // Clear all handles. + ClearHandles(); + + // Clear all landscapes. + ClearLandscapes(); + + // Set Houdini logo to be default geometry. + ReleaseObjectGeoPartResources( StaticMeshes ); + StaticMeshes.Empty(); + StaticMeshComponents.Empty(); + CreateStaticMeshHoudiniLogoResource( StaticMeshes ); + + bIsPreviewComponent = false; + if ( !InHoudiniAsset ) + { +#if WITH_EDITOR + UpdateEditorProperties( false ); +#endif + return; + } + + if ( HoudiniAssetActor ) + bIsPreviewComponent = HoudiniAssetActor->IsUsedForPreview(); + + if( !bIsNativeComponent ) + bLoadedComponent = false; + + // Get instance of Houdini Engine. + FHoudiniEngine & HoudiniEngine = FHoudiniEngine::Get(); + +#if WITH_EDITOR + + if ( !bIsPreviewComponent ) + { + // If the fist session on the engine was never initialized, do it now + if (!FHoudiniEngine::Get().GetFirstSessionCreated()) + { + HOUDINI_LOG_MESSAGE(TEXT("First initialization of the Houdini Engine session")); + FHoudiniEngine::Get().RestartSession(); + } + + if ( HoudiniEngine.IsInitialized() ) + { + if ( !bLoadedComponent ) + { + // If component is not a loaded component, instantiate and start ticking. + StartTaskAssetInstantiation( false, true ); + } + else if ( bTransactionAssetChange ) + { + // If assigned asset is transacted asset, instantiate and start ticking. Also treat as loaded component. + StartTaskAssetInstantiation( true, true ); + } + } + else + { + if ( UHoudiniAssetComponent::bDisplayEngineHapiVersionMismatch && HoudiniEngine.CheckHapiVersionMismatch() ) + { + // We have mismatch in defined and running versions. + int32 RunningEngineMajor = 0; + int32 RunningEngineMinor = 0; + int32 RunningEngineApi = 0; + + const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); + + // Retrieve version numbers for running Houdini Engine. + FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_MAJOR, &RunningEngineMajor ); + FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_MINOR, &RunningEngineMinor ); + FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_API, &RunningEngineApi ); + + // Get platform specific name of libHAPI. + FString LibHAPIName = FHoudiniEngineUtils::HoudiniGetLibHAPIName(); + + // Some sanity checks. + int32 BuiltEngineMajor = FMath::Max( 0, HAPI_VERSION_HOUDINI_ENGINE_MAJOR ); + int32 BuiltEngineMinor = FMath::Max( 0, HAPI_VERSION_HOUDINI_ENGINE_MINOR ); + int32 BuiltEngineApi = FMath::Max( 0, HAPI_VERSION_HOUDINI_ENGINE_API ); + + FString WarningMessage = FString::Printf( + TEXT( "Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d mismatch. " ) + TEXT( "%s was loaded, but has wrong version. " ) + TEXT( "No cooking / instantiation will take place." ), + BuiltEngineMajor, + BuiltEngineMinor, + BuiltEngineApi, + RunningEngineMajor, + RunningEngineMinor, + RunningEngineApi, + *LibHAPIName ); + + FString WarningTitle = TEXT( "Houdini Engine Plugin Warning" ); + FText WarningTitleText = FText::FromString( WarningTitle ); + FMessageDialog::Debugf( FText::FromString( WarningMessage ), &WarningTitleText ); + + UHoudiniAssetComponent::bDisplayEngineHapiVersionMismatch = false; + } + else if ( UHoudiniAssetComponent::bDisplayEngineNotInitialized ) + { + // Get platform specific name of libHAPI. + FString LibHAPIName = FHoudiniEngineUtils::HoudiniGetLibHAPIName(); + + // If this is first time component is instantiated and we do not have Houdini Engine initialized. + FString WarningTitle = TEXT( "Houdini Engine Plugin Warning" ); + FText WarningTitleText = FText::FromString( WarningTitle ); + FString WarningMessage = FString::Printf( + TEXT( "Houdini Installation was not detected." ) + TEXT( "Failed to locate or load %s. " ) + TEXT( "No cooking / instantiation will take place." ), + *LibHAPIName ); + + FMessageDialog::Debugf( FText::FromString( WarningMessage ), &WarningTitleText ); + + UHoudiniAssetComponent::bDisplayEngineNotInitialized = false; + } + } + } + +#endif +} + +void +UHoudiniAssetComponent::AddDownstreamAsset( UHoudiniAssetComponent * InDownstreamAssetComponent, int32 InInputIndex ) +{ + if ( InDownstreamAssetComponent && !InDownstreamAssetComponent->IsPendingKill() ) + { + if ( DownstreamAssetConnections.Contains( InDownstreamAssetComponent ) ) + { + TSet< int32 > & InputIndicesSet = DownstreamAssetConnections[ InDownstreamAssetComponent ]; + InputIndicesSet.Add( InInputIndex ); + } + else + { + TSet< int32 > InputIndicesSet; + InputIndicesSet.Add( InInputIndex ); + DownstreamAssetConnections.Add( InDownstreamAssetComponent, InputIndicesSet ); + } + } +} + +void +UHoudiniAssetComponent::RemoveDownstreamAsset( UHoudiniAssetComponent * InDownstreamAssetComponent, int32 InInputIndex ) +{ + if ( !InDownstreamAssetComponent || InDownstreamAssetComponent->IsPendingKill() ) + return; + + if ( DownstreamAssetConnections.Contains( InDownstreamAssetComponent ) ) + { + TSet< int32 > & InputIndicesSet = DownstreamAssetConnections[ InDownstreamAssetComponent ]; + if ( InputIndicesSet.Contains( InInputIndex ) ) + InputIndicesSet.Remove( InInputIndex ); + + // Remove the downstream asset if we're not link to at least one of its input + if (InputIndicesSet.Num() <= 0) + DownstreamAssetConnections.Remove(InDownstreamAssetComponent); + } +} + +void +UHoudiniAssetComponent::CreateObjectGeoPartResources( + TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshMap ) +{ + // Reset Houdini logo flag. + bContainsHoudiniLogoGeometry = false; + + // We need to store instancers as they need to be processed after all other meshes. + TArray< FHoudiniGeoPartObject > FoundInstancers; + TArray< FHoudiniGeoPartObject > FoundCurves; + TArray< FHoudiniGeoPartObject > FoundVolumes; + TMap< FHoudiniGeoPartObject, UStaticMesh* > StaleParts; + + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshMap ); Iter; ++Iter ) + { + const FHoudiniGeoPartObject HoudiniGeoPartObject = Iter.Key(); + UStaticMesh * StaticMesh = Iter.Value(); + + if ( HoudiniGeoPartObject.IsInstancer() ) + { + FoundInstancers.Add( HoudiniGeoPartObject ); + } + else if ( HoudiniGeoPartObject.IsPackedPrimitiveInstancer() ) + { + // Packed Primitives should be processed before other instancer in case they are instanced by the same + FoundInstancers.Insert( HoudiniGeoPartObject, 0 ); + } + else if ( HoudiniGeoPartObject.IsCurve() ) + { + // This geo part is a curve and has no mesh assigned. + check( !StaticMesh ); + FoundCurves.Add( HoudiniGeoPartObject ); + } + else if ( HoudiniGeoPartObject.IsVolume() ) + { + FoundVolumes.Add( HoudiniGeoPartObject ); + } + else + { + // This geo part is visible and not an instancer and must have static mesh assigned. + if ( HoudiniGeoPartObject.IsVisible() && ( !StaticMesh || StaticMesh->IsPendingKill() ) ) + { + HOUDINI_LOG_WARNING( TEXT( "No static mesh generated for visible part %d,%d,%d" ), HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId, HoudiniGeoPartObject.PartId ); + continue; + } + + UStaticMeshComponent * StaticMeshComponent = nullptr; + UStaticMeshComponent * FoundStaticMeshComponent = LocateStaticMeshComponent( StaticMesh ); + + if ( FoundStaticMeshComponent && !FoundStaticMeshComponent->IsPendingKill() ) + { + StaticMeshComponent = FoundStaticMeshComponent; + if ( !HoudiniGeoPartObject.IsVisible() && !HoudiniGeoPartObject.IsCollidable()) + { + // We have a mesh and component for a part which is invisible. + // Visibility may have changed since last cook + StaleParts.Add( HoudiniGeoPartObject, StaticMesh ); + continue; + } + } + else if ( HoudiniGeoPartObject.IsVisible() || HoudiniGeoPartObject.IsCollidable()) + { + // Create necessary component. + StaticMeshComponent = NewObject< UStaticMeshComponent >( + GetOwner() ? GetOwner() : GetOuter(), UStaticMeshComponent::StaticClass(), + NAME_None, RF_Transactional ); + + if ( StaticMeshComponent && !StaticMeshComponent->IsPendingKill() ) + { + StaticMeshComponent->SetStaticMesh(StaticMesh); + StaticMeshComponent->SetVisibility(HoudiniGeoPartObject.IsVisible()); + StaticMeshComponent->SetMobility(Mobility); + + // Property propagation: set the new SMC's properties to the HAC's current settings + CopyComponentPropertiesTo(StaticMeshComponent); + + // Register the component AFTER copying the properties! + StaticMeshComponent->RegisterComponent(); + + // Attach created static mesh component to our Houdini component. + StaticMeshComponent->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + + // Add to the map of components. + StaticMeshComponents.Add(StaticMesh, StaticMeshComponent); + } + } + + // No need to change visible/collision/uproperties if we're not fully loaded, + // as this likely means that we're in the process of being deserialized... + if (bFullyLoaded && StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + { + // If this is a collision geo, we need to make it invisible. + if (HoudiniGeoPartObject.IsCollidable()) + { + StaticMeshComponent->SetVisibility( false ); + StaticMeshComponent->SetHiddenInGame( true ); + StaticMeshComponent->SetCollisionProfileName( FName( TEXT( "InvisibleWall" ) ) ); + StaticMeshComponent->SetCastShadow(false); + } + else + { + // Visibility may have changed so we still need to update it + StaticMeshComponent->SetVisibility( HoudiniGeoPartObject.IsVisible() ); + StaticMeshComponent->SetHiddenInGame( !HoudiniGeoPartObject.IsVisible() ); + } + + // And we will need to update the navmesh later + if( HoudiniGeoPartObject.IsCollidable() || HoudiniGeoPartObject.IsRenderCollidable() ) + bNeedToUpdateNavigationSystem = true; + + // Transform the component by transformation provided by HAPI. + StaticMeshComponent->SetRelativeTransform( HoudiniGeoPartObject.TransformMatrix ); + + // If the static mesh had sockets, we can assign the desired actor to them now + int32 NumberOfSockets = StaticMesh == nullptr ? 0 : StaticMesh->Sockets.Num(); + for( int32 nSocket = 0; nSocket < NumberOfSockets; nSocket++ ) + { + UStaticMeshSocket* MeshSocket = StaticMesh->Sockets[ nSocket ]; + if ( MeshSocket && !MeshSocket->IsPendingKill() && ( MeshSocket->Tag.IsEmpty() ) ) + continue; + + FHoudiniEngineUtils::AddActorsToMeshSocket( StaticMesh->Sockets[ nSocket ], StaticMeshComponent ); + } + + // Try to update uproperty atributes + FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject( StaticMeshComponent, HoudiniGeoPartObject ); + } + } + } + + if ( StaleParts.Num() ) + { + for ( auto Iter : StaleParts ) + { + StaticMeshMap.Remove( Iter.Key ); + } + ReleaseObjectGeoPartResources( StaleParts, true ); + } + + // Skip self assignment. + if ( &StaticMeshes != &StaticMeshMap ) + StaticMeshes = StaticMeshMap; + +#if WITH_EDITOR + if ( FHoudiniEngineUtils::IsHoudiniNodeValid( AssetId ) ) + { + // Create necessary instance inputs. + CreateInstanceInputs( FoundInstancers ); + + // Create necessary curves. + CreateCurves( FoundCurves ); + + // Create necessary landscapes + CreateAllLandscapes( FoundVolumes ); + } +#endif + + CleanUpAttachedStaticMeshComponents(); + + // Now that all the Meshes/Landscapes are created, see if we need to create material instances from attributes + CreateOrUpdateMaterialInstances(); + + // Mobility will be set to static if we generated a Landscape, + // Or to movable if any of our children is movable + UpdateMobility(); +} + + +void +UHoudiniAssetComponent::ReleaseObjectGeoPartResources( bool bDeletePackages ) +{ + ReleaseObjectGeoPartResources( StaticMeshes, bDeletePackages ); +} + +void +UHoudiniAssetComponent::ReleaseObjectGeoPartResources( + TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshMap, + bool bDeletePackages, + TMap< FHoudiniGeoPartObject, UStaticMesh * > * pKeepIfContainedIn ) +{ + // Record generated static meshes which we need to delete. + TArray< UStaticMesh * > StaticMeshesToDelete; + + // Get Houdini logo. + UStaticMesh * HoudiniLogoMesh = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); + + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshMap ); Iter; ++Iter ) + { + // If we find the new mesh in the existing list, don't destroy the existing static mesh slot + if ( pKeepIfContainedIn ) + { + const FHoudiniGeoPartObject& GeoPart = Iter.Key(); + if (LocateStaticMeshByNames(GeoPart, *pKeepIfContainedIn)) + { + // The old static mesh was found in the new list, no need to destroy it + continue; + } + } + + UStaticMesh * StaticMesh = Iter.Value(); + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + continue; + + // Removes the static mesh component from the map, detaches and destroys it. + RemoveStaticMeshComponent( StaticMesh ); + + if ( bDeletePackages && ( StaticMesh != HoudiniLogoMesh ) ) + { + // Make sure this static mesh is not referenced. + UObject * ObjectMesh = (UObject *) StaticMesh; + FReferencerInformationList Referencers; + + // Check if object is referenced and get its referencers, if it is. + bool bReferenced = IsReferenced( + ObjectMesh, GARBAGE_COLLECTION_KEEPFLAGS, + EInternalObjectFlags::GarbageCollectionKeepFlags, true, &Referencers ); + + if ( !bReferenced || IsObjectReferencedLocally( StaticMesh, Referencers ) ) + { + // Only delete generated mesh if it has not been saved manually. + UPackage * Package = Cast< UPackage >( StaticMesh->GetOuter() ); + if ( !Package || !Package->bHasBeenFullyLoaded ) + StaticMeshesToDelete.Add(StaticMesh); + } + } + } + + // CleanUpAttachedStaticMeshComponents(); + + // Remove unused meshes. + StaticMeshMap.Empty(); + +#if WITH_EDITOR // Delete no longer used generated static meshes. + int32 MeshNum = StaticMeshesToDelete.Num(); + + if ( bDeletePackages && MeshNum > 0 ) + { + for ( int32 MeshIdx = 0; MeshIdx < MeshNum; ++MeshIdx ) + { + UStaticMesh * StaticMesh = StaticMeshesToDelete[ MeshIdx ]; + + // Free any RHI resources. + StaticMesh->PreEditChange( nullptr ); + StaticMesh->MarkPendingKill(); + + ObjectTools::DeleteSingleObject( StaticMesh, false ); + } + } + +#endif +} + +void +UHoudiniAssetComponent::CleanUpAttachedStaticMeshComponents() +{ + // Record generated static meshes which we need to delete. + TArray< UStaticMesh * > StaticMeshesToDelete; + + // Collect all the static mesh component for this asset + TMap AllSMC = CollectAllStaticMeshComponents(); + + // We'll check all the children static mesh components for junk + const auto & LocalAttachChildren = GetAttachChildren(); + for (TArray< USceneComponent * >::TConstIterator Iter( LocalAttachChildren ); Iter; ++Iter) + { + UStaticMeshComponent * StaticMeshComponent = Cast< UStaticMeshComponent >(*Iter); + if ( !StaticMeshComponent || StaticMeshComponent->IsPendingKill() ) + continue; + + bool bNeedToCleanMeshComponent = false; + UStaticMesh * StaticMesh = StaticMeshComponent->GetStaticMesh(); + + if (AllSMC.Find(StaticMeshComponent) == nullptr) + bNeedToCleanMeshComponent = true; + + // Do not clean up component attached to a socket + if ( StaticMeshComponent->GetAttachSocketName() != NAME_None ) + bNeedToCleanMeshComponent = false; + + if ( bNeedToCleanMeshComponent ) + { + // This StaticMeshComponent is attached to the asset but not in the map, and not an instance. + // It may be a leftover from previous cook/undo/redo and needs to be properly destroyed + StaticMeshComponent->DetachFromComponent( FDetachmentTransformRules::KeepRelativeTransform ); + StaticMeshComponent->UnregisterComponent(); + StaticMeshComponent->DestroyComponent(); + + StaticMeshesToDelete.Add( StaticMesh ); + + //HOUDINI_LOG_WARNING( TEXT("CLEANUP: Deleted extra Static Mesh Component for %s"), *(StaticMesh->GetName()) ); + } + } + +#if WITH_EDITOR + // We'll try to delete the undesirable static meshes too + for (int32 MeshIdx = 0; MeshIdx < StaticMeshesToDelete.Num(); ++MeshIdx) + { + UStaticMesh * StaticMesh = StaticMeshesToDelete[MeshIdx]; + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + continue; + + UObject * ObjectMesh = (UObject *)StaticMesh; + if ( ObjectMesh->IsUnreachable() ) + continue; + + // Check if object is referenced and get its referencers, if it is. + FReferencerInformationList Referencers; + bool bReferenced = IsReferenced( + ObjectMesh, GARBAGE_COLLECTION_KEEPFLAGS, + EInternalObjectFlags::GarbageCollectionKeepFlags, true, &Referencers); + + if ( !bReferenced || IsObjectReferencedLocally( StaticMesh, Referencers ) ) + { + // Only delete generated mesh if it has not been saved manually. + UPackage * Package = Cast< UPackage >( StaticMesh->GetOuter() ); + if ( !Package || !Package->bHasBeenFullyLoaded ) + { + StaticMesh->PreEditChange( nullptr ); + ObjectTools::DeleteSingleObject( StaticMesh, false ); + } + } + } +#endif +} + +bool +UHoudiniAssetComponent::IsObjectReferencedLocally( + UStaticMesh * StaticMesh, + FReferencerInformationList & Referencers ) const +{ + if ( Referencers.InternalReferences.Num() == 0 && Referencers.ExternalReferences.Num() == 1 ) + { + const FReferencerInformation & ExternalReferencer = Referencers.ExternalReferences[ 0 ]; + if ( ExternalReferencer.Referencer == this ) + { + // Only referencer is this component. + return true; + } + } + + return false; +} + +void +UHoudiniAssetComponent::CollectSubstanceParameters( TMap< FString, UHoudiniAssetParameter * > & SubstanceParameters ) const +{ + SubstanceParameters.Empty(); + + for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TConstIterator IterParams( Parameters ); IterParams; ++IterParams ) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill() ) + continue; + + if ( HoudiniAssetParameter->IsSubstanceParameter() ) + SubstanceParameters.Add( HoudiniAssetParameter->GetParameterName(), HoudiniAssetParameter ); + } +} + +void +UHoudiniAssetComponent::CollectAllParametersOfType( + UClass * ParameterClass, + TMap< FString, UHoudiniAssetParameter * > & ClassParameters ) const +{ + ClassParameters.Empty(); + + for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TConstIterator IterParams( Parameters ); IterParams; ++IterParams ) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill()) + continue; + + if ( HoudiniAssetParameter->IsA( ParameterClass ) ) + ClassParameters.Add( HoudiniAssetParameter->GetParameterName(), HoudiniAssetParameter ); + } +} + +UHoudiniAssetParameter * +UHoudiniAssetComponent::FindParameter( const FString & ParameterName ) const +{ + UHoudiniAssetParameter * const * FoundHoudiniAssetParameter = ParameterByName.Find( ParameterName ); + UHoudiniAssetParameter * HoudiniAssetParameter = nullptr; + + if ( FoundHoudiniAssetParameter ) + HoudiniAssetParameter = *FoundHoudiniAssetParameter; + + if ( !HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill() ) + return nullptr; + + return HoudiniAssetParameter; +} + +void +UHoudiniAssetComponent::GetInputs( TArray< UHoudiniAssetInput* >& AllInputs, bool IncludeObjectPathParameter ) +{ + AllInputs.Empty(); + + for ( TArray< UHoudiniAssetInput * >::TIterator IterInputs( Inputs ); IterInputs; ++IterInputs ) + { + UHoudiniAssetInput * HoudiniAssetInput = *IterInputs; + if ( !HoudiniAssetInput || HoudiniAssetInput->IsPendingKill() ) + continue; + + AllInputs.Add( HoudiniAssetInput ); + } + + if ( !IncludeObjectPathParameter ) + return; + + for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TConstIterator IterParams( Parameters ); IterParams; ++IterParams ) + { + UHoudiniAssetInput* ObjectPathInput = Cast< UHoudiniAssetInput >( IterParams.Value() ); + if ( !ObjectPathInput || ObjectPathInput->IsPendingKill() ) + continue; + + AllInputs.Add ( ObjectPathInput ); + } +} + +void +UHoudiniAssetComponent::GetAllUsedStaticMeshes( TArray< UStaticMesh * > & UsedStaticMeshes ) +{ + UsedStaticMeshes.Empty(); + + // Add all static meshes. + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshes ); Iter; ++Iter ) + { + UStaticMesh * StaticMesh = Iter.Value(); + if ( StaticMesh && !StaticMesh->IsPendingKill() ) + UsedStaticMeshes.Add( StaticMesh ); + } +} + +TMap +UHoudiniAssetComponent::CollectAllStaticMeshComponents() const +{ + TMap OutSMComponentToPart; + + // Add all the instance meshes, including the variations + for ( const UHoudiniAssetInstanceInput* InstanceInput : InstanceInputs ) + { + if ( !InstanceInput || InstanceInput->IsPendingKill() ) + continue; + + for ( const UHoudiniAssetInstanceInputField* InputField : InstanceInput->GetInstanceInputFields() ) + { + if ( !InputField || InputField->IsPendingKill() ) + continue; + + for ( int32 VarIndex = 0; VarIndex < InputField->InstanceVariationCount(); ++VarIndex ) + { + UObject* Comp = InputField->GetInstancedComponent( VarIndex ); + if ( Comp && InputField->GetInstanceVariation( VarIndex ) && Comp->IsA() ) + { + OutSMComponentToPart.Add( Cast( Comp ), InputField->GetHoudiniGeoPartObject() ); + } + } + } + } + + // add all the plain UStaticMeshComponent + for ( const auto& MeshPart : GetStaticMeshes() ) + { + if ( UStaticMeshComponent * SMC = LocateStaticMeshComponent( MeshPart.Value ) ) + { + OutSMComponentToPart.Add( SMC, MeshPart.Key ); + } + } + + return OutSMComponentToPart; +} + +TMap +UHoudiniAssetComponent::CollectAllInstancedActorComponents() const +{ + TMap OutSMComponentToPart; + for ( const UHoudiniAssetInstanceInput* InstanceInput : InstanceInputs ) + { + if ( !InstanceInput || InstanceInput->IsPendingKill() ) + continue; + + for ( const UHoudiniAssetInstanceInputField* InputField : InstanceInput->GetInstanceInputFields() ) + { + if ( !InputField || InputField->IsPendingKill() ) + continue; + + for ( int32 VarIndex = 0; VarIndex < InputField->InstanceVariationCount(); ++VarIndex ) + { + UObject* Comp = InputField->GetInstancedComponent( VarIndex ); + if (!Comp || Comp->IsPendingKill()) + continue; + + if ( InputField->GetInstanceVariation( VarIndex ) && Comp->IsA() ) + { + OutSMComponentToPart.Add( Cast( Comp ), InputField->GetHoudiniGeoPartObject() ); + } + } + } + } + return OutSMComponentToPart; +} + +TMap +UHoudiniAssetComponent::CollectAllMeshSplitInstancerComponents() const +{ + TMap OutSMComponentToPart; + for( const UHoudiniAssetInstanceInput* InstanceInput : InstanceInputs ) + { + if (!InstanceInput || InstanceInput->IsPendingKill()) + continue; + + for( const UHoudiniAssetInstanceInputField* InputField : InstanceInput->GetInstanceInputFields() ) + { + if ( !InputField || InputField->IsPendingKill() ) + continue; + + for( int32 VarIndex = 0; VarIndex < InputField->InstanceVariationCount(); ++VarIndex ) + { + UObject* Comp = InputField->GetInstancedComponent(VarIndex); + if (!Comp || Comp->IsPendingKill()) + continue; + + if( InputField->GetInstanceVariation(VarIndex) && Comp->IsA() ) + { + OutSMComponentToPart.Add(Cast(Comp), InputField->GetHoudiniGeoPartObject()); + } + } + } + } + return OutSMComponentToPart; +} + +bool +UHoudiniAssetComponent::CheckGlobalSettingScaleFactors() const +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if ( HoudiniRuntimeSettings ) + { + return ( GeneratedGeometryScaleFactor == HoudiniRuntimeSettings->GeneratedGeometryScaleFactor && + TransformScaleFactor == HoudiniRuntimeSettings->TransformScaleFactor ); + } + + return ( GeneratedGeometryScaleFactor == HAPI_UNREAL_SCALE_FACTOR_POSITION && + TransformScaleFactor == HAPI_UNREAL_SCALE_FACTOR_TRANSLATION ); +} + +#if WITH_EDITOR + +bool +UHoudiniAssetComponent::IsInstantiatingOrCooking() const +{ + return HapiGUID.IsValid(); +} + +bool +UHoudiniAssetComponent::HasBeenInstantiatedButNotCooked() const +{ + return ( FHoudiniEngineUtils::IsHoudiniNodeValid( AssetId ) && ( 0 == AssetCookCount ) ); +} + +void +UHoudiniAssetComponent::AssignUniqueActorLabel() +{ + if ( GEditor && FHoudiniEngineUtils::IsValidNodeId( AssetId ) ) + { + AHoudiniAssetActor * HoudiniAssetActor = GetHoudiniAssetActorOwner(); + if ( HoudiniAssetActor ) + { + FString UniqueName; + if ( FHoudiniEngineUtils::GetHoudiniAssetName( AssetId, UniqueName ) ) + FActorLabelUtilities::SetActorLabelUnique( HoudiniAssetActor, UniqueName ); + } + } +} + +void +UHoudiniAssetComponent::StartHoudiniUIUpdateTicking() +{ + // If we have no timer delegate spawned for ui update, spawn one. + if ( !TimerDelegateUIUpdate.IsBound() && GEditor ) + { + TimerDelegateUIUpdate = FTimerDelegate::CreateUObject( this, &UHoudiniAssetComponent::TickHoudiniUIUpdate ); + + // We need to register delegate with the timer system. + static const float TickTimerDelay = 0.25f; + GEditor->GetTimerManager()->SetTimer( TimerHandleUIUpdate, TimerDelegateUIUpdate, TickTimerDelay, true ); + } +} + +void +UHoudiniAssetComponent::StopHoudiniUIUpdateTicking() +{ + if ( TimerDelegateUIUpdate.IsBound() && GEditor ) + { + GEditor->GetTimerManager()->ClearTimer( TimerHandleUIUpdate ); + TimerDelegateUIUpdate.Unbind(); + } +} + +void +UHoudiniAssetComponent::TickHoudiniUIUpdate() +{ + UpdateEditorProperties( true ); +} + +void +UHoudiniAssetComponent::StartHoudiniTicking() +{ + // If we have no timer delegate spawned for this component, spawn one. + if ( !TimerDelegateCooking.IsBound() && GEditor ) + { + TimerDelegateCooking = FTimerDelegate::CreateUObject( this, &UHoudiniAssetComponent::TickHoudiniComponent ); + + // We need to register delegate with the timer system. + static const float TickTimerDelay = 0.25f; + GEditor->GetTimerManager()->SetTimer( TimerHandleCooking, TimerDelegateCooking, TickTimerDelay, true ); + + // Grab current time for delayed notification. + HapiNotificationStarted = FPlatformTime::Seconds(); + } + + // If the fist session of houdini engine hasnt been initialized yet, do it now + if (!FHoudiniEngine::Get().GetFirstSessionCreated()) + { + HOUDINI_LOG_MESSAGE(TEXT("First initialization of the Houdini Engine session")); + FHoudiniEngine::Get().RestartSession(); + } +} + +void +UHoudiniAssetComponent::StopHoudiniTicking() +{ + if ( TimerDelegateCooking.IsBound() && GEditor ) + { + GEditor->GetTimerManager()->ClearTimer( TimerHandleCooking ); + TimerDelegateCooking.Unbind(); + + // Reset time for delayed notification. + HapiNotificationStarted = 0.0; + } +} + +void +UHoudiniAssetComponent::PostCook( bool bCookError ) +{ + // Show busy cursor. + FScopedBusyCursor ScopedBusyCursor; + + // Create parameters and inputs. + CreateParameters(); + CreateInputs(); + CreateHandles(); + + if ( bCookError ) + { + // We need to reset the manual recook flag here to avoid endless cooking + bManualRecookRequested = false; + return; + } + + FTransform ComponentTransform; + TMap< FHoudiniGeoPartObject, UStaticMesh * > NewStaticMeshes; + + FHoudiniCookParams HoudiniCookParams( this ); + HoudiniCookParams.StaticMeshBakeMode = FHoudiniCookParams::GetDefaultStaticMeshesCookMode(); + HoudiniCookParams.MaterialAndTextureBakeMode = FHoudiniCookParams::GetDefaultMaterialAndTextureCookMode(); + + if ( FHoudiniEngineUtils::CreateStaticMeshesFromHoudiniAsset( + GetAssetId(), + HoudiniCookParams, + !CheckGlobalSettingScaleFactors(), + bManualRecookRequested, + StaticMeshes, + NewStaticMeshes, + ComponentTransform ) ) + { + // Remove all duplicates. After this operation, old map will have meshes which we need to deallocate. + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator + Iter( NewStaticMeshes ); Iter; ++Iter ) + { + const FHoudiniGeoPartObject HoudiniGeoPartObject = Iter.Key(); + UStaticMesh * StaticMesh = Iter.Value(); + + // Removes the mesh from previous map of meshes + UStaticMesh * FoundOldStaticMesh = LocateStaticMesh( HoudiniGeoPartObject ); + if (!FoundOldStaticMesh) + FoundOldStaticMesh = LocateStaticMeshByNames(HoudiniGeoPartObject, StaticMeshes); + + if ( ( FoundOldStaticMesh ) && ( FoundOldStaticMesh == StaticMesh ) ) + { + // Mesh has not changed, we need to remove it from the old map to avoid deallocation. + StaticMeshes.Remove( HoudiniGeoPartObject ); + } + + // See if we need to update the HoudiniAssetComponent uproperties + FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject( this, HoudiniGeoPartObject ); + } + + // Make sure rendering is done + FlushRenderingCommands(); + + // Update the bake folder as it might have been updated by an override + BakeFolder = HoudiniCookParams.BakeFolder; + + if ( NewStaticMeshes.Num() == 0 && bContainsHoudiniLogoGeometry ) + { + // We're not creating anything new and are already using the houdini logo + // No need to destroy then recreate the Houdini Logo + if ( StaticMeshes.Num() != 1) + CreateStaticMeshHoudiniLogoResource( StaticMeshes ); + } + else + { + // Free meshes and components that are no longer used. + // compare with first array, keep if contained in NewMeshes + ReleaseObjectGeoPartResources(StaticMeshes, true, &NewStaticMeshes); + + // Set meshes and create new components for those meshes that do not have them. + if (NewStaticMeshes.Num() > 0) + CreateObjectGeoPartResources(NewStaticMeshes); + else + CreateStaticMeshHoudiniLogoResource(NewStaticMeshes); + } + } + + // Invoke cooks of downstream assets. + if ( bCookingTriggersDownstreamCooks && DownstreamAssetConnections.Num() > 0) + { + // First, make sure our downstream assets are valid + // (check that the component is valid and we are properly set as one of its asset input) + ValidateDownstreamAssets(); + + for ( TMap >::TIterator IterAssets( DownstreamAssetConnections ); + IterAssets; + ++IterAssets ) + { + UHoudiniAssetComponent * DownstreamAsset = IterAssets.Key(); + if ( !DownstreamAsset || DownstreamAsset->IsPendingKill() ) + continue; + + DownstreamAsset->bManualRecookRequested = bManualRecookRequested; + DownstreamAsset->NotifyParameterChanged( nullptr ); + } + } + + // We can reset the manual recook flag now that the static meshes have been created + bManualRecookRequested = false; +} + +void +UHoudiniAssetComponent::ValidateDownstreamAssets() +{ + TArray InvalidDowmnstreamAssets; + for (TMap >::TIterator IterAssets(DownstreamAssetConnections); IterAssets; ++IterAssets) + { + UHoudiniAssetComponent * DownstreamAsset = IterAssets.Key(); + if (!DownstreamAsset || DownstreamAsset->IsPendingKill()) + { + InvalidDowmnstreamAssets.Add(DownstreamAsset); + continue; + } + + // Check that the downstream asset is valid (that we are indeed set as asset input) + bool bInvalidDownstreamAsset = false; + TSet InputIndexes = IterAssets.Value(); + if (InputIndexes.Num() <= 0) + { + InvalidDowmnstreamAssets.Add(DownstreamAsset); + continue; + } + + // Get the downstream assets inputs (including object path parameters!) + TArray DownstreamInputs; + DownstreamAsset->GetInputs(DownstreamInputs, true); + + // Check that asset component's input + for (auto DownstreamInputIdx : InputIndexes) + { + if (!DownstreamInputs.IsValidIndex(DownstreamInputIdx)) + { + RemoveDownstreamAsset(DownstreamAsset, DownstreamInputIdx); + continue; + } + + UHoudiniAssetInput* DownInput = DownstreamInputs[DownstreamInputIdx]; + if (!DownInput || DownInput->IsPendingKill()) + { + RemoveDownstreamAsset(DownstreamAsset, DownstreamInputIdx); + continue; + } + + if (DownInput->GetChoiceIndex() != EHoudiniAssetInputType::AssetInput + || DownInput->GetConnectedInputAssetComponent() != this) + { + RemoveDownstreamAsset(DownstreamAsset, DownstreamInputIdx); + continue; + } + } + } + + // Remove the fully invalid HAC + for (auto InvalidHAC : InvalidDowmnstreamAssets) + { + DownstreamAssetConnections.Remove(InvalidHAC); + } + +} +void +UHoudiniAssetComponent::TickHoudiniComponent() +{ + // Only tick/cook when in Editor + // This prevents PIE cooks or runtime cooks due to inputs moving + if (!GetWorld() || (GetWorld()->WorldType != EWorldType::Editor)) + { + return; + } + + // Get settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + FHoudiniEngineTaskInfo TaskInfo; + bool bStopTicking = false; + bool bFinishedLoadedInstantiation = false; + + static float NotificationFadeOutDuration = 2.0f; + static float NotificationExpireDuration = 2.0f; + static double NotificationUpdateFrequency = 2.0f; + + // Check whether we want to display Slate cooking and instantiation notifications. + bool bDisplaySlateCookingNotifications = false; + if ( HoudiniRuntimeSettings ) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if ( HapiGUID.IsValid() ) + { + // If we have a valid task GUID. + if ( FHoudiniEngine::Get().RetrieveTaskInfo( HapiGUID, TaskInfo ) ) + { + if ( EHoudiniEngineTaskState::None != TaskInfo.TaskState ) + { + if ( !NotificationPtr.IsValid() && bDisplaySlateCookingNotifications ) + { + FNotificationInfo Info( TaskInfo.StatusText ); + + Info.bFireAndForget = false; + Info.FadeOutDuration = NotificationFadeOutDuration; + Info.ExpireDuration = NotificationExpireDuration; + + TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniLogoBrush(); + if ( HoudiniBrush.IsValid() ) + Info.Image = HoudiniBrush.Get(); + + if ( ( FPlatformTime::Seconds() - HapiNotificationStarted) >= NotificationUpdateFrequency ) + { + if ( !IsPIEActive() ) + NotificationPtr = FSlateNotificationManager::Get().AddNotification( Info ); + } + } + } + + FString DisplayName; + if (GetOwner()) + DisplayName = (GetOwner()->GetName()); + else + DisplayName = GetName(); + + switch( TaskInfo.TaskState ) + { + case EHoudiniEngineTaskState::FinishedInstantiation: + { + HOUDINI_LOG_MESSAGE( TEXT(" %s FinishedInstantiation." ), *DisplayName ); + + if ( FHoudiniEngineUtils::IsValidNodeId( TaskInfo.AssetId ) ) + { + // Set new asset id. + SetAssetId( TaskInfo.AssetId ); + + AHoudiniAssetActor * HoudiniAssetActor = GetHoudiniAssetActorOwner(); + if( HoudiniAssetActor && HoudiniAssetActor->GetName().StartsWith( AHoudiniAssetActor::StaticClass()->GetName() ) ) + { + // Assign unique actor label based on asset name if it seems to have not been renamed already + AssignUniqueActorLabel(); + } + + // Create default preset buffer. + CreateDefaultPreset(); + + // If necessary, set asset transform. + if ( bUploadTransformsToHoudiniEngine ) + { + // Retrieve the current component-to-world transform for this component. + if ( !FHoudiniEngineUtils::HapiSetAssetTransform( AssetId, GetComponentTransform() ) ) + HOUDINI_LOG_MESSAGE( TEXT( "Failed Uploading Initial Transformation back to HAPI." ) ); + } + + if ( NotificationPtr.IsValid() && bDisplaySlateCookingNotifications ) + { + TSharedPtr< SNotificationItem > NotificationItem = NotificationPtr.Pin(); + if ( NotificationItem.IsValid() ) + { + NotificationItem->SetText( TaskInfo.StatusText ); + NotificationItem->ExpireAndFadeout(); + + NotificationPtr.Reset(); + } + } + + FHoudiniEngine::Get().RemoveTaskInfo( HapiGUID ); + HapiGUID.Invalidate(); + + // We just finished instantiation, we need to reset cook counter. + AssetCookCount = 0; + + if ( TaskInfo.bLoadedComponent ) + bFinishedLoadedInstantiation = true; + + FHoudiniEngine::Get().SetHapiState( HAPI_RESULT_SUCCESS ); + } + else + { + bStopTicking = true; + HOUDINI_LOG_MESSAGE( TEXT( " %s Received invalid asset id." ), *DisplayName ); + } + + break; + } + + case EHoudiniEngineTaskState::FinishedCooking: + { + HOUDINI_LOG_MESSAGE( TEXT( " %s FinishedCooking." ), *DisplayName ); + + if ( FHoudiniEngineUtils::IsValidNodeId( TaskInfo.AssetId ) ) + { + // Set new asset id. + SetAssetId( TaskInfo.AssetId ); + + // Call post cook event. + PostCook(); + + // Need to update rendering information. + UpdateRenderingInformation(); + +#if WITH_EDITOR + // Force editor to redraw viewports. + if ( GEditor ) + GEditor->RedrawAllViewports(); + + // Update properties panel after instantiation. + UpdateEditorProperties( true ); + + // We may have toolshelf input presets to apply (may cause a recook) + if ( HoudiniToolInputPreset.Num() > 0 ) + ApplyHoudiniToolInputPreset(); +#endif + } + else + { + HOUDINI_LOG_MESSAGE( TEXT( " %s Received invalid asset id." ), *DisplayName ); + } + + if ( NotificationPtr.IsValid() && bDisplaySlateCookingNotifications ) + { + TSharedPtr< SNotificationItem > NotificationItem = NotificationPtr.Pin(); + if ( NotificationItem.IsValid() ) + { + NotificationItem->SetText( TaskInfo.StatusText ); + NotificationItem->ExpireAndFadeout(); + + NotificationPtr.Reset(); + } + } + + FHoudiniEngine::Get().RemoveTaskInfo( HapiGUID ); + HapiGUID.Invalidate(); + + bStopTicking = true; + AssetCookCount++; + + break; + } + + case EHoudiniEngineTaskState::FinishedCookingWithErrors: + { + HOUDINI_LOG_MESSAGE( TEXT( " %s FinishedCookingWithErrors." ), *DisplayName ); + + if ( FHoudiniEngineUtils::IsValidNodeId( TaskInfo.AssetId ) ) + { + // Call post cook event with error parameter. This will create parameters, inputs and handles. + PostCook( true ); + + // Create default preset buffer. + CreateDefaultPreset(); +#if WITH_EDITOR + // Apply the Input presets for Houdini tools if we have any... + ApplyHoudiniToolInputPreset(); + + // Update properties panel. + UpdateEditorProperties( true ); +#endif + // If necessary, set asset transform. + if ( bUploadTransformsToHoudiniEngine ) + { + // Retrieve the current component-to-world transform for this component. + if ( !FHoudiniEngineUtils::HapiSetAssetTransform(AssetId, GetComponentTransform() ) ) + HOUDINI_LOG_MESSAGE( TEXT( "Failed Uploading Initial Transformation back to HAPI." ) ); + } + } + else + { + // Make sure to reset the manual cook flag here to avoid endless cooking/error spam + bManualRecookRequested = false; + } + + // Try to detect possible crash/failures of HARS + if ( TaskInfo.Result == HAPI_RESULT_NOT_INITIALIZED + || TaskInfo.Result == HAPI_RESULT_INVALID_SESSION ) + { + HOUDINI_LOG_ERROR( TEXT("Failed to cook due to an invalid session.\n\ + This could be due to a crash in the Houdini Engine session / HARS.\n\ + Try restarting it via \"File > Restart Houdini Engine session\"\n\ + then rebuild your assets." ) ); + } + + if ( NotificationPtr.IsValid() && bDisplaySlateCookingNotifications ) + { + TSharedPtr< SNotificationItem > NotificationItem = NotificationPtr.Pin(); + if ( NotificationItem.IsValid() ) + { + NotificationItem->SetText( TaskInfo.StatusText ); + NotificationItem->ExpireAndFadeout(); + + NotificationPtr.Reset(); + } + } + + FHoudiniEngine::Get().RemoveTaskInfo( HapiGUID ); + HapiGUID.Invalidate(); + + bStopTicking = true; + AssetCookCount++; + + break; + } + + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::FinishedInstantiationWithErrors: + { + HOUDINI_LOG_ERROR( TEXT( " %s FinishedInstantiationWithErrors." ), *DisplayName ); + + bool bLicensingIssue = false; + switch( TaskInfo.Result ) + { + case HAPI_RESULT_NO_LICENSE_FOUND: + { + FHoudiniEngine::Get().SetHapiState( HAPI_RESULT_NO_LICENSE_FOUND ); + + bLicensingIssue = true; + break; + } + + case HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND: + case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE: + case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE: + case HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE: + { + bLicensingIssue = true; + break; + } + + default: + { + break; + } + } + + if ( bLicensingIssue ) + { + const FString & StatusMessage = TaskInfo.StatusText.ToString() ; + HOUDINI_LOG_MESSAGE( TEXT( "%s" ), *StatusMessage ); + + FString WarningTitle = TEXT( "Houdini Engine Plugin Warning" ); + FText WarningTitleText = FText::FromString( WarningTitle ); + FString WarningMessage = FString::Printf( TEXT( "Houdini License issue - %s." ), *StatusMessage ); + + FMessageDialog::Debugf( FText::FromString( WarningMessage ), &WarningTitleText ); + } + + if ( NotificationPtr.IsValid() && bDisplaySlateCookingNotifications ) + { + TSharedPtr< SNotificationItem > NotificationItem = NotificationPtr.Pin(); + if ( NotificationItem.IsValid() ) + { + NotificationItem->SetText( TaskInfo.StatusText ); + NotificationItem->ExpireAndFadeout(); + + NotificationPtr.Reset(); + } + } + + if ( TaskInfo.bLoadedComponent ) + bFinishedLoadedInstantiation = true; + + FHoudiniEngine::Get().RemoveTaskInfo( HapiGUID ); + HapiGUID.Invalidate(); + + bStopTicking = true; + AssetCookCount = 0; + + break; + } + + case EHoudiniEngineTaskState::Processing: + { + + if ( NotificationPtr.IsValid() && bDisplaySlateCookingNotifications ) + { + TSharedPtr< SNotificationItem > NotificationItem = NotificationPtr.Pin(); + if ( NotificationItem.IsValid() ) + NotificationItem->SetText( TaskInfo.StatusText ); + } + + break; + } + + case EHoudiniEngineTaskState::None: + default: + { + break; + } + } + } + else + { + // Task information does not exist, we can stop ticking. + HapiGUID.Invalidate(); + bStopTicking = true; + } + } + + if (bFinishedLoadedInstantiation) + bAssetIsBeingInstantiated = false; + + if ( !IsInstantiatingOrCooking() ) + { + if (( HasBeenInstantiatedButNotCooked() || bParametersChanged || bComponentNeedsCook || bManualRecookRequested ) + && (FHoudiniEngine::Get().GetEnableCookingGlobal() || bManualRecookRequested) ) + { + // Grab current time for delayed notification. + HapiNotificationStarted = FPlatformTime::Seconds(); + + // We just submitted a task, we want to continue ticking. + bStopTicking = false; + + if ( bWaitingForUpstreamAssetsToInstantiate ) + { + // We are waiting for upstream assets to instantiate. Update the flag + UpdateWaitingForUpstreamAssetsToInstantiate(); + + // Try instantiating this asset again. + if ( !bWaitingForUpstreamAssetsToInstantiate ) + bLoadedComponentRequiresInstantiation = true; + } + else if ( bLoadedComponentRequiresInstantiation ) + { + // This component has been loaded and requires instantiation. + bLoadedComponentRequiresInstantiation = false; + StartTaskAssetInstantiation( true ); + } + else if ( bFinishedLoadedInstantiation ) + { + // If we are doing first cook after instantiation. + RefreshEditableNodesAfterLoad(); + + // Update parameter node id for all loaded parameters. + UpdateLoadedParameters(); + + // Additionally, we need to update and create assets for all input parameters that have geos assigned. + UpdateLoadedInputs( bManualRecookRequested ); + + // We also need to upload loaded curve points. + UploadLoadedCurves(); + + // If we finished loading instantiation, we can restore preset data. + if ( PresetBuffer.Num() > 0 ) + { + FHoudiniEngineUtils::SetAssetPreset( AssetId, PresetBuffer ); + PresetBuffer.Empty(); + } + + // Upload changed parameters back to HAPI. + UploadChangedParameters(); + + // Reset tranform changed flag. + bComponentNeedsCook = false; + + // Create asset cooking task object and submit it for processing. + StartTaskAssetCooking(); + } + else + { + if ( IsCookingEnabled() || bManualRecookRequested ) + { + // Upload changed parameters back to HAPI. + UploadChangedParameters(); + + // Create asset cooking task object and submit it for processing. + StartTaskAssetCooking(); + + // Reset ComponentNeedsCook flag. + bComponentNeedsCook = false; + } + else + { + // Cooking is disabled, but we still need to upload the parameters + // and update the editor properties + UploadChangedParameters(); + + // Update properties panel. + UpdateEditorProperties(true); + + // Remember that we have uncooked changes + bComponentNeedsCook = true; + + // Stop ticking + bStopTicking = true; + } + } + } + else + { + // Nothing has changed, we can terminate ticking. + bStopTicking = true; + } + } + + if ( bNeedToUpdateNavigationSystem ) + { +#ifdef WITH_EDITOR + // notify navigation system + AHoudiniAssetActor* HoudiniActor = GetHoudiniAssetActorOwner(); + if ( HoudiniActor && !HoudiniActor->IsPendingKill() ) + FNavigationSystem::UpdateActorAndComponentData(*HoudiniActor); +#endif + bNeedToUpdateNavigationSystem = false; + } + + if ( bStopTicking ) + StopHoudiniTicking(); +} + +void +UHoudiniAssetComponent::UpdateEditorProperties( bool bConditionalUpdate ) +{ + AHoudiniAssetActor * HoudiniAssetActor = GetHoudiniAssetActorOwner(); + if ( !HoudiniAssetActor ) + return; + + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >( "PropertyEditor" ); + + // We want to iterate on all the details panel + static const FName DetailsTabIdentifiers[] = { "LevelEditorSelectionDetails", "LevelEditorSelectionDetails2", "LevelEditorSelectionDetails3", "LevelEditorSelectionDetails4" }; + for (const FName& DetailsPanelName : DetailsTabIdentifiers ) + { + // Locate the details panel. + //FName DetailsPanelName = "LevelEditorSelectionDetails"; + TSharedPtr< IDetailsView > DetailsView = PropertyModule.FindDetailView( DetailsPanelName ); + + if ( !DetailsView.IsValid() ) + { + // We have no details panel, nothing to update. + continue; + } + + if ( DetailsView->IsLocked() ) + { + // If details panel is locked, locate selected actors and check if this component belongs to one of them. + const TArray< TWeakObjectPtr< AActor > > & SelectedDetailActors = DetailsView->GetSelectedActors(); + bool bFoundActor = false; + + for (int32 ActorIdx = 0, ActorNum = SelectedDetailActors.Num(); ActorIdx < ActorNum; ++ActorIdx) + { + TWeakObjectPtr< AActor > SelectedActor = SelectedDetailActors[ActorIdx]; + if ( SelectedActor.IsValid() && SelectedActor.Get() == HoudiniAssetActor ) + { + bFoundActor = true; + break; + } + } + + // Details panel is locked, but our actor is not selected. + if ( !bFoundActor ) + continue; + } + else + { + // If our actor is not selected (and details panel is not locked) don't do any updates. + if ( !HoudiniAssetActor->IsSelected() ) + continue; + } + + if ( GEditor && HoudiniAssetActor && bIsNativeComponent ) + { + if ( bConditionalUpdate && FSlateApplication::Get().HasAnyMouseCaptor() ) + { + // We want to avoid UI update if this is a conditional update and widget has captured the mouse. + StartHoudiniUIUpdateTicking(); + continue; + } + + TArray< UObject * > SelectedActors; + SelectedActors.Add( HoudiniAssetActor ); + + // bEditorPropertiesNeedFullUpdate is false only when small changes (parameters value) have been made + // We do not reselect the actor to avoid loosing the current selected parameter + + // Reset selected actor to itself, force refresh and override the lock. + DetailsView->SetObjects( SelectedActors, bEditorPropertiesNeedFullUpdate, true ); + + if ( !bEditorPropertiesNeedFullUpdate ) + bEditorPropertiesNeedFullUpdate = true; + + if ( GUnrealEd ) + { + GUnrealEd->UpdateFloatingPropertyWindows(); + } + } + + StopHoudiniUIUpdateTicking(); + } +} + +void +UHoudiniAssetComponent::StartTaskAssetInstantiation( bool bLocalLoadedComponent, bool bStartTicking ) +{ + // We do not want to be instantiated twice + bAssetIsBeingInstantiated = true; + + // We first need to make sure all our asset inputs have been instantiated and reconnected. + UpdateWaitingForUpstreamAssetsToInstantiate( true ); + + if ( !bWaitingForUpstreamAssetsToInstantiate ) + { + // Check if asset has multiple Houdini assets inside. + HAPI_AssetLibraryId AssetLibraryId = -1; + TArray< HAPI_StringHandle > AssetNames; + + if ( FHoudiniEngineUtils::GetAssetNames( HoudiniAsset, AssetLibraryId, AssetNames ) ) + { + HAPI_StringHandle PickedAssetName = AssetNames[ 0 ]; + bool bShowMultiAssetDialog = false; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if ( HoudiniRuntimeSettings ) + bShowMultiAssetDialog = HoudiniRuntimeSettings->bShowMultiAssetDialog; + + if ( bShowMultiAssetDialog && AssetNames.Num() > 1 ) + { + // If we have more than one asset, we need to present user with choice dialog. + + TSharedPtr< SWindow > ParentWindow; + + // Check if the main frame is loaded. When using the old main frame it may not be. + if ( FModuleManager::Get().IsModuleLoaded( "MainFrame" ) ) + { + IMainFrameModule & MainFrame = FModuleManager::LoadModuleChecked< IMainFrameModule >( "MainFrame" ); + ParentWindow = MainFrame.GetParentWindow(); + } + + if ( ParentWindow.IsValid() ) + { + TSharedPtr< SAssetSelectionWidget > AssetSelectionWidget; + + TSharedRef< SWindow > Window = SNew( SWindow ) + .Title( LOCTEXT( "WindowTitle", "Select an asset to instantiate" ) ) + .ClientSize( FVector2D( 640, 480 ) ) + .SupportsMinimize( false ) + .SupportsMaximize( false ) + .HasCloseButton( false ); + + Window->SetContent( SAssignNew( AssetSelectionWidget, SAssetSelectionWidget ) + .WidgetWindow( Window ) + .AvailableAssetNames( AssetNames ) ); + + if ( AssetSelectionWidget->IsValidWidget() ) + { + FSlateApplication::Get().AddModalWindow( Window, ParentWindow, false ); + + int32 DialogPickedAssetName = AssetSelectionWidget->GetSelectedAssetName(); + if ( DialogPickedAssetName != -1 ) + PickedAssetName = DialogPickedAssetName; + } + } + } + + // Create new GUID to identify this request. + HapiGUID = FGuid::NewGuid(); + + FHoudiniEngineTask Task( EHoudiniEngineTaskType::AssetInstantiation, HapiGUID ); + Task.Asset = HoudiniAsset; + Task.ActorName = GetOuter()->GetName(); + Task.bLoadedComponent = bLocalLoadedComponent; + Task.AssetLibraryId = AssetLibraryId; + Task.AssetHapiName = PickedAssetName; + FHoudiniEngine::Get().AddTask( Task ); + } + else + { + HOUDINI_LOG_MESSAGE( TEXT( "Cancelling asset instantiation - unable to retrieve asset names." ) ); + return; + } + } + + // Start ticking - this will poll the cooking system for completion. + if ( bStartTicking ) + StartHoudiniTicking(); +} + +void +UHoudiniAssetComponent::StartTaskAssetCookingManual() +{ + if ( !IsInstantiatingOrCooking() ) + { + bManualRecookRequested = true; + if ( FHoudiniEngineUtils::IsValidNodeId( GetAssetId() ) ) + { + StartHoudiniTicking(); + } + else + { + if ( bLoadedComponent ) + { + // This is a loaded component which requires instantiation. + StartTaskAssetInstantiation( true, true ); + bParametersChanged = true; + } + } + } +} + +void +UHoudiniAssetComponent::StartTaskAssetResetManual() +{ + if ( !IsInstantiatingOrCooking() ) + { + if ( FHoudiniEngineUtils::IsValidNodeId( GetAssetId() ) ) + { + if ( FHoudiniEngineUtils::SetAssetPreset( GetAssetId(), DefaultPresetBuffer ) ) + { + UnmarkChangedParameters(); + StartTaskAssetCookingManual(); + } + } + else + { + if ( bLoadedComponent ) + { + // This is a loaded component which requires instantiation. + bLoadedComponentRequiresInstantiation = true; + bParametersChanged = true; + + // Replace serialized preset buffer with default preset buffer. + PresetBuffer = DefaultPresetBuffer; + StartHoudiniTicking(); + } + } + } +} + +void +UHoudiniAssetComponent::StartTaskAssetRebuildManual() +{ + if ( !IsInstantiatingOrCooking() ) + { + if ( FHoudiniEngineUtils::IsHoudiniNodeValid( AssetId ) ) + { + if ( !FHoudiniEngineUtils::GetAssetPreset( AssetId, PresetBuffer ) ) + { + HOUDINI_LOG_WARNING(TEXT("Failed to get the asset's preset, rebuilt asset may have lost its parameters.")); + } + + // We need to delete the asset and request a new one. + StartTaskAssetDeletion(); + } + + HapiGUID = FGuid::NewGuid(); + + // If this is a loaded component, then we just need to instantiate. + bLoadedComponentRequiresInstantiation = true; + bParametersChanged = true; + bManualRecookRequested = true; + + StartHoudiniTicking(); + } +} + +void +UHoudiniAssetComponent::StartTaskAssetDeletion() +{ + if ( FHoudiniEngineUtils::IsValidNodeId( AssetId ) && bIsNativeComponent ) + { + // Get the Asset's NodeInfo + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetNodeInfo ); + + HAPI_NodeId OBJNodeToDelete = AssetId; + if ( AssetNodeInfo.type == HAPI_NODETYPE_SOP ) + { + // For SOP Asset, we want to delete their parent's OBJ node + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId( AssetId ); + OBJNodeToDelete = ParentId != -1 ? ParentId : AssetId; + } + + // Generate GUID for our new task. + FGuid HapiDeletionGUID = FGuid::NewGuid(); + + // Create asset deletion task object and submit it for processing. + FHoudiniEngineTask Task( EHoudiniEngineTaskType::AssetDeletion, HapiDeletionGUID ); + Task.AssetId = OBJNodeToDelete; + FHoudiniEngine::Get().AddTask( Task ); + + // Reset asset id + AssetId = -1; + + // We do not need to tick as we are not interested in result. + } +} + +void +UHoudiniAssetComponent::StartTaskAssetCooking( bool bStartTicking ) +{ + if ( !IsInstantiatingOrCooking() ) + { + if (!FHoudiniEngineUtils::IsValidNodeId(AssetId)) + return; + + // Generate GUID for our new task. + HapiGUID = FGuid::NewGuid(); + + FHoudiniEngineTask Task( EHoudiniEngineTaskType::AssetCooking, HapiGUID ); + Task.ActorName = GetOuter()->GetName(); + Task.AssetId = GetAssetId(); + FHoudiniEngine::Get().AddTask( Task ); + + if ( bStartTicking ) + StartHoudiniTicking(); + } +} + +void +UHoudiniAssetComponent::ResetHoudiniResources() +{ + if ( HapiGUID.IsValid() ) + { + // If we have a valid task GUID. + FHoudiniEngineTaskInfo TaskInfo; + + if ( FHoudiniEngine::Get().RetrieveTaskInfo( HapiGUID, TaskInfo ) ) + { + FHoudiniEngine::Get().RemoveTaskInfo( HapiGUID ); + HapiGUID.Invalidate(); + StopHoudiniTicking(); + + // Get settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Check whether we want to display Slate cooking and instantiation notifications. + bool bDisplaySlateCookingNotifications = false; + if ( HoudiniRuntimeSettings ) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if ( NotificationPtr.IsValid() && bDisplaySlateCookingNotifications ) + { + TSharedPtr< SNotificationItem > NotificationItem = NotificationPtr.Pin(); + if ( NotificationItem.IsValid() ) + { + NotificationItem->ExpireAndFadeout(); + NotificationPtr.Reset(); + } + } + } + } + + // Start asset deletion. + StartTaskAssetDeletion(); +} + +void +UHoudiniAssetComponent::SubscribeEditorDelegates() +{ + // Add delegate for viewport drag and drop events. + DelegateHandleApplyObjectToActor = + FEditorDelegates::OnApplyObjectToActor.AddUObject( this, &UHoudiniAssetComponent::OnApplyObjectToActor ); + + if ( GEditor ) + { + // Add delegate for asset post import. + if (UImportSubsystem* ImportSys = GEditor->GetEditorSubsystem()) + { + ImportSys->OnAssetPostImport.AddUObject(this, &UHoudiniAssetComponent::OnAssetPostImport); + } + + // Add delegate for actor moved. + GEditor->OnActorMoved().AddUObject( this, &UHoudiniAssetComponent::OnActorMoved ); + } +} + +void +UHoudiniAssetComponent::UnsubscribeEditorDelegates() +{ + // Remove delegate for viewport drag and drop events. + FEditorDelegates::OnApplyObjectToActor.Remove( DelegateHandleApplyObjectToActor ); + + if ( GEditor ) + { + // Remove delegate for asset post import. + if (UImportSubsystem* ImportSys = GEditor->GetEditorSubsystem()) + { + ImportSys->OnAssetPostImport.Remove(DelegateHandleAssetPostImport); + } + + // Remove delegate for actor moved. + GEditor->OnActorMoved().RemoveAll( this ); + } +} + +void +UHoudiniAssetComponent::PostEditChangeProperty( FPropertyChangedEvent & PropertyChangedEvent ) +{ + Super::PostEditChangeProperty( PropertyChangedEvent ); + + if (!bIsNativeComponent) + return; + + FProperty * Property = PropertyChangedEvent.MemberProperty; + if ( !Property ) + return; + + if ( Property->GetName() == TEXT( "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 ); + } + + return; + } + else if ( Property->GetName() == TEXT( "bVisible" ) ) + { + // Visibility has changed, propagate it to children. + SetVisibility( IsVisible(), true ); + return; + } + else if ( ( Property->GetName() == TEXT( "RelativeLocation" ) ) + || (Property->GetName() == TEXT( "RelativeRotation" ) ) + || (Property->GetName() == TEXT( "RelativeScale3D" ) ) ) + { + // Location has changed + CheckedUploadTransform(); + } + else if ( Property->GetName() == TEXT( "bHiddenInGame" ) ) + { + // Visibility has changed, propagate it to children. + SetHiddenInGame( bHiddenInGame, true ); + return; + } + + if ( Property->HasMetaData( TEXT( "Category" ) ) ) + { + const FString & Category = Property->GetMetaData( TEXT( "Category" ) ); + static const FString CategoryHoudiniGeneratedStaticMeshSettings = TEXT( "HoudiniGeneratedStaticMeshSettings" ); + 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 (TMap< FHoudiniGeoPartObject, UStaticMesh * > ::TIterator Iter(StaticMeshes); Iter; ++Iter) + { + UStaticMesh * StaticMesh = Iter.Value(); + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + continue; + + SetStaticMeshGenerationParameters( 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( "LpvBiasMultiplier" ) ) + { + HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, LpvBiasMultiplier ); + } + 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 ); + } + } + } +} + +void +UHoudiniAssetComponent::RemoveAllAttachedComponents() +{ + while ( true ) + { + const int32 ChildCount = GetAttachChildren().Num(); + if ( ChildCount <= 0 ) + break; + + USceneComponent * Component = GetAttachChildren()[ ChildCount - 1 ]; + + Component->DetachFromComponent( FDetachmentTransformRules::KeepRelativeTransform ); + Component->UnregisterComponent(); + Component->DestroyComponent(); + } + + check( GetAttachChildren().Num() == 0 ); +} + +void +UHoudiniAssetComponent::OnComponentClipboardCopy( UHoudiniAssetComponent * HoudiniAssetComponent ) +{ + // Store copied component. + CopiedHoudiniComponent = HoudiniAssetComponent; + + if ( bIsNativeComponent ) + { + // This component has been loaded. + bLoadedComponent = true; + bFullyLoaded = false; + } + + // Mark this component as imported. + bComponentCopyImported = true; +} + +void +UHoudiniAssetComponent::OnAssetPostImport( UFactory * Factory, UObject * Object ) +{ + if (!bComponentCopyImported || !CopiedHoudiniComponent.IsValid() ) + return; + + // Show busy cursor. + FScopedBusyCursor ScopedBusyCursor; + + // Copy the original scale - this gets lost sometimes in the copy/paste procedure + SetWorldScale3D( CopiedHoudiniComponent->GetComponentScale() ); + + // Copy mobility + SetMobility( CopiedHoudiniComponent->Mobility ); + + // Get original asset id. + HAPI_NodeId CopiedHoudiniComponentAssetId = CopiedHoudiniComponent->AssetId; + + // Set Houdini asset. + HoudiniAsset = CopiedHoudiniComponent->HoudiniAsset; + + // Copy preset buffer. + if ( FHoudiniEngineUtils::IsValidNodeId( CopiedHoudiniComponentAssetId ) ) + FHoudiniEngineUtils::GetAssetPreset( CopiedHoudiniComponentAssetId, PresetBuffer ); + else + PresetBuffer = CopiedHoudiniComponent->PresetBuffer; + + // Copy default preset buffer. + DefaultPresetBuffer = CopiedHoudiniComponent->DefaultPresetBuffer; + + // Clean up all generated and auto-attached components. + RemoveAllAttachedComponents(); + + // Release static mesh related resources. + ReleaseObjectGeoPartResources( StaticMeshes ); + StaticMeshes.Empty(); + StaticMeshComponents.Empty(); + + TMap ReplacementMap; + + // We need to reconstruct geometry from copied actor. + for( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( CopiedHoudiniComponent->StaticMeshes ); Iter; ++Iter ) + { + FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key(); + UStaticMesh * StaticMesh = Iter.Value(); + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + continue; + + // Prevents the default Houdini logo mesh from being duplicated and saved into sub-levels + const UStaticMesh * HoudiniLogoMesh = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); + if (StaticMesh == HoudiniLogoMesh) + { + HOUDINI_LOG_WARNING(TEXT("Ignoring HoudiniAsset duplication of default logo static mesh.")); + continue; + } + + // Duplicate static mesh and all related generated Houdini materials and textures. + UStaticMesh * DuplicatedStaticMesh = + FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( StaticMesh, this, HoudiniGeoPartObject, FHoudiniCookParams::GetDefaultStaticMeshesCookMode() ); + + if( DuplicatedStaticMesh && !DuplicatedStaticMesh->IsPendingKill() ) + { + // Store this duplicated mesh. + StaticMeshes.Add( FHoudiniGeoPartObject( HoudiniGeoPartObject, true ), DuplicatedStaticMesh ); + ReplacementMap.Add( StaticMesh, DuplicatedStaticMesh ); + } + } + + // Copy material information. + HoudiniAssetComponentMaterials = CopiedHoudiniComponent->HoudiniAssetComponentMaterials->Duplicate( this, ReplacementMap ); + + // Copy parameters. + { + ClearParameters(); + CopiedHoudiniComponent->DuplicateParameters( this ); + } + + // Copy inputs. + { + ClearInputs(); + CopiedHoudiniComponent->DuplicateInputs( this ); + } + + // Copy instance inputs. + { + ClearInstanceInputs(); + CopiedHoudiniComponent->DuplicateInstanceInputs( this, ReplacementMap ); + } + + // We need to reconstruct splines. + for( TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* >::TIterator + Iter( CopiedHoudiniComponent->SplineComponents ); Iter; ++Iter ) + { + FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key(); + UHoudiniSplineComponent * HoudiniSplineComponent = Iter.Value(); + if ( !HoudiniSplineComponent || !HoudiniSplineComponent->IsValidLowLevel() ) + continue; + + // Duplicate spline component. + UHoudiniSplineComponent * DuplicatedSplineComponent = + DuplicateObject< UHoudiniSplineComponent >( HoudiniSplineComponent, this ); + + if ( DuplicatedSplineComponent ) + { + DuplicatedSplineComponent->SetFlags( RF_Transactional | RF_Public ); + SplineComponents.Add( HoudiniGeoPartObject, DuplicatedSplineComponent ); + } + } + + /* + // We need to duplicate landscapes. + for ( TMap< FHoudiniGeoPartObject, ALandscapeProxy * >::TIterator + Iter(CopiedHoudiniComponent->LandscapeComponents); Iter; ++Iter) + { + FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key(); + ALandscapeProxy * HoudiniLandscape = Iter.Value(); + + // Duplicate landscape component. + ALandscapeProxy * DuplicatedLandscape = + DuplicateObject< ALandscapeProxy >( HoudiniLandscape, this ); + + if ( DuplicatedLandscape ) + { + DuplicatedLandscape->SetFlags(RF_Transactional | RF_Public); + LandscapeComponents.Add(HoudiniGeoPartObject, DuplicatedLandscape); + } + } + */ + + // We cannot duplicate landscape for now... + // we will have to recook the asset to recreate them + bool bNeedsRecook = false; + if ( CopiedHoudiniComponent->LandscapeComponents.Num() > 0 ) + bNeedsRecook = true; + + // Perform any necessary post loading. + PostLoad(); + + DuplicateHandles( CopiedHoudiniComponent.Get() ); + + // Mark this component as no longer copy imported and reset copied component. + bComponentCopyImported = false; + CopiedHoudiniComponent = nullptr; + + if ( bNeedsRecook ) + StartTaskAssetCookingManual(); +} + +void +UHoudiniAssetComponent::OnApplyObjectToActor( UObject* ObjectToApply, AActor * ActorToApplyTo ) +{ + if ( GetHoudiniAssetActorOwner() != ActorToApplyTo ) + return; + + // We want to handle material replacements. + UMaterialInterface * Material = Cast< UMaterialInterface >( ObjectToApply ); + if (!Material) + return; + + bool bMaterialReplaced = false; + + TMap< UStaticMesh*, int32 > MaterialReplacementsMap; + + // We need to detect which components have material overriden, and replace it on their corresponding + // generated static meshes. + for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TIterator Iter( StaticMeshComponents ); Iter; ++Iter ) + { + UStaticMesh * StaticMesh = Iter.Key(); + UStaticMeshComponent * StaticMeshComponent = Iter.Value(); + + if ( !StaticMeshComponent || !StaticMesh || StaticMeshComponent->IsPendingKill() || StaticMesh->IsPendingKill() ) + continue; + + const TArray< class UMaterialInterface * > & OverrideMaterials = StaticMeshComponent->OverrideMaterials; + for ( int32 MaterialIdx = 0; MaterialIdx < OverrideMaterials.Num(); ++MaterialIdx ) + { + UMaterialInterface * OverridenMaterial = OverrideMaterials[ MaterialIdx ]; + if ( OverridenMaterial && OverridenMaterial == Material ) + { + if ( MaterialIdx < StaticMesh->StaticMaterials.Num() ) + MaterialReplacementsMap.Add( StaticMesh, MaterialIdx ); + } + } + } + + for ( auto& InstanceInput : InstanceInputs ) + { + if ( InstanceInput && !InstanceInput->IsPendingKill() ) + InstanceInput->GetMaterialReplacementMeshes( Material, MaterialReplacementsMap ); + } + + if (MaterialReplacementsMap.Num() <= 0) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniMaterialReplacement", "Houdini Material Replacement" ), this ); + + for ( TMap< UStaticMesh *, int32 >::TIterator Iter( MaterialReplacementsMap ); Iter; ++Iter ) + { + UStaticMesh * StaticMesh = Iter.Key(); + int32 MaterialIdx = Iter.Value(); + + // Get old material. + UMaterialInterface * OldMaterial = StaticMesh->StaticMaterials[ MaterialIdx ].MaterialInterface; + + // Locate geo part object. + FHoudiniGeoPartObject HoudiniGeoPartObject = LocateGeoPartObject( StaticMesh ); + if ( !HoudiniGeoPartObject.IsValid() ) + continue; + + if ( ReplaceMaterial( HoudiniGeoPartObject, Material, OldMaterial, MaterialIdx ) ) + { + StaticMesh->Modify(); + StaticMesh->StaticMaterials[ MaterialIdx ].MaterialInterface = Material; + + StaticMesh->PreEditChange( nullptr ); + StaticMesh->PostEditChange(); + StaticMesh->MarkPackageDirty(); + + UStaticMeshComponent * StaticMeshComponent = LocateStaticMeshComponent( StaticMesh ); + if ( StaticMeshComponent ) + { + StaticMeshComponent->Modify(); + StaticMeshComponent->SetMaterial( MaterialIdx, Material ); + + bMaterialReplaced = true; + } + + TArray< UInstancedStaticMeshComponent * > InstancedStaticMeshComponents; + if ( LocateInstancedStaticMeshComponents( StaticMesh, InstancedStaticMeshComponents ) ) + { + for ( int32 Idx = 0; Idx < InstancedStaticMeshComponents.Num(); ++Idx ) + { + UInstancedStaticMeshComponent * InstancedStaticMeshComponent = InstancedStaticMeshComponents[ Idx ]; + + if ( InstancedStaticMeshComponent ) + { + InstancedStaticMeshComponent->Modify(); + InstancedStaticMeshComponent->SetMaterial( MaterialIdx, Material ); + + bMaterialReplaced = true; + } + } + } + } + } + + if ( bMaterialReplaced ) + UpdateEditorProperties( false ); +} + +void +UHoudiniAssetComponent::OnActorMoved( AActor* Actor ) +{ + if ( GetOwner() == Actor ) + { + CheckedUploadTransform(); + } +} + +void +UHoudiniAssetComponent::CreateDefaultPreset() +{ + if ( !bLoadedComponent && !FHoudiniEngineUtils::GetAssetPreset( GetAssetId(), DefaultPresetBuffer ) ) + DefaultPresetBuffer.Empty(); +} + +void +UHoudiniAssetComponent::OnUpdateTransform( EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport ) +{ + Super::OnUpdateTransform( UpdateTransformFlags, Teleport ); + + CheckedUploadTransform(); +} + +void +UHoudiniAssetComponent::CheckedUploadTransform() +{ + // Only if the asset is done loading, else this might cause a cook + // upon loading a map if the asset has TransformChangeTRiggersCook enabled + if ( !bFullyLoaded ) + return; + + // If we have to upload transforms. + if ( bUploadTransformsToHoudiniEngine && AssetCookCount > 0 ) + { + // Retrieve the current component-to-world transform for this component. + if ( !FHoudiniEngineUtils::HapiSetAssetTransform( AssetId, GetComponentTransform() ) ) + HOUDINI_LOG_MESSAGE( TEXT( "Failed Uploading Transformation change back to HAPI." ) ); + } + + // If transforms trigger cooks, we need to schedule a cook. + if ( bTransformChangeTriggersCooks ) + { + // If we have landscape inputs in auto select components mode, we need to mark them + // as changed so to recommit the landscape properly with updated transforms + bool NeedLandscapeUpdate = false; + for (int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx) + { + // Retrieve input at this index. + UHoudiniAssetInput * AssetInput = Inputs[InputIdx]; + if (!AssetInput || AssetInput->IsPendingKill()) + continue; + + if (!AssetInput->IsLandscapeUpdateNeededOnTransformChange()) + continue; + + NeedLandscapeUpdate = true; + AssetInput->MarkChanged(); + } + + if (bLoadedComponent && !FHoudiniEngineUtils::IsValidNodeId(AssetId) && !bAssetIsBeingInstantiated) + StartTaskAssetCookingManual(); + + bComponentNeedsCook = true; + StartHoudiniTicking(); + } +} + +void +UHoudiniAssetComponent::SetBakingBaseNameOverride( const FHoudiniGeoPartObject& GeoPartObject, const FString& BaseName ) +{ + if( const FString* FoundOverride = BakeNameOverrides.Find( GeoPartObject ) ) + { + // forget the last baked package since we changed the name + if( *FoundOverride != BaseName ) + { + BakedStaticMeshPackagesForParts.Remove( GeoPartObject ); + } + } + BakeNameOverrides.Add( GeoPartObject, BaseName ); +} + +bool +UHoudiniAssetComponent::RemoveBakingBaseNameOverride( const FHoudiniGeoPartObject& GeoPartObject ) +{ + return BakeNameOverrides.Remove( GeoPartObject ) > 0; +} + +#endif // WITH_EDITOR + +FText +UHoudiniAssetComponent::GetBakeFolder() const +{ + // Empty indicates default - which is Content root + if( BakeFolder.IsEmpty() ) + { + return LOCTEXT( "Game", "/Game" ); + } + return BakeFolder; +} + +void +UHoudiniAssetComponent::SetBakeFolder( const FString& Folder ) +{ + FText NewBakeFolder = FText::FromString( Folder ); + if( !NewBakeFolder.EqualTo( BakeFolder ) ) + { + BakeFolder = NewBakeFolder; + BakedStaticMeshPackagesForParts.Empty(); + BakedMaterialPackagesForIds.Empty(); + } +} + +FText +UHoudiniAssetComponent::GetTempCookFolder() const +{ + // Empty indicates default + if( TempCookFolder.IsEmpty() ) + { + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + check( HoudiniRuntimeSettings ); + + return HoudiniRuntimeSettings->TemporaryCookFolder; + } + + return TempCookFolder; +} + +void +UHoudiniAssetComponent::SetTempCookFolder(const FString& Folder) +{ + FText NewTempCookFolder = FText::FromString(Folder); + if (!NewTempCookFolder.EqualTo(TempCookFolder)) + { + TempCookFolder = NewTempCookFolder; + } +} + +FString UHoudiniAssetComponent::GetBakingBaseName( const FHoudiniGeoPartObject& GeoPartObject ) const +{ + if( const FString* FoundOverride = BakeNameOverrides.Find( GeoPartObject ) ) + { + return *FoundOverride; + } + if( GeoPartObject.HasCustomName() ) + { + return GeoPartObject.PartName; + } + + FString DisplayName; + if (GetOwner()) + DisplayName = GetOwner()->GetName(); + else + DisplayName = GetName(); + + return FString::Printf( TEXT( "%s_%d_%d_%d_%d" ), + *DisplayName, + GeoPartObject.ObjectId, GeoPartObject.GeoId, + GeoPartObject.PartId, GeoPartObject.SplitId ); +} + +FBoxSphereBounds +UHoudiniAssetComponent::CalcBounds( const FTransform & LocalToWorld ) const +{ + FBoxSphereBounds LocalBounds; + FBox BoundingBox = GetAssetBounds(); + 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; +} + +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( SceneComponent ) + SceneComponent->RecreatePhysicsState(); + } + + // Since we have new asset, we need to update bounds. + UpdateBounds(); +} + +void +UHoudiniAssetComponent::PostLoadReattachComponents() +{ + for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TIterator Iter( StaticMeshComponents ); Iter; ++Iter ) + { + UStaticMeshComponent * StaticMeshComponent = Iter.Value(); + if ( StaticMeshComponent && !StaticMeshComponent->IsPendingKill() ) + StaticMeshComponent->AttachToComponent( this, FAttachmentTransformRules::KeepRelativeTransform ); + } + + for ( TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* >::TIterator Iter( SplineComponents ); Iter; ++Iter ) + { + UHoudiniSplineComponent * HoudiniSplineComponent = Iter.Value(); + if( HoudiniSplineComponent && HoudiniSplineComponent->IsValidLowLevel() ) + HoudiniSplineComponent->AttachToComponent( this, FAttachmentTransformRules::KeepRelativeTransform ); + } + + for ( TMap< FString, UHoudiniHandleComponent * >::TIterator Iter( HandleComponents ); Iter; ++Iter ) + { + UHoudiniHandleComponent * HoudiniHandleComponent = Iter.Value(); + if ( HoudiniHandleComponent && !HoudiniHandleComponent->IsPendingKill() ) + HoudiniHandleComponent->AttachToComponent( this, FAttachmentTransformRules::KeepRelativeTransform ); + } + + for (TMap< FHoudiniGeoPartObject, TWeakObjectPtr >::TIterator Iter(LandscapeComponents); Iter; ++Iter) + { + ALandscapeProxy * HoudiniLandscape = Iter.Value().Get(); + if ( HoudiniLandscape && HoudiniLandscape->IsValidLowLevel() ) + HoudiniLandscape->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + } +} + + +#if WITH_EDITOR + +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 ); + } + + // Subscribe to delegates. + SubscribeEditorDelegates(); +} + +void +UHoudiniAssetComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) +{ + // Release static mesh related resources. + ReleaseObjectGeoPartResources( StaticMeshes ); + StaticMeshes.Empty(); + StaticMeshComponents.Empty(); + + // Release all curve related resources. + ClearCurves(); + + // Destroy all parameters. + ClearParameters(); + + // Destroy all inputs. + ClearInputs(); + + // Destroy all instance inputs. + ClearInstanceInputs(); + + // Destroy all handles. + ClearHandles(); + + // Destroy all landscapes. + ClearLandscapes(); + + // Inform downstream assets that we are dieing. + ClearDownstreamAssets(); + + // Clear cook content temp file + ClearCookTempFile(); + + // Release all Houdini related resources. + ResetHoudiniResources(); + + // Unsubscribe from Editor events. + UnsubscribeEditorDelegates(); + + Super::OnComponentDestroyed( bDestroyingHierarchy ); +} + +void +UHoudiniAssetComponent::OnRegister() +{ + Super::OnRegister(); + + // 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(); + } + } + + // We can now consider the asset as fully loaded + bFullyLoaded = true; +} +#endif + +bool +UHoudiniAssetComponent::ContainsHoudiniLogoGeometry() const +{ + return bContainsHoudiniLogoGeometry; +} + +void +UHoudiniAssetComponent::CreateStaticMeshHoudiniLogoResource( TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshMap ) +{ + if ( !bIsNativeComponent ) + return; + + // Create Houdini logo static mesh and component for it. + FHoudiniGeoPartObject HoudiniGeoPartObject; + StaticMeshMap.Add( HoudiniGeoPartObject, FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get() ); + CreateObjectGeoPartResources( StaticMeshMap ); + bContainsHoudiniLogoGeometry = true; +} + +#if WITH_EDITOR +void +UHoudiniAssetComponent::PostLoad() +{ + Super::PostLoad(); + + // If we are being cooked, we don't want to do anything + if( GIsCookerLoadingPackage ) + return; + + // Only do PostLoad stuff if we are in the editor world + if( UWorld* World = GetWorld() ) + { + if( World->WorldType != EWorldType::Editor && World->WorldType != EWorldType::Inactive ) + { + return; + } + } + else + { + // we aren't in _any_ world - how unusual + return; + } + + SanitizePostLoad(); + + // We loaded a component which has no asset associated with it. + if ( !HoudiniAsset && StaticMeshes.Num() <= 0) + { + // Set geometry to be Houdini logo geometry, since we have no other geometry. + CreateStaticMeshHoudiniLogoResource( StaticMeshes ); + return; + } + + // Show busy cursor. + FScopedBusyCursor ScopedBusyCursor; + + if ( StaticMeshes.Num() > 0 ) + { + CreateObjectGeoPartResources( StaticMeshes ); + } + else + { + // If the only component our owner has is us, then we should show the logo mesh + TArray< USceneComponent* > AllSceneComponents; + if(GetOwner()) + GetOwner()->GetComponents(AllSceneComponents); + + if (AllSceneComponents.Num() == 1 ) + { + CreateStaticMeshHoudiniLogoResource( StaticMeshes ); + } + } + + // Perform post load initialization on parameters. + PostLoadInitializeParameters(); + + // Perform post load initialization on instance inputs. + PostLoadInitializeInstanceInputs(); + + // Post attach components to parent asset component. + PostLoadReattachComponents(); + + // Update mobility. + // It'll be changed to static if we generated a landscape, + // and if not, to movable if any of our children is movable + UpdateMobility(); + + // Need to update rendering information. + UpdateRenderingInformation(); + + // Force editor to redraw viewports. + if ( GEditor ) + GEditor->RedrawAllViewports(); + + // Update properties panel after instantiation. + UpdateEditorProperties( false ); +} +#endif + +void +UHoudiniAssetComponent::Serialize( FArchive & Ar ) +{ + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + if ( !Ar.IsSaving() && !Ar.IsLoading() ) + return; + + // Serialize component flags. + Ar << HoudiniAssetComponentFlagsPacked; + + // State of this component. + EHoudiniAssetComponentState::Enum ComponentState = EHoudiniAssetComponentState::Invalid; + + if ( Ar.IsSaving() ) + { + if ( FHoudiniEngineUtils::IsValidNodeId( AssetId ) ) + { + // Asset has been previously instantiated. + + if ( HapiGUID.IsValid() ) + { + // Asset is being re-cooked asynchronously. + ComponentState = EHoudiniAssetComponentState::BeingCooked; + } + else + { + // We have no pending asynchronous cook requests. + ComponentState = EHoudiniAssetComponentState::Instantiated; + } + } + else + { + if ( HoudiniAsset ) + { + // Asset has not been instantiated and therefore must have asynchronous instantiation + // request in progress. + ComponentState = EHoudiniAssetComponentState::None; + } + else + { + // Component is in invalid state (for example is a default class object). + ComponentState = EHoudiniAssetComponentState::Invalid; + } + } + } + + // Serialize format version. + uint32 HoudiniAssetComponentVersion = Ar.CustomVer( FHoudiniCustomSerializationVersion::GUID ); + Ar << HoudiniAssetComponentVersion; + + // Serialize component state. + SerializeEnumeration< EHoudiniAssetComponentState::Enum >( Ar, ComponentState ); + + // Serialize scaling information and import axis. + Ar << GeneratedGeometryScaleFactor; + Ar << TransformScaleFactor; + SerializeEnumeration< EHoudiniRuntimeSettingsAxisImport >( Ar, ImportAxis ); + + // Serialize generated component GUID. + Ar << ComponentGUID; + + // If component is in invalid state, we can skip the rest of serialization. + if ( ComponentState == EHoudiniAssetComponentState::Invalid ) + return; + + // If we have no asset, we can stop. + if ( !HoudiniAsset && Ar.IsSaving() ) + return; + + // Serialize Houdini asset. + UHoudiniAsset * HoudiniSerializedAsset = nullptr; + + if ( Ar.IsSaving() ) + HoudiniSerializedAsset = HoudiniAsset; + + Ar << HoudiniSerializedAsset; + + if ( Ar.IsLoading() ) + { + if ( Ar.IsTransacting() && HoudiniAsset != HoudiniSerializedAsset ) + { + bTransactionAssetChange = true; + PreviousTransactionHoudiniAsset = HoudiniAsset; + } + + HoudiniAsset = HoudiniSerializedAsset; + } + + // If we are going into playmode, save asset id. + // NOTE: bIsPlayModeActive_Unused is known to have been mistakenly saved to disk as ON, + // the following fixes that case - should only happen once when first loading + if( Ar.IsLoading() && bIsPlayModeActive_Unused ) + { + HAPI_NodeId TempId; + Ar << TempId; + bIsPlayModeActive_Unused = false; + } + + // Serialization of default preset. + Ar << DefaultPresetBuffer; + + // Serialization of preset. + { + bool bPresetSaved = false; + + if ( Ar.IsSaving() ) + { + bPresetSaved = true; + if ( FHoudiniEngineUtils::IsValidNodeId( AssetId ) ) + { + FHoudiniEngineUtils::GetAssetPreset( AssetId, PresetBuffer ); + } + } + + Ar << bPresetSaved; + + if ( bPresetSaved ) + { + Ar << PresetBuffer; + } + } + + // Serialize parameters. + SerializeParameters( Ar ); + + // Serialize parameters name map. + if ( HoudiniAssetComponentVersion >= VER_HOUDINI_ENGINE_COMPONENT_PARAMETER_NAME_MAP ) + { + Ar << ParameterByName; + } + else + { + if ( Ar.IsLoading() ) + { + ParameterByName.Empty(); + + // Otherwise if we are loading an older serialization format, we can reconstruct parameters name map. + for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams( Parameters ); IterParams; ++IterParams ) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if ( HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill() ) + ParameterByName.Add( HoudiniAssetParameter->GetParameterName(), HoudiniAssetParameter ); + } + } + } + + // Serialize inputs. + SerializeInputs( Ar ); + + // Serialize material replacements and material assignments. + Ar << HoudiniAssetComponentMaterials; + + // Serialize geo parts and generated static meshes. + Ar << StaticMeshes; + Ar << StaticMeshComponents; + + // Serialize instance inputs (we do this after geometry loading as we might need it). + SerializeInstanceInputs( Ar ); + + // Serialize curves. + Ar << SplineComponents; + + // Serialize handles. + Ar << HandleComponents; + + // Serialize downstream asset connections. + Ar << DownstreamAssetConnections; + + // Serialize Landscape/GeoPart map + if ( HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_LANDSCAPES ) + { + Ar << LandscapeComponents; + } + + if( HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BAKENAME_OVERRIDE ) + { + Ar << BakeNameOverrides; + } + + TArray DirtyPackages; + if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES) + { + TMap SavedPackages; + if ( Ar.IsSaving() ) + { + for ( TMap > ::TIterator IterPackage(CookedTemporaryPackages); IterPackage; ++IterPackage ) + { + if (!IterPackage.Value().IsValid()) + continue; + + UPackage * Package = IterPackage.Value().Get(); + if ( !Package || UPackage::IsEmptyPackage( Package ) ) + continue; + + if (Package->IsDirty()) + DirtyPackages.Add(Package); + + FString sValue = Package->GetFName().ToString(); + + FString sKey = IterPackage.Key(); + SavedPackages.Add( sKey, sValue ); + } + } + + Ar << SavedPackages; + + if ( Ar.IsLoading() ) + { + for ( TMap ::TIterator IterPackage( SavedPackages ); IterPackage; ++IterPackage ) + { + FString sKey = IterPackage.Key(); + FString PackageFile = IterPackage.Value(); + if (!FPackageName::DoesPackageExist(PackageFile)) + continue; + + UPackage * Package = nullptr; + if ( !PackageFile.IsEmpty() ) + { + Package = FindPackage(nullptr, *PackageFile); + } + + if ( !Package ) + continue; + + if ( Package->IsDirty() ) + DirtyPackages.Add(Package); + + CookedTemporaryPackages.Add( sKey, Package ); + } + } + } + + if ( HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS ) + { + // Temporary Mesh Packages + TMap MeshPackages; + if ( Ar.IsSaving() ) + { + for ( TMap > ::TIterator IterPackage(CookedTemporaryStaticMeshPackages); IterPackage; ++IterPackage ) + { + if ( !IterPackage.Value().IsValid() ) + continue; + + UPackage * Package = IterPackage.Value().Get(); + if ( !Package || UPackage::IsEmptyPackage( Package ) ) + continue; + + if (Package->IsDirty()) + DirtyPackages.Add(Package); + + FString sValue = Package->GetFName().ToString(); + + FHoudiniGeoPartObject Key = IterPackage.Key(); + MeshPackages.Add( Key, sValue ); + } + } + + Ar << MeshPackages; + + if ( Ar.IsLoading() ) + { + for ( TMap ::TIterator IterPackage( MeshPackages ); IterPackage; ++IterPackage ) + { + FHoudiniGeoPartObject Key = IterPackage.Key(); + FString PackageFile = IterPackage.Value(); + if (!FPackageName::DoesPackageExist(PackageFile)) + continue; + + UPackage * Package = nullptr; + if ( !PackageFile.IsEmpty() ) + { + Package = FindPackage(nullptr, *PackageFile); + } + + if ( !Package ) + continue; + + if (Package->IsDirty()) + DirtyPackages.Add(Package); + + CookedTemporaryStaticMeshPackages.Add( Key, Package ); + } + } + + // Temporary Landscape Layers Packages + TMap LayerPackages; + if ( Ar.IsSaving() ) + { + for ( TMap, FHoudiniGeoPartObject > ::TIterator IterPackage( CookedTemporaryLandscapeLayers ); IterPackage; ++IterPackage ) + { + if (!IterPackage.Key().IsValid()) + continue; + + UPackage * Package = IterPackage.Key().Get(); + if ( !Package || UPackage::IsEmptyPackage( Package ) ) + continue; + + if ( Package->IsDirty() ) + DirtyPackages.Add(Package); + + FString sKey = Package->GetFName().ToString(); + + FHoudiniGeoPartObject Value = IterPackage.Value(); + LayerPackages.Add( sKey, Value ); + } + } + + Ar << LayerPackages; + + if ( Ar.IsLoading() ) + { + for ( TMap ::TIterator IterPackage( LayerPackages ); IterPackage; ++IterPackage ) + { + FHoudiniGeoPartObject Value = IterPackage.Value(); + FString PackageFile = IterPackage.Key(); + if ( !FPackageName::DoesPackageExist(PackageFile) ) + continue; + + UPackage * Package = nullptr; + if ( !PackageFile.IsEmpty() ) + { + Package = FindPackage(nullptr, *PackageFile); + } + + if ( !Package ) + continue; + + if ( Package->IsDirty() ) + DirtyPackages.Add(Package); + + CookedTemporaryLandscapeLayers.Add( Package, Value ); + } + } + } + +#if WITH_EDITOR + if (DirtyPackages.Num() > 0) + { + if (Ar.IsSaving() && !Ar.IsTransacting()) + { + // Save the dirty packages that we're still using + const bool bCheckDirty = false; + const bool bPromptToSave = false; + FEditorFileUtils::PromptForCheckoutAndSave(DirtyPackages, bCheckDirty, bPromptToSave); + } + } +#endif + + if ( Ar.IsLoading() && bIsNativeComponent ) + { + // This component has been loaded. + bLoadedComponent = true; + bFullyLoaded = false; + } +} + +void +UHoudiniAssetComponent::SetStaticMeshGenerationParameters( UStaticMesh * StaticMesh ) const +{ +#if WITH_EDITOR + if( !StaticMesh ) + return; + + // Make sure static mesh has a new lighting guid. + StaticMesh->LightingGuid = FGuid::NewGuid(); + StaticMesh->LODGroup = NAME_None; + + // Set resolution of lightmap. + StaticMesh->LightMapResolution = GeneratedLightMapResolution; + + // Set Bias multiplier for Light Propagation Volume lighting. + StaticMesh->LpvBiasMultiplier = GeneratedLpvBiasMultiplier; + + // Set the global light map coordinate index if it looks valid + if ( StaticMesh->RenderData.IsValid() && StaticMesh->RenderData->LODResources.Num() > 0) + { + int32 NumUVs = StaticMesh->RenderData->LODResources[0].GetNumTexCoords(); + if ( NumUVs > GeneratedLightMapCoordinateIndex ) + { + StaticMesh->LightMapCoordinateIndex = GeneratedLightMapCoordinateIndex; + } + } + + // Set method for LOD texture factor computation. + /* TODO_414 + //StaticMesh->bUseMaximumStreamingTexelRatio = bGeneratedUseMaximumStreamingTexelRatio; + + // Set distance where textures using UV 0 are streamed in/out. - GOES ON COMPONENT + // StaticMesh->StreamingDistanceMultiplier = GeneratedStreamingDistanceMultiplier; + */ + + // Add user data. + for( int32 AssetUserDataIdx = 0; AssetUserDataIdx < GeneratedAssetUserData.Num(); ++AssetUserDataIdx ) + StaticMesh->AddAssetUserData( GeneratedAssetUserData[ AssetUserDataIdx ] ); + + StaticMesh->CreateBodySetup(); + UBodySetup * BodySetup = StaticMesh->BodySetup; + check( BodySetup ); + + // Set flag whether physics triangle mesh will use double sided faces when doing scene queries. + BodySetup->bDoubleSidedGeometry = bGeneratedDoubleSidedGeometry; + + // Assign physical material for simple collision. + BodySetup->PhysMaterial = GeneratedPhysMaterial; + + BodySetup->DefaultInstance.CopyBodyInstancePropertiesFrom(&DefaultBodyInstance); + + // Assign collision trace behavior. + BodySetup->CollisionTraceFlag = GeneratedCollisionTraceFlag; + + // Assign walkable slope behavior. + BodySetup->WalkableSlopeOverride = GeneratedWalkableSlopeOverride; + + // We want to use all of geometry for collision detection purposes. + BodySetup->bMeshCollideAll = true; + +#endif +} + +const TArray< UHoudiniAssetInstanceInputField * > +UHoudiniAssetComponent::GetAllInstanceInputFields() const +{ + TArray< UHoudiniAssetInstanceInputField * > AllInstanceInputFields; + + // Duplicate instanced static mesh components. + for (auto& InstanceInput : InstanceInputs) + { + if (!InstanceInput || InstanceInput->IsPendingKill()) + continue; + + const TArray< UHoudiniAssetInstanceInputField * > CurrentInstanceInputFields = InstanceInput->GetInstanceInputFields(); + for (auto currentInputField : CurrentInstanceInputFields) + AllInstanceInputFields.Add(currentInputField); + } + + return AllInstanceInputFields; +} + +#if WITH_EDITOR + +AActor * +UHoudiniAssetComponent::CloneComponentsAndCreateActor() +{ + // Display busy cursor. + FScopedBusyCursor ScopedBusyCursor; + + ULevel * Level = GetHoudiniAssetActorOwner() ? GetHoudiniAssetActorOwner()->GetLevel() : nullptr; + if ( !Level ) + Level = GWorld->GetCurrentLevel(); + + AActor * Actor = NewObject< AActor >( Level, NAME_None ); + Actor->AddToRoot(); + + USceneComponent * RootComponent = + NewObject< USceneComponent >( Actor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional ); + + RootComponent->SetMobility(EComponentMobility::Static); + RootComponent->bVisualizeComponent = true; + + const FTransform & ComponentWorldTransform = GetComponentTransform(); + RootComponent->SetWorldLocationAndRotation( + ComponentWorldTransform.GetLocation(), + ComponentWorldTransform.GetRotation() ); + + Actor->SetRootComponent( RootComponent ); + Actor->AddInstanceComponent( RootComponent ); + + RootComponent->RegisterComponent(); + + // Duplicate static mesh components. + { + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshes ); Iter; ++Iter ) + { + FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key(); + UStaticMesh * StaticMesh = Iter.Value(); + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + continue; + + // Retrieve referenced static mesh component. + UStaticMeshComponent * StaticMeshComponent = LocateStaticMeshComponent( StaticMesh ); + if ( !StaticMeshComponent || StaticMeshComponent->IsPendingKill() ) + continue; + + // Bake the referenced static mesh. + UStaticMesh * OutStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( + StaticMesh, this, HoudiniGeoPartObject, EBakeMode::CreateNewAssets ); + + if ( !OutStaticMesh || OutStaticMesh->IsPendingKill() ) + continue; + + FAssetRegistryModule::AssetCreated( OutStaticMesh ); + + // Create static mesh component for baked mesh. + UStaticMeshComponent * DuplicatedComponent = + NewObject< UStaticMeshComponent >( Actor, UStaticMeshComponent::StaticClass(), NAME_None );//, RF_Transactional ); + + if ( !DuplicatedComponent || DuplicatedComponent->IsPendingKill() ) + continue; + + Actor->AddInstanceComponent( DuplicatedComponent ); + + DuplicatedComponent->SetStaticMesh( OutStaticMesh ); + DuplicatedComponent->SetVisibility( true ); + DuplicatedComponent->SetRelativeTransform( HoudiniGeoPartObject.TransformMatrix ); + + // If this is a collision geo, we need to make it invisible. + if ( HoudiniGeoPartObject.IsCollidable() ) + { + DuplicatedComponent->SetVisibility( false ); + DuplicatedComponent->SetHiddenInGame( true ); + DuplicatedComponent->SetCollisionProfileName( FName( TEXT( "InvisibleWall" ) ) ); + } + + // Reapply the uproperties modified by attributes on the duplicated component + FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject( DuplicatedComponent, HoudiniGeoPartObject ); + + DuplicatedComponent->AttachToComponent( RootComponent, FAttachmentTransformRules::KeepRelativeTransform ); + DuplicatedComponent->RegisterComponent(); + } + } + + // Duplicate instanced static mesh components. + for ( auto& InstanceInput : InstanceInputs ) + { + if ( InstanceInput && !InstanceInput->IsPendingKill() ) + InstanceInput->CloneComponentsAndAttachToActor( Actor ); + } + + return Actor; +} + +bool +UHoudiniAssetComponent::IsCookingEnabled() const +{ + return FHoudiniEngine::Get().GetEnableCookingGlobal() && bEnableCooking; +} + +void +UHoudiniAssetComponent::PostEditUndo() +{ + // We need to make sure that all mesh components in the maps are valid ones + CleanUpAttachedStaticMeshComponents(); + + // Check the cooked materials refer to something.. + bool bCookedContentNeedRecook = false; + for ( TMap< FString, TWeakObjectPtr< UPackage > > ::TIterator IterPackage( CookedTemporaryPackages ); IterPackage; ++IterPackage ) + { + if ( bCookedContentNeedRecook ) + break; + + UPackage * Package = IterPackage.Value().Get(); + if ( Package ) + { + FString PackageName = Package->GetName(); + if ( !PackageName.IsEmpty() && ( PackageName != TEXT( "None" ) ) ) + continue; + } + + bCookedContentNeedRecook = true; + } + + // Check the cooked meshes refer to something.. + for ( TMap< FHoudiniGeoPartObject, TWeakObjectPtr< UPackage > > ::TIterator IterPackage( CookedTemporaryStaticMeshPackages ); IterPackage; ++IterPackage ) + { + if ( bCookedContentNeedRecook ) + break; + + UPackage * Package = IterPackage.Value().Get(); + if ( Package ) + { + FString PackageName = Package->GetName(); + if ( !PackageName.IsEmpty() && ( PackageName != TEXT("None") ) ) + continue; + } + + bCookedContentNeedRecook = true; + } + + // Check the cooked landscape layers refer to something.. + for ( TMap< TWeakObjectPtr< UPackage >, FHoudiniGeoPartObject >::TIterator IterPackage( CookedTemporaryLandscapeLayers ); IterPackage; ++IterPackage ) + { + if ( bCookedContentNeedRecook ) + break; + + UPackage * Package = IterPackage.Key().Get(); + if ( Package ) + { + FString PackageName = Package->GetName(); + if ( !PackageName.IsEmpty() && ( PackageName != TEXT("None") ) ) + continue; + } + + bCookedContentNeedRecook = true; + } + + + if ( bCookedContentNeedRecook ) + StartTaskAssetCookingManual(); + + Super::PostEditUndo(); +} + +void +UHoudiniAssetComponent::PostEditImport() +{ + Super::PostEditImport(); + + AHoudiniAssetActor * CopiedActor = FHoudiniEngineUtils::LocateClipboardActor( GetOwner(), TEXT( "" ) ); + if ( CopiedActor ) + OnComponentClipboardCopy( CopiedActor->HoudiniAssetComponent ); +} + +void UHoudiniAssetComponent::SanitizePostLoad() +{ + AActor* Owner = GetOwner(); + + for(auto Iter : Parameters) + { + UHoudiniAssetParameter* Param = Iter.Value; + if( !Param || Param->IsPendingKill() ) + { + // we have at least one bad parameter, clear them all + FMessageLog("LoadErrors").Error(LOCTEXT("NullParameterFound", "Houdini Engine: Null parameter found, clearing parameters")) + ->AddToken(FUObjectToken::Create(Owner)); + Parameters.Empty(); + ParameterByName.Empty(); + break; + } + } + + for(auto Input : Inputs) + { + if( !Input || Input->IsPendingKill() ) + { + // we have at least one bad input, clear them all + FMessageLog("LoadErrors").Error(LOCTEXT("NullInputFound", "Houdini Engine: Null input found, clearing inputs")) + ->AddToken(FUObjectToken::Create(Owner)); + Inputs.Empty(); + break; + } + } + + // Patch any invalid material references + for( auto& SMCElem : StaticMeshComponents ) + { + UStaticMesh* SM = SMCElem.Key; + UMeshComponent* SMC = SMCElem.Value; + if ( !SM || SM->IsPendingKill() ) + continue; + + if ( !SMC || SMC->IsPendingKill() ) + continue; + + auto& StaticMeshMaterials = SM->StaticMaterials; + for( int32 MaterialIdx = 0; MaterialIdx < StaticMeshMaterials.Num(); ++MaterialIdx ) + { + if( nullptr == StaticMeshMaterials[ MaterialIdx ].MaterialInterface ) + { + auto DefaultMI = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + StaticMeshMaterials[ MaterialIdx ].MaterialInterface = DefaultMI; + SMC->SetMaterial( MaterialIdx, DefaultMI ); + } + } + } + + /* + // Fetch the dirty package that need to be reloaded + TArray DirtyPackages; + for (TMap > ::TIterator IterPackage(CookedTemporaryPackages); IterPackage; ++IterPackage) + { + if (!IterPackage.Value().IsValid()) + continue; + + UPackage * Package = IterPackage.Value().Get(); + if (!Package || UPackage::IsEmptyPackage(Package)) + continue; + + if (Package->IsDirty()) + DirtyPackages.Add(Package); + } + + for (TMap > ::TIterator IterPackage(CookedTemporaryStaticMeshPackages); IterPackage; ++IterPackage) + { + if (!IterPackage.Value().IsValid()) + continue; + + UPackage * Package = IterPackage.Value().Get(); + if (!Package || UPackage::IsEmptyPackage(Package)) + continue; + + if (Package->IsDirty()) + DirtyPackages.Add(Package); + } + + for (TMap, FHoudiniGeoPartObject > ::TIterator IterPackage(CookedTemporaryLandscapeLayers); IterPackage; ++IterPackage) + { + if (!IterPackage.Key().IsValid()) + continue; + + UPackage * Package = IterPackage.Key().Get(); + if (!Package || UPackage::IsEmptyPackage(Package)) + continue; + + if (Package->IsDirty()) + DirtyPackages.Add(Package); + } + + if (DirtyPackages.Num() > 0) + { + FlushAsyncLoading(); + + FText ErrorMessage; + UPackageTools::ReloadPackages(DirtyPackages, ErrorMessage, UPackageTools::EReloadPackagesInteractionMode::AssumePositive); + } + */ +} + +void +UHoudiniAssetComponent::PostInitProperties() +{ + Super::PostInitProperties(); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + if ( HoudiniRuntimeSettings ) + { + // Copy cooking defaults from settings. + bEnableCooking = HoudiniRuntimeSettings->bEnableCooking; + bUploadTransformsToHoudiniEngine = HoudiniRuntimeSettings->bUploadTransformsToHoudiniEngine; + bTransformChangeTriggersCooks = HoudiniRuntimeSettings->bTransformChangeTriggersCooks; + + // Copy static mesh generation parameters from settings. + bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry; + GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; + DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; + GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; + GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier; + GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; + GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; + bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; + GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier; + GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; + GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; + GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; + GeneratedDistanceFieldResolutionScale = HoudiniRuntimeSettings->GeneratedDistanceFieldResolutionScale; + } +} + +#endif + +bool +UHoudiniAssetComponent::LocateStaticMeshes( + const FString & ObjectName, + TMap< FString, TArray< FHoudiniGeoPartObject > > & InOutObjectsToInstance, bool bSubstring ) const +{ + // See if map has entry for this object name. + if ( !InOutObjectsToInstance.Contains( ObjectName ) ) + InOutObjectsToInstance.Add( ObjectName, TArray< FHoudiniGeoPartObject >() ); + + { + // Get array entry for this object name. + TArray< FHoudiniGeoPartObject > & Objects = InOutObjectsToInstance[ ObjectName ]; + + // Go through all geo part objects and see if we have matches. + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TConstIterator Iter( StaticMeshes ); Iter; ++Iter ) + { + const FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key(); + UStaticMesh * StaticMesh = Iter.Value(); + + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + continue; + + if ( ObjectName.Len() > 0 ) + { + if ( bSubstring && ObjectName.Len() >= HoudiniGeoPartObject.ObjectName.Len() ) + { + int32 Index = ObjectName.Find( + *HoudiniGeoPartObject.ObjectName, + ESearchCase::IgnoreCase, + ESearchDir::FromEnd, INDEX_NONE ); + + if ( ( Index != -1 ) && ( Index + HoudiniGeoPartObject.ObjectName.Len() == ObjectName.Len() ) ) + Objects.Add( HoudiniGeoPartObject ); + } + else if ( HoudiniGeoPartObject.ObjectName.Equals( ObjectName ) ) + { + Objects.Add( HoudiniGeoPartObject ); + } + } + } + } + + // Sort arrays. + for ( TMap< FString, TArray< FHoudiniGeoPartObject > >::TIterator Iter( InOutObjectsToInstance ); Iter; ++Iter ) + { + TArray< FHoudiniGeoPartObject > & Objects = Iter.Value(); + Objects.Sort( FHoudiniGeoPartObjectSortPredicate() ); + } + + return InOutObjectsToInstance.Num() > 0; +} + +bool +UHoudiniAssetComponent::LocateStaticMeshes( + int32 ObjectToInstanceId, + TArray< FHoudiniGeoPartObject > & InOutObjectsToInstance ) const +{ + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TConstIterator Iter( StaticMeshes ); Iter; ++Iter ) + { + const FHoudiniGeoPartObject& HoudiniGeoPartObject = Iter.Key(); + if ( HoudiniGeoPartObject.ObjectId == ObjectToInstanceId ) + { + // Check that this part isn't being instanced at the part level + if ( !HoudiniGeoPartObject.HapiPartIsInstanced() ) + InOutObjectsToInstance.Add( HoudiniGeoPartObject ); + } + } + + // Sort array. + InOutObjectsToInstance.Sort( FHoudiniGeoPartObjectSortPredicate() ); + + return InOutObjectsToInstance.Num() > 0; +} + + +FHoudiniGeoPartObject +UHoudiniAssetComponent::LocateGeoPartObject( UStaticMesh * StaticMesh ) const +{ + FHoudiniGeoPartObject GeoPartObject; + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + return GeoPartObject; + + const FHoudiniGeoPartObject * FoundGeoPartObject = StaticMeshes.FindKey( StaticMesh ); + if ( FoundGeoPartObject ) + GeoPartObject = *FoundGeoPartObject; + + return GeoPartObject; +} + +bool +UHoudiniAssetComponent::IsPIEActive() const +{ +#if WITH_EDITOR + for( const FWorldContext& Context : GEngine->GetWorldContexts() ) + { + if( Context.WorldType == EWorldType::PIE ) + { + return true; + } + } +#endif + return false; +} + +#if WITH_EDITOR + +void +UHoudiniAssetComponent::CreateCurves( const TArray< FHoudiniGeoPartObject > & FoundCurves ) +{ + bool bCurveCreated = false; + + TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* > NewSplineComponents; + for ( TArray< FHoudiniGeoPartObject >::TConstIterator Iter( FoundCurves ); Iter; ++Iter ) + { + const FHoudiniGeoPartObject & HoudiniGeoPartObject = *Iter; + + // Retrieve node id from geo part. + HAPI_NodeId NodeId = HoudiniGeoPartObject.HapiGeoGetNodeId( AssetId ); + if ( NodeId == -1 ) + { + // Invalid node id. + continue; + } + + if ( !HoudiniGeoPartObject.HasParameters( AssetId ) ) + { + // We have no parameters on this curve. + continue; + } + + // We need to cook the spline node. + FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), NodeId, nullptr); + + FString CurvePointsString; + EHoudiniSplineComponentType::Enum CurveTypeValue = EHoudiniSplineComponentType::Bezier; + EHoudiniSplineComponentMethod::Enum CurveMethodValue = EHoudiniSplineComponentMethod::CVs; + int32 CurveClosed = 1; + + HAPI_AttributeInfo AttributeRefinedCurvePositions; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); + + TArray< float > RefinedCurvePositions; + if ( !FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HoudiniGeoPartObject, HAPI_UNREAL_ATTRIB_POSITION, + AttributeRefinedCurvePositions, RefinedCurvePositions ) ) + { + continue; + } + + if ( !AttributeRefinedCurvePositions.exists && RefinedCurvePositions.Num() > 0 ) + continue; + + // Transfer refined positions to position vector and perform necessary axis swap. + TArray< FVector > CurveDisplayPoints; + FHoudiniEngineUtils::ConvertScaleAndFlipVectorData( RefinedCurvePositions, CurveDisplayPoints ); + + if ( !FHoudiniEngineUtils::HapiGetParameterDataAsString( + NodeId, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT(""), CurvePointsString) || + !FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + NodeId, HAPI_UNREAL_PARAM_CURVE_TYPE, (int32) EHoudiniSplineComponentType::Bezier, (int32&) CurveTypeValue ) || + !FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + NodeId, HAPI_UNREAL_PARAM_CURVE_METHOD, (int32) EHoudiniSplineComponentMethod::CVs, (int32&) CurveMethodValue ) || + !FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + NodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 1, CurveClosed ) ) + { + continue; + } + + // Process coords string and extract positions. + TArray< FVector > CurvePositions; + FHoudiniEngineUtils::ExtractStringPositions( CurvePointsString, CurvePositions ); + + // Check if this curve already exists. + UHoudiniSplineComponent * HoudiniSplineComponent = LocateSplineComponent( HoudiniGeoPartObject ); + + if ( HoudiniSplineComponent ) + { + // The curve already exists, we can reuse it. + // Remove it from old map. + SplineComponents.Remove( HoudiniGeoPartObject ); + } + else + { + // We need to create a new curve. + HoudiniSplineComponent = NewObject< UHoudiniSplineComponent >( + this, UHoudiniSplineComponent::StaticClass(), + NAME_None, RF_Public | RF_Transactional ); + + bCurveCreated = true; + } + + // Set the GeoPartObject + HoudiniSplineComponent->SetHoudiniGeoPartObject( HoudiniGeoPartObject ); + + // If we have no parent, we need to re-attach. + if ( !HoudiniSplineComponent->GetAttachParent() ) + HoudiniSplineComponent->AttachToComponent( this, FAttachmentTransformRules::KeepRelativeTransform ); + + HoudiniSplineComponent->SetVisibility( true ); + + // If component is not registered, register it. + if ( !HoudiniSplineComponent->IsRegistered() ) + HoudiniSplineComponent->RegisterComponent(); + + // Add to map of components. + NewSplineComponents.Add( HoudiniGeoPartObject, HoudiniSplineComponent ); + + // Transform the component by transformation provided by HAPI. + HoudiniSplineComponent->SetRelativeTransform( HoudiniGeoPartObject.TransformMatrix ); + + // Create Transform for the HoudiniSplineComponents + TArray< FTransform > CurvePoints; + CurvePoints.SetNumUninitialized(CurvePositions.Num()); + + FTransform trans = FTransform::Identity; + for (int32 n = 0; n < CurvePoints.Num(); n++) + { + trans.SetLocation(CurvePositions[n]); + CurvePoints[n] = trans; + } + + // Construct curve from available data. + HoudiniSplineComponent->Construct( + HoudiniGeoPartObject, CurvePoints, CurveDisplayPoints, CurveTypeValue, + CurveMethodValue, ( CurveClosed == 1 ) ); + } + +#if WITH_EDITOR + // The editor caches the current selection visualizer, so we need to trick + // and pretend the selection has changed so that the HSplineVisualizer can be drawn immediately + if ( bCurveCreated && GUnrealEd ) + GUnrealEd->NoteSelectionChange(); +#endif + + ClearCurves(); + SplineComponents = NewSplineComponents; +} + +void +UHoudiniAssetComponent::CreateParameters() +{ + TMap< HAPI_ParmId, class UHoudiniAssetParameter * > NewParameters; + if( FHoudiniParamUtils::Build(AssetId, this, Parameters, NewParameters) ) + { + bEditorPropertiesNeedFullUpdate = true; + + // Remove all unused parameters. + ClearParameters(); + + // Update parameters. + Parameters = NewParameters; + for ( auto& ParmPair : NewParameters ) + { + UHoudiniAssetParameter* Param = ParmPair.Value; + if( Param && !Param->IsPendingKill() ) + ParameterByName.Add( Param->GetParameterName(), Param ); + } + } +} + +void +UHoudiniAssetComponent::NotifyParameterChanged( UHoudiniAssetParameter * HoudiniAssetParameter ) +{ + if ( bLoadedComponent && !FHoudiniEngineUtils::IsValidNodeId( AssetId ) && !bAssetIsBeingInstantiated ) + bLoadedComponentRequiresInstantiation = true; + + if ( HoudiniAssetParameter ) + { + // Some parameter types won't require a full update of the editor panel + // This will avoid breaking the current selection + UClass* FoundClass = HoudiniAssetParameter->GetClass(); + /* + if ( FoundClass->IsChildOf< UHoudiniAssetParameterFloat >() + || FoundClass->IsChildOf< UHoudiniAssetParameterInt >() + || FoundClass->IsChildOf< UHoudiniAssetParameterString >() ) + */ + if ( !FoundClass->IsChildOf< UHoudiniAssetInput >() ) + bEditorPropertiesNeedFullUpdate = false; + } + + bParametersChanged = true; + StartHoudiniTicking(); +} + +void +UHoudiniAssetComponent::NotifyHoudiniSplineChanged( UHoudiniSplineComponent * HoudiniSplineComponent ) +{ + if ( bLoadedComponent && !FHoudiniEngineUtils::IsValidNodeId( AssetId ) && !bAssetIsBeingInstantiated ) + bLoadedComponentRequiresInstantiation = true; + + bParametersChanged = true; + StartHoudiniTicking(); +} + +void +UHoudiniAssetComponent::UnmarkChangedParameters() +{ + if ( bParametersChanged ) + { + for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams( Parameters ); IterParams; ++IterParams ) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if ( !HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill() ) + continue; + + // If parameter has changed, unmark it. + if ( HoudiniAssetParameter->HasChanged() ) + HoudiniAssetParameter->UnmarkChanged(); + } + } +} + +void +UHoudiniAssetComponent::UploadChangedParameters() +{ + bool Success = true; + + if ( bParametersChanged ) + { + // Upload inputs. + for ( TArray< UHoudiniAssetInput * >::TIterator IterInputs( Inputs ); IterInputs; ++IterInputs ) + { + UHoudiniAssetInput * HoudiniAssetInput = *IterInputs; + if ( !HoudiniAssetInput || HoudiniAssetInput->IsPendingKill() ) + continue; + + // If input has changed, upload it to HAPI. + if ( HoudiniAssetInput->HasChanged() ) + { + Success &= HoudiniAssetInput->UploadParameterValue(); + } + } + + // Upload parameters. + for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams( Parameters ); IterParams; ++IterParams ) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill()) + continue; + + // If parameter has changed, upload it to HAPI. + if ( HoudiniAssetParameter->HasChanged() ) + { + Success &= HoudiniAssetParameter->UploadParameterValue(); + } + } + } + + if( !Success ) + { + HOUDINI_LOG_ERROR(TEXT("%s UploadChangedParameters failed"), GetOwner() ? *GetOwner()->GetName() : *GetName()); + } + + // We no longer have changed parameters. + bParametersChanged = false; +} + +void +UHoudiniAssetComponent::UpdateLoadedParameters() +{ + if (!FHoudiniEngineUtils::IsValidNodeId(AssetId)) + return; + + for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams( Parameters ); IterParams; ++IterParams ) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill()) + continue; + + HoudiniAssetParameter->SetNodeId( AssetId ); + } +} + +bool +UHoudiniAssetComponent::CreateHandles() +{ + if ( !FHoudiniEngineUtils::IsValidNodeId( AssetId ) ) + { + // There's no Houdini asset, we can return. + return false; + } + + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + if ( FHoudiniApi::GetAssetInfo( FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ) != HAPI_RESULT_SUCCESS ) + return false; + + FHandleComponentMap NewHandleComponents; + + // If we have handles. + if ( AssetInfo.handleCount > 0 ) + { + TArray< HAPI_HandleInfo > HandleInfos; + HandleInfos.SetNumZeroed( AssetInfo.handleCount ); + for (int32 Idx = 0; Idx < HandleInfos.Num(); Idx++) + FHoudiniApi::HandleInfo_Init(&(HandleInfos[Idx])); + + if ( FHoudiniApi::GetHandleInfo( + FHoudiniEngine::Get().GetSession(), AssetId, + &HandleInfos[ 0 ], 0, AssetInfo.handleCount ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + for ( int32 HandleIdx = 0; HandleIdx < AssetInfo.handleCount; ++HandleIdx ) + { + // Retrieve handle info for this index. + const HAPI_HandleInfo & HandleInfo = HandleInfos[ HandleIdx ]; + + // If we do not have bindings, we can skip. + if ( HandleInfo.bindingsCount <= 0 ) + continue; + + FString TypeName = TEXT( "" ); + EHoudiniHandleType HandleType = EHoudiniHandleType::Unsupported; + { + FHoudiniEngineString HoudiniEngineString( HandleInfo.typeNameSH ); + if( !HoudiniEngineString.ToFString( TypeName ) ) + { + continue; + } + + if( TypeName.Equals( TEXT( HAPI_UNREAL_HANDLE_TRANSFORM ) ) ) + HandleType = EHoudiniHandleType::Xform; + else if( TypeName.Equals( TEXT( HAPI_UNREAL_HANDLE_BOUNDER ) ) ) + HandleType = EHoudiniHandleType::Bounder; + } + + FString HandleName = TEXT( "" ); + + { + FHoudiniEngineString HoudiniEngineString( HandleInfo.nameSH ); + if ( !HoudiniEngineString.ToFString( HandleName ) ) + continue; + } + + if( HandleType == EHoudiniHandleType::Unsupported ) + { + HOUDINI_LOG_DISPLAY( TEXT( "%s: Unsupported Handle Type %s for handle %s" ), GetOwner() ? *(GetOwner()->GetName()) : *GetName(), *TypeName, *HandleName ); + continue; + } + + UHoudiniHandleComponent * HandleComponent = nullptr; + UHoudiniHandleComponent ** FoundHandleComponent = HandleComponents.Find( HandleName ); + + if ( FoundHandleComponent ) + { + HandleComponent = *FoundHandleComponent; + + // Remove so that it's not destroyed. + HandleComponents.Remove( HandleName ); + } + else + { + HandleComponent = NewObject< UHoudiniHandleComponent >( + this, UHoudiniHandleComponent::StaticClass(), + NAME_None, RF_Public | RF_Transactional ); + } + + if ( !HandleComponent ) + continue; + + // If we have no parent, we need to re-attach. + if ( !HandleComponent->GetAttachParent() ) + HandleComponent->AttachToComponent( this, FAttachmentTransformRules::KeepRelativeTransform ); + + HandleComponent->SetVisibility( true ); + + // If component is not registered, register it. + if ( !HandleComponent->IsRegistered() ) + HandleComponent->RegisterComponent(); + + if ( HandleComponent->Construct( AssetId, HandleIdx, HandleName, HandleInfo, Parameters, HandleType ) ) + NewHandleComponents.Add( HandleName, HandleComponent ); + } + } + + ClearHandles(); + HandleComponents = NewHandleComponents; + + return true; +} + +void +UHoudiniAssetComponent::CreateInputs() +{ + if ( !FHoudiniEngineUtils::IsValidNodeId( AssetId ) ) + { + // There's no Houdini asset, we can return. + return; + } + + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + int32 InputCount = 0; + if ( FHoudiniApi::GetAssetInfo( FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ) == HAPI_RESULT_SUCCESS + && AssetInfo.hasEverCooked ) + { + InputCount = AssetInfo.geoInputCount; + } + + // We've already created the number of required inputs. + if( Inputs.Num() == InputCount ) + return; + + // Resize our inputs to match the asset + if( InputCount == 0 ) + { + ClearInputs(); + } + else + { + if( InputCount > Inputs.Num() ) + { + int32 NumNewInputs = InputCount - Inputs.Num(); + for( int32 InputIdx = Inputs.Num(); InputIdx < InputCount; ++InputIdx ) + Inputs.Add( UHoudiniAssetInput::Create( this, InputIdx, AssetId ) ); + } + else + { + // Must be fewer inputs + for( int32 InputIdx = InputCount; InputIdx < Inputs.Num(); ++InputIdx ) + { + Inputs[ InputIdx ]->ConditionalBeginDestroy(); + } + Inputs.SetNum( InputCount ); + } + } + +} + +void +UHoudiniAssetComponent::UpdateLoadedInputs( const bool& ForceRefresh ) +{ + if ( !FHoudiniEngineUtils::IsValidNodeId(AssetId) ) + return; + + bool Success = true; + for ( TArray< UHoudiniAssetInput * >::TIterator IterInputs( Inputs ); IterInputs; ++IterInputs ) + { + UHoudiniAssetInput * HoudiniAssetInput = *IterInputs; + if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill() ) + continue; + + HoudiniAssetInput->SetNodeId( AssetId ); + + // If we're updating the input landscape data, we need to restore the original input geometry + // to submit it as the source mesh + EHoudiniAssetInputType::Enum InputType = HoudiniAssetInput->GetChoiceIndex(); + if (InputType == EHoudiniAssetInputType::LandscapeInput + && HoudiniAssetInput->IsUpdatingInputLandscape() ) + //&& !HoudiniAssetInput->IsLandscapeAssetConnected() ) + FHoudiniLandscapeUtils::RestoreLandscapeFromFile( HoudiniAssetInput->GetLandscapeInput() ); + + Success &= HoudiniAssetInput->ChangeInputType( HoudiniAssetInput->GetChoiceIndex(), ForceRefresh ); + Success &= HoudiniAssetInput->UploadParameterValue(); + } + + if( !Success ) + { + HOUDINI_LOG_ERROR(TEXT("%s UpdateLoadedInputs failed"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); + } +} + +bool +UHoudiniAssetComponent::UpdateWaitingForUpstreamAssetsToInstantiate( bool bNotifyUpstreamAsset ) +{ + bWaitingForUpstreamAssetsToInstantiate = false; + + // We first need to make sure all our asset inputs have been instantiated and reconnected. + for ( auto LocalInput : Inputs ) + { + if ( !LocalInput || LocalInput->IsPendingKill() ) + continue; + + bool bInputAssetNeedsInstantiation = LocalInput->DoesInputAssetNeedInstantiation(); + if ( !bInputAssetNeedsInstantiation ) + continue; + + bWaitingForUpstreamAssetsToInstantiate = true; + + if ( bNotifyUpstreamAsset ) + { + UHoudiniAssetComponent * LocalInputAssetComponent = LocalInput->GetConnectedInputAssetComponent(); + if ( LocalInputAssetComponent && !LocalInputAssetComponent->IsPendingKill() ) + LocalInputAssetComponent->NotifyParameterChanged( nullptr ); + } + } + + for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams( Parameters ); IterParams; ++IterParams ) + { + UHoudiniAssetInput* Input = Cast< UHoudiniAssetInput >( IterParams.Value() ); + if ( !Input || Input->IsPendingKill() ) + continue; + + bool bInputAssetNeedsInstantiation = Input->DoesInputAssetNeedInstantiation(); + if ( !bInputAssetNeedsInstantiation ) + continue; + + bWaitingForUpstreamAssetsToInstantiate = true; + + if ( bNotifyUpstreamAsset ) + { + UHoudiniAssetComponent * LocalInputAssetComponent = Input->GetConnectedInputAssetComponent(); + if ( LocalInputAssetComponent && !LocalInputAssetComponent->IsPendingKill() ) + LocalInputAssetComponent->NotifyParameterChanged( nullptr ); + } + } + + return bWaitingForUpstreamAssetsToInstantiate; +} + +bool +UHoudiniAssetComponent::RefreshEditableNodesAfterLoad() +{ + // For some reason, we need to go through all the editable nodes once + // To "Activate" them... + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + // Retrieve information about each object contained within our asset. + TArray< HAPI_ObjectInfo > ObjectInfos; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos)) + return false; + + // Iterate through all objects. + for (int32 ObjectIdx = 0; ObjectIdx < ObjectInfos.Num(); ++ObjectIdx) + { + // Retrieve object at this index. + const HAPI_ObjectInfo & ObjectInfo = ObjectInfos[ObjectIdx]; + + // Get all the GeoInfos for the editable nodes + int32 EditableNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), ObjectInfo.nodeId, + HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, + true, &EditableNodeCount), false); + + if (EditableNodeCount <= 0) + continue; + + TArray< HAPI_NodeId > EditableNodeIds; + EditableNodeIds.SetNumUninitialized(EditableNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), AssetId, + EditableNodeIds.GetData(), EditableNodeCount), false); + + for (int nEditable = 0; nEditable < EditableNodeCount; nEditable++) + { + HAPI_GeoInfo CurrentEditableGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + EditableNodeIds[nEditable], + &CurrentEditableGeoInfo), false); + + if ( CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE ) + continue; + + FString NodePathTemp; + if ( !FHoudiniEngineUtils::HapiGetNodePath( CurrentEditableGeoInfo.nodeId, AssetId, NodePathTemp ) ) + continue; + + // We need to refresh the spline corresponding to that node + for ( TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* >::TIterator Iter( SplineComponents ); Iter; ++Iter ) + { + FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key(); + + // Get NodePath appends the partId to the node path, so we use contains instead of equals + if ( HoudiniGeoPartObject.GetNodePath().Contains( NodePathTemp ) ) + { + // Update the Geo/Node Id + HoudiniGeoPartObject.GeoId = CurrentEditableGeoInfo.nodeId; + + // Update the attached spline component too + UHoudiniSplineComponent * SplineComponent = Iter.Value(); + if ( SplineComponent ) + SplineComponent->SetHoudiniGeoPartObject( HoudiniGeoPartObject ); + } + } + } + } + + return true; +} + + +void +UHoudiniAssetComponent::UploadLoadedCurves() +{ + for ( TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* >::TIterator Iter( SplineComponents ); Iter; ++Iter ) + { + UHoudiniSplineComponent * HoudiniSplineComponent = Iter.Value(); + if ( !HoudiniSplineComponent ) + continue; + + HoudiniSplineComponent->UploadControlPoints(); + } +} + +UHoudiniAssetInstanceInput* +UHoudiniAssetComponent::LocateInstanceInput( const FHoudiniGeoPartObject& GeoPart ) const +{ + for ( UHoudiniAssetInstanceInput* InstanceInput : InstanceInputs ) + { + if ( !InstanceInput || InstanceInput->IsPendingKill() ) + continue; + + // Verify path to instancer + various configuration flags + if ( InstanceInput->GetGeoPartObject().GetNodePath() == GeoPart.GetNodePath() && + UHoudiniAssetInstanceInput::GetInstancerFlags(GeoPart).HoudiniAssetInstanceInputFlagsPacked == + InstanceInput->Flags.HoudiniAssetInstanceInputFlagsPacked ) + { + return InstanceInput; + } + } + return nullptr; +} + +void +UHoudiniAssetComponent::CreateInstanceInputs( const TArray< FHoudiniGeoPartObject > & Instancers ) +{ + TArray< UHoudiniAssetInstanceInput * > NewInstanceInputs; + + for ( const FHoudiniGeoPartObject& GeoPart : Instancers ) + { + if ( GeoPart.IsVisible() ) + { + // Check if this instance input already exists. + UHoudiniAssetInstanceInput * HoudiniAssetInstanceInput = nullptr; + + UHoudiniAssetInstanceInput * FoundHoudiniAssetInstanceInput = LocateInstanceInput(GeoPart); + if ( FoundHoudiniAssetInstanceInput && !FoundHoudiniAssetInstanceInput->IsPendingKill() ) + { + // Input already exists, we can reuse it. + HoudiniAssetInstanceInput = FoundHoudiniAssetInstanceInput; + + // Since this is the corresponding part, we will refresh the InstanceInput's GeoPart + HoudiniAssetInstanceInput->SetGeoPartObject( GeoPart ); + + // Remove it from old map. + InstanceInputs.Remove( FoundHoudiniAssetInstanceInput ); + } + else + { + // Otherwise we need to create new instance input. + HoudiniAssetInstanceInput = UHoudiniAssetInstanceInput::Create( this, GeoPart ); + } + + if ( !HoudiniAssetInstanceInput || HoudiniAssetInstanceInput->IsPendingKill() ) + { + // Invalid instance input. + HOUDINI_LOG_WARNING( TEXT( "%s: Failed to create Instancer from part %s" ), GetOwner() ? *(GetOwner()->GetName()) : *GetName(), *GeoPart.GetNodePath() ); + } + else + { + // Add input to new map. + NewInstanceInputs.Add( HoudiniAssetInstanceInput ); + // Create or re-create this input. + HoudiniAssetInstanceInput->CreateInstanceInput(); + } + } + } + + // Clear all the existing instance inputs and replace with the new + ClearInstanceInputs(); + InstanceInputs = NewInstanceInputs; +} + +void +UHoudiniAssetComponent::DuplicateParameters( UHoudiniAssetComponent * DuplicatedHoudiniComponent ) +{ + if ( !DuplicatedHoudiniComponent || DuplicatedHoudiniComponent->IsPendingKill() ) + return; + + TMap< HAPI_ParmId, UHoudiniAssetParameter * > & InParameters = DuplicatedHoudiniComponent->Parameters; + TMap< FString, UHoudiniAssetParameter * > & InParametersByName = DuplicatedHoudiniComponent->ParameterByName; + + for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams( Parameters ); IterParams; ++IterParams ) + { + HAPI_ParmId HoudiniAssetParameterKey = IterParams.Key(); + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if ( !HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill() ) + continue; + + if (HoudiniAssetParameterKey == -1) + continue; + + // Duplicate parameter. + UHoudiniAssetParameter * DuplicatedHoudiniAssetParameter = HoudiniAssetParameter->Duplicate( DuplicatedHoudiniComponent ); + if (!DuplicatedHoudiniAssetParameter || DuplicatedHoudiniAssetParameter->IsPendingKill()) + continue; + + // PIE does not like standalone flags. + DuplicatedHoudiniAssetParameter->ClearFlags( RF_Standalone ); + DuplicatedHoudiniAssetParameter->SetHoudiniAssetComponent( DuplicatedHoudiniComponent ); + + // For Input Parameters + UHoudiniAssetInput* HoudiniAssetInputParameter = Cast< UHoudiniAssetInput >(HoudiniAssetParameter); + UHoudiniAssetInput* DuplicatedHoudiniAssetInputParameter = Cast< UHoudiniAssetInput >(DuplicatedHoudiniAssetParameter); + if(HoudiniAssetInputParameter && DuplicatedHoudiniAssetInputParameter) + { + // Invalidate the node ids on the duplicate so that new inputs will be created. + DuplicatedHoudiniAssetInputParameter->InvalidateNodeIds(); + + // We also need to duplicate the attached curves properly + DuplicatedHoudiniAssetInputParameter->DuplicateCurves(HoudiniAssetInputParameter); + } + + InParameters.Add( HoudiniAssetParameterKey, DuplicatedHoudiniAssetParameter ); + InParametersByName.Add( + DuplicatedHoudiniAssetParameter->GetParameterName(), + DuplicatedHoudiniAssetParameter ); + } +} + +void +UHoudiniAssetComponent::DuplicateHandles( UHoudiniAssetComponent * SrcAssetComponent ) +{ + if (!SrcAssetComponent || SrcAssetComponent->IsPendingKill()) + return; + + for ( auto const & SrcNameToHandle : SrcAssetComponent->HandleComponents ) + { + if (!SrcNameToHandle.Value || SrcNameToHandle.Value->IsPendingKill()) + continue; + + // Duplicate spline component. + UHoudiniHandleComponent * NewHandleComponent = + DuplicateObject< UHoudiniHandleComponent >( SrcNameToHandle.Value, this ); + + if ( NewHandleComponent && !NewHandleComponent->IsPendingKill() ) + { + NewHandleComponent->SetFlags( RF_Transactional | RF_Public ); + NewHandleComponent->ResolveDuplicatedParameters( Parameters ); + HandleComponents.Add( SrcNameToHandle.Key, NewHandleComponent ); + } + } +} + +void +UHoudiniAssetComponent::DuplicateInputs( UHoudiniAssetComponent * DuplicatedHoudiniComponent ) +{ + if (!DuplicatedHoudiniComponent || DuplicatedHoudiniComponent->IsPendingKill()) + return; + + TArray< UHoudiniAssetInput * > & InInputs = DuplicatedHoudiniComponent->Inputs; + for ( int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx ) + { + // Retrieve input at this index. + UHoudiniAssetInput * AssetInput = Inputs[ InputIdx ]; + if (!AssetInput || AssetInput->IsPendingKill() ) + continue; + + // Duplicate input. + UHoudiniAssetInput * DuplicateAssetInput = DuplicateObject( AssetInput, DuplicatedHoudiniComponent ); + if (!DuplicateAssetInput || DuplicateAssetInput->IsPendingKill() ) + continue; + + DuplicateAssetInput->SetHoudiniAssetComponent( DuplicatedHoudiniComponent ); + + // Invalidate the node ids on the duplicate so that new inputs will be created. + DuplicateAssetInput->InvalidateNodeIds(); + + // We also need to duplicate the attached curves properly + DuplicateAssetInput->DuplicateCurves(AssetInput); + + // PIE does not like standalone flags. + DuplicateAssetInput->ClearFlags( RF_Standalone ); + + InInputs.Add( DuplicateAssetInput ); + } +} + +void +UHoudiniAssetComponent::DuplicateInstanceInputs( UHoudiniAssetComponent * DuplicatedHoudiniComponent, const TMap& ReplacementMap ) +{ + if ( !DuplicatedHoudiniComponent || DuplicatedHoudiniComponent->IsPendingKill() ) + return; + + auto& InInstanceInputs = DuplicatedHoudiniComponent->InstanceInputs; + + for ( auto& HoudiniAssetInstanceInput : InstanceInputs ) + { + if ( !HoudiniAssetInstanceInput && HoudiniAssetInstanceInput->IsPendingKill() ) + continue; + + UHoudiniAssetInstanceInput * DuplicatedHoudiniAssetInstanceInput = + UHoudiniAssetInstanceInput::Create( DuplicatedHoudiniComponent, HoudiniAssetInstanceInput ); + + if ( !DuplicatedHoudiniAssetInstanceInput || DuplicatedHoudiniAssetInstanceInput->IsPendingKill() ) + continue; + + // PIE does not like standalone flags. + DuplicatedHoudiniAssetInstanceInput->ClearFlags( RF_Standalone ); + + InInstanceInputs.Add( DuplicatedHoudiniAssetInstanceInput ); + + // remap our instanced objects (only necessary for unbaked assets) + for( UHoudiniAssetInstanceInputField* InputField : DuplicatedHoudiniAssetInstanceInput->GetInstanceInputFields() ) + { + if ( !InputField || InputField->IsPendingKill() ) + continue; + + InputField->FixInstancedObjects( ReplacementMap ); + } + } +} + +bool +UHoudiniAssetComponent::CreateAllLandscapes( const TArray< FHoudiniGeoPartObject > & FoundVolumes ) +{ + if ( FoundVolumes.Num() <= 0 ) + return false; + + // Try to create a Landscape for each HeightData found + TMap< FHoudiniGeoPartObject, TWeakObjectPtr > NewLandscapes; + + FHoudiniCookParams HoudiniCookParams( this ); + HoudiniCookParams.StaticMeshBakeMode = FHoudiniCookParams::GetDefaultStaticMeshesCookMode(); + HoudiniCookParams.MaterialAndTextureBakeMode = FHoudiniCookParams::GetDefaultMaterialAndTextureCookMode(); + + // Keep a map of the valid landscape, this is to ensure that none of the new/input landscapes will be destroyed when cleaning up the old map + TArray ValidLandscapes; + // See if we have any landscape input that is marked as needing an update + TArray InputLandscapesToUpdate; + // Get all the inputs, including the object merge inputs + TArray< UHoudiniAssetInput * > AllInputs; + GetInputs(AllInputs, true); + for ( auto CurrentInput : AllInputs) + { + if ( !CurrentInput ) + continue; + + if ( CurrentInput->GetChoiceIndex() != EHoudiniAssetInputType::Enum::LandscapeInput ) + continue; + + ALandscapeProxy* InputLandscape = CurrentInput->GetLandscapeInput(); + if ( !InputLandscape || InputLandscape->IsPendingKill() ) + continue; + + ValidLandscapes.Add( InputLandscape ); + + if ( CurrentInput->IsUpdatingInputLandscape() ) + InputLandscapesToUpdate.Add( CurrentInput->GetLandscapeInput() ); + } + + if ( !FHoudiniLandscapeUtils::CreateAllLandscapes( HoudiniCookParams, FoundVolumes, LandscapeComponents, NewLandscapes, InputLandscapesToUpdate ) ) + return false; + + // The asset needs to be static in order to attach the landscapes to it + SetMobility( EComponentMobility::Static ); + + for ( TMap< FHoudiniGeoPartObject, TWeakObjectPtr >::TIterator IterLandscape( NewLandscapes ); IterLandscape; ++IterLandscape ) + { + ALandscapeProxy* NewLandscape = IterLandscape.Value().Get(); + if ( !NewLandscape || !NewLandscape->IsValidLowLevel() ) + continue; + + // Add the new landscape to the valid list to avoid its destruction if we updated it + ValidLandscapes.Add( NewLandscape ); + + // Attach the new landscapes to ourselves if we dont already own it + if ( GetAttachChildren().Find( NewLandscape->GetRootComponent() ) == INDEX_NONE ) + NewLandscape->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + + FHoudiniGeoPartObject Heightfield = IterLandscape.Key(); + + // Update the materials from our assignement/replacement and the materials assigned on the previous version of this landscape + UpdateLandscapeMaterialsAssignementsAndReplacements( NewLandscape, Heightfield ); + + // Replace any reference we might still have to the old landscape with the new one + TWeakObjectPtr* OldLandscapePtr = LandscapeComponents.Find( Heightfield ); + if ( !OldLandscapePtr) + continue; + + ALandscapeProxy* OldLandscape = OldLandscapePtr->Get(); + if ( !OldLandscape || !OldLandscape->IsValidLowLevel() ) + continue; + + if ( OldLandscape != NewLandscape ) + FHoudiniLandscapeUtils::UpdateOldLandscapeReference( OldLandscape, NewLandscape ); + } + + // Replace the old landscapes map with the new ones + for (TMap< FHoudiniGeoPartObject, TWeakObjectPtr >::TIterator Iter( LandscapeComponents ); Iter; ++Iter) + { + ALandscapeProxy * HoudiniLandscape = Iter.Value().Get(); + if ( !HoudiniLandscape || HoudiniLandscape->IsPendingKill() || !HoudiniLandscape->IsValidLowLevel() ) + continue; + + if ( ValidLandscapes.Contains( HoudiniLandscape ) ) + continue; + + HoudiniLandscape->UnregisterAllComponents(); + HoudiniLandscape->Destroy(); + } + + LandscapeComponents.Empty(); + LandscapeComponents = NewLandscapes; + + return true; +} + +void UHoudiniAssetComponent::UpdateLandscapeMaterialsAssignementsAndReplacements(ALandscapeProxy* Landscape, FHoudiniGeoPartObject Heightfield ) +{ + if ( !Landscape ) + return; + + // Handle the material assignment/replacement here + UMaterialInterface* LandscapeMaterial = Landscape->GetLandscapeMaterial(); + if ( LandscapeMaterial ) + { + // Make sure this material is in the assignements before trying to replacing it. + if ( !GetAssignmentMaterial( LandscapeMaterial->GetName() ) && HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() ) + HoudiniAssetComponentMaterials->Assignments.Add( LandscapeMaterial->GetName(), LandscapeMaterial ); + + // See if we have a replacement material for this. + UMaterialInterface * ReplacementMaterialInterface = GetReplacementMaterial( Heightfield, LandscapeMaterial->GetName() ); + if ( ReplacementMaterialInterface ) + LandscapeMaterial = ReplacementMaterialInterface; + } + + UMaterialInterface* LandscapeHoleMaterial = Landscape->GetLandscapeHoleMaterial(); + if ( LandscapeHoleMaterial ) + { + // Make sure this material is in the assignemets before trying to replacing it. + if ( !GetAssignmentMaterial( LandscapeHoleMaterial->GetName() ) && HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() ) + HoudiniAssetComponentMaterials->Assignments.Add( LandscapeHoleMaterial->GetName(), LandscapeHoleMaterial ); + + // See if we have a replacement material for this. + UMaterialInterface * ReplacementMaterialInterface = GetReplacementMaterial( Heightfield, LandscapeHoleMaterial->GetName() ); + if ( ReplacementMaterialInterface ) + LandscapeHoleMaterial = ReplacementMaterialInterface; + } + + // Try to see if we can find materials used by the previous landscape for this Heightfield + // ( the user might have replaced the landscape materials manually without us knowing it ) + if ( LandscapeComponents.Contains( Heightfield ) ) + { + ALandscapeProxy* PreviousLandscape = LandscapeComponents[ Heightfield ].Get(); + if ( PreviousLandscape && PreviousLandscape->IsValidLowLevel() ) + { + // Get the previously used materials, but ignore the default ones + UMaterialInterface* PreviousLandscapeMaterial = PreviousLandscape->GetLandscapeMaterial(); + if ( PreviousLandscapeMaterial == UMaterial::GetDefaultMaterial( MD_Surface ) ) + PreviousLandscapeMaterial = nullptr; + + if ( PreviousLandscapeMaterial && PreviousLandscapeMaterial != LandscapeMaterial ) + { + ReplaceMaterial( Heightfield, PreviousLandscapeMaterial, LandscapeMaterial, 0 ); + LandscapeMaterial = PreviousLandscapeMaterial; + } + + // Do the same thing for the hole material + UMaterialInterface* PreviousLandscapeHoleMaterial = PreviousLandscape->GetLandscapeHoleMaterial(); + if ( PreviousLandscapeHoleMaterial == UMaterial::GetDefaultMaterial( MD_Surface ) ) + PreviousLandscapeHoleMaterial = nullptr; + + if ( PreviousLandscapeHoleMaterial && PreviousLandscapeHoleMaterial != LandscapeHoleMaterial ) + { + ReplaceMaterial( Heightfield, PreviousLandscapeHoleMaterial, LandscapeHoleMaterial, 0 ); + LandscapeHoleMaterial = PreviousLandscapeHoleMaterial; + } + } + } + + // Assign the new material if they have been updated + if ( Landscape->LandscapeMaterial != LandscapeMaterial ) + Landscape->LandscapeMaterial = LandscapeMaterial; + + if ( Landscape->LandscapeHoleMaterial != LandscapeHoleMaterial ) + Landscape->LandscapeHoleMaterial = LandscapeHoleMaterial; + + Landscape->UpdateAllComponentMaterialInstances(); + + /* + // As UpdateAllComponentMaterialInstances() is not accessible to us, we'll try to access the Material's UProperty + // to trigger a fake Property change event that will call the Update function... + UProperty* FoundProperty = FindField< UProperty >(Landscape->GetClass(), (MaterialIdx == 0) ? TEXT("LandscapeMaterial") : TEXT("LandscapeHoleMaterial")); + if (FoundProperty) + { + FPropertyChangedEvent PropChanged(FoundProperty, EPropertyChangeType::ValueSet); + Landscape->PostEditChangeProperty(PropChanged); + } + */ +} + +bool +UHoudiniAssetComponent::ReplaceLandscapeInInputs(ALandscapeProxy* Old, ALandscapeProxy* New ) +{ + bool bReturn = false; + + // First, get all the inputs, including the object path parameters + TArray< UHoudiniAssetInput * > AllInputs; + GetInputs( AllInputs, true ); + + // Look for landscape input, selecting the old landscape + for ( TArray< UHoudiniAssetInput * >::TIterator IterInputs( AllInputs ); IterInputs; ++IterInputs ) + { + UHoudiniAssetInput * HoudiniAssetInput = *IterInputs; + if ( !HoudiniAssetInput || HoudiniAssetInput->IsPendingKill() ) + continue; + + if ( HoudiniAssetInput->GetChoiceIndex() != EHoudiniAssetInputType::LandscapeInput ) + continue; + + if ( Old != HoudiniAssetInput->GetLandscapeInput() ) + continue; + + HoudiniAssetInput->OnLandscapeActorSelected( New ); + HoudiniAssetInput->UploadParameterValue(); + + bReturn = true; + } + + return bReturn; +} + +#endif + +void +UHoudiniAssetComponent::ClearInstanceInputs() +{ + for ( auto& InstanceInput : InstanceInputs ) + { + if ( InstanceInput && !InstanceInput->IsPendingKill() ) + InstanceInput->ConditionalBeginDestroy(); + } + + InstanceInputs.Empty(); +} + +void +UHoudiniAssetComponent::ClearCurves() +{ + for ( TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent*>::TIterator Iter( SplineComponents ); Iter; ++Iter ) + { + UHoudiniSplineComponent * SplineComponent = Iter.Value(); + if (SplineComponent) + { + SplineComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SplineComponent->UnregisterComponent(); + SplineComponent->DestroyComponent(); + } + } + + SplineComponents.Empty(); +} + +void +UHoudiniAssetComponent::ClearLandscapes() +{ + for (TMap< FHoudiniGeoPartObject, TWeakObjectPtr >::TIterator Iter(LandscapeComponents); Iter; ++Iter) + { + ALandscapeProxy * HoudiniLandscape = Iter.Value().Get(); + if ( !IsValid( HoudiniLandscape ) || !HoudiniLandscape->IsValidLowLevel() ) + continue; + + // Make sure we never destroy an input landscape + bool IsCurrentLandscapeAnInput = false; + for (auto CurrentInput : Inputs) + { + if (!CurrentInput) + continue; + + if (CurrentInput->GetChoiceIndex() != EHoudiniAssetInputType::Enum::LandscapeInput) + continue; + + ALandscapeProxy* InputLandscape = CurrentInput->GetLandscapeInput(); + if (!InputLandscape || InputLandscape->IsPendingKill()) + continue; + + IsCurrentLandscapeAnInput = true; + break; + } + + if ( IsCurrentLandscapeAnInput ) + continue; + + HoudiniLandscape->UnregisterAllComponents(); + HoudiniLandscape->Destroy(); + } + + LandscapeComponents.Empty(); +} + +void +UHoudiniAssetComponent::ClearParameters() +{ + for ( auto Iter : Parameters ) + { + if ( Iter.Value && !Iter.Value->IsPendingKill() ) + { + Iter.Value->ConditionalBeginDestroy(); + } + else if(GetWorld() != NULL && GetWorld()->WorldType != EWorldType::PIE) + { + // Avoid spamming that error when leaving PIE mode + HOUDINI_LOG_WARNING(TEXT("%s: null parameter when clearing"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); + } + } + + Parameters.Empty(); + ParameterByName.Empty(); +} + +void +UHoudiniAssetComponent::ClearHandles() +{ + for ( auto & NameToComponent : HandleComponents ) + { + UHoudiniHandleComponent * HandleComponent = NameToComponent.Value; + + HandleComponent->DetachFromComponent( FDetachmentTransformRules::KeepRelativeTransform ); + HandleComponent->UnregisterComponent(); + HandleComponent->DestroyComponent(); + } + + HandleComponents.Empty(); +} + +void +UHoudiniAssetComponent::ClearInputs() +{ + for ( TArray< UHoudiniAssetInput * >::TIterator IterInputs( Inputs ); IterInputs; ++IterInputs ) + { + UHoudiniAssetInput * HoudiniAssetInput = *IterInputs; + if ( !HoudiniAssetInput || HoudiniAssetInput->IsPendingKill() ) + continue; + + if (HoudiniAssetInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) + continue; + + // Destroy connected Houdini asset. + HoudiniAssetInput->ConditionalBeginDestroy(); + } + + Inputs.Empty(); +} + +void +UHoudiniAssetComponent::ClearDownstreamAssets() +{ + for ( TMap< UHoudiniAssetComponent *, TSet< int32 > >::TIterator IterAssets(DownstreamAssetConnections ); + IterAssets; ++IterAssets ) + { + UHoudiniAssetComponent * DownstreamAsset = IterAssets.Key(); + if (!DownstreamAsset || DownstreamAsset->IsPendingKill()) + continue; + + TSet< int32 > & LocalInputIndicies = IterAssets.Value(); + for ( auto LocalInputIndex : LocalInputIndicies ) + { + if( DownstreamAsset->Inputs.IsValidIndex( LocalInputIndex ) + && DownstreamAsset->Inputs[ LocalInputIndex ] != nullptr + && !DownstreamAsset->Inputs[LocalInputIndex]->IsPendingKill() ) + { + DownstreamAsset->Inputs[ LocalInputIndex ]->ExternalDisconnectInputAssetActor(); + } + else + { + // It could be connected as an operator path parameter + bool DidADisconnect = false; + for( auto& OtherParmElem : DownstreamAsset->Parameters ) + { + UHoudiniAssetInput* OtherParm = Cast(OtherParmElem.Value); + if (!OtherParm || OtherParm->IsPendingKill()) + continue; + + if( OtherParm->GetConnectedInputAssetComponent() == this && OtherParm->IsInputAssetConnected() ) + { + OtherParm->ExternalDisconnectInputAssetActor(); + DidADisconnect = true; + } + } + + if ( !DidADisconnect ) + HOUDINI_LOG_ERROR( TEXT( "%s: Invalid downstream asset connection" ), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); + } + } + } + + DownstreamAssetConnections.Empty(); +} + +void +UHoudiniAssetComponent::ClearCookTempFile() +{ + // First, Clean up the assignement/replacement map + if ( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() ) + HoudiniAssetComponentMaterials->ResetMaterialInfo(); + + // Then delete all the materials + for ( TMap > ::TIterator IterPackage(CookedTemporaryPackages ); + IterPackage; ++IterPackage) + { + UPackage * Package = IterPackage.Value().Get(); + if ( !Package ) + continue; + + Package->ClearFlags( RF_Standalone ); + //Package->ConditionalBeginDestroy(); + } + + CookedTemporaryPackages.Empty(); + + // Delete all cooked Static Meshes + for ( TMap > ::TIterator IterPackage( CookedTemporaryStaticMeshPackages ); + IterPackage; ++IterPackage ) + { + UPackage * Package = IterPackage.Value().Get(); + if ( !Package ) + continue; + + Package->ClearFlags( RF_Standalone ); + //Package->ConditionalBeginDestroy(); + } + + CookedTemporaryStaticMeshPackages.Empty(); + + // Delete all cooked Landscape Layers + for ( TMap, FHoudiniGeoPartObject > ::TIterator IterPackage(CookedTemporaryLandscapeLayers); + IterPackage; ++IterPackage ) + { + UPackage * Package = IterPackage.Key().Get(); + if ( !Package ) + continue; + + Package->ClearFlags( RF_Standalone ); + //Package->ConditionalBeginDestroy(); + } + + CookedTemporaryLandscapeLayers.Empty(); +} + +UStaticMesh * +UHoudiniAssetComponent::LocateStaticMesh( const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& ExactSearch ) const +{ + UStaticMesh * const * FoundStaticMesh = StaticMeshes.Find( HoudiniGeoPartObject ); + UStaticMesh * StaticMesh = nullptr; + + if ( !ExactSearch && !FoundStaticMesh ) + { + // We couldnt find the exact SM corresponding to this geo part + // Try again without caring for the split id + for (TMap< FHoudiniGeoPartObject, UStaticMesh * >::TConstIterator IterSM( StaticMeshes ); IterSM; ++IterSM ) + { + const FHoudiniGeoPartObject& HGPO = IterSM.Key(); + if (HGPO.AssetId == HoudiniGeoPartObject.AssetId && HGPO.ObjectId == HoudiniGeoPartObject.ObjectId + && HGPO.GeoId == HoudiniGeoPartObject.GeoId && HGPO.PartId == HoudiniGeoPartObject.PartId) + { + FoundStaticMesh = &IterSM.Value(); + break; + } + } + } + + if ( FoundStaticMesh ) + StaticMesh = *FoundStaticMesh; + + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + return nullptr; + + return StaticMesh; +} + +UStaticMeshComponent * +UHoudiniAssetComponent::LocateStaticMeshComponent( const UStaticMesh * StaticMesh ) const +{ + UStaticMeshComponent * const * FoundStaticMeshComponent = StaticMeshComponents.Find( StaticMesh ); + UStaticMeshComponent * StaticMeshComponent = nullptr; + + if ( FoundStaticMeshComponent ) + StaticMeshComponent = *FoundStaticMeshComponent; + + if ( StaticMeshComponent && StaticMeshComponent->IsPendingKill() ) + return nullptr; + + return StaticMeshComponent; +} + +bool +UHoudiniAssetComponent::LocateInstancedStaticMeshComponents( + const UStaticMesh * StaticMesh, TArray< UInstancedStaticMeshComponent * > & Components ) const +{ + Components.Empty(); + + bool bResult = false; + + for ( auto& InstanceInput : InstanceInputs ) + { + if ( InstanceInput && !InstanceInput->IsPendingKill() ) + bResult |= InstanceInput->CollectAllInstancedStaticMeshComponents( Components, StaticMesh ); + } + + return bResult; +} + +UHoudiniSplineComponent* +UHoudiniAssetComponent::LocateSplineComponent(const FHoudiniGeoPartObject & HoudiniGeoPartObject) const +{ + UHoudiniSplineComponent * const * FoundHoudiniSplineComponent = SplineComponents.Find(HoudiniGeoPartObject); + UHoudiniSplineComponent * SplineComponent = nullptr; + + if ( FoundHoudiniSplineComponent ) + SplineComponent = *FoundHoudiniSplineComponent; + + if ( !SplineComponent || SplineComponent->IsPendingKill() || !SplineComponent->IsValidLowLevel() ) + return nullptr; + + return SplineComponent; +} + +// Allow searching of geopart in array by names and not guid +UStaticMesh * +UHoudiniAssetComponent::LocateStaticMeshByNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const TMap< FHoudiniGeoPartObject, UStaticMesh * >& FindInMap) const +{ + UStaticMesh * StaticMesh = nullptr; + + // Find via the object and Part names. + for (TMap< FHoudiniGeoPartObject, UStaticMesh * >::TConstIterator IterSM(FindInMap); IterSM; ++IterSM) + { + const FHoudiniGeoPartObject& HGPO = IterSM.Key(); + if (HGPO.CompareNames(HoudiniGeoPartObject)) + { + StaticMesh = IterSM.Value(); + break; + } + } + + if (!StaticMesh || StaticMesh->IsPendingKill()) + return nullptr; + + return StaticMesh; +} + +void +UHoudiniAssetComponent::SerializeInputs( FArchive & Ar ) +{ + if ( Ar.IsLoading() ) + { + if ( !Ar.IsTransacting() ) + ClearInputs(); + } + + // We have to make sure that inputs are NOT saved with an empty name, as this will cause UE to crash on load + for (TArray< UHoudiniAssetInput * >::TIterator IterInputs(Inputs); IterInputs; ++IterInputs) + { + UHoudiniAssetInput * HoudiniAssetInput = *IterInputs; + if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill() ) + continue; + + if (HoudiniAssetInput->GetFName() != NAME_None) + continue; + + // Calling Rename with null parameters will make sure the instanced input has a unique name + HoudiniAssetInput->Rename(); + } + + Ar << Inputs; + + if ( Ar.IsTransacting() ) + { + for (int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx) + { + UHoudiniAssetInput * HoudiniAssetInput = Inputs[InputIdx]; + if ( HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill() ) + HoudiniAssetInput->Serialize(Ar); + } + } + else if ( Ar.IsLoading() ) + { + for ( int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx ) + { + UHoudiniAssetInput * HoudiniAssetInput = Inputs[ InputIdx ]; + if (HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill()) + Inputs[ InputIdx ]->SetHoudiniAssetComponent( this ); + } + } +} + +void +UHoudiniAssetComponent::SerializeInstanceInputs( FArchive & Ar ) +{ + if ( Ar.IsLoading() ) + { + // When loading for undo, we want to call Serialize on each InstanceInput + if ( !Ar.IsTransacting() ) + ClearInstanceInputs(); + + int32 HoudiniAssetComponentVersion = Ar.CustomVer( FHoudiniCustomSerializationVersion::GUID ); + if ( HoudiniAssetComponentVersion > VER_HOUDINI_ENGINE_COMPONENT_PARAMETER_NAME_MAP ) + { + Ar << InstanceInputs; + } + else + { + int32 InstanceInputCount = 0; + Ar << InstanceInputCount; + + InstanceInputs.SetNumUninitialized( InstanceInputCount ); + + for ( int32 InstanceInputIdx = 0; InstanceInputIdx < InstanceInputCount; ++InstanceInputIdx ) + { + HAPI_NodeId HoudiniInstanceInputKey = -1; + + Ar << HoudiniInstanceInputKey; + Ar << InstanceInputs[ InstanceInputIdx ]; + } + } + } + else + { + // We have to make sure that instanced inputs are NOT saved with an empty name, as this will cause UE to crash on load + for ( TArray< UHoudiniAssetInstanceInput * >::TIterator IterInstance(InstanceInputs ); IterInstance; ++IterInstance ) + { + UHoudiniAssetInstanceInput * HoudiniInstanceInput = *IterInstance; + if ( !HoudiniInstanceInput || HoudiniInstanceInput->IsPendingKill() ) + continue; + + if ( HoudiniInstanceInput->GetFName() != NAME_None ) + continue; + + // Calling Rename with null parameters will make sure the instanced input has a unique name + HoudiniInstanceInput->Rename(); + } + + Ar << InstanceInputs; + } + + if ( Ar.IsTransacting() ) + { + for ( UHoudiniAssetInstanceInput* InstanceInput : InstanceInputs ) + { + if ( InstanceInput && !InstanceInput->IsPendingKill() ) + InstanceInput->Serialize( Ar ); + } + } +} + +void +UHoudiniAssetComponent::SerializeParameters( FArchive & Ar ) +{ + // We have to make sure that parameter are NOT saaved with an empty name, as this will cause UE to crash on load + for (TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams(Parameters); IterParams; ++IterParams) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill() ) + continue; + + if (HoudiniAssetParameter->GetFName() != NAME_None) + continue; + + // Calling Rename with null parameters will make sure the parameter has a unique name + HoudiniAssetParameter->Rename(); + } + + Ar << Parameters; +} + +void +UHoudiniAssetComponent::PostLoadInitializeInstanceInputs() +{ + for ( auto& InstanceInput : InstanceInputs ) + { + if (InstanceInput && !InstanceInput->IsPendingKill() ) + InstanceInput->SetHoudiniAssetComponent( this ); + } +} + +void +UHoudiniAssetComponent::PostLoadInitializeParameters() +{ + // We need to re-patch parent parameter links. + for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams( Parameters ); IterParams; ++IterParams ) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill()) + continue; + + HoudiniAssetParameter->SetHoudiniAssetComponent( this ); + + HAPI_ParmId ParentParameterId = HoudiniAssetParameter->GetParmParentId(); + if ( ParentParameterId != -1 ) + { + UHoudiniAssetParameter * const * FoundParentParameter = Parameters.Find( ParentParameterId ); + if (FoundParentParameter) + { + if ( *FoundParentParameter && !(*FoundParentParameter)->IsPendingKill() ) + HoudiniAssetParameter->SetParentParameter(*FoundParentParameter); + } + } + } +} + +void +UHoudiniAssetComponent::RemoveStaticMeshComponent( UStaticMesh * StaticMesh ) +{ + UStaticMeshComponent * const * FoundStaticMeshComponent = StaticMeshComponents.Find( StaticMesh ); + if ( FoundStaticMeshComponent ) + { + StaticMeshComponents.Remove( StaticMesh ); + + UStaticMeshComponent * StaticMeshComponent = *FoundStaticMeshComponent; + if ( StaticMeshComponent && !StaticMeshComponent->IsPendingKill() ) + { + StaticMeshComponent->DetachFromComponent( FDetachmentTransformRules::KeepRelativeTransform ); + StaticMeshComponent->UnregisterComponent(); + StaticMeshComponent->DestroyComponent(); + } + } +} + +const FGuid & +UHoudiniAssetComponent::GetComponentGuid() const +{ + return ComponentGUID; +} + +UMaterialInterface * +UHoudiniAssetComponent::GetReplacementMaterial( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + const FString & MaterialName ) +{ + UMaterialInterface * ReplacementMaterial = nullptr; + + if ( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() ) + { + TMap< FHoudiniGeoPartObject, TMap< FString, UMaterialInterface * > > & MaterialReplacements = + HoudiniAssetComponentMaterials->Replacements; + + if ( MaterialReplacements.Contains( HoudiniGeoPartObject ) ) + { + TMap< FString, UMaterialInterface * > & FoundReplacements = MaterialReplacements[ HoudiniGeoPartObject ]; + + UMaterialInterface * const * FoundReplacementMaterial = FoundReplacements.Find( MaterialName ); + if ( FoundReplacementMaterial ) + ReplacementMaterial = *FoundReplacementMaterial; + } + } + + return ReplacementMaterial; +} + +bool +UHoudiniAssetComponent::GetReplacementMaterialShopName( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + UMaterialInterface * MaterialInterface, FString & MaterialName) +{ + if ( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() ) + { + TMap< FHoudiniGeoPartObject, TMap< FString, UMaterialInterface * > > & MaterialReplacements = + HoudiniAssetComponentMaterials->Replacements; + + if ( MaterialReplacements.Contains( HoudiniGeoPartObject ) ) + { + TMap< FString, UMaterialInterface * > & FoundReplacements = MaterialReplacements[ HoudiniGeoPartObject ]; + + const FString * FoundMaterialShopName = FoundReplacements.FindKey( MaterialInterface ); + if ( FoundMaterialShopName ) + { + MaterialName = *FoundMaterialShopName; + return true; + } + } + } + + return false; +} + +UMaterialInterface * +UHoudiniAssetComponent::GetAssignmentMaterial( const FString & MaterialName ) +{ + UMaterialInterface * Material = nullptr; + + if ( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() ) + { + TMap< FString, UMaterialInterface * > & MaterialAssignments = HoudiniAssetComponentMaterials->Assignments; + + UMaterialInterface * const * FoundMaterial = MaterialAssignments.Find( MaterialName ); + if ( FoundMaterial ) + Material = *FoundMaterial; + } + + return Material; +} + +void UHoudiniAssetComponent::ClearAssignmentMaterials() +{ + if( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() ) + { + HoudiniAssetComponentMaterials->Assignments.Empty(); + } +} + +void +UHoudiniAssetComponent::AddAssignmentMaterial( const FString& MaterialName, UMaterialInterface* MaterialInterface ) +{ + if( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() ) + { + HoudiniAssetComponentMaterials->Assignments.Add( MaterialName, MaterialInterface ); + } +} + +bool +UHoudiniAssetComponent::ReplaceMaterial( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + UMaterialInterface * NewMaterialInterface, + UMaterialInterface * OldMaterialInterface, + int32 MaterialIndex ) +{ + if ( !HoudiniAssetComponentMaterials || HoudiniAssetComponentMaterials->IsPendingKill() ) + return false; + + // Check that we do own this GeoPartObject, either via StaticMeshes or Landscapes + UStaticMesh * StaticMesh = LocateStaticMesh( HoudiniGeoPartObject, false ); + if ( StaticMesh ) + { + UStaticMeshComponent * StaticMeshComponent = LocateStaticMeshComponent( StaticMesh ); + if ( !StaticMeshComponent ) + { + TArray< UInstancedStaticMeshComponent * > InstancedStaticMeshComponents; + if ( !LocateInstancedStaticMeshComponents( StaticMesh, InstancedStaticMeshComponents ) ) + return false; + } + } + else + { + if ( !LandscapeComponents.Find( HoudiniGeoPartObject ) ) + return false; + } + + TMap< FHoudiniGeoPartObject, TMap< FString, UMaterialInterface * > > & MaterialReplacements = + HoudiniAssetComponentMaterials->Replacements; + + TMap< FString, UMaterialInterface * > & MaterialAssignments = HoudiniAssetComponentMaterials->Assignments; + + UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + + if ( !MaterialReplacements.Contains( HoudiniGeoPartObject ) ) + { + // If there's no replacement map for this geo part object, add one. + MaterialReplacements.Add( HoudiniGeoPartObject, TMap< FString, UMaterialInterface * >() ); + } + + // Retrieve replacements for this geo part object. + TMap< FString, UMaterialInterface * > & MaterialReplacementsValues = MaterialReplacements[ HoudiniGeoPartObject ]; + + const FString * FoundMaterialShopName = MaterialReplacementsValues.FindKey( OldMaterialInterface ); + if ( FoundMaterialShopName ) + { + // This material has been replaced previously. Replace old material with new material. + FString MaterialShopName = *FoundMaterialShopName; + MaterialReplacementsValues.Add( MaterialShopName, NewMaterialInterface ); + } + else + { + UMaterialInterface * OldMaterial = Cast< UMaterialInterface >( OldMaterialInterface ); + if ( OldMaterial ) + { + // We have no previous replacement for this material, see if we have it in list of material assignments. + FoundMaterialShopName = MaterialAssignments.FindKey( OldMaterial ); + if ( FoundMaterialShopName ) + { + // This material has been assigned previously. Add material replacement entry. + FString MaterialShopName = *FoundMaterialShopName; + MaterialReplacementsValues.Add( MaterialShopName, NewMaterialInterface ); + } + else if ( OldMaterial == DefaultMaterial ) + { + // This is replacement for default material. Add material replacement entry. + FString MaterialShopName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + MaterialReplacementsValues.Add( MaterialShopName, NewMaterialInterface ); + } + else + { + // External Material? + MaterialReplacementsValues.Add(OldMaterial->GetName(), NewMaterialInterface); + } + } + else + { + return false; + } + } + + return true; +} + +void +UHoudiniAssetComponent::SetMaterial( int32 ElementIndex, class UMaterialInterface* Material ) +{ + Super::SetMaterial(ElementIndex, Material); + +#if WITH_EDITOR + UpdateEditorProperties(false); +#endif +} + +void +UHoudiniAssetComponent::RemoveReplacementMaterial( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + const FString & MaterialName ) +{ + if ( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() ) + { + TMap< FHoudiniGeoPartObject, TMap > & MaterialReplacements = + HoudiniAssetComponentMaterials->Replacements; + + if ( MaterialReplacements.Contains( HoudiniGeoPartObject ) ) + { + TMap< FString, UMaterialInterface * > & MaterialReplacementsValues = + MaterialReplacements[ HoudiniGeoPartObject ]; + + MaterialReplacementsValues.Remove( MaterialName ); + } + } +} + +bool +UHoudiniAssetComponent::CreateOrUpdateMaterialInstances() +{ +#if WITH_EDITOR + FHoudiniCookParams HoudiniCookParams( this ); + HoudiniCookParams.PackageGUID = ComponentGUID; + HoudiniCookParams.MaterialAndTextureBakeMode = FHoudiniCookParams::GetDefaultMaterialAndTextureCookMode(); + + bool bMaterialReplaced = false; + + // 1. FOR STATIC MESHES + // Handling the creation of material instances from attributes + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshes ); Iter; ++Iter ) + { + const FHoudiniGeoPartObject HoudiniGeoPartObject = Iter.Key(); + UStaticMesh * StaticMesh = Iter.Value(); + + // Invisible meshes are used for instancers, so we will not skip them as the material instance/parameter attributes + // need to be set on the "source" mesh + //if ( !HoudiniGeoPartObject.IsVisible() ) + // continue; + + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + continue; + + // Replace the source material with the newly created/updated instance + for( int32 MatIdx = 0; MatIdx < StaticMesh->StaticMaterials.Num(); MatIdx++ ) + { + // The "source" material we want to create an instance of should have already been assigned to the mesh + UMaterialInstance* NewMaterialInstance = nullptr; + UMaterialInterface* SourceMaterialInterface = nullptr; + + // Create a new material instance if needed and update its parameter if needed + if (!FHoudiniEngineMaterialUtils::CreateMaterialInstances( + HoudiniGeoPartObject, HoudiniCookParams, NewMaterialInstance, SourceMaterialInterface, + HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, MatIdx ) ) + continue; + + if (!NewMaterialInstance || !SourceMaterialInterface) + continue; + + UMaterialInterface * SMMatInterface = StaticMesh->StaticMaterials[ MatIdx ].MaterialInterface; + if ( SMMatInterface != SourceMaterialInterface && SMMatInterface->GetBaseMaterial() != SourceMaterialInterface ) + continue; + + // Replace the material assignment + if ( !ReplaceMaterial( HoudiniGeoPartObject, NewMaterialInstance, SourceMaterialInterface, MatIdx ) ) + continue; + + // Update the StaticMesh, StaticMeshComponents and Instanced Static Mesh Components + StaticMesh->Modify(); + StaticMesh->PreEditChange( nullptr ); + StaticMesh->StaticMaterials[ MatIdx ].MaterialInterface = NewMaterialInstance; + StaticMesh->PostEditChange(); + StaticMesh->MarkPackageDirty(); + + UStaticMeshComponent * StaticMeshComponent = LocateStaticMeshComponent( StaticMesh ); + if ( StaticMeshComponent ) + { + StaticMeshComponent->Modify(); + StaticMeshComponent->SetMaterial( MatIdx, NewMaterialInstance ); + + bMaterialReplaced = true; + } + + TArray< UInstancedStaticMeshComponent * > InstancedStaticMeshComponents; + if ( LocateInstancedStaticMeshComponents( StaticMesh, InstancedStaticMeshComponents ) ) + { + for ( int32 Idx = 0; Idx < InstancedStaticMeshComponents.Num(); ++Idx ) + { + UInstancedStaticMeshComponent * InstancedStaticMeshComponent = InstancedStaticMeshComponents[ Idx ]; + if ( InstancedStaticMeshComponent ) + { + InstancedStaticMeshComponent->Modify(); + InstancedStaticMeshComponent->SetMaterial( MatIdx, NewMaterialInstance ); + + bMaterialReplaced = true; + } + } + } + } + } + + // 2. FOR LANDSCAPES + // Handling the creation of material instances from attributes + for ( TMap< FHoudiniGeoPartObject, TWeakObjectPtr >::TIterator Iter( LandscapeComponents ); Iter; ++Iter ) + { + FHoudiniGeoPartObject HoudiniGeoPartObject = Iter.Key(); + ALandscapeProxy* Landscape = Iter.Value().Get(); + if ( !Landscape || !Landscape->IsValidLowLevel() ) + continue; + + // The "source" landscape material we want to create an instance of should have already been assigned to the landscape + UMaterialInstance* NewMaterialInstance = nullptr; + UMaterialInterface* SourceMaterialInterface = nullptr; + // Create/update a material instance if needed for the Landscape Material + bool bLandscapeMaterialReplaced = false; + if ( FHoudiniEngineMaterialUtils::CreateMaterialInstances( + HoudiniGeoPartObject, HoudiniCookParams, NewMaterialInstance, SourceMaterialInterface, HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE ) ) + { + if ( NewMaterialInstance && SourceMaterialInterface ) + { + // Get the old material. + UMaterialInterface * OldMaterial = Landscape->GetLandscapeMaterial(); + if ( OldMaterial == SourceMaterialInterface || OldMaterial->GetBaseMaterial() == SourceMaterialInterface ) + { + // Update our replacement table + bLandscapeMaterialReplaced = ReplaceMaterial( HoudiniGeoPartObject, OldMaterial, NewMaterialInstance, 0 ); + + // Update the landscape's material itself + Landscape->Modify(); + Landscape->LandscapeMaterial = NewMaterialInstance; + bLandscapeMaterialReplaced = true; + bMaterialReplaced = true; + } + } + } + + // Repeat the same process for the Landscape HoleMaterial + // The "source" hole material we want to create an instance of should have already been assigned to the landscape + UMaterialInstance* NewHoleMaterialInstance = nullptr; + UMaterialInterface* SourceHoleMaterialInterface = nullptr; + // Create/update a material instance if needed for the Landscape Hole Material + bool bLandscapeHoleMaterialReplaced = false; + if ( FHoudiniEngineMaterialUtils::CreateMaterialInstances( + HoudiniGeoPartObject, HoudiniCookParams, NewHoleMaterialInstance, SourceHoleMaterialInterface, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE ) ) + { + if ( NewHoleMaterialInstance && SourceHoleMaterialInterface ) + { + // Get old material. + UMaterialInterface * OldHoleMaterial = Landscape->GetLandscapeHoleMaterial(); + if (OldHoleMaterial == SourceHoleMaterialInterface || OldHoleMaterial->GetBaseMaterial() == SourceHoleMaterialInterface) + { + // Update our replacement table + bLandscapeHoleMaterialReplaced = ReplaceMaterial(HoudiniGeoPartObject, OldHoleMaterial, NewHoleMaterialInstance, 0); + + // Update the landscape's hole material + Landscape->Modify(); + Landscape->LandscapeHoleMaterial = NewHoleMaterialInstance; + bLandscapeHoleMaterialReplaced = true; + bMaterialReplaced = true; + } + } + } + + // For the landscape update: + // As UpdateAllComponentMaterialInstances() is not accessible to us, we'll try to access the Material's UProperty + // to trigger a fake Property change event that will call the Update function... + if ( bLandscapeMaterialReplaced ) + { + FProperty* FoundProperty = FindFProperty< FProperty >( Landscape->GetClass(), TEXT( "LandscapeMaterial" ) ); + if ( FoundProperty ) + { + FPropertyChangedEvent PropChanged( FoundProperty, EPropertyChangeType::ValueSet ); + Landscape->PostEditChangeProperty( PropChanged ); + } + } + + if ( bLandscapeHoleMaterialReplaced ) + { + FProperty* FoundProperty = FindFProperty< FProperty >( Landscape->GetClass(), TEXT( "LandscapeHoleMaterial" ) ); + if ( FoundProperty ) + { + FPropertyChangedEvent PropChanged( FoundProperty, EPropertyChangeType::ValueSet ); + Landscape->PostEditChangeProperty( PropChanged ); + } + } + } + + if ( bMaterialReplaced ) + UpdateEditorProperties( false ); + + return bMaterialReplaced; +#else + return false; +#endif +} + +bool +UHoudiniAssetComponent::HasAnySockets() const +{ + // Return true if any of our StaticMeshComponent HasAnySocket + for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TConstIterator Iter( StaticMeshComponents ); Iter; ++Iter ) + { + UStaticMeshComponent * StaticMeshComponent = Iter.Value(); + if ( !StaticMeshComponent || StaticMeshComponent->IsPendingKill() ) + continue; + + if ( StaticMeshComponent->HasAnySockets() ) + return true; + } + + return Super::HasAnySockets(); +} + + +/** Get a list of sockets this component contains */ +void +UHoudiniAssetComponent::QuerySupportedSockets( TArray& OutSockets ) const +{ + // Query all the sockets in our StaticMeshComponents + for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TConstIterator Iter(StaticMeshComponents); Iter; ++Iter ) + { + UStaticMeshComponent * StaticMeshComponent = Iter.Value(); + if ( !StaticMeshComponent || StaticMeshComponent->IsPendingKill() ) + continue; + + if ( !StaticMeshComponent->HasAnySockets() ) + continue; + + TArray< FComponentSocketDescription > ComponentSocket; + StaticMeshComponent->QuerySupportedSockets( ComponentSocket ); + + OutSockets.Append( ComponentSocket ); + } +} + + +bool +UHoudiniAssetComponent::DoesSocketExist( FName SocketName ) const +{ + // Query all the sockets in our StaticMeshComponents + for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TConstIterator Iter( StaticMeshComponents ); Iter; ++Iter ) + { + UStaticMeshComponent * StaticMeshComponent = Iter.Value(); + if ( !StaticMeshComponent || StaticMeshComponent->IsPendingKill() ) + continue; + + if ( StaticMeshComponent->DoesSocketExist( SocketName ) ) + return true; + } + + return Super::DoesSocketExist( SocketName ); +} + + +FTransform +UHoudiniAssetComponent::GetSocketTransform( FName InSocketName, ERelativeTransformSpace TransformSpace ) const +{ + // Query all the sockets in our StaticMeshComponents + for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TConstIterator Iter( StaticMeshComponents ); Iter; ++Iter ) + { + UStaticMeshComponent * StaticMeshComponent = Iter.Value(); + if ( !StaticMeshComponent ) + continue; + + if ( !StaticMeshComponent->DoesSocketExist( InSocketName ) ) + continue; + + return StaticMeshComponent->GetSocketTransform( InSocketName, TransformSpace ); + } + + return Super::GetSocketTransform( InSocketName, TransformSpace ); +} + +FBox +UHoudiniAssetComponent::GetAssetBounds( UHoudiniAssetInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape ) const +{ + FBox BoxBounds( ForceInitToZero ); + + // Query the bounds of all our static mesh components.. + for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TConstIterator Iter( StaticMeshComponents ); Iter; ++Iter ) + { + UStaticMeshComponent * StaticMeshComponent = Iter.Value(); + if ( !StaticMeshComponent || StaticMeshComponent->IsPendingKill() ) + continue; + + FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox(); + if ( StaticMeshBounds.IsValid ) + BoxBounds += StaticMeshBounds; + } + + // Also scan all our decendants for SMC bounds not just top-level children + // ( split mesh instances' mesh bounds were not gathered proiperly ) + TArray 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(pChild)) + { + if (!StaticMeshComponent || StaticMeshComponent->IsPendingKill()) + continue; + + FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox(); + if (StaticMeshBounds.IsValid) + BoxBounds += StaticMeshBounds; + } + } + + //... all our Handles + for ( TMap< FString, UHoudiniHandleComponent * >::TConstIterator Iter( HandleComponents ); Iter; ++Iter ) + { + UHoudiniHandleComponent * HandleComponent = Iter.Value(); + if ( !HandleComponent ) + continue; + + BoxBounds += HandleComponent->GetComponentLocation(); + } + + // ... all our curves + for ( TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* >::TConstIterator Iter( SplineComponents ); Iter; ++Iter ) + { + UHoudiniSplineComponent * SplineComponent = Iter.Value(); + if ( !SplineComponent || !SplineComponent->IsValidLowLevel() ) + continue; + + TArray SplinePositions; + SplineComponent->GetCurvePositions( SplinePositions ); + + for (int32 n = 0; n < SplinePositions.Num(); n++) + { + BoxBounds += SplinePositions[ n ]; + } + } + + // ... and inputs + for ( int32 n = 0; n < Inputs.Num(); n++ ) + { + UHoudiniAssetInput* CurrentInput = Inputs[ n ]; + if ( !CurrentInput || CurrentInput->IsPendingKill() ) + continue; + + if ( CurrentInput == IgnoreInput ) + continue; + + FBox StaticMeshBounds = CurrentInput->GetInputBounds( GetComponentLocation() ); + if ( StaticMeshBounds.IsValid ) + BoxBounds += StaticMeshBounds; + } + + // ... all our landscapes + if ( !bIgnoreGeneratedLandscape ) + { + for (TMap< FHoudiniGeoPartObject, TWeakObjectPtr >::TConstIterator Iter( LandscapeComponents ); Iter; ++Iter ) + { + ALandscapeProxy * Landscape = Iter.Value().Get(); + if ( !Landscape ) + continue; + + FVector Origin, Extent; + Landscape->GetActorBounds( false, Origin, Extent ); + + FBox LandscapeBounds = FBox::BuildAABB( Origin, Extent ); + BoxBounds += LandscapeBounds; + } + } + + // If nothing was found, init with the asset's location + if ( BoxBounds.GetVolume() == 0.0f ) + BoxBounds += GetComponentLocation(); + + return BoxBounds; +} + +bool UHoudiniAssetComponent::HasLandscapeActor(ALandscapeProxy* LandscapeActor ) const +{ + // Check if we created the landscape + for (TMap>::TConstIterator Iter(LandscapeComponents); Iter; ++Iter) + { + ALandscapeProxy * HoudiniLandscape = Iter.Value().Get(); + if ( HoudiniLandscape && HoudiniLandscape == LandscapeActor ) + return true; + } + + return false; +} + +TMap< FHoudiniGeoPartObject, TWeakObjectPtr > * +UHoudiniAssetComponent::GetLandscapeComponents() +{ + return &LandscapeComponents; +} + + +/** Set the preset Input for HoudiniTools **/ +void +UHoudiniAssetComponent::SetHoudiniToolInputPresets( const TMap& InPresets ) +{ +#if WITH_EDITOR + HoudiniToolInputPreset = InPresets; +#endif +} + +#if WITH_EDITOR +void +UHoudiniAssetComponent::ApplyHoudiniToolInputPreset() +{ + if ( HoudiniToolInputPreset.Num() <= 0 ) + return; + + // We'll ignore inputs that have been preset to a curve type + TArray< UHoudiniAssetInput*> InputArray; + for ( auto CurrentInput : Inputs ) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if ( CurrentInput->GetChoiceIndex() != EHoudiniAssetInputType::CurveInput ) + InputArray.Add( CurrentInput ); + } + + // Also look for ObjectPath parameter inputs + for ( auto CurrentParam : Parameters ) + { + UHoudiniAssetInput* CurrentInput = Cast< UHoudiniAssetInput > ( CurrentParam.Value ); + if ( !CurrentInput || CurrentInput->IsPendingKill() ) + continue; + + if ( CurrentInput->GetChoiceIndex() != EHoudiniAssetInputType::CurveInput ) + InputArray.Add( CurrentInput ); + } + + // Identify some special cases first + bool OnlyHoudiniAssets = true; + bool OnlyLandscapes = true; + bool OnlyOneInput = true; + for ( TMap< UObject*, int32 >::TIterator IterToolPreset( HoudiniToolInputPreset ); IterToolPreset; ++IterToolPreset ) + { + UObject * Object = IterToolPreset.Key(); + AHoudiniAssetActor* HAsset = Cast( Object ); + if ( !HAsset || HAsset->IsPendingKill() ) + OnlyHoudiniAssets = false; + + ALandscapeProxy* Landscape = Cast( Object ); + if ( !Landscape || Landscape->IsPendingKill() ) + OnlyLandscapes = false; + + if ( IterToolPreset.Value() != 0 ) + OnlyOneInput = false; + } + + /* + if ( OnlyHoudiniAssets ) + { + // Try to apply the supplied Object to the Input + for (TMap< UObject*, int32 >::TIterator IterToolPreset( HoudiniToolInputPreset ); IterToolPreset; ++IterToolPreset) + { + AHoudiniAssetActor* HAsset = Cast< AHoudiniAssetActor >( IterToolPreset.Key() ); + if (!HAsset) + continue; + + int32 InputNumber = IterToolPreset.Value(); + if ( !InputArray.IsValidIndex( InputNumber ) ) + continue; + + InputArray[ InputNumber ]->ChangeInputType( EHoudiniAssetInputType::AssetInput ); + InputArray[ InputNumber ]->OnInputActorSelected( HAsset ); + } + } + else if ( OnlyLandscapes ) + { + // Try to apply the supplied Object to the Input + for (TMap< UObject*, int32 >::TIterator IterToolPreset( HoudiniToolInputPreset ); IterToolPreset; ++IterToolPreset) + { + ALandscapeProxy* Landscape = Cast< ALandscape >( IterToolPreset.Key() ); + if ( !Landscape ) + continue; + + int32 InputNumber = IterToolPreset.Value(); + if ( !InputArray.IsValidIndex( InputNumber ) ) + continue; + + InputArray[ InputNumber ]->ChangeInputType( EHoudiniAssetInputType::LandscapeInput ); + InputArray[ InputNumber ]->OnLandscapeActorSelected( Landscape ); + } + } + else + */ + { + // Try to apply the supplied Object to the Input + for (TMap< UObject*, int32 >::TIterator IterToolPreset( HoudiniToolInputPreset ); IterToolPreset; ++IterToolPreset) + { + UObject * Object = IterToolPreset.Key(); + if (!Object || Object->IsPendingKill()) + continue; + + int32 InputNumber = IterToolPreset.Value(); + + if ( !InputArray.IsValidIndex( InputNumber ) ) + continue; + + InputArray[ InputNumber ]->AddInputObject( Object ); + + if ( OnlyLandscapes && ( InputArray[ InputNumber ]->GetChoiceIndex() != EHoudiniAssetInputType::LandscapeInput ) ) + InputArray[ InputNumber ]->ChangeInputType( EHoudiniAssetInputType::LandscapeInput, true ); + + // Dont auto select asset inputs because they dont have the unreal transform, which can cause issue, prefer WorldInput instead + //if ( OnlyHoudiniAssets && ( InputArray[ InputNumber ]->GetChoiceIndex() != EHoudiniAssetInputType::AssetInput ) ) + // InputArray[ InputNumber ]->ChangeInputType( EHoudiniAssetInputType::AssetInput ); + } + } + + // Discard the tool presets after their first setup + HoudiniToolInputPreset.Empty(); +} +#endif + +FPrimitiveSceneProxy* +UHoudiniAssetComponent::CreateSceneProxy() +{ + /** Represents a UHoudiniAssetComponent to the scene manager. */ + class FHoudiniAssetSceneProxy final : public FPrimitiveSceneProxy + { + public: + SIZE_T GetTypeHash() const override + { + static size_t UniquePointer; + return reinterpret_cast( &UniquePointer ); + } + + FHoudiniAssetSceneProxy( const UHoudiniAssetComponent* InComponent ) + : FPrimitiveSceneProxy( InComponent ) + { + } + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override + { + FPrimitiveViewRelevance Result; + Result.bDrawRelevance = IsShown( View ); + return Result; + } + + virtual uint32 GetMemoryFootprint( void ) const override { return( sizeof( *this ) + GetAllocatedSize() ); } + uint32 GetAllocatedSize( void ) const { return( FPrimitiveSceneProxy::GetAllocatedSize() ); } + }; + + return new FHoudiniAssetSceneProxy( this ); +} + +void +UHoudiniAssetComponent::NotifyAssetNeedsToBeReinstantiated() +{ + bLoadedComponentRequiresInstantiation = true; + bLoadedComponent = true; + bFullyLoaded = false; + AssetCookCount = 0; + AssetId = -1; + + // Mark all input as changed + for ( TArray< UHoudiniAssetInput * >::TIterator IterInputs( Inputs ); IterInputs; ++IterInputs ) + { + UHoudiniAssetInput * HoudiniAssetInput = *IterInputs; + if ( HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill() ) + HoudiniAssetInput->MarkChanged( false ); + } + + // Upload parameters. + for (TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams(Parameters); IterParams; ++IterParams) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if ( HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill() ) + HoudiniAssetParameter->MarkChanged( false ); + } +} + +void +UHoudiniAssetComponent::UpdateMobility() +{ + // If we generated a landscape, force ourself to Static! + if ( LandscapeComponents.Num() > 0 ) + { + SetMobility(EComponentMobility::Static); + } + else + { + // Changed GetAttachChildren to 'GetAllDescendants' due to HoudiniMeshSplitInstanceComponent + // not propagating property changes to their own child StaticMeshComponents. + TArray< USceneComponent * > LocalAttachChildren; + GetChildrenComponents(true, LocalAttachChildren); + + // If one of the children we created is movable, we need to set ourselves to movable as well + for (TArray< USceneComponent * >::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) + { + USceneComponent * SceneComponent = *Iter; + if (SceneComponent->Mobility == EComponentMobility::Movable) + SetMobility(EComponentMobility::Movable); + } + } +} + + +ELightMapInteractionType UHoudiniAssetComponent::GetStaticLightingType() const +{ + // Override preventing the automatic reset to default of LightmapType in UPrimitiveComponent::PostEditChangeProperty + // see UStaticMeshComponent::GetStaticLightingType and UStaticMeshComponent::GetStaticLightingInfo + switch (LightmapType) + { + //default and ForceSurface are Texture mode. + case ELightmapType::Default: + case ELightmapType::ForceSurface: + return LMIT_Texture; + + case ELightmapType::ForceVolumetric: + return LMIT_GlobalVolume; + } + + return LMIT_None; +} + +void +UHoudiniAssetComponent::CopyComponentPropertiesTo(UPrimitiveComponent * pPrimComp) +{ + // Fixes UProperties not being propagated to newly created UStaticMeshComponents + // Copies all of the properties that would normally get propagated. + if (!pPrimComp || pPrimComp->IsPendingKill()) + return; + + pPrimComp->CastShadow = this->CastShadow; + pPrimComp->bCastDynamicShadow = this->bCastDynamicShadow; + pPrimComp->bCastStaticShadow = this->bCastStaticShadow; + pPrimComp->bCastVolumetricTranslucentShadow = this->bCastVolumetricTranslucentShadow; + pPrimComp->bCastInsetShadow = this->bCastInsetShadow; + pPrimComp->bCastHiddenShadow = this->bCastHiddenShadow; + pPrimComp->bCastShadowAsTwoSided = this->bCastShadowAsTwoSided; + pPrimComp->LightmapType = this->LightmapType; + pPrimComp->bLightAttachmentsAsGroup = this->bLightAttachmentsAsGroup; + pPrimComp->IndirectLightingCacheQuality = this->IndirectLightingCacheQuality; + pPrimComp->bVisibleInReflectionCaptures = this->bVisibleInReflectionCaptures; + pPrimComp->bRenderInMainPass = this->bRenderInMainPass; + //pPrimComp->bRenderInMono = this->bRenderInMono; + pPrimComp->bOwnerNoSee = this->bOwnerNoSee; + pPrimComp->bOnlyOwnerSee = this->bOnlyOwnerSee; + pPrimComp->bTreatAsBackgroundForOcclusion = this->bTreatAsBackgroundForOcclusion; + pPrimComp->bUseAsOccluder = this->bUseAsOccluder; + pPrimComp->bRenderCustomDepth = this->bRenderCustomDepth; + pPrimComp->CustomDepthStencilValue = this->CustomDepthStencilValue; + pPrimComp->CustomDepthStencilWriteMask = this->CustomDepthStencilWriteMask; + pPrimComp->TranslucencySortPriority = this->TranslucencySortPriority; + pPrimComp->LpvBiasMultiplier = this->LpvBiasMultiplier; + pPrimComp->bReceivesDecals = this->bReceivesDecals; + pPrimComp->BoundsScale = this->BoundsScale; + pPrimComp->bUseAttachParentBound = this->bUseAttachParentBound; + pPrimComp->bAlwaysCreatePhysicsState = this->bAlwaysCreatePhysicsState; + pPrimComp->bMultiBodyOverlap = this->bMultiBodyOverlap; + //pPrimComp->bCheckAsyncSceneOnMove = this->bCheckAsyncSceneOnMove; + pPrimComp->bTraceComplexOnMove = this->bTraceComplexOnMove; + pPrimComp->bReturnMaterialOnMove = this->bReturnMaterialOnMove; + pPrimComp->BodyInstance = this->BodyInstance; + pPrimComp->CanCharacterStepUpOn = this->CanCharacterStepUpOn; + pPrimComp->bIgnoreRadialImpulse = this->bIgnoreRadialImpulse; + pPrimComp->bIgnoreRadialForce = this->bIgnoreRadialForce; + pPrimComp->bApplyImpulseOnDamage = this->bApplyImpulseOnDamage; + pPrimComp->MinDrawDistance = this->MinDrawDistance; + pPrimComp->LDMaxDrawDistance = this->LDMaxDrawDistance; + pPrimComp->CachedMaxDrawDistance = this->CachedMaxDrawDistance; + pPrimComp->bAllowCullDistanceVolume = this->bAllowCullDistanceVolume; + pPrimComp->DetailMode = this->DetailMode; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h new file mode 100644 index 00000000..821b614a --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h @@ -0,0 +1,890 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HAPI.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniCookHandler.h" + +#include "CoreMinimal.h" +#include "Landscape.h" +#include "TimerManager.h" +#include "Components/PrimitiveComponent.h" +#if WITH_EDITOR +#include "Factories/Factory.h" +#endif +#include "PhysicsEngine/BodySetup.h" +#include "HoudiniAssetComponent.generated.h" + +class UBlueprint; +class UStaticMesh; +class UHoudiniAsset; +class FObjectProperty; +class USplineComponent; +class UInstancedStaticMeshComponent; +class UPhysicalMaterial; +class UHoudiniAssetInput; +class AHoudiniAssetActor; +class UHoudiniAssetHandle; +class UStaticMeshComponent; +class UHoudiniAssetParameter; +class UHoudiniHandleComponent; +class UHoudiniSplineComponent; +class UHoudiniAssetInstanceInput; +class UHoudiniAssetInstanceInputField; +class UHoudiniAssetComponentMaterials; +class UFoliageType_InstancedStaticMesh; + +struct FTransform; +struct FPropertyChangedEvent; +struct FWalkableSlopeOverride; + + +namespace EHoudiniAssetComponentState +{ + /** This enumeration represents a state of component when it is serialized. **/ + + enum Enum + { + /** Invalid type corresponds to state when component has been created, but is in invalid state. It had no **/ + /** Houdini asset assigned. Typically this will be the case when component instance is a default class **/ + /** object or belongs to an actor instance which is a default class object also. **/ + Invalid, + + /** None type corresponds to state when component has been created, but corresponding Houdini asset has not **/ + /** been instantiated. **/ + None, + + /** This type corresponds to a state when component has been created, corresponding Houdini asset has been **/ + /** instantiated, and component has no pending asynchronous cook request. **/ + Instantiated, + + /** This type corresponds to a state when component has been created, corresponding Houdini asset has been **/ + /** instantiated, and component has a pending asynchronous cook in progress. **/ + BeingCooked + }; +} + +UCLASS( ClassGroup = (Rendering, Common), hidecategories = (Object,Activation,"Components|Activation"), + ShowCategories = (Mobility), editinlinenew ) +class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveComponent, public IHoudiniCookHandler +{ + friend class AHoudiniAssetActor; + friend struct FHoudiniEngineUtils; + friend class FHoudiniMeshSceneProxy; + friend class UHoudiniHandleComponent; + friend class UHoudiniSplineComponent; + +#if WITH_EDITOR + + friend class FHoudiniAssetComponentDetails; + friend class FHoudiniAssetTypeActions; + +#endif + + GENERATED_UCLASS_BODY() + + virtual ~UHoudiniAssetComponent(); + + /** Static mesh generation properties.**/ + public: + + /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ + UPROPERTY( EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = ( DisplayName = "Double Sided Geometry" ) ) + uint32 bGeneratedDoubleSidedGeometry : 1; + + /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ + UPROPERTY( EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = ( DisplayName = "Simple Collision Physical Material" ) ) + UPhysicalMaterial * GeneratedPhysMaterial; + + /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ + UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, meta = ( FullyExpand = "true" )) + struct FBodyInstance DefaultBodyInstance; + + /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ + UPROPERTY( EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = ( DisplayName = "Collision Complexity" ) ) + TEnumAsByte< enum ECollisionTraceFlag > GeneratedCollisionTraceFlag; + + /** Resolution of lightmap. */ + UPROPERTY( EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = ( DisplayName = "Light Map Resolution", FixedIncrement = "4.0" ) ) + int32 GeneratedLightMapResolution; + + /** Bias multiplier for Light Propagation Volume lighting. */ + UPROPERTY( EditAnywhere, BlueprintReadOnly, + Category = HoudiniGeneratedStaticMeshSettings, + meta = ( DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0" ) ) + float GeneratedLpvBiasMultiplier; + + /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) + float GeneratedDistanceFieldResolutionScale; + + /** Custom walkable slope setting for generated mesh's body. */ + UPROPERTY( EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = ( DisplayName = "Walkable Slope Override" ) ) + FWalkableSlopeOverride GeneratedWalkableSlopeOverride; + + /** The light map coordinate index. */ + UPROPERTY( EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = ( DisplayName = "Light map coordinate index" ) ) + int32 GeneratedLightMapCoordinateIndex; + + /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ + UPROPERTY( EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = ( DisplayName = "Use Maximum Streaming Texel Ratio" ) ) + uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; + + /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ + UPROPERTY( EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = ( DisplayName = "Streaming Distance Multiplier" ) ) + float GeneratedStreamingDistanceMultiplier; + + /** Default settings when using this mesh for instanced foliage. */ + UPROPERTY( EditAnywhere, AdvancedDisplay, Instanced, + Category = HoudiniGeneratedStaticMeshSettings, + meta = ( DisplayName = "Foliage Default Settings" ) ) + UFoliageType_InstancedStaticMesh * GeneratedFoliageDefaultSettings; + + /** Array of user data stored with the asset. */ + UPROPERTY( EditAnywhere, AdvancedDisplay, Instanced, + Category = HoudiniGeneratedStaticMeshSettings, + meta = ( DisplayName = "Asset User Data" ) ) + TArray GeneratedAssetUserData; + +public: + + /** Change the Houdini Asset used by this component. **/ + virtual void SetHoudiniAsset( UHoudiniAsset * NewHoudiniAsset ); + + /** Returns true if this component has any sockets */ + virtual bool HasAnySockets() const; + + /** Get a list of sockets this component contains */ + virtual void QuerySupportedSockets( TArray< FComponentSocketDescription >& OutSockets ) const; + + /** Returns true if this component has the desired socket */ + virtual bool DoesSocketExist( FName SocketName ) const; + + /** Returns the transform for the desired socket */ + virtual FTransform GetSocketTransform( FName InSocketName, ERelativeTransformSpace TransformSpace ) const; + + /** Assign generation parameters to static mesh. **/ + void SetStaticMeshGenerationParameters( class UStaticMesh * StaticMesh ) const override; + +#if WITH_EDITOR + + /** Start cooking / instantiation ticking. **/ + void StartHoudiniTicking(); + + /** Return true if this component has no cooking or instantiation in progress. **/ + bool IsInstantiatingOrCooking() const; + + /** Return true if this component's asset has been instantiated, but not cooked. **/ + bool HasBeenInstantiatedButNotCooked() const; + + /** Ticking function to check cooking / instatiation status. **/ + void TickHoudiniComponent(); + + /** Ticking function to check whether UI update can be performed. This is necessary so that widget which has **/ + /** captured the mouse does not lose it. **/ + void TickHoudiniUIUpdate(); + + /** Refresh editor's detail panel and update properties. **/ + void UpdateEditorProperties( bool bConditionalUpdate ); + + /** Callback used by parameters to notify component about their changes. **/ + void NotifyParameterChanged( UHoudiniAssetParameter * HoudiniAssetParameter ); + + /** Notification used by spline visualizer to notify main Houdini asset component about spline change. **/ + void NotifyHoudiniSplineChanged( UHoudiniSplineComponent * HoudiniSplineComponent ); + + /** Used by Blueprint baking; create temporary actor and necessary components to bake a blueprint. **/ + AActor * CloneComponentsAndCreateActor(); + + /** Return true if cooking is enabled for this component. **/ + bool IsCookingEnabled() const; + + /** Start asset instantiation task. **/ + void StartTaskAssetInstantiation( bool bLoadedComponent = false, bool bStartTicking = false ); + + /** Start manual asset cooking task. **/ + void StartTaskAssetCookingManual(); + + /** Start manual asset rebuild task. **/ + void StartTaskAssetRebuildManual(); +#endif + + /** Used to differentiate native components from dynamic ones. **/ + void SetNative( bool InbIsNativeComponent ); + + /** Return id of a Houdini asset. **/ + UFUNCTION( BlueprintCallable, Category = HoudiniAsset ) + int32 GetAssetId() const; + + /** Set id of a Houdini asset. **/ + void SetAssetId( HAPI_NodeId InAssetId ); + + /** Return true if asset id is valid. **/ + bool HasValidAssetId() const; + + /** Returns true if the asset is valid for cook/bake **/ + bool IsComponentValid() const; + + /** Invalidates the assets, causing it to be reinstantiated upon recook **/ + void NotifyAssetNeedsToBeReinstantiated(); + + /** Return current referenced Houdini asset. **/ + UHoudiniAsset * GetHoudiniAsset() const; + + /** Return owner Houdini actor. **/ + AHoudiniAssetActor * GetHoudiniAssetActorOwner() const; + + /** Return true if this component contains Houdini logo geometry. **/ + bool ContainsHoudiniLogoGeometry() const; + + /** Return all static meshes used by this component. For both instanced and uinstanced components. **/ + void GetAllUsedStaticMeshes( TArray< UStaticMesh * > & UsedStaticMeshes ); + + /** Return all the UStaticMeshComponent & UInstancedStataicMeshComponent owned by the actor, along with their associated parts */ + TMap CollectAllStaticMeshComponents() const; + + /** Return all the UHoudiniInstancedActorComponents that have content */ + TMap CollectAllInstancedActorComponents() const; + + /** Return all the UHoudiniMeshSplitInstancerComponent that have content */ + TMap CollectAllMeshSplitInstancerComponents() const; + + /** Returns all the instance input field for this asset **/ + const TArray< UHoudiniAssetInstanceInputField * > GetAllInstanceInputFields() const; + + /** Return true if global setting scale factors are different from the ones used for this component. **/ + bool CheckGlobalSettingScaleFactors() const; + + // override to prevent automatic reset to default of LightmapType in UPrimitiveComponent::PostEditChangeProperty ( @ line 917 ) + virtual ELightMapInteractionType GetStaticLightingType() const override; + + // copy all of the properties that normally gets propagated to a given component. + // See UHoudiniAssetComponent::PostEditChangeProperty and UHoudiniMeshSplitInstancerComponent::SetInstances + void CopyComponentPropertiesTo(UPrimitiveComponent * pPrimComp); + + public: + + /** Locate static mesh by geo part object name. By default will use substring matching. **/ + bool LocateStaticMeshes( + const FString & ObjectName, + TMap< FString, TArray< FHoudiniGeoPartObject > > & InOutObjectsToInstance, + bool bSubstring = true ) const; + + /** Locate static mesh by geo part object id. **/ + bool LocateStaticMeshes( int32 ObjectToInstanceId, + TArray< FHoudiniGeoPartObject > & InOutObjectsToInstance ) const; + + /** Locate static mesh for a given geo part. **/ + UStaticMesh * LocateStaticMesh( const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& ExactSearch = true ) const; + + FORCEINLINE const TMap< FHoudiniGeoPartObject, UStaticMesh * >& GetStaticMeshes() const { return StaticMeshes; } + + /** Locate static mesh component for given static mesh. **/ + UStaticMeshComponent * LocateStaticMeshComponent( const UStaticMesh * StaticMesh ) const; + + /** Locate instanced static mesh components for given static mesh. **/ + bool LocateInstancedStaticMeshComponents( + const UStaticMesh * StaticMesh, + TArray< UInstancedStaticMeshComponent * > & Components ) const; + + /** Locate geo part object for given static mesh. Reverse map search. **/ + FHoudiniGeoPartObject LocateGeoPartObject( UStaticMesh * StaticMesh ) const; + + /** Locate spline component for a given geo part. **/ + UHoudiniSplineComponent * LocateSplineComponent( + const FHoudiniGeoPartObject & HoudiniGeoPartObject ) const; + + // Allow searching of geopart in array by names and not guid + UStaticMesh * LocateStaticMeshByNames( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + const TMap< FHoudiniGeoPartObject, UStaticMesh * >& FindInMap) const; + + /** Return true if this component is in playmode. **/ + bool IsPIEActive() const; + + /** Return component GUID. **/ + const FGuid& GetComponentGuid() const; + + /** If given material has been replaced for a given geo part object, return it. Otherwise return null. **/ + class UMaterialInterface * GetReplacementMaterial( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + const FString & MaterialName ); + + /** If given material has been replaced for a given geo part object, return its name by reference. **/ + bool GetReplacementMaterialShopName( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + class UMaterialInterface * MaterialInterface, FString & MaterialName); + + /** Given a shop name return material assignment. **/ + class UMaterialInterface * GetAssignmentMaterial( const FString & MaterialName ) override; + + /** Clear the assignment material cache */ + void ClearAssignmentMaterials() override; + + /** Add a material for the given shop name */ + void AddAssignmentMaterial( const FString& MaterialName, class UMaterialInterface* MaterialInterface ) override; + + /** Material Change **/ + virtual void SetMaterial(int32 ElementIndex, class UMaterialInterface* Material) override; + + /** Perform material replacement. **/ + bool ReplaceMaterial( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, class UMaterialInterface * NewMaterialInterface, + class UMaterialInterface * OldMaterialInterface, int32 MaterialIndex ); + + /** Remove material replacement. **/ + void RemoveReplacementMaterial( const FHoudiniGeoPartObject & HoudiniGeoPartObject, const FString & MaterialName ); + + /** Handle the creation/update of material instance via attributes **/ + bool CreateOrUpdateMaterialInstances(); + + /** Collect all Substance parameters. **/ + void CollectSubstanceParameters( TMap< FString, UHoudiniAssetParameter * > & SubstanceParameters ) const; + + /** Collect all parameters of a given type. **/ + void CollectAllParametersOfType( UClass * ParameterClass, TMap< FString, UHoudiniAssetParameter * > & ClassParameters ) const; + + /** Locate parameter by name. **/ + UHoudiniAssetParameter * FindParameter( const FString & ParameterName ) const; + + FORCEINLINE const TArray< UHoudiniAssetInput* >& GetInputs() const { return Inputs; } + + /** Returns an array containing all the inputs (including the object path parameters)**/ + void GetInputs(TArray< UHoudiniAssetInput* >& AllInputs, bool IncludeObjectPathParameter = true ); + + /** Returns the path to the baking folder */ + FText GetBakeFolder() const; + + /** Sets a new bake folder */ + void SetBakeFolder( const FString& Folder ); + + /** Returns the path to the temporary cooking folder */ + FText GetTempCookFolder() const; + + /** changes the temporary cooking folder path */ + void SetTempCookFolder(const FString& Folder); + + FString GetBakingBaseName( const FHoudiniGeoPartObject& GeoPartObject ) const override; + + /** Is the asset still waiting for upstream asset to finish instantiating **/ + bool UpdateWaitingForUpstreamAssetsToInstantiate( bool bNotifyUpstreamAsset = false ); + + /** Updates the HAC's mobility depending on its children's mobility **/ + void UpdateMobility(); + + /** UObject methods. **/ + public: + +#if WITH_EDITOR + + virtual void PostEditChangeProperty( FPropertyChangedEvent & PropertyChangedEvent ) override; + virtual void PostEditUndo() override; + virtual void PostEditImport() override; + virtual void PostLoad() override; + virtual void PostInitProperties() override; + /** UActorComponent methods. **/ + protected: + + virtual void OnComponentCreated() override; + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + virtual void OnRegister() override; +#endif + virtual void Serialize( FArchive & Ar ) override; + static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); + + /** USceneComponent methods. **/ + private: + + virtual FBoxSphereBounds CalcBounds( const FTransform & LocalToWorld ) const override; +#if WITH_EDITOR + virtual void OnUpdateTransform( EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport ) override; +#endif + + private: + + /** Update rendering information. **/ + void UpdateRenderingInformation(); + + /** Re-attach components after loading or copying. **/ + void PostLoadReattachComponents(); + +#if WITH_EDITOR + + /** Called after each cook. **/ + void PostCook( bool bCookError = false ); + + /** Check ourselves over and fix up any errors */ + void SanitizePostLoad(); + + /** Remove all attached components. **/ + void RemoveAllAttachedComponents(); + + /** If we are being copied from a component, transfer necessary state. **/ + void OnComponentClipboardCopy( UHoudiniAssetComponent * HoudiniAssetComponent ); + + /** Delegate for asset post import. **/ + void OnAssetPostImport( UFactory * Factory, UObject * Object ); + + /** Delegate to handle drag and drop events. **/ + void OnApplyObjectToActor( UObject * ObjectToApply, AActor * ActorToApplyTo ); + + /** Delegate to handle asset actor movement */ + void OnActorMoved( AActor* Actor ); + + /** Subscribe to Editor events. **/ + void SubscribeEditorDelegates(); + + /** Unsubscribe from Editor events. **/ + void UnsubscribeEditorDelegates(); + + /** Stop cooking / instantiation ticking. **/ + void StopHoudiniTicking(); + + /** Start UI update ticking. **/ + void StartHoudiniUIUpdateTicking(); + + /** Stop UI update ticking. **/ + void StopHoudiniUIUpdateTicking(); + + /** Assign actor label based on asset instance name. **/ + void AssignUniqueActorLabel(); + + /** Reset all Houdini related information, the asset, cooking trackers, generated geometry, related state, etc. **/ + void ResetHoudiniResources(); + + /** Start manual asset reset task. **/ + void StartTaskAssetResetManual(); + + /** Start asset deletion task. **/ + void StartTaskAssetDeletion(); + + /** Start asset cooking task. **/ + void StartTaskAssetCooking( bool bStartTicking = false ); + + /** Create default preset buffer. **/ + void CreateDefaultPreset(); + + /** Create curves. **/ + void CreateCurves( const TArray< FHoudiniGeoPartObject > & Curves ); + + /** Create new parameters and attempt to reuse existing ones. **/ + void CreateParameters(); + + /** Create handles.**/ + bool CreateHandles(); + + /** Create all landscapes in the GeoPartObject array **/ + bool CreateAllLandscapes( const TArray< FHoudiniGeoPartObject > & FoundVolumes ); + + /** Updates the materials for a newly created landscape **/ + void UpdateLandscapeMaterialsAssignementsAndReplacements(ALandscapeProxy* Landscape, FHoudiniGeoPartObject Heightfield ); + + /** Unmark all changed parameters. **/ + void UnmarkChangedParameters(); + + /** Upload changed parameters back to HAPI. **/ + void UploadChangedParameters(); + + /** If parameters were loaded, they need to be updated with proper ids after HAPI instantiation. **/ + void UpdateLoadedParameters(); + + /** Create inputs. Number of inputs for asset does not change. **/ + void CreateInputs(); + + /** If inputs were loaded, they need to be updated and assigned geos need to be connected. **/ + void UpdateLoadedInputs( const bool& ForceRefresh ); + + /** If curves were loaded, their points need to be uploaded. **/ + void UploadLoadedCurves(); + + /** Refreshes editables nodes after loading the level **/ + bool RefreshEditableNodesAfterLoad(); + + /** Find an instance input for the given geo part */ + UHoudiniAssetInstanceInput* LocateInstanceInput( const FHoudiniGeoPartObject& GeoPart ) const; + + /** Create instance inputs. **/ + void CreateInstanceInputs( const TArray< FHoudiniGeoPartObject > & Instancers ); + + /** Duplicate all parameters. Used during copying. **/ + void DuplicateParameters( UHoudiniAssetComponent * DuplicatedHoudiniComponent ); + + /** Duplicate all handles. Used during copying. **/ + void DuplicateHandles( UHoudiniAssetComponent * DuplicatedHoudiniComponent ); + + /** Duplicate inputs. Used during copying. **/ + void DuplicateInputs( UHoudiniAssetComponent * DuplicatedHoudiniComponent ); + + /** Duplicate instance inputs. Used during copying. **/ + void DuplicateInstanceInputs( UHoudiniAssetComponent * DuplicatedHoudiniComponent, const TMap& ReplacementMap ); + + /** Helper called when world transform changes */ + void CheckedUploadTransform(); + + /** Per-part overrides for baking file name */ + void SetBakingBaseNameOverride( const FHoudiniGeoPartObject& GeoPartObject, const FString& BaseName ); + bool RemoveBakingBaseNameOverride( const FHoudiniGeoPartObject& GeoPartObject ); + + /** Apply the preset input for HoudiniTools**/ + void ApplyHoudiniToolInputPreset(); +#endif + + /** Clear all spline related resources. **/ + void ClearCurves(); + + /** Clear all parameters. **/ + void ClearParameters(); + + /** Clear handles. **/ + void ClearHandles(); + + /** Clear all inputs. **/ + void ClearInputs(); + + /** Clear all instance inputs. **/ + void ClearInstanceInputs(); + + /** Inform downstream assets that we are dieing. **/ + void ClearDownstreamAssets(); + + /** Clear cooked content temp files **/ + void ClearCookTempFile(); + + /** Delete Static mesh resources. This will free static meshes and corresponding components. **/ + void ReleaseObjectGeoPartResources( + TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshMap, + bool bDeletePackages = false, + TMap< FHoudiniGeoPartObject, UStaticMesh * > * pKeepIfContainedIn = nullptr); + + /** Return true if given object is referenced locally only, by objects generated and owned by this component. **/ + bool IsObjectReferencedLocally( UStaticMesh * StaticMesh, FReferencerInformationList & Referencers ) const; + + public: + + /** Clear all landscapes **/ + void ClearLandscapes(); + + /** Add to the list of dependent downstream assets that have this asset as an asset input. **/ + void AddDownstreamAsset( UHoudiniAssetComponent * InDownstreamAssetComponent, int32 InInputIndex ); + + /** Remove from the list of dependent downstream assets that have this asset as an asset input. **/ + void RemoveDownstreamAsset( UHoudiniAssetComponent * InDownstreamAssetComponent, int32 InInputIndex ); + + // Makes sure that our downstream assets are valid and we are actually set as their asset input + void ValidateDownstreamAssets(); + + /** Create Static mesh resources. This will create necessary components for each mesh and update maps. **/ + void CreateObjectGeoPartResources( TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshMap ); + + /** Delete Static mesh resources. This will free static meshes and corresponding components. **/ + void ReleaseObjectGeoPartResources( bool bDeletePackages = false ); + + /** Check all the attached StaticMeshComponents to delete invalid ones **/ + void CleanUpAttachedStaticMeshComponents(); + + /** Create Static mesh resource which corresponds to Houdini logo. **/ + void CreateStaticMeshHoudiniLogoResource( TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMesDhMap ); + + /** Serialize inputs. **/ + void SerializeInputs( FArchive & Ar ); + + /** Serialize instance inputs. **/ + void SerializeInstanceInputs( FArchive & Ar ); + + /** Serialize parameters. **/ + void SerializeParameters( FArchive & Ar ); + + /** Used to perform post loading initialization on instance inputs. **/ + void PostLoadInitializeInstanceInputs(); + + /** Used to perform post loading initialization of parameters. **/ + void PostLoadInitializeParameters(); + + /** Remove static mesh and associated component and deallocate corresponding resources. **/ + void RemoveStaticMeshComponent( UStaticMesh * StaticMesh ); + + /** Returns the AABB for the asset component and its inputs **/ + FBox GetAssetBounds( UHoudiniAssetInput* IgnoreInput = nullptr, const bool& bIgnoreGeneratedLandscape = false) const; + + /** Return true if this Houdini asset component has a landscape **/ + bool HasLandscape() const { return ( LandscapeComponents.Num() > 0 ); } + + /** Returns true if the landscape actor has been created by this asset **/ + bool HasLandscapeActor(ALandscapeProxy* LandscapeActor) const; + + /** Returns a pointer to the landscape component map **/ + TMap< FHoudiniGeoPartObject, TWeakObjectPtr > * GetLandscapeComponents(); + + /** Set the preset Input for HoudiniTools **/ + void SetHoudiniToolInputPresets( const TMap< UObject*, int32 >& InPresets ); + + /** Replaces references to a landscape actor by the newly generated one **/ + bool ReplaceLandscapeInInputs(ALandscapeProxy* Old, ALandscapeProxy* New ); + + /** From UPrimitiveComponent Interface. **/ + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + + private: + + /** This flag is used when Houdini engine is not initialized to display a popup message once. **/ + static bool bDisplayEngineNotInitialized; + + /** This flag is used when Hapi version mismatch is detected (between defined and running versions. **/ + static bool bDisplayEngineHapiVersionMismatch; + + public: + + /** Houdini Asset associated with this component. **/ + UHoudiniAsset * HoudiniAsset; + + /** Indicates a manual recook has been asked by the user **/ + bool bManualRecookRequested; + + /** Transient cache of last baked parts */ + TMap > BakedStaticMeshPackagesForParts; + /** Transient cache of last baked materials and textures */ + TMap > BakedMaterialPackagesForIds; + + /** Cache of the temp cook content packages created by the asset for its meshes **/ + TMap > CookedTemporaryStaticMeshPackages; + /** Cache of the temp cook content packages created by the asset for its materials/textures **/ + TMap > CookedTemporaryPackages; + /** Cache of the temp cook content packages created by the asset for its Landscape layers **/ + /** As packages are unique their are used as the key (we can have multiple package for the same geopartobj **/ + TMap< TWeakObjectPtr , FHoudiniGeoPartObject > CookedTemporaryLandscapeLayers; + + /** Indicates that the details panels doesn't need a "full" update to avoid breaking parameter selection **/ + /** (default behavior is true) **/ + bool bEditorPropertiesNeedFullUpdate; + + /** Overrides for baking names per part */ + TMap< FHoudiniGeoPartObject, FString > BakeNameOverrides; + + protected: + + /** Previous asset, if it has been changed through transaction. **/ + UHoudiniAsset * PreviousTransactionHoudiniAsset; + + /** Parameters for this component's asset, indexed by parameter id. **/ + TMap< HAPI_ParmId, UHoudiniAssetParameter * > Parameters; + + /** Parameters for this component's asset, indexed by name for fast look up. **/ + TMap< FString, UHoudiniAssetParameter * > ParameterByName; + + /** Inputs for this component's asset. **/ + TArray< UHoudiniAssetInput * > Inputs; + + /** Instance inputs for this component's asset **/ + TArray< UHoudiniAssetInstanceInput * > InstanceInputs; + + /** List of dependent downstream asset connections that have this asset as an asset input. **/ + TMap< UHoudiniAssetComponent * , TSet< int32 > > DownstreamAssetConnections; + + /** Map of HAPI objects and corresponding static meshes. Also map of static meshes and corresponding components. **/ + TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshes; + TMap< UStaticMesh *, UStaticMeshComponent * > StaticMeshComponents; + + /** Map of asset handle components. **/ + typedef TMap< FString, UHoudiniHandleComponent * > FHandleComponentMap; + FHandleComponentMap HandleComponents; + + /** Map of curve / spline components. **/ + TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* > SplineComponents; + + /** Map of Landscape / Heightfield components. **/ + TMap< FHoudiniGeoPartObject, TWeakObjectPtr > LandscapeComponents; + + /** Material assignments. **/ + UHoudiniAssetComponentMaterials * HoudiniAssetComponentMaterials; + + /** Buffer to hold preset data for serialization purposes. Used only during serialization. **/ + TArray< char > PresetBuffer; + + /** Buffer to hold default preset for reset purposes. **/ + TArray< char > DefaultPresetBuffer; + + /** The output folder for baking actions */ + UPROPERTY() + FText BakeFolder; + + /** The temporary output folder for cooking actions */ + UPROPERTY() + FText TempCookFolder; + + /** list of the modified uproperties per geopartobject **/ + //TMap< FHoudiniGeoPartObject, TArray< UPropertyAttribute > > ModifedUProperties; + +#if WITH_EDITOR + + /** Notification used by this component. **/ + TWeakPtr< class SNotificationItem > NotificationPtr; + + /** Component from which this component has been copied. **/ + TWeakObjectPtr CopiedHoudiniComponent; + +#endif + + /** Unique GUID created by component. **/ + FGuid ComponentGUID; + + /** GUID used to track asynchronous cooking requests. **/ + FGuid HapiGUID; + + /** Delegate handle returned by editor asset post import delegate. **/ + FDelegateHandle DelegateHandleAssetPostImport; + + /** Delegate to handle editor viewport drag and drop events. **/ + FDelegateHandle DelegateHandleApplyObjectToActor; + + /** Timer handle, this timer is used for cooking. **/ + FTimerHandle TimerHandleCooking; + + /** Timer delegate, we use it for ticking during cooking or instantiation. **/ + FTimerDelegate TimerDelegateCooking; + + /** Timer handle, this timer is used for UI updates. **/ + FTimerHandle TimerHandleUIUpdate; + + /** Timer delegate, we use it for checking if details panel update can be performed. **/ + FTimerDelegate TimerDelegateUIUpdate; + + /** Id of corresponding Houdini asset. **/ + HAPI_NodeId AssetId; + + /** Scale factor used for generated geometry of this component. **/ + float GeneratedGeometryScaleFactor; + + /** Scale factor used for geo transforms of this component. **/ + float TransformScaleFactor; + + /** Import axis. **/ + EHoudiniRuntimeSettingsAxisImport ImportAxis; + + /** Used to delay notification updates for HAPI asynchronous work. **/ + double HapiNotificationStarted; + + /** Number of times this asset has been cooked. **/ + int32 AssetCookCount; + + /** Indicates the asset is being istantiated to avoid instantiating it twice on load **/ + bool bAssetIsBeingInstantiated; + + /** Indicates that new asset's mesh must rebuild the Navigation System to update the NavMesh properly **/ + bool bNeedToUpdateNavigationSystem; + + /** Map used to preset the asset's inputs for Houdini Tools, maps a UObject to an Input number **/ + TMap HoudiniToolInputPreset; + + /** Flags used by Houdini component. **/ + union + { + struct + { + /** Enables cooking for this Houdini Asset. **/ + uint32 bEnableCooking : 1; + + /** Enables uploading of transformation changes back to Houdini Engine. **/ + uint32 bUploadTransformsToHoudiniEngine : 1; + + /** Enables cooking upon transformation changes. **/ + uint32 bTransformChangeTriggersCooks : 1; + + /** Is set to true when this component contains Houdini logo geometry. **/ + uint32 bContainsHoudiniLogoGeometry : 1; + + /** Is set to true when this component is native and false is when it is dynamic. **/ + uint32 bIsNativeComponent : 1; + + /** Is set to true when this component belongs to a preview actor. **/ + uint32 bIsPreviewComponent : 1; + + /** Is set to true if this component has been loaded. **/ + uint32 bLoadedComponent : 1; + + /** Unused **/ + uint32 bIsPlayModeActive_Unused : 1; + + /** unused flag **/ + uint32 bTimeCookInPlaymode_Unused : 1; + + /** Is set to true when Houdini materials are used. **/ + uint32 bUseHoudiniMaterials : 1; + + /** Is set to true when cooking this asset will trigger cooks of downstream connected assets. **/ + uint32 bCookingTriggersDownstreamCooks : 1; + + /** Is set to true after the asset is fully loaded and registered **/ + uint32 bFullyLoaded : 1; + }; + + uint32 HoudiniAssetComponentFlagsPacked; + }; + + /** Transient flags used by Houdini component. **/ + union + { + struct + { + /** Is set to true when component has been created as result of copying / import. **/ + uint32 bComponentCopyImported : 1; + + /** Is set when asset is changed during transaction. **/ + uint32 bTransactionAssetChange : 1; + + /** Is set to true when we are waiting for upstream input assets to instantiate. **/ + uint32 bWaitingForUpstreamAssetsToInstantiate : 1; + + /** Is set to true when one of the parameters has been modified. This will trigger recook. **/ + uint32 bParametersChanged : 1; + + /** Is set to true when the asset needs cooking after a parameter/transform change wasn't cooked. **/ + uint32 bComponentNeedsCook : 1; + + /** Is set to true when component is loaded and requires instantiation. **/ + uint32 bLoadedComponentRequiresInstantiation : 1; + }; + + uint32 HoudiniAssetComponentTransientFlagsPacked; + }; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponentMaterials.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponentMaterials.cpp new file mode 100644 index 00000000..bc873397 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponentMaterials.cpp @@ -0,0 +1,138 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniAssetComponent.h" + +#include "HoudiniApi.h" +#include "HoudiniAssetComponentMaterials.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + + +UHoudiniAssetComponentMaterials::UHoudiniAssetComponentMaterials( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) + , HoudiniAssetComponentMaterialsFlagsPacked( 0u ) +{} + +void +UHoudiniAssetComponentMaterials::Serialize( FArchive & Ar ) +{ + // Call base implementation. + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + Ar << Assignments; + Ar << Replacements; +} + +void +UHoudiniAssetComponentMaterials::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniAssetComponentMaterials * HoudiniAssetComponentMaterials = Cast< UHoudiniAssetComponentMaterials >( InThis ); + if ( HoudiniAssetComponentMaterials ) + { + // Add references to all cached materials. + for ( TMap< FString, UMaterialInterface * >::TIterator + Iter( HoudiniAssetComponentMaterials->Assignments ); Iter; ++Iter ) + { + UMaterialInterface * Material = Iter.Value(); + Collector.AddReferencedObject( Material, InThis ); + } + + // Add references for replaced materials. + for ( TMap< FHoudiniGeoPartObject, TMap< FString, UMaterialInterface * > >::TIterator + Iter( HoudiniAssetComponentMaterials->Replacements ); Iter; ++Iter ) + { + TMap< FString, UMaterialInterface * > & MaterialReplacementsValues = Iter.Value(); + + for ( TMap< FString, UMaterialInterface * >::TIterator + IterInterfaces( MaterialReplacementsValues ); IterInterfaces; ++IterInterfaces ) + { + UMaterialInterface * MaterialInterface = IterInterfaces.Value(); + if ( MaterialInterface ) + Collector.AddReferencedObject( MaterialInterface, InThis ); + } + } + } + + // Call base implementation. + Super::AddReferencedObjects( InThis, Collector ); +} + +void +UHoudiniAssetComponentMaterials::ResetMaterialInfo() +{ + Assignments.Empty(); + Replacements.Empty(); +} + +UHoudiniAssetComponentMaterials* +UHoudiniAssetComponentMaterials::Duplicate( class UHoudiniAssetComponent* InOuter, TMap& InReplacements ) +{ + UHoudiniAssetComponentMaterials* ACM = DuplicateObject( this, InOuter ); + + // Build a map of MI duplications + TMap MatReplacements; + for( auto ReplacementPair : InReplacements ) + { + UStaticMesh* OriginalSM = Cast( ReplacementPair.Key ); + UStaticMesh* NewSM = Cast( ReplacementPair.Value ); + if( OriginalSM && NewSM ) + { + for( int32 Ix = 0; Ix < OriginalSM->StaticMaterials.Num(); ++Ix ) + { + MatReplacements.Add( OriginalSM->StaticMaterials[ Ix ].MaterialInterface, + NewSM->StaticMaterials[ Ix ].MaterialInterface); + } + } + } + + // Remap MIs + for( auto& AssignmentPair : ACM->Assignments ) + { + if( auto NewMI = MatReplacements.Find( AssignmentPair.Value ) ) + { + AssignmentPair.Value = *NewMI; + } + } + + for( auto& ReplacementPair : ACM->Replacements ) + { + for( auto& MatPair : ReplacementPair.Value ) + { + if( auto NewMI = MatReplacements.Find( MatPair.Value ) ) + { + MatPair.Value = *NewMI; + } + } + } + return ACM; +} \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponentMaterials.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponentMaterials.h new file mode 100644 index 00000000..fb056e4d --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponentMaterials.h @@ -0,0 +1,74 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + + +#pragma once +#include "HoudiniGeoPartObject.h" +#include "UObject/Object.h" +#include "HoudiniAssetComponentMaterials.generated.h" + + +class UMaterial; +class UMaterialInterface; +class UHoudiniAssetComponent; + +UCLASS( EditInlineNew, config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniAssetComponentMaterials : public UObject +{ + GENERATED_UCLASS_BODY() + + friend class UHoudiniAssetComponent; + friend struct FHoudiniEngineUtils; + + /** UObject methods. **/ + public: + + virtual void Serialize( FArchive & Ar ) override; + static void AddReferencedObjects( + UObject * InThis, FReferenceCollector & Collector); + public: + /** Duplicate this object for the given Outer. Replace material references based on the given replacement map */ + class UHoudiniAssetComponentMaterials* Duplicate( class UHoudiniAssetComponent* InOuter, TMap& InReplacements ); + + /** Reset the object. **/ + void ResetMaterialInfo(); + + protected: + + /** Material assignments. **/ + TMap< FString, UMaterialInterface * > Assignments; + + /** Material replacements. **/ + TMap< FHoudiniGeoPartObject, TMap< FString, UMaterialInterface * > > Replacements; + + /** Flags used by this instance. **/ + uint32 HoudiniAssetComponentMaterialsFlagsPacked; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInput.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInput.cpp new file mode 100644 index 00000000..fbf04077 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInput.cpp @@ -0,0 +1,4147 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetInput.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniAssetParameter.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetParameterInt.h" +#include "HoudiniAssetParameterChoice.h" +#include "HoudiniAssetParameterToggle.h" +#include "HoudiniEngine.h" +#include "HoudiniAssetActor.h" +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniEngineString.h" +#include "HoudiniLandscapeUtils.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "Components/SplineComponent.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Engine/Selection.h" +#include "Internationalization/Internationalization.h" +#include "EngineUtils.h" // for TActorIterator<> + +#if WITH_EDITOR +#include "Editor.h" +#include "Editor/UnrealEdEngine.h" +#include "UnrealEdGlobals.h" +#endif + +#if WITH_EDITOR +// Allows checking of objects currently being dragged around +struct FHoudiniMoveTracker +{ + FHoudiniMoveTracker() : IsObjectMoving(false) + { + GEditor->OnBeginObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = true; }); + GEditor->OnEndObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); + } + static FHoudiniMoveTracker& Get() { static FHoudiniMoveTracker Instance; return Instance; } + + bool IsObjectMoving; +}; +#endif + +static FName NAME_HoudiniNoUpload( TEXT( "HoudiniNoUpload" ) ); + + +#define LOCTEXT_NAMESPACE HOUDINI_MODULE_RUNTIME + +void +FHoudiniAssetInputOutlinerMesh::Serialize( FArchive & Ar ) +{ + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniAssetParameterVersion; + + Ar << ActorPtr; + if ( ( HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME ) + && ( HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX ) ) + { + Ar << ActorPathName; + } + + if ( HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY ) + { + Ar << StaticMeshComponent; + Ar << StaticMesh; + } + + Ar << ActorTransform; + + Ar << AssetId; + if ( Ar.IsLoading() && !Ar.IsTransacting() ) + AssetId = -1; + + if ( HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE + && HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY ) + { + Ar << SplineComponent; + Ar << NumberOfSplineControlPoints; + Ar << SplineLength; + Ar << SplineResolution; + Ar << ComponentTransform; + } + + if ( HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM ) + Ar << KeepWorldTransform; + + // UE4.19 SERIALIZATION FIX: + // The component materials serialization (24) was actually missing in the UE4.19 H17.0 / H16.5 plugin. + // However subsequent serialized changes (25+) were present in those version. This caused crashes when loading + // a level that was saved with 4.19+16.5/17.0 on a newer version of Unreal or Houdini... + // If the serialized version is exactly that of the fix, we can ignore the materials paths as well + if ( ( HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT ) + && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX ) ) + Ar << MeshComponentsMaterials; + + if ( HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX ) + Ar << InstanceIndex; +} + +void +FHoudiniAssetInputOutlinerMesh::RebuildSplineTransformsArrayIfNeeded() +{ + // Rebuilding the SplineTransform array after reloading the asset + // This is required to properly detect Transform changes after loading the asset. + + // We need an Unreal spline + if ( !SplineComponent || SplineComponent->IsPendingKill() ) + return; + + // If those are different, the input component has changed + if ( NumberOfSplineControlPoints != SplineComponent->GetNumberOfSplinePoints() ) + return; + + // If those are equals, there's no need to rebuild the array + if ( SplineControlPointsTransform.Num() == SplineComponent->GetNumberOfSplinePoints() ) + return; + + SplineControlPointsTransform.SetNumUninitialized(SplineComponent->GetNumberOfSplinePoints()); + for ( int32 n = 0; n < SplineControlPointsTransform.Num(); n++ ) + SplineControlPointsTransform[n] = SplineComponent->GetTransformAtSplinePoint( n, ESplineCoordinateSpace::Local, true ); +} + +bool +FHoudiniAssetInputOutlinerMesh::HasSplineComponentChanged(float fCurrentSplineResolution) const +{ + if ( !SplineComponent || SplineComponent->IsPendingKill() ) + return false; + + // Total length of the spline has changed ? + if ( SplineComponent->GetSplineLength() != SplineLength ) + return true; + + // Number of CVs has changed ? + if ( NumberOfSplineControlPoints != SplineComponent->GetNumberOfSplinePoints() ) + return true; + + if ( SplineControlPointsTransform.Num() != SplineComponent->GetNumberOfSplinePoints() ) + return true; + + // Current Spline resolution has changed? + if ( fCurrentSplineResolution == -1.0 && SplineResolution != -1.0) + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if ( HoudiniRuntimeSettings ) + fCurrentSplineResolution = HoudiniRuntimeSettings->MarshallingSplineResolution; + else + fCurrentSplineResolution = HAPI_UNREAL_PARAM_SPLINE_RESOLUTION_DEFAULT; + } + + if ( SplineResolution != fCurrentSplineResolution ) + return true; + + // Has any of the CV's transform been modified? + for ( int32 n = 0; n < SplineControlPointsTransform.Num(); n++ ) + { + if ( !SplineControlPointsTransform[ n ].GetLocation().Equals( SplineComponent->GetLocationAtSplinePoint( n, ESplineCoordinateSpace::Local) ) ) + return true; + + if ( !SplineControlPointsTransform[ n ].GetRotation().Equals( SplineComponent->GetQuaternionAtSplinePoint( n, ESplineCoordinateSpace::World ) ) ) + return true; + + if ( !SplineControlPointsTransform[ n ].GetScale3D().Equals( SplineComponent->GetScaleAtSplinePoint( n ) ) ) + return true; + } + + return false; +} + + +bool +FHoudiniAssetInputOutlinerMesh::HasActorTransformChanged() const +{ + if ( !ActorPtr.IsValid() ) + return false; + + if ( !ActorTransform.Equals( ActorPtr->GetTransform() ) ) + return true; + + return false; +} + + +bool +FHoudiniAssetInputOutlinerMesh::HasComponentTransformChanged() const +{ + if ( StaticMeshComponent && !StaticMeshComponent->IsPendingKill() ) + { + // Handle instances here + UInstancedStaticMeshComponent * InstancedStaticMeshComponent = Cast< UInstancedStaticMeshComponent >( StaticMeshComponent ); + if (InstancedStaticMeshComponent ) + { + FTransform InstanceTransform; + if ( InstancedStaticMeshComponent->GetInstanceTransform( InstanceIndex, InstanceTransform, true ) ) + return !ComponentTransform.Equals( InstanceTransform ); + } + else + return !ComponentTransform.Equals( StaticMeshComponent->GetComponentTransform() ); + } + + if ( SplineComponent && !SplineComponent->IsPendingKill() ) + return !ComponentTransform.Equals( SplineComponent->GetComponentTransform() ); + + return false; +} + +bool +FHoudiniAssetInputOutlinerMesh::HasComponentMaterialsChanged() const +{ + if ( !StaticMeshComponent || StaticMeshComponent->IsPendingKill() ) + return false; + + if ( StaticMeshComponent->GetNumMaterials() != MeshComponentsMaterials.Num() ) + return true; + + for ( int32 n = 0; n < MeshComponentsMaterials.Num(); n++ ) + { + UMaterialInterface* MI = StaticMeshComponent->GetMaterial(n); + FString mat_interface_path = MI->GetPathName(); + if (!MI || MI->IsPendingKill()) + mat_interface_path = FString(); + + if (mat_interface_path != MeshComponentsMaterials[ n ]) + return true; + } + + return false; +} + +bool +FHoudiniAssetInputOutlinerMesh::NeedsComponentUpdate() const +{ + if ( !ActorPtr.IsValid() || ActorPtr->IsPendingKill() ) + return false; + + if ( StaticMesh && ( !StaticMesh->IsValidLowLevel() || StaticMesh->IsPendingKill() ) ) + return true; + + if ( StaticMeshComponent && ( !StaticMeshComponent->IsValidLowLevel() || StaticMeshComponent->IsPendingKill() ) ) + return true; + + if ( SplineComponent && ( !SplineComponent->IsValidLowLevel() || SplineComponent->IsPendingKill() ) ) + return true; + + if ( !StaticMesh && !StaticMeshComponent && !SplineComponent ) + return true; + + return false; +} + + +bool +FHoudiniAssetInputOutlinerMesh::TryToUpdateActorPtrFromActorPathName() +{ + // Ensure our current ActorPathName looks valid + if (ActorPathName.IsEmpty() || ActorPathName.Equals(TEXT("None"), ESearchCase::IgnoreCase)) + return false; + + // We'll try to find the corresponding actor by browsing through all the actors in the world.. + // Get the editor world + UWorld* World = nullptr; +#if WITH_EDITOR + World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr; +#endif + if (!World) + return false; + + // Then try to find the actor corresponding to our path/name + bool FoundActor = false; + for (TActorIterator ActorIt(World); ActorIt; ++ActorIt) + { + if (ActorIt->GetPathName() != ActorPathName) + continue; + + // We found the actor + ActorPtr = *ActorIt; + FoundActor = true; + + break; + } + + if ( FoundActor ) + { + // We need to invalid our components so they can be updated later + // from the new actor + StaticMesh = NULL; + StaticMeshComponent = NULL; + SplineComponent = NULL; + } + + return FoundActor; +} + +UHoudiniAssetInput::UHoudiniAssetInput( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) + , InputCurve( nullptr ) + , InputAssetComponent( nullptr ) + , InputLandscapeProxy( nullptr ) + , ConnectedAssetId( -1 ) + , InputIndex( 0 ) + , ChoiceIndex( EHoudiniAssetInputType::GeometryInput ) + , UnrealSplineResolution( -1.0f ) + , OutlinerInputsNeedPostLoadInit( false ) + , HoudiniAssetInputFlagsPacked( 0u ) +{ + // flags + bLandscapeExportMaterials = true; + bKeepWorldTransform = 2; + bLandscapeExportAsHeightfield = true; + bLandscapeAutoSelectComponent = true; + bPackBeforeMerge = false; + bExportAllLODs = false; + bExportSockets = false; + bUpdateInputLandscape = false; + + ChoiceStringValue = TEXT( "" ); + + // Initialize the arrays + InputObjects.SetNumUninitialized( 1 ); + InputObjects[ 0 ] = nullptr; + InputTransforms.SetNumUninitialized( 1 ); + InputTransforms[ 0 ] = FTransform::Identity; + TransformUIExpanded.SetNumUninitialized( 1 ); + TransformUIExpanded[ 0 ] = false; + SkeletonInputObjects.SetNumUninitialized(1); + SkeletonInputObjects[0] = nullptr; + InputLandscapeTransform = FTransform::Identity; +} + +UHoudiniAssetInput * +UHoudiniAssetInput::Create( UObject * InPrimaryObject, int32 InInputIndex, HAPI_NodeId InNodeId ) +{ + UHoudiniAssetInput * HoudiniAssetInput = nullptr; + + if (!InPrimaryObject || InPrimaryObject->IsPendingKill()) + return HoudiniAssetInput; + + // Get name of this input. + HAPI_StringHandle InputStringHandle; + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInputName( + FHoudiniEngine::Get().GetSession(), + InNodeId, InInputIndex, &InputStringHandle ) ) + { + return HoudiniAssetInput; + } + + HoudiniAssetInput = NewObject< UHoudiniAssetInput >( + InPrimaryObject, + UHoudiniAssetInput::StaticClass(), + NAME_None, RF_Public | RF_Transactional ); + + // Set component and other information. + HoudiniAssetInput->PrimaryObject = InPrimaryObject; + HoudiniAssetInput->InputIndex = InInputIndex; + + // Get input string from handle. + HoudiniAssetInput->SetNameAndLabel( InputStringHandle ); + + HoudiniAssetInput->SetNodeId( InNodeId ); + + // Set the default input type from the input's label + HoudiniAssetInput->SetDefaultInputTypeFromLabel(); + + // Create necessary widget resources. + HoudiniAssetInput->CreateWidgetResources(); + + return HoudiniAssetInput; +} + +UHoudiniAssetInput * +UHoudiniAssetInput::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo) +{ + UObject * Outer = InPrimaryObject; + if (!Outer) + { + Outer = InParentParameter; + if (!Outer) + { + // Must have either component or parent not null. + check(false); + } + } + + UHoudiniAssetInput * HoudiniAssetInput = NewObject< UHoudiniAssetInput >( + Outer, UHoudiniAssetInput::StaticClass(), NAME_None, RF_Public | RF_Transactional); + + // This is being used as a parameter + HoudiniAssetInput->bIsObjectPathParameter = true; + + HoudiniAssetInput->CreateParameter(InPrimaryObject, InParentParameter, InNodeId, ParmInfo); + + HoudiniAssetInput->SetNodeId( InNodeId ); + + // Set the default input type from the input's label + HoudiniAssetInput->SetDefaultInputTypeFromLabel(); + + // Create necessary widget resources. + HoudiniAssetInput->CreateWidgetResources(); + + // Use the value of the object path param to preset the + // default object for Geometry inputs + HoudiniAssetInput->SetDefaultAssetFromHDA(); + + return HoudiniAssetInput; +} + +void +UHoudiniAssetInput::CreateWidgetResources() +{ + ChoiceStringValue = TEXT( "" ); + StringChoiceLabels.Empty(); + + { + FString * ChoiceLabel = new FString( TEXT( "Geometry Input" ) ); + StringChoiceLabels.Add( TSharedPtr< FString >( ChoiceLabel ) ); + + if ( ChoiceIndex == EHoudiniAssetInputType::GeometryInput ) + ChoiceStringValue = *ChoiceLabel; + } + { + FString * ChoiceLabel = new FString( TEXT( "Asset Input" ) ); + StringChoiceLabels.Add( TSharedPtr< FString >( ChoiceLabel ) ); + + if ( ChoiceIndex == EHoudiniAssetInputType::AssetInput ) + ChoiceStringValue = *ChoiceLabel; + } + { + FString * ChoiceLabel = new FString( TEXT( "Curve Input" ) ); + StringChoiceLabels.Add( TSharedPtr< FString >( ChoiceLabel ) ); + + if ( ChoiceIndex == EHoudiniAssetInputType::CurveInput ) + ChoiceStringValue = *ChoiceLabel; + } + { + FString * ChoiceLabel = new FString( TEXT( "Landscape Input" ) ); + StringChoiceLabels.Add( TSharedPtr< FString >( ChoiceLabel ) ); + + if ( ChoiceIndex == EHoudiniAssetInputType::LandscapeInput ) + ChoiceStringValue = *ChoiceLabel; + } + { + FString * ChoiceLabel = new FString( TEXT( "World Outliner Input" ) ); + StringChoiceLabels.Add( TSharedPtr< FString >( ChoiceLabel ) ); + + if ( ChoiceIndex == EHoudiniAssetInputType::WorldInput ) + ChoiceStringValue = *ChoiceLabel; + } + { + FString * ChoiceLabel = new FString(TEXT("Skeletal Mesh Input")); + StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); + + if (ChoiceIndex == EHoudiniAssetInputType::SkeletonInput) + ChoiceStringValue = *ChoiceLabel; + } +} + +void +UHoudiniAssetInput::DisconnectAndDestroyInputAsset() +{ + if ( ChoiceIndex == EHoudiniAssetInputType::AssetInput ) + { + if( bIsObjectPathParameter ) + { + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NodeId, "", + ParmId, 0 ); + } + if ( InputAssetComponent ) + InputAssetComponent->RemoveDownstreamAsset( GetHoudiniAssetComponent(), InputIndex ); + + bInputAssetConnectedInHoudini = false; + ConnectedAssetId = -1; + } + else + { + if ( bIsObjectPathParameter ) + { + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NodeId, "", ParmId, 0 ); + } + else + { + HAPI_NodeId HostAssetId = GetAssetId(); + if ( FHoudiniEngineUtils::IsValidNodeId( ConnectedAssetId ) && FHoudiniEngineUtils::IsValidNodeId( HostAssetId ) ) + FHoudiniEngineUtils::HapiDisconnectAsset( HostAssetId, InputIndex ); + } + + // Destroy all the geo input assets + for ( HAPI_NodeId AssetNodeId : CreatedInputDataAssetIds ) + { + if ( FHoudiniEngineUtils::IsHoudiniNodeValid( AssetNodeId ) ) + FHoudiniEngineUtils::DestroyHoudiniAsset( AssetNodeId ); + } + CreatedInputDataAssetIds.Empty(); + + // Then simply destroy the input's parent OBJ node + if ( FHoudiniEngineUtils::IsValidNodeId( ConnectedAssetId ) ) + { + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId( ConnectedAssetId ); + if ( FHoudiniEngineUtils::IsHoudiniNodeValid( ParentId ) ) + FHoudiniEngineUtils::DestroyHoudiniAsset( ParentId ); + + FHoudiniEngineUtils::DestroyHoudiniAsset( ConnectedAssetId ); + } + ConnectedAssetId = -1; + if ( ChoiceIndex == EHoudiniAssetInputType::WorldInput ) + { + // World Input Actors' Meshes need to have their corresponding Input Assets destroyed too. + for ( int32 n = 0; n < InputOutlinerMeshArray.Num(); n++ ) + { + InputOutlinerMeshArray[ n ].AssetId = -1; + } + } + + /* + if ( ChoiceIndex == EHoudiniAssetInputType::WorldInput ) + { + // World Input Actors' Meshes need to have their corresponding Input Assets destroyed too. + for ( int32 n = 0; n < InputOutlinerMeshArray.Num(); n++ ) + { + if ( FHoudiniEngineUtils::IsValidAssetId( InputOutlinerMeshArray[n].AssetId ) ) + { + FHoudiniEngineUtils::HapiDisconnectAsset( ConnectedAssetId, n );//InputOutlinerMeshArray[n].AssetId); + FHoudiniEngineUtils::DestroyHoudiniAsset( InputOutlinerMeshArray[n].AssetId ); + InputOutlinerMeshArray[n].AssetId = -1; + } + } + } + else if ( ChoiceIndex == EHoudiniAssetInputType::GeometryInput ) + { + // Destroy all the geo input assets + for ( HAPI_NodeId AssetNodeId : CreatedInputDataAssetIds ) + { + FHoudiniEngineUtils::DestroyHoudiniAsset( AssetNodeId ); + } + CreatedInputDataAssetIds.Empty(); + } + else if ( ChoiceIndex == EHoudiniAssetInputType::LandscapeInput ) + { + // Destroy the landscape node + // If the landscape was sent as a heightfield, also destroy the heightfield's input node + FHoudiniLandscapeUtils::DestroyLandscapeAssetNode( ConnectedAssetId, CreatedInputDataAssetIds ); + } + else if ( ChoiceIndex == EHoudiniAssetInputType::SkeletonInput ) + { + // Destroy all the skeleton input assets + for ( HAPI_NodeId AssetNodeId : CreatedInputDataAssetIds ) + { + FHoudiniEngineUtils::DestroyHoudiniAsset( AssetNodeId ); + } + CreatedInputDataAssetIds.Empty(); + } + + if ( FHoudiniEngineUtils::IsValidAssetId( ConnectedAssetId ) ) + { + FHoudiniEngineUtils::DestroyHoudiniAsset( ConnectedAssetId ); + ConnectedAssetId = -1; + } + */ + } +} + +bool +UHoudiniAssetInput::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo) +{ + // Inputs should never call this + check(bIsObjectPathParameter); + + if (!Super::CreateParameter(InPrimaryObject, InParentParameter, InNodeId, ParmInfo)) + return false; + + // We can only handle node type. + if (ParmInfo.type != HAPI_PARMTYPE_NODE) + return false; + + return true; +} + +#if WITH_EDITOR + +void +UHoudiniAssetInput::PostEditUndo() +{ + Super::PostEditUndo(); + + if ( InputCurve + && !InputCurve->IsPendingKill() + && ChoiceIndex == EHoudiniAssetInputType::CurveInput ) + { + USceneComponent* RootComp = GetHoudiniAssetComponent(); + if ( RootComp && !RootComp->IsPendingKill() ) + { + AActor* Owner = RootComp->GetOwner(); + if ( Owner && !Owner->IsPendingKill() ) + Owner->AddOwnedComponent( InputCurve ); + + InputCurve->AttachToComponent( RootComp, FAttachmentTransformRules::KeepRelativeTransform ); + InputCurve->RegisterComponent(); + InputCurve->SetVisibility( true ); + } + } +} + +void +UHoudiniAssetInput::ForceSetInputObject(UObject * InObject, int32 AtIndex, bool CommitChange) +{ + EHoudiniAssetInputType::Enum NewInputType = EHoudiniAssetInputType::GeometryInput; + if ( AActor* Actor = Cast( InObject ) ) + { + // Update the OutlinerInputArray from the actor + UpdateInputOulinerArrayFromActor(Actor, true); + if ( InputOutlinerMeshArray.Num() > 0 ) + NewInputType = EHoudiniAssetInputType::WorldInput; + + // Looking for houdini assets + if ( AHoudiniAssetActor * HoudiniAssetActor = Cast( Actor ) ) + { + OnInputActorSelected( Actor ); + if ( InputAssetComponent ) + NewInputType = EHoudiniAssetInputType::AssetInput; + + } + + // Looking for Landscapes + if (ALandscapeProxy * Landscape = Cast< ALandscapeProxy >( Actor ) ) + { + // Store new landscape. + OnLandscapeActorSelected( Actor ); + NewInputType = EHoudiniAssetInputType::LandscapeInput; + } + } + else + { + // The object is not a world actor, so add it to the geo input + if( InputObjects.IsValidIndex( AtIndex ) ) + { + InputObjects[ AtIndex ] = InObject; + } + else + { + InputObjects.Insert( InObject, AtIndex ); + } + + if ( !InputTransforms.IsValidIndex( AtIndex ) ) + { + InputTransforms.Insert( FTransform::Identity, AtIndex ); + } + + if ( !TransformUIExpanded.IsValidIndex( AtIndex ) ) + { + TransformUIExpanded.Insert( false, AtIndex ); + } + } + + if( CommitChange ) + { + // We must change ChoiceIndex's value manually before calling ChangeInputType: + // If we are forcing the input to an asset input, not doing so would actually destroy the + // parent HDA node on the houdini side, making the input HDA to need a reinstantiation on first cook... + ChoiceIndex = NewInputType; + + ChangeInputType( NewInputType, true ); + MarkChanged(); + } +} + +// Note: This method is only used for testing +void +UHoudiniAssetInput::ClearInputs() +{ + ChangeInputType( EHoudiniAssetInputType::GeometryInput, true ); + InputOutlinerMeshArray.Empty(); + InputObjects.Empty(); + InputTransforms.Empty(); + TransformUIExpanded.Empty(); + MarkChanged(); +} + +#endif // WITH_EDITOR + +bool +UHoudiniAssetInput::ConnectInputNode() +{ + if (!FHoudiniEngineUtils::IsValidNodeId(ConnectedAssetId)) + return false; + + // Helper for connecting our input or setting the object path parameter + if (!bIsObjectPathParameter) + { + // Now we can connect input node to the asset node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GetAssetId(), InputIndex, + ConnectedAssetId, 0), false); + } + else + { + if (!FHoudiniEngineUtils::IsValidNodeId(NodeId)) + return false; + + // Now we can assign the input node path to the parameter + std::string ParamNameString = TCHAR_TO_UTF8(*GetParameterName()); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmNodeValue( + FHoudiniEngine::Get().GetSession(), NodeId, + ParamNameString.c_str(), ConnectedAssetId), false); + } + return true; +} + +bool +UHoudiniAssetInput::UploadParameterValue() +{ + bool Success = true; + + if ( !PrimaryObject || PrimaryObject->IsPendingKill() ) + return false; + + HAPI_NodeId HostAssetId = GetAssetId(); + + switch ( ChoiceIndex ) + { + case EHoudiniAssetInputType::GeometryInput: + { + if ( !InputObjects.Num() ) + { + // Either mesh was reset or null mesh has been assigned. + DisconnectAndDestroyInputAsset(); + } + else + { + if ( bStaticMeshChanged || bLoadedParameter ) + { + // Disconnect and destroy currently connected asset, if there's one. + DisconnectAndDestroyInputAsset(); + + // Connect input and create connected asset. Will return by reference. + if ( !FHoudiniEngineUtils::HapiCreateInputNodeForObjects( + HostAssetId, InputObjects, InputTransforms, + ConnectedAssetId, CreatedInputDataAssetIds, + false, bExportAllLODs, bExportSockets ) ) + { + bChanged = false; + ConnectedAssetId = -1; + return false; + } + else + { + Success &= ConnectInputNode(); + } + + bStaticMeshChanged = false; + } + + Success &= UpdateObjectMergeTransformType(); + Success &= UpdateObjectMergePackBeforeMerge(); + } + break; + } + + case EHoudiniAssetInputType::AssetInput: + { + // Process connected asset. + if ( InputAssetComponent + && !InputAssetComponent->IsPendingKill() + && FHoudiniEngineUtils::IsValidNodeId( InputAssetComponent->GetAssetId() ) ) + { + // Connect a new asset / we have previously connected asset + if (!bInputAssetConnectedInHoudini) + ConnectInputAssetActor(); + else + bChanged = false; + + Success &= bInputAssetConnectedInHoudini; + Success &= UpdateObjectMergeTransformType(); + } + else if ( bInputAssetConnectedInHoudini && ( !InputAssetComponent || InputAssetComponent->IsPendingKill() ) ) + { + // Previously connected asset deleted/invalid + DisconnectInputAssetActor(); + } + else + { + // Nothing to connect + bChanged = false; + if ( InputAssetComponent ) + return false; + } + break; + } + + case EHoudiniAssetInputType::CurveInput: + { + // If we have no curve node, create it. + bool bCreated = false; + if ( !FHoudiniEngineUtils::IsValidNodeId( ConnectedAssetId ) ) + { + if ( !FHoudiniEngineUtils::HapiCreateCurveNode( ConnectedAssetId ) ) + { + bChanged = false; + ConnectedAssetId = -1; + return false; + } + + // Connect the node to the asset. + Success &= ConnectInputNode(); + + bCreated = true; + } + + if ( bLoadedParameter || bCreated ) + { + // If we just loaded or created our curve, we need to set parameters. + for (TMap< FString, UHoudiniAssetParameter * >::TIterator + IterParams(InputCurveParameters); IterParams; ++IterParams) + { + UHoudiniAssetParameter * Parameter = IterParams.Value(); + if ( !Parameter || Parameter->IsPendingKill() ) + continue; + + // We need to update the node id for the parameters. + Parameter->SetNodeId(ConnectedAssetId); + + // Upload parameter value. + Success &= Parameter->UploadParameterValue(); + } + } + + if ( ConnectedAssetId != -1 && InputCurve && !InputCurve->IsPendingKill() ) + { + // The curve node has now been created and set up, we can upload points and rotation/scale attributes + const TArray< FTransform > & CurvePoints = InputCurve->GetCurvePoints(); + TArray Positions; + InputCurve->GetCurvePositions(Positions); + + TArray Rotations; + InputCurve->GetCurveRotations(Rotations); + + TArray Scales; + InputCurve->GetCurveScales(Scales); + + if(!FHoudiniEngineUtils::HapiCreateCurveInputNodeForData( + HostAssetId, + ConnectedAssetId, + &Positions, + &Rotations, + &Scales, + nullptr)) + { + bChanged = false; + ConnectedAssetId = -1; + return false; + } + } + + if (bCreated && InputCurve && !InputCurve->IsPendingKill() ) + { + // We need to check that the SplineComponent has no offset. + // if the input was set to WorldOutliner before, it might have one + FTransform CurveTransform = InputCurve->GetRelativeTransform(); + if (!CurveTransform.GetLocation().IsZero()) + InputCurve->SetRelativeLocation(FVector::ZeroVector); + } + + Success &= UpdateObjectMergeTransformType(); + + // Cook the spline node. + if( HAPI_RESULT_SUCCESS != FHoudiniApi::CookNode( FHoudiniEngine::Get().GetSession(), ConnectedAssetId, nullptr ) ) + Success = false; + + // We need to update the curve. + Success &= UpdateInputCurve(); + + bSwitchedToCurve = false; + + break; + } + + case EHoudiniAssetInputType::LandscapeInput: + { + if ( !InputLandscapeProxy || InputLandscapeProxy->IsPendingKill() ) + { + // Either landscape was reset or null landscape has been assigned. + DisconnectAndDestroyInputAsset(); + } + else + { + // Disconnect and destroy currently connected asset, if there's one. + DisconnectAndDestroyInputAsset(); + + // If we're auto-selecting the components, we need to get the asset's bound + FBox Bounds(ForceInitToZero); + if ( bLandscapeAutoSelectComponent ) + { + // Get our asset's or our connected input asset's bounds + UHoudiniAssetComponent* AssetComponent = (UHoudiniAssetComponent*)PrimaryObject; + if (!AssetComponent || AssetComponent->IsPendingKill()) + AssetComponent = InputAssetComponent; + + // Get the bounds, without this input + if (AssetComponent && !AssetComponent->IsPendingKill()) + Bounds = AssetComponent->GetAssetBounds(this, true); + } + + // Connect input and create connected asset. Will return by reference. + if ( !FHoudiniEngineUtils::HapiCreateInputNodeForLandscape( + HostAssetId, InputLandscapeProxy.Get(), + ConnectedAssetId, CreatedInputDataAssetIds, + bLandscapeExportSelectionOnly, bLandscapeExportCurves, + bLandscapeExportMaterials, bLandscapeExportAsMesh, bLandscapeExportLighting, + bLandscapeExportNormalizedUVs, bLandscapeExportTileUVs, Bounds, + bLandscapeExportAsHeightfield, bLandscapeAutoSelectComponent ) ) + { + bChanged = false; + ConnectedAssetId = -1; + return false; + } + + // Connect the inputs and update the transform type + Success &= ConnectInputNode(); + Success &= UpdateObjectMergeTransformType(); + } + break; + } + + case EHoudiniAssetInputType::WorldInput: + { + if ( InputOutlinerMeshArray.Num() <= 0 ) + { + // Either mesh was reset or null mesh has been assigned. + DisconnectAndDestroyInputAsset(); + } + else + { + if ( bStaticMeshChanged || bLoadedParameter ) + { + // Disconnect and destroy currently connected asset, if there's one. + DisconnectAndDestroyInputAsset(); + + // Connect input and create connected asset. Will return by reference. + if ( !FHoudiniEngineUtils::HapiCreateInputNodeForWorldOutliner( + HostAssetId, InputOutlinerMeshArray, ConnectedAssetId, CreatedInputDataAssetIds, + UnrealSplineResolution, bExportAllLODs, bExportSockets ) ) + { + bChanged = false; + ConnectedAssetId = -1; + return false; + } + else + { + Success &= ConnectInputNode(); + } + + bStaticMeshChanged = false; + + Success &= UpdateObjectMergeTransformType(); + } + + Success &= UpdateObjectMergePackBeforeMerge(); + } + break; + } + + case EHoudiniAssetInputType::SkeletonInput: + { + if (!SkeletonInputObjects.Num()) + { + // Either mesh was reset or null mesh has been assigned. + DisconnectAndDestroyInputAsset(); + } + else + { + if (bStaticMeshChanged || bLoadedParameter) + { + // Disconnect and destroy currently connected asset, if there's one. + DisconnectAndDestroyInputAsset(); + + // Connect input and create connected asset. Will return by reference. + if ( !FHoudiniEngineUtils::HapiCreateInputNodeForObjects( + HostAssetId, SkeletonInputObjects, InputTransforms, + ConnectedAssetId, CreatedInputDataAssetIds, true ) ) + { + bChanged = false; + ConnectedAssetId = -1; + return false; + } + else + { + Success &= ConnectInputNode(); + } + + bStaticMeshChanged = false; + } + + Success &= UpdateObjectMergeTransformType(); + Success &= UpdateObjectMergePackBeforeMerge(); + } + break; + } + + default: + { + check( 0 ); + } + } + + bLoadedParameter = false; + return Success & Super::UploadParameterValue(); +} + + +uint32 +UHoudiniAssetInput::GetDefaultTranformTypeValue() const +{ + switch (ChoiceIndex) + { + // NONE + case EHoudiniAssetInputType::CurveInput: + case EHoudiniAssetInputType::GeometryInput: + return 0; + + // INTO THIS OBJECT + case EHoudiniAssetInputType::AssetInput: + case EHoudiniAssetInputType::LandscapeInput: + case EHoudiniAssetInputType::WorldInput: + return 1; + } + + return 0; +} + + +UObject* +UHoudiniAssetInput::GetInputObject( int32 AtIndex ) const +{ + return InputObjects.IsValidIndex( AtIndex ) ? InputObjects[ AtIndex ] : nullptr; +} + +FTransform +UHoudiniAssetInput::GetInputTransform( int32 AtIndex ) const +{ + return InputTransforms.IsValidIndex( AtIndex ) ? InputTransforms[ AtIndex ] : FTransform::Identity; +} + +UObject* +UHoudiniAssetInput::GetSkeletonInputObject( int32 AtIndex ) const +{ + return SkeletonInputObjects.IsValidIndex( AtIndex ) ? SkeletonInputObjects[ AtIndex ] : nullptr; +} + +UHoudiniAssetComponent* +UHoudiniAssetInput::GetHoudiniAssetComponent() +{ + return Cast( PrimaryObject ); +} + +const UHoudiniAssetComponent* +UHoudiniAssetInput::GetHoudiniAssetComponent() const +{ + return Cast( PrimaryObject ); +} + +HAPI_NodeId +UHoudiniAssetInput::GetAssetId() const +{ + return NodeId; +} + +bool +UHoudiniAssetInput::UpdateObjectMergeTransformType() +{ + if ( !PrimaryObject || PrimaryObject->IsPendingKill() ) + return false; + + uint32 nTransformType = -1; + if ( bKeepWorldTransform == 2 ) + nTransformType = GetDefaultTranformTypeValue(); + else if ( bKeepWorldTransform ) + nTransformType = 1; + else + nTransformType = 0; + + // Geometry inputs are always set to none + if ( ChoiceIndex == EHoudiniAssetInputType::GeometryInput ) + nTransformType = 0; + + // Get the Input node ID from the host ID + HAPI_NodeId InputNodeId = -1; + HAPI_NodeId HostAssetId = GetAssetId(); + + bool bSuccess = false; + const std::string sXformType = "xformtype"; + + if ( bIsObjectPathParameter ) + { + // Directly change the Parameter xformtype + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + NodeId, sXformType.c_str(), 0, nTransformType ) ) + bSuccess = true; + } + else + { + // Query the object merge's node ID via the input + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + HostAssetId, InputIndex, &InputNodeId ) ) + { + // Change Parameter xformtype + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + InputNodeId, sXformType.c_str(), 0, nTransformType ) ) + bSuccess = true; + } + } + + if ( ChoiceIndex == EHoudiniAssetInputType::WorldInput ) + { + // For World Inputs, we need to go through each asset selected + // and change the transform type on the merge node's input + for ( int32 n = 0; n < InputOutlinerMeshArray.Num(); n++ ) + { + // Get the Input node ID from the host ID + InputNodeId = -1; + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, n, &InputNodeId ) ) + continue; + + if ( InputNodeId == -1 ) + continue; + + // Change Parameter xformtype + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), InputNodeId, + sXformType.c_str(), 0, nTransformType ) ) + bSuccess = false; + } + } + + return bSuccess; +} + + +bool +UHoudiniAssetInput::UpdateObjectMergePackBeforeMerge() +{ + if ( !PrimaryObject || PrimaryObject->IsPendingKill() ) + return false; + + uint32 nPackValue = bPackBeforeMerge ? 1 : 0; + + // Get the Input node ID from the host ID + HAPI_NodeId InputNodeId = -1; + HAPI_NodeId HostAssetId = GetAssetId(); + + bool bSuccess = true; + const std::string sPack = "pack"; + + // Going through each input asset plugged in the geometry input, + // or through each input asset select in a world input. + int32 NumberOfInputObjects = 0; + if (ChoiceIndex == EHoudiniAssetInputType::GeometryInput) + NumberOfInputObjects = InputObjects.Num(); + else if (ChoiceIndex == EHoudiniAssetInputType::WorldInput) + NumberOfInputObjects = InputOutlinerMeshArray.Num(); + + if (bIsObjectPathParameter) + { + // Directly change the Parameter xformtype + if (HAPI_RESULT_SUCCESS == FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + NodeId, sPack.c_str(), 0, nPackValue)) + bSuccess = true; + } + else + { + // Query the object merge's node ID via the input + if (HAPI_RESULT_SUCCESS == FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + HostAssetId, InputIndex, &InputNodeId)) + { + // Change Parameter xformtype + if (HAPI_RESULT_SUCCESS == FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + InputNodeId, sPack.c_str(), 0, nPackValue)) + bSuccess = true; + } + } + + // We also need to modify the transform types of the merge node's inputs + for ( int n = 0; n < NumberOfInputObjects; n++ ) + { + // Get the Input node ID from the host ID + InputNodeId = -1; + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, n, &InputNodeId ) ) + continue; + + if ( InputNodeId == -1 ) + continue; + + // Change Parameter xformtype + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), InputNodeId, + sPack.c_str(), 0, nPackValue ) ) + bSuccess = false; + } + + return bSuccess; +} + +void +UHoudiniAssetInput::BeginDestroy() +{ + Super::BeginDestroy(); + + // Destroy anything curve related. + DestroyInputCurve(); + + // Disconnect and destroy the asset we may have connected. + DisconnectAndDestroyInputAsset(); +} + +void +UHoudiniAssetInput::PostLoad() +{ + Super::PostLoad(); + + // Generate widget related resources. + CreateWidgetResources(); + + // Patch input curve parameter links. + for ( TMap< FString, UHoudiniAssetParameter * >::TIterator IterParams( InputCurveParameters ); IterParams; ++IterParams ) + { + FString ParameterKey = IterParams.Key(); + UHoudiniAssetParameter * Parameter = IterParams.Value(); + + if ( Parameter ) + { + Parameter->SetHoudiniAssetComponent( nullptr ); + Parameter->SetParentParameter( this ); + } + } + + if ( InputCurve && !InputCurve->IsPendingKill() ) + { + if (ChoiceIndex == EHoudiniAssetInputType::CurveInput) + { + // Set input callback object for this curve. + InputCurve->SetHoudiniAssetInput(this); + USceneComponent* RootComp = GetHoudiniAssetComponent(); + if( RootComp && !RootComp->IsPendingKill() ) + { + InputCurve->AttachToComponent( RootComp, FAttachmentTransformRules::KeepRelativeTransform ); + } + } + else + { + // Manually destroying the "ghost" curve + InputCurve->DetachFromComponent( FDetachmentTransformRules::KeepRelativeTransform ); + InputCurve->UnregisterComponent(); + InputCurve->DestroyComponent(); + InputCurve = nullptr; + } + } + + if (InputOutlinerMeshArray.Num() > 0) + { + // Proper initialization of the outliner inputs is delayed to the first WorldTick, + // As some of the Actors' components might not be properly initialized yet + OutlinerInputsNeedPostLoadInit = true; + +#if WITH_EDITOR + StartWorldOutlinerTicking(); +#endif + } + + // Also set the expanded ui + TransformUIExpanded.SetNumZeroed( InputObjects.Num() ); +} + +void +UHoudiniAssetInput::Serialize( FArchive & Ar ) +{ + // Call base implementation. + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + // Serialize current choice selection. + SerializeEnumeration< EHoudiniAssetInputType::Enum >( Ar, ChoiceIndex ); + Ar << ChoiceStringValue; + + // We need these temporary variables for undo state tracking. + bool bLocalInputAssetConnectedInHoudini = bInputAssetConnectedInHoudini; + UHoudiniAssetComponent * LocalInputAssetComponent = InputAssetComponent; + + Ar << HoudiniAssetInputFlagsPacked; + + // Serialize input index. + Ar << InputIndex; + + // Serialize input objects (if it's assigned). + if ( HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT) + { + Ar << InputObjects; + } + else + { + UObject* InputObject = nullptr; + Ar << InputObject; + InputObjects.Empty(); + InputObjects.Add( InputObject ); + } + + // Serialize input asset. + Ar << InputAssetComponent; + + // Serialize curve and curve parameters (if we have those). + Ar << InputCurve; + Ar << InputCurveParameters; + + // Serialize landscape used for input. + if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT) + { + if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF) + { + ALandscapeProxy* InputLandscapePtr = nullptr; + Ar << InputLandscapePtr; + + InputLandscapeProxy = InputLandscapePtr; + } + else + { + Ar << InputLandscapeProxy; + } + + } + + // Serialize world outliner inputs. + if ( HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT ) + { + Ar << InputOutlinerMeshArray; + } + + // Create necessary widget resources. + if ( Ar.IsLoading() ) + { + bLoadedParameter = true; + + if ( Ar.IsTransacting() ) + { + bInputAssetConnectedInHoudini = bLocalInputAssetConnectedInHoudini; + + if ( LocalInputAssetComponent != InputAssetComponent ) + { + if ( InputAssetComponent ) + bInputAssetConnectedInHoudini = false; + + if ( LocalInputAssetComponent ) + LocalInputAssetComponent->RemoveDownstreamAsset( GetHoudiniAssetComponent(), InputIndex ); + } + } + else + { + // If we're loading for real for the first time we need to reset this + // flag so we can reconnect when we get our parameters uploaded. + bInputAssetConnectedInHoudini = false; + } + } + + if ( HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT ) + Ar << UnrealSplineResolution; + + if ( HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS ) + { + Ar << InputTransforms; + } + else + { + InputTransforms.SetNum( InputObjects.Num() ); + for( int32 n = 0; n < InputTransforms.Num(); n++ ) + InputTransforms[ n ] = FTransform::Identity; + } + + if ( ( HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM ) + && ( HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX ) ) + Ar << InputLandscapeTransform; +} + +void +UHoudiniAssetInput::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniAssetInput * HoudiniAssetInput = Cast< UHoudiniAssetInput >( InThis ); + if ( HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill() ) + { + // Add reference to held geometry object. + if ( HoudiniAssetInput->InputObjects.Num() + && ( HoudiniAssetInput->GetChoiceIndex() == EHoudiniAssetInputType::GeometryInput ) ) + Collector.AddReferencedObjects( HoudiniAssetInput->InputObjects, InThis ); + + /* + // Add reference to held input asset component, if we have one. + if ( HoudiniAssetInput->InputAssetComponent && !HoudiniAssetInput->InputAssetComponent->IsPendingKill() + && ( HoudiniAssetInput->GetChoiceIndex() == EHoudiniAssetInputType::AssetInput ) ) + Collector.AddReferencedObject( HoudiniAssetInput->InputAssetComponent, InThis ); + */ + + // Add reference to held curve object. + if ( HoudiniAssetInput->InputCurve && !HoudiniAssetInput->InputCurve->IsPendingKill() ) + Collector.AddReferencedObject( HoudiniAssetInput->InputCurve, InThis ); + + // Do not add references to Actors/Landscapes in our world outliner as if they are in + // an other level it will prevent saving due to the external references... + // Add references for all curve input parameters. + for ( TMap< FString, UHoudiniAssetParameter * >::TIterator IterParams( HoudiniAssetInput->InputCurveParameters ); + IterParams; ++IterParams ) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if ( HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill() ) + Collector.AddReferencedObject( HoudiniAssetParameter, InThis ); + } + } + + // Call base implementation. + Super::AddReferencedObjects( InThis, Collector ); +} + +void +UHoudiniAssetInput::ClearInputCurveParameters() +{ + for( TMap< FString, UHoudiniAssetParameter * >::TIterator IterParams( InputCurveParameters ); IterParams; ++IterParams ) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if ( HoudiniAssetParameter ) + HoudiniAssetParameter->ConditionalBeginDestroy(); + } + + InputCurveParameters.Empty(); +} + +void +UHoudiniAssetInput::DisconnectInputCurve() +{ + // If we have spline, delete it. + if ( InputCurve && !InputCurve->IsPendingKill() ) + { + InputCurve->DetachFromComponent( FDetachmentTransformRules::KeepRelativeTransform ); + InputCurve->UnregisterComponent(); + } +} + +void +UHoudiniAssetInput::DestroyInputCurve() +{ + // If we have spline, delete it. + if ( InputCurve && !InputCurve->IsPendingKill() ) + { + InputCurve->DetachFromComponent( FDetachmentTransformRules::KeepRelativeTransform ); + InputCurve->UnregisterComponent(); + InputCurve->DestroyComponent(); + + InputCurve = nullptr; + } + + ClearInputCurveParameters(); +} + +#if WITH_EDITOR + +void +UHoudiniAssetInput::OnStaticMeshDropped( UObject * InObject, int32 AtIndex ) +{ + UObject* InputObject = GetInputObject( AtIndex ); + if ( InObject != InputObject ) + { + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Geometry Change" ), + PrimaryObject ); + Modify(); + + if ( InputObjects.IsValidIndex( AtIndex ) ) + { + InputObjects[ AtIndex ] = InObject; + } + else + { + check( AtIndex == 0 ); + InputObjects.Add( InObject ); + } + + if ( !InputTransforms.IsValidIndex( AtIndex ) ) + InputTransforms.Add( FTransform::Identity ); + + if ( !TransformUIExpanded.IsValidIndex( AtIndex ) ) + TransformUIExpanded.Add( false ); + + bStaticMeshChanged = true; + MarkChanged(); + + OnParamStateChanged(); + } +} + +void +UHoudiniAssetInput::OnSkeletalMeshDropped( UObject * InObject, int32 AtIndex ) +{ + UObject* InputObject = GetSkeletonInputObject( AtIndex ); + if ( InObject != InputObject ) + { + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Skeleton Input Change" ), + PrimaryObject ); + Modify(); + + if ( SkeletonInputObjects.IsValidIndex( AtIndex ) ) + { + SkeletonInputObjects[ AtIndex ] = InObject; + } + else + { + check( AtIndex == 0 ); + SkeletonInputObjects.Add( InObject ); + } + + /* + if ( !InputTransforms.IsValidIndex( AtIndex ) ) + InputTransforms.Add( FTransform::Identity ); + + if ( !TransformUIExpanded.IsValidIndex( AtIndex ) ) + TransformUIExpanded.Add( false ); + */ + + bStaticMeshChanged = true; + MarkChanged(); + + OnParamStateChanged(); + } +} + + +FReply +UHoudiniAssetInput::OnThumbnailDoubleClick( const FGeometry & InMyGeometry, const FPointerEvent & InMouseEvent, int32 AtIndex ) +{ + UObject* InputObject = GetInputObject( AtIndex ); + if ( InputObject && InputObject->IsA( UStaticMesh::StaticClass() ) && GEditor ) + GEditor->EditObject( InputObject ); + + return FReply::Handled(); +} + +void UHoudiniAssetInput::OnStaticMeshBrowse( int32 AtIndex ) +{ + UObject* InputObject = GetInputObject( AtIndex ); + if ( GEditor && InputObject ) + { + TArray< UObject * > Objects; + Objects.Add( InputObject ); + GEditor->SyncBrowserToObjects( Objects ); + } +} + +void UHoudiniAssetInput::OnSkeletalMeshBrowse( int32 AtIndex ) +{ + UObject* InputObject = GetSkeletonInputObject( AtIndex ); + if ( GEditor && InputObject ) + { + TArray< UObject * > Objects; + Objects.Add( InputObject ); + GEditor->SyncBrowserToObjects( Objects ); + } +} + +FReply +UHoudiniAssetInput::OnResetStaticMeshClicked( int32 AtIndex ) +{ + OnStaticMeshDropped( nullptr, AtIndex ); + return FReply::Handled(); +} + +FReply +UHoudiniAssetInput::OnResetSkeletalMeshClicked( int32 AtIndex ) +{ + OnSkeletalMeshDropped( nullptr, AtIndex ); + return FReply::Handled(); +} + +void +UHoudiniAssetInput::OnChoiceChange( TSharedPtr< FString > NewChoice ) +{ + if ( !NewChoice.IsValid() ) + return; + + ChoiceStringValue = *( NewChoice.Get() ); + + // We need to match selection based on label. + bool bLocalChanged = false; + int32 ActiveLabel = 0; + + for ( int32 LabelIdx = 0; LabelIdx < StringChoiceLabels.Num(); ++LabelIdx ) + { + FString * ChoiceLabel = StringChoiceLabels[ LabelIdx ].Get(); + + if ( ChoiceLabel && ChoiceLabel->Equals( ChoiceStringValue ) ) + { + bLocalChanged = true; + ActiveLabel = LabelIdx; + break; + } + } + + if ( !bLocalChanged ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Type Change" ), + PrimaryObject ); + Modify(); + + // Switch mode. + EHoudiniAssetInputType::Enum newChoice = static_cast(ActiveLabel); + ChangeInputType( newChoice, false ); + MarkChanged(); +} + +bool +UHoudiniAssetInput::ChangeInputType(const EHoudiniAssetInputType::Enum& newType, const bool& ForceRefresh ) +{ + if ( ChoiceIndex == newType && !ForceRefresh ) + return true; + + switch ( ChoiceIndex ) + { + case EHoudiniAssetInputType::GeometryInput: + { + // We are switching away from geometry input. + break; + } + + case EHoudiniAssetInputType::AssetInput: + { + // We are switching away from asset input. WIll be handled in DisconnectAndDestroyInputAsset + break; + } + + case EHoudiniAssetInputType::CurveInput: + { + // We are switching away from curve input. + DisconnectInputCurve(); + break; + } + + case EHoudiniAssetInputType::LandscapeInput: + { + break; + } + + case EHoudiniAssetInputType::WorldInput: + { + // We are switching away from World Outliner input. + // Stop monitoring the Actors for transform changes. + StopWorldOutlinerTicking(); + + break; + } + + case EHoudiniAssetInputType::SkeletonInput: + { + // We are switching away from a skeleton input. + break; + } + + default: + { + // Unhandled new input type? + check(0); + break; + } + } + + // Disconnect currently connected asset. + DisconnectAndDestroyInputAsset(); + + // Make sure we'll fully update the editor properties + if ( ChoiceIndex != newType && InputAssetComponent ) + InputAssetComponent->bEditorPropertiesNeedFullUpdate = true; + + // Switch mode. + ChoiceIndex = newType; + + // Switch mode. + switch ( newType ) + { + case EHoudiniAssetInputType::GeometryInput: + { + // We are switching to geometry input. + if ( InputObjects.Num() ) + bStaticMeshChanged = true; + break; + } + + case EHoudiniAssetInputType::AssetInput: + { + // We are switching to asset input. + ConnectInputAssetActor(); + break; + } + + case EHoudiniAssetInputType::CurveInput: + { + // We are switching to curve input. + + // Create new spline component if necessary. + USceneComponent* RootComp = GetHoudiniAssetComponent(); + if( RootComp && !RootComp->IsPendingKill() + && RootComp->GetOwner() && !RootComp->GetOwner()->IsPendingKill() ) + { + if( !InputCurve || InputCurve->IsPendingKill() ) + { + InputCurve = NewObject< UHoudiniSplineComponent >( + RootComp->GetOwner(), UHoudiniSplineComponent::StaticClass(), + NAME_None, RF_Public | RF_Transactional ); + } + // Attach or re-attach curve component to asset. + InputCurve->AttachToComponent( RootComp, FAttachmentTransformRules::KeepRelativeTransform ); + InputCurve->RegisterComponent(); + InputCurve->SetVisibility( true ); + InputCurve->SetHoudiniAssetInput( this ); + + bSwitchedToCurve = true; + } + break; + } + + case EHoudiniAssetInputType::LandscapeInput: + { + // We are switching to Landscape input. + break; + } + + case EHoudiniAssetInputType::WorldInput: + { + // We are switching to World Outliner input. + + // Start monitoring the Actors for transform changes. + StartWorldOutlinerTicking(); + + // Force recook and reconnect of the input assets. + HAPI_NodeId HostAssetId = GetAssetId(); + if ( FHoudiniEngineUtils::HapiCreateInputNodeForWorldOutliner( + HostAssetId, InputOutlinerMeshArray, + ConnectedAssetId, CreatedInputDataAssetIds, + UnrealSplineResolution ) ) + { + ConnectInputNode(); + } + + break; + } + + case EHoudiniAssetInputType::SkeletonInput: + { + // We are switching to skeleton input. + if ( SkeletonInputObjects.Num() ) + bStaticMeshChanged = true; + break; + } + + default: + { + // Unhandled new input type? + check(0); + break; + } + } + + // Make sure the Input choice string corresponds to the selected input type + if ( StringChoiceLabels.IsValidIndex( ChoiceIndex ) ) + { + FString NewStringChoice = *( StringChoiceLabels[ ChoiceIndex ].Get() ); + if ( !ChoiceStringValue.Equals(NewStringChoice, ESearchCase::IgnoreCase ) ) + ChoiceStringValue = NewStringChoice; + } + + // If we have input object and geometry asset, we need to connect it back. + MarkChanged(); + + return true; +} + +bool +UHoudiniAssetInput::OnShouldFilterActor( const AActor * const Actor ) const +{ + if ( !Actor ) + return false; + + if ( ChoiceIndex == EHoudiniAssetInputType::AssetInput ) + { + // Only return HoudiniAssetActors + if ( Actor->IsA() ) + { + // But not our own Asset Actor + if( const USceneComponent* RootComp = Cast( GetHoudiniAssetComponent() )) + { + if( RootComp && Cast( RootComp->GetOwner() ) != Actor ) + return true; + } + return false; + } + } + else if ( ChoiceIndex == EHoudiniAssetInputType::LandscapeInput ) + { + // Only returns Landscape + if ( Actor->IsA() ) + { + ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); + // But not a landscape generated by this asset + const UHoudiniAssetComponent * HoudiniAssetComponent = GetHoudiniAssetComponent(); + if (HoudiniAssetComponent && LandscapeProxy && !HoudiniAssetComponent->HasLandscapeActor( LandscapeProxy->GetLandscapeActor() ) ) + return true; + } + return false; + } + else if ( ChoiceIndex == EHoudiniAssetInputType::WorldInput ) + { + for ( auto & OutlinerMesh : InputOutlinerMeshArray ) + if ( OutlinerMesh.ActorPtr.Get() == Actor ) + return true; + } + + return false; +} + +void +UHoudiniAssetInput::OnActorSelected( AActor * Actor ) +{ + if ( ChoiceIndex == EHoudiniAssetInputType::AssetInput ) + return OnInputActorSelected( Actor ); + else if ( ChoiceIndex == EHoudiniAssetInputType::WorldInput ) + return OnWorldOutlinerActorSelected( Actor ); + else if ( ChoiceIndex == EHoudiniAssetInputType::LandscapeInput ) + return OnLandscapeActorSelected( Actor ); +} + +void +UHoudiniAssetInput::OnInputActorSelected( AActor * Actor ) +{ + if ( ( !Actor || Actor->IsPendingKill() ) + && ( InputAssetComponent && !InputAssetComponent->IsPendingKill() ) ) + { + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Asset Change" ), + PrimaryObject ); + Modify(); + + // Tell the old input asset we are no longer connected. + InputAssetComponent->RemoveDownstreamAsset( GetHoudiniAssetComponent(), InputIndex ); + + // We cleared the selection so just reset all the values. + InputAssetComponent = nullptr; + ConnectedAssetId = -1; + } + else + { + AHoudiniAssetActor * HoudiniAssetActor = (AHoudiniAssetActor *)Actor; + if ( !HoudiniAssetActor || HoudiniAssetActor->IsPendingKill() ) + return; + + UHoudiniAssetComponent * ConnectedHoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if ( !ConnectedHoudiniAssetComponent || ConnectedHoudiniAssetComponent->IsPendingKill() ) + return; + + // If we just selected the already selected Actor do nothing if we are properly connected. + if ( ConnectedHoudiniAssetComponent == InputAssetComponent && bInputAssetConnectedInHoudini) + return; + + // Do not allow the input asset to be ourself! + if ( ConnectedHoudiniAssetComponent == PrimaryObject ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Asset Change" ), + PrimaryObject ); + Modify(); + + // Tell the old input asset we are no longer connected. + if ( InputAssetComponent && !InputAssetComponent->IsPendingKill() ) + InputAssetComponent->RemoveDownstreamAsset( GetHoudiniAssetComponent(), InputIndex ); + + InputAssetComponent = ConnectedHoudiniAssetComponent; + ConnectedAssetId = InputAssetComponent->GetAssetId(); + + // Do we have to wait for the input asset to cook? + const UHoudiniAssetComponent* HAC = GetHoudiniAssetComponent(); + if ( HAC && !HAC->IsPendingKill() ) + GetHoudiniAssetComponent()->UpdateWaitingForUpstreamAssetsToInstantiate( true ); + + // Mark as disconnected since we need to reconnect to the new asset. + bInputAssetConnectedInHoudini = false; + } + + MarkChanged(); +} + +void +UHoudiniAssetInput::OnLandscapeActorSelected( AActor * Actor ) +{ + ALandscapeProxy * LandscapeProxy = Cast< ALandscapeProxy >( Actor ); + if ( LandscapeProxy && !LandscapeProxy->IsPendingKill() ) + { + // If we just selected the already selected landscape, do nothing. + if ( LandscapeProxy == InputLandscapeProxy ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Landscape Change." ), + PrimaryObject ); + Modify(); + + // Store new landscape. + InputLandscapeProxy = LandscapeProxy; + InputLandscapeTransform = LandscapeProxy->ActorToWorld(); + } + else + { + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Landscape Change." ), + PrimaryObject ); + Modify(); + + InputLandscapeProxy = nullptr; + InputLandscapeTransform = FTransform::Identity; + } + + MarkChanged(); +} + +void +UHoudiniAssetInput::OnWorldOutlinerActorSelected( AActor * ) +{ + // Do nothing. +} + +void +UHoudiniAssetInput::TickWorldOutlinerInputs() +{ + // Do not tick non world inputs + if (ChoiceIndex != EHoudiniAssetInputType::WorldInput) + { + return; + } + + // Only tick/cook when in Editor + // This prevents PIE cooks or runtime cooks due to inputs moving + if (!GetWorld() || (GetWorld()->WorldType != EWorldType::Editor)) + { + return; + } + +#if WITH_EDITOR + // Stop outliner objects from causing recooks while input objects are dragged around + if (FHoudiniMoveTracker::Get().IsObjectMoving) + { + return; + } +#endif + + // PostLoad initialization must be done on the first tick + // as some components might now have been fully initialized during PostLoad() + if ( OutlinerInputsNeedPostLoadInit ) + { + for (auto & OutlinerInput : InputOutlinerMeshArray) + { + if (OutlinerInput.ActorPtr.IsValid()) + continue; + + // Try to update the actor ptr via the pathname + OutlinerInput.TryToUpdateActorPtrFromActorPathName(); + } + + UpdateInputOulinerArray(); + + // The spline Transform array might need to be rebuilt after loading + for (auto & OutlinerInput : InputOutlinerMeshArray) + { + OutlinerInput.RebuildSplineTransformsArrayIfNeeded(); + OutlinerInput.KeepWorldTransform = bKeepWorldTransform; + OutlinerInput.SplineResolution = UnrealSplineResolution; + } + + OutlinerInputsNeedPostLoadInit = false; + return; + } + + // Don't do anything more if HEngine cooking is paused + // We need to be able to detect updates/changes to the input actor when cooking is unpaused + if ( !FHoudiniEngine::Get().GetEnableCookingGlobal() ) + return; + + // Lambda use to Modify / Prechange only once + bool bLocalChanged = false; + auto MarkLocalChanged = [&]() + { + if ( !bLocalChanged ) + { + Modify(); + bLocalChanged = true; + } + }; + + // Refresh the input's component from the actor + // If the Actor is a blueprint, its component are recreated for every modification + if ( UpdateInputOulinerArray() ) + { + MarkLocalChanged(); + bStaticMeshChanged = true; + MarkChanged(); + } + + // + if ( bStaticMeshChanged ) + return; + + // Check for destroyed / modified outliner inputs + for ( auto & OutlinerInput : InputOutlinerMeshArray ) + { + if ( !OutlinerInput.ActorPtr.IsValid() ) + continue; + + if ( OutlinerInput.HasActorTransformChanged() && ( OutlinerInput.AssetId >= 0 ) ) + { + MarkLocalChanged(); + + // Updates to the new Transform + UpdateWorldOutlinerTransforms( OutlinerInput ); + + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + //FMemory::Memzero< HAPI_TransformEuler >( HapiTransform ); + FHoudiniEngineUtils::TranslateUnrealTransform( OutlinerInput.ComponentTransform, HapiTransform ); + + HAPI_NodeInfo LocalAssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&LocalAssetNodeInfo); + const HAPI_Result LocalResult = FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), OutlinerInput.AssetId, + &LocalAssetNodeInfo); + + if ( LocalResult == HAPI_RESULT_SUCCESS ) + FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + LocalAssetNodeInfo.parentId, &HapiTransform ); + } + else if ( OutlinerInput.HasComponentTransformChanged() + || ( OutlinerInput.HasSplineComponentChanged( UnrealSplineResolution ) ) + || ( OutlinerInput.KeepWorldTransform != bKeepWorldTransform ) ) + { + MarkLocalChanged(); + + // Update to the new Transforms + UpdateWorldOutlinerTransforms( OutlinerInput ); + + // The component or spline has been modified so so we need to indicate that the "static mesh" + // has changed in order to rebuild the asset properly in UploadParameterValue() + bStaticMeshChanged = true; + } + else if ( OutlinerInput.HasComponentMaterialsChanged() ) + { + MarkLocalChanged(); + + // Update the materials + UpdateWorldOutlinerMaterials( OutlinerInput ); + + // The materials have been changed so so we need to indicate that the "static mesh" + // has changed in order to rebuild the asset properly in UploadParameterValue() + bStaticMeshChanged = true; + } + } + + if ( bLocalChanged ) + MarkChanged(); +} + +#endif + +void +UHoudiniAssetInput::ConnectInputAssetActor() +{ + // Check the component we're connected to is valid + if ( !InputAssetComponent ) + return; + + if ( !FHoudiniEngineUtils::IsValidNodeId( InputAssetComponent->GetAssetId() ) ) + return; + + // Check we have the correct Id + if ( ConnectedAssetId != InputAssetComponent->GetAssetId() ) + ConnectedAssetId = InputAssetComponent->GetAssetId(); + + // Connect if needed + if ( !bInputAssetConnectedInHoudini ) + { + InputAssetComponent->AddDownstreamAsset( GetHoudiniAssetComponent(), InputIndex ); + bInputAssetConnectedInHoudini = ConnectInputNode(); + } +} + +void +UHoudiniAssetInput::DisconnectInputAssetActor() +{ + if ( bInputAssetConnectedInHoudini && !InputAssetComponent ) + { + if( bIsObjectPathParameter ) + { + std::string ParamNameString = TCHAR_TO_UTF8( *GetParameterName() ); + + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NodeId, "", + ParmId, 0); + } + else + { + FHoudiniEngineUtils::HapiDisconnectAsset( GetAssetId(), InputIndex ); + } + bInputAssetConnectedInHoudini = false; + } +} + +void +UHoudiniAssetInput::ConnectLandscapeActor() +{} + +void +UHoudiniAssetInput::DisconnectLandscapeActor() +{} + +HAPI_NodeId +UHoudiniAssetInput::GetConnectedAssetId() const +{ + return ConnectedAssetId; +} + +bool +UHoudiniAssetInput::IsGeometryAssetConnected() const +{ + if ( FHoudiniEngineUtils::IsValidNodeId( ConnectedAssetId ) && ( ChoiceIndex == EHoudiniAssetInputType::GeometryInput ) ) + { + for ( auto InputObject : InputObjects ) + { + if ( InputObject && !InputObject->IsPendingKill() ) + return true; + } + } + + return false; +} + +bool +UHoudiniAssetInput::IsInputAssetConnected() const +{ + if ( FHoudiniEngineUtils::IsValidNodeId( ConnectedAssetId ) + && InputAssetComponent + && !InputAssetComponent->IsPendingKill() + && bInputAssetConnectedInHoudini ) + { + if ( ChoiceIndex == EHoudiniAssetInputType::AssetInput ) + return true; + } + + return false; +} + +bool +UHoudiniAssetInput::IsCurveAssetConnected() const +{ + if (InputCurve && !InputCurve->IsPendingKill() && ( ChoiceIndex == EHoudiniAssetInputType::CurveInput ) ) + return true; + + return false; +} + +bool +UHoudiniAssetInput::IsLandscapeAssetConnected() const +{ + if ( FHoudiniEngineUtils::IsValidNodeId( ConnectedAssetId ) ) + { + if ( ChoiceIndex == EHoudiniAssetInputType::LandscapeInput ) + return true; + } + + return false; +} + +bool +UHoudiniAssetInput::IsWorldInputAssetConnected() const +{ + if ( FHoudiniEngineUtils::IsValidNodeId( ConnectedAssetId ) ) + { + if ( ChoiceIndex == EHoudiniAssetInputType::WorldInput ) + return true; + } + + return false; +} + +void +UHoudiniAssetInput::OnInputCurveChanged() +{ + MarkChanged(); +} + +void +UHoudiniAssetInput::ExternalDisconnectInputAssetActor() +{ + InputAssetComponent = nullptr; + ConnectedAssetId = -1; + + MarkChanged(); +} + +bool +UHoudiniAssetInput::DoesInputAssetNeedInstantiation() +{ + //if ( ChoiceIndex != EHoudiniAssetInputType::AssetInput ) + // return false; + + if ( InputAssetComponent == nullptr ) + return false; + + if ( !FHoudiniEngineUtils::IsValidNodeId(InputAssetComponent->GetAssetId() ) ) + return true; + + return false; +} + +UHoudiniAssetComponent * +UHoudiniAssetInput::GetConnectedInputAssetComponent() +{ + return InputAssetComponent; +} + +void +UHoudiniAssetInput::NotifyChildParameterChanged( UHoudiniAssetParameter * HoudiniAssetParameter ) +{ + if ( HoudiniAssetParameter && ChoiceIndex == EHoudiniAssetInputType::CurveInput ) + { + if ( FHoudiniEngineUtils::IsValidNodeId( ConnectedAssetId ) ) + { + // We need to upload changed param back to HAPI. + if (! HoudiniAssetParameter->UploadParameterValue() ) + { + HOUDINI_LOG_ERROR( TEXT("%s UploadParameterValue failed"), InputAssetComponent ? *InputAssetComponent->GetOwner()->GetName() : TEXT("unknown") ); + } + } + + MarkChanged(); + } +} + +bool +UHoudiniAssetInput::UpdateInputCurve() +{ + bool Success = true; + FString CurvePointsString; + EHoudiniSplineComponentType::Enum CurveTypeValue = EHoudiniSplineComponentType::Bezier; + EHoudiniSplineComponentMethod::Enum CurveMethodValue = EHoudiniSplineComponentMethod::CVs; + int32 CurveClosed = 1; + + if(ConnectedAssetId != -1) + { + FHoudiniEngineUtils::HapiGetParameterDataAsString( + ConnectedAssetId, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT( "" ), + CurvePointsString ); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + ConnectedAssetId, HAPI_UNREAL_PARAM_CURVE_TYPE, + (int32) EHoudiniSplineComponentType::Bezier, (int32 &) CurveTypeValue ); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + ConnectedAssetId, HAPI_UNREAL_PARAM_CURVE_METHOD, + (int32) EHoudiniSplineComponentMethod::CVs, (int32 &) CurveMethodValue ); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + ConnectedAssetId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 1, CurveClosed ); + } + + // We need to get the NodeInfo to get the parent id + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &NodeInfo), + false); + + // Construct geo part object. + FHoudiniGeoPartObject HoudiniGeoPartObject( ConnectedAssetId, NodeInfo.parentId, ConnectedAssetId, 0 ); + HoudiniGeoPartObject.bIsCurve = true; + + HAPI_AttributeInfo AttributeRefinedCurvePositions; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeRefinedCurvePositions ); + + TArray< float > RefinedCurvePositions; + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HoudiniGeoPartObject, HAPI_UNREAL_ATTRIB_POSITION, + AttributeRefinedCurvePositions, RefinedCurvePositions ); + + // Process coords string and extract positions. + TArray< FVector > CurvePoints; + FHoudiniEngineUtils::ExtractStringPositions( CurvePointsString, CurvePoints ); + + TArray< FVector > CurveDisplayPoints; + FHoudiniEngineUtils::ConvertScaleAndFlipVectorData( RefinedCurvePositions, CurveDisplayPoints ); + + if (InputCurve && !InputCurve->IsPendingKill() ) + { + InputCurve->Construct( + HoudiniGeoPartObject, CurveDisplayPoints, CurveTypeValue, CurveMethodValue, + (CurveClosed == 1)); + } + + // We also need to construct curve parameters we care about. + TMap< FString, UHoudiniAssetParameter * > NewInputCurveParameters; + + TArray< HAPI_ParmInfo > ParmInfos; + ParmInfos.SetNumUninitialized( NodeInfo.parmCount ); + for (int32 Idx = 0; Idx < ParmInfos.Num(); Idx++) + FHoudiniApi::ParmInfo_Init(&(ParmInfos[Idx])); + + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, + &ParmInfos[ 0 ], 0, NodeInfo.parmCount ), false); + + // Retrieve integer values for this asset. + TArray< int32 > ParmValueInts; + ParmValueInts.SetNumZeroed( NodeInfo.parmIntValueCount ); + if ( NodeInfo.parmIntValueCount > 0 ) + { + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &ParmValueInts[ 0 ], 0, NodeInfo.parmIntValueCount ), + false ); + } + + // Retrieve float values for this asset. + TArray< float > ParmValueFloats; + ParmValueFloats.SetNumZeroed( NodeInfo.parmFloatValueCount ); + if ( NodeInfo.parmFloatValueCount > 0 ) + { + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &ParmValueFloats[ 0 ], 0, NodeInfo.parmFloatValueCount ), + false ); + } + + // Retrieve string values for this asset. + TArray< HAPI_StringHandle > ParmValueStrings; + ParmValueStrings.SetNumZeroed( NodeInfo.parmStringValueCount ); + if ( NodeInfo.parmStringValueCount > 0 ) + { + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, true, &ParmValueStrings[ 0 ], 0, NodeInfo.parmStringValueCount ), + false ); + } + + // Create properties for parameters. + for ( int32 ParamIdx = 0; ParamIdx < NodeInfo.parmCount; ++ParamIdx ) + { + // Retrieve param info at this index. + const HAPI_ParmInfo & ParmInfo = ParmInfos[ ParamIdx ]; + + // If parameter is invisible, skip it. + if ( ParmInfo.invisible ) + continue; + + FString LocalParameterName; + FHoudiniEngineString HoudiniEngineString( ParmInfo.nameSH ); + if ( !HoudiniEngineString.ToFString( LocalParameterName ) ) + { + // We had trouble retrieving name of this parameter, skip it. + continue; + } + + // See if it's one of parameters we are interested in. + if ( !LocalParameterName.Equals( TEXT( HAPI_UNREAL_PARAM_CURVE_METHOD ) ) && + !LocalParameterName.Equals( TEXT( HAPI_UNREAL_PARAM_CURVE_TYPE ) ) && + !LocalParameterName.Equals( TEXT( HAPI_UNREAL_PARAM_CURVE_CLOSED ) ) ) + { + // Not parameter we are interested in. + continue; + } + + // See if this parameter has already been created. + UHoudiniAssetParameter * const * FoundHoudiniAssetParameter = InputCurveParameters.Find( LocalParameterName ); + UHoudiniAssetParameter * HoudiniAssetParameter = FoundHoudiniAssetParameter ? *FoundHoudiniAssetParameter : nullptr; + + // If parameter exists, we can reuse it. + if ( HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill() ) + { + // Remove parameter from current map. + InputCurveParameters.Remove( LocalParameterName ); + + // Reinitialize parameter and add it to map. + HoudiniAssetParameter->CreateParameter( nullptr, this, ConnectedAssetId, ParmInfo ); + NewInputCurveParameters.Add( LocalParameterName, HoudiniAssetParameter ); + continue; + } + else + { + if ( ParmInfo.type == HAPI_PARMTYPE_INT ) + { + if ( !ParmInfo.choiceCount ) + HoudiniAssetParameter = UHoudiniAssetParameterInt::Create( nullptr, this, ConnectedAssetId, ParmInfo ); + else + HoudiniAssetParameter = UHoudiniAssetParameterChoice::Create( nullptr, this, ConnectedAssetId, ParmInfo ); + } + else if ( ParmInfo.type == HAPI_PARMTYPE_TOGGLE ) + { + HoudiniAssetParameter = UHoudiniAssetParameterToggle::Create( nullptr, this, ConnectedAssetId, ParmInfo ); + } + else + { + Success = false; + check( false ); + } + + if ( HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill() ) + NewInputCurveParameters.Add( LocalParameterName, HoudiniAssetParameter ); + } + } + + ClearInputCurveParameters(); + InputCurveParameters = NewInputCurveParameters; + + if ( bSwitchedToCurve ) + { + +#if WITH_EDITOR + // We need to trigger details panel update. + OnParamStateChanged(); + + // The editor caches the current selection visualizer, so we need to trick + // and pretend the selection has changed so that the HSplineVisualizer can be drawn immediately + if (GUnrealEd) + GUnrealEd->NoteSelectionChange(); +#endif + + bSwitchedToCurve = false; + } + return Success; +} + +FText +UHoudiniAssetInput::HandleChoiceContentText() const +{ + return FText::FromString( ChoiceStringValue ); +} + +#if WITH_EDITOR + +void +UHoudiniAssetInput::CheckStateChangedExportOnlySelected( ECheckBoxState NewState ) +{ + int32 bState = ( NewState == ECheckBoxState::Checked ); + + if ( bLandscapeExportSelectionOnly != bState ) + { + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Export Selected Landscape Components mode change." ), + PrimaryObject ); + Modify(); + + bLandscapeExportSelectionOnly = bState; + + // Mark this parameter as changed. + MarkChanged(); + } +} + +ECheckBoxState +UHoudiniAssetInput::IsCheckedExportOnlySelected() const +{ + if ( bLandscapeExportSelectionOnly ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::CheckStateChangedAutoSelectLandscape( ECheckBoxState NewState ) +{ + int32 bState = (NewState == ECheckBoxState::Checked); + + if (bLandscapeAutoSelectComponent != bState) + { + // Record undo information. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniInputChange", "Houdini Export Auto-Select Landscape Components mode change."), + PrimaryObject); + Modify(); + + bLandscapeAutoSelectComponent = bState; + + // Mark this parameter as changed. + MarkChanged(); + } +} + +ECheckBoxState +UHoudiniAssetInput::IsCheckedAutoSelectLandscape() const +{ + if ( bLandscapeAutoSelectComponent ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::CheckStateChangedExportCurves( ECheckBoxState NewState ) +{ + int32 bState = ( NewState == ECheckBoxState::Checked ); + + if ( bLandscapeExportCurves != bState ) + { + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Export Landscape Curve mode change." ), + PrimaryObject ); + Modify(); + + bLandscapeExportCurves = bState; + + // Mark this parameter as changed. + MarkChanged(); + } +} + +ECheckBoxState +UHoudiniAssetInput::IsCheckedExportCurves() const +{ + if ( bLandscapeExportCurves ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::CheckStateChangedExportAsMesh( ECheckBoxState NewState ) +{ + int32 bState = ( NewState == ECheckBoxState::Checked ); + + if ( bLandscapeExportAsMesh != bState ) + { + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Export Landscape As Mesh mode change." ), + PrimaryObject ); + Modify(); + + bLandscapeExportAsMesh = bState; + + if ( bState ) + bLandscapeExportAsHeightfield = false; + + // Mark this parameter as changed. + MarkChanged(); + } +} + +ECheckBoxState +UHoudiniAssetInput::IsCheckedExportAsMesh() const +{ + if ( bLandscapeExportAsMesh ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::CheckStateChangedExportAsHeightfield( ECheckBoxState NewState ) +{ + int32 bState = ( NewState == ECheckBoxState::Checked ); + + if ( bLandscapeExportAsHeightfield != bState ) + { + // Record undo information. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniInputChange", "Houdini Export Landscape As Heightfield mode change."), + PrimaryObject); + Modify(); + + bLandscapeExportAsHeightfield = bState; + if ( bState ) + bLandscapeExportAsMesh = false; + else + bLandscapeExportAsMesh = true; + + // Mark this parameter as changed. + MarkChanged(); + } +} + +ECheckBoxState +UHoudiniAssetInput::IsCheckedExportAsHeightfield() const +{ + if ( bLandscapeExportAsHeightfield ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::CheckStateChangedExportAsPoints( ECheckBoxState NewState ) +{ + int32 bState = (NewState == ECheckBoxState::Checked); + + uint32 bExportAsPoints = !bLandscapeExportAsHeightfield && !bLandscapeExportAsMesh; + if (bExportAsPoints != bState) + { + // Record undo information. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniInputChange", "Houdini Export Landscape As Points mode change."), + PrimaryObject); + Modify(); + + if ( bState ) + { + bLandscapeExportAsHeightfield = false; + bLandscapeExportAsMesh = false; + } + else + { + bLandscapeExportAsHeightfield = true; + bLandscapeExportAsMesh = false; + } + + // Mark this parameter as changed. + MarkChanged(); + } +} + +ECheckBoxState +UHoudiniAssetInput::IsCheckedExportAsPoints() const +{ + if ( !bLandscapeExportAsHeightfield && !bLandscapeExportAsMesh ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::CheckStateChangedExportMaterials( ECheckBoxState NewState ) +{ + int32 bState = ( NewState == ECheckBoxState::Checked ); + + if ( bLandscapeExportMaterials != bState ) + { + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Export Landscape Materials mode change." ), + PrimaryObject ); + Modify(); + + bLandscapeExportMaterials = bState; + + // Mark this parameter as changed. + MarkChanged(); + } +} + +ECheckBoxState +UHoudiniAssetInput::IsCheckedExportMaterials() const +{ + if ( bLandscapeExportMaterials ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::CheckStateChangedExportLighting( ECheckBoxState NewState ) +{ + int32 bState = ( NewState == ECheckBoxState::Checked ); + + if ( bLandscapeExportLighting != bState ) + { + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Export Landscape Lighting mode change." ), + PrimaryObject ); + Modify(); + + bLandscapeExportLighting = bState; + + // Mark this parameter as changed. + MarkChanged(); + } +} + +ECheckBoxState +UHoudiniAssetInput::IsCheckedExportLighting() const +{ + if ( bLandscapeExportLighting ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::CheckStateChangedExportNormalizedUVs( ECheckBoxState NewState ) +{ + int32 bState = ( NewState == ECheckBoxState::Checked ); + + if ( bLandscapeExportNormalizedUVs != bState ) + { + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Export Landscape Normalized UVs mode change." ), + PrimaryObject ); + Modify(); + + bLandscapeExportNormalizedUVs = bState; + + // Mark this parameter as changed. + MarkChanged(); + } +} + +ECheckBoxState +UHoudiniAssetInput::IsCheckedExportNormalizedUVs() const +{ + if ( bLandscapeExportNormalizedUVs ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::CheckStateChangedExportTileUVs( ECheckBoxState NewState ) +{ + int32 bState = ( NewState == ECheckBoxState::Checked ); + + if ( bLandscapeExportTileUVs != bState ) + { + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Export Landscape Tile UVs mode change." ), + PrimaryObject ); + Modify(); + + bLandscapeExportTileUVs = bState; + + // Mark this parameter as changed. + MarkChanged(); + } +} + + +ECheckBoxState +UHoudiniAssetInput::IsCheckedExportTileUVs() const +{ + if ( bLandscapeExportTileUVs ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::CheckStateChangedKeepWorldTransform(ECheckBoxState NewState) +{ + int32 bState = ( NewState == ECheckBoxState::Checked ); + + if ( bKeepWorldTransform == bState ) + return; + + // Record undo information. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniInputChange", "Houdini Input Transform Type change."), + PrimaryObject); + Modify(); + + bKeepWorldTransform = bState; + + // Mark this parameter as changed. + MarkChanged(); +} + +ECheckBoxState +UHoudiniAssetInput::IsCheckedKeepWorldTransform() const +{ + if ( bKeepWorldTransform == 2 ) + { + if (GetDefaultTranformTypeValue()) + return ECheckBoxState::Checked; + else + return ECheckBoxState::Unchecked; + } + else if ( bKeepWorldTransform ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::CheckStateChangedExportAllLODs( ECheckBoxState NewState ) +{ + int32 bState = ( NewState == ECheckBoxState::Checked ); + + if ( bExportAllLODs == bState ) + return; + + // Record undo information. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniInputChange", "Houdini Input Export all LODs changed."), + PrimaryObject); + Modify(); + + bExportAllLODs = bState; + + // Changing the export of LODs changes the StaticMesh! + if ( HasLODs() ) + bStaticMeshChanged = true; + + // Mark this parameter as changed. + MarkChanged(); +} + +ECheckBoxState +UHoudiniAssetInput::IsCheckedExportAllLODs() const +{ + if ( bExportAllLODs ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::CheckStateChangedExportSockets( ECheckBoxState NewState ) +{ + int32 bState = ( NewState == ECheckBoxState::Checked ); + + if ( bExportSockets == bState ) + return; + + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input export sockets changed." ), + PrimaryObject ); + Modify(); + + bExportSockets = bState; + + // Changing the export of LODs changes the StaticMesh! + if ( HasSockets() ) + bStaticMeshChanged = true; + + // Mark this parameter as changed. + MarkChanged(); +} + +ECheckBoxState +UHoudiniAssetInput::IsCheckedExportSockets() const +{ + if (bExportSockets) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::CheckStateChangedUpdateInputLandscape( ECheckBoxState NewState ) +{ + int32 bState = ( NewState == ECheckBoxState::Checked ); + + if ( bUpdateInputLandscape == bState ) + return; + + // Record undo information. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniInputChange", "Houdini Input Update Input Landscape changed."), + PrimaryObject); + Modify(); + + UHoudiniAssetComponent* ParentComponent = GetHoudiniAssetComponent(); + if ( InputLandscapeProxy && ParentComponent ) + { + if ( bState ) + { + // Build the backup file name + FString BackupBaseName = ParentComponent->GetTempCookFolder().ToString() + + TEXT("/") + + InputLandscapeProxy->GetName() + + TEXT("_") + + ParentComponent->GetComponentGuid().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); + + // We need to cache the input landscape to a file + //FString BaseName = TEXT("/Game/HoudiniEngine/Temp/LandscapeBak"); + FHoudiniLandscapeUtils::BackupLandscapeToFile( BackupBaseName, InputLandscapeProxy.Get()); + InputLandscapeTransform = InputLandscapeProxy->ActorToWorld(); + } + else + { + // Detach the input landscape from the HDA + InputLandscapeProxy->DetachFromActor( FDetachmentTransformRules::KeepWorldTransform ); + + // Clear the landscape map to avoid reusing the input landscape + ParentComponent->ClearLandscapes(); + + // Restore the input landscape's backup data + FHoudiniLandscapeUtils::RestoreLandscapeFromFile( InputLandscapeProxy.Get()); + + // Reapply the source Landscape's transform + InputLandscapeProxy->SetActorTransform(InputLandscapeTransform); + } + } + + bUpdateInputLandscape = bState; + + // Mark this parameter as changed. + MarkChanged(); +} + +ECheckBoxState +UHoudiniAssetInput::IsCheckedUpdateInputLandscape() const +{ + if ( bUpdateInputLandscape ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::CheckStateChangedPackBeforeMerge( ECheckBoxState NewState ) +{ + int32 bState = ( NewState == ECheckBoxState::Checked ); + + if ( bPackBeforeMerge == bState ) + return; + + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Pack Before Merge changed." ), + PrimaryObject ); + Modify(); + + bPackBeforeMerge = bState; + + // Mark this parameter as changed. + MarkChanged( true ); +} + + +ECheckBoxState +UHoudiniAssetInput::IsCheckedPackBeforeMerge() const +{ + if ( bPackBeforeMerge ) + return ECheckBoxState::Checked; + else + return ECheckBoxState::Unchecked; +} + +void +UHoudiniAssetInput::OnInsertGeo( int32 AtIndex ) +{ + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Geometry Change" ), + PrimaryObject ); + Modify(); + InputObjects.Insert( nullptr, AtIndex ); + InputTransforms.Insert( FTransform::Identity, AtIndex ); + TransformUIExpanded.Insert( false, AtIndex ); + bStaticMeshChanged = true; + MarkChanged(); + OnParamStateChanged(); +} + +void +UHoudiniAssetInput::OnDeleteGeo( int32 AtIndex ) +{ + if ( ensure( InputObjects.IsValidIndex( AtIndex ) && InputTransforms.IsValidIndex( AtIndex ) ) ) + { + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Geometry Change" ), + PrimaryObject ); + Modify(); + InputObjects.RemoveAt( AtIndex ); + InputTransforms.RemoveAt( AtIndex ); + TransformUIExpanded.RemoveAt( AtIndex ); + bStaticMeshChanged = true; + MarkChanged(); + OnParamStateChanged(); + } +} + +void +UHoudiniAssetInput::OnDuplicateGeo( int32 AtIndex ) +{ + if ( ensure( InputObjects.IsValidIndex( AtIndex ) ) && InputTransforms.IsValidIndex( AtIndex ) ) + { + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Geometry Change" ), + PrimaryObject ); + Modify(); + UObject* Dupe = InputObjects[AtIndex]; + InputObjects.Insert( Dupe, AtIndex ); + FTransform DupeTransform = InputTransforms[AtIndex]; + InputTransforms.Insert( DupeTransform, AtIndex ); + bool DupeUIExpanded = TransformUIExpanded[AtIndex]; + TransformUIExpanded.Insert( DupeUIExpanded, AtIndex ); + bStaticMeshChanged = true; + MarkChanged(); + OnParamStateChanged(); + } +} + +FReply +UHoudiniAssetInput::OnButtonClickRecommit() +{ + // There's no undo operation for button. + MarkChanged(); + + return FReply::Handled(); +} + +void +UHoudiniAssetInput::StartWorldOutlinerTicking() +{ + if ( InputOutlinerMeshArray.Num() > 0 && !WorldOutlinerTimerDelegate.IsBound() && GEditor ) + { + WorldOutlinerTimerDelegate = FTimerDelegate::CreateUObject( this, &UHoudiniAssetInput::TickWorldOutlinerInputs ); + + // We need to register delegate with the timer system. + static const float TickTimerDelay = 0.5f; + GEditor->GetTimerManager()->SetTimer( WorldOutlinerTimerHandle, WorldOutlinerTimerDelegate, TickTimerDelay, true ); + } +} + +void +UHoudiniAssetInput::StopWorldOutlinerTicking() +{ + if ( InputOutlinerMeshArray.Num() <= 0 && WorldOutlinerTimerDelegate.IsBound() && GEditor ) + { + GEditor->GetTimerManager()->ClearTimer( WorldOutlinerTimerHandle ); + WorldOutlinerTimerDelegate.Unbind(); + } +} + +void UHoudiniAssetInput::InvalidateNodeIds() +{ + ConnectedAssetId = -1; + for (auto& OutlinerInputMesh : InputOutlinerMeshArray) + { + OutlinerInputMesh.AssetId = -1; + } +} + +void UHoudiniAssetInput::DuplicateCurves(UHoudiniAssetInput * OriginalInput) +{ + if (!InputCurve || InputCurve->IsPendingKill() ) + return; + + if (!OriginalInput || OriginalInput->IsPendingKill()) + return; + + USceneComponent* RootComp = GetHoudiniAssetComponent(); + if( !RootComp || RootComp->IsPendingKill() ) + return; + + if ( !RootComp->GetOwner() || RootComp->GetOwner()->IsPendingKill() ) + return; + + // The previous call to DuplicateObject did not duplicate the curves properly + // Both the original and duplicated Inputs now share the same InputCurve, so we + // need to create a proper copy of that curve + + // Keep the original pointer to the curve, as we need to duplicate its data + UHoudiniSplineComponent* OriginalCurve = InputCurve; + + // Creates a new Curve + InputCurve = NewObject< UHoudiniSplineComponent >( + RootComp->GetOwner(), UHoudiniSplineComponent::StaticClass(), + NAME_None, RF_Public | RF_Transactional); + + // Attach curve component to asset. + InputCurve->AttachToComponent( RootComp, FAttachmentTransformRules::KeepRelativeTransform); + InputCurve->RegisterComponent(); + InputCurve->SetVisibility(true); + + // The new curve need do know that it is connected to this Input + InputCurve->SetHoudiniAssetInput(this); + + // The call to DuplicateObject has actually modified the original object's Input + // so we need to fix that as well. + OriginalCurve->SetHoudiniAssetInput(OriginalInput); + + // "Copy" the old curves parameters to the new one + InputCurve->CopyFrom(OriginalCurve); + + // to force rebuild... + bSwitchedToCurve = true; +} + +void +UHoudiniAssetInput::RemoveWorldOutlinerInput( int32 AtIndex ) +{ + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini World Outliner Input Change" ), + PrimaryObject ); + Modify(); + + bStaticMeshChanged = true; + InputOutlinerMeshArray.RemoveAt( AtIndex ); + MarkChanged(); +} + +void +UHoudiniAssetInput::UpdateWorldOutlinerTransforms( FHoudiniAssetInputOutlinerMesh& OutlinerMesh ) +{ + // Update to the new Transforms + if ( OutlinerMesh.ActorPtr.IsValid() ) + OutlinerMesh.ActorTransform = OutlinerMesh.ActorPtr->GetTransform(); + + if ( OutlinerMesh.StaticMeshComponent && !OutlinerMesh.StaticMeshComponent->IsPendingKill() ) + { + OutlinerMesh.ComponentTransform = OutlinerMesh.StaticMeshComponent->GetComponentTransform(); + + // Handle instances here + UInstancedStaticMeshComponent * InstancedStaticMeshComponent = Cast< UInstancedStaticMeshComponent >(OutlinerMesh.StaticMeshComponent); + if (InstancedStaticMeshComponent) + { + FTransform InstanceTransform; + if (InstancedStaticMeshComponent->GetInstanceTransform(OutlinerMesh.InstanceIndex, InstanceTransform, true)) + OutlinerMesh.ComponentTransform = InstanceTransform; + } + } + else if (OutlinerMesh.SplineComponent && !OutlinerMesh.SplineComponent->IsPendingKill() ) + { + OutlinerMesh.ComponentTransform = OutlinerMesh.SplineComponent->GetComponentTransform(); + } + + OutlinerMesh.KeepWorldTransform = bKeepWorldTransform; +} + +void +UHoudiniAssetInput::UpdateWorldOutlinerMaterials(FHoudiniAssetInputOutlinerMesh& OutlinerMesh) +{ + OutlinerMesh.MeshComponentsMaterials.Empty(); + if ( !OutlinerMesh.StaticMeshComponent || OutlinerMesh.StaticMeshComponent->IsPendingKill() ) + return; + + // Keep track of the materials used by the SMC + for ( int32 n = 0; n < OutlinerMesh.StaticMeshComponent->GetNumMaterials(); n++ ) + { + UMaterialInterface* mi = OutlinerMesh.StaticMeshComponent->GetMaterial( n ); + if ( !mi || mi->IsPendingKill() ) + OutlinerMesh.MeshComponentsMaterials.Add( FString() ); + else + OutlinerMesh.MeshComponentsMaterials.Add( mi->GetPathName() ); + } +} + +void UHoudiniAssetInput::OnAddToInputObjects() +{ + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Geometry Change" ), + PrimaryObject ); + Modify(); + InputObjects.Add( nullptr ); + InputTransforms.Add( FTransform::Identity ); + TransformUIExpanded.Add( false ); + MarkChanged(); + bStaticMeshChanged = true; + OnParamStateChanged(); +} + +void UHoudiniAssetInput::OnEmptyInputObjects() +{ + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Geometry Change" ), + PrimaryObject ); + Modify(); + + // Empty the arrays + InputObjects.Empty(); + InputTransforms.Empty(); + TransformUIExpanded.Empty(); + + // To avoid displaying 0 elements when there's one (empty), initialize the array + InputObjects.Add( nullptr ); + InputTransforms.Add( FTransform::Identity ); + TransformUIExpanded.Add( false ); + MarkChanged(); + bStaticMeshChanged = true; + OnParamStateChanged(); +} + +void UHoudiniAssetInput::OnAddToSkeletonInputObjects() +{ + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniInputChange", "Houdini Input Geometry Change"), + PrimaryObject); + Modify(); + SkeletonInputObjects.Add(nullptr); + //InputTransforms.Add(FTransform::Identity); + //TransformUIExpanded.Add(false); + MarkChanged(); + bStaticMeshChanged = true; + OnParamStateChanged(); +} + +void UHoudiniAssetInput::OnEmptySkeletonInputObjects() +{ + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniInputChange", "Houdini Input Geometry Change"), + PrimaryObject); + Modify(); + + // Empty the arrays + SkeletonInputObjects.Empty(); + //InputTransforms.Empty(); + //TransformUIExpanded.Empty(); + + // To avoid displaying 0 elements when there's one (empty), initialize the array + SkeletonInputObjects.Add(nullptr); + //InputTransforms.Add(FTransform::Identity); + //TransformUIExpanded.Add(false); + MarkChanged(); + bStaticMeshChanged = true; + OnParamStateChanged(); +} + +TOptional< float > +UHoudiniAssetInput::GetSplineResolutionValue() const +{ + if (UnrealSplineResolution != -1.0f) + return UnrealSplineResolution; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + return HoudiniRuntimeSettings->MarshallingSplineResolution; + else + return HAPI_UNREAL_PARAM_SPLINE_RESOLUTION_DEFAULT; +} + + +void +UHoudiniAssetInput::SetSplineResolutionValue(float InValue) +{ + if (InValue < 0) + OnResetSplineResolutionClicked(); + else + UnrealSplineResolution = FMath::Clamp< float >(InValue, 0.0f, 10000.0f); +} + + +bool +UHoudiniAssetInput::IsSplineResolutionEnabled() const +{ + if (ChoiceIndex != EHoudiniAssetInputType::WorldInput) + return false; + + for (int32 n = 0; n < InputOutlinerMeshArray.Num(); n++) + { + if (InputOutlinerMeshArray[n].SplineComponent && !InputOutlinerMeshArray[n].SplineComponent->IsPendingKill() ) + return true; + } + + return false; +} + + +FReply +UHoudiniAssetInput::OnResetSplineResolutionClicked() +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + UnrealSplineResolution = HoudiniRuntimeSettings->MarshallingSplineResolution; + else + UnrealSplineResolution = HAPI_UNREAL_PARAM_SPLINE_RESOLUTION_DEFAULT; + + return FReply::Handled(); +} + +FText +UHoudiniAssetInput::GetCurrentSelectionText() const +{ + FText CurrentSelectionText; + if ( ChoiceIndex == EHoudiniAssetInputType::AssetInput ) + { + if ( InputAssetComponent && !InputAssetComponent->IsPendingKill() && InputAssetComponent->GetHoudiniAssetActorOwner() ) + { + CurrentSelectionText = FText::FromString( InputAssetComponent->GetHoudiniAssetActorOwner()->GetName() ); + } + } + else if ( ChoiceIndex == EHoudiniAssetInputType::LandscapeInput ) + { + if ( InputLandscapeProxy && !InputLandscapeProxy->IsPendingKill() ) + { + CurrentSelectionText = FText::FromString( InputLandscapeProxy->GetName() ); + } + } + + return CurrentSelectionText; +} + +#endif + +FArchive & +operator<<( FArchive & Ar, FHoudiniAssetInputOutlinerMesh & HoudiniAssetInputOutlinerMesh ) +{ + HoudiniAssetInputOutlinerMesh.Serialize( Ar ); + return Ar; +} + +FBox +UHoudiniAssetInput::GetInputBounds( const FVector& ParentLocation ) const +{ + FBox Bounds( ForceInitToZero ); + + if ( IsCurveAssetConnected() && InputCurve && !InputCurve->IsPendingKill() ) + { + // Houdini Curves are expressed locally so we need to add the parent component's transform + TArray CurvePositions; + InputCurve->GetCurvePositions( CurvePositions ); + + for ( int32 n = 0; n < CurvePositions.Num(); n++ ) + Bounds += ( ParentLocation + CurvePositions[ n ] ); + } + + if ( IsWorldInputAssetConnected() ) + { + for (int32 n = 0; n < InputOutlinerMeshArray.Num(); n++) + { + if ( !InputOutlinerMeshArray[ n ].ActorPtr.IsValid() ) + continue; + + FVector Origin, Extent; + InputOutlinerMeshArray[ n ].ActorPtr->GetActorBounds( false, Origin, Extent ); + + Bounds += FBox::BuildAABB( Origin, Extent ); + } + } + + if ( IsInputAssetConnected() && InputAssetComponent && !InputAssetComponent->IsPendingKill() ) + { + Bounds += InputAssetComponent->GetAssetBounds(); + } + + if ( IsLandscapeAssetConnected() && InputLandscapeProxy && !InputLandscapeProxy->IsPendingKill() ) + { + FVector Origin, Extent; + InputLandscapeProxy->GetActorBounds( false, Origin, Extent ); + + Bounds += FBox::BuildAABB( Origin, Extent ); + } + + return Bounds; +} + +void UHoudiniAssetInput::SetDefaultInputTypeFromLabel() +{ +#if WITH_EDITOR + // Look if we can find an input type prefix in the input name + FString inputName = GetParameterLabel(); + + // We'll try to find these magic words to try to detect the default input type + //FString geoPrefix = TEXT("geo"); + FString curvePrefix = TEXT( "curve" ); + FString landscapePrefix = TEXT( "landscape" ); + FString landscapePrefix2 = TEXT( "terrain" ); + FString landscapePrefix3 = TEXT( "heightfield" ); + FString worldPrefix = TEXT( "world" ); + FString worldPrefix2 = TEXT( "outliner" ); + FString assetPrefix = TEXT( "asset" ); + FString assetPrefix2 = TEXT( "hda" ); + + // By default, geometry input is chosen. + EHoudiniAssetInputType::Enum newInputType = EHoudiniAssetInputType::GeometryInput; + + if ( inputName.Contains( curvePrefix, ESearchCase::IgnoreCase ) ) + newInputType = EHoudiniAssetInputType::CurveInput; + + else if ( ( inputName.Contains( landscapePrefix, ESearchCase::IgnoreCase ) ) + || ( inputName.Contains( landscapePrefix2, ESearchCase::IgnoreCase ) ) + || ( inputName.Contains( landscapePrefix3, ESearchCase::IgnoreCase ) ) ) + newInputType = EHoudiniAssetInputType::LandscapeInput; + + else if ( ( inputName.Contains( worldPrefix, ESearchCase::IgnoreCase ) ) + || ( inputName.Contains( worldPrefix2, ESearchCase::IgnoreCase ) ) ) + newInputType = EHoudiniAssetInputType::WorldInput; + + else if ( ( inputName.Contains( assetPrefix, ESearchCase::IgnoreCase ) ) + || ( inputName.Contains( assetPrefix2, ESearchCase::IgnoreCase ) ) ) + newInputType = EHoudiniAssetInputType::AssetInput; + + if ( ChoiceIndex != newInputType ) + ChangeInputType( newInputType, false ); + +#else + ChoiceIndex = EHoudiniAssetInputType::GeometryInput; +#endif +} + +bool +UHoudiniAssetInput::HasChanged() const +{ + // Inputs should be considered changed after being loaded + return bChanged || bLoadedParameter || ( !bInputAssetConnectedInHoudini && ChoiceIndex == EHoudiniAssetInputType::AssetInput ); +} + +#if WITH_EDITOR +bool +UHoudiniAssetInput::UpdateInputOulinerArray() +{ + bool NeedsUpdate = false; + + // See if some outliner inputs need to be updated, or removed + // If an input's Actor is no longer valid, then when need to remove that input. + // If an input's Components needs to be updated (are no longer valid), then all the components + // from the same actor needs to be updated as well. + // This can happen for example when a blueprint actor is modified/serialized etc.. + TArray ActorToUpdateArray; + for ( int32 n = InputOutlinerMeshArray.Num() - 1; n >= 0; n-- ) + { + FHoudiniAssetInputOutlinerMesh& OutlinerInput = InputOutlinerMeshArray[ n ]; + if (OutlinerInput.ActorPtr.IsStale()) // || !OutlinerInput.ActorPtr.IsValid()); + { + // If our ActorPtr is stale, try to find an updated version of it by pathname + // This can happen when a blueprint is updated or recompiled... + OutlinerInput.TryToUpdateActorPtrFromActorPathName(); + } + + if ( !OutlinerInput.ActorPtr.IsValid() ) + { + // This input has an invalid actor: destroy it and its asset + NeedsUpdate = true; + + // Destroy Houdini asset + if ( FHoudiniEngineUtils::IsValidNodeId( OutlinerInput.AssetId ) ) + { + FHoudiniEngineUtils::DestroyHoudiniAsset( OutlinerInput.AssetId ); + OutlinerInput.AssetId = -1; + } + + // Remove that input + InputOutlinerMeshArray.RemoveAt( n ); + } + else + { + // This input has a valid actor, see if its component needs to be updated + if ( !OutlinerInput.NeedsComponentUpdate() ) + continue; + + if ( ActorToUpdateArray.Contains( OutlinerInput.ActorPtr.Get() ) ) + continue; + + ActorToUpdateArray.Add( OutlinerInput.ActorPtr.Get() ); + + NeedsUpdate = true; + } + } + + // Creates the inputs from the actors + for ( auto & CurrentActor : ActorToUpdateArray ) + UpdateInputOulinerArrayFromActor( CurrentActor, true ); + + return NeedsUpdate; +} + +void +UHoudiniAssetInput::UpdateInputOulinerArrayFromActor( AActor * Actor, const bool& NeedCleanUp ) +{ + if ( !Actor ) + return; + + // Don't allow selection of ourselves. Bad things happen if we do. + if ( GetHoudiniAssetComponent() && ( Actor == GetHoudiniAssetComponent()->GetOwner() ) ) + return; + + // Destroy previous outliner inputs linked to this actor if needed + if (NeedCleanUp) + { + for (int32 n = InputOutlinerMeshArray.Num() - 1; n >= 0; n--) + { + if (InputOutlinerMeshArray[n].ActorPtr.Get() != Actor) + continue; + + // Remove from the input array. + // No need to destroy the houdini nodes since this will be taken care of + // when we update the input. (see bug 95415) + InputOutlinerMeshArray.RemoveAt(n); + } + } + + // Looking for StaticMeshes + TArray< UStaticMeshComponent* > AllSMComponents; + Actor->GetComponents(AllSMComponents); + for (UStaticMeshComponent * StaticMeshComponent : AllSMComponents) + { + if ( !StaticMeshComponent || StaticMeshComponent->ComponentHasTag( NAME_HoudiniNoUpload ) ) + continue; + + UStaticMesh * StaticMesh = StaticMeshComponent->GetStaticMesh(); + if ( !StaticMesh ) + continue; + + UInstancedStaticMeshComponent * InstancedStaticMeshComponent = Cast< UInstancedStaticMeshComponent >(StaticMeshComponent); + if (InstancedStaticMeshComponent) + { + // Handle ISM separately + // We'll create an Outliner Mesh for each of the instances.. + for (int32 n = 0; n < InstancedStaticMeshComponent->GetInstanceCount(); n++) + { + // Add the mesh to the array + FHoudiniAssetInputOutlinerMesh OutlinerMesh; + OutlinerMesh.ActorPtr = Actor; + OutlinerMesh.ActorPathName = Actor->GetPathName(); + OutlinerMesh.StaticMeshComponent = InstancedStaticMeshComponent; + OutlinerMesh.StaticMesh = StaticMesh; + OutlinerMesh.SplineComponent = nullptr; + OutlinerMesh.AssetId = -1; + OutlinerMesh.InstanceIndex = n; + + UpdateWorldOutlinerTransforms(OutlinerMesh); + UpdateWorldOutlinerMaterials(OutlinerMesh); + + InputOutlinerMeshArray.Add(OutlinerMesh); + } + } + else + { + // Add the Static Mesh to the array + FHoudiniAssetInputOutlinerMesh OutlinerMesh; + OutlinerMesh.ActorPtr = Actor; + OutlinerMesh.ActorPathName = Actor->GetPathName(); + OutlinerMesh.StaticMeshComponent = StaticMeshComponent; + OutlinerMesh.StaticMesh = StaticMesh; + OutlinerMesh.SplineComponent = nullptr; + OutlinerMesh.AssetId = -1; + + UpdateWorldOutlinerTransforms(OutlinerMesh); + UpdateWorldOutlinerMaterials(OutlinerMesh); + + InputOutlinerMeshArray.Add(OutlinerMesh); + } + } + + // Looking for Splines + TArray< USplineComponent* > AllSplineComponents; + Actor->GetComponents(AllSplineComponents); + for (USplineComponent * SplineComponent : AllSplineComponents) + { + if ( !SplineComponent || SplineComponent->ComponentHasTag( NAME_HoudiniNoUpload ) ) + continue; + + // Add the spline to the array + FHoudiniAssetInputOutlinerMesh OutlinerMesh; + + OutlinerMesh.ActorPtr = Actor; + OutlinerMesh.ActorPathName = Actor->GetPathName(); + OutlinerMesh.StaticMeshComponent = nullptr; + OutlinerMesh.StaticMesh = nullptr; + OutlinerMesh.SplineComponent = SplineComponent; + OutlinerMesh.AssetId = -1; + + UpdateWorldOutlinerTransforms( OutlinerMesh ); + + // Updating the OutlinerMesh's struct infos + OutlinerMesh.SplineResolution = UnrealSplineResolution; + OutlinerMesh.SplineLength = SplineComponent->GetSplineLength(); + OutlinerMesh.NumberOfSplineControlPoints = SplineComponent->GetNumberOfSplinePoints(); + + InputOutlinerMeshArray.Add( OutlinerMesh ); + } +} + +FReply +UHoudiniAssetInput::OnExpandInputTransform( int32 AtIndex ) +{ + if ( TransformUIExpanded.IsValidIndex( AtIndex ) ) + { + TransformUIExpanded[ AtIndex ] = !TransformUIExpanded[ AtIndex ]; + OnParamStateChanged(); + } + + return FReply::Handled(); +} + +TOptional< float > +UHoudiniAssetInput::GetPositionX( int32 AtIndex ) const +{ + FTransform transform = FTransform::Identity; + if ( InputTransforms.IsValidIndex( AtIndex ) ) + transform = InputTransforms[ AtIndex ]; + + return transform.GetLocation().X; +} + +TOptional< float > +UHoudiniAssetInput::GetPositionY( int32 AtIndex ) const +{ + FTransform transform = FTransform::Identity; + if ( InputTransforms.IsValidIndex( AtIndex ) ) + transform = InputTransforms[ AtIndex ]; + + return transform.GetLocation().Y; +} + +TOptional< float > +UHoudiniAssetInput::GetPositionZ( int32 AtIndex ) const +{ + FTransform transform = FTransform::Identity; + if ( InputTransforms.IsValidIndex( AtIndex ) ) + transform = InputTransforms[ AtIndex ]; + + return transform.GetLocation().Z; +} + +void +UHoudiniAssetInput::SetPositionX( float Value, int32 AtIndex ) +{ + if ( !InputTransforms.IsValidIndex( AtIndex ) ) + return; + + FVector Position = InputTransforms[ AtIndex ].GetLocation(); + if ( Position.X == Value ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Change" ), + this ); + + Modify(); + + Position.X = Value; + InputTransforms[ AtIndex ].SetLocation( Position ); + + MarkChanged( true ); + bStaticMeshChanged = true; +} + +void +UHoudiniAssetInput::SetPositionY( float Value, int32 AtIndex ) +{ + if ( !InputTransforms.IsValidIndex( AtIndex ) ) + return; + + FVector Position = InputTransforms[ AtIndex ].GetLocation(); + if ( Position.Y == Value ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Change" ), + this ); + + Modify(); + + Position.Y = Value; + InputTransforms[ AtIndex ].SetLocation( Position ); + + MarkChanged( true ); + bStaticMeshChanged = true; +} + +void +UHoudiniAssetInput::SetPositionZ( float Value, int32 AtIndex ) +{ + if ( !InputTransforms.IsValidIndex( AtIndex ) ) + return; + + FVector Position = InputTransforms[ AtIndex ].GetLocation(); + if ( Position.Z == Value ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Change" ), + this ); + + Modify(); + + Position.Z = Value; + InputTransforms[ AtIndex ].SetLocation( Position ); + + MarkChanged( true ); + bStaticMeshChanged = true; +} + +TOptional< float > +UHoudiniAssetInput::GetRotationRoll( int32 AtIndex ) const +{ + FTransform transform = FTransform::Identity; + if ( InputTransforms.IsValidIndex( AtIndex ) ) + transform = InputTransforms[ AtIndex ]; + + return transform.Rotator().Roll; +} + +TOptional< float > +UHoudiniAssetInput::GetRotationPitch( int32 AtIndex ) const +{ + FTransform transform = FTransform::Identity; + if ( InputTransforms.IsValidIndex( AtIndex ) ) + transform = InputTransforms[ AtIndex ]; + + return transform.Rotator().Pitch; +} + +TOptional< float > +UHoudiniAssetInput::GetRotationYaw( int32 AtIndex ) const +{ + FTransform transform = FTransform::Identity; + if ( InputTransforms.IsValidIndex( AtIndex ) ) + transform = InputTransforms[ AtIndex ]; + + return transform.Rotator().Yaw; +} + +void +UHoudiniAssetInput::SetRotationRoll( float Value, int32 AtIndex ) +{ + if ( !InputTransforms.IsValidIndex( AtIndex ) ) + return; + + FRotator Rotator = InputTransforms[ AtIndex ].Rotator(); + if ( FMath::IsNearlyEqual( Rotator.Roll, Value, SMALL_NUMBER ) ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Change" ), + this ); + + Modify(); + + Rotator.Roll = Value; + InputTransforms[ AtIndex ].SetRotation( Rotator.Quaternion() ); + + MarkChanged( true ); + bStaticMeshChanged = true; +} + +void +UHoudiniAssetInput::SetRotationPitch( float Value, int32 AtIndex ) +{ + if ( !InputTransforms.IsValidIndex( AtIndex ) ) + return; + + FRotator Rotator = InputTransforms[ AtIndex ].Rotator(); + if ( FMath::IsNearlyEqual( Rotator.Pitch, Value, SMALL_NUMBER ) ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Change" ), + this); + + Modify(); + + Rotator.Pitch = Value; + InputTransforms[ AtIndex ].SetRotation( Rotator.Quaternion() ); + + MarkChanged( true ); + bStaticMeshChanged = true; +} + +void +UHoudiniAssetInput::SetRotationYaw( float Value, int32 AtIndex ) +{ + if ( !InputTransforms.IsValidIndex( AtIndex ) ) + return; + + FRotator Rotator = InputTransforms[ AtIndex ].Rotator(); + if ( FMath::IsNearlyEqual( Rotator.Yaw, Value, SMALL_NUMBER ) ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Change" ), + this ); + + Modify(); + + Rotator.Yaw = Value; + InputTransforms[ AtIndex ].SetRotation( Rotator.Quaternion() ); + + MarkChanged( true ); + bStaticMeshChanged = true; +} + +/** Returns the input's transform scale values **/ +TOptional< float > +UHoudiniAssetInput::GetScaleX( int32 AtIndex ) const +{ + FTransform transform = FTransform::Identity; + if ( InputTransforms.IsValidIndex( AtIndex ) ) + transform = InputTransforms[ AtIndex ]; + + return transform.GetScale3D().X; +} + +TOptional< float > +UHoudiniAssetInput::GetScaleY( int32 AtIndex ) const +{ + FTransform transform = FTransform::Identity; + if ( InputTransforms.IsValidIndex( AtIndex ) ) + transform = InputTransforms[ AtIndex ]; + + return transform.GetScale3D().Y; +} + +TOptional< float > +UHoudiniAssetInput::GetScaleZ( int32 AtIndex ) const +{ + FTransform transform = FTransform::Identity; + if ( InputTransforms.IsValidIndex( AtIndex ) ) + transform = InputTransforms[ AtIndex ]; + + return transform.GetScale3D().Z; +} + +void +UHoudiniAssetInput::SetScaleX( float Value, int32 AtIndex ) +{ + if ( !InputTransforms.IsValidIndex( AtIndex ) ) + return; + + FVector Scale = InputTransforms[ AtIndex ].GetScale3D(); + if ( Scale.X == Value ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Change" ), + this ); + + Modify(); + + Scale.X = Value; + InputTransforms[ AtIndex ].SetScale3D( Scale ); + + MarkChanged( true ); + bStaticMeshChanged = true; +} + +void +UHoudiniAssetInput::SetScaleY( float Value, int32 AtIndex ) +{ + if ( !InputTransforms.IsValidIndex( AtIndex ) ) + return; + + FVector Scale = InputTransforms[ AtIndex ].GetScale3D(); + if ( Scale.Y == Value ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Change" ), + this ); + + Modify(); + + Scale.Y = Value; + InputTransforms[ AtIndex ].SetScale3D( Scale ); + + MarkChanged( true ); + bStaticMeshChanged = true; +} + +void +UHoudiniAssetInput::SetScaleZ( float Value, int32 AtIndex ) +{ + if ( !InputTransforms.IsValidIndex( AtIndex ) ) + return; + + FVector Scale = InputTransforms[ AtIndex ].GetScale3D(); + if ( Scale.Z == Value ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInputChange", "Houdini Input Change" ), + this ); + + Modify(); + + Scale.Z = Value; + InputTransforms[ AtIndex ].SetScale3D( Scale ); + + MarkChanged( true ); + bStaticMeshChanged = true; +} + +bool +UHoudiniAssetInput::AddInputObject( UObject* ObjectToAdd ) +{ + if ( !ObjectToAdd || ObjectToAdd->IsPendingKill() ) + return false; + + // Fix for the bug due to the first (but null) geometry input mesh + int32 IndexToAdd = InputObjects.Num(); + if ( IndexToAdd == 1 && ( InputObjects[ 0 ] == nullptr ) ) + IndexToAdd = 0; + + if ( UStaticMesh* StaticMesh = Cast< UStaticMesh >( ObjectToAdd ) ) + { + ForceSetInputObject( ObjectToAdd, IndexToAdd, true ); + return true; + } + else if ( AActor* WorldActor = Cast< AActor >( ObjectToAdd ) ) + { + ForceSetInputObject( ObjectToAdd, IndexToAdd, true ); + return true; + } + + //if ( InputAssetComponent ) + // InputAssetComponent->bEditorPropertiesNeedFullUpdate = true; + + return false; +} + +#endif + +ALandscapeProxy* +UHoudiniAssetInput::GetLandscapeInput() +{ + if (!InputLandscapeProxy || InputLandscapeProxy->IsPendingKill()) + return nullptr; + + return InputLandscapeProxy->GetLandscapeActor(); +} + +bool +UHoudiniAssetInput::HasLODs() const +{ + switch ( ChoiceIndex ) + { + case EHoudiniAssetInputType::GeometryInput: + { + if ( !InputObjects.Num() ) + return false; + + for ( int32 Idx = 0; Idx < InputObjects.Num(); Idx++ ) + { + UStaticMesh* SM = Cast( InputObjects[ Idx ] ); + if ( !SM || SM->IsPendingKill() ) + continue; + + if ( SM->GetNumLODs() > 1 ) + return true; + } + } + break; + + case EHoudiniAssetInputType::WorldInput: + { + if ( !InputOutlinerMeshArray.Num() ) + return false; + + for ( int32 Idx = 0; Idx < InputOutlinerMeshArray.Num(); Idx++ ) + { + UStaticMesh* SM = InputOutlinerMeshArray[ Idx ].StaticMesh; + if ( !SM || SM->IsPendingKill() ) + continue; + + if ( SM->GetNumLODs() > 1 ) + return true; + } + } + break; + } + + return false; +} + +bool +UHoudiniAssetInput::HasSockets() const +{ + switch ( ChoiceIndex ) + { + case EHoudiniAssetInputType::GeometryInput: + { + if ( !InputObjects.Num() ) + return false; + + for ( int32 Idx = 0; Idx < InputObjects.Num(); Idx++ ) + { + UStaticMesh* SM = Cast( InputObjects[ Idx ] ); + if ( !SM ) + continue; + + if ( SM->Sockets.Num() > 0 ) + return true; + } + } + break; + + case EHoudiniAssetInputType::WorldInput: + { + if ( !InputOutlinerMeshArray.Num() ) + return false; + + for ( int32 Idx = 0; Idx < InputOutlinerMeshArray.Num(); Idx++ ) + { + UStaticMesh* SM = InputOutlinerMeshArray[ Idx ].StaticMesh; + if ( !SM ) + continue; + + if ( SM->Sockets.Num() > 1 ) + return true; + } + } + break; + } + + return false; +} + +bool +UHoudiniAssetInput::IsLandscapeUpdateNeededOnTransformChange() const +{ + if ( GetChoiceIndex() != EHoudiniAssetInputType::LandscapeInput ) + return false; + + if ( !bLandscapeAutoSelectComponent || !bLandscapeExportSelectionOnly ) + return false; + + return true; +} + +bool +UHoudiniAssetInput::SetDefaultAssetFromHDA() +{ +#if WITH_EDITOR + // We just handle geo inputs + if (EHoudiniAssetInputType::GeometryInput != ChoiceIndex) + return false; + + // There is a default slot, don't add if slot is already filled + if (InputObjects.Num() > 1) + return false; + + // Make sure we're linked to a valid parameter + if (ParmId < 0) + return false; + + // Get our ParmInfo + HAPI_ParmInfo FoundParamInfo; + FHoudiniApi::ParmInfo_Init(&FoundParamInfo); + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParamInfo) ) + { + return false; + } + + // Get our string value + HAPI_StringHandle StringHandle; + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), NodeId, false, + &StringHandle, FoundParamInfo.stringValuesIndex, 1) == HAPI_RESULT_SUCCESS) + { + FString OutValue; + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (HoudiniEngineString.ToFString(OutValue)) + { + // Set default object on the HDA instance - will override the parameter string + // and apply the object input local-path thing for the HDA cook. + if (OutValue.Len() > 0) + { + UObject * pObject = LoadObject(nullptr, *OutValue); + if (pObject) + { + return AddInputObject(pObject); + } + } + } + } + +#endif //WITH EDITOR + return false; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInput.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInput.h new file mode 100644 index 00000000..069e048d --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInput.h @@ -0,0 +1,684 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once +#include "HoudiniAssetParameter.h" +#include "CoreMinimal.h" +#include "TimerManager.h" +#include "GameFramework/Actor.h" +#if WITH_EDITOR +#include "Styling/SlateTypes.h" +#include "Input/Reply.h" +#endif +#include "HoudiniAssetInput.generated.h" + +class ALandscape; +class ALandscapeProxy; +class UHoudiniSplineComponent; +class USplineComponent; + +namespace EHoudiniAssetInputType +{ + enum Enum + { + GeometryInput = 0, + AssetInput, + CurveInput, + LandscapeInput, + WorldInput, + SkeletonInput + }; +} + +struct HOUDINIENGINERUNTIME_API FHoudiniAssetInputOutlinerMesh +{ + FHoudiniAssetInputOutlinerMesh() + : ActorPtr(nullptr), + StaticMeshComponent(nullptr), + StaticMesh(nullptr), + SplineComponent(nullptr), + NumberOfSplineControlPoints(-1), + SplineControlPointsTransform(), + SplineLength(-1.0f), + SplineResolution(-1.0f), + ActorTransform(), + ComponentTransform(), + AssetId(-1), + KeepWorldTransform(2), + MeshComponentsMaterials(), + InstanceIndex(-1) + { + } + + /** Serialization. **/ + void Serialize( FArchive & Ar ); + + /** return true if the attached spline component has been modified **/ + bool HasSplineComponentChanged(float fCurrentSplineResolution) const; + + /** return true if the attached actor's transform has been modified **/ + bool HasActorTransformChanged() const; + + /** return true if the attached component's transform has been modified **/ + bool HasComponentTransformChanged() const; + + /** return true if the attached component's materials have been modified **/ + bool HasComponentMaterialsChanged() const; + + /** rebuilds the SplineTransform array after reloading the asset **/ + void RebuildSplineTransformsArrayIfNeeded(); + + /** Indicates that the components used are no longer valid and should be updated from the actor **/ + bool NeedsComponentUpdate() const; + + /** Update the Actor pointer from the store Actor path/name **/ + bool TryToUpdateActorPtrFromActorPathName(); + + /** Selected Actor. **/ + TWeakObjectPtr ActorPtr = nullptr; + + /** Selected Actor's path, used to find the actor back after loading. **/ + FString ActorPathName = TEXT("NONE"); + + /** Selected mesh's component, for reference. **/ + class UStaticMeshComponent * StaticMeshComponent = nullptr; + + /** The selected mesh. **/ + class UStaticMesh * StaticMesh = nullptr; + + /** Spline Component **/ + class USplineComponent * SplineComponent = nullptr; + + /** Number of CVs used by the spline component, used to detect modification **/ + int32 NumberOfSplineControlPoints = -1; + + /** Transform of the UnrealSpline CVs, used to detect modification of the spline (Rotation/Scale) **/ + TArray SplineControlPointsTransform; + + /** Spline Length, used to detect modification of the spline.. **/ + float SplineLength = -1.0f; + + /** Spline resolution used to generate the asset, used to detect setting modification **/ + float SplineResolution = -1.0f; + + /** Actor transform used to see if the transfrom changed since last marshal into Houdini. **/ + FTransform ActorTransform; + + /** Component transform used to see if the transform has changed since last marshalling **/ + FTransform ComponentTransform; + + /** Mesh's input asset id. **/ + HAPI_NodeId AssetId = -1; + + /** TranformType used to generate the asset **/ + int32 KeepWorldTransform = 2; + + /** Temporary variable holding serialization version. **/ + uint32 HoudiniAssetParameterVersion; + + /** Path Materials assigned on the SMC **/ + TArray MeshComponentsMaterials; + + /** If the world In is a ISM, index of this instance **/ + uint32 InstanceIndex = -1; +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetInput : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + /** Create instance of this class for use as an input **/ + static UHoudiniAssetInput * Create( UObject * InPrimaryObject, int32 InInputIndex, HAPI_NodeId InNodeId ); + /** Create instance of this class for use as parameter **/ + static UHoudiniAssetInput* Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo); + + /** Create this parameter from HAPI information - this implementation does nothing as this is not a true parameter. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo) override; + + /** Sets the default input type from the input/parameter label **/ + void SetDefaultInputTypeFromLabel(); + + // Add the ability to set default object paths in the parameter setting of the Houdini HDA that are picked up by the plugin + bool SetDefaultAssetFromHDA(); + +#if WITH_EDITOR + virtual void PostEditUndo() override; + + // Note: This method is to be only used for testing or for presetting Houdini tools input!! + void ForceSetInputObject( UObject * InObject, int32 AtIndex, bool CommitChange ); + + // Clears all selected objects for all input types and revert back to the default geo input + void ClearInputs(); + + bool AddInputObject( UObject* ObjectToAdd ); +#endif + + /** Upload parameter value to HAPI. **/ + virtual bool UploadParameterValue() override; + + /** Notification from a child parameter about its change. **/ + virtual void NotifyChildParameterChanged( UHoudiniAssetParameter * HoudiniAssetParameter ) override; + + /** UObject methods. **/ + virtual void BeginDestroy() override; + virtual void Serialize( FArchive & Ar ) override; + virtual void PostLoad() override; + static void AddReferencedObjects( UObject * InThis, FReferenceCollector& Collector ); + + /** Return id of connected asset id. **/ + HAPI_NodeId GetConnectedAssetId() const; + + /** Return true if connected asset is a geometry asset. **/ + bool IsGeometryAssetConnected() const; + + /** Return true if connected asset is an instantiated (actor) asset. **/ + bool IsInputAssetConnected() const; + + /** Return true if connected asset is a curve asset. **/ + bool IsCurveAssetConnected() const; + + /** Return true if connected asset is a landscape asset. **/ + bool IsLandscapeAssetConnected() const; + + /** Return true if connected asset is a merge of a world outliner set of inputs. **/ + bool IsWorldInputAssetConnected() const; + + /** Return true if we are updating the input landscape's data. **/ + bool IsUpdatingInputLandscape() const { return bUpdateInputLandscape; }; + + /** Disconnect connected input asset. **/ + void DisconnectAndDestroyInputAsset(); + + /** Called by attached spline component whenever its state changes. **/ + void OnInputCurveChanged(); + + /** Changes the input type to the new one **/ + bool ChangeInputType(const EHoudiniAssetInputType::Enum& newType, const bool& ForceRefresh ); + + /** Forces a disconnect of the input asset actor. This is used by external actors, usually when they die. **/ + void ExternalDisconnectInputAssetActor(); + + /** See if we need to instantiate the input asset. **/ + bool DoesInputAssetNeedInstantiation(); + + /** Get the HoudiniAssetComponent for the input asset. **/ + UHoudiniAssetComponent* GetConnectedInputAssetComponent(); + + /** Invalidate all connected node ids */ + void InvalidateNodeIds(); + + /** Duplicates the data from the input curve properly **/ + void DuplicateCurves(UHoudiniAssetInput * OriginalInput); + + FORCEINLINE const TArray< FHoudiniAssetInputOutlinerMesh >& GetWorldOutlinerInputs() const { return InputOutlinerMeshArray; } + + /** Returns the selected landscape Actor **/ + ALandscapeProxy* GetLandscapeInput(); + + /** Remove a specific element of the world outliner input selection */ + void RemoveWorldOutlinerInput( int32 AtIndex ); + + EHoudiniAssetInputType::Enum GetChoiceIndex() const { return ChoiceIndex; } + + FText GetCurrentSelectionText() const; + + // Return the bounds of this input + FBox GetInputBounds( const FVector& ParentLocation ) const; + + /** Return true if this parameter has been changed. **/ + bool HasChanged() const override; + + /** Retruns true if at least one object in this input has more than 1 LOD **/ + bool HasLODs() const; + + /** Retruns true if at least one object in this input has sockets **/ + bool HasSockets() const; + + /** Returns the input's transform scale values **/ + TOptional< float > GetPositionX( int32 AtIndex ) const; + TOptional< float > GetPositionY( int32 AtIndex ) const; + TOptional< float > GetPositionZ( int32 AtIndex ) const; + + /** Sets the input's transform scale values **/ + void SetPositionX( float Value, int32 AtIndex ); + void SetPositionY( float Value, int32 AtIndex ); + void SetPositionZ( float Value, int32 AtIndex ); + + /** Returns the input's transform rotation values **/ + TOptional< float > GetRotationRoll(int32 AtIndex) const; + TOptional< float > GetRotationPitch(int32 AtIndex) const; + TOptional< float > GetRotationYaw(int32 AtIndex) const; + + /** Sets the input's transform rotation value **/ + void SetRotationRoll(float Value, int32 AtIndex); + void SetRotationPitch(float Value, int32 AtIndex); + void SetRotationYaw(float Value, int32 AtIndex); + + /** Returns the input's transform scale values **/ + TOptional< float > GetScaleX(int32 AtIndex) const; + TOptional< float > GetScaleY(int32 AtIndex) const; + TOptional< float > GetScaleZ(int32 AtIndex) const; + + /** Sets the input's transform scale values **/ + void SetScaleX(float Value, int32 AtIndex); + void SetScaleY(float Value, int32 AtIndex); + void SetScaleZ(float Value, int32 AtIndex); + + bool IsLandscapeUpdateNeededOnTransformChange() const; + +#if WITH_EDITOR + + /** Called when change of input actor selection. **/ + void OnInputActorSelected( AActor * Actor ); + + /** Called when change of landscape selection. **/ + void OnLandscapeActorSelected(AActor * Actor); + + protected: + FReply OnExpandInputTransform( int32 AtIndex ); + + /** Delegate used when static mesh has been drag and dropped. **/ + void OnStaticMeshDropped( UObject * InObject, int32 AtIndex ); + + /** Handler for when static mesh thumbnail is double clicked. We open editor in this case. **/ + FReply OnThumbnailDoubleClick( const FGeometry & InMyGeometry, const FPointerEvent & InMouseEvent, int32 AtIndex ); + + /** Browse to static mesh. **/ + void OnStaticMeshBrowse( int32 AtIndex ); + + /** Handler for reset static mesh button. **/ + FReply OnResetStaticMeshClicked( int32 AtIndex ); + + /** Delegate used when skeleton mesh has been drag and dropped. **/ + void OnSkeletalMeshDropped( UObject * InObject, int32 AtIndex ); + + /** Browse to skeletal mesh. **/ + void OnSkeletalMeshBrowse( int32 AtIndex ); + + /** Handler for reset skeletal mesh button. **/ + FReply OnResetSkeletalMeshClicked( int32 AtIndex ); + + /** Called when change of selection is triggered. **/ + void OnChoiceChange( TSharedPtr< FString > NewChoice ); + + /** Called on actor picker selection to filter the actors by type. **/ + bool OnShouldFilterActor( const AActor * const Actor ) const; + + /** Called when actor selection changed. **/ + void OnActorSelected(AActor * Actor); + + /** Called when change of World Outliner selection in Actor Picker. **/ + void OnWorldOutlinerActorSelected( AActor * Actor ); + + /** Check if input Actors have had their Transforms changed. **/ + void TickWorldOutlinerInputs(); + + /** Update WorldOutliners Transform after they changed **/ + void UpdateWorldOutlinerTransforms(FHoudiniAssetInputOutlinerMesh& OutlinerMesh); + + /** Update WorldOutliners Materials after they changed **/ + void UpdateWorldOutlinerMaterials(FHoudiniAssetInputOutlinerMesh& OutlinerMesh); + + /** Removes invalid inputs or updates inputs with invalid components in InputOutlinerArray. **/ + /** Returns true when a change was made **/ + bool UpdateInputOulinerArray(); + + /** Update InputOutlinerArray with the components found on actor **/ + /** NeedCleanUp indicates that existing inputs from Actor needs to be removed **/ + void UpdateInputOulinerArrayFromActor( AActor * Actor, const bool& NeedCleanUp ); + + /** Called to append a slot to the list of geometry input objects */ + void OnAddToInputObjects(); + /** Called to empty the list of geometry input objects */ + void OnEmptyInputObjects(); + /** Called to append a slot to the list of skeleton input objects */ + void OnAddToSkeletonInputObjects(); + /** Called to empty the list of skeleton input objects */ + void OnEmptySkeletonInputObjects(); + +#endif + /** Finds an actor in the world by using a path name string **/ + //static AActor* TryToFindActorByPathName(const FString& ActorPathName); + + /** Called to retrieve the name of selected item. **/ + FText HandleChoiceContentText() const; + + /** Connect the input asset in Houdini. **/ + void ConnectInputAssetActor(); + + /** Disconnect the input asset in Houdini. **/ + void DisconnectInputAssetActor(); + + /** Connect the landscape asset in Houdini. **/ + void ConnectLandscapeActor(); + + /** Disconnect the landscape asset in Houdini. **/ + void DisconnectLandscapeActor(); + + /** Extract curve parameters and update the attached spline component. **/ + bool UpdateInputCurve(); + + /** Clear input curve parameters. **/ + void ClearInputCurveParameters(); + + /** Disconnect the input curve component but do not destroy the curve. **/ + void DisconnectInputCurve(); + + /** Destroy input curve object. **/ + void DestroyInputCurve(); + + /** Create necessary resources for this input. **/ + void CreateWidgetResources(); + + /** Connects input node to asset or sets the object path parameter, returns true on success */ + bool ConnectInputNode(); + + /** Updates the input's Object Merge Transform type **/ + bool UpdateObjectMergeTransformType(); + + /** Update's the input's object merge Pack before merging value **/ + bool UpdateObjectMergePackBeforeMerge(); + + /** Returns the default value for this input Transform Type, 0 = none / 1 = IntoThisObject **/ + uint32 GetDefaultTranformTypeValue() const; + + /** Returns the geometry input object at index or nullptr */ + UObject* GetInputObject( int32 AtIndex ) const; + + /** Returns the skeleton input object at index or nullptr */ + UObject* GetSkeletonInputObject(int32 AtIndex) const; + + /** Returns the input transform at index or the identity transform */ + FTransform GetInputTransform( int32 AtIndex ) const; + + /** Returns primary object cast to HoudiniAssetComponent */ + class UHoudiniAssetComponent* GetHoudiniAssetComponent(); + const class UHoudiniAssetComponent* GetHoudiniAssetComponent() const; + + /** Get the node id of the asset we are associated with */ + HAPI_NodeId GetAssetId() const; + + /** Parameters used by a curve input asset. **/ + TMap< FString, UHoudiniAssetParameter * > InputCurveParameters; + + /** Choice labels for this property. **/ + TArray< TSharedPtr< FString > > StringChoiceLabels; + +#if WITH_EDITOR + + /** Check if state of landscape selection checkbox has changed. **/ + void CheckStateChangedExportOnlySelected( ECheckBoxState NewState ); + + /** Return checked state of landscape selection checkbox. **/ + ECheckBoxState IsCheckedExportOnlySelected() const; + + /** Check if state of landscape auto selection checkbox has changed. **/ + void CheckStateChangedAutoSelectLandscape(ECheckBoxState NewState); + + /** Return checked state of landscape auto selection checkbox. **/ + ECheckBoxState IsCheckedAutoSelectLandscape() const; + + /** Check if state of landscape curves checkbox has changed. **/ + void CheckStateChangedExportCurves( ECheckBoxState NewState ); + + /** Return checked state of landscape curves checkbox. **/ + ECheckBoxState IsCheckedExportCurves() const; + + /** Check if the state of the export landscape as mesh checkbox has changed. **/ + void CheckStateChangedExportAsMesh( ECheckBoxState NewState ); + + /** Return checked state of the export landscape as mesh checkbox. **/ + ECheckBoxState IsCheckedExportAsMesh() const; + + /** Check if the state of the export landscape as heightfield checkbox has changed. **/ + void CheckStateChangedExportAsHeightfield( ECheckBoxState NewState ); + + /** Return checked state of the export landscape as heightfield checkbox. **/ + ECheckBoxState IsCheckedExportAsHeightfield() const; + + /** Check if the state of the export landscape as points checkbox has changed. **/ + void CheckStateChangedExportAsPoints( ECheckBoxState NewState ); + + /** Return checked state of the export landscape as points checkbox. **/ + ECheckBoxState IsCheckedExportAsPoints() const; + + /** Check if state of landscape materials checkbox has changed. **/ + void CheckStateChangedExportMaterials( ECheckBoxState NewState ); + + /** Return checked state of landscape materials checkbox. **/ + ECheckBoxState IsCheckedExportMaterials() const; + + /** Check if state of landscape lighting checkbox has changed. **/ + void CheckStateChangedExportLighting( ECheckBoxState NewState ); + + /** Return checked state of landscape lighting checkbox. **/ + ECheckBoxState IsCheckedExportLighting() const; + + /** Check if state of landscape normalized uv checkbox has changed. **/ + void CheckStateChangedExportNormalizedUVs( ECheckBoxState NewState ); + + /** Return checked state of landscape normalized uv checkbox. **/ + ECheckBoxState IsCheckedExportNormalizedUVs() const; + + /** Check if state of landscape tile uv checkbox has changed. **/ + void CheckStateChangedExportTileUVs( ECheckBoxState NewState ); + + /** Return checked state of landscape tile uv checkbox. **/ + ECheckBoxState IsCheckedExportTileUVs() const; + + /** Check if state of the transform type checkbox has changed. **/ + void CheckStateChangedKeepWorldTransform( ECheckBoxState NewState ); + + /** Return checked state of transform type checkbox. **/ + ECheckBoxState IsCheckedKeepWorldTransform() const; + + /** Check if state of the export LODs checkbox has changed. **/ + void CheckStateChangedExportAllLODs( ECheckBoxState NewState ); + + /** Return checked state of export LODs checkbox. **/ + ECheckBoxState IsCheckedExportAllLODs() const; + + /** Check if state of the export sockets checkbox has changed. **/ + void CheckStateChangedExportSockets(ECheckBoxState NewState); + + /** Return checked state of export sockets checkbox. **/ + ECheckBoxState IsCheckedExportSockets() const; + + /** Check if state of the UpdateInputLandscape checkbox has changed. **/ + void CheckStateChangedUpdateInputLandscape(ECheckBoxState NewState); + + /** Return checked state of the UpdateInputLandscape checkbox. **/ + ECheckBoxState IsCheckedUpdateInputLandscape() const; + + /** Handler for landscape recommit button. **/ + FReply OnButtonClickRecommit(); + + /** Start world outliner Actor transform monitor ticking. **/ + void StartWorldOutlinerTicking(); + + /** Stop world outliner Actor transform monitor ticking. **/ + void StopWorldOutlinerTicking(); + + /** Set value of the SplineResolution for world outliners, used by Slate. **/ + void SetSplineResolutionValue(float InValue); + + /** Return value of the SplineResolution for world outliners. **/ + TOptional< float > GetSplineResolutionValue() const; + + /** Should the SplineResolution box be enabled?**/ + bool IsSplineResolutionEnabled() const; + + /** Reset the spline resolution to default **/ + FReply OnResetSplineResolutionClicked(); + + /** Check if state of the transform type checkbox has changed. **/ + void CheckStateChangedPackBeforeMerge( ECheckBoxState NewState ); + + /** Return checked state of landscape tile uv checkbox. **/ + ECheckBoxState IsCheckedPackBeforeMerge() const; + + /** Callbacks for geo array UI buttons */ + void OnInsertGeo( int32 AtIndex ); + void OnDeleteGeo( int32 AtIndex ); + void OnDuplicateGeo( int32 AtIndex ); + +#endif + + /** Value of choice option. **/ + FString ChoiceStringValue; + + /** Objects used for geometry input. **/ + TArray InputObjects; + + /** Houdini spline component which is used for curve input. **/ + class UHoudiniSplineComponent * InputCurve; + + /** Houdini asset component pointer of the input asset (actor). **/ + class UHoudiniAssetComponent * InputAssetComponent; + + /** Landscape actor used for input. **/ + TSoftObjectPtr InputLandscapeProxy; + + /** List of selected meshes and actors from the World Outliner. **/ + TArray< FHoudiniAssetInputOutlinerMesh > InputOutlinerMeshArray; + + /** Objects used for skeletal mesh input. **/ + TArray SkeletonInputObjects; + + /** Id of currently connected asset. **/ + HAPI_NodeId ConnectedAssetId; + + /** The ids of the assets connected to the input for GeometryInput mode */ + /** or the ids of the volume connected to the input for Landscape mode (heightfield) */ + TArray< HAPI_NodeId > CreatedInputDataAssetIds; + + /** Index of this input. **/ + int32 InputIndex; + + /** Choice selection. **/ + EHoudiniAssetInputType::Enum ChoiceIndex; + + /** Timer handle, this timer is used for seeing if input Actors have changed. **/ + FTimerHandle WorldOutlinerTimerHandle; + + /** Timer delegate, we use it for ticking to see if input Actors have changed. **/ + FTimerDelegate WorldOutlinerTimerDelegate; + + float UnrealSplineResolution; + + /** Indicates that the OutlinerInputs have just been loaded and needs to be updated **/ + bool OutlinerInputsNeedPostLoadInit; + + /** Array containing the transform corrections for the assets in a geometry input **/ + TArray< FTransform > InputTransforms; + + /** Is the transform UI expanded ? **/ + TArray< bool > TransformUIExpanded; + + /** Transform used by the input landscape **/ + FTransform InputLandscapeTransform; + + /** Flags used by this input. **/ + union + { + struct + { + /** Is set to true when static mesh used for geometry input has changed. **/ + uint32 bStaticMeshChanged : 1; + + /** Is set to true when choice switches to curve mode. **/ + uint32 bSwitchedToCurve : 1; + + /** Is set to true if this parameter has been loaded. **/ + uint32 bLoadedParameter : 1; + + /** Is set to true if the asset input is actually connected inside Houdini. **/ + uint32 bInputAssetConnectedInHoudini : 1; + + /** Is set to true when landscape input is set to selection only. **/ + uint32 bLandscapeExportSelectionOnly : 1; + + /** Is set to true when landscape curves are to be exported. **/ + uint32 bLandscapeExportCurves : 1; + + /** Is set to true when the landscape is to be exported as a mesh, not just points. **/ + uint32 bLandscapeExportAsMesh : 1; + + /** Is set to true when materials are to be exported. **/ + uint32 bLandscapeExportMaterials : 1; + + /** Is set to true when lightmap information export is desired. **/ + uint32 bLandscapeExportLighting : 1; + + /** Is set to true when uvs should be exported in [0,1] space. **/ + uint32 bLandscapeExportNormalizedUVs : 1; + + /** Is set to true when uvs should be exported for each tile separately. **/ + uint32 bLandscapeExportTileUVs : 1; + + /** Is set to true when being used as an object-path parameter instead of an input */ + uint32 bIsObjectPathParameter : 1; + + /** Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value **/ + uint32 bKeepWorldTransform : 2; + + /** Is set to true when the landscape is to be exported as a heightfield **/ + uint32 bLandscapeExportAsHeightfield : 1; + + /** Is set to true when the automatic selection of landscape component is active **/ + uint32 bLandscapeAutoSelectComponent : 1; + + /** Indicates that the geometry must be packed before merging it into the input **/ + uint32 bPackBeforeMerge : 1; + + /** Indicates that all LODs in the input should be marshalled to Houdini **/ + uint32 bExportAllLODs : 1; + + /** Indicates that all sockets in the input should be marshalled to Houdini **/ + uint32 bExportSockets : 1; + + /** Indicates that the landscape input's source landscape should be updated instead of creating a new component **/ + uint32 bUpdateInputLandscape : 1; + }; + + uint32 HoudiniAssetInputFlagsPacked; + }; +}; + +/** Serialization function. **/ +HOUDINIENGINERUNTIME_API FArchive & operator<<( + FArchive & Ar, FHoudiniAssetInputOutlinerMesh & HoudiniAssetInputOutlinerMesh ); diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInstanceInput.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInstanceInput.cpp new file mode 100644 index 00000000..4763b982 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInstanceInput.cpp @@ -0,0 +1,1408 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetInstanceInput.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetInstanceInputField.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineString.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "Components/AudioComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "Particles/ParticleSystemComponent.h" +#include "Sound/SoundBase.h" + +#include "Internationalization/Internationalization.h" +#include "HoudiniEngineBakeUtils.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + + +UHoudiniAssetInstanceInput::UHoudiniAssetInstanceInput( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) + , ObjectToInstanceId( -1 ) +{ + Flags.HoudiniAssetInstanceInputFlagsPacked = 0; + TupleSize = 0; +} + +UHoudiniAssetInstanceInput * +UHoudiniAssetInstanceInput::Create( + UHoudiniAssetComponent * InPrimaryObject, + const FHoudiniGeoPartObject & InHoudiniGeoPartObject ) +{ + if (!InPrimaryObject || InPrimaryObject->IsPendingKill()) + return nullptr; + + UHoudiniAssetInstanceInput * NewInstanceInput = nullptr; + + // Get object to be instanced. + HAPI_NodeId ObjectToInstance = InHoudiniGeoPartObject.HapiObjectGetToInstanceId(); + + auto Flags = GetInstancerFlags(InHoudiniGeoPartObject); + + // This is invalid combination, no object to instance and input is not an attribute instancer. + if ( !Flags.bIsAttributeInstancer && !Flags.bAttributeInstancerOverride && ObjectToInstance == -1 && !Flags.bIsPackedPrimitiveInstancer ) + return nullptr; + + NewInstanceInput = NewObject< UHoudiniAssetInstanceInput >( + InPrimaryObject, + UHoudiniAssetInstanceInput::StaticClass(), + NAME_None, RF_Public | RF_Transactional ); + + if ( !NewInstanceInput || NewInstanceInput->IsPendingKill() ) + return nullptr; + + NewInstanceInput->PrimaryObject = InPrimaryObject; + NewInstanceInput->HoudiniGeoPartObject = InHoudiniGeoPartObject; + NewInstanceInput->SetNameAndLabel( InHoudiniGeoPartObject.ObjectName ); + NewInstanceInput->ObjectToInstanceId = ObjectToInstance; + + NewInstanceInput->Flags = Flags; + + return NewInstanceInput; +} + +UHoudiniAssetInstanceInput::FHoudiniAssetInstanceInputFlags +UHoudiniAssetInstanceInput::GetInstancerFlags(const FHoudiniGeoPartObject & InHoudiniGeoPartObject) +{ + FHoudiniAssetInstanceInputFlags Flags; + Flags.HoudiniAssetInstanceInputFlagsPacked = 0; + + // Get object to be instanced. + HAPI_NodeId ObjectToInstance = InHoudiniGeoPartObject.HapiObjectGetToInstanceId(); + + Flags.bIsPackedPrimitiveInstancer = InHoudiniGeoPartObject.IsPackedPrimitiveInstancer(); + + // If this is an attribute instancer, see if attribute exists. + Flags.bIsAttributeInstancer = InHoudiniGeoPartObject.IsAttributeInstancer(); + + // Check if this is an attribute override instancer (on detail or point). + Flags.bAttributeInstancerOverride = InHoudiniGeoPartObject.IsAttributeOverrideInstancer(); + + // Check if this is a No-Instancers ( unreal_split_instances ) + Flags.bIsSplitMeshInstancer = + InHoudiniGeoPartObject.HapiCheckAttributeExistance( + HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, HAPI_ATTROWNER_DETAIL ); + return Flags; +} + +UHoudiniAssetInstanceInput * +UHoudiniAssetInstanceInput::Create( + UHoudiniAssetComponent * InPrimaryObject, + const UHoudiniAssetInstanceInput * OtherInstanceInput ) +{ + if (!InPrimaryObject || InPrimaryObject->IsPendingKill() ) + return nullptr; + + UHoudiniAssetInstanceInput * HoudiniAssetInstanceInput = + DuplicateObject( OtherInstanceInput, InPrimaryObject ); + + if (!HoudiniAssetInstanceInput || HoudiniAssetInstanceInput->IsPendingKill()) + return nullptr; + + // We need to duplicate field objects manually + HoudiniAssetInstanceInput->InstanceInputFields.Empty(); + for ( const auto& OtherField : OtherInstanceInput->InstanceInputFields ) + { + UHoudiniAssetInstanceInputField * NewField = + UHoudiniAssetInstanceInputField::Create( InPrimaryObject, OtherField ); + + if (!NewField || NewField->IsPendingKill()) + continue; + + HoudiniAssetInstanceInput->InstanceInputFields.Add( NewField ); + } + // Fix the back-reference to the component + HoudiniAssetInstanceInput->PrimaryObject = InPrimaryObject; + return HoudiniAssetInstanceInput; +} + +bool +UHoudiniAssetInstanceInput::CreateInstanceInput() +{ + if ( !PrimaryObject || PrimaryObject->IsPendingKill() ) + return false; + + HAPI_NodeId AssetId = GetAssetId(); + + // Retrieve instance transforms (for each point). + TArray< FTransform > AllTransforms; + HoudiniGeoPartObject.HapiGetInstanceTransforms( AssetId, AllTransforms ); + + // List of new fields. Reused input fields will also be placed here. + TArray< UHoudiniAssetInstanceInputField * > NewInstanceInputFields; + + if ( Flags.bIsPackedPrimitiveInstancer ) + { + // This is using packed primitives + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, + &PartInfo ), false ); + + // Retrieve part name. + //FString PartName; + //FHoudiniEngineString HoudiniEngineStringPartName( PartInfo.nameSH ); + //HoudiniEngineStringPartName.ToFString( PartName ); + + //HOUDINI_LOG_MESSAGE( TEXT( "Part Instancer (%s): IPC=%d, IC=%d" ), *PartName, PartInfo.instancedPartCount, PartInfo.instanceCount ); + + // Get transforms for each instance + TArray InstancerPartTransforms; + InstancerPartTransforms.SetNumUninitialized( PartInfo.instanceCount ); + for (int32 Idx = 0; Idx < InstancerPartTransforms.Num(); Idx++) + FHoudiniApi::Transform_Init(&(InstancerPartTransforms[Idx])); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstancerPartTransforms( + FHoudiniEngine::Get().GetSession(), HoudiniGeoPartObject.GeoId, PartInfo.id, + HAPI_RSTORDER_DEFAULT, InstancerPartTransforms.GetData(), 0, PartInfo.instanceCount ), false ); + + // Get the part ids for parts being instanced + TArray InstancedPartIds; + InstancedPartIds.SetNumZeroed( PartInfo.instancedPartCount ); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstancedPartIds( + FHoudiniEngine::Get().GetSession(), HoudiniGeoPartObject.GeoId, PartInfo.id, + InstancedPartIds.GetData(), 0, PartInfo.instancedPartCount ), false ); + + for ( auto InstancedPartId : InstancedPartIds ) + { + HAPI_PartInfo InstancedPartInfo; + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), HoudiniGeoPartObject.GeoId, InstancedPartId, + &InstancedPartInfo ), false ); + + TArray ObjectTransforms; + ObjectTransforms.SetNumUninitialized( InstancerPartTransforms.Num() ); + for ( int32 InstanceIdx = 0; InstanceIdx < InstancerPartTransforms.Num(); ++InstanceIdx ) + { + const auto& InstanceTransform = InstancerPartTransforms[InstanceIdx]; + FHoudiniEngineUtils::TranslateHapiTransform( InstanceTransform, ObjectTransforms[InstanceIdx] ); + //HOUDINI_LOG_MESSAGE( TEXT( "Instance %d:%d Transform: %s for Part Id %d" ), HoudiniGeoPartObject.PartId, InstanceIdx, *ObjectTransforms[ InstanceIdx ].ToString(), InstancedPartId ); + } + + // Create this instanced input field for this instanced part + // + FHoudiniGeoPartObject InstancedPart( HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId, HoudiniGeoPartObject.GeoId, InstancedPartId ); + InstancedPart.TransformMatrix = HoudiniGeoPartObject.TransformMatrix; + InstancedPart.UpdateCustomName(); + CreateInstanceInputField( InstancedPart, ObjectTransforms, InstanceInputFields, NewInstanceInputFields ); + } + } + else if ( Flags.bIsAttributeInstancer ) + { + int32 NumPoints = HoudiniGeoPartObject.HapiPartGetPointCount(); + TArray< HAPI_NodeId > InstancedObjectIds; + InstancedObjectIds.SetNumUninitialized( NumPoints ); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstancedObjectIds( + FHoudiniEngine::Get().GetSession(), HoudiniGeoPartObject.GeoId, + InstancedObjectIds.GetData(), + 0, NumPoints), false ); + + // Find the set of instanced object ids and locate the corresponding parts + TSet< int32 > UniqueInstancedObjectIds( InstancedObjectIds ); + TArray< FTransform > InstanceTransforms; + for ( int32 InstancedObjectId : UniqueInstancedObjectIds ) + { + UHoudiniAssetComponent* Comp = GetHoudiniAssetComponent(); + if( Comp && !Comp->IsPendingKill() ) + { + TArray< FHoudiniGeoPartObject > PartsToInstance; + if( Comp->LocateStaticMeshes( InstancedObjectId, PartsToInstance ) ) + { + // copy out the transforms for this instance id + InstanceTransforms.Empty(); + for( int32 Ix = 0; Ix < InstancedObjectIds.Num(); ++Ix ) + { + if( ( InstancedObjectIds[ Ix ] == InstancedObjectId ) && ( AllTransforms.IsValidIndex( Ix ) ) ) + { + InstanceTransforms.Add( AllTransforms[ Ix ] ); + } + } + + // Locate or create an instance input field for each part for this instanced object id + for( FHoudiniGeoPartObject& Part : PartsToInstance ) + { + // Change the transform of the part being instanced to match the instancer + Part.TransformMatrix = HoudiniGeoPartObject.TransformMatrix; + Part.UpdateCustomName(); + CreateInstanceInputField( Part, InstanceTransforms, InstanceInputFields, NewInstanceInputFields ); + } + } + } + } + } + else if ( Flags.bAttributeInstancerOverride ) + { + // This is an attribute override. Unreal mesh is specified through an attribute and we use points. + std::string MarshallingAttributeInstanceOverride = HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE; + UHoudiniRuntimeSettings::GetSettingsValue( + TEXT( "MarshallingAttributeInstanceOverride" ), + MarshallingAttributeInstanceOverride ); + + HAPI_AttributeInfo ResultAttributeInfo; + FHoudiniApi::AttributeInfo_Init(&ResultAttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >( ResultAttributeInfo ); + + if ( !HoudiniGeoPartObject.HapiGetAttributeInfo( + AssetId, MarshallingAttributeInstanceOverride, ResultAttributeInfo ) ) + { + // We had an error while retrieving the attribute info. + return false; + } + + if ( !ResultAttributeInfo.exists ) + { + // Attribute does not exist. + return false; + } + + if ( ResultAttributeInfo.owner == HAPI_ATTROWNER_DETAIL ) + { + // Attribute is on detail, this means it gets applied to all points. + TArray< FString > DetailInstanceValues; + if ( !HoudiniGeoPartObject.HapiGetAttributeDataAsString( + AssetId, MarshallingAttributeInstanceOverride, + HAPI_ATTROWNER_DETAIL, ResultAttributeInfo, DetailInstanceValues ) ) + { + // This should not happen - attribute exists, but there was an error retrieving it. + return false; + } + + if ( DetailInstanceValues.Num() <= 0 ) + { + // No values specified. + return false; + } + + // Attempt to load specified asset. + const FString & AssetName = DetailInstanceValues[ 0 ]; + UObject * AttributeObject = + StaticLoadObject( + UObject::StaticClass(), nullptr, *AssetName, nullptr, LOAD_None, nullptr ); + + if ( AttributeObject && !AttributeObject->IsPendingKill() ) + { + CreateInstanceInputField( + AttributeObject, AllTransforms, InstanceInputFields, NewInstanceInputFields ); + } + else + { + return false; + } + } + else if ( ResultAttributeInfo.owner == HAPI_ATTROWNER_POINT ) + { + TArray< FString > PointInstanceValues; + if ( !HoudiniGeoPartObject.HapiGetAttributeDataAsString( + AssetId, MarshallingAttributeInstanceOverride, + HAPI_ATTROWNER_POINT, ResultAttributeInfo, PointInstanceValues ) ) + { + // This should not happen - attribute exists, but there was an error retrieving it. + return false; + } + + // Attribute is on points, number of points must match number of transforms. + if ( PointInstanceValues.Num() != AllTransforms.Num() ) + { + // This should not happen, we have mismatch between number of instance values and transforms. + return false; + } + + // If instance attribute exists on points, we need to get all unique values. + TMap< FString, UObject * > ObjectsToInstance; + + for ( auto Iter : PointInstanceValues ) + { + const FString & UniqueName = *Iter; + + if ( !ObjectsToInstance.Contains( UniqueName ) ) + { + UObject * AttributeObject = StaticLoadObject( + UObject::StaticClass(), nullptr, *UniqueName, nullptr, LOAD_None, nullptr ); + + if ( AttributeObject && !AttributeObject->IsPendingKill() ) + ObjectsToInstance.Add( UniqueName, AttributeObject ); + } + } + + bool Success = false; + + for( auto Iter : ObjectsToInstance ) + { + const FString & InstancePath = Iter.Key; + UObject * AttributeObject = Iter.Value; + + if ( AttributeObject && !AttributeObject->IsPendingKill() ) + { + TArray< FTransform > ObjectTransforms; + GetPathInstaceTransforms( InstancePath, PointInstanceValues, AllTransforms, ObjectTransforms ); + CreateInstanceInputField( AttributeObject, ObjectTransforms, InstanceInputFields, NewInstanceInputFields ); + Success = true; + } + } + if ( !Success ) + return false; + } + else + { + // We don't support this attribute on other owners. + return false; + } + } + else + { + // This is a standard object type instancer. + + // Locate all geo objects requiring instancing (can be multiple if geo / part / object split took place). + TArray< FHoudiniGeoPartObject > ObjectsToInstance; + if( UHoudiniAssetComponent* Comp = GetHoudiniAssetComponent() ) + { + if ( !Comp->IsPendingKill() ) + Comp->LocateStaticMeshes( ObjectToInstanceId, ObjectsToInstance ); + } + + // Process each existing detected object that needs to be instanced. + for ( int32 GeoIdx = 0; GeoIdx < ObjectsToInstance.Num(); ++GeoIdx ) + { + FHoudiniGeoPartObject & ItemHoudiniGeoPartObject = ObjectsToInstance[ GeoIdx ]; + + // Change the transform of the part being instanced to match the instancer + ItemHoudiniGeoPartObject.TransformMatrix = HoudiniGeoPartObject.TransformMatrix; + ItemHoudiniGeoPartObject.UpdateCustomName(); + + // Locate or create an input field. + CreateInstanceInputField( + ItemHoudiniGeoPartObject, AllTransforms, InstanceInputFields, NewInstanceInputFields ); + } + } + + // Sort and store new fields. + NewInstanceInputFields.Sort( FHoudiniAssetInstanceInputFieldSortPredicate() ); + CleanInstanceInputFields( InstanceInputFields ); + InstanceInputFields = NewInstanceInputFields; + + return true; +} + +UHoudiniAssetInstanceInputField * +UHoudiniAssetInstanceInput::LocateInputField( + const FHoudiniGeoPartObject & GeoPartObject) +{ + UHoudiniAssetInstanceInputField * FoundHoudiniAssetInstanceInputField = nullptr; + for ( int32 FieldIdx = 0; FieldIdx < InstanceInputFields.Num(); ++FieldIdx ) + { + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField = InstanceInputFields[ FieldIdx ]; + if (!HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill()) + continue; + + if ( HoudiniAssetInstanceInputField->GetHoudiniGeoPartObject().GetNodePath() == GeoPartObject.GetNodePath() ) + { + FoundHoudiniAssetInstanceInputField = HoudiniAssetInstanceInputField; + break; + } + } + + return FoundHoudiniAssetInstanceInputField; +} + +void +UHoudiniAssetInstanceInput::CleanInstanceInputFields( TArray< UHoudiniAssetInstanceInputField * > & InInstanceInputFields ) +{ + UHoudiniAssetInstanceInputField * FoundHoudiniAssetInstanceInputField = nullptr; + for ( int32 FieldIdx = 0; FieldIdx < InstanceInputFields.Num(); ++FieldIdx ) + { + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField = InstanceInputFields[ FieldIdx ]; + if ( HoudiniAssetInstanceInputField && !HoudiniAssetInstanceInputField->IsPendingKill() ) + HoudiniAssetInstanceInputField->ConditionalBeginDestroy(); + } + + InInstanceInputFields.Empty(); +} + +UHoudiniAssetComponent* +UHoudiniAssetInstanceInput::GetHoudiniAssetComponent() +{ + return Cast( PrimaryObject ); +} + +const UHoudiniAssetComponent* +UHoudiniAssetInstanceInput::GetHoudiniAssetComponent() const +{ + return Cast( PrimaryObject ); +} + +HAPI_NodeId +UHoudiniAssetInstanceInput::GetAssetId() const +{ + if( const UHoudiniAssetComponent* Comp = GetHoudiniAssetComponent() ) + return Comp->GetAssetId(); + return -1; +} + +void +UHoudiniAssetInstanceInput::CreateInstanceInputField( + const FHoudiniGeoPartObject & InHoudiniGeoPartObject, + const TArray< FTransform > & ObjectTransforms, + const TArray< UHoudiniAssetInstanceInputField * > & OldInstanceInputFields, + TArray & NewInstanceInputFields) +{ + UHoudiniAssetComponent* Comp = GetHoudiniAssetComponent(); + UStaticMesh * StaticMesh = nullptr; + if ( Comp && !Comp->IsPendingKill() ) + StaticMesh = Comp->LocateStaticMesh(InHoudiniGeoPartObject, false); + + // Locate static mesh for this geo part. + if ( StaticMesh && !StaticMesh->IsPendingKill() ) + { + // Locate corresponding input field. + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField = + LocateInputField( InHoudiniGeoPartObject ); + + if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) + { + // Input field does not exist, we need to create it. + HoudiniAssetInstanceInputField = UHoudiniAssetInstanceInputField::Create( + PrimaryObject, this, InHoudiniGeoPartObject ); + + if (HoudiniAssetInstanceInputField && !HoudiniAssetInstanceInputField->IsPendingKill()) + { + // Assign original and static mesh. + HoudiniAssetInstanceInputField->OriginalObject = StaticMesh; + HoudiniAssetInstanceInputField->AddInstanceVariation(StaticMesh, 0); + } + } + else + { + // refresh the geo part + HoudiniAssetInstanceInputField->SetGeoPartObject( InHoudiniGeoPartObject ); + + // Remove item from old list. + InstanceInputFields.RemoveSingleSwap( HoudiniAssetInstanceInputField, false ); + + TArray< int > MatchingIndices; + HoudiniAssetInstanceInputField->FindObjectIndices( + HoudiniAssetInstanceInputField->GetOriginalObject(), + MatchingIndices ); + + for ( int Idx = 0; Idx < MatchingIndices.Num(); Idx++ ) + { + int ReplacementIndex = MatchingIndices[ Idx ]; + HoudiniAssetInstanceInputField->ReplaceInstanceVariation( StaticMesh, ReplacementIndex ); + } + + HoudiniAssetInstanceInputField->OriginalObject = StaticMesh; + } + + if ( HoudiniAssetInstanceInputField && !HoudiniAssetInstanceInputField->IsPendingKill() ) + { + // Set transforms for this input. + HoudiniAssetInstanceInputField->SetInstanceTransforms(ObjectTransforms); + HoudiniAssetInstanceInputField->UpdateInstanceUPropertyAttributes(); + + // Add field to list of fields. + NewInstanceInputFields.Add(HoudiniAssetInstanceInputField); + } + } + else if ( InHoudiniGeoPartObject.IsPackedPrimitiveInstancer() ) + { + HAPI_Result Result = HAPI_RESULT_SUCCESS; + // We seem to be instancing a PP instancer, we need to get the transforms + + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR( &Result, FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), InHoudiniGeoPartObject.GeoId, InHoudiniGeoPartObject.PartId, + &PartInfo ) ); + + // Get transforms for each instance + TArray InstancerPartTransforms; + InstancerPartTransforms.SetNumUninitialized( PartInfo.instanceCount ); + for (int32 Idx = 0; Idx < InstancerPartTransforms.Num(); Idx++) + FHoudiniApi::Transform_Init(&(InstancerPartTransforms[Idx])); + + HOUDINI_CHECK_ERROR( &Result, FHoudiniApi::GetInstancerPartTransforms( + FHoudiniEngine::Get().GetSession(), InHoudiniGeoPartObject.GeoId, PartInfo.id, + HAPI_RSTORDER_DEFAULT, InstancerPartTransforms.GetData(), 0, PartInfo.instanceCount ) ); + + // Get the part ids for parts being instanced + TArray InstancedPartIds; + InstancedPartIds.SetNumZeroed( PartInfo.instancedPartCount ); + HOUDINI_CHECK_ERROR( &Result, FHoudiniApi::GetInstancedPartIds( + FHoudiniEngine::Get().GetSession(), InHoudiniGeoPartObject.GeoId, PartInfo.id, + InstancedPartIds.GetData(), 0, PartInfo.instancedPartCount ) ); + + for ( auto InstancedPartId : InstancedPartIds ) + { + HAPI_PartInfo InstancedPartInfo; + FHoudiniApi::PartInfo_Init(&InstancedPartInfo); + HOUDINI_CHECK_ERROR( &Result, + FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), InHoudiniGeoPartObject.GeoId, InstancedPartId, + &InstancedPartInfo ) ); + + TArray PPObjectTransforms; + PPObjectTransforms.SetNumUninitialized( InstancerPartTransforms.Num() ); + for ( int32 InstanceIdx = 0; InstanceIdx < InstancerPartTransforms.Num(); ++InstanceIdx ) + { + const auto& InstanceTransform = InstancerPartTransforms[InstanceIdx]; + FHoudiniEngineUtils::TranslateHapiTransform( InstanceTransform, PPObjectTransforms[InstanceIdx] ); + } + + // Create this instanced input field for this instanced part + + // find static mesh for this instancer + FHoudiniGeoPartObject TempInstancedPart( InHoudiniGeoPartObject.AssetId, InHoudiniGeoPartObject.ObjectId, InHoudiniGeoPartObject.GeoId, InstancedPartId ); + UStaticMesh* FoundStaticMesh = Comp->LocateStaticMesh(TempInstancedPart, false); + if ( FoundStaticMesh && !FoundStaticMesh->IsPendingKill() ) + { + // Build the list of transforms for this instancer + TArray< FTransform > AllTransforms; + AllTransforms.Empty( PPObjectTransforms.Num() * ObjectTransforms.Num() ); + for ( const FTransform& ObjectTransform : ObjectTransforms ) + { + for ( const FTransform& PPTransform : PPObjectTransforms ) + { + AllTransforms.Add( PPTransform * ObjectTransform ); + } + } + + CreateInstanceInputField( FoundStaticMesh, AllTransforms, InstanceInputFields, NewInstanceInputFields ); + } + else + { + HOUDINI_LOG_WARNING( + TEXT( "CreateInstanceInputField for Packed Primitive: Could not find static mesh for object [%d %s], geo %d, part %d]" ), InHoudiniGeoPartObject.ObjectId, *InHoudiniGeoPartObject.ObjectName, InHoudiniGeoPartObject.GeoId, InstancedPartId ); + } + } + } + else + { + HOUDINI_LOG_WARNING( + TEXT( "CreateInstanceInputField: Could not find static mesh for object [%d %s], geo %d, part %d]" ), InHoudiniGeoPartObject.ObjectId, *InHoudiniGeoPartObject.ObjectName, InHoudiniGeoPartObject.GeoId, InHoudiniGeoPartObject.PartId ); + } +} + +void +UHoudiniAssetInstanceInput::CreateInstanceInputField( + UObject * InstancedObject, + const TArray< FTransform > & ObjectTransforms, + const TArray< UHoudiniAssetInstanceInputField * > & OldInstanceInputFields, + TArray< UHoudiniAssetInstanceInputField * > & NewInstanceInputFields ) +{ + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField = nullptr; + + // Locate field which have this static mesh set as original mesh. + // Use FindByPredicate instead of FilterByPredicate, + // this fixes the order of the Houdini Instanced Inputs array's randomly changing after a param update. + UHoudiniAssetInstanceInputField** FoundField = InstanceInputFields.FindByPredicate([&](const UHoudiniAssetInstanceInputField* Field) + { + return Field->GetOriginalObject() == InstancedObject; + }); + + if ( FoundField ) + { + HoudiniAssetInstanceInputField = *FoundField; + InstanceInputFields.RemoveSingleSwap( HoudiniAssetInstanceInputField, false ); + + TArray< int32 > MatchingIndices; + HoudiniAssetInstanceInputField->FindObjectIndices( + HoudiniAssetInstanceInputField->GetOriginalObject(), + MatchingIndices ); + + for ( int32 ReplacementIndex : MatchingIndices ) + { + HoudiniAssetInstanceInputField->ReplaceInstanceVariation( InstancedObject, ReplacementIndex ); + } + + HoudiniAssetInstanceInputField->OriginalObject = InstancedObject; + + // refresh the geo part + FHoudiniGeoPartObject RefreshedGeoPart = HoudiniAssetInstanceInputField->GetHoudiniGeoPartObject(); + RefreshedGeoPart.TransformMatrix = HoudiniGeoPartObject.TransformMatrix; + HoudiniAssetInstanceInputField->SetGeoPartObject( RefreshedGeoPart ); + // Update component transformation. + HoudiniAssetInstanceInputField->UpdateRelativeTransform(); + } + else + { + // Create a dummy part for this field + FHoudiniGeoPartObject InstancedPart; + InstancedPart.TransformMatrix = HoudiniGeoPartObject.TransformMatrix; + + HoudiniAssetInstanceInputField = UHoudiniAssetInstanceInputField::Create( + PrimaryObject, this, InstancedPart ); + + // Assign original and static mesh. + HoudiniAssetInstanceInputField->OriginalObject = InstancedObject; + HoudiniAssetInstanceInputField->AddInstanceVariation( InstancedObject, 0 ); + } + + // Set transforms for this input. + HoudiniAssetInstanceInputField->SetInstanceTransforms( ObjectTransforms ); + + // Add field to list of fields. + NewInstanceInputFields.Add( HoudiniAssetInstanceInputField ); +} + +void +UHoudiniAssetInstanceInput::RecreateRenderStates() +{ + for ( int32 Idx = 0; Idx < InstanceInputFields.Num(); ++Idx ) + { + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField = InstanceInputFields[ Idx ]; + HoudiniAssetInstanceInputField->RecreateRenderState(); + } +} + +void +UHoudiniAssetInstanceInput::RecreatePhysicsStates() +{ + for ( int32 Idx = 0; Idx < InstanceInputFields.Num(); ++Idx ) + { + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField = InstanceInputFields[ Idx ]; + HoudiniAssetInstanceInputField->RecreatePhysicsState(); + } +} + +void UHoudiniAssetInstanceInput::SetGeoPartObject( const FHoudiniGeoPartObject& InGeoPartObject ) +{ + HoudiniGeoPartObject = InGeoPartObject; + if ( ObjectToInstanceId == -1 ) + { + ObjectToInstanceId = InGeoPartObject.HapiObjectGetToInstanceId(); + } +} + +bool +UHoudiniAssetInstanceInput::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo) +{ + // This implementation is not a true parameter. This method should not be called. + check( false ); + return false; +} + +#if WITH_EDITOR + +void +UHoudiniAssetInstanceInput::OnAddInstanceVariation( UHoudiniAssetInstanceInputField * InstanceInputField, int32 Index ) +{ + InstanceInputField->AddInstanceVariation( InstanceInputField->GetInstanceVariation( Index ), Index ); + + OnParamStateChanged(); +} + +void +UHoudiniAssetInstanceInput::OnRemoveInstanceVariation( UHoudiniAssetInstanceInputField * InstanceInputField, int32 Index ) +{ + InstanceInputField->RemoveInstanceVariation( Index ); + + OnParamStateChanged(); +} + +FString UHoudiniAssetInstanceInput::GetFieldLabel( int32 FieldIdx, int32 VariationIdx ) const +{ + FString FieldNameText = FString(); + if (!InstanceInputFields.IsValidIndex(FieldIdx)) + return FieldNameText; + + UHoudiniAssetInstanceInputField * Field = InstanceInputFields[ FieldIdx ]; + if ( !Field || Field->IsPendingKill() ) + return FieldNameText; + + if ( Flags.bIsPackedPrimitiveInstancer ) + { + // If the packed prim itself has a cutsom name, use it + // else, use the instancer's custom name + if ( Field->GetHoudiniGeoPartObject().HasCustomName() ) + FieldNameText = Field->GetHoudiniGeoPartObject().PartName; + else if( HoudiniGeoPartObject.HasCustomName() ) + FieldNameText = HoudiniGeoPartObject.PartName; + else + FieldNameText = Field->GetHoudiniGeoPartObject().GetNodePath(); + } + else if ( Flags.bAttributeInstancerOverride ) + { + if ( HoudiniGeoPartObject.HasCustomName() ) + FieldNameText = HoudiniGeoPartObject.PartName; + else + FieldNameText = HoudiniGeoPartObject.GetNodePath() + TEXT( "/Override_" ) + FString::FromInt( FieldIdx ); + } + else + { + // For object-instancers we use the instancer's name as well + if (HoudiniGeoPartObject.HasCustomName()) + FieldNameText = HoudiniGeoPartObject.PartName; + else + FieldNameText = HoudiniGeoPartObject.GetNodePath() + TEXT( "/" ) + Field->GetHoudiniGeoPartObject().ObjectName; + } + + if ( Field->InstanceVariationCount() > 1 ) + FieldNameText += FString::Printf( TEXT( " [%d]" ), VariationIdx ); + + return FieldNameText; +} + +#endif + +void +UHoudiniAssetInstanceInput::BeginDestroy() +{ + for ( int32 Idx = 0; Idx < InstanceInputFields.Num(); ++Idx ) + InstanceInputFields[ Idx ]->ConditionalBeginDestroy(); + + InstanceInputFields.Empty(); + + Super::BeginDestroy(); +} + +void +UHoudiniAssetInstanceInput::SetHoudiniAssetComponent( UHoudiniAssetComponent * InComponent ) +{ + UHoudiniAssetParameter::SetHoudiniAssetComponent( InComponent ); + + for ( int32 Idx = 0; Idx < InstanceInputFields.Num(); ++Idx ) + { + InstanceInputFields[ Idx ]->HoudiniAssetComponent = InComponent; + InstanceInputFields[ Idx ]->HoudiniAssetInstanceInput = this; + } +} + +void +UHoudiniAssetInstanceInput::Serialize( FArchive & Ar ) +{ + // Call base implementation. + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + Ar << Flags.HoudiniAssetInstanceInputFlagsPacked; + Ar << HoudiniGeoPartObject; + + Ar << ObjectToInstanceId; + // Object id is transient + if ( Ar.IsLoading() && !Ar.IsTransacting() ) + ObjectToInstanceId = -1; + + // Serialize fields. + Ar << InstanceInputFields; +} + +void +UHoudiniAssetInstanceInput::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniAssetInstanceInput * HoudiniAssetInstanceInput = Cast< UHoudiniAssetInstanceInput >( InThis ); + if ( HoudiniAssetInstanceInput && !HoudiniAssetInstanceInput->IsPendingKill() ) + { + // Add references to all used fields. + for ( int32 Idx = 0; Idx < HoudiniAssetInstanceInput->InstanceInputFields.Num(); ++Idx ) + { + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField = + HoudiniAssetInstanceInput->InstanceInputFields[ Idx ]; + + if ( HoudiniAssetInstanceInputField && !HoudiniAssetInstanceInputField->IsPendingKill() ) + Collector.AddReferencedObject( HoudiniAssetInstanceInputField, InThis ); + } + } + + // Call base implementation. + Super::AddReferencedObjects( InThis, Collector ); +} + +#if WITH_EDITOR + +void +UHoudiniAssetInstanceInput::CloneComponentsAndAttachToActor( AActor * Actor ) +{ + if (!Actor || Actor->IsPendingKill()) + return; + + USceneComponent * RootComponent = Actor->GetRootComponent(); + TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; + + for ( int32 Idx = 0; Idx < InstanceInputFields.Num(); ++Idx ) + { + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField = InstanceInputFields[ Idx ]; + if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) + continue; + + for ( int32 VariationIdx = 0; + VariationIdx < HoudiniAssetInstanceInputField->InstanceVariationCount(); VariationIdx++ ) + { + UInstancedStaticMeshComponent * ISMC = + Cast( HoudiniAssetInstanceInputField->GetInstancedComponent( VariationIdx ) ); + UHoudiniMeshSplitInstancerComponent * MSIC = + Cast( HoudiniAssetInstanceInputField->GetInstancedComponent( VariationIdx ) ); + + if ( ( !ISMC || ISMC->IsPendingKill() ) && ( !MSIC || MSIC->IsPendingKill() ) ) + { + UHoudiniInstancedActorComponent* IAC = Cast(HoudiniAssetInstanceInputField->GetInstancedComponent(VariationIdx)); + if ( !IAC || IAC->IsPendingKill() || !IAC->InstancedAsset ) + continue; + + UClass* ObjectClass = IAC->InstancedAsset->GetClass(); + if ( !ObjectClass || ObjectClass->IsPendingKill() ) + continue; + + TSubclassOf ActorClass; + if( ObjectClass->IsChildOf() ) + { + ActorClass = ObjectClass; + } + else if( ObjectClass->IsChildOf() ) + { + UBlueprint* BlueprintObj = StaticCast( IAC->InstancedAsset ); + if ( BlueprintObj && !BlueprintObj->IsPendingKill() ) + ActorClass = *BlueprintObj->GeneratedClass; + } + + if( *ActorClass ) + { + for( AActor* InstancedActor : IAC->Instances ) + { + if( !InstancedActor || InstancedActor->IsPendingKill() ) + continue; + + UChildActorComponent* CAC = NewObject< UChildActorComponent >( Actor, UChildActorComponent::StaticClass(), NAME_None, RF_Public ); + if ( !CAC || CAC->IsPendingKill() ) + continue; + + Actor->AddInstanceComponent( CAC ); + + CAC->SetChildActorClass( ActorClass ); + CAC->RegisterComponent(); + CAC->SetWorldTransform( InstancedActor->GetTransform() ); + if ( RootComponent && !RootComponent->IsPendingKill() ) + CAC->AttachToComponent( RootComponent, FAttachmentTransformRules::KeepWorldTransform ); + } + } + else if( ObjectClass->IsChildOf() ) + { + for( AActor* InstancedActor : IAC->Instances ) + { + if( InstancedActor && !InstancedActor->IsPendingKill() ) + { + UParticleSystemComponent* PSC = NewObject< UParticleSystemComponent >( Actor, UParticleSystemComponent::StaticClass(), NAME_None, RF_Public ); + if ( !PSC || PSC->IsPendingKill() ) + continue; + + Actor->AddInstanceComponent( PSC ); + PSC->SetTemplate( StaticCast( IAC->InstancedAsset ) ); + PSC->RegisterComponent(); + PSC->SetWorldTransform( InstancedActor->GetTransform() ); + + if ( RootComponent && !RootComponent->IsPendingKill() ) + PSC->AttachToComponent( RootComponent, FAttachmentTransformRules::KeepWorldTransform ); + } + } + } + else if( ObjectClass->IsChildOf() ) + { + for( AActor* InstancedActor : IAC->Instances ) + { + if( InstancedActor && !InstancedActor->IsPendingKill() ) + { + UAudioComponent* AC = NewObject< UAudioComponent >( Actor, UAudioComponent::StaticClass(), NAME_None, RF_Public ); + if ( !AC || AC->IsPendingKill() ) + continue; + + Actor->AddInstanceComponent( AC ); + AC->SetSound( StaticCast( IAC->InstancedAsset ) ); + AC->RegisterComponent(); + AC->SetWorldTransform( InstancedActor->GetTransform() ); + + if ( RootComponent && !RootComponent->IsPendingKill() ) + AC->AttachToComponent( RootComponent, FAttachmentTransformRules::KeepWorldTransform ); + } + } + } + else + { + // Oh no, the asset is not something we know. We will need to handle each asset type case by case. + // for example we could create a bunch of ParticleSystemComponent if given an emitter asset + HOUDINI_LOG_ERROR( TEXT( "Can not bake instanced actor component for asset type %s" ), *ObjectClass->GetName() ); + } + } + + // If original static mesh is used, then we need to bake it (once) and use that one instead. + UStaticMesh* OutStaticMesh = Cast( HoudiniAssetInstanceInputField->GetInstanceVariation( VariationIdx ) ); + if ( HoudiniAssetInstanceInputField->IsOriginalObjectUsed( VariationIdx ) ) + { + UStaticMesh* OriginalSM = Cast(HoudiniAssetInstanceInputField->GetOriginalObject()); + if( OriginalSM && !OriginalSM->IsPendingKill() ) + { + auto Comp = GetHoudiniAssetComponent(); + if ( Comp && !Comp->IsPendingKill() ) + { + auto&& ItemGeoPartObject = Comp->LocateGeoPartObject(OutStaticMesh); + FHoudiniEngineBakeUtils::CheckedBakeStaticMesh(Comp, OriginalToBakedMesh, ItemGeoPartObject, OriginalSM); + OutStaticMesh = OriginalToBakedMesh[OutStaticMesh]; + } + } + } + + if( ISMC && !ISMC->IsPendingKill() ) + { + // Do we need a Hierarchical ISMC ? + UHierarchicalInstancedStaticMeshComponent * HISMC = + Cast( HoudiniAssetInstanceInputField->GetInstancedComponent( VariationIdx ) ); + + UInstancedStaticMeshComponent* DuplicatedComponent = nullptr; + if ( HISMC && !HISMC->IsPendingKill() ) + DuplicatedComponent = NewObject< UHierarchicalInstancedStaticMeshComponent >( + Actor, UHierarchicalInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Public ); + else + DuplicatedComponent = NewObject< UInstancedStaticMeshComponent >( + Actor, UInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Public ); + + if (DuplicatedComponent && !DuplicatedComponent->IsPendingKill()) + { + Actor->AddInstanceComponent(DuplicatedComponent); + DuplicatedComponent->SetStaticMesh(OutStaticMesh); + + // Reapply the uproperties modified by attributes on the duplicated component + FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject(DuplicatedComponent, HoudiniGeoPartObject); + + TArray ProcessedTransforms; + HoudiniAssetInstanceInputField->GetProcessedTransforms(ProcessedTransforms, VariationIdx); + + // Set component instances. + UHoudiniInstancedActorComponent::UpdateInstancerComponentInstances( + DuplicatedComponent, ProcessedTransforms, HoudiniAssetInstanceInputField->GetInstancedColors( VariationIdx ) ); + + // Copy visibility. + DuplicatedComponent->SetVisibility(ISMC->IsVisible()); + + if ( RootComponent && !RootComponent->IsPendingKill() ) + DuplicatedComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + + DuplicatedComponent->RegisterComponent(); + DuplicatedComponent->GetBodyInstance()->bAutoWeld = false; + } + } + else if ( MSIC && !MSIC->IsPendingKill() ) + { + for( UStaticMeshComponent* OtherSMC : MSIC->GetInstances() ) + { + if ( !OtherSMC || OtherSMC->IsPendingKill() ) + continue; + + FString CompName = OtherSMC->GetName(); + CompName.ReplaceInline( TEXT("StaticMeshComponent"), TEXT("StaticMesh") ); + + UStaticMeshComponent* NewSMC = DuplicateObject< UStaticMeshComponent >(OtherSMC, Actor, *CompName); + if( NewSMC && !NewSMC->IsPendingKill() ) + { + if ( RootComponent && !RootComponent->IsPendingKill() ) + NewSMC->SetupAttachment( RootComponent ); + + NewSMC->SetStaticMesh( OutStaticMesh ); + Actor->AddInstanceComponent( NewSMC ); + NewSMC->SetWorldTransform( OtherSMC->GetComponentTransform() ); + NewSMC->RegisterComponent(); + } + } + } + } + } +} + +#endif + +void +UHoudiniAssetInstanceInput::GetPathInstaceTransforms( + const FString & ObjectInstancePath, + const TArray< FString > & PointInstanceValues, const TArray< FTransform > & Transforms, + TArray< FTransform > & OutTransforms) +{ + OutTransforms.Empty(); + + for ( int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx ) + { + if ( ObjectInstancePath.Equals( PointInstanceValues[ Idx ] ) ) + OutTransforms.Add( Transforms[ Idx ] ); + } +} + +#if WITH_EDITOR + +void +UHoudiniAssetInstanceInput::OnStaticMeshDropped( + UObject * InObject, + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, + int32 Idx, int32 VariationIdx ) +{ + if ( !InObject || InObject->IsPendingKill() ) + return; + + ULevel* CurrentLevel = GWorld->GetCurrentLevel(); + ULevel* MyLevel = GetHoudiniAssetComponent()->GetOwner()->GetLevel(); + + if( MyLevel != CurrentLevel ) + { + HOUDINI_LOG_ERROR( TEXT( "%s: Could not spawn instanced actor because the actor's level is not the Current Level." ), *GetHoudiniAssetComponent()->GetOwner()->GetName() ); + FHoudiniEngineUtils::CreateSlateNotification( TEXT( "Could not spawn instanced actor because the actor's level is not the Current Level." ) ); + + return; + } + + UObject * UsedObj = HoudiniAssetInstanceInputField->GetInstanceVariation( VariationIdx ); + if ( UsedObj != InObject ) + { + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInstanceInputChange", "Houdini Instance Input Change" ), + PrimaryObject ); + HoudiniAssetInstanceInputField->Modify(); + + HoudiniAssetInstanceInputField->ReplaceInstanceVariation( InObject, VariationIdx ); + + OnParamStateChanged(); + } +} + +FReply +UHoudiniAssetInstanceInput::OnThumbnailDoubleClick( + const FGeometry & InMyGeometry, const FPointerEvent & InMouseEvent, UObject * Object ) +{ + if ( Object && GEditor ) + GEditor->EditObject( Object ); + + return FReply::Handled(); +} + +void UHoudiniAssetInstanceInput::OnInstancedObjectBrowse( UObject* InstancedObject ) +{ + if ( GEditor ) + { + TArray< UObject * > Objects; + Objects.Add( InstancedObject ); + GEditor->SyncBrowserToObjects( Objects ); + } +} + +FReply +UHoudiniAssetInstanceInput::OnResetStaticMeshClicked( + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, + int32 Idx, int32 VariationIdx ) +{ + UObject * Obj = HoudiniAssetInstanceInputField->GetOriginalObject(); + OnStaticMeshDropped( Obj, HoudiniAssetInstanceInputField, Idx, VariationIdx ); + + return FReply::Handled(); +} + +void +UHoudiniAssetInstanceInput::CloseStaticMeshComboButton( + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, + int32 Idx, int32 VariationIdx ) +{ + // Do nothing. +} + +void +UHoudiniAssetInstanceInput::ChangedStaticMeshComboButton( + bool bOpened, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 Idx, int32 VariationIdx ) +{ + if ( !bOpened ) + { + // If combo button has been closed, update the UI. + OnParamStateChanged(); + } +} + +TOptional< float > +UHoudiniAssetInstanceInput::GetRotationRoll( + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ) const +{ + const FRotator & Rotator = HoudiniAssetInstanceInputField->GetRotationOffset( VariationIdx ); + return Rotator.Roll; +} + +TOptional< float > +UHoudiniAssetInstanceInput::GetRotationPitch( + UHoudiniAssetInstanceInputField* HoudiniAssetInstanceInputField, int32 VariationIdx ) const +{ + const FRotator & Rotator = HoudiniAssetInstanceInputField->GetRotationOffset( VariationIdx ); + return Rotator.Pitch; +} + +TOptional< float > +UHoudiniAssetInstanceInput::GetRotationYaw( + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ) const +{ + const FRotator& Rotator = HoudiniAssetInstanceInputField->GetRotationOffset( VariationIdx ); + return Rotator.Yaw; +} + +void +UHoudiniAssetInstanceInput::SetRotationRoll( + float Value, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ) +{ + if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInstanceInputChange", "Houdini Instance Input Change" ), + PrimaryObject ); + HoudiniAssetInstanceInputField->Modify(); + + FRotator Rotator = HoudiniAssetInstanceInputField->GetRotationOffset( VariationIdx ); + Rotator.Roll = Value; + HoudiniAssetInstanceInputField->SetRotationOffset( Rotator, VariationIdx ); + HoudiniAssetInstanceInputField->UpdateInstanceTransforms( false ); +} + +void +UHoudiniAssetInstanceInput::SetRotationPitch( + float Value, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ) +{ + if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInstanceInputChange", "Houdini Instance Input Change" ), + PrimaryObject ); + HoudiniAssetInstanceInputField->Modify(); + + FRotator Rotator = HoudiniAssetInstanceInputField->GetRotationOffset( VariationIdx ); + Rotator.Pitch = Value; + HoudiniAssetInstanceInputField->SetRotationOffset( Rotator, VariationIdx ); + HoudiniAssetInstanceInputField->UpdateInstanceTransforms( false ); +} + +void +UHoudiniAssetInstanceInput::SetRotationYaw( + float Value, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ) +{ + if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInstanceInputChange", "Houdini Instance Input Change" ), + PrimaryObject ); + HoudiniAssetInstanceInputField->Modify(); + + FRotator Rotator = HoudiniAssetInstanceInputField->GetRotationOffset( VariationIdx ); + Rotator.Yaw = Value; + HoudiniAssetInstanceInputField->SetRotationOffset( Rotator, VariationIdx ); + HoudiniAssetInstanceInputField->UpdateInstanceTransforms( false ); +} + +TOptional< float > +UHoudiniAssetInstanceInput::GetScaleX( + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ) const +{ + if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) + return 1.0f; + + const FVector & Scale3D = HoudiniAssetInstanceInputField->GetScaleOffset( VariationIdx ); + return Scale3D.X; +} + +TOptional< float > +UHoudiniAssetInstanceInput::GetScaleY( + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ) const +{ + if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) + return 1.0f; + + const FVector & Scale3D = HoudiniAssetInstanceInputField->GetScaleOffset( VariationIdx ); + return Scale3D.Y; +} + +TOptional< float > +UHoudiniAssetInstanceInput::GetScaleZ( + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ) const +{ + if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) + return 1.0f; + + const FVector & Scale3D = HoudiniAssetInstanceInputField->GetScaleOffset( VariationIdx ); + return Scale3D.Z; +} + +void +UHoudiniAssetInstanceInput::SetScaleX( + float Value, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ) +{ + if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInstanceInputChange", "Houdini Instance Input Change" ), + PrimaryObject ); + HoudiniAssetInstanceInputField->Modify(); + + FVector Scale3D = HoudiniAssetInstanceInputField->GetScaleOffset( VariationIdx ); + Scale3D.X = Value; + + if ( HoudiniAssetInstanceInputField->AreOffsetsScaledLinearly( VariationIdx ) ) + { + Scale3D.Y = Value; + Scale3D.Z = Value; + } + + HoudiniAssetInstanceInputField->SetScaleOffset( Scale3D, VariationIdx ); + HoudiniAssetInstanceInputField->UpdateInstanceTransforms( false ); +} + +void +UHoudiniAssetInstanceInput::SetScaleY( + float Value, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx) +{ + if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInstanceInputChange", "Houdini Instance Input Change" ), + PrimaryObject ); + HoudiniAssetInstanceInputField->Modify(); + + FVector Scale3D = HoudiniAssetInstanceInputField->GetScaleOffset( VariationIdx ); + Scale3D.Y = Value; + + if ( HoudiniAssetInstanceInputField->AreOffsetsScaledLinearly( VariationIdx ) ) + { + Scale3D.X = Value; + Scale3D.Z = Value; + } + + HoudiniAssetInstanceInputField->SetScaleOffset( Scale3D, VariationIdx ); + HoudiniAssetInstanceInputField->UpdateInstanceTransforms( false ); +} + +void +UHoudiniAssetInstanceInput::SetScaleZ( + float Value, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx) +{ + if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInstanceInputChange", "Houdini Instance Input Change" ), + PrimaryObject ); + HoudiniAssetInstanceInputField->Modify(); + + FVector Scale3D = HoudiniAssetInstanceInputField->GetScaleOffset( VariationIdx ); + Scale3D.Z = Value; + + if ( HoudiniAssetInstanceInputField->AreOffsetsScaledLinearly( VariationIdx ) ) + { + Scale3D.Y = Value; + Scale3D.X = Value; + } + + HoudiniAssetInstanceInputField->SetScaleOffset( Scale3D, VariationIdx ); + HoudiniAssetInstanceInputField->UpdateInstanceTransforms( false ); +} + +void +UHoudiniAssetInstanceInput::CheckStateChanged( + bool IsChecked, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ) +{ + if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) + return; + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniInstanceInputChange", "Houdini Instance Input Change" ), + PrimaryObject ); + HoudiniAssetInstanceInputField->Modify(); + + HoudiniAssetInstanceInputField->SetLinearOffsetScale( IsChecked, VariationIdx ); +} + +#endif + +bool +UHoudiniAssetInstanceInput::CollectAllInstancedStaticMeshComponents( + TArray< UInstancedStaticMeshComponent * > & Components, const UStaticMesh * StaticMesh ) +{ + bool bCollected = false; + for ( int32 Idx = 0; Idx < InstanceInputFields.Num(); ++Idx ) + { + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField = InstanceInputFields[ Idx ]; + if ( HoudiniAssetInstanceInputField && !HoudiniAssetInstanceInputField->IsPendingKill() ) + { + UStaticMesh * OriginalStaticMesh = Cast< UStaticMesh >( HoudiniAssetInstanceInputField->GetOriginalObject() ); + if ( OriginalStaticMesh == StaticMesh ) + { + for ( int32 IdxMesh = 0; IdxMesh < HoudiniAssetInstanceInputField->InstancedObjects.Num(); ++IdxMesh ) + { + UStaticMesh * UsedStaticMesh = Cast< UStaticMesh >( HoudiniAssetInstanceInputField->InstancedObjects[ IdxMesh ] ); + if ( UsedStaticMesh == StaticMesh ) + { + if ( !HoudiniAssetInstanceInputField->InstancerComponents.IsValidIndex(IdxMesh) ) + continue; + + UInstancedStaticMeshComponent* ISMC = Cast< UInstancedStaticMeshComponent >( HoudiniAssetInstanceInputField->InstancerComponents[ IdxMesh ] ); + if ( !ISMC || ISMC->IsPendingKill() ) + continue; + + Components.Add( ISMC ); + bCollected = true; + } + } + } + } + } + + return bCollected; +} + +bool +UHoudiniAssetInstanceInput::GetMaterialReplacementMeshes( + UMaterialInterface * Material, + TMap< UStaticMesh *, int32 > & MaterialReplacementsMap ) +{ + bool bResult = false; + + for ( int32 Idx = 0; Idx < InstanceInputFields.Num(); ++Idx ) + { + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField = InstanceInputFields[ Idx ]; + if ( HoudiniAssetInstanceInputField && !HoudiniAssetInstanceInputField->IsPendingKill() ) + bResult |= HoudiniAssetInstanceInputField->GetMaterialReplacementMeshes( Material, MaterialReplacementsMap ); + } + + return bResult; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInstanceInput.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInstanceInput.h new file mode 100644 index 00000000..10768e95 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInstanceInput.h @@ -0,0 +1,267 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniGeoPartObject.h" +#include "HoudiniAssetParameter.h" +#include "Input/Reply.h" +#include "Input/Events.h" +#include "Layout/Geometry.h" +#include "HoudiniAssetInstanceInput.generated.h" + + +class AActor; +class UStaticMesh; +class UMaterialInterface; +class UInstancedStaticMeshComponent; +class UHoudiniAssetInstanceInputField; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetInstanceInput : public UHoudiniAssetParameter +{ + friend class UHoudiniAssetInstanceInputField; + + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + /** Create instance of this class. **/ + static UHoudiniAssetInstanceInput * Create( + UHoudiniAssetComponent * InPrimaryObject, + const FHoudiniGeoPartObject & HoudiniGeoPartObject ); + + /** Create instance from another input. **/ + static UHoudiniAssetInstanceInput * Create( + UHoudiniAssetComponent * InPrimaryObject, + const UHoudiniAssetInstanceInput * OtherInstanceInput ); + + public: + + /** Create this instance input. **/ + bool CreateInstanceInput(); + + /** Recreates render states for used instanced static mesh components. **/ + void RecreateRenderStates(); + + /** Recreates physics states for used instanced static mesh components. **/ + void RecreatePhysicsStates(); + + /** Retrieve all instanced mesh components used by this input. **/ + bool CollectAllInstancedStaticMeshComponents( + TArray< UInstancedStaticMeshComponent * > & Components, + const UStaticMesh * StaticMesh ); + + /** Get material replacement meshes for a given input. **/ + bool GetMaterialReplacementMeshes( + UMaterialInterface * Material, + TMap< UStaticMesh *, int32 > & MaterialReplacementsMap ); + + FORCEINLINE const TArray< UHoudiniAssetInstanceInputField * >& GetInstanceInputFields() const { return InstanceInputFields; } + + FORCEINLINE const FHoudiniGeoPartObject& GetGeoPartObject() const { return HoudiniGeoPartObject; } + + /** Refresh state based on the given geo part object */ + void SetGeoPartObject( const FHoudiniGeoPartObject& InGeoPartObject ); + + /** UHoudiniAssetParameter methods. **/ + public: + + /** Create this parameter from HAPI information - this implementation does nothing as this is not **/ + /** a true parameter. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ); + + /** Set component for this parameter. **/ + virtual void SetHoudiniAssetComponent( UHoudiniAssetComponent * InComponent ) override; + + /** UObject methods. **/ + public: + + virtual void BeginDestroy() override; + virtual void Serialize( FArchive & Ar ) override; + static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); + +#if WITH_EDITOR + + /** Clone all used instance static mesh components and and attach them to provided actor. **/ + void CloneComponentsAndAttachToActor( AActor * Actor ); + +#endif + + protected: + + /** Retrieve all transforms for a given path. Used by attribute instancer. **/ + void GetPathInstaceTransforms( + const FString & ObjectInstancePath, const TArray< FString > & PointInstanceValues, + const TArray< FTransform > & Transforms, TArray< FTransform > & OutTransforms ); + + protected: + + /** Locate field which matches given criteria. Return null if not found. **/ + UHoudiniAssetInstanceInputField * LocateInputField( + const FHoudiniGeoPartObject & GeoPartObject ); + + /** Locate or create (if it does not exist) an input field. **/ + void CreateInstanceInputField( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + const TArray< FTransform > & ObjectTransforms, + const TArray< UHoudiniAssetInstanceInputField * > & OldInstanceInputFields, + TArray< UHoudiniAssetInstanceInputField * > & NewInstanceInputFields ); + + /** Locate or create (if it does not exist) an input field. This version is used with override attribute. **/ + void CreateInstanceInputField( + UObject * InstancedObject, const TArray< FTransform > & ObjectTransforms, + const TArray< UHoudiniAssetInstanceInputField * > & OldInstanceInputFields, + TArray< UHoudiniAssetInstanceInputField * > & NewInstanceInputFields ); + + /** Clean unused input fields. **/ + void CleanInstanceInputFields( TArray< UHoudiniAssetInstanceInputField * > & InInstanceInputFields ); + + /** Returns primary object cast to HoudiniAssetComponent */ + class UHoudiniAssetComponent* GetHoudiniAssetComponent(); + const class UHoudiniAssetComponent* GetHoudiniAssetComponent() const; + + /** Get the node id of the asset we are associated with */ + HAPI_NodeId GetAssetId() const; + + protected: + +#if WITH_EDITOR + + /** Delegate used when static mesh has been drag and dropped. **/ + void OnStaticMeshDropped( + UObject * InObject, + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, + int32 Idx, int32 VariationIdx ); + + /** Handler for when static mesh thumbnail is double clicked. We open editor in this case. **/ + FReply OnThumbnailDoubleClick( + const FGeometry & InMyGeometry, const FPointerEvent & InMouseEvent, UObject * Object ); + + /** Closes the combo button. **/ + void CloseStaticMeshComboButton( + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 Idx, + int32 VariationIdx ); + + /** Triggered when combo button is opened or closed. **/ + void ChangedStaticMeshComboButton( + bool bOpened, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, + int32 Idx, int32 VariationIdx ); + + /** Browse to static mesh. **/ + void OnInstancedObjectBrowse( UObject* InstancedObject ); + + /** Handler for reset static mesh button. **/ + FReply OnResetStaticMeshClicked( + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 Idx, + int32 VariationIdx ); + + /** Handler for adding instance variation **/ + void OnAddInstanceVariation( UHoudiniAssetInstanceInputField * InstanceInputField, int32 Index ); + + /** Handler for removing instance variation **/ + void OnRemoveInstanceVariation( UHoudiniAssetInstanceInputField * InstanceInputField, int32 Index ); + + /** Helper for field label */ + FString GetFieldLabel( int32 FieldIdx, int32 VariationIdx ) const; + + /** Get rotation components for given index. **/ + TOptional< float > GetRotationRoll( + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, + int32 VariationIdx ) const; + TOptional< float > GetRotationPitch( + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, + int32 VariationIdx ) const; + TOptional< float > GetRotationYaw( + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, + int32 VariationIdx ) const; + + /** Set rotation components for given index. **/ + void SetRotationRoll( + float Value, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ); + void SetRotationPitch( + float Value, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ); + void SetRotationYaw( + float Value, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ); + + /** Get scale components for a given index. **/ + TOptional< float > GetScaleX( + UHoudiniAssetInstanceInputField* HoudiniAssetInstanceInputField, int32 VariationIdx ) const; + TOptional< float > GetScaleY( + UHoudiniAssetInstanceInputField* HoudiniAssetInstanceInputField, int32 VariationIdx ) const; + TOptional< float > GetScaleZ( + UHoudiniAssetInstanceInputField* HoudiniAssetInstanceInputField, int32 VariationIdx ) const; + + /** Set scale components for a given index. **/ + void SetScaleX( float Value, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ); + void SetScaleY( float Value, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ); + void SetScaleZ( float Value, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, int32 VariationIdx ); + + /** Set option for whether scale should be linear. **/ + void CheckStateChanged( + bool IsChecked, UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField, + int32 VariationIdx ); + +#endif + + protected: + + /** List of fields created by this instance input. **/ + TArray< UHoudiniAssetInstanceInputField * > InstanceInputFields; + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject HoudiniGeoPartObject; + + /** Id of an object to instance. **/ + HAPI_NodeId ObjectToInstanceId; + +public: + /** Flags used by this input. **/ + union FHoudiniAssetInstanceInputFlags + { + struct + { + /** Set to true if this is an attribute instancer. **/ + uint32 bIsAttributeInstancer : 1; + + /** Set to true if this attribute instancer uses overrides. **/ + uint32 bAttributeInstancerOverride : 1; + + /** Set to true if this is a packed primitive instancer **/ + uint32 bIsPackedPrimitiveInstancer : 1; + + /** Set to true if this is a split mesh instancer */ + uint32 bIsSplitMeshInstancer : 1; + }; + + uint32 HoudiniAssetInstanceInputFlagsPacked; + }; + FHoudiniAssetInstanceInputFlags Flags; + + static FHoudiniAssetInstanceInputFlags GetInstancerFlags(const FHoudiniGeoPartObject & InHoudiniGeoPartObject); +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInstanceInputField.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInstanceInputField.cpp new file mode 100644 index 00000000..e6722683 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInstanceInputField.cpp @@ -0,0 +1,861 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetInstanceInputField.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" + +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" + + +// Fastrand is a faster alternative to std::rand() +// and doesn't oscillate when looking for 2 values like Unreal's. +inline int fastrand(int& nSeed) +{ + nSeed = (214013 * nSeed + 2531011); + return (nSeed >> 16) & 0x7FFF; +} + +bool +FHoudiniAssetInstanceInputFieldSortPredicate::operator()( + const UHoudiniAssetInstanceInputField & A, + const UHoudiniAssetInstanceInputField & B ) const +{ + FHoudiniGeoPartObjectSortPredicate HoudiniGeoPartObjectSortPredicate; + return HoudiniGeoPartObjectSortPredicate( A.GetHoudiniGeoPartObject(), B.GetHoudiniGeoPartObject() ); +} + +UHoudiniAssetInstanceInputField::UHoudiniAssetInstanceInputField( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) + , OriginalObject( nullptr ) + , HoudiniAssetComponent( nullptr ) + , HoudiniAssetInstanceInput( nullptr ) + , HoudiniAssetInstanceInputFieldFlagsPacked( 0 ) +{} + +UHoudiniAssetInstanceInputField * +UHoudiniAssetInstanceInputField::Create( + UObject * HoudiniAssetComponent, + UHoudiniAssetInstanceInput * InHoudiniAssetInstanceInput, + const FHoudiniGeoPartObject & HoudiniGeoPartObject ) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return nullptr; + + UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField = + NewObject< UHoudiniAssetInstanceInputField >( + HoudiniAssetComponent, + UHoudiniAssetInstanceInputField::StaticClass(), + NAME_None, + RF_Public | RF_Transactional ); + + HoudiniAssetInstanceInputField->HoudiniGeoPartObject = HoudiniGeoPartObject; + HoudiniAssetInstanceInputField->HoudiniAssetComponent = HoudiniAssetComponent; + HoudiniAssetInstanceInputField->HoudiniAssetInstanceInput = InHoudiniAssetInstanceInput; + + return HoudiniAssetInstanceInputField; +} + +UHoudiniAssetInstanceInputField * +UHoudiniAssetInstanceInputField::Create( + UObject * InPrimaryObject, + const UHoudiniAssetInstanceInputField * OtherInputField ) +{ + UHoudiniAssetInstanceInputField * InputField = DuplicateObject< UHoudiniAssetInstanceInputField >( OtherInputField, InPrimaryObject ); + + InputField->HoudiniAssetComponent = InPrimaryObject; + + InputField->InstancerComponents.Empty(); + + // Duplicate the given field's InstancedStaticMesh components + if( USceneComponent* InRootComp = Cast( InPrimaryObject ) ) + { + for( const USceneComponent* OtherISMC : OtherInputField->InstancerComponents ) + { + USceneComponent* NewISMC = DuplicateObject< USceneComponent >( OtherISMC, InRootComp ); + NewISMC->RegisterComponent(); + NewISMC->AttachToComponent( InRootComp, FAttachmentTransformRules::KeepRelativeTransform ); + InputField->InstancerComponents.Add( NewISMC ); + } + } + + return InputField; +} + +void +UHoudiniAssetInstanceInputField::Serialize( FArchive & Ar ) +{ + // Call base implementation first. + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + const int32 InstanceInputFieldVersion = Ar.CustomVer( FHoudiniCustomSerializationVersion::GUID ); + + Ar << HoudiniAssetInstanceInputFieldFlagsPacked; + Ar << HoudiniGeoPartObject; + + FString UnusedInstancePathName; + Ar << UnusedInstancePathName; + Ar << RotationOffsets; + Ar << ScaleOffsets; + Ar << bScaleOffsetsLinearlyArray; + + Ar << InstancedTransforms; + Ar << VariationTransformsArray; + + if ( Ar.IsSaving() || ( Ar.IsLoading() && InstanceInputFieldVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS ) ) + { + Ar << InstanceColorOverride; + Ar << VariationInstanceColorOverrideArray; + } + + Ar << InstancerComponents; + Ar << InstancedObjects; + Ar << OriginalObject; +} + +void +UHoudiniAssetInstanceInputField::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniAssetInstanceInputField * ThisHAIF = Cast< UHoudiniAssetInstanceInputField >( InThis ); + if ( ThisHAIF && !ThisHAIF->IsPendingKill() ) + { + if ( ThisHAIF->OriginalObject && !ThisHAIF->OriginalObject->IsPendingKill() ) + Collector.AddReferencedObject(ThisHAIF->OriginalObject, ThisHAIF); + + Collector.AddReferencedObjects(ThisHAIF->InstancedObjects, ThisHAIF); + Collector.AddReferencedObjects(ThisHAIF->InstancerComponents, ThisHAIF); + } + + // Call base implementation. + Super::AddReferencedObjects( InThis, Collector ); +} + +void +UHoudiniAssetInstanceInputField::BeginDestroy() +{ + for ( USceneComponent* Comp : InstancerComponents ) + { + if ( Comp ) + { + Comp->UnregisterComponent(); + Comp->DetachFromComponent( FDetachmentTransformRules::KeepRelativeTransform ); + Comp->DestroyComponent(); + } + } + + Super::BeginDestroy(); +} + +#if WITH_EDITOR + +void +UHoudiniAssetInstanceInputField::PostEditUndo() +{ + Super::PostEditUndo(); + + int32 VariationCount = InstanceVariationCount(); + for ( int32 Idx = 0; Idx < VariationCount; Idx++ ) + { + if ( ensure( InstancedObjects.IsValidIndex( Idx ) ) && ensure( InstancerComponents.IsValidIndex( Idx ) ) ) + { + UStaticMesh* StaticMesh = Cast(InstancedObjects[Idx]); + if ( StaticMesh && !StaticMesh->IsPendingKill()) + { + UInstancedStaticMeshComponent* ISMC = Cast(InstancerComponents[Idx]); + if ( ISMC && !ISMC->IsPendingKill() ) + { + ISMC->SetStaticMesh( StaticMesh ); + } + else + { + UHoudiniMeshSplitInstancerComponent* MSIC = Cast(InstancerComponents[Idx]); + if ( MSIC && !MSIC->IsPendingKill() ) + MSIC->SetStaticMesh( StaticMesh ); + } + } + else + { + UHoudiniInstancedActorComponent* IAC = Cast(InstancerComponents[Idx]); + if ( IAC && !IAC->IsPendingKill() ) + { + IAC->InstancedAsset = InstancedObjects[ Idx ]; + } + } + } + } + + UpdateInstanceTransforms( true ); + + UHoudiniAssetComponent* HAC = Cast(HoudiniAssetComponent); + if ( HAC && !HAC->IsPendingKill() ) + HAC->UpdateEditorProperties( false ); + + UpdateInstanceUPropertyAttributes(); +} + +#endif // WITH_EDITOR + +void +UHoudiniAssetInstanceInputField::AddInstanceComponent( int32 VariationIdx ) +{ + if ( !InstancedObjects.IsValidIndex( VariationIdx ) ) + return; + + UHoudiniAssetComponent* Comp = Cast( HoudiniAssetComponent ); + if( !Comp || Comp->IsPendingKill() ) + return; + + USceneComponent* RootComp = Comp; + if (!RootComp->GetOwner() || RootComp->GetOwner()->IsPendingKill()) + return; + + // Check if instancer material is available. + const FHoudiniGeoPartObject & InstancerHoudiniGeoPartObject = HoudiniAssetInstanceInput->HoudiniGeoPartObject; + + UStaticMesh * StaticMesh = Cast(InstancedObjects[VariationIdx]); + if ( StaticMesh && !StaticMesh->IsPendingKill() ) + { + UMaterialInterface * InstancerMaterial = nullptr; + + // We check attribute material first. + if( InstancerHoudiniGeoPartObject.bInstancerAttributeMaterialAvailable ) + { + InstancerMaterial = Comp->GetAssignmentMaterial( + InstancerHoudiniGeoPartObject.InstancerAttributeMaterialName); + } + + // If attribute material was not found, we check for presence of shop instancer material. + if( !InstancerMaterial && InstancerHoudiniGeoPartObject.bInstancerMaterialAvailable ) + InstancerMaterial = Comp->GetAssignmentMaterial( + InstancerHoudiniGeoPartObject.InstancerMaterialName); + + USceneComponent* NewComp = nullptr; + if( HoudiniAssetInstanceInput->Flags.bIsSplitMeshInstancer ) + { + UHoudiniMeshSplitInstancerComponent* MSIC = NewObject< UHoudiniMeshSplitInstancerComponent >( + RootComp->GetOwner(), UHoudiniMeshSplitInstancerComponent::StaticClass(), + NAME_None, RF_Transactional); + + if ( MSIC && !MSIC->IsPendingKill() ) + { + MSIC->SetStaticMesh(StaticMesh); + MSIC->SetOverrideMaterial(InstancerMaterial); + + // Check for instance colors + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi:: AttributeInfo_Init(&AttributeInfo); + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), InstancerHoudiniGeoPartObject.GeoId, InstancerHoudiniGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_PRIM, &AttributeInfo)) + { + if ( AttributeInfo.exists ) + { + if ( AttributeInfo.tupleSize == 4 ) + { + // Allocate sufficient buffer for data. + InstanceColorOverride.SetNumUninitialized(AttributeInfo.count); + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InstancerHoudiniGeoPartObject.GeoId, InstancerHoudiniGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)InstanceColorOverride.GetData(), 0, AttributeInfo.count)) + { + // got some override colors + } + } + else + { + HOUDINI_LOG_WARNING(TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_COLOR " must be a float[4] prim attribute")); + } + } + } + NewComp = MSIC; + } + } + else + { + UInstancedStaticMeshComponent * InstancedStaticMeshComponent = nullptr; + if ( StaticMesh->GetNumLODs() > 1 ) + { + // If the mesh has LODs, use Hierarchical ISMC + InstancedStaticMeshComponent = NewObject< UHierarchicalInstancedStaticMeshComponent >( + RootComp->GetOwner(), UHierarchicalInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + } + else + { + // If the mesh doesnt have LOD, we can use a regular ISMC + InstancedStaticMeshComponent = NewObject< UInstancedStaticMeshComponent >( + RootComp->GetOwner(),UInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional ); + } + + if ( InstancedStaticMeshComponent && !InstancedStaticMeshComponent->IsPendingKill() ) + { + InstancedStaticMeshComponent->SetStaticMesh(StaticMesh); + InstancedStaticMeshComponent->GetBodyInstance()->bAutoWeld = false; + + // Copy the CollisionTraceFlag from the SM + if ( InstancedStaticMeshComponent->GetBodySetup() && StaticMesh->BodySetup ) + InstancedStaticMeshComponent->GetBodySetup()->CollisionTraceFlag = StaticMesh->BodySetup->CollisionTraceFlag; + + if ( InstancerMaterial && !InstancerMaterial->IsPendingKill() ) + { + InstancedStaticMeshComponent->OverrideMaterials.Empty(); + + int32 MeshMaterialCount = StaticMesh->StaticMaterials.Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + InstancedStaticMeshComponent->SetMaterial(Idx, InstancerMaterial); + } + NewComp = InstancedStaticMeshComponent; + } + } + + if ( NewComp && !NewComp->IsPendingKill() ) + { + NewComp->SetMobility(RootComp->Mobility); + NewComp->AttachToComponent(RootComp, FAttachmentTransformRules::KeepRelativeTransform); + NewComp->RegisterComponent(); + // We want to make this invisible if it's a collision instancer. + NewComp->SetVisibility(!HoudiniGeoPartObject.bIsCollidable); + + InstancerComponents.Insert(NewComp, VariationIdx); + FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject(NewComp, InstancerHoudiniGeoPartObject); + } + } + else + { + // Create the actor instancer component + UHoudiniInstancedActorComponent * InstancedObjectComponent = NewObject< UHoudiniInstancedActorComponent >( + RootComp->GetOwner(), UHoudiniInstancedActorComponent::StaticClass(), NAME_None, RF_Transactional ); + + if ( InstancedObjectComponent && !InstancedObjectComponent->IsPendingKill() ) + { + InstancerComponents.Insert(InstancedObjectComponent, VariationIdx); + InstancedObjectComponent->InstancedAsset = InstancedObjects[VariationIdx]; + InstancedObjectComponent->SetMobility(RootComp->Mobility); + InstancedObjectComponent->AttachToComponent(RootComp, FAttachmentTransformRules::KeepRelativeTransform); + InstancedObjectComponent->RegisterComponent(); + + FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject(InstancedObjectComponent, HoudiniGeoPartObject); + } + } + + UpdateRelativeTransform(); +} + +void +UHoudiniAssetInstanceInputField::SetInstanceTransforms( const TArray< FTransform > & ObjectTransforms ) +{ + InstancedTransforms = ObjectTransforms; + UpdateInstanceTransforms( true ); +} + +void +UHoudiniAssetInstanceInputField::UpdateInstanceTransforms( bool RecomputeVariationAssignments ) +{ + int32 VariationCount = InstanceVariationCount(); + + int nSeed = 1234; + if ( RecomputeVariationAssignments ) + { + VariationTransformsArray.Empty(); + VariationTransformsArray.SetNum(VariationCount); + + VariationInstanceColorOverrideArray.Empty(); + VariationInstanceColorOverrideArray.SetNum(VariationCount); + + for ( int32 Idx = 0; Idx < InstancedTransforms.Num(); Idx++ ) + { + int32 VariationIndex = fastrand(nSeed) % VariationCount; + if ( VariationTransformsArray.IsValidIndex(VariationIndex) ) + VariationTransformsArray[ VariationIndex ].Add(InstancedTransforms[Idx]); + + if( InstanceColorOverride.Num() > Idx ) + { + if ( VariationInstanceColorOverrideArray.IsValidIndex( VariationIndex ) + && InstanceColorOverride.IsValidIndex(Idx) ) + VariationInstanceColorOverrideArray[VariationIndex].Add(InstanceColorOverride[Idx]); + } + } + } + + for ( int32 Idx = 0; Idx < VariationCount; Idx++ ) + { + if ( !InstancerComponents.IsValidIndex( Idx ) + || !VariationTransformsArray.IsValidIndex( Idx ) + || !VariationInstanceColorOverrideArray.IsValidIndex( Idx ) ) + { + // TODO: fix this properly + continue; + } + + TArray ProcessedTransform; + this->GetProcessedTransforms( ProcessedTransform, Idx ); + + UHoudiniInstancedActorComponent::UpdateInstancerComponentInstances( + InstancerComponents[ Idx ], + ProcessedTransform, + VariationInstanceColorOverrideArray[ Idx ] ); + } +} + +void +UHoudiniAssetInstanceInputField::UpdateRelativeTransform() +{ + int32 VariationCount = InstanceVariationCount(); + for ( int32 Idx = 0; Idx < VariationCount; Idx++ ) + InstancerComponents[ Idx ]->SetRelativeTransform( HoudiniGeoPartObject.TransformMatrix ); +} + +void +UHoudiniAssetInstanceInputField::UpdateInstanceUPropertyAttributes() +{ + if ( !HoudiniAssetInstanceInput || HoudiniAssetInstanceInput->IsPendingKill() ) + return; + + // Check if instancer material is available. + const FHoudiniGeoPartObject & InstancerHoudiniGeoPartObject = HoudiniAssetInstanceInput->HoudiniGeoPartObject; + + for ( int32 Idx = 0; Idx < InstancerComponents.Num(); Idx++ ) + FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject(InstancerComponents[ Idx ], InstancerHoudiniGeoPartObject ); +} + +const FHoudiniGeoPartObject & +UHoudiniAssetInstanceInputField::GetHoudiniGeoPartObject() const +{ + return HoudiniGeoPartObject; +} + +void +UHoudiniAssetInstanceInputField::SetGeoPartObject( const FHoudiniGeoPartObject & InHoudiniGeoPartObject ) +{ + HoudiniGeoPartObject = InHoudiniGeoPartObject; +} + +UObject* +UHoudiniAssetInstanceInputField::GetOriginalObject() const +{ + return OriginalObject; +} + +UObject * +UHoudiniAssetInstanceInputField::GetInstanceVariation( int32 VariationIndex ) const +{ + if ( VariationIndex < 0 || VariationIndex >= InstancedObjects.Num() ) + return nullptr; + + UObject * Obj = InstancedObjects[VariationIndex]; + return Obj; +} + +void +UHoudiniAssetInstanceInputField::AddInstanceVariation( UObject * InObject, int32 VariationIdx ) +{ + check( InObject ); + check( HoudiniAssetComponent ); + + if (!InObject || InObject->IsPendingKill()) + return; + + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return; + + InstancedObjects.Insert( InObject, VariationIdx ); + RotationOffsets.Insert( FRotator( 0, 0, 0 ), VariationIdx ); + ScaleOffsets.Insert( FVector( 1, 1, 1 ), VariationIdx ); + bScaleOffsetsLinearlyArray.Insert( true, VariationIdx ); + + AddInstanceComponent( VariationIdx ); + UpdateInstanceTransforms( true ); + UpdateInstanceUPropertyAttributes(); +} + +void +UHoudiniAssetInstanceInputField::RemoveInstanceVariation( int32 VariationIdx ) +{ + check( VariationIdx >= 0 && VariationIdx < InstanceVariationCount() ); + + if ( InstanceVariationCount() == 1 ) + return; + + bool bIsStaticMesh = Cast( InstancedObjects[ VariationIdx ] ) != nullptr; + InstancedObjects.RemoveAt( VariationIdx ); + RotationOffsets.RemoveAt( VariationIdx ); + ScaleOffsets.RemoveAt( VariationIdx ); + bScaleOffsetsLinearlyArray.RemoveAt( VariationIdx ); + + // Remove instanced component. + if ( InstancerComponents.IsValidIndex( VariationIdx ) ) + { + if ( USceneComponent* Comp = InstancerComponents[ VariationIdx ] ) + { + Comp->DestroyComponent(); + } + InstancerComponents.RemoveAt( VariationIdx ); + } + + UpdateInstanceTransforms( true ); +} + +void +UHoudiniAssetInstanceInputField::ReplaceInstanceVariation( UObject * InObject, int Index ) +{ + if ( !InObject || InObject->IsPendingKill() ) + { + HOUDINI_LOG_WARNING( TEXT("ReplaceInstanceVariation: Invalid input object") ); + return; + } + + if ( !InstancedObjects.IsValidIndex(Index) ) + { + HOUDINI_LOG_WARNING(TEXT("ReplaceInstanceVariation: Input index doesnt match valid Instanced Object")); + return; + } + + if ( !InstancerComponents.IsValidIndex( Index ) ) + { + HOUDINI_LOG_WARNING(TEXT("ReplaceInstanceVariation: Input index doesnt match valid Instanced Component")); + return; + } + + if (InstancerComponents.Num() != InstancedObjects.Num()) + { + HOUDINI_LOG_WARNING(TEXT("ReplaceInstanceVariation: Invalid instanced component and objects")); + return; + } + + // Check if the replacing object and the current object are different types (StaticMesh vs. Non) + // if so we need to swap out the component + bool bInIsStaticMesh = InObject->IsA(); + bool bCurrentIsStaticMesh = false; + if ( InstancedObjects[ Index ] && !InstancedObjects[ Index ]->IsPendingKill() ) + bCurrentIsStaticMesh = InstancedObjects[ Index ]->IsA(); + + InstancedObjects[ Index ] = InObject; + + bool bComponentNeedToBeCreated = true; + if ( bInIsStaticMesh == bCurrentIsStaticMesh ) + { + // If the in mesh has LODs, we need a Hierarchical ISMC + UStaticMesh* StaticMesh = Cast< UStaticMesh >( InObject ); + bool bInHasLODs = false; + if ( StaticMesh && !StaticMesh->IsPendingKill() && ( StaticMesh->GetNumLODs() > 1 ) ) + bInHasLODs = true; + + // We'll try to reuse the InstanceComponent + if ( UInstancedStaticMeshComponent* ISMC = Cast( InstancerComponents[ Index ] ) ) + { + // If we have LODs, make sure we the component is a HISM + // If we don't, make sure the component is not a HISM + UHierarchicalInstancedStaticMeshComponent* HISMC = Cast( InstancerComponents[ Index ] ); + if ( !HISMC && bInHasLODs ) + bComponentNeedToBeCreated = true; + else if ( HISMC && !bInHasLODs ) + bComponentNeedToBeCreated = true; + else if ( !ISMC->IsPendingKill() ) + { + ISMC->SetStaticMesh( Cast( InObject ) ); + bComponentNeedToBeCreated = false; + } + } + else if( UHoudiniMeshSplitInstancerComponent* MSPIC = Cast( InstancerComponents[ Index ] ) ) + { + if( !MSPIC->IsPendingKill() ) + { + MSPIC->SetStaticMesh( Cast( InObject ) ); + bComponentNeedToBeCreated = false; + } + } + else if ( UHoudiniInstancedActorComponent* IAC = Cast( InstancerComponents[ Index ] ) ) + { + if ( !IAC->IsPendingKill() ) + { + IAC->InstancedAsset = InObject; + bComponentNeedToBeCreated = false; + } + } + } + + if ( bComponentNeedToBeCreated ) + { + // We'll create a new InstanceComponent + FTransform SavedXform = FTransform::Identity; + + // Delete the old instancer + if ( InstancerComponents.IsValidIndex( Index ) ) + { + USceneComponent* InstancerComponentToRemove = InstancerComponents[Index]; + InstancerComponents.RemoveAt(Index); + if ( InstancerComponentToRemove && !InstancerComponentToRemove->IsPendingKill() ) + { + SavedXform = InstancerComponentToRemove->GetRelativeTransform(); + InstancerComponentToRemove->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + InstancerComponentToRemove->UnregisterComponent(); + InstancerComponentToRemove->DestroyComponent(); + } + } + + // Create a new one and restore the transform + AddInstanceComponent( Index ); + InstancerComponents[ Index ]->SetRelativeTransform( SavedXform ); + } + + UpdateInstanceTransforms( false ); + UpdateInstanceUPropertyAttributes(); +} + +void +UHoudiniAssetInstanceInputField::FindObjectIndices( UObject * InStaticMesh, TArray< int32 > & Indices ) +{ + for ( int32 Idx = 0; Idx < InstancedObjects.Num(); ++Idx ) + { + if ( InstancedObjects[ Idx ] == InStaticMesh ) + Indices.Add( Idx ); + } +} + +int32 +UHoudiniAssetInstanceInputField::InstanceVariationCount() const +{ + return InstancedObjects.Num(); +} + +const FRotator & +UHoudiniAssetInstanceInputField::GetRotationOffset( int32 VariationIdx ) const +{ + if ( RotationOffsets.IsValidIndex( VariationIdx ) ) + return RotationOffsets[ VariationIdx ]; + else + return FRotator::ZeroRotator; +} + +void +UHoudiniAssetInstanceInputField::SetRotationOffset( const FRotator & Rotator, int32 VariationIdx ) +{ + if ( RotationOffsets.IsValidIndex( VariationIdx ) ) + RotationOffsets[ VariationIdx ] = Rotator; +} + +const FVector & +UHoudiniAssetInstanceInputField::GetScaleOffset( int32 VariationIdx ) const +{ + if ( ScaleOffsets.IsValidIndex( VariationIdx ) ) + return ScaleOffsets[ VariationIdx ]; + else + return FVector::OneVector; +} + +void +UHoudiniAssetInstanceInputField::SetScaleOffset( const FVector & InScale, int32 VariationIdx ) +{ + if ( ScaleOffsets.IsValidIndex( VariationIdx ) ) + ScaleOffsets[ VariationIdx ] = InScale; +} + +bool +UHoudiniAssetInstanceInputField::AreOffsetsScaledLinearly( int32 VariationIdx ) const +{ + if ( bScaleOffsetsLinearlyArray.IsValidIndex( VariationIdx ) ) + return bScaleOffsetsLinearlyArray[ VariationIdx ]; + else + return false; +} + +void +UHoudiniAssetInstanceInputField::SetLinearOffsetScale( bool bEnabled, int32 VariationIdx ) +{ + if ( bScaleOffsetsLinearlyArray.IsValidIndex( VariationIdx ) ) + bScaleOffsetsLinearlyArray[ VariationIdx ] = bEnabled; +} + +bool +UHoudiniAssetInstanceInputField::IsOriginalObjectUsed( int32 VariationIdx ) const +{ + if ( !InstancedObjects.IsValidIndex(VariationIdx) ) + return false; + + return OriginalObject == InstancedObjects[ VariationIdx ]; +} + +USceneComponent * +UHoudiniAssetInstanceInputField::GetInstancedComponent( int32 VariationIdx ) const +{ + if ( !InstancerComponents.IsValidIndex(VariationIdx) ) + return nullptr; + + return InstancerComponents[ VariationIdx ]; +} + +const TArray< FTransform > & +UHoudiniAssetInstanceInputField::GetInstancedTransforms( int32 VariationIdx ) const +{ + check(VariationIdx >= 0 && VariationIdx < VariationTransformsArray.Num()); + return VariationTransformsArray[VariationIdx]; +} + +void +UHoudiniAssetInstanceInputField::RecreateRenderState() +{ + check( InstancerComponents.Num() == InstancedObjects.Num() ); + for ( auto Comp : InstancerComponents ) + { + UInstancedStaticMeshComponent* ISMC = Cast(Comp); + if ( ISMC && !ISMC->IsPendingKill() ) + ISMC->RecreateRenderState_Concurrent(); + } +} + +void +UHoudiniAssetInstanceInputField::RecreatePhysicsState() +{ + check( InstancerComponents.Num() == InstancedObjects.Num() ); + for ( auto Comp : InstancerComponents ) + { + UInstancedStaticMeshComponent* ISMC = Cast(Comp); + if ( ISMC && !ISMC->IsPendingKill() ) + ISMC->RecreatePhysicsState(); + } +} + +bool +UHoudiniAssetInstanceInputField::GetMaterialReplacementMeshes( + UMaterialInterface * Material, + TMap< UStaticMesh *, int32 > & MaterialReplacementsMap ) +{ + bool bResult = false; + + for ( int32 Idx = 0; Idx < InstancedObjects.Num(); ++Idx ) + { + UStaticMesh * StaticMesh = Cast(InstancedObjects[ Idx ]); + if ( !StaticMesh || StaticMesh->IsPendingKill() || StaticMesh != OriginalObject ) + continue; + + if (!InstancerComponents.IsValidIndex(Idx)) + continue; + + UInstancedStaticMeshComponent * InstancedStaticMeshComponent = Cast(InstancerComponents[Idx]); + if ( !InstancedStaticMeshComponent || InstancedStaticMeshComponent->IsPendingKill() ) + continue; + + const TArray< class UMaterialInterface * > & OverrideMaterials = InstancedStaticMeshComponent->OverrideMaterials; + for ( int32 MaterialIdx = 0; MaterialIdx < OverrideMaterials.Num(); ++MaterialIdx ) + { + UMaterialInterface * OverridenMaterial = OverrideMaterials[ MaterialIdx ]; + if ( !OverridenMaterial || OverridenMaterial->IsPendingKill() || OverridenMaterial != Material ) + continue; + + if ( !StaticMesh->StaticMaterials.IsValidIndex( MaterialIdx ) ) + continue; + + MaterialReplacementsMap.Add( StaticMesh, MaterialIdx ); + bResult = true; + } + } + + return bResult; +} + +void +UHoudiniAssetInstanceInputField::FixInstancedObjects( const TMap& ReplacementMap ) +{ + if ( OriginalObject ) + { + UObject *const *ReplacementObj = ReplacementMap.Find( OriginalObject ); + if( ReplacementObj ) + { + OriginalObject = *ReplacementObj; + } + } + + int32 VariationCount = InstanceVariationCount(); + for( int32 Idx = 0; Idx < VariationCount; Idx++ ) + { + UObject *const *ReplacementObj = ReplacementMap.Find( InstancedObjects[ Idx ] ); + if( ReplacementObj && *ReplacementObj && !(*ReplacementObj)->IsPendingKill() ) + { + InstancedObjects[ Idx ] = *ReplacementObj; + if( UInstancedStaticMeshComponent* ISMC = Cast( InstancerComponents[ Idx ] ) ) + { + if ( !ISMC->IsPendingKill() ) + ISMC->SetStaticMesh( CastChecked( *ReplacementObj ) ); + } + else if( UHoudiniMeshSplitInstancerComponent* MSIC = Cast(InstancerComponents[Idx]) ) + { + if ( !MSIC->IsPendingKill() ) + MSIC->SetStaticMesh( CastChecked( *ReplacementObj ) ); + } + else if( UHoudiniInstancedActorComponent* IAC = Cast( InstancerComponents[ Idx ] ) ) + { + if ( !IAC->IsPendingKill() ) + IAC->InstancedAsset = *ReplacementObj; + } + } + } +} + + +/** Return the array of processed transforms **/ +void +UHoudiniAssetInstanceInputField::GetProcessedTransforms( TArray& ProcessedTransforms, const int32& VariationIdx ) const +{ + ProcessedTransforms.Empty(); + if (!VariationTransformsArray.IsValidIndex(VariationIdx)) + return; + + ProcessedTransforms.Reserve(VariationTransformsArray[ VariationIdx ].Num()); + + FTransform CurrentTransform = FTransform::Identity; + for (int32 InstanceIdx = 0; InstanceIdx < VariationTransformsArray[ VariationIdx ].Num(); ++InstanceIdx) + { + CurrentTransform = VariationTransformsArray[VariationIdx][ InstanceIdx ]; + + // Compute new rotation and scale. + FQuat TransformRotation = CurrentTransform.GetRotation() * GetRotationOffset( VariationIdx ).Quaternion(); + FVector TransformScale3D = CurrentTransform.GetScale3D() * GetScaleOffset( VariationIdx ); + + // Make sure inverse matrix exists - seems to be a bug in Unreal when submitting instances. + // Happens in blueprint as well. + // We want to make sure the scale is not too small, but keep negative values! (Bug 90876) + if (FMath::Abs(TransformScale3D.X) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.X = (TransformScale3D.X > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + if (FMath::Abs(TransformScale3D.Y) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.Y = (TransformScale3D.Y > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + if (FMath::Abs(TransformScale3D.Z) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.Z = (TransformScale3D.Z > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + CurrentTransform.SetRotation(TransformRotation); + CurrentTransform.SetScale3D(TransformScale3D); + + if ( CurrentTransform.IsValid() ) + ProcessedTransforms.Add( CurrentTransform ); + } +} + diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInstanceInputField.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInstanceInputField.h new file mode 100644 index 00000000..5dbceeab --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetInstanceInputField.h @@ -0,0 +1,214 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once +#include "HoudiniGeoPartObject.h" +#include "HoudiniAssetInstanceInput.h" +#include "HoudiniAssetInstanceInputField.generated.h" + +class UStaticMesh; +class UMaterialInterface; +class UHoudiniAssetComponent; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetInstanceInputField : public UObject +{ + friend class UHoudiniAssetInstanceInput; + + GENERATED_UCLASS_BODY() + + public: + + /** Create an instance of input field. **/ + static UHoudiniAssetInstanceInputField * Create( + UObject * InPrimaryObject, + UHoudiniAssetInstanceInput * InHoudiniAssetInstanceInput, + const FHoudiniGeoPartObject & HoudiniGeoPartObject ); + + /** Create an instance of input field from another input field. **/ + static UHoudiniAssetInstanceInputField * Create( + UObject * InPrimaryObject, + const UHoudiniAssetInstanceInputField * OtherInputField ); + + /** UObject methods. **/ + public: + + virtual void Serialize( FArchive & Ar ) override; + static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); + virtual void BeginDestroy() override; + +#if WITH_EDITOR + + virtual void PostEditUndo() override; + +#endif + + public: + + /** Return geo part object associated with this field. **/ + const FHoudiniGeoPartObject & GetHoudiniGeoPartObject() const; + + /** Refresh state based on the given geo part object */ + void SetGeoPartObject( const FHoudiniGeoPartObject & InHoudiniGeoPartObject ); + + /** Return original static mesh. **/ + UObject* GetOriginalObject() const; + + /** Return the static mesh associated with an instance variation. **/ + UObject * GetInstanceVariation( int32 VariationIndex ) const; + + /** Add a variation to the instancing. **/ + void AddInstanceVariation( UObject * InstancedObject, int32 VariationIdx ); + + /** Replace the instance variation in a particular slot. **/ + void ReplaceInstanceVariation( UObject * InObject, int32 Index ); + + /** Remove a variation from instancing **/ + void RemoveInstanceVariation( int32 VariationIdx ); + + /** Returns the number of instance variations. **/ + int32 InstanceVariationCount() const; + + /** Given a static mesh, find which slot(s) it occupies in the instance variations. **/ + void FindObjectIndices( UObject * InStaticMesh, TArray< int32 > & Indices ); + + /** Get material replacements. **/ + bool GetMaterialReplacementMeshes( + UMaterialInterface * Material, + TMap< UStaticMesh *, int32 > & MaterialReplacementsMap ); + + /** After duplicating this field we need to fix up the referenced external objects */ + void FixInstancedObjects( const TMap& ReplacementMap ); + + /** Get rotator used by this field. **/ + const FRotator & GetRotationOffset( int32 VariationIdx ) const; + + /** Set rotation offset used by this field. **/ + void SetRotationOffset( const FRotator & Rotator, int32 VariationIdx ); + + /** Get scale used by this field. **/ + const FVector & GetScaleOffset( int32 VariationIdx ) const; + + /** Set scale used by this field. **/ + void SetScaleOffset( const FVector & InScale, int32 VariationIdx ); + + /** Return true if all fields are scaled linearly. **/ + bool AreOffsetsScaledLinearly( int32 VariationIdx ) const; + + /** Set whether offsets are scaled linearly. **/ + void SetLinearOffsetScale( bool bEnabled, int32 VariationIdx ); + + /** Return true if original static mesh is used. **/ + bool IsOriginalObjectUsed( int32 VariationIdx ) const; + + /** Return corresponding instanced static mesh component. **/ + class USceneComponent * GetInstancedComponent( int32 VariationIdx ) const; + + /** Return transformations of all instances used by the variation **/ + const TArray< FTransform > & GetInstancedTransforms( int32 VariationIdx ) const; + + /** Return the array of transforms for all variations **/ + FORCEINLINE const TArray< FTransform > & GetInstancedTransforms() { return InstancedTransforms; } + + /** Return transformations of all instances used by the variation **/ + FORCEINLINE const TArray< FLinearColor > & GetInstancedColors(int32 VariationIdx) const { return VariationInstanceColorOverrideArray[VariationIdx]; } + + /** Return the array of transforms for all variations **/ + FORCEINLINE const TArray< FLinearColor > & GetInstancedColors() { return InstanceColorOverride; } + + /** Return the array of processed transforms **/ + void GetProcessedTransforms(TArray& ProcessedTransform, const int32& VariationIdx) const; + + /** Recreates render states for instanced static mesh component. **/ + void RecreateRenderState(); + + /** Recreates physics states for instanced static mesh component. **/ + void RecreatePhysicsState(); + + /** Updates the uproperty attributes for the generated instanced components **/ + void UpdateInstanceUPropertyAttributes(); + + protected: + + /** Create instanced component for this field. **/ + void AddInstanceComponent( int32 VariationIdx ); + + /** Set transforms for this field. **/ + void SetInstanceTransforms( const TArray< FTransform > & ObjectTransforms ); + + /** Update relative transform for this field. **/ + void UpdateRelativeTransform(); + + /** Update instance transformations. **/ + void UpdateInstanceTransforms( bool RecomputeVariationAssignments ); + + protected: + + /** Original object used by the instancer. **/ + UObject* OriginalObject; + + /** Currently used Objects */ + TArray< UObject* > InstancedObjects; + + /** Used instanced actor component. **/ + TArray< USceneComponent * > InstancerComponents; + + /** Parent Houdini asset component. **/ + UObject * HoudiniAssetComponent; + + /** Owner input. **/ + UHoudiniAssetInstanceInput * HoudiniAssetInstanceInput; + + /** Transforms, one for each instance. **/ + TArray< FTransform > InstancedTransforms; + + /** Assignment of Transforms to each variation **/ + TArray< TArray< FTransform > > VariationTransformsArray; + + /** Color overrides, one per instance **/ + TArray InstanceColorOverride; + + /** Per-variation color override assignments */ + TArray< TArray< FLinearColor > > VariationInstanceColorOverrideArray; + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject HoudiniGeoPartObject; + + /** Rotation offset for instanced component. **/ + TArray< FRotator > RotationOffsets; + + /** Scale offset for instanced component. **/ + TArray< FVector > ScaleOffsets; + + /** Whether to scale linearly for all fields. **/ + TArray< bool > bScaleOffsetsLinearlyArray; + + /** Flags used by this input field. **/ + uint32 HoudiniAssetInstanceInputFieldFlagsPacked; +}; + +/** Functor used to sort instance input fields. **/ +struct HOUDINIENGINERUNTIME_API FHoudiniAssetInstanceInputFieldSortPredicate +{ + bool operator()( const UHoudiniAssetInstanceInputField & A, const UHoudiniAssetInstanceInputField & B ) const; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameter.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameter.cpp new file mode 100644 index 00000000..d6f26e50 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameter.cpp @@ -0,0 +1,570 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetParameter.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" +#include "HoudiniAssetParameterMultiparm.h" +#include "HoudiniAssetParameterRamp.h" +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniEngineString.h" + + +#include "Misc/Variant.h" +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +uint32 +GetTypeHash( const UHoudiniAssetParameter * HoudiniAssetParameter ) +{ + if ( HoudiniAssetParameter ) + return HoudiniAssetParameter->GetTypeHash(); + + return 0; +} + +UHoudiniAssetParameter::UHoudiniAssetParameter( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) + , PrimaryObject( nullptr ) + , ParentParameter( nullptr ) + , NodeId( -1 ) + , ParmId( -1 ) + , ParmParentId( -1 ) + , ChildIndex( 0 ) + , TupleSize( 1 ) + , ValuesIndex( -1 ) + , MultiparmInstanceIndex( -1 ) + , ActiveChildParameter( 0 ) + , HoudiniAssetParameterFlagsPacked( 0u ) + , HoudiniAssetParameterVersion( VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE ) +{ + ParameterName = TEXT( "" ); + ParameterLabel = TEXT( "" ); + ParameterHelp = TEXT( "" ); +} + +bool +UHoudiniAssetParameter::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ) +{ + // We need to reset child parameters. + ResetChildParameters(); + + // If parameter has changed, we do not need to recreate it. + if ( bChanged ) + return false; + + // If parameter is invisible, we cannot create it. + if ( !IsVisible( ParmInfo ) ) + return false; + + // Set name and label. + if ( !SetNameAndLabel( ParmInfo ) ) + return false; + + // Set the help + if ( !SetHelp( ParmInfo ) ) + return false; + + // If it is a Substance parameter, mark it as such. + bIsSubstanceParameter = ParameterName.StartsWith( HAPI_UNREAL_PARAM_SUBSTANCE_PREFIX ); + + // Set ids. + SetNodeParmIds( InNodeId, ParmInfo.id ); + + // Set parent id. + ParmParentId = ParmInfo.parentId; + + // Set the index within parent. + ChildIndex = ParmInfo.childIndex; + + // Set tuple count. + TupleSize = ParmInfo.size; + + // Set the multiparm instance index. + MultiparmInstanceIndex = ParmInfo.instanceNum; + + // Set spare flag. + bIsSpare = ParmInfo.spare; + + // Set disabled flag. + bIsDisabled = ParmInfo.disabled; + + // Set child of multiparm flag. + bIsChildOfMultiparm = ParmInfo.isChildOfMultiParm; + + // Set ismultiparmflag + bIsMultiparm = ( ParmInfo.type == HAPI_PARMTYPE_MULTIPARMLIST ); + + // Set component. + PrimaryObject = InPrimaryObject; + + // Store parameter parent. + ParentParameter = InParentParameter; + + return true; +} + +UHoudiniAssetParameter * +UHoudiniAssetParameter::Duplicate( UObject* InOuter ) +{ + return DuplicateObject(this, InOuter ); +} + +void +UHoudiniAssetParameter::NotifyChildParameterChanged( UHoudiniAssetParameter * HoudiniAssetParameter ) +{ + // Default implementation does nothing. +} + +void +UHoudiniAssetParameter::NotifyChildParametersCreated() +{ + // Default implementation does nothing. +} + +bool +UHoudiniAssetParameter::UploadParameterValue() +{ + // Mark this parameter as no longer changed. + bChanged = false; + return true; +} + +bool +UHoudiniAssetParameter::SetParameterVariantValue( const FVariant& Variant, int32 Idx, bool bTriggerModify, bool bRecordUndo ) +{ + // Default implementation does nothing. + return false; +} + +bool +UHoudiniAssetParameter::HasChanged() const +{ + return bChanged; +} + +void +UHoudiniAssetParameter::SetHoudiniAssetComponent( UHoudiniAssetComponent * InComponent ) +{ + PrimaryObject = InComponent; +} + +void +UHoudiniAssetParameter::SetParentParameter( UHoudiniAssetParameter * InParentParameter ) +{ + if ( ParentParameter != InParentParameter ) + { + ParentParameter = InParentParameter; + if ( ParentParameter && !ParentParameter->IsPendingKill() ) + { + // Retrieve parent parameter id. We ignore folder lists, they are artificial parents created by us. + ParmParentId = ParentParameter->GetParmId(); + + // Add this parameter to parent collection of child parameters. + ParentParameter->AddChildParameter( this ); + } + else + { + // Reset parent parm id. + ParmParentId = -1; + } + } +} + +bool +UHoudiniAssetParameter::IsChildParameter() const +{ + return ParentParameter != nullptr; +} + +void +UHoudiniAssetParameter::AddChildParameter( UHoudiniAssetParameter * HoudiniAssetParameter ) +{ + if ( HoudiniAssetParameter ) + ChildParameters.Add( HoudiniAssetParameter ); +} + +uint32 +UHoudiniAssetParameter::GetTypeHash() const +{ + // We do hashing based on parameter name. + return ::GetTypeHash( ParameterName ); +} + +HAPI_ParmId +UHoudiniAssetParameter::GetParmId() const +{ + return ParmId; +} + +HAPI_ParmId +UHoudiniAssetParameter::GetParmParentId() const +{ + return ParmParentId; +} + +void +UHoudiniAssetParameter::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniAssetParameter * HoudiniAssetParameter = Cast< UHoudiniAssetParameter >( InThis ); + if ( HoudiniAssetParameter ) + { + if ( HoudiniAssetParameter->PrimaryObject && !HoudiniAssetParameter->PrimaryObject->IsPendingKill() ) + Collector.AddReferencedObject( HoudiniAssetParameter->PrimaryObject, InThis ); + } + + // Call base implementation. + Super::AddReferencedObjects( InThis, Collector ); +} + +void +UHoudiniAssetParameter::Serialize( FArchive & Ar ) +{ + // Call base implementation. + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniAssetParameterVersion; + + Ar << HoudiniAssetParameterFlagsPacked; + + if ( Ar.IsLoading() ) + bChanged = false; + + Ar << ParameterName; + Ar << ParameterLabel; + + Ar << NodeId; + if( !Ar.IsTransacting() && Ar.IsLoading() ) + { + // NodeId is invalid after load + NodeId = -1; + } + Ar << ParmId; + + Ar << ParmParentId; + Ar << ChildIndex; + + Ar << TupleSize; + Ar << ValuesIndex; + Ar << MultiparmInstanceIndex; + + if( HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER ) + { + UObject* Dummy = nullptr; + Ar << Dummy; + } + + if ( HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP ) + { + Ar << ParameterHelp; + } + else + { + ParameterHelp = TEXT( "" ); + } + + if ( Ar.IsTransacting() ) + { + Ar << PrimaryObject; + Ar << ParentParameter; + } +} + +#if WITH_EDITOR + +void +UHoudiniAssetParameter::PostEditUndo() +{ + Super::PostEditUndo(); + MarkChanged(); +} + +#endif // WITH_EDITOR + +void +UHoudiniAssetParameter::SetNodeParmIds( HAPI_NodeId InNodeId, HAPI_ParmId InParmId ) +{ + NodeId = InNodeId; + ParmId = InParmId; +} + +bool +UHoudiniAssetParameter::HasValidNodeParmIds() const +{ + return ( NodeId != -1 ) && ( ParmId != -1 ); +} + +bool +UHoudiniAssetParameter::IsVisible( const HAPI_ParmInfo & ParmInfo ) const +{ + return ParmInfo.invisible == false; +} + +bool +UHoudiniAssetParameter::SetNameAndLabel( const HAPI_ParmInfo & ParmInfo ) +{ + FHoudiniEngineString HoudiniEngineStringName( ParmInfo.nameSH ); + FHoudiniEngineString HoudiniEngineStringLabel( ParmInfo.labelSH ); + + bool bresult = true; + + bresult |= HoudiniEngineStringName.ToFString( ParameterName ); + bresult |= HoudiniEngineStringLabel.ToFString( ParameterLabel ); + + return bresult; +} + +bool +UHoudiniAssetParameter::SetNameAndLabel( HAPI_StringHandle StringHandle ) +{ + FHoudiniEngineString HoudiniEngineString( StringHandle ); + + bool bresult = true; + + bresult |= HoudiniEngineString.ToFString( ParameterName ); + bresult |= HoudiniEngineString.ToFString( ParameterLabel ); + + return bresult; +} + +bool +UHoudiniAssetParameter::SetNameAndLabel( const FString & Name ) +{ + ParameterName = Name; + ParameterLabel = Name; + + return true; +} + +bool +UHoudiniAssetParameter::SetHelp( const HAPI_ParmInfo & ParmInfo ) +{ + FHoudiniEngineString HoudiniEngineStringHelp( ParmInfo.helpSH ); + + bool bresult = true; + bresult = HoudiniEngineStringHelp.ToFString( ParameterHelp ); + + return bresult; +} + +void +UHoudiniAssetParameter::MarkChanged( bool bMarkAndTriggerUpdate ) +{ + // Set changed flag. + bChanged = true; + +#if WITH_EDITOR + + // Notify component about change. + if( bMarkAndTriggerUpdate ) + { + if( UHoudiniAssetComponent* Component = Cast(PrimaryObject) ) + if ( !Component->IsPendingKill() ) + Component->NotifyParameterChanged( this ); + + // Notify parent parameter about change. + if( ParentParameter && !ParentParameter->IsPendingKill() ) + ParentParameter->NotifyChildParameterChanged( this ); + } + +#endif // WITH_EDITOR +} + +void +UHoudiniAssetParameter::UnmarkChanged() +{ + bChanged = false; +} + +void +UHoudiniAssetParameter::ResetChildParameters() +{ + ChildParameters.Empty(); +} + +int32 +UHoudiniAssetParameter::GetTupleSize() const +{ + return TupleSize; +} + +bool +UHoudiniAssetParameter::IsArray() const +{ + return TupleSize > 1; +} + +void +UHoudiniAssetParameter::SetValuesIndex( int32 InValuesIndex ) +{ + ValuesIndex = InValuesIndex; +} + +int32 +UHoudiniAssetParameter::GetActiveChildParameter() const +{ + return ActiveChildParameter; +} + +void UHoudiniAssetParameter::OnParamStateChanged() +{ +#if WITH_EDITOR + if( UHoudiniAssetComponent* Comp = Cast( PrimaryObject ) ) + { + if ( !Comp->IsPendingKill() ) + Comp->UpdateEditorProperties( false ); + } +#endif +} + +bool +UHoudiniAssetParameter::HasChildParameters() const +{ + return ChildParameters.Num() > 0; +} + +bool +UHoudiniAssetParameter::IsSubstanceParameter() const +{ + return bIsSubstanceParameter; +} + +bool +UHoudiniAssetParameter::IsActiveChildParameter( UHoudiniAssetParameter * ChildParam ) const +{ + if (!ChildParam || ChildParam->IsPendingKill()) + return false; + + if (!ChildParameters.IsValidIndex(ActiveChildParameter)) + return false; + + return ChildParameters[ ActiveChildParameter ] == ChildParam; +} + +UHoudiniAssetParameter * +UHoudiniAssetParameter::GetParentParameter() const +{ + return ParentParameter; +} + +const FString & +UHoudiniAssetParameter::GetParameterName() const +{ + return ParameterName; +} + +const FString & +UHoudiniAssetParameter::GetParameterLabel() const +{ + return ParameterLabel; +} + +const FString & +UHoudiniAssetParameter::GetParameterHelp() const +{ + return ParameterHelp; +} + + +void +UHoudiniAssetParameter::SetNodeId( HAPI_NodeId InNodeId ) +{ + NodeId = InNodeId; +} + +bool +UHoudiniAssetParameter::IsSpare() const +{ + return bIsSpare; +} + +bool +UHoudiniAssetParameter::IsDisabled() const +{ + return bIsDisabled; +} + +const UHoudiniAssetComponent * +UHoudiniAssetParameter::GetHoudiniAssetComponent() const +{ + return Cast(PrimaryObject); +} + + +FReply +UHoudiniAssetParameter::OnRevertParmToDefault(int32 AtIndex) +{ +#if WITH_EDITOR + bool bReverted = true; + if (AtIndex < 0 || AtIndex >= TupleSize) + { + // Revert the whole parameter to its default value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefaults( + FHoudiniEngine::Get().GetSession(), NodeId, TCHAR_TO_UTF8(*ParameterName))) + { + HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s to its default value."), *ParameterName); + bReverted = false; + } + } + else + { + // Revert the parameter to its default value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefault( + FHoudiniEngine::Get().GetSession(), NodeId, TCHAR_TO_UTF8(*ParameterName), AtIndex)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s to its default value."), *ParameterName); + bReverted = false; + } + } + + if (bReverted) + { + // We need to manually notify of the value change to trigger an update, + // As calling MarkChanged() would also cause the value to be reuploaded to the non default value + + // Notify component about change. + UHoudiniAssetComponent* Component = Cast(PrimaryObject); + if (Component && !Component->IsPendingKill()) + Component->NotifyParameterChanged(this); + + // Notify parent parameter about change. + if (ParentParameter && !ParentParameter->IsPendingKill()) + ParentParameter->NotifyChildParameterChanged(this); + } +#endif // WITH_EDITOR + + return FReply::Handled(); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameter.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameter.h new file mode 100644 index 00000000..b9ea2432 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameter.h @@ -0,0 +1,271 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once +#include "HAPI.h" +#include "HoudiniAssetParameter.generated.h" + + +class FArchive; +class FVariant; +class FReferenceCollector; +class FReply; + +UCLASS( config = Editor ) +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameter : public UObject +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + friend class UHoudiniAssetComponent; + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ); + + /** Create a duplicate of this parameter for the given parent component, takes care of any subobjects */ + virtual UHoudiniAssetParameter * Duplicate( UObject* InOuter ); + + /** Set component for this parameter. **/ + virtual void SetHoudiniAssetComponent( class UHoudiniAssetComponent * InComponent ); + + /** Set parent parameter for this parameter. **/ + void SetParentParameter( UHoudiniAssetParameter * InParentParameter ); + + /** Return parent parameter for this parameter, if there's one. **/ + UHoudiniAssetParameter * GetParentParameter() const; + + /** Upload parameter value to HAPI. **/ + virtual bool UploadParameterValue(); + + /** Set parameter value. **/ + virtual bool SetParameterVariantValue( + const FVariant & Variant, + int32 Idx = 0, + bool bTriggerModify = true, + bool bRecordUndo = true ); + + /** Notification from a child parameter about its change. **/ + virtual void NotifyChildParameterChanged( UHoudiniAssetParameter * HoudiniAssetParameter ); + + /** Notification from component that all child parameters have been created. **/ + virtual void NotifyChildParametersCreated(); + + public: + + /** returns the owner houdini asset component **/ + const UHoudiniAssetComponent * GetHoudiniAssetComponent() const; + + /** Return true if this parameter has been changed. **/ + virtual bool HasChanged() const; + + /** Return hash value for this object, used when using this object as a key inside hashing containers. **/ + uint32 GetTypeHash() const; + + /** Return parameter id of this parameter. **/ + HAPI_ParmId GetParmId() const; + + /** Return parameter id of a parent of this parameter. **/ + HAPI_ParmId GetParmParentId() const; + + /** Return true if parent parameter exists for this parameter. **/ + bool IsChildParameter() const; + + /** Return parameter name. **/ + const FString & GetParameterName() const; + + /** Return label name. **/ + const FString & GetParameterLabel() const; + + /** Return parameter help **/ + const FString & GetParameterHelp() const; + + /** Update parameter's node id. This is necessary after parameter is loaded. **/ + void SetNodeId( HAPI_NodeId InNodeId ); + + /** Mark this parameter as unchanged. **/ + void UnmarkChanged(); + + /** Reset array containing all child parameters. **/ + void ResetChildParameters(); + + /** Add a child parameter. **/ + void AddChildParameter( UHoudiniAssetParameter * HoudiniAssetParameter ); + + /** Return true if given parameter is an active child parameter. **/ + bool IsActiveChildParameter( UHoudiniAssetParameter * ChildParameter ) const; + + /** Return true if this parameter contains child parameters. **/ + bool HasChildParameters() const; + + /** Return true if this is Substance parameter. **/ + bool IsSubstanceParameter() const; + + /** Return tuple size. **/ + int32 GetTupleSize() const; + + // Revert the parameter to its default value + FReply OnRevertParmToDefault(int32 AtIndex); + + /** UObject methods. **/ + public: + + virtual void Serialize( FArchive & Ar ) override; + static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); + +#if WITH_EDITOR + + virtual void PostEditUndo() override; + +#endif // WITH_EDITOR + + protected: + + /** Set parameter and node ids. **/ + void SetNodeParmIds( HAPI_NodeId InNodeId, HAPI_ParmId InParmId ); + + /** Return true if parameter and node ids are valid. **/ + bool HasValidNodeParmIds() const; + + /** Set name and label. If label does not exist, name will be used instead for label. If error occurs, false will be returned. **/ + bool SetNameAndLabel( const HAPI_ParmInfo & ParmInfo ); + + /** Set name and label to be same value from string handle. **/ + bool SetNameAndLabel( HAPI_StringHandle StringHandle ); + + /** Set name and label. **/ + bool SetNameAndLabel( const FString & Name ); + + /** Set the parameter's help (tooltip)**/ + bool SetHelp( const HAPI_ParmInfo & ParmInfo ); + + /** Check if parameter is visible. **/ + bool IsVisible( const HAPI_ParmInfo & ParmInfo ) const; + + /** Mark this parameter as changed. This occurs when user modifies the value of this parameter through UI. **/ + void MarkChanged( bool bMarkAndTriggerUpdate = true ); + + /** Return true if this parameter is an array (has tuple size larger than one). **/ + bool IsArray() const; + + /** Sets internal value index used by this parameter. **/ + void SetValuesIndex( int32 InValuesIndex ); + + /** Return index of active child parameter. **/ + int32 GetActiveChildParameter() const; + + /** Called when state of the parameter changes as side-effect of some action */ + void OnParamStateChanged(); + + /** Return true if parameter is spare, that is, created by Houdini Engine only. **/ + bool IsSpare() const; + + /** Return true if parameter is disabled. **/ + bool IsDisabled() const; + + protected: + + /** Array containing all child parameters. **/ + TArray< UHoudiniAssetParameter * > ChildParameters; + + /** Owner component. **/ + UObject * PrimaryObject; + + /** Parent parameter. **/ + UHoudiniAssetParameter * ParentParameter; + + /** Name of this parameter. **/ + FString ParameterName; + + /** Label of this parameter. **/ + FString ParameterLabel; + + /** Node this parameter belongs to. **/ + HAPI_NodeId NodeId; + + /** Id of this parameter. **/ + HAPI_ParmId ParmId; + + /** Id of parent parameter, -1 if root is parent. **/ + HAPI_ParmId ParmParentId; + + /** Child index within its parent parameter. **/ + int32 ChildIndex; + + /** Tuple size - arrays. **/ + int32 TupleSize; + + /** Internal HAPI cached value index. **/ + int32 ValuesIndex; + + /** The multiparm instance index. **/ + int32 MultiparmInstanceIndex; + + /** Active child parameter. **/ + int32 ActiveChildParameter; + + /** The parameter's help, to be used as a tooltip **/ + FString ParameterHelp; + + /** Flags used by this parameter. **/ + union + { + struct + { + /** Is set to true if this parameter is spare, that is, created by Houdini Engine only. **/ + uint32 bIsSpare : 1; + + /** Is set to true if this parameter is disabled. **/ + uint32 bIsDisabled : 1; + + /** Is set to true if value of this parameter has been changed by user. **/ + uint32 bChanged : 1; + + /** Is set to true when parameter's slider (if it has one) is being dragged. Transient. **/ + uint32 bSliderDragged : 1; + + /** Is set to true if the parameter is a multiparm child parameter. **/ + uint32 bIsChildOfMultiparm : 1; + + /** Is set to true if this parameter is a Substance parameter. **/ + uint32 bIsSubstanceParameter : 1; + + /** Is set to true if this parameter is a multiparm **/ + uint32 bIsMultiparm : 1; + }; + + uint32 HoudiniAssetParameterFlagsPacked; + }; + + /** Temporary variable holding parameter serialization version. **/ + uint32 HoudiniAssetParameterVersion; +}; + + +/** Function used by hasing containers to create a unique hash for this type of object. **/ +uint32 GetTypeHash( const UHoudiniAssetParameter * HoudiniAssetParameter ); diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterButton.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterButton.cpp new file mode 100644 index 00000000..9766debb --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterButton.cpp @@ -0,0 +1,102 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetParameterButton.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" + +#include "Misc/Variant.h" + +UHoudiniAssetParameterButton::UHoudiniAssetParameterButton( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{} + +UHoudiniAssetParameterButton * +UHoudiniAssetParameterButton::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + UObject * Outer = InPrimaryObject; + if ( !Outer ) + { + Outer = InParentParameter; + if ( !Outer ) + { + // Must have either component or parent not null. + check( false ); + } + } + + UHoudiniAssetParameterButton * HoudiniAssetParameterButton = NewObject< UHoudiniAssetParameterButton >( + Outer, UHoudiniAssetParameterButton::StaticClass(), NAME_None, RF_Public | RF_Transactional ); + + HoudiniAssetParameterButton->CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ); + return HoudiniAssetParameterButton; +} + +bool +UHoudiniAssetParameterButton::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ) +{ + if ( !Super::CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ) ) + return false; + + // We can only handle button type. + if ( ParmInfo.type != HAPI_PARMTYPE_BUTTON ) + return false; + + // Assign internal Hapi values index. + SetValuesIndex( ParmInfo.intValuesIndex ); + + return true; +} + +bool +UHoudiniAssetParameterButton::UploadParameterValue() +{ + int32 PressValue = 1; + if ( FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), NodeId, &PressValue, ValuesIndex, 1 ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + return Super::UploadParameterValue(); +} + +bool +UHoudiniAssetParameterButton::SetParameterVariantValue( + const FVariant & Variant, int32 Idx, bool bTriggerModify, bool bRecordUndo ) +{ + // We don't care about variant values for button. Just trigger the click. + MarkChanged( bTriggerModify ); + + return true; +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterButton.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterButton.h new file mode 100644 index 00000000..e50f3b1b --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterButton.h @@ -0,0 +1,61 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniAssetParameter.h" +#include "HoudiniAssetParameterButton.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterButton : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + /** Create instance of this class. **/ + static UHoudiniAssetParameterButton * Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ); + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ) override; + + /** Upload parameter value to HAPI. **/ + virtual bool UploadParameterValue() override; + + /** Set parameter value. **/ + virtual bool SetParameterVariantValue( + const FVariant & Variant, int32 Idx = 0, bool bTriggerModify = true, + bool bRecordUndo = true ) override; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterChoice.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterChoice.cpp new file mode 100644 index 00000000..447ce485 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterChoice.cpp @@ -0,0 +1,436 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetParameterChoice.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" + +#include "Misc/Variant.h" +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetParameterChoice::UHoudiniAssetParameterChoice( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) + , CurrentValue( 0 ) + , bStringChoiceList( false ) +{ + StringValue = TEXT( "" ); +} + +UHoudiniAssetParameterChoice * +UHoudiniAssetParameterChoice::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ) +{ + UObject * Outer = InPrimaryObject; + if ( !Outer ) + { + Outer = InParentParameter; + if ( !Outer ) + { + // Must have either component or parent not null. + check( false ); + } + } + + UHoudiniAssetParameterChoice * HoudiniAssetParameterChoice = NewObject( + Outer, UHoudiniAssetParameterChoice::StaticClass(), NAME_None, RF_Public | RF_Transactional ); + + HoudiniAssetParameterChoice->CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ); + return HoudiniAssetParameterChoice; +} + +bool +UHoudiniAssetParameterChoice::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ) +{ + if ( !Super::CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ) ) + return false; + + // Choice lists can be only integer or string. + if ( ParmInfo.type != HAPI_PARMTYPE_INT && ParmInfo.type != HAPI_PARMTYPE_STRING ) + return false; + + // Get the actual value for this property. + CurrentValue = 0; + + if ( ParmInfo.type == HAPI_PARMTYPE_INT ) + { + // This is an integer choice list. + bStringChoiceList = false; + + // Assign internal Hapi values index. + SetValuesIndex( ParmInfo.intValuesIndex ); + + if ( FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), NodeId, &CurrentValue, + ValuesIndex, TupleSize) != HAPI_RESULT_SUCCESS ) + { + return false; + } + if( CurrentValue >= ParmInfo.choiceCount ) + { + HOUDINI_LOG_WARNING(TEXT("parm '%s' has an invalid value %d, menu tokens are not supported for choice menus"), *GetParameterName(), CurrentValue); + CurrentValue = 0; + } + } + else if ( ParmInfo.type == HAPI_PARMTYPE_STRING ) + { + // This is a string choice list. + bStringChoiceList = true; + + // Assign internal Hapi values index. + SetValuesIndex( ParmInfo.stringValuesIndex ); + + HAPI_StringHandle StringHandle; + if ( FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), NodeId, false, + &StringHandle, ValuesIndex, TupleSize ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + // Get the actual string value. + FHoudiniEngineString HoudiniEngineString( StringHandle ); + if ( !HoudiniEngineString.ToFString( StringValue ) ) + return false; + } + + // Get choice descriptors. + TArray< HAPI_ParmChoiceInfo > ParmChoices; + ParmChoices.SetNumUninitialized( ParmInfo.choiceCount ); + for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) + FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); + + if ( FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), NodeId, &ParmChoices[ 0 ], + ParmInfo.choiceIndex, ParmInfo.choiceCount ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + // Get string values for all available choices. + StringChoiceValues.Empty(); + StringChoiceLabels.Empty(); + + bool bMatchedSelectionLabel = false; + for ( int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx ) + { + FString * ChoiceValue = new FString(); + FString * ChoiceLabel = new FString(); + + { + FHoudiniEngineString HoudiniEngineString( ParmChoices[ ChoiceIdx ].valueSH ); + if ( !HoudiniEngineString.ToFString( *ChoiceValue ) ) + return false; + + StringChoiceValues.Add( TSharedPtr< FString >( ChoiceValue ) ); + } + + { + FHoudiniEngineString HoudiniEngineString( ParmChoices[ ChoiceIdx ].labelSH ); + if ( !HoudiniEngineString.ToFString( *ChoiceLabel ) ) + return false; + + StringChoiceLabels.Add( TSharedPtr< FString >( ChoiceLabel ) ); + } + + // If this is a string choice list, we need to match name with corresponding selection label. + if ( bStringChoiceList && !bMatchedSelectionLabel && ChoiceValue->Equals( StringValue ) ) + { + StringValue = *ChoiceLabel; + bMatchedSelectionLabel = true; + CurrentValue = ChoiceIdx; + } + else if ( !bStringChoiceList && ChoiceIdx == CurrentValue ) + { + StringValue = *ChoiceLabel; + } + } + + return true; +} + +TOptional< TSharedPtr< FString > > +UHoudiniAssetParameterChoice::GetValue( int32 Idx ) const +{ + if ( Idx == 0 && StringChoiceValues.IsValidIndex(CurrentValue) ) + return TOptional< TSharedPtr< FString > >( StringChoiceValues[ CurrentValue ] ); + + return TOptional< TSharedPtr< FString > >(); +} + +int32 +UHoudiniAssetParameterChoice::GetParameterValueInt() const +{ + return CurrentValue; +} + +const FString & +UHoudiniAssetParameterChoice::GetParameterValueString() const +{ + return StringValue; +} + +bool +UHoudiniAssetParameterChoice::IsStringChoiceList() const +{ + return bStringChoiceList; +} + +void +UHoudiniAssetParameterChoice::SetValueInt( int32 Value, bool bTriggerModify, bool bRecordUndo ) +{ +#if WITH_EDITOR + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterChoiceChange", "Houdini Parameter Choice: Changing a value" ), + PrimaryObject ); + Modify(); + + if ( !bRecordUndo ) + Transaction.Cancel(); + +#endif // WITH_EDITOR + + CurrentValue = Value; + + MarkChanged( bTriggerModify ); +} + +bool +UHoudiniAssetParameterChoice::UploadParameterValue() +{ + if ( bStringChoiceList ) + { + // Get corresponding value. + FString* ChoiceValue = StringChoiceValues.IsValidIndex(CurrentValue) ? StringChoiceValues[ CurrentValue ].Get() : nullptr; + std::string String = TCHAR_TO_UTF8( *( *ChoiceValue ) ); + FHoudiniApi::SetParmStringValue( FHoudiniEngine::Get().GetSession(), NodeId, String.c_str(), ParmId, 0 ); + } + else + { + // This is an int choice list. + FHoudiniApi::SetParmIntValues( FHoudiniEngine::Get().GetSession(), NodeId, &CurrentValue, ValuesIndex, TupleSize ); + } + + return Super::UploadParameterValue(); +} + +bool +UHoudiniAssetParameterChoice::SetParameterVariantValue( const FVariant & Variant, int32 Idx, bool bTriggerModify, bool bRecordUndo ) +{ + EVariantTypes VariantType = Variant.GetType(); + int32 VariantValue = 0; + + switch ( VariantType ) + { + case EVariantTypes::String: + { + if ( bStringChoiceList ) + { + bool bLabelFound = false; + const FString & VariantStringValue = Variant.GetValue< FString >(); + + // We need to match selection based on label. + int32 LabelIdx = 0; + for ( ; LabelIdx < StringChoiceLabels.Num(); ++LabelIdx ) + { + FString * ChoiceLabel = StringChoiceLabels[ LabelIdx ].Get(); + + if ( ChoiceLabel->Equals( VariantStringValue ) ) + { + VariantValue = LabelIdx; + bLabelFound = true; + break; + } + } + + if ( !bLabelFound ) + return false; + } + else + { + return false; + } + + break; + } + + case EVariantTypes::Int8: + case EVariantTypes::Int16: + case EVariantTypes::Int32: + case EVariantTypes::Int64: + case EVariantTypes::UInt8: + case EVariantTypes::UInt16: + case EVariantTypes::UInt32: + case EVariantTypes::UInt64: + { + if ( CurrentValue >= 0 && CurrentValue < StringChoiceValues.Num() ) + { + VariantValue = Variant.GetValue< int32 >(); + } + else + { + return false; + } + + break; + } + + default: + { + return false; + } + } + +#if WITH_EDITOR + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterChoiceChange", "Houdini Parameter Choice: Changing a value" ), + PrimaryObject ); + + Modify(); + + if ( !bRecordUndo ) + Transaction.Cancel(); + +#endif // WITH_EDITOR + + CurrentValue = VariantValue; + MarkChanged( bTriggerModify ); + + return false; +} + +void +UHoudiniAssetParameterChoice::Serialize( FArchive & Ar ) +{ + // Call base implementation. + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + if ( Ar.IsLoading() ) + { + StringChoiceValues.Empty(); + StringChoiceLabels.Empty(); + } + + int32 NumChoices = StringChoiceValues.Num(); + Ar << NumChoices; + + int32 NumLabels = StringChoiceLabels.Num(); + Ar << NumLabels; + + if ( Ar.IsSaving() ) + { + for ( int32 ChoiceIdx = 0; ChoiceIdx < StringChoiceValues.Num(); ++ChoiceIdx ) + Ar << *( StringChoiceValues[ ChoiceIdx ].Get() ); + + for ( int32 LabelIdx = 0; LabelIdx < StringChoiceLabels.Num(); ++LabelIdx ) + Ar << *( StringChoiceLabels[ LabelIdx ].Get() ); + } + else if ( Ar.IsLoading() ) + { + FString Temp; + + for ( int32 ChoiceIdx = 0; ChoiceIdx < NumChoices; ++ChoiceIdx ) + { + Ar << Temp; + FString * ChoiceName = new FString( Temp ); + StringChoiceValues.Add( TSharedPtr< FString >( ChoiceName ) ); + } + + for ( int32 LabelIdx = 0; LabelIdx < NumLabels; ++LabelIdx ) + { + Ar << Temp; + FString * ChoiceLabel = new FString( Temp ); + StringChoiceLabels.Add( TSharedPtr< FString >( ChoiceLabel ) ); + } + } + + Ar << StringValue; + Ar << CurrentValue; + + Ar << bStringChoiceList; +} + +#if WITH_EDITOR + +void +UHoudiniAssetParameterChoice::OnChoiceChange( TSharedPtr< FString > NewChoice ) +{ + bool bLocalChanged = false; + StringValue = *( NewChoice.Get() ); + + // We need to match selection based on label. + int32 LabelIdx = 0; + for ( ; LabelIdx < StringChoiceLabels.Num(); ++LabelIdx ) + { + FString * ChoiceLabel = StringChoiceLabels[ LabelIdx ].Get(); + + if ( ChoiceLabel->Equals( StringValue ) ) + { + bLocalChanged = true; + break; + } + } + + if ( bLocalChanged ) + { + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterChoiceChange", "Houdini Parameter Choice: Changing a value" ), + PrimaryObject ); + Modify(); + + CurrentValue = LabelIdx; + + // Mark this property as changed. + MarkChanged(); + } +} + +FText +UHoudiniAssetParameterChoice::HandleChoiceContentText() const +{ + return FText::FromString( StringValue ); +} + +#endif // WITH_EDITOR + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterChoice.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterChoice.h new file mode 100644 index 00000000..e0a76606 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterChoice.h @@ -0,0 +1,110 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniAssetParameter.h" +#include "HoudiniAssetParameterChoice.generated.h" + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterChoice : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + /** Create instance of this class. **/ + static UHoudiniAssetParameterChoice* Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ); + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ) override; + + /** Upload parameter value to HAPI. **/ + virtual bool UploadParameterValue() override; + + /** Set parameter value. **/ + virtual bool SetParameterVariantValue( + const FVariant & Variant, int32 Idx = 0, bool bTriggerModify = true, + bool bRecordUndo = true ) override; + + /** UObject methods. **/ + public: + + virtual void Serialize( FArchive & Ar ) override; + + public: + + /** Retrieve value for string choice list, used by Slate. **/ + TOptional< TSharedPtr< FString > > GetValue( int32 Idx ) const; + + /** Retrieve value for integer parameter. **/ + int32 GetParameterValueInt() const; + + /** Retrieve value for string parameter. **/ + const FString& GetParameterValueString() const; + + /** Return true if this is a string choice list. **/ + bool IsStringChoiceList() const; + + /** Set integer value. **/ + void SetValueInt( int32 Value, bool bTriggerModify = true, bool bRecordUndo = true ); + +#if WITH_EDITOR + /** Called when change of selection is triggered. **/ + void OnChoiceChange( TSharedPtr< FString > NewChoice ); + + /** Called to retrieve the name of selected item. **/ + FText HandleChoiceContentText() const; + +#endif // WITH_EDITOR + + protected: + + /** Choice values for this property. **/ + TArray< TSharedPtr< FString > > StringChoiceValues; + + /** Choice labels for this property. **/ + TArray< TSharedPtr< FString > > StringChoiceLabels; + + /** Value of this property. **/ + FString StringValue; + + /** Current value for this property. **/ + int32 CurrentValue; + + /** Is set to true when this choice list is a string choice list. **/ + bool bStringChoiceList; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterColor.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterColor.cpp new file mode 100644 index 00000000..159a2fbb --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterColor.cpp @@ -0,0 +1,196 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetParameterColor.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" + +#include "Misc/Variant.h" +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetParameterColor::UHoudiniAssetParameterColor( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) + , Color( FColor::White ) +{} + +void +UHoudiniAssetParameterColor::Serialize( FArchive & Ar ) +{ + // Call base implementation. + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + if ( Ar.IsLoading() ) + Color = FColor::White; + + Ar << Color; +} + +UHoudiniAssetParameterColor * +UHoudiniAssetParameterColor::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ) +{ + UObject * Outer = InPrimaryObject; + if ( !Outer ) + { + Outer = InParentParameter; + if ( !Outer ) + { + // Must have either component or parent not null. + check( false ); + } + } + + UHoudiniAssetParameterColor * HoudiniAssetParameterColor = NewObject< UHoudiniAssetParameterColor >( + Outer, UHoudiniAssetParameterColor::StaticClass(), NAME_None, RF_Public | RF_Transactional ); + + HoudiniAssetParameterColor->CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ); + return HoudiniAssetParameterColor; +} + +bool +UHoudiniAssetParameterColor::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ) +{ + if ( !Super::CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ) ) + return false; + + // We can only handle float type. + if ( ParmInfo.type != HAPI_PARMTYPE_COLOR ) + return false; + + // Assign internal Hapi values index. + SetValuesIndex( ParmInfo.floatValuesIndex ); + + // Get the actual value for this property. + Color = FLinearColor::White; + if ( FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + (float *) &Color.R, ValuesIndex, TupleSize ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + if ( TupleSize == 3 ) + Color.A = 1.0f; + + return true; +} + +bool +UHoudiniAssetParameterColor::UploadParameterValue() +{ + if ( FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), NodeId, (const float*)&Color.R, ValuesIndex, TupleSize ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + return Super::UploadParameterValue(); +} + +bool +UHoudiniAssetParameterColor::SetParameterVariantValue( const FVariant& Variant, int32 Idx, bool bTriggerModify, bool bRecordUndo ) +{ + EVariantTypes VariantType = Variant.GetType(); + FLinearColor VariantLinearColor = FLinearColor::White; + + if ( EVariantTypes::Color == VariantType ) + { + FColor VariantColor = Variant.GetValue(); + VariantLinearColor = VariantColor.ReinterpretAsLinear(); + } + else if ( EVariantTypes::LinearColor == VariantType ) + { + VariantLinearColor = Variant.GetValue< FLinearColor >(); + } + else + { + return false; + } + +#if WITH_EDITOR + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterColorChange", "Houdini Parameter Color: Changing a value" ), + PrimaryObject ); + + Modify(); + + if ( !bRecordUndo ) + Transaction.Cancel(); + +#endif // WITH_EDITOR + + Color = VariantLinearColor; + MarkChanged( bTriggerModify ); + + return true; +} + +FLinearColor +UHoudiniAssetParameterColor::GetColor() const +{ + return Color; +} + +void +UHoudiniAssetParameterColor::OnPaintColorChanged( FLinearColor InNewColor, bool bTriggerModify, bool bRecordUndo ) +{ + if ( InNewColor != Color ) + { + +#if WITH_EDITOR + + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterColorChange", "Houdini Parameter Color: Changing a value" ), + PrimaryObject ); + Modify(); + + if ( !bRecordUndo ) + Transaction.Cancel(); + +#endif // WITH_EDITOR + + Color = InNewColor; + + // Mark this parameter as changed. + MarkChanged( bTriggerModify ); + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterColor.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterColor.h new file mode 100644 index 00000000..89aab6c0 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterColor.h @@ -0,0 +1,83 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniAssetParameter.h" +#include "CoreMinimal.h" +#include "HoudiniAssetParameterColor.generated.h" + +struct FPointerEvent; +struct FGeometry; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterColor : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + /** Create instance of this class. **/ + static UHoudiniAssetParameterColor* Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ); + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ) override; + + /** Upload parameter value to HAPI. **/ + virtual bool UploadParameterValue() override; + + /** Set parameter value. **/ + virtual bool SetParameterVariantValue( + const FVariant & Variant, int32 Idx = 0, bool bTriggerModify = true, + bool bRecordUndo = true ) override; + + /** UObject methods. **/ + public: + + virtual void Serialize( FArchive & Ar ) override; + + public: + + /** Return color for this color parameter. **/ + FLinearColor GetColor() const; + + /** Called when new color is selected. **/ + void OnPaintColorChanged( FLinearColor InNewColor, bool bTriggerModify = true, bool bRecordUndo = true ); + + protected: + + /** Color for this property. **/ + FLinearColor Color; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFile.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFile.cpp new file mode 100644 index 00000000..f9879d1c --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFile.cpp @@ -0,0 +1,319 @@ +/* + * Copyright (c) <2017> Side Effects Software Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include "HoudiniAssetParameterFile.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniEngineString.h" + +#include "Internationalization/Internationalization.h" +#include "Misc/Paths.h" +#include "Misc/Variant.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetParameterFile::UHoudiniAssetParameterFile( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) + , Filters( TEXT( "" ) ) + , IsReadOnly( false) +{ + Values.Add( TEXT( "" ) ); +} + +UHoudiniAssetParameterFile * +UHoudiniAssetParameterFile::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo& ParmInfo ) +{ + UObject * Outer = InPrimaryObject; + if ( !Outer ) + { + Outer = InParentParameter; + if ( !Outer ) + { + // Must have either component or parent not null. + check( false ); + } + } + + UHoudiniAssetParameterFile * HoudiniAssetParameterFile = NewObject< UHoudiniAssetParameterFile >( + Outer, UHoudiniAssetParameterFile::StaticClass(), NAME_None, RF_Public | RF_Transactional ); + + HoudiniAssetParameterFile->CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ); + return HoudiniAssetParameterFile; +} + +bool +UHoudiniAssetParameterFile::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ) +{ + if ( !Super::CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ) ) + return false; + + // Look at the filechooser_mode tag to decide if the file param is read only + IsReadOnly = false; + FString FileChooserTag; + if (FHoudiniEngineUtils::HapiGetParameterTag(InNodeId, ParmId, TEXT("filechooser_mode"), FileChooserTag)) + { + if (FileChooserTag.Equals(TEXT("read"), ESearchCase::IgnoreCase)) + IsReadOnly = true; + } + + // We can only handle file types. + switch ( ParmInfo.type ) + { + case HAPI_PARMTYPE_PATH_FILE: + case HAPI_PARMTYPE_PATH_FILE_DIR: + { + break; + } + + case HAPI_PARMTYPE_PATH_FILE_GEO: + { + ParameterLabel += TEXT( " (geo)" ); + break; + } + + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + { + ParameterLabel += TEXT( " (img)" ); + break; + } + + default: + { + return false; + } + } + + // Assign internal Hapi values index. + SetValuesIndex( ParmInfo.stringValuesIndex ); + + // Get the actual value for this property. + TArray< HAPI_StringHandle > StringHandles; + StringHandles.SetNumZeroed( TupleSize ); + if ( FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), InNodeId, false, + &StringHandles[ 0 ], ValuesIndex, TupleSize ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + // Convert HAPI string handles to Unreal strings. + Values.SetNum( TupleSize ); + for ( int32 Idx = 0; Idx < TupleSize; ++Idx ) + { + FString ValueString = TEXT( "" ); + FHoudiniEngineString HoudiniEngineString( StringHandles[ Idx ] ); + HoudiniEngineString.ToFString( ValueString ); + + // Detect and update relative paths. + Values[ Idx ] = UpdateCheckRelativePath( ValueString ); + } + + // Retrieve filters for this file. + if ( ParmInfo.typeInfoSH > 0 ) + { + FHoudiniEngineString HoudiniEngineString( ParmInfo.typeInfoSH ); + if ( HoudiniEngineString.ToFString( Filters ) ) + { + if ( !Filters.IsEmpty() ) + ParameterLabel = FString::Printf( TEXT( "%s (%s)" ), *ParameterLabel, *Filters ); + } + } + + return true; +} + +void +UHoudiniAssetParameterFile::Serialize( FArchive & Ar ) +{ + // Call base implementation. + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + Ar << Values; + Ar << Filters; + + if ( HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY ) + Ar << IsReadOnly; +} + +bool +UHoudiniAssetParameterFile::UploadParameterValue() +{ + for ( int32 Idx = 0; Idx < Values.Num(); ++Idx ) + { + std::string ConvertedString = TCHAR_TO_UTF8( *( Values[ Idx ] ) ); + if ( FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NodeId, ConvertedString.c_str(), ParmId, Idx) != HAPI_RESULT_SUCCESS ) + { + return false; + } + } + + return Super::UploadParameterValue(); +} + +bool +UHoudiniAssetParameterFile::SetParameterVariantValue( + const FVariant & Variant, int32 Idx, bool bTriggerModify, + bool bRecordUndo ) +{ + EVariantTypes VariantType = Variant.GetType(); + if ( EVariantTypes::String == VariantType && Values.IsValidIndex(Idx)) + { + FString VariantStringValue = Variant.GetValue< FString >(); + +#if WITH_EDITOR + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterFileChange", "Houdini Parameter File: Changing a value" ), + PrimaryObject ); + + if ( !bRecordUndo ) + Transaction.Cancel(); + + Modify(); + +#endif // WITH_EDITOR + + // Detect and fix relative paths. + VariantStringValue = UpdateCheckRelativePath( VariantStringValue ); + + Values[ Idx ] = VariantStringValue; + MarkChanged( bTriggerModify ); + + return true; + } + + return false; +} + +#if WITH_EDITOR + +void +UHoudiniAssetParameterFile::HandleFilePathPickerPathPicked( const FString & PickedPath, int32 Idx ) +{ + if ( !Values.IsValidIndex(Idx) ) + return; + + if ( Values[Idx] == PickedPath ) + return; + + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterFileChange", "Houdini Parameter File: Changing a value" ), + PrimaryObject ); + Modify(); + + Values[ Idx ] = UpdateCheckRelativePath( PickedPath ); + + // Mark this parameter as changed. + MarkChanged(); +} + +#endif + +FString +UHoudiniAssetParameterFile::UpdateCheckRelativePath( const FString & PickedPath ) const +{ + if ( PrimaryObject && !PickedPath.IsEmpty() && FPaths::IsRelative( PickedPath ) ) + { + // Check if the path is relative to the UE4 project + FString AbsolutePath = FPaths::ConvertRelativePathToFull( PickedPath ); + if (FPaths::FileExists( AbsolutePath )) + { + return AbsolutePath; + } + + // Check if the path is relative to the asset + UHoudiniAssetComponent* Comp = Cast(PrimaryObject); + if ( Comp && !Comp->IsPendingKill() ) + { + if( Comp->HoudiniAsset && !Comp->HoudiniAsset->IsPendingKill() ) + { + FString AssetFilePath = FPaths::GetPath( Comp->HoudiniAsset->AssetFileName ); + if( FPaths::FileExists( AssetFilePath ) ) + { + FString UpdatedFileWidgetPath = FPaths::Combine( *AssetFilePath, *PickedPath ); + if( FPaths::FileExists( UpdatedFileWidgetPath ) ) + { + return UpdatedFileWidgetPath; + } + } + } + } + } + + return PickedPath; +} + +const FString & +UHoudiniAssetParameterFile::GetParameterValue( int32 Idx, const FString & DefaultValue ) const +{ + if ( Values.IsValidIndex(Idx) ) + return Values[ Idx ]; + + return DefaultValue; +} + +void +UHoudiniAssetParameterFile::SetParameterValue( const FString& InValue, int32 Idx, bool bTriggerModify, bool bRecordUndo ) +{ + if ( !Values.IsValidIndex(Idx) ) + return; + + if (Values[Idx] == InValue) + return; + +#if WITH_EDITOR + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterFileChange", "Houdini Parameter File: Changing a value" ), + PrimaryObject ); + + Modify(); + + if ( !bRecordUndo ) + Transaction.Cancel(); +#endif + + Values[ Idx ] = InValue; + MarkChanged( bTriggerModify ); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFile.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFile.h new file mode 100644 index 00000000..c627b130 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFile.h @@ -0,0 +1,99 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniAssetParameter.h" +#include "HoudiniAssetParameterFile.generated.h" + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterFile : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + /** Create instance of this class. **/ + static UHoudiniAssetParameterFile* Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ); + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ) override; + + /** Upload parameter value to HAPI. **/ + virtual bool UploadParameterValue() override; + + /** Set parameter value. **/ + virtual bool SetParameterVariantValue( + const FVariant & Variant, int32 Idx = 0, bool bTriggerModify = true, + bool bRecordUndo = true ) override; + + /** UObject methods. **/ + public: + + virtual void Serialize( FArchive & Ar ) override; + + protected: + +#if WITH_EDITOR + + void HandleFilePathPickerPathPicked( const FString & PickedPath, int32 Idx ); + +#endif + + public: + + /** Return value of this property with optional fallback. **/ + const FString & GetParameterValue( int32 Idx, const FString & DefaultValue ) const; + + /** Set value of this file property. **/ + void SetParameterValue( const FString & InValue, int32 Idx, bool bTriggerModify = true, bool bRecordUndo = true ); + + protected: + + /** Given a path check if it's relative. If it is, try to transform it into an absolute one. **/ + FString UpdateCheckRelativePath( const FString & PickedPath ) const; + + protected: + + /** Values of this property. **/ + TArray< FString > Values; + + /** Filters of this property. **/ + FString Filters; + + /** Is the file parameter read-only? **/ + bool IsReadOnly; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFloat.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFloat.cpp new file mode 100644 index 00000000..9b2b276c --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFloat.cpp @@ -0,0 +1,340 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetParameterFloat.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineString.h" + +#include "Misc/Variant.h" +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetParameterFloat::UHoudiniAssetParameterFloat( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) + , ValueMin( TNumericLimits< float >::Lowest() ) + , ValueMax( TNumericLimits< float >::Max() ) + , ValueUIMin( TNumericLimits< float >::Lowest() ) + , ValueUIMax( TNumericLimits< float >::Max() ) + , ValueUnit ( TEXT("") ) + , NoSwap ( false ) +{ + // Parameter will have at least one value. + Values.AddZeroed( 1 ); +} + +void +UHoudiniAssetParameterFloat::Serialize( FArchive & Ar ) +{ + // Call base implementation. + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + Ar << Values; + + Ar << ValueMin; + Ar << ValueMax; + + Ar << ValueUIMin; + Ar << ValueUIMax; + + if ( HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT ) + Ar << ValueUnit; + + if ( HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP ) + Ar << NoSwap; +} + +UHoudiniAssetParameterFloat * +UHoudiniAssetParameterFloat::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + UObject * Outer = InPrimaryObject; + if ( !Outer ) + { + Outer = InParentParameter; + if ( !Outer ) + { + // Must have either component or parent not null. + check( false ); + } + } + + UHoudiniAssetParameterFloat * HoudiniAssetParameterFloat = NewObject< UHoudiniAssetParameterFloat >( + Outer, UHoudiniAssetParameterFloat::StaticClass(), NAME_None, RF_Public | RF_Transactional ); + + HoudiniAssetParameterFloat->CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ); + return HoudiniAssetParameterFloat; +} + +bool +UHoudiniAssetParameterFloat::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + if ( !Super::CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ) ) + return false; + + // We can only handle float type. + if ( ParmInfo.type != HAPI_PARMTYPE_FLOAT ) + { + return false; + } + + // Assign internal Hapi values index. + SetValuesIndex( ParmInfo.floatValuesIndex ); + + if ( TupleSize <= 0 ) + return false; + + Values.SetNumZeroed(TupleSize); + + // Get the actual value for this property. + if ( FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InNodeId, &Values[ 0 ], + ValuesIndex, TupleSize ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + // Set min and max for this property. + if ( ParmInfo.hasMin ) + ValueMin = ParmInfo.min; + + if ( ParmInfo.hasMax ) + ValueMax = ParmInfo.max; + + bool bUsesDefaultMinMax = false; + + // Set min and max for UI for this property. + if ( ParmInfo.hasUIMin ) + { + ValueUIMin = ParmInfo.UIMin; + } + else + { + // If it is not set, use supplied min. + if ( ParmInfo.hasMin ) + { + ValueUIMin = ValueMin; + } + else + { + // Min value Houdini uses by default. + ValueUIMin = HAPI_UNREAL_PARAM_FLOAT_UI_MIN; + bUsesDefaultMinMax = true; + } + } + + if ( ParmInfo.hasUIMax ) + { + ValueUIMax = ParmInfo.UIMax; + } + else + { + // If it is not set, use supplied max. + if ( ParmInfo.hasMax ) + { + ValueUIMax = ValueMax; + } + else + { + // Max value Houdini uses by default. + ValueUIMax = HAPI_UNREAL_PARAM_FLOAT_UI_MAX; + bUsesDefaultMinMax = true; + } + } + + if ( bUsesDefaultMinMax ) + { + // If we are using defaults, we can detect some most common parameter names and alter defaults. + + FString LocalParameterName = TEXT( "" ); + FHoudiniEngineString HoudiniEngineString( ParmInfo.nameSH ); + HoudiniEngineString.ToFString( LocalParameterName ); + + static const FString ParameterNameTranslate( TEXT( HAPI_UNREAL_PARAM_TRANSLATE ) ); + static const FString ParameterNameRotate( TEXT( HAPI_UNREAL_PARAM_ROTATE ) ); + static const FString ParameterNameScale( TEXT( HAPI_UNREAL_PARAM_SCALE ) ); + static const FString ParameterNamePivot( TEXT( HAPI_UNREAL_PARAM_PIVOT ) ); + + if ( !LocalParameterName.IsEmpty() ) + { + if ( LocalParameterName.Equals( ParameterNameTranslate ) + || LocalParameterName.Equals( ParameterNameScale ) + || LocalParameterName.Equals( ParameterNamePivot ) ) + { + ValueUIMin = -1.0f; + ValueUIMax = 1.0f; + } + else if ( LocalParameterName.Equals( ParameterNameRotate ) ) + { + ValueUIMin = 0.0f; + ValueUIMax = 360.0f; + } + } + } + + // Get this parameter's unit if it has one + FHoudiniEngineUtils::HapiGetParameterUnit( InNodeId, ParmId, ValueUnit ); + + // Get this parameter's no swap tag if it has one + FHoudiniEngineUtils::HapiGetParameterNoSwapTag( InNodeId, ParmId, NoSwap ); + + return true; +} + +bool +UHoudiniAssetParameterFloat::UploadParameterValue() +{ + if ( Values.Num() <= 0 ) + return false; + + if ( FHoudiniApi::SetParmFloatValues( FHoudiniEngine::Get().GetSession(), NodeId, &Values[ 0 ], + ValuesIndex, TupleSize ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + return Super::UploadParameterValue(); +} + +bool +UHoudiniAssetParameterFloat::SetParameterVariantValue( const FVariant & Variant, int32 Idx, bool bTriggerModify, bool bRecordUndo ) +{ + EVariantTypes VariantType = Variant.GetType(); + float VariantValue = 0.0f; + + if ( !Values.IsValidIndex(Idx) ) + return false; + + if ( VariantType == EVariantTypes::Float ) + VariantValue = Variant.GetValue(); + else if ( VariantType == EVariantTypes::Double ) + VariantValue = (float) Variant.GetValue< double >(); + else + return false; + +#if WITH_EDITOR + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterFloatChange", "Houdini Parameter Float: Changing a value"), + PrimaryObject ); + + Modify(); + + if ( !bRecordUndo ) + Transaction.Cancel(); + +#endif // WITH_EDITOR + + Values[ Idx ] = VariantValue; + MarkChanged( bTriggerModify ); + + return true; +} + +TOptional< float > +UHoudiniAssetParameterFloat::GetValue( int32 Idx ) const +{ + if (Values.IsValidIndex(Idx)) + return TOptional< float >(Values[Idx]); + else + return TOptional< float >(); +} + +void +UHoudiniAssetParameterFloat::SetValue( float InValue, int32 Idx, bool bTriggerModify, bool bRecordUndo ) +{ + if (!Values.IsValidIndex(Idx)) + return; + + if (InValue == Values[Idx]) + return; + +#if WITH_EDITOR + + // If this is not a slider change (user typed in values manually), record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterFloatChange", "Houdini Parameter Float: Changing a value" ), + PrimaryObject ); + Modify(); + + if ( !bRecordUndo ) + Transaction.Cancel(); + +#endif // WITH_EDITOR + + Values[ Idx ] = FMath::Clamp< float >( InValue, ValueMin, ValueMax ); + + // Mark this parameter as changed. + MarkChanged( bTriggerModify ); +} + +float +UHoudiniAssetParameterFloat::GetParameterValue( int32 Idx, float DefaultValue ) const +{ + if ( !Values.IsValidIndex(Idx) ) + return DefaultValue; + + return Values[ Idx ]; +} + +#if WITH_EDITOR + +void +UHoudiniAssetParameterFloat::OnSliderMovingBegin( int32 Idx ) +{ + bSliderDragged = true; + + // We want to record undo increments only when user lets go of the slider. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterIntChange", "Houdini Parameter Float: Changing a value" ), + PrimaryObject ); + + Modify(); +} + +void +UHoudiniAssetParameterFloat::OnSliderMovingFinish( float InValue, int32 Idx ) +{ + bSliderDragged = false; + + // Mark this parameter as changed. + MarkChanged( true ); +} + +#endif // WITH_EDITOR + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFloat.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFloat.h new file mode 100644 index 00000000..81091313 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFloat.h @@ -0,0 +1,106 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniAssetParameter.h" +#include "HoudiniAssetParameterFloat.generated.h" + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterFloat : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + /** Create instance of this class. **/ + static UHoudiniAssetParameterFloat* Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ); + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ) override; + + /** Upload parameter value to HAPI. **/ + virtual bool UploadParameterValue() override; + + /** Set parameter value. **/ + virtual bool SetParameterVariantValue( + const FVariant & Variant, int32 Idx = 0, bool bTriggerModify = true, + bool bRecordUndo = true ) override; + + /** UObject methods. **/ + public: + + virtual void Serialize( FArchive & Ar ) override; + + public: + + /** Get value of this property, used by Slate. **/ + TOptional< float > GetValue( int32 Idx ) const; + + /** Set value of this property, used by Slate. **/ + void SetValue( float InValue, int32 Idx, bool bTriggerModify = true, bool bRecordUndo = true ); + + /** Return value of this property with optional fallback. **/ + float GetParameterValue( int32 Idx, float DefaultValue ) const; + +#if WITH_EDITOR + /** Delegate fired when slider for this property begins moving. **/ + void OnSliderMovingBegin( int32 Idx ); + + /** Delegate fired when slider for this property has finished moving. **/ + void OnSliderMovingFinish( float InValue, int32 Idx ); + +#endif // WITH_EDITOR + + protected: + + /** Values of this property. **/ + TArray< float > Values; + + /** Min and Max values for this property. **/ + float ValueMin; + float ValueMax; + + /** Min and Max values for UI for this property. **/ + float ValueUIMin; + float ValueUIMax; + + /** Unit for this property **/ + FString ValueUnit; + + /** Do we have the noswap tag? **/ + bool NoSwap; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFolder.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFolder.cpp new file mode 100644 index 00000000..08b7124c --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFolder.cpp @@ -0,0 +1,77 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetParameterFolder.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" + +UHoudiniAssetParameterFolder::UHoudiniAssetParameterFolder( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{} + +UHoudiniAssetParameterFolder * +UHoudiniAssetParameterFolder::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo) +{ + UObject * Outer = InPrimaryObject; + if ( !Outer ) + { + Outer = InParentParameter; + if ( !Outer ) + { + // Must have either component or parent not null. + check( false ); + } + } + + UHoudiniAssetParameterFolder * HoudiniAssetParameterFolder = + NewObject< UHoudiniAssetParameterFolder >( + Outer, UHoudiniAssetParameterFolder::StaticClass(), NAME_None, RF_Public | RF_Transactional ); + + HoudiniAssetParameterFolder->CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ); + return HoudiniAssetParameterFolder; +} + +bool +UHoudiniAssetParameterFolder::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + if ( !Super::CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ) ) + return false; + + // We can only handle folder and folder list types. + if ( ParmInfo.type != HAPI_PARMTYPE_FOLDER ) + return false; + + // Assign internal Hapi values index. + SetValuesIndex( ParmInfo.intValuesIndex ); + + return true; +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFolder.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFolder.h new file mode 100644 index 00000000..36f66974 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFolder.h @@ -0,0 +1,51 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniAssetParameter.h" +#include "HoudiniAssetParameterFolder.generated.h" + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterFolder : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + public: + + /** Create instance of this class. **/ + static UHoudiniAssetParameterFolder * Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ); + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) override; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFolderList.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFolderList.cpp new file mode 100644 index 00000000..d4648924 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFolderList.cpp @@ -0,0 +1,81 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetParameterFolderList.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetParameterFolder.h" +#include "HoudiniAssetComponent.h" + +UHoudiniAssetParameterFolderList::UHoudiniAssetParameterFolderList( const FObjectInitializer & ObjectInitializer) + : Super( ObjectInitializer ) +{} + +UHoudiniAssetParameterFolderList * +UHoudiniAssetParameterFolderList::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo) +{ + UObject * Outer = InPrimaryObject; + if ( !Outer ) + { + Outer = InParentParameter; + if ( !Outer ) + { + // Must have either component or parent not null. + check( false ); + } + } + + UHoudiniAssetParameterFolderList * HoudiniAssetParameterFolderList = + NewObject< UHoudiniAssetParameterFolderList >( + Outer, UHoudiniAssetParameterFolderList::StaticClass(), NAME_None, + RF_Public | RF_Transactional ); + + HoudiniAssetParameterFolderList->CreateParameter( + InPrimaryObject, InParentParameter, InNodeId, ParmInfo ); + + return HoudiniAssetParameterFolderList; +} + +bool +UHoudiniAssetParameterFolderList::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo) +{ + if ( !Super::CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ) ) + return false; + + // We can only handle folder and folder list types. + if ( ParmInfo.type != HAPI_PARMTYPE_FOLDERLIST ) + return false; + + // Assign internal Hapi values index. + SetValuesIndex( ParmInfo.intValuesIndex ); + + return true; +} + diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFolderList.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFolderList.h new file mode 100644 index 00000000..675c5f24 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterFolderList.h @@ -0,0 +1,54 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniAssetParameter.h" +#include "HoudiniAssetParameterFolderList.generated.h" + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterFolderList : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + /** Create instance of this class. **/ + static UHoudiniAssetParameterFolderList * Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ); + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, + const HAPI_ParmInfo & ParmInfo ) override; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterInt.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterInt.cpp new file mode 100644 index 00000000..1af2b2a7 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterInt.cpp @@ -0,0 +1,308 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetParameterInt.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" + +#include "Misc/Variant.h" +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetParameterInt::UHoudiniAssetParameterInt( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) + , ValueMin( TNumericLimits::Lowest() ) + , ValueMax( TNumericLimits::Max() ) + , ValueUIMin( TNumericLimits::Lowest() ) + , ValueUIMax( TNumericLimits::Max() ) +{ + // Parameter will have at least one value. + Values.AddZeroed( 1 ); +} + +UHoudiniAssetParameterInt * +UHoudiniAssetParameterInt::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + UObject * Outer = InPrimaryObject; + if ( !Outer ) + { + Outer = InParentParameter; + if ( !Outer ) + { + // Must have either component or parent not null. + check( false ); + } + } + + UHoudiniAssetParameterInt * HoudiniAssetParameterInt = NewObject< UHoudiniAssetParameterInt >( + Outer, UHoudiniAssetParameterInt::StaticClass(), NAME_None, RF_Public | RF_Transactional ); + + HoudiniAssetParameterInt->CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ); + return HoudiniAssetParameterInt; +} + +bool +UHoudiniAssetParameterInt::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + if ( !Super::CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ) ) + return false; + + // We can only handle integer type. + if ( ParmInfo.type != HAPI_PARMTYPE_INT ) + return false; + + // Assign internal Hapi values index. + SetValuesIndex( ParmInfo.intValuesIndex ); + + if ( TupleSize <= 0 ) + return false; + + // Get the actual value for this property. + Values.SetNumZeroed( TupleSize ); + if ( FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, &Values[ 0 ], + ValuesIndex, TupleSize ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + // Set min and max for this property. + if ( ParmInfo.hasMin ) + ValueMin = (int32) ParmInfo.min; + + if ( ParmInfo.hasMax ) + ValueMax = (int32) ParmInfo.max; + + // Set min and max for UI for this property. + if ( ParmInfo.hasUIMin ) + { + ValueUIMin = (int32) ParmInfo.UIMin; + } + else + { + // If it is not set, use supplied min. + if ( ParmInfo.hasMin ) + { + ValueUIMin = ValueMin; + } + else + { + // Min value Houdini uses by default. + ValueUIMin = HAPI_UNREAL_PARAM_INT_UI_MIN; + } + } + + if ( ParmInfo.hasUIMax ) + { + ValueUIMax = (int32) ParmInfo.UIMax; + } + else + { + // If it is not set, use supplied max. + if ( ParmInfo.hasMax ) + { + ValueUIMax = ValueMax; + } + else + { + // Max value Houdini uses by default. + ValueUIMax = HAPI_UNREAL_PARAM_INT_UI_MAX; + } + } + + // Get this parameter's unit if it has one + FHoudiniEngineUtils::HapiGetParameterUnit( InNodeId, ParmId, ValueUnit ); + + return true; +} + +bool +UHoudiniAssetParameterInt::UploadParameterValue() +{ + if ( Values.Num() <= 0 ) + return false; + + if ( FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), NodeId, &Values[ 0 ], + ValuesIndex, TupleSize) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + return Super::UploadParameterValue(); +} + +bool +UHoudiniAssetParameterInt::SetParameterVariantValue( const FVariant & Variant, int32 Idx, bool bTriggerModify, bool bRecordUndo ) +{ + EVariantTypes VariantType = Variant.GetType(); + int32 VariantValue = 0; + + if ( !Values.IsValidIndex(Idx) ) + return false; + + switch ( VariantType ) + { + case EVariantTypes::Int8: + case EVariantTypes::Int16: + case EVariantTypes::Int32: + case EVariantTypes::Int64: + case EVariantTypes::UInt8: + case EVariantTypes::UInt16: + case EVariantTypes::UInt32: + case EVariantTypes::UInt64: + { + VariantValue = Variant.GetValue< int32 >(); + break; + } + + default: + { + return false; + } + } + +#if WITH_EDITOR + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterIntChange", "Houdini Parameter Integer: Changing a value" ), + PrimaryObject ); + + Modify(); + + if ( !bRecordUndo ) + Transaction.Cancel(); + +#endif + + Values[ Idx ] = VariantValue; + MarkChanged( bTriggerModify ); + + return true; +} + +TOptional< int32 > +UHoudiniAssetParameterInt::GetValue( int32 Idx ) const +{ + if ( Values.IsValidIndex(Idx) ) + return TOptional< int32 >( Values[Idx] ); + else + return TOptional< int32 >(); +} + +void +UHoudiniAssetParameterInt::SetValue( int32 InValue, int32 Idx, bool bTriggerModify, bool bRecordUndo ) +{ + if (!Values.IsValidIndex(Idx)) + return; + + if ( Values[Idx] == InValue ) + return; + +#if WITH_EDITOR + + // If this is not a slider change (user typed in values manually), record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterIntChange", "Houdini Parameter Integer: Changing a value" ), + PrimaryObject ); + Modify(); + + if ( !bRecordUndo ) + Transaction.Cancel(); +#endif + + Values[ Idx ] = FMath::Clamp< int32 >( InValue, ValueMin, ValueMax ); + + // Mark this parameter as changed. + MarkChanged( bTriggerModify ); +} + +int32 +UHoudiniAssetParameterInt::GetParameterValue( int32 Idx, int32 DefaultValue ) const +{ + if ( !Values.IsValidIndex(Idx) ) + return DefaultValue; + + return Values[ Idx ]; +} + +#if WITH_EDITOR + +void +UHoudiniAssetParameterInt::OnSliderMovingBegin( int32 Idx ) +{ + bSliderDragged = true; + + // We want to record undo increments only when user lets go of the slider. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterIntChange", "Houdini Parameter Float: Changing a value" ), + PrimaryObject ); + + Modify(); +} + +void +UHoudiniAssetParameterInt::OnSliderMovingFinish( int32 InValue, int32 Idx ) +{ + bSliderDragged = false; + + // Mark this parameter as changed. + MarkChanged(true); +} + +#endif + +void +UHoudiniAssetParameterInt::Serialize( FArchive & Ar ) +{ + // Call base implementation. + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + Ar << Values; + + Ar << ValueMin; + Ar << ValueMax; + + Ar << ValueUIMin; + Ar << ValueUIMax; + + if ( HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT ) + Ar << ValueUnit; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterInt.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterInt.h new file mode 100644 index 00000000..7bd7c0f1 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterInt.h @@ -0,0 +1,102 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniAssetParameter.h" +#include "HoudiniAssetParameterInt.generated.h" + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterInt : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + /** Create instance of this class. **/ + static UHoudiniAssetParameterInt * Create( + UObject* InPrimaryObject, + UHoudiniAssetParameter* InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ); + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) override; + + /** Upload parameter value to HAPI. **/ + virtual bool UploadParameterValue() override; + + /** Set parameter value. **/ + virtual bool SetParameterVariantValue( + const FVariant & Variant, int32 Idx = 0, bool bTriggerModify = true, + bool bRecordUndo = true ) override; + + /** UObject methods. **/ + public: + + virtual void Serialize( FArchive & Ar ) override; + + public: + + /** Get value of this property, used by Slate. **/ + TOptional< int32 > GetValue( int32 Idx ) const; + + /** Set value of this property, used by Slate. **/ + void SetValue( int32 InValue, int32 Idx, bool bTriggerModify = true, bool bRecordUndo = true ); + + /** Return value of this property with optional fallback. **/ + int32 GetParameterValue( int32 Idx, int32 DefaultValue ) const; + +#if WITH_EDITOR + + /** Delegate fired when slider for this property begins moving. **/ + void OnSliderMovingBegin( int32 Idx ); + + /** Delegate fired when slider for this property has finished moving. **/ + void OnSliderMovingFinish( int32 InValue, int32 Idx ); + +#endif + + protected: + + /** Values of this property. **/ + TArray< int32 > Values; + + /** Min and Max values for this property. **/ + int32 ValueMin; + int32 ValueMax; + + /** Min and Max values for UI for this property. **/ + int32 ValueUIMin; + int32 ValueUIMax; + + /** Unit for this property **/ + FString ValueUnit; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterLabel.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterLabel.cpp new file mode 100644 index 00000000..280b887e --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterLabel.cpp @@ -0,0 +1,74 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetParameterLabel.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +UHoudiniAssetParameterLabel::UHoudiniAssetParameterLabel( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{} + +UHoudiniAssetParameterLabel * +UHoudiniAssetParameterLabel::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + UObject * Outer = InPrimaryObject; + if ( !Outer ) + { + Outer = InParentParameter; + if ( !Outer ) + { + // Must have either component or parent not null. + check( false ); + } + } + + UHoudiniAssetParameterLabel * HoudiniAssetParameterLabel = NewObject< UHoudiniAssetParameterLabel >( + Outer, UHoudiniAssetParameterLabel::StaticClass(), NAME_None, RF_Public | RF_Transactional ); + + HoudiniAssetParameterLabel->CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ); + return HoudiniAssetParameterLabel; +} + +bool +UHoudiniAssetParameterLabel::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo) +{ + if ( !Super::CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ) ) + return false; + + // We can only handle label type. + if ( ParmInfo.type != HAPI_PARMTYPE_LABEL ) + return false; + + // Assign internal Hapi values index. + SetValuesIndex( ParmInfo.stringValuesIndex ); + + return true; +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterLabel.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterLabel.h new file mode 100644 index 00000000..55fa7a23 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterLabel.h @@ -0,0 +1,52 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniAssetParameter.h" +#include "HoudiniAssetParameterLabel.generated.h" + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterLabel : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + /** Create instance of this class. **/ + static UHoudiniAssetParameterLabel * Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ); + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) override; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterMultiparm.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterMultiparm.cpp new file mode 100644 index 00000000..d399e721 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterMultiparm.cpp @@ -0,0 +1,312 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetParameterMultiparm.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" + +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetParameterMultiparm::UHoudiniAssetParameterMultiparm( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) + , MultiparmValue( 0 ) + , LastModificationType( RegularValueChange ) + , LastRemoveAddInstanceIndex( -1 ) +{} + +UHoudiniAssetParameterMultiparm * +UHoudiniAssetParameterMultiparm::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo) +{ + UObject * Outer = InPrimaryObject; + if ( !Outer ) + { + Outer = InParentParameter; + if ( !Outer ) + { + // Must have either component or parent not null. + check( false ); + } + } + + UHoudiniAssetParameterMultiparm * HoudiniAssetParameterMultiparm = NewObject< UHoudiniAssetParameterMultiparm >( + Outer, UHoudiniAssetParameterMultiparm::StaticClass(), NAME_None, RF_Public | RF_Transactional ); + + HoudiniAssetParameterMultiparm->CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ); + return HoudiniAssetParameterMultiparm; +} + +bool +UHoudiniAssetParameterMultiparm::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + if ( !Super::CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ) ) + return false; + + if ( ParmInfo.type != HAPI_PARMTYPE_MULTIPARMLIST ) + return false; + + // Assign internal Hapi values index. + SetValuesIndex( ParmInfo.intValuesIndex ); + + // Get the actual value for this property. + MultiparmValue = 0; + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, &MultiparmValue, ValuesIndex, 1 ) + , false ); + return true; +} + +#if WITH_EDITOR + +void +UHoudiniAssetParameterMultiparm::AddMultiparmInstance( int32 ChildMultiparmInstanceIndex ) +{ + // Set the last modification type and instance index before the current state + // is saved by Modify(). + LastModificationType = InstanceAdded; + LastRemoveAddInstanceIndex = ChildMultiparmInstanceIndex - 1; // Added above the current one. + + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterMultiparmChange", "Houdini Parameter Multiparm: Adding instance" ), + PrimaryObject ); + Modify(); + + FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + ChildMultiparmInstanceIndex ); + + MultiparmValue++; + + // Save the Redo modification type (should be the opposite operation to this one). + LastModificationType = InstanceRemoved; + LastRemoveAddInstanceIndex = ChildMultiparmInstanceIndex; + + // Mark this parameter as changed. + MarkChanged(); +} + +void +UHoudiniAssetParameterMultiparm::RemoveMultiparmInstance( int32 ChildMultiparmInstanceIndex ) +{ + // Set the last modification type and instance index before the current state + // is saved by Modify(). + LastModificationType = InstanceRemoved; + LastRemoveAddInstanceIndex = ChildMultiparmInstanceIndex; + + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterMultiparmChange", "Houdini Parameter Multiparm: Removing instance" ), + PrimaryObject ); + Modify(); + + FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + ChildMultiparmInstanceIndex ); + + MultiparmValue--; + + // Save the Redo modification type (should be the opposite operation to this one). + LastModificationType = InstanceAdded; + LastRemoveAddInstanceIndex = ChildMultiparmInstanceIndex; + + // Mark this parameter as changed. + MarkChanged(); +} + +#endif + +bool +UHoudiniAssetParameterMultiparm::UploadParameterValue() +{ + if ( FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), NodeId, + &MultiparmValue, ValuesIndex, 1 ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + return Super::UploadParameterValue(); +} + +TOptional< int32 > +UHoudiniAssetParameterMultiparm::GetValue() const +{ + return TOptional< int32 >( MultiparmValue ); +} + +void +UHoudiniAssetParameterMultiparm::SetValue( int32 InValue ) +{ + if ( MultiparmValue != InValue ) + { + LastModificationType = RegularValueChange; + LastRemoveAddInstanceIndex = -1; + +#if WITH_EDITOR + + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterMultiparmChange", "Houdini Parameter Multiparm: Changing a value" ), + PrimaryObject ); + Modify(); + +#endif + + MultiparmValue = InValue; + + // Mark this parameter as changed. + MarkChanged(); + } +} + +void +UHoudiniAssetParameterMultiparm::AddElement( bool bTriggerModify, bool bRecordUndo ) +{ + AddElements( 1, bTriggerModify, bRecordUndo ); +} + +void +UHoudiniAssetParameterMultiparm::AddElements( int32 NumElements, bool bTriggerModify, bool bRecordUndo ) +{ + if ( NumElements > 0 ) + { + LastModificationType = RegularValueChange; + LastRemoveAddInstanceIndex = -1; + +#if WITH_EDITOR + + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterMultiparmChange", "Houdini Parameter Multiparm: Changing a value" ), + PrimaryObject ); + Modify(); + + if ( !bRecordUndo ) + Transaction.Cancel(); + +#endif + + MultiparmValue += NumElements; + + MarkChanged( bTriggerModify ); + } +} + +void +UHoudiniAssetParameterMultiparm::RemoveElement( bool bTriggerModify, bool bRecordUndo ) +{ + RemoveElements( 1, bTriggerModify, bRecordUndo ); +} + +void +UHoudiniAssetParameterMultiparm::RemoveElements( int32 NumElements, bool bTriggerModify, bool bRecordUndo ) +{ + if ( NumElements > 0 ) + { + if ( MultiparmValue - NumElements < 0 ) + NumElements = MultiparmValue; + + LastModificationType = RegularValueChange; + LastRemoveAddInstanceIndex = -1; + +#if WITH_EDITOR + + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterMultiparmChange", "Houdini Parameter Multiparm: Changing a value" ), + PrimaryObject ); + Modify(); + + if ( !bRecordUndo ) + Transaction.Cancel(); + +#endif + + MultiparmValue -= NumElements; + + MarkChanged( bTriggerModify ); + } +} + +void +UHoudiniAssetParameterMultiparm::Serialize( FArchive & Ar ) +{ + // Call base implementation. + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + if ( Ar.IsTransacting() ) + { + SerializeEnumeration( Ar, LastModificationType ); + Ar << LastRemoveAddInstanceIndex; + } + + if ( Ar.IsLoading() ) + MultiparmValue = 0; + + Ar << MultiparmValue; +} + + +#if WITH_EDITOR + +void +UHoudiniAssetParameterMultiparm::PostEditUndo() +{ + if ( LastModificationType == InstanceAdded ) + { + FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + LastRemoveAddInstanceIndex ); + } + else if( LastModificationType == InstanceRemoved ) + { + FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + LastRemoveAddInstanceIndex ); + } + + Super::PostEditUndo(); +} + +#endif + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterMultiparm.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterMultiparm.h new file mode 100644 index 00000000..523169ea --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterMultiparm.h @@ -0,0 +1,111 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniAssetParameter.h" +#include "HoudiniAssetParameterMultiparm.generated.h" + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterMultiparm : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + /** Create instance of this class. **/ + static UHoudiniAssetParameterMultiparm * Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ); + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) override; + +#if WITH_EDITOR + + /** Add multiparm instance. **/ + void AddMultiparmInstance( int32 ChildMultiparmInstanceIndex ); + + /** Remove multiparm instance. **/ + void RemoveMultiparmInstance( int32 ChildMultiparmInstanceIndex ); + +#endif + + /** Upload parameter value to HAPI. **/ + virtual bool UploadParameterValue() override; + + /** UObject methods. **/ + public: + + virtual void Serialize( FArchive & Ar ) override; + +#if WITH_EDITOR + + virtual void PostEditUndo() override; + +#endif + + /** Get value of this property, used by Slate. **/ + TOptional< int32 > GetValue() const; + + /** Set value of this property, used by Slate. **/ + void SetValue( int32 InValue ); + + /** Increment value, used by Slate. **/ + void AddElement( bool bTriggerModify = true, bool bRecordUndo = true ); + void AddElements( int32 NumElements, bool bTriggerModify = true, bool bRecordUndo = true ); + + /** Decrement value, used by Slate. **/ + void RemoveElement( bool bTriggerModify = true, bool bRecordUndo = true ); + void RemoveElements( int32 NumElements, bool bTriggerModify = true, bool bRecordUndo = true ); + + protected: + + /** Value of this property. **/ + int32 MultiparmValue; + + private: + + enum ModificationType + { + RegularValueChange, + InstanceAdded, + InstanceRemoved + }; + + /** Last modification type. **/ + ModificationType LastModificationType; + + /** Last remove/add instance index. **/ + int32 LastRemoveAddInstanceIndex; + +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterRamp.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterRamp.cpp new file mode 100644 index 00000000..805b46a4 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterRamp.cpp @@ -0,0 +1,779 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetParameterRamp.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetParameterColor.h" +#include "HoudiniAssetParameterChoice.h" +#include "HoudiniAssetParameterFloat.h" +#include "HoudiniAssetComponent.h" +#include "Curves/CurveBase.h" +#include "Framework/Application/SlateApplication.h" +#if WITH_EDITOR + #include "SCurveEditor.h" +#endif + +UHoudiniAssetParameterRampCurveFloat::UHoudiniAssetParameterRampCurveFloat( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{} + +#if WITH_EDITOR + +void +UHoudiniAssetParameterRampCurveFloat::OnCurveChanged( const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos ) +{ + Super::OnCurveChanged( ChangedCurveEditInfos ); + + if ( HoudiniAssetParameterRamp.IsValid() ) + HoudiniAssetParameterRamp->OnCurveFloatChanged( this ); +} + +#endif + +void +UHoudiniAssetParameterRampCurveFloat::SetParentRampParameter( UHoudiniAssetParameterRamp * InHoudiniAssetParameterRamp ) +{ + HoudiniAssetParameterRamp = InHoudiniAssetParameterRamp; +} + +UHoudiniAssetParameterRampCurveColor::UHoudiniAssetParameterRampCurveColor( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) + , ColorEvent( EHoudiniAssetParameterRampCurveColorEvent::None ) +{} + +#if WITH_EDITOR + +void +UHoudiniAssetParameterRampCurveColor::OnCurveChanged( const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos ) +{ + Super::OnCurveChanged( ChangedCurveEditInfos ); + + if ( HoudiniAssetParameterRamp.IsValid() ) + HoudiniAssetParameterRamp->OnCurveColorChanged( this ); + + // FIXME + // Unfortunately this will not work as SColorGradientEditor is missing OnCurveChange callback calls. + // This is most likely UE4 bug. +} + +#endif + +bool +UHoudiniAssetParameterRampCurveColor::Modify( bool bAlwaysMarkDirty ) +{ + ColorEvent = GetEditorCurveTransaction(); + return Super::Modify(bAlwaysMarkDirty); +} + +EHoudiniAssetParameterRampCurveColorEvent::Type +UHoudiniAssetParameterRampCurveColor::GetEditorCurveTransaction() const +{ + EHoudiniAssetParameterRampCurveColorEvent::Type TransactionType = EHoudiniAssetParameterRampCurveColorEvent::None; + +#if WITH_EDITOR + + if ( GEditor ) + { + const FString & TransactionName = GEditor->GetTransactionName().ToString(); + + if ( TransactionName.Equals( TEXT( "Move Gradient Stop" ) ) ) + TransactionType = EHoudiniAssetParameterRampCurveColorEvent::MoveStop; + else if ( TransactionName.Equals( TEXT( "Add Gradient Stop" ) ) ) + TransactionType = EHoudiniAssetParameterRampCurveColorEvent::AddStop; + else if ( TransactionName.Equals( TEXT( "Delete Gradient Stop" ) ) ) + TransactionType = EHoudiniAssetParameterRampCurveColorEvent::RemoveStop; + else if ( TransactionName.Equals( TEXT( "Change Gradient Stop Time" ) ) ) + TransactionType = EHoudiniAssetParameterRampCurveColorEvent::ChangeStopTime; + else if ( TransactionName.Equals( TEXT( "Change Gradient Stop Color" ) ) ) + TransactionType = EHoudiniAssetParameterRampCurveColorEvent::ChangeStopColor; + else + TransactionType = EHoudiniAssetParameterRampCurveColorEvent::None; + } + else + { + TransactionType = EHoudiniAssetParameterRampCurveColorEvent::None; + } + +#endif + + return TransactionType; +} + +void +UHoudiniAssetParameterRampCurveColor::SetParentRampParameter( UHoudiniAssetParameterRamp * InHoudiniAssetParameterRamp ) +{ + HoudiniAssetParameterRamp = InHoudiniAssetParameterRamp; +} + +EHoudiniAssetParameterRampCurveColorEvent::Type +UHoudiniAssetParameterRampCurveColor::GetColorEvent() const +{ + return ColorEvent; +} + +void +UHoudiniAssetParameterRampCurveColor::ResetColorEvent() +{ + ColorEvent = EHoudiniAssetParameterRampCurveColorEvent::None; +} + +bool +UHoudiniAssetParameterRampCurveColor::IsTickableInEditor() const +{ + return true; +} + +bool +UHoudiniAssetParameterRampCurveColor::IsTickableWhenPaused() const +{ + return true; +} + +void +UHoudiniAssetParameterRampCurveColor::Tick( float DeltaTime ) +{ + if ( HoudiniAssetParameterRamp.IsValid() ) + { + +#if WITH_EDITOR + + if ( GEditor && !GEditor->IsTransactionActive() ) + { + switch ( ColorEvent ) + { + case EHoudiniAssetParameterRampCurveColorEvent::ChangeStopTime: + case EHoudiniAssetParameterRampCurveColorEvent::ChangeStopColor: + { + // If color picker is open, we need to wait until it is closed. + TSharedPtr< SWindow > ActiveTopLevelWindow = FSlateApplication::Get().GetActiveTopLevelWindow(); + if ( ActiveTopLevelWindow.IsValid() ) + { + const FString& ActiveTopLevelWindowTitle = ActiveTopLevelWindow->GetTitle().ToString(); + if ( ActiveTopLevelWindowTitle.Equals( TEXT( "Color Picker" ) ) ) + return; + } + } + + default: + { + break; + } + } + } + + // Notify parent ramp parameter that color has changed. + HoudiniAssetParameterRamp->OnCurveColorChanged( this ); + +#endif + + } + else + { + // If we are ticking for whatever reason, stop. + ResetColorEvent(); + } +} + +TStatId +UHoudiniAssetParameterRampCurveColor::GetStatId() const +{ + RETURN_QUICK_DECLARE_CYCLE_STAT( UHoudiniAssetParameterRampCurveColor, STATGROUP_Tickables ); +} + +bool +UHoudiniAssetParameterRampCurveColor::IsTickable() const +{ +#if WITH_EDITOR + + if ( GEditor ) + { + return ColorEvent != EHoudiniAssetParameterRampCurveColorEvent::None; + } + +#endif + + return false; +} + +const EHoudiniAssetParameterRampKeyInterpolation::Type +UHoudiniAssetParameterRamp::DefaultSplineInterpolation = EHoudiniAssetParameterRampKeyInterpolation::MonotoneCubic; + +const EHoudiniAssetParameterRampKeyInterpolation::Type +UHoudiniAssetParameterRamp::DefaultUnknownInterpolation = EHoudiniAssetParameterRampKeyInterpolation::Linear; + +UHoudiniAssetParameterRamp::UHoudiniAssetParameterRamp( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) + , HoudiniAssetParameterRampCurveFloat( nullptr ) + , HoudiniAssetParameterRampCurveColor( nullptr ) + , bIsFloatRamp( true ) + , bIsCurveChanged( false ) + , bIsCurveUploadRequired( false ) +{ +#if WITH_EDITOR + CurveEditor = nullptr; +#endif +} + +UHoudiniAssetParameterRamp::~UHoudiniAssetParameterRamp() +{ +#if WITH_EDITOR + // We need to properly remove CurveOwner on CurveEditor or this could + // cause a crash upon changing the level with the details up + if ( CurveEditor.IsValid() ) + { + CurveEditor->SetCurveOwner( nullptr ); + CurveEditor = nullptr; + } +#endif +} + +UHoudiniAssetParameter * +UHoudiniAssetParameterRamp::Duplicate( UObject* InOuter ) +{ + if( UHoudiniAssetParameterRamp* NewParm = Cast( Super::Duplicate( InOuter ) ) ) + { + // The duplicate has had PostLoad called, so we need to fix ownership of the curve subobjects + if( HoudiniAssetParameterRampCurveColor ) + { + NewParm->HoudiniAssetParameterRampCurveColor = DuplicateObject( HoudiniAssetParameterRampCurveColor, InOuter ); + NewParm->HoudiniAssetParameterRampCurveColor->SetParentRampParameter( NewParm ); + HoudiniAssetParameterRampCurveColor->SetParentRampParameter( this ); + } + + if( HoudiniAssetParameterRampCurveFloat ) + { + NewParm->HoudiniAssetParameterRampCurveFloat = DuplicateObject( HoudiniAssetParameterRampCurveFloat, InOuter ); + NewParm->HoudiniAssetParameterRampCurveFloat->SetParentRampParameter( NewParm ); + HoudiniAssetParameterRampCurveFloat->SetParentRampParameter( this ); + } + return NewParm; + } + return nullptr; +} + +UHoudiniAssetParameterRamp * +UHoudiniAssetParameterRamp::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + UObject * Outer = InPrimaryObject; + if ( !Outer ) + { + Outer = InParentParameter; + if ( !Outer ) + { + // Must have either component or parent not null. + check( false ); + } + } + + UHoudiniAssetParameterRamp * HoudiniAssetParameterRamp = NewObject< UHoudiniAssetParameterRamp >( + Outer, UHoudiniAssetParameterRamp::StaticClass(), NAME_None, RF_Public | RF_Transactional ); + + HoudiniAssetParameterRamp->CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ); + + return HoudiniAssetParameterRamp; +} + +bool +UHoudiniAssetParameterRamp::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + if ( !Super::CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ) ) + return false; + + if ( ParmInfo.rampType == HAPI_RAMPTYPE_FLOAT ) + { + bIsFloatRamp = true; + } + else if ( ParmInfo.rampType == HAPI_RAMPTYPE_COLOR ) + { + bIsFloatRamp = false; + } + else + { + return false; + } + + return true; +} + +void +UHoudiniAssetParameterRamp::NotifyChildParametersCreated() +{ + if ( bIsCurveUploadRequired ) + { + bIsCurveChanged = true; + OnCurveEditingFinished(); + bIsCurveUploadRequired = false; + } + else + { + GenerateCurvePoints(); + +#if WITH_EDITOR + // This causes UI flickering when moving/adding points + //if ( HoudiniAssetParameterRampCurveColor ) + // OnParamStateChanged(); + +#endif + + } +} + +void +UHoudiniAssetParameterRamp::OnCurveFloatChanged( UHoudiniAssetParameterRampCurveFloat * CurveFloat ) +{ + if ( !CurveFloat ) + return; + + FRichCurve & RichCurve = CurveFloat->FloatCurve; + + if ( RichCurve.GetNumKeys() < MultiparmValue ) + { + // Keys have been removed. + bIsCurveUploadRequired = true; + RemoveElements( MultiparmValue - RichCurve.GetNumKeys() ); + } + else if ( RichCurve.GetNumKeys() > MultiparmValue ) + { + // Keys have been added. + bIsCurveUploadRequired = true; + AddElements( RichCurve.GetNumKeys() - MultiparmValue ); + } + else + { + // We have curve point modification. + bIsCurveChanged = true; + } +} + +void +UHoudiniAssetParameterRamp::OnCurveColorChanged( UHoudiniAssetParameterRampCurveColor * CurveColor ) +{ + if ( !CurveColor ) + return; + + EHoudiniAssetParameterRampCurveColorEvent::Type ColorEvent = CurveColor->GetColorEvent(); + switch( ColorEvent ) + { + case EHoudiniAssetParameterRampCurveColorEvent::AddStop: + { + bIsCurveUploadRequired = true; + AddElement(); + break; + } + + case EHoudiniAssetParameterRampCurveColorEvent::RemoveStop: + { + bIsCurveUploadRequired = true; + RemoveElement(); + break; + } + + case EHoudiniAssetParameterRampCurveColorEvent::ChangeStopTime: + case EHoudiniAssetParameterRampCurveColorEvent::ChangeStopColor: + { + // We have curve point modification. + bIsCurveChanged = true; + OnCurveEditingFinished(); + break; + } + + case EHoudiniAssetParameterRampCurveColorEvent::MoveStop: + { + // We have curve point modification. + bIsCurveChanged = true; + // WITH UE4 FIX + //OnCurveEditingFinished(); + // WITHOUT UE4 FIX + OnCurveEditingFinished(); + break; + } + + default: + { + /*// WITH UE4 FIX + if ( bIsCurveChanged ) + OnCurveEditingFinished(); + */ + break; + } + } + + CurveColor->ResetColorEvent(); +} + +void +UHoudiniAssetParameterRamp::OnCurveEditingFinished() +{ + if ( bIsCurveChanged ) + { + if ( MultiparmValue * 3 != ChildParameters.Num() ) + return; + + bIsCurveChanged = false; + + if ( HoudiniAssetParameterRampCurveFloat && !HoudiniAssetParameterRampCurveFloat->IsPendingKill() ) + { + FRichCurve & RichCurve = HoudiniAssetParameterRampCurveFloat->FloatCurve; + + // We need to update ramp key positions. + for ( int32 KeyIdx = 0, KeyNum = RichCurve.GetNumKeys(); KeyIdx < KeyNum; ++KeyIdx ) + { + UHoudiniAssetParameterFloat * ChildParamPosition = nullptr; + UHoudiniAssetParameterFloat * ChildParamValue = nullptr; + UHoudiniAssetParameterChoice * ChildParamInterpolation = nullptr; + + if ( !GetRampKeysCurveFloat( KeyIdx, ChildParamPosition, ChildParamValue, ChildParamInterpolation ) ) + continue; + + const FRichCurveKey & RichCurveKey = RichCurve.Keys[ KeyIdx ]; + + if ( !ChildParamPosition->IsPendingKill() ) + ChildParamPosition->SetValue( RichCurveKey.Time, 0, false, false ); + + if ( !ChildParamValue->IsPendingKill() ) + ChildParamValue->SetValue( RichCurveKey.Value, 0, false, false ); + + EHoudiniAssetParameterRampKeyInterpolation::Type RichCurveKeyInterpolation = + TranslateUnrealRampKeyInterpolation( RichCurveKey.InterpMode ); + + if ( !ChildParamInterpolation->IsPendingKill() ) + ChildParamInterpolation->SetValueInt( (int32) RichCurveKeyInterpolation, false, false ); + } + + MarkChanged(); + } + else if ( HoudiniAssetParameterRampCurveColor ) + { + FRichCurve & RichCurveR = HoudiniAssetParameterRampCurveColor->FloatCurves[ 0 ]; + FRichCurve & RichCurveG = HoudiniAssetParameterRampCurveColor->FloatCurves[ 1 ]; + FRichCurve & RichCurveB = HoudiniAssetParameterRampCurveColor->FloatCurves[ 2 ]; + FRichCurve & RichCurveA = HoudiniAssetParameterRampCurveColor->FloatCurves[ 3 ]; + + // We need to update ramp key positions. + for ( int32 KeyIdx = 0, KeyNum = RichCurveR.GetNumKeys(); KeyIdx < KeyNum; ++KeyIdx ) + { + UHoudiniAssetParameterFloat * ChildParamPosition = nullptr; + UHoudiniAssetParameterColor * ChildParamColor = nullptr; + UHoudiniAssetParameterChoice * ChildParamInterpolation = nullptr; + + if ( !GetRampKeysCurveColor( KeyIdx, ChildParamPosition, ChildParamColor, ChildParamInterpolation ) ) + continue; + + const FRichCurveKey & RichCurveKeyR = RichCurveR.Keys[ KeyIdx ]; + const FRichCurveKey & RichCurveKeyG = RichCurveG.Keys[ KeyIdx ]; + const FRichCurveKey & RichCurveKeyB = RichCurveB.Keys[ KeyIdx ]; + //const FRichCurveKey & RichCurveKeyA = RichCurveA.Keys[ KeyIdx ]; + + if ( !ChildParamPosition->IsPendingKill() ) + ChildParamPosition->SetValue( RichCurveKeyR.Time, 0, false, false ); + + FLinearColor KeyColor( RichCurveKeyR.Value, RichCurveKeyG.Value, RichCurveKeyB.Value, 1.0f ); + if ( !ChildParamColor->IsPendingKill() ) + ChildParamColor->OnPaintColorChanged( KeyColor, false, false ); + + EHoudiniAssetParameterRampKeyInterpolation::Type RichCurveKeyInterpolation = + TranslateUnrealRampKeyInterpolation( RichCurveKeyR.InterpMode ); + + if ( !ChildParamInterpolation->IsPendingKill() ) + ChildParamInterpolation->SetValueInt( (int32) RichCurveKeyInterpolation, false, false ); + } + + MarkChanged(); + } + } +} + +void +UHoudiniAssetParameterRamp::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniAssetParameterRamp * HoudiniAssetParameterRamp = Cast< UHoudiniAssetParameterRamp >( InThis ); + if ( HoudiniAssetParameterRamp ) + { + if ( HoudiniAssetParameterRamp->HoudiniAssetParameterRampCurveFloat ) + Collector.AddReferencedObject( HoudiniAssetParameterRamp->HoudiniAssetParameterRampCurveFloat, InThis ); + + if ( HoudiniAssetParameterRamp->HoudiniAssetParameterRampCurveColor ) + Collector.AddReferencedObject( HoudiniAssetParameterRamp->HoudiniAssetParameterRampCurveColor, InThis ); + } + + // Call base implementation. + Super::AddReferencedObjects( InThis, Collector ); +} + +void +UHoudiniAssetParameterRamp::Serialize( FArchive & Ar ) +{ + // Call base implementation. + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + Ar << HoudiniAssetParameterRampCurveFloat; + Ar << HoudiniAssetParameterRampCurveColor; + + Ar << bIsFloatRamp; + + if ( Ar.IsLoading() ) + { + bIsCurveChanged = false; + bIsCurveUploadRequired = false; + } +} + +void +UHoudiniAssetParameterRamp::PostLoad() +{ + Super::PostLoad(); + + if ( HoudiniAssetParameterRampCurveFloat ) + HoudiniAssetParameterRampCurveFloat->SetParentRampParameter( this ); + + if ( HoudiniAssetParameterRampCurveColor ) + HoudiniAssetParameterRampCurveColor->SetParentRampParameter( this ); +} + +void +UHoudiniAssetParameterRamp::GenerateCurvePoints() +{ + if ( ChildParameters.Num() % 3 != 0 ) + { + HOUDINI_LOG_MESSAGE( + TEXT( "Invalid Ramp parameter [%s] : Number of child parameters is not a tuple of 3." ), + *ParameterName ); + + return; + } + + if ( HoudiniAssetParameterRampCurveFloat ) + { + HoudiniAssetParameterRampCurveFloat->ResetCurve(); + + for ( int32 ChildIdx = 0, ChildNum = GetRampKeyCount(); ChildIdx < ChildNum; ++ChildIdx ) + { + UHoudiniAssetParameterFloat * ChildParamPosition = nullptr; + UHoudiniAssetParameterFloat * ChildParamValue = nullptr; + UHoudiniAssetParameterChoice * ChildParamInterpolation = nullptr; + + if ( !GetRampKeysCurveFloat(ChildIdx, ChildParamPosition, ChildParamValue, ChildParamInterpolation ) ) + { + HoudiniAssetParameterRampCurveFloat->ResetCurve(); + return; + } + + float CurveKeyPosition = ChildParamPosition->GetParameterValue( 0, 0.0f ); + float CurveKeyValue = ChildParamValue->GetParameterValue( 0, 0.0f ); + EHoudiniAssetParameterRampKeyInterpolation::Type RampKeyInterpolation = + TranslateChoiceKeyInterpolation( ChildParamInterpolation ); + ERichCurveInterpMode RichCurveInterpMode = TranslateHoudiniRampKeyInterpolation( RampKeyInterpolation ); + + FRichCurve & RichCurve = HoudiniAssetParameterRampCurveFloat->FloatCurve; + + FKeyHandle const KeyHandle = RichCurve.AddKey( CurveKeyPosition, CurveKeyValue ); + RichCurve.SetKeyInterpMode( KeyHandle, RichCurveInterpMode ); + } + } + else if ( HoudiniAssetParameterRampCurveColor ) + { + HoudiniAssetParameterRampCurveColor->ResetCurve(); + + for ( int32 ChildIdx = 0, ChildNum = GetRampKeyCount(); ChildIdx < ChildNum; ++ChildIdx ) + { + UHoudiniAssetParameterFloat * ChildParamPosition = nullptr; + UHoudiniAssetParameterColor * ChildParamColor = nullptr; + UHoudiniAssetParameterChoice * ChildParamInterpolation = nullptr; + + if ( !GetRampKeysCurveColor( ChildIdx, ChildParamPosition, ChildParamColor, ChildParamInterpolation ) ) + { + HoudiniAssetParameterRampCurveColor->ResetCurve(); + return; + } + + float CurveKeyPosition = ChildParamPosition->GetParameterValue( 0, 0.0f ); + FLinearColor CurveKeyValue = ChildParamColor->GetColor(); + EHoudiniAssetParameterRampKeyInterpolation::Type RampKeyInterpolation = + TranslateChoiceKeyInterpolation( ChildParamInterpolation ); + ERichCurveInterpMode RichCurveInterpMode = TranslateHoudiniRampKeyInterpolation( RampKeyInterpolation ); + + for ( int CurveIdx = 0; CurveIdx < 4; ++CurveIdx ) + { + FRichCurve & RichCurve = HoudiniAssetParameterRampCurveColor->FloatCurves[ CurveIdx ]; + + FKeyHandle const KeyHandle = + RichCurve.AddKey( CurveKeyPosition, CurveKeyValue.Component( CurveIdx ) ); + RichCurve.SetKeyInterpMode( KeyHandle, RichCurveInterpMode ); + } + } + } +} + +bool +UHoudiniAssetParameterRamp::GetRampKeysCurveFloat( + int32 Idx, UHoudiniAssetParameterFloat *& Position, + UHoudiniAssetParameterFloat *& Value, + UHoudiniAssetParameterChoice *& Interp ) const +{ + Position = nullptr; + Value = nullptr; + Interp = nullptr; + + int32 NumChildren = ChildParameters.Num(); + + if ( ChildParameters.IsValidIndex( 3 * Idx + 0 ) ) + Position = Cast< UHoudiniAssetParameterFloat >(ChildParameters[ 3 * Idx + 0 ] ); + + if ( ChildParameters.IsValidIndex( 3 * Idx + 1 ) ) + Value = Cast< UHoudiniAssetParameterFloat >( ChildParameters[ 3 * Idx + 1 ] ); + + if ( ChildParameters.IsValidIndex( 3 * Idx + 2 ) ) + Interp = Cast< UHoudiniAssetParameterChoice >( ChildParameters[ 3 * Idx + 2 ] ); + + return Position != nullptr && Value != nullptr && Interp != nullptr; +} + +bool +UHoudiniAssetParameterRamp::GetRampKeysCurveColor( + int32 Idx, UHoudiniAssetParameterFloat *& Position, + UHoudiniAssetParameterColor *& Value, + UHoudiniAssetParameterChoice *& Interp ) const +{ + Position = nullptr; + Value = nullptr; + Interp = nullptr; + + int32 NumChildren = ChildParameters.Num(); + + if ( 3 * Idx + 0 < NumChildren ) + Position = Cast< UHoudiniAssetParameterFloat >( ChildParameters[ 3 * Idx + 0 ] ); + + if ( 3 * Idx + 1 < NumChildren ) + Value = Cast< UHoudiniAssetParameterColor >( ChildParameters[ 3 * Idx + 1 ] ); + + if ( 3 * Idx + 2 < NumChildren ) + Interp = Cast< UHoudiniAssetParameterChoice >( ChildParameters[ 3 * Idx + 2 ] ); + + return Position != nullptr && Value != nullptr && Interp != nullptr; +} + +int32 +UHoudiniAssetParameterRamp::GetRampKeyCount() const +{ + int32 ChildParamCount = ChildParameters.Num(); + + if ( ChildParamCount % 3 != 0 ) + { + HOUDINI_LOG_MESSAGE( + TEXT( "Invalid Ramp parameter [%s] : Number of child parameters is not a tuple of 3." ), + *ParameterName ); + + return 0; + } + + return ChildParamCount / 3; +} + +EHoudiniAssetParameterRampKeyInterpolation::Type +UHoudiniAssetParameterRamp::TranslateChoiceKeyInterpolation( UHoudiniAssetParameterChoice * ChoiceParam ) const +{ + EHoudiniAssetParameterRampKeyInterpolation::Type ChoiceInterpolationValue = + UHoudiniAssetParameterRamp::DefaultUnknownInterpolation; + + if ( ChoiceParam ) + { + if ( ChoiceParam->IsStringChoiceList() ) + { + const FString & ChoiceValueString = ChoiceParam->GetParameterValueString(); + + if ( ChoiceValueString.Equals( TEXT( HAPI_UNREAL_RAMP_KEY_INTERPOLATION_CONSTANT ) ) ) + ChoiceInterpolationValue = EHoudiniAssetParameterRampKeyInterpolation::Constant; + else if ( ChoiceValueString.Equals( TEXT( HAPI_UNREAL_RAMP_KEY_INTERPOLATION_LINEAR ) ) ) + ChoiceInterpolationValue = EHoudiniAssetParameterRampKeyInterpolation::Linear; + else if ( ChoiceValueString.Equals( TEXT( HAPI_UNREAL_RAMP_KEY_INTERPOLATION_CATMULL_ROM ) ) ) + ChoiceInterpolationValue = EHoudiniAssetParameterRampKeyInterpolation::CatmullRom; + else if ( ChoiceValueString.Equals( TEXT( HAPI_UNREAL_RAMP_KEY_INTERPOLATION_MONOTONE_CUBIC ) ) ) + ChoiceInterpolationValue = EHoudiniAssetParameterRampKeyInterpolation::MonotoneCubic; + else if ( ChoiceValueString.Equals( TEXT( HAPI_UNREAL_RAMP_KEY_INTERPOLATION_BEZIER ) ) ) + ChoiceInterpolationValue = EHoudiniAssetParameterRampKeyInterpolation::Bezier; + else if ( ChoiceValueString.Equals( TEXT( HAPI_UNREAL_RAMP_KEY_INTERPOLATION_B_SPLINE ) ) ) + ChoiceInterpolationValue = EHoudiniAssetParameterRampKeyInterpolation::BSpline; + else if ( ChoiceValueString.Equals( TEXT( HAPI_UNREAL_RAMP_KEY_INTERPOLATION_HERMITE ) ) ) + ChoiceInterpolationValue = EHoudiniAssetParameterRampKeyInterpolation::Hermite; + } + else + { + int ChoiceValueInt = ChoiceParam->GetParameterValueInt(); + if ( ChoiceValueInt >= 0 || ChoiceValueInt <= 6 ) + ChoiceInterpolationValue = (EHoudiniAssetParameterRampKeyInterpolation::Type) ChoiceValueInt; + } + } + + return ChoiceInterpolationValue; +} + +ERichCurveInterpMode +UHoudiniAssetParameterRamp::TranslateHoudiniRampKeyInterpolation( + EHoudiniAssetParameterRampKeyInterpolation::Type KeyInterpolation ) const +{ + switch ( KeyInterpolation ) + { + case EHoudiniAssetParameterRampKeyInterpolation::Constant: + return ERichCurveInterpMode::RCIM_Constant; + + case EHoudiniAssetParameterRampKeyInterpolation::Linear: + return ERichCurveInterpMode::RCIM_Linear; + + default: + break; + } + + return ERichCurveInterpMode::RCIM_Cubic; +} + +EHoudiniAssetParameterRampKeyInterpolation::Type +UHoudiniAssetParameterRamp::TranslateUnrealRampKeyInterpolation( ERichCurveInterpMode RichCurveInterpMode ) const +{ + switch ( RichCurveInterpMode ) + { + case ERichCurveInterpMode::RCIM_Constant: + return EHoudiniAssetParameterRampKeyInterpolation::Constant; + + case ERichCurveInterpMode::RCIM_Linear: + return EHoudiniAssetParameterRampKeyInterpolation::Linear; + + case ERichCurveInterpMode::RCIM_Cubic: + return UHoudiniAssetParameterRamp::DefaultSplineInterpolation; + + case ERichCurveInterpMode::RCIM_None: + default: + break; + } + + return UHoudiniAssetParameterRamp::DefaultUnknownInterpolation; +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterRamp.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterRamp.h new file mode 100644 index 00000000..7523779c --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterRamp.h @@ -0,0 +1,251 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#pragma once + +#include "HoudiniAssetParameter.h" +#include "HoudiniAssetParameterMultiparm.h" +#include "Tickable.h" +#include "Curves/CurveFloat.h" +#include "Curves/CurveLinearColor.h" +#include "HoudiniAssetParameterRamp.generated.h" + + +class UCurveBase; +class UHoudiniAssetParameterRamp; +class UHoudiniAssetParameterFloat; +class UHoudiniAssetParameterColor; +class UHoudiniAssetParameterChoice; +class SCurveEditor; + +UCLASS(BlueprintType) +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterRampCurveFloat : public UCurveFloat +{ + GENERATED_UCLASS_BODY() + + public: + + /** Set parent ramp parameter. **/ + void SetParentRampParameter( UHoudiniAssetParameterRamp * InHoudiniAssetParameterRamp ); + + /** FCurveOwnerInterface methods. **/ + public: + +#if WITH_EDITOR + + virtual void OnCurveChanged( const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos ) override; + +#endif + + protected: + + /** Parent ramp parameter. **/ + TWeakObjectPtr HoudiniAssetParameterRamp; +}; + +namespace EHoudiniAssetParameterRampCurveColorEvent +{ + enum Type + { + None = 0, + MoveStop, + ChangeStopTime, + ChangeStopColor, + AddStop, + RemoveStop + }; +} + +UCLASS( BlueprintType ) +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterRampCurveColor : public UCurveLinearColor, public FTickableGameObject +{ + GENERATED_UCLASS_BODY() + + + public: + + /** Set parent ramp parameter. **/ + void SetParentRampParameter( UHoudiniAssetParameterRamp * InHoudiniAssetParameterRamp ); + + /** Return the current type of event. **/ + EHoudiniAssetParameterRampCurveColorEvent::Type GetColorEvent() const; + + /** Reset the current type of event. **/ + void ResetColorEvent(); + + + /** FCurveOwnerInterface methods. **/ + public: + +#if WITH_EDITOR + + virtual void OnCurveChanged( const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos ) override; + +#endif + + /** UObject methods. **/ + public: + + virtual bool Modify( bool bAlwaysMarkDirty ); + + /** FTickableGameObject methods. **/ + public: + + virtual bool IsTickableInEditor() const; + virtual bool IsTickableWhenPaused() const; + virtual void Tick( float DeltaTime ) override; + virtual TStatId GetStatId() const override; + virtual bool IsTickable() const override; + + protected: + + /** Attempt to map current editor transaction type to curve transactions. **/ + EHoudiniAssetParameterRampCurveColorEvent::Type GetEditorCurveTransaction() const; + + protected: + + /** Parent ramp parameter. **/ + TWeakObjectPtr HoudiniAssetParameterRamp; + + /** Current event. **/ + EHoudiniAssetParameterRampCurveColorEvent::Type ColorEvent; +}; + +namespace EHoudiniAssetParameterRampKeyInterpolation +{ + enum Type + { + Constant = 0, + Linear, + CatmullRom, + MonotoneCubic, + Bezier, + BSpline, + Hermite + }; +} + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterRamp : public UHoudiniAssetParameterMultiparm +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + virtual ~UHoudiniAssetParameterRamp(); + + /** Create instance of this class. **/ + static UHoudiniAssetParameterRamp * Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ); + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo) override; + + /** Notification from component that all child parameters have been created. **/ + virtual void NotifyChildParametersCreated(); + + /** Duplicates this object as well as owned subobjects */ + virtual UHoudiniAssetParameter * Duplicate( UObject* InOuter ) override; + + /** UObject methods. **/ + public: + + virtual void Serialize( FArchive & Ar ) override; + virtual void PostLoad() override; + static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); + + /** Called when curve editing is finished and update should take place. **/ + void OnCurveEditingFinished(); + + /** Called when float ramp parameter changes via user interface. **/ + void OnCurveFloatChanged( UHoudiniAssetParameterRampCurveFloat * CurveFloat ); + + /** Called when color ramp parameter changes via user interface. **/ + void OnCurveColorChanged( UHoudiniAssetParameterRampCurveColor * CurveColor ); + + protected: + + /** Populate curve with point data. **/ + void GenerateCurvePoints(); + + /** Return number of ramp keys. **/ + int32 GetRampKeyCount() const; + + /** Translate choice value into interpolation enumeration. **/ + EHoudiniAssetParameterRampKeyInterpolation::Type + TranslateChoiceKeyInterpolation( UHoudiniAssetParameterChoice * ChoiceParam ) const; + + /** Return Unreal ramp key interpolation type from Houdini ramp key interpolation type. **/ + ERichCurveInterpMode TranslateHoudiniRampKeyInterpolation( + EHoudiniAssetParameterRampKeyInterpolation::Type KeyInterpolation ) const; + + /** Return Houdini ramp key interpolation type from Unreal ramp key interpolation type. **/ + EHoudiniAssetParameterRampKeyInterpolation::Type + TranslateUnrealRampKeyInterpolation( ERichCurveInterpMode RichCurveInterpMode ) const; + + /** Retrieve ramp key parameters for a given index of a float ramp. **/ + bool GetRampKeysCurveFloat( + int32 Idx, UHoudiniAssetParameterFloat *& Position, + UHoudiniAssetParameterFloat *& Value, + UHoudiniAssetParameterChoice *& Interp ) const; + + /** Retrieve ramp key parameters for a given index of a color ramp. **/ + bool GetRampKeysCurveColor( + int32 Idx, UHoudiniAssetParameterFloat *& Position, + UHoudiniAssetParameterColor *& Value, + UHoudiniAssetParameterChoice *& Interp ) const; + + protected: + + //! Default spline interpolation method. + static const EHoudiniAssetParameterRampKeyInterpolation::Type DefaultSplineInterpolation; + + //! Default unknown interpolation method. + static const EHoudiniAssetParameterRampKeyInterpolation::Type DefaultUnknownInterpolation; + + protected: + + //! Curves which are being edited. + UHoudiniAssetParameterRampCurveFloat * HoudiniAssetParameterRampCurveFloat; + UHoudiniAssetParameterRampCurveColor * HoudiniAssetParameterRampCurveColor; +#if WITH_EDITOR + TSharedPtr CurveEditor; +#endif + + //! Set to true if this ramp is a float ramp. Otherwise is considered a color ramp. + bool bIsFloatRamp; + + //! Set to true if the curve has changed through Slate interaction. + bool bIsCurveChanged; + + //! Set to true when curve data needs to be re-uploaded to Houdini Engine. + bool bIsCurveUploadRequired; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterSeparator.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterSeparator.cpp new file mode 100644 index 00000000..adbcbcb1 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterSeparator.cpp @@ -0,0 +1,74 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetParameterSeparator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +UHoudiniAssetParameterSeparator::UHoudiniAssetParameterSeparator( const FObjectInitializer & ObjectInitializer ) : + Super(ObjectInitializer) +{} + +UHoudiniAssetParameterSeparator * +UHoudiniAssetParameterSeparator::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + UObject * Outer = InPrimaryObject; + if ( !Outer ) + { + Outer = InParentParameter; + if ( !Outer ) + { + // Must have either component or parent not null. + check( false ); + } + } + + UHoudiniAssetParameterSeparator * HoudiniAssetParameterSeparator = NewObject< UHoudiniAssetParameterSeparator >( + Outer, UHoudiniAssetParameterSeparator::StaticClass(), NAME_None, RF_Public | RF_Transactional ); + + HoudiniAssetParameterSeparator->CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ); + return HoudiniAssetParameterSeparator; +} + +bool +UHoudiniAssetParameterSeparator::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + if ( !Super::CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ) ) + return false; + + // We can only handle separator type. + if ( ParmInfo.type != HAPI_PARMTYPE_SEPARATOR ) + return false; + + // Assign internal Hapi values index. + SetValuesIndex( ParmInfo.stringValuesIndex ); + + return true; +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterSeparator.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterSeparator.h new file mode 100644 index 00000000..40d17621 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterSeparator.h @@ -0,0 +1,52 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniAssetParameter.h" +#include "HoudiniAssetParameterSeparator.generated.h" + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterSeparator : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + /** Create instance of this class. **/ + static UHoudiniAssetParameterSeparator * Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ); + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) override; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterString.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterString.cpp new file mode 100644 index 00000000..9e8bad4b --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterString.cpp @@ -0,0 +1,195 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetParameterString.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineString.h" + +#include "Misc/Variant.h" +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetParameterString::UHoudiniAssetParameterString( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{ + Values.Add( TEXT( "" ) ); +} + +UHoudiniAssetParameterString * +UHoudiniAssetParameterString::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + UObject * Outer = InPrimaryObject; + if ( !Outer ) + { + Outer = InParentParameter; + if ( !Outer ) + { + // Must have either component or parent not null. + check( false ); + } + } + + UHoudiniAssetParameterString * HoudiniAssetParameterString = NewObject< UHoudiniAssetParameterString >( + Outer, UHoudiniAssetParameterString::StaticClass(), NAME_None, RF_Public | RF_Transactional ); + + HoudiniAssetParameterString->CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ); + return HoudiniAssetParameterString; +} + +bool +UHoudiniAssetParameterString::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + if ( !Super::CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ) ) + return false; + + // We can only handle string type. + if ( ParmInfo.type != HAPI_PARMTYPE_STRING ) + return false; + + // Assign internal Hapi values index. + SetValuesIndex( ParmInfo.stringValuesIndex ); + + // Get the actual value for this property. + TArray< HAPI_StringHandle > StringHandles; + StringHandles.SetNumZeroed( TupleSize ); + if ( FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), InNodeId, false, + &StringHandles[ 0 ], ValuesIndex, TupleSize) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + // Convert HAPI string handles to Unreal strings. + Values.SetNum( TupleSize ); + for ( int32 Idx = 0; Idx < TupleSize; ++Idx ) + { + FString ValueString = TEXT( "" ); + FHoudiniEngineString HoudiniEngineString( StringHandles[ Idx ] ); + HoudiniEngineString.ToFString( ValueString ); + Values[ Idx ] = ValueString; + } + + return true; +} + +void +UHoudiniAssetParameterString::Serialize( FArchive & Ar ) +{ + // Call base implementation. + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + Ar << Values; +} + +bool +UHoudiniAssetParameterString::UploadParameterValue() +{ + for ( int32 Idx = 0; Idx < Values.Num(); ++Idx ) + { + std::string ConvertedString = TCHAR_TO_UTF8( *( Values[ Idx ] ) ); + if ( FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NodeId, + ConvertedString.c_str(), ParmId, Idx ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + } + + return Super::UploadParameterValue(); +} + +bool +UHoudiniAssetParameterString::SetParameterVariantValue( + const FVariant & Variant, int32 Idx, bool bTriggerModify, bool bRecordUndo ) +{ + EVariantTypes VariantType = Variant.GetType(); + + if ( !Values.IsValidIndex(Idx) ) + return false; + + if (EVariantTypes::String != VariantType) + return false; + + const FString & VariantStringValue = Variant.GetValue< FString >(); + +#if WITH_EDITOR + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniAssetParameterStringChange", "Houdini Parameter String: Changing a value"), + PrimaryObject); + + Modify(); + + if (!bRecordUndo) + Transaction.Cancel(); + +#endif + + Values[Idx] = VariantStringValue; + MarkChanged(); + + return true; +} + +#if WITH_EDITOR + +void +UHoudiniAssetParameterString::SetValueCommitted( const FText & InValue, ETextCommit::Type CommitType, int32 Idx ) +{ + if (!Values.IsValidIndex(Idx)) + return; + + FString CommittedValue = InValue.ToString(); + if (Values[Idx] == CommittedValue) + return; + + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterStringChange", "Houdini Parameter String: Changing a value" ), + PrimaryObject ); + Modify(); + + Values[ Idx ] = CommittedValue; + + // Mark this parameter as changed. + MarkChanged(); +} + +#endif + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterString.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterString.h new file mode 100644 index 00000000..34325cdf --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterString.h @@ -0,0 +1,78 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniAssetParameter.h" +#include "Types/SlateEnums.h" +#include "HoudiniAssetParameterString.generated.h" + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterString : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + /** Create instance of this class. **/ + static UHoudiniAssetParameterString* Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ); + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) override; + + /** Upload parameter value to HAPI. **/ + virtual bool UploadParameterValue() override; + + /** Set parameter value. **/ + virtual bool SetParameterVariantValue( + const FVariant & Variant, int32 Idx = 0, bool bTriggerModify = true, + bool bRecordUndo = true ) override; + + /** UObject methods. **/ + public: + + virtual void Serialize( FArchive & Ar ) override; + + public: + +#if WITH_EDITOR + /** Set value of this property through commit action, used by Slate. **/ + void SetValueCommitted( const FText & InValue, ETextCommit::Type CommitType, int32 Idx ); +#endif + + protected: + + /** Values of this property. **/ + TArray< FString > Values; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterToggle.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterToggle.cpp new file mode 100644 index 00000000..4ff16a27 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterToggle.cpp @@ -0,0 +1,218 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniAssetParameterToggle.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" + +#include "Misc/Variant.h" +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetParameterToggle::UHoudiniAssetParameterToggle( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{ + // Parameter will have at least one value. + Values.AddZeroed( 1 ); +} + +UHoudiniAssetParameterToggle * +UHoudiniAssetParameterToggle::Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + UObject * Outer = InPrimaryObject; + if ( !Outer ) + { + Outer = InParentParameter; + if ( !Outer ) + { + // Must have either component or parent not null. + check( false ); + } + } + + UHoudiniAssetParameterToggle * HoudiniAssetParameterToggle = NewObject< UHoudiniAssetParameterToggle >( + Outer, UHoudiniAssetParameterToggle::StaticClass(), NAME_None, RF_Public | RF_Transactional ); + + HoudiniAssetParameterToggle->CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ); + return HoudiniAssetParameterToggle; +} + +bool +UHoudiniAssetParameterToggle::CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) +{ + if ( !Super::CreateParameter( InPrimaryObject, InParentParameter, InNodeId, ParmInfo ) ) + return false; + + // We can only handle toggle type. + if ( ParmInfo.type != HAPI_PARMTYPE_TOGGLE ) + return false; + + // Assign internal Hapi values index. + SetValuesIndex( ParmInfo.intValuesIndex ); + + // Get the actual value for this property. + Values.SetNumZeroed( TupleSize ); + if ( FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, &Values[ 0 ], + ValuesIndex, TupleSize ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + // Min and max make no sense for this type of parameter. + return true; +} + +bool +UHoudiniAssetParameterToggle::UploadParameterValue() +{ + if (Values.Num() <= 0) + return false; + + if ( FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), NodeId, &Values[ 0 ], + ValuesIndex, TupleSize ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + return Super::UploadParameterValue(); +} + +bool +UHoudiniAssetParameterToggle::SetParameterVariantValue( + const FVariant & Variant, int32 Idx, bool bTriggerModify, bool bRecordUndo ) +{ + EVariantTypes VariantType = Variant.GetType(); + int32 VariantValue = 0; + + if ( !Values.IsValidIndex(Idx) ) + return false; + + switch ( VariantType ) + { + case EVariantTypes::Int8: + case EVariantTypes::Int16: + case EVariantTypes::Int32: + case EVariantTypes::Int64: + case EVariantTypes::UInt8: + case EVariantTypes::UInt16: + case EVariantTypes::UInt32: + case EVariantTypes::UInt64: + { + VariantValue = Variant.GetValue< int32 >(); + break; + } + + case EVariantTypes::Bool: + { + VariantValue = (int32) Variant.GetValue< bool >(); + break; + } + + default: + { + return false; + } + } + +#if WITH_EDITOR + + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterToggleChange", "Houdini Parameter Toggle: Changing a value" ), + PrimaryObject ); + + Modify(); + + if ( !bRecordUndo ) + Transaction.Cancel(); + +#endif + + Values[ Idx ] = VariantValue; + MarkChanged(); + + return true; +} + +void +UHoudiniAssetParameterToggle::Serialize( FArchive & Ar ) +{ + // Call base implementation. + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + Ar << Values; +} + +#if WITH_EDITOR + +void +UHoudiniAssetParameterToggle::CheckStateChanged( ECheckBoxState NewState, int32 Idx ) +{ + if (!Values.IsValidIndex(Idx)) + return; + + int32 bState = ( NewState == ECheckBoxState::Checked ); + if (Values[Idx] == bState) + return; + + // Record undo information. + FScopedTransaction Transaction( + TEXT( HOUDINI_MODULE_RUNTIME ), + LOCTEXT( "HoudiniAssetParameterToggleChange", "Houdini Parameter Toggle: Changing a value" ), + PrimaryObject ); + Modify(); + + Values[ Idx ] = bState; + + // Mark this parameter as changed. + MarkChanged(); +} + +ECheckBoxState +UHoudiniAssetParameterToggle::IsChecked( int32 Idx ) const +{ + if (!Values.IsValidIndex(Idx)) + return ECheckBoxState::Undetermined; + + if ( Values[ Idx ] ) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; +} + +#endif + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterToggle.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterToggle.h new file mode 100644 index 00000000..aefdd193 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAssetParameterToggle.h @@ -0,0 +1,84 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniAssetParameter.h" +#if WITH_EDITOR +#include "Styling/SlateTypes.h" +#endif +#include "HoudiniAssetParameterToggle.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniAssetParameterToggle : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniParameterDetails; + + public: + + /** Create instance of this class. **/ + static UHoudiniAssetParameterToggle* Create( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ); + + public: + + /** Create this parameter from HAPI information. **/ + virtual bool CreateParameter( + UObject * InPrimaryObject, + UHoudiniAssetParameter * InParentParameter, + HAPI_NodeId InNodeId, const HAPI_ParmInfo & ParmInfo ) override; + + /** Upload parameter value to HAPI. **/ + virtual bool UploadParameterValue() override; + + /** Set parameter value. **/ + virtual bool SetParameterVariantValue( + const FVariant & Variant, int32 Idx = 0, bool bTriggerModify = true, + bool bRecordUndo = true ) override; + + /** UObject methods. **/ + public: + + virtual void Serialize( FArchive & Ar ) override; + + public: + +#if WITH_EDITOR + + /** Get value of this property, used by Slate. **/ + void CheckStateChanged( ECheckBoxState NewState, int32 Idx ); + + /** Return checked state of this property, used by Slate. **/ + ECheckBoxState IsChecked( int32 Idx ) const; + +#endif + + protected: + + /** Values of this property. **/ + TArray< int32 > Values; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAttributeDataComponent.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAttributeDataComponent.cpp new file mode 100644 index 00000000..f09dfcae --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAttributeDataComponent.cpp @@ -0,0 +1,136 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniAttributeDataComponent.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngine.h" +#include "HoudiniPluginSerializationVersion.h" + +UHoudiniAttributeDataComponent::UHoudiniAttributeDataComponent( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{ +} + +UHoudiniAttributeDataComponent::~UHoudiniAttributeDataComponent() +{} + +void +UHoudiniAttributeDataComponent::SetAttributeData( FHoudiniPointAttributeData&& InVertexAttributeData ) +{ + VertexAttributeData.Add( InVertexAttributeData ); +} + +FHoudiniPointAttributeData* +UHoudiniAttributeDataComponent::GetAttributeData( class UStaticMeshComponent* StaticMeshComponent ) +{ + for ( FHoudiniPointAttributeData& AttributeData : VertexAttributeData ) + { + if ( AttributeData.Component.IsValid() && AttributeData.Component.Get() == StaticMeshComponent ) + { + return &AttributeData; + } + } + return nullptr; +} + +void +UHoudiniAttributeDataComponent::Serialize( FArchive & Ar ) +{ + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + int32 Version = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << Version; +} + +bool +UHoudiniAttributeDataComponent::Upload( HAPI_NodeId GeoNodeId, class UStaticMeshComponent* StaticMeshComponent ) const +{ + for ( const FHoudiniPointAttributeData& AttributeData : VertexAttributeData ) + { + if ( AttributeData.Component.IsValid() && AttributeData.Component.Get() == StaticMeshComponent ) + { + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi:: AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = AttributeData.Count; + AttributeInfoPoint.tupleSize = AttributeData.TupleSize; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + const char* AttrNameRaw = TCHAR_TO_ANSI( *AttributeData.AttrName ); + + switch ( AttributeData.DataType ) + { + case EHoudiniVertexAttributeDataType::VADT_Bool: + case EHoudiniVertexAttributeDataType::VADT_Int32: + { + AttributeInfoPoint.storage = HAPI_STORAGETYPE_INT; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), GeoNodeId, + 0, AttrNameRaw, &AttributeInfoPoint ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + GeoNodeId, 0, AttrNameRaw, &AttributeInfoPoint, + AttributeData.IntData.GetData(), 0, AttributeInfoPoint.count ), false ); + + break; + } + case EHoudiniVertexAttributeDataType::VADT_Float: + { + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), GeoNodeId, + 0, AttrNameRaw, &AttributeInfoPoint ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + GeoNodeId, 0, AttrNameRaw, &AttributeInfoPoint, + AttributeData.FloatData.GetData(), 0, AttributeInfoPoint.count ), false ); + + break; + } + default: + checkNoEntry(); + } + break; + } + } + return true; +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAttributeDataComponent.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAttributeDataComponent.h new file mode 100644 index 00000000..e81cb7fe --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniAttributeDataComponent.h @@ -0,0 +1,91 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once +#include "UObject/ObjectMacros.h" +#include "Components/ActorComponent.h" +#include "Components/StaticMeshComponent.h" + +#include "HAPI.h" +#include "HoudiniAttributeDataComponent.generated.h" + +UENUM() +enum EHoudiniVertexAttributeDataType +{ + VADT_Float UMETA( DisplayName = "Float" ), + VADT_Int32 UMETA( DisplayName = "Integer" ), + VADT_Bool UMETA( DisplayName = "Boolean" ) +}; + +struct FHoudiniPointAttributeData +{ + FHoudiniPointAttributeData( const FString& InAttrName, class UStaticMeshComponent* InComponent, EHoudiniVertexAttributeDataType InDataType, int32 InCount, int32 InTupleSize ) + : AttrName( InAttrName ) + , Component ( InComponent ) + , DataType( InDataType ) + , Count ( InCount ) + , TupleSize ( InTupleSize ) + { + switch ( InDataType ) + { + case VADT_Float: + FloatData.SetNumZeroed( Count * TupleSize ); + break; + case VADT_Int32: + case VADT_Bool: + IntData.SetNumZeroed( Count * TupleSize ); + break; + default: + checkNoEntry(); + } + } + FString AttrName; + TWeakObjectPtr Component; + EHoudiniVertexAttributeDataType DataType; + int32 Count; + int32 TupleSize; + + // Only one of the following are used + TArray FloatData; + TArray IntData; +}; + +UCLASS( config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniAttributeDataComponent : public UActorComponent +{ + GENERATED_UCLASS_BODY() + + virtual ~UHoudiniAttributeDataComponent(); + +public: + void SetAttributeData( FHoudiniPointAttributeData&& InVertexAttributeData ); + FHoudiniPointAttributeData* GetAttributeData( class UStaticMeshComponent* StaticMeshComponent ); + + /** UObject methods. **/ + void Serialize( FArchive & Ar ) override; + + /** Upload all data for the given mesh to the specified geo node */ + bool Upload( HAPI_NodeId GeoNodeId, class UStaticMeshComponent* StaticMeshComponent ) const; +private: + TArray< FHoudiniPointAttributeData > VertexAttributeData; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngine.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngine.cpp new file mode 100644 index 00000000..d9e4efc0 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngine.cpp @@ -0,0 +1,695 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniEngine.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineScheduler.h" +#include "HoudiniEngineTask.h" +#include "HoudiniEngineTaskInfo.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniLandscapeUtils.h" +#include "HoudiniEngineInstancerUtils.h" +#include "HoudiniAsset.h" +#include "HoudiniRuntimeSettings.h" + +#include "HAL/PlatformMisc.h" +#include "HAL/PlatformFilemanager.h" +#include "Misc/ScopeLock.h" +#include "Framework/Application/SlateApplication.h" +#include "Materials/Material.h" + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + + +const FName FHoudiniEngine::HoudiniEngineAppIdentifier = FName( TEXT( "HoudiniEngineApp" ) ); + +IMPLEMENT_MODULE( FHoudiniEngine, HoudiniEngineRuntime ); +DEFINE_LOG_CATEGORY( LogHoudiniEngine ); + +FHoudiniEngine * +FHoudiniEngine::HoudiniEngineInstance = nullptr; + +FHoudiniEngine::FHoudiniEngine() + : HoudiniLogoStaticMesh( nullptr ) + , HoudiniDefaultMaterial( nullptr ) + , HoudiniBgeoAsset( nullptr ) + , HoudiniEngineSchedulerThread( nullptr ) + , HoudiniEngineScheduler( nullptr ) + , EnableCookingGlobal( true ) + , FirstSessionCreated( false ) +{ + Session.type = HAPI_SESSION_MAX; + Session.id = -1; +} + +#if WITH_EDITOR + +TSharedPtr< FSlateDynamicImageBrush > +FHoudiniEngine::GetHoudiniLogoBrush() const +{ + return HoudiniLogoBrush; +} + +#endif + +TWeakObjectPtr +FHoudiniEngine::GetHoudiniLogoStaticMesh() const +{ + return HoudiniLogoStaticMesh; +} + +TWeakObjectPtr +FHoudiniEngine::GetHoudiniDefaultMaterial() const +{ + return HoudiniDefaultMaterial; +} + +TWeakObjectPtr +FHoudiniEngine::GetHoudiniBgeoAsset() const +{ + return HoudiniBgeoAsset; +} + +bool +FHoudiniEngine::CheckHapiVersionMismatch() const +{ + return bHAPIVersionMismatch; +} + +const FString & +FHoudiniEngine::GetLibHAPILocation() const +{ + return LibHAPILocation; +} + +HAPI_Result +FHoudiniEngine::GetHapiState() const +{ + return HAPIState; +} + +void +FHoudiniEngine::SetHapiState( HAPI_Result Result ) +{ + HAPIState = Result; +} + +const HAPI_Session * +FHoudiniEngine::GetSession() const +{ + return Session.type == HAPI_SESSION_MAX ? nullptr : &Session; +} + +FHoudiniEngine & +FHoudiniEngine::Get() +{ + check( FHoudiniEngine::HoudiniEngineInstance ); + return *FHoudiniEngine::HoudiniEngineInstance; +} + +bool +FHoudiniEngine::IsInitialized() +{ + return FHoudiniEngine::HoudiniEngineInstance != nullptr && FHoudiniEngineUtils::IsInitialized(); +} + +void +FHoudiniEngine::StartupModule() +{ + bHAPIVersionMismatch = false; + HAPIState = HAPI_RESULT_NOT_INITIALIZED; + + HOUDINI_LOG_MESSAGE( TEXT( "Starting the Houdini Engine module." ) ); + +#if WITH_EDITOR + // Register settings. + if( ISettingsModule * SettingsModule = FModuleManager::GetModulePtr< ISettingsModule >( "Settings" ) ) + { + SettingsModule->RegisterSettings( + "Project", "Plugins", "HoudiniEngine", + LOCTEXT( "RuntimeSettingsName", "Houdini Engine" ), + LOCTEXT( "RuntimeSettingsDescription", "Configure the HoudiniEngine plugin" ), + GetMutableDefault< UHoudiniRuntimeSettings >() ); + } + + // Before starting the module, we need to locate and load HAPI library. + { + void * HAPILibraryHandle = FHoudiniEngineUtils::LoadLibHAPI( LibHAPILocation ); + + if ( HAPILibraryHandle ) + { + FHoudiniApi::InitializeHAPI( HAPILibraryHandle ); + } + else + { + // Get platform specific name of libHAPI. + FString LibHAPIName = FHoudiniEngineUtils::HoudiniGetLibHAPIName(); + HOUDINI_LOG_MESSAGE( TEXT( "Failed locating or loading %s" ), *LibHAPIName ); + } + } + +#endif + + // Create static mesh Houdini logo. + HoudiniLogoStaticMesh = LoadObject< UStaticMesh >( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_LOGO, nullptr, LOAD_None, nullptr ); + if ( HoudiniLogoStaticMesh.IsValid() ) + HoudiniLogoStaticMesh->AddToRoot(); + + // Create default material. + HoudiniDefaultMaterial = LoadObject< UMaterial >( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL, nullptr, LOAD_None, nullptr ); + if ( HoudiniDefaultMaterial.IsValid() ) + HoudiniDefaultMaterial->AddToRoot(); + + // Create Houdini digital asset which is used for loading the bgeo files. + HoudiniBgeoAsset = LoadObject< UHoudiniAsset >( + nullptr, HAPI_UNREAL_RESOURCE_BGEO_IMPORT, nullptr, LOAD_None, nullptr ); + if ( HoudiniBgeoAsset.IsValid() ) + HoudiniBgeoAsset->AddToRoot(); + +#if WITH_EDITOR + + if ( !IsRunningCommandlet() && !IsRunningDedicatedServer() ) + { + // Create Houdini logo brush. + const TArray< TSharedRef< IPlugin > > Plugins = IPluginManager::Get().GetDiscoveredPlugins(); + for ( auto PluginIt( Plugins.CreateConstIterator() ); PluginIt; ++PluginIt ) + { + const TSharedRef< IPlugin > & Plugin = *PluginIt; + if (Plugin->GetName() != TEXT("HoudiniEngine")) + continue; + + FString Icon128FilePath = Plugin->GetBaseDir() / TEXT("Resources/Icon128.png"); + if (FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) + { + const FName BrushName(*Icon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + break; + } + } + + // Build and running versions match, we can perform HAPI initialization. + if ( FHoudiniApi::IsHAPIInitialized() ) + { + // We do not automatically try to start a session when starting up the module now. + FirstSessionCreated = false; + + // Create HAPI scheduler and processing thread. + HoudiniEngineScheduler = new FHoudiniEngineScheduler(); + HoudiniEngineSchedulerThread = FRunnableThread::Create( + HoudiniEngineScheduler, TEXT( "HoudiniTaskCookAsset" ), 0, TPri_Normal ); + + // Set the default value for pausing houdini engine cooking + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + EnableCookingGlobal = !HoudiniRuntimeSettings->bPauseCookingOnStart; + } + +#endif + + // Store the instance. + FHoudiniEngine::HoudiniEngineInstance = this; +} + +void +FHoudiniEngine::ShutdownModule() +{ + HOUDINI_LOG_MESSAGE( TEXT( "Shutting down the Houdini Engine module." ) ); + + // We no longer need Houdini logo static mesh. + if ( HoudiniLogoStaticMesh.IsValid() ) + { + HoudiniLogoStaticMesh->RemoveFromRoot(); + HoudiniLogoStaticMesh = nullptr; + } + + // We no longer need Houdini default material. + if ( HoudiniDefaultMaterial.IsValid() ) + { + HoudiniDefaultMaterial->RemoveFromRoot(); + HoudiniDefaultMaterial = nullptr; + } + + // We no longer need Houdini digital asset used for loading bgeo files. + if ( HoudiniBgeoAsset.IsValid() ) + { + HoudiniBgeoAsset->RemoveFromRoot(); + HoudiniBgeoAsset = nullptr; + } + +#if WITH_EDITOR + // Unregister settings. + ISettingsModule * SettingsModule = FModuleManager::GetModulePtr< ISettingsModule >( "Settings" ); + if ( SettingsModule ) + SettingsModule->UnregisterSettings( "Project", "Plugins", "HoudiniEngine" ); +#endif + + // Do scheduler and thread clean up. + if ( HoudiniEngineScheduler ) + HoudiniEngineScheduler->Stop(); + + if ( HoudiniEngineSchedulerThread ) + { + //HoudiniEngineSchedulerThread->Kill( true ); + HoudiniEngineSchedulerThread->WaitForCompletion(); + + delete HoudiniEngineSchedulerThread; + HoudiniEngineSchedulerThread = nullptr; + } + + if ( HoudiniEngineScheduler ) + { + delete HoudiniEngineScheduler; + HoudiniEngineScheduler = nullptr; + } + + // Perform HAPI finalization. + if ( FHoudiniApi::IsHAPIInitialized() ) + { + FHoudiniApi::Cleanup( GetSession() ); + FHoudiniApi::CloseSession( GetSession() ); + } + + FHoudiniApi::FinalizeHAPI(); +} + +void +FHoudiniEngine::AddTask( const FHoudiniEngineTask & Task ) +{ + if ( HoudiniEngineScheduler ) + HoudiniEngineScheduler->AddTask( Task ); + + FScopeLock ScopeLock( &CriticalSection ); + FHoudiniEngineTaskInfo TaskInfo; + TaskInfos.Add( Task.HapiGUID, TaskInfo ); +} + +void +FHoudiniEngine::AddTaskInfo( const FGuid HapIGUID, const FHoudiniEngineTaskInfo & TaskInfo ) +{ + FScopeLock ScopeLock( &CriticalSection ); + TaskInfos.Add( HapIGUID, TaskInfo ); +} + +void +FHoudiniEngine::RemoveTaskInfo( const FGuid HapIGUID ) +{ + FScopeLock ScopeLock( &CriticalSection ); + TaskInfos.Remove( HapIGUID ); +} + +bool +FHoudiniEngine::RetrieveTaskInfo( const FGuid HapIGUID, FHoudiniEngineTaskInfo & TaskInfo ) +{ + FScopeLock ScopeLock( &CriticalSection ); + + if ( TaskInfos.Contains( HapIGUID ) ) + { + TaskInfo = TaskInfos[ HapIGUID ]; + return true; + } + + return false; +} + +bool +FHoudiniEngine::CookNode( + HAPI_NodeId AssetId, FHoudiniCookParams& HoudiniCookParams, + bool ForceRebuildStaticMesh, bool ForceRecookAll, + const TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesIn, + TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesOut, + TMap< FHoudiniGeoPartObject, TWeakObjectPtr >& LandscapesIn, + TMap< FHoudiniGeoPartObject, TWeakObjectPtr >& LandscapesOut, + TMap< FHoudiniGeoPartObject, USceneComponent * >& InstancersIn, + TMap< FHoudiniGeoPartObject, USceneComponent * >& InstancersOut, + USceneComponent* ParentComponent, FTransform & ComponentTransform ) +{ + // + TMap< FHoudiniGeoPartObject, UStaticMesh * > CookResultArray; + bool bReturn = FHoudiniEngineUtils::CreateStaticMeshesFromHoudiniAsset( + AssetId, HoudiniCookParams, ForceRebuildStaticMesh, + ForceRecookAll, StaticMeshesIn, CookResultArray, ComponentTransform ); + + if ( !bReturn ) + return false; + + // Extract the static mesh and the volumes/heightfields from the CookResultArray + TArray< FHoudiniGeoPartObject > FoundVolumes; + TArray< FHoudiniGeoPartObject > FoundInstancers; + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( CookResultArray ); Iter; ++Iter ) + { + const FHoudiniGeoPartObject HoudiniGeoPartObject = Iter.Key(); + + UStaticMesh * StaticMesh = Iter.Value(); + if ( HoudiniGeoPartObject.IsInstancer() ) + FoundInstancers.Add( HoudiniGeoPartObject ); + else if (HoudiniGeoPartObject.IsPackedPrimitiveInstancer()) + FoundInstancers.Add( HoudiniGeoPartObject ); + else if (HoudiniGeoPartObject.IsCurve()) + continue; + else if (HoudiniGeoPartObject.IsVolume()) + FoundVolumes.Add( HoudiniGeoPartObject ); + else + StaticMeshesOut.Add(HoudiniGeoPartObject, StaticMesh); + } +#if WITH_EDITOR + // The meshes are already created but we need to create the landscape too + if ( FoundVolumes.Num() > 0 ) + { + TArray< ALandscapeProxy* > NullLandscapes; + if ( !FHoudiniLandscapeUtils::CreateAllLandscapes( HoudiniCookParams, FoundVolumes, LandscapesIn, LandscapesOut, NullLandscapes , -200.0f, 200.0f ) ) + HOUDINI_LOG_WARNING( TEXT("FHoudiniEngine::CookNode : Failed to create landscapes!") ); + } +#endif + + // And the instancers + if ( FoundInstancers.Num() > 0 ) + { + if ( !FHoudiniEngineInstancerUtils::CreateAllInstancers( + HoudiniCookParams, AssetId, FoundInstancers, + StaticMeshesOut, ParentComponent, + InstancersIn, InstancersOut ) ) + { + HOUDINI_LOG_WARNING( TEXT( "FHoudiniEngine::CookNode : Failed to create instancers!" ) ); + } + } + + if ( StaticMeshesOut.Num() <= 0 && LandscapesOut.Num() <= 0 && InstancersOut.Num() <= 0 ) + return false; + + return true; +} + +void +FHoudiniEngine::SetEnableCookingGlobal(const bool& enableCooking) +{ + EnableCookingGlobal = enableCooking; +} + +bool +FHoudiniEngine::GetEnableCookingGlobal() +{ + return EnableCookingGlobal; +} + + +bool +FHoudiniEngine::StartSession( HAPI_Session*& SessionPtr, + const bool& StartAutomaticServer, + const float& AutomaticServerTimeout, + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const int32& ServerPort, + const FString& ServerHost ) +{ + // Indicates that we've tried to start the session once + // whether it failed or succeed + FirstSessionCreated = true; + + // HAPI needs to be initialized + if ( !FHoudiniApi::IsHAPIInitialized() ) + return false; + + // Only start a new Session if we dont have a valid one + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid( SessionPtr ) ) + return true; + + HAPI_Result SessionResult = HAPI_RESULT_FAILURE; + + HAPI_ThriftServerOptions ServerOptions; + FMemory::Memzero< HAPI_ThriftServerOptions >( ServerOptions ); + ServerOptions.autoClose = true; + ServerOptions.timeoutMs = AutomaticServerTimeout; + + auto UpdatePathForServer = [&] + { + // Modify our PATH so that HARC will find HARS.exe + const TCHAR* PathDelimiter = FPlatformMisc::GetPathVarDelimiter(); + + FString OrigPathVar = FPlatformMisc::GetEnvironmentVariable(TEXT("PATH")); + + FString ModifiedPath = +#if PLATFORM_MAC + // On Mac our binaries are split between two folders + LibHAPILocation + TEXT( "/../Resources/bin" ) + PathDelimiter + +#endif + LibHAPILocation + PathDelimiter + OrigPathVar; + + FPlatformMisc::SetEnvironmentVar( TEXT( "PATH" ), *ModifiedPath ); + }; + + switch ( SessionType ) + { + case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: + { + // As of Unreal 4.19, InProcess sessions are not supported anymore + // We create an auto started pipe session instead using default values + /* + SessionResult = FHoudiniApi::CreateInProcessSession(&this->Session); + #if PLATFORM_WINDOWS + // Workaround for Houdini libtools setting stdout to binary + FWindowsPlatformMisc::SetUTF8Output(); + #endif + */ + + UpdatePathForServer(); + FHoudiniApi::StartThriftNamedPipeServer( + &ServerOptions, TCHAR_TO_UTF8( *ServerPipeName ), nullptr ); + + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + SessionPtr, TCHAR_TO_UTF8( *ServerPipeName ) ); + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: + { + // Try to connect to an existing socket session first + SessionResult = FHoudiniApi::CreateThriftSocketSession( + SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort); + + // Start a session and try to connect to it if we failed + if (StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS) + { + UpdatePathForServer(); + FHoudiniApi::StartThriftSocketServer( + &ServerOptions, ServerPort, nullptr ); + + SessionResult = FHoudiniApi::CreateThriftSocketSession( + SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort ); + } + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: + { + // Try to connect to an existing pipe session first + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + SessionPtr, TCHAR_TO_UTF8(*ServerPipeName)); + + // Start a session and try to connect to it if we failed + if (StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS) + { + UpdatePathForServer(); + FHoudiniApi::StartThriftNamedPipeServer( + &ServerOptions, TCHAR_TO_UTF8( *ServerPipeName ), nullptr ); + + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + SessionPtr, TCHAR_TO_UTF8(*ServerPipeName) ); + } + } + break; + + default: + HOUDINI_LOG_ERROR( TEXT( "Unsupported Houdini Engine session type" ) ); + break; + } + + if (SessionResult != HAPI_RESULT_SUCCESS || !SessionPtr) + return false; + + return true; +} + +bool +FHoudiniEngine::InitializeHAPISession() +{ + // The HAPI stubs needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The Houdini API stubs have not been properly initialized.")); + return false; + } + + // We need a Valid Session + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(&Session)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The session is invalid.")); + return false; + } + + // Now, initialize HAPI with the new session + // We need to make sure HAPI version is correct. + int32 RunningEngineMajor = 0; + int32 RunningEngineMinor = 0; + int32 RunningEngineApi = 0; + + // Retrieve version numbers for running Houdini Engine. + FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_MAJOR, &RunningEngineMajor ); + FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_MINOR, &RunningEngineMinor ); + FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_API, &RunningEngineApi ); + + // Compare defined and running versions. + if (RunningEngineMajor != HAPI_VERSION_HOUDINI_ENGINE_MAJOR + || RunningEngineMinor != HAPI_VERSION_HOUDINI_ENGINE_MINOR) + { + // Major or minor HAPI version differs, stop here + HOUDINI_LOG_ERROR( + TEXT("Starting up the Houdini Engine module failed: built and running versions do not match.")); + HOUDINI_LOG_ERROR( + TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), + HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, + RunningEngineMajor, RunningEngineMinor, RunningEngineApi); + + return false; + + } + else if (RunningEngineApi != HAPI_VERSION_HOUDINI_ENGINE_API) + { + // Major/minor HAPIversions match, but only the API version differs, + // Allow the user to continue but warn him of possible instabilities + HOUDINI_LOG_WARNING( + TEXT("Starting up the Houdini Engine module: built and running API versions do not match.")); + HOUDINI_LOG_WARNING( + TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), + HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, + RunningEngineMajor, RunningEngineMinor, RunningEngineApi); + HOUDINI_LOG_WARNING( + TEXT("This could cause instabilities and crashes when using the Houdini Engine plugin")); + } + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Default CookOptions + HAPI_CookOptions CookOptions; + FHoudiniApi::CookOptions_Init(&CookOptions); + //FMemory::Memzero< HAPI_CookOptions >( CookOptions ); + CookOptions.curveRefineLOD = 8.0f; + CookOptions.clearErrorsAndWarnings = true; + CookOptions.maxVerticesPerPrimitive = 3; + CookOptions.splitGeosByGroup = false; + CookOptions.splitGeosByAttribute = false; + CookOptions.splitAttrSH = 0; + CookOptions.refineCurveToLinear = true; + CookOptions.handleBoxPartTypes = false; + CookOptions.handleSpherePartTypes = false; + CookOptions.splitPointsByVertexAttributes = false; + CookOptions.packedPrimInstancingMode = HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT; + + HAPI_Result Result = FHoudiniApi::Initialize( &Session, &CookOptions, true, + HoudiniRuntimeSettings->CookingThreadStackSize, + TCHAR_TO_UTF8( *HoudiniRuntimeSettings->HoudiniEnvironmentFiles), + TCHAR_TO_UTF8( *HoudiniRuntimeSettings->OtlSearchPath), + TCHAR_TO_UTF8( *HoudiniRuntimeSettings->DsoSearchPath), + TCHAR_TO_UTF8( *HoudiniRuntimeSettings->ImageDsoSearchPath), + TCHAR_TO_UTF8( *HoudiniRuntimeSettings->AudioDsoSearchPath) ); + + if ( Result != HAPI_RESULT_SUCCESS ) + { + HOUDINI_LOG_MESSAGE( + TEXT("Starting up the Houdini Engine API module failed: %s"), + *FHoudiniEngineUtils::GetErrorDescription( Result ) ); + + return false; + } + + HOUDINI_LOG_MESSAGE( TEXT( "Successfully intialized the Houdini Engine API module." ) ); + FHoudiniApi::SetServerEnvString(&Session, HAPI_ENV_CLIENT_NAME, HAPI_UNREAL_CLIENT_NAME ); + + return true; +} + +bool +FHoudiniEngine::StopSession( HAPI_Session*& SessionPtr ) +{ + // HAPI needs to be initialized + if ( !FHoudiniApi::IsHAPIInitialized() ) + return false; + + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid( SessionPtr ) ) + { + // SessionPtr is valid, clean up and close the session + FHoudiniApi::Cleanup( SessionPtr ); + FHoudiniApi::CloseSession( SessionPtr ); + } + + return true; +} + +bool +FHoudiniEngine::RestartSession() +{ + HAPI_Session* SessionPtr = &Session; + + // Stop the current session if it is still valid + if ( !StopSession( SessionPtr ) ) + return false; + + // Try to reconnect/start a new session + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!StartSession( + SessionPtr, true, + HoudiniRuntimeSettings->AutomaticServerTimeout, + HoudiniRuntimeSettings->SessionType, + HoudiniRuntimeSettings->ServerPipeName, + HoudiniRuntimeSettings->ServerPort, + HoudiniRuntimeSettings->ServerHost)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session")); + return false; + } + + // Now initialize HAPI for this session + if (!InitializeHAPISession()) + return false; + + return true; +} + +bool +FHoudiniEngine::GetFirstSessionCreated() const +{ + return FirstSessionCreated; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngine.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngine.h new file mode 100644 index 00000000..bc494c3f --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngine.h @@ -0,0 +1,163 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "IHoudiniEngine.h" +#include "HoudiniEngineTaskInfo.h" +#include "HoudiniRuntimeSettings.h" + +class UStaticMesh; +class FRunnableThread; +class FHoudiniEngineScheduler; + +class HOUDINIENGINERUNTIME_API FHoudiniEngine : public IHoudiniEngine +{ + public: + FHoudiniEngine(); + + /** IModuleInterface methods. **/ + public: + + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + /** IHoudiniEngine methods. **/ + public: + + virtual TWeakObjectPtr GetHoudiniLogoStaticMesh() const override; + virtual TWeakObjectPtr GetHoudiniDefaultMaterial() const override; + virtual TWeakObjectPtr GetHoudiniBgeoAsset() const override; + +#if WITH_EDITOR + + virtual TSharedPtr< FSlateDynamicImageBrush > GetHoudiniLogoBrush() const override; + +#endif + + virtual bool CheckHapiVersionMismatch() const override; + virtual const FString & GetLibHAPILocation() const override; + virtual void AddTask( const FHoudiniEngineTask & Task ) override; + virtual void AddTaskInfo( const FGuid HapIGUID, const FHoudiniEngineTaskInfo & TaskInfo ) override; + virtual void RemoveTaskInfo( const FGuid HapIGUID ) override; + virtual bool RetrieveTaskInfo( const FGuid HapIGUID, FHoudiniEngineTaskInfo & TaskInfo ) override; + virtual HAPI_Result GetHapiState() const override; + virtual void SetHapiState( HAPI_Result Result ) override; + virtual const HAPI_Session * GetSession() const override; + virtual bool CookNode( + HAPI_NodeId AssetId, FHoudiniCookParams& HoudiniCookParams, + bool ForceRebuildStaticMesh, bool ForceRecookAll, + const TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesIn, + TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesOut, + TMap< FHoudiniGeoPartObject, TWeakObjectPtr >& LandscapesIn, + TMap< FHoudiniGeoPartObject, TWeakObjectPtr >& LandscapesOut, + TMap< FHoudiniGeoPartObject, USceneComponent * >& InstancersIn, + TMap< FHoudiniGeoPartObject, USceneComponent * >& InstancersOut, + USceneComponent* ParentComponent, + FTransform & ComponentTransform ) override; + + void SetEnableCookingGlobal(const bool& enableCooking); + bool GetEnableCookingGlobal(); + + bool GetFirstSessionCreated() const; + + bool StartSession( + HAPI_Session*& SessionPtr, + const bool& StartAutomaticServer, + const float& AutomaticServerTimeout, + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const int32& ServerPort, + const FString& ServerHost); + bool StopSession( HAPI_Session*& SessionPtr ); + bool RestartSession(); + bool InitializeHAPISession(); + + public: + + /** App identifier string. **/ + static const FName HoudiniEngineAppIdentifier; + + public: + + /** Return singleton instance of Houdini Engine, used internally. **/ + static FHoudiniEngine & Get(); + + /** Return true if singleton instance has been created. **/ + static bool IsInitialized(); + + private: + + /** Singleton instance of Houdini Engine. **/ + static FHoudiniEngine * HoudiniEngineInstance; + + private: + + /** Static mesh used for Houdini logo rendering. **/ + TWeakObjectPtr HoudiniLogoStaticMesh; + + /** Material used as default material. **/ + TWeakObjectPtr HoudiniDefaultMaterial; + + /** Houdini digital asset used for loading the bgeo files. **/ + TWeakObjectPtr HoudiniBgeoAsset; + +#if WITH_EDITOR + + /** Houdini logo brush. **/ + TSharedPtr< FSlateDynamicImageBrush > HoudiniLogoBrush; + +#endif + + /** Synchronization primitive. **/ + FCriticalSection CriticalSection; + + /** Map of task statuses. **/ + TMap< FGuid, FHoudiniEngineTaskInfo > TaskInfos; + + /** Thread used to execute the scheduler. **/ + FRunnableThread * HoudiniEngineSchedulerThread; + + /** Scheduler used to schedule HAPI instantiation and cook tasks. **/ + FHoudiniEngineScheduler * HoudiniEngineScheduler; + + /** Location of libHAPI binary. **/ + FString LibHAPILocation; + + /** Keep current state of HAPI. **/ + HAPI_Result HAPIState; + + /** Is set to true when mismatch between defined and running HAPI versions is detected. **/ + bool bHAPIVersionMismatch; + + /** The Houdini Engine session. **/ + HAPI_Session Session; + + /** Global cooking flag, used to pause HEngine while using the editor **/ + bool EnableCookingGlobal; + + // Indicates that the first attempt to create a session has been done + // This is to delay the first "automatic" session creation for the first cook + // or instantiation rather than when the module started. + bool FirstSessionCreated; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineBakeUtils.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineBakeUtils.cpp new file mode 100644 index 00000000..8a913443 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineBakeUtils.cpp @@ -0,0 +1,1941 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniEngineBakeUtils.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniEngineString.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniAssetInstanceInputField.h" + +#include "CoreMinimal.h" +#include "Engine/StaticMesh.h" +#include "Engine/StaticMeshActor.h" +#include "Materials/Material.h" + +#if WITH_EDITOR + #include "ActorFactories/ActorFactory.h" + #include "Editor.h" + #include "Factories/MaterialFactoryNew.h" + #include "ActorFactories/ActorFactoryStaticMesh.h" + #include "Interfaces/ITargetPlatform.h" + #include "Interfaces/ITargetPlatformManagerModule.h" + #include "FileHelpers.h" + #include "Materials/Material.h" + #include "Materials/MaterialInstance.h" + #include "Materials/MaterialExpressionTextureSample.h" + #include "Materials/MaterialExpressionTextureCoordinate.h" + #include "StaticMeshResources.h" + #include "InstancedFoliage.h" + #include "InstancedFoliageActor.h" + #include "Layers/LayersSubsystem.h" +#endif +#include "EngineUtils.h" +#include "UObject/MetaData.h" +#include "PhysicsEngine/BodySetup.h" +#include "Components/InstancedStaticMeshComponent.h" + +#if PLATFORM_WINDOWS + #include "Windows/WindowsHWrapper.h" + + // Of course, Windows defines its own GetGeoInfo, + // So we need to undefine that before including HoudiniApi.h to avoid collision... + #ifdef GetGeoInfo + #undef GetGeoInfo + #endif +#endif + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UPackage * +FHoudiniEngineBakeUtils::BakeCreateBlueprintPackageForComponent( + UHoudiniAssetComponent * HoudiniAssetComponent, + FString & BlueprintName ) +{ + UPackage * Package = nullptr; + +#if WITH_EDITOR + + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return nullptr; + + FString HoudiniAssetName; + if ( HoudiniAssetComponent->HoudiniAsset ) + HoudiniAssetName = HoudiniAssetComponent->HoudiniAsset->GetName(); + else if ( HoudiniAssetComponent->GetOuter() ) + HoudiniAssetName = HoudiniAssetComponent->GetOuter()->GetName(); + else + HoudiniAssetName = HoudiniAssetComponent->GetName(); + + FGuid BakeGUID = FGuid::NewGuid(); + + if( !BakeGUID.IsValid() ) + BakeGUID = FGuid::NewGuid(); + + // We only want half of generated guid string. + FString BakeGUIDString = BakeGUID.ToString().Left( FHoudiniEngineUtils::PackageGUIDItemNameLength ); + + // Generate Blueprint name. + BlueprintName = HoudiniAssetName + TEXT( "_" ) + BakeGUIDString; + + // Generate unique package name. + FString PackageName = HoudiniAssetComponent->GetBakeFolder().ToString() + TEXT( "/" ) + BlueprintName; + + PackageName = UPackageTools::SanitizePackageName( PackageName ); + + // See if package exists, if it does, we need to regenerate the name. + Package = FindPackage( nullptr, *PackageName ); + + if( Package && !Package->IsPendingKill() ) + { + // Package does exist, there's a collision, we need to generate a new name. + BakeGUID.Invalidate(); + } + else + { + // Create actual package. + Package = CreatePackage( nullptr, *PackageName ); + } +#endif + + return Package; +} + +UStaticMesh * +FHoudiniEngineBakeUtils::BakeStaticMesh( + UHoudiniAssetComponent * HoudiniAssetComponent, + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + UStaticMesh * InStaticMesh ) +{ + UStaticMesh * StaticMesh = nullptr; + +#if WITH_EDITOR + + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return nullptr; + + UHoudiniAsset * HoudiniAsset = HoudiniAssetComponent->HoudiniAsset; + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + return nullptr; + + // We cannot bake curves. + if( HoudiniGeoPartObject.IsCurve() ) + return nullptr; + + if( HoudiniGeoPartObject.IsInstancer() ) + { + HOUDINI_LOG_MESSAGE( TEXT( "Baking of instanced static meshes is not supported at the moment." ) ); + return nullptr; + } + + // Get platform manager LOD specific information. + ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); + check( CurrentPlatform ); + const FStaticMeshLODGroup & LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup( NAME_None ); + int32 NumLODs = LODGroup.GetDefaultNumLODs(); + + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + check( HoudiniRuntimeSettings ); + + FHoudiniCookParams HoudiniCookParams( HoudiniAssetComponent ); + HoudiniCookParams.StaticMeshBakeMode = EBakeMode::CreateNewAssets; + FString MeshName; + FGuid BakeGUID; + UPackage * Package = FHoudiniEngineBakeUtils::BakeCreateStaticMeshPackageForComponent( + HoudiniCookParams, HoudiniGeoPartObject, MeshName, BakeGUID ); + + if( !Package || Package->IsPendingKill() ) + return nullptr; + + // Create static mesh. + StaticMesh = NewObject< UStaticMesh >( Package, FName( *MeshName ), RF_Public | RF_Transactional ); + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + return nullptr; + + // Add meta information to this package. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + Package, StaticMesh, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT( "true" ) ); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + Package, StaticMesh, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MeshName ); + + // Notify registry that we created a new asset. + FAssetRegistryModule::AssetCreated( StaticMesh ); + + // Copy materials. + StaticMesh->StaticMaterials = InStaticMesh->StaticMaterials; + + // Create new source model for current static mesh. + if (!StaticMesh->GetNumSourceModels()) + StaticMesh->AddSourceModel(); + + FStaticMeshSourceModel * SrcModel = &StaticMesh->GetSourceModel(0); + + // Load raw data bytes. + FRawMesh RawMesh; + FStaticMeshSourceModel * InSrcModel = &InStaticMesh->GetSourceModel(0); + InSrcModel->LoadRawMesh( RawMesh ); + + // Some mesh generation settings. + HoudiniRuntimeSettings->SetMeshBuildSettings( SrcModel->BuildSettings, RawMesh ); + + // Setting the DistanceField resolution + SrcModel->BuildSettings.DistanceFieldResolutionScale = HoudiniAssetComponent->GeneratedDistanceFieldResolutionScale; + + // We need to check light map uv set for correctness. Unreal seems to have occasional issues with + // zero UV sets when building lightmaps. + if( SrcModel->BuildSettings.bGenerateLightmapUVs ) + { + // See if we need to disable lightmap generation because of bad UVs. + if( FHoudiniEngineUtils::ContainsInvalidLightmapFaces( RawMesh, StaticMesh->LightMapCoordinateIndex ) ) + { + SrcModel->BuildSettings.bGenerateLightmapUVs = false; + + HOUDINI_LOG_MESSAGE( + TEXT( "Skipping Lightmap Generation: Object %s " ) + TEXT( "- skipping." ), + *MeshName ); + } + } + + // Store the new raw mesh. + SrcModel->StaticMeshOwner = StaticMesh; + SrcModel->SaveRawMesh( RawMesh ); + + while (StaticMesh->GetNumSourceModels() < NumLODs) + StaticMesh->AddSourceModel(); + + for( int32 ModelLODIndex = 0; ModelLODIndex < NumLODs; ++ModelLODIndex ) + { + StaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); + + for( int32 MaterialIndex = 0; MaterialIndex < StaticMesh->StaticMaterials.Num(); ++MaterialIndex ) + { + FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get( ModelLODIndex, MaterialIndex ); + Info.MaterialIndex = MaterialIndex; + Info.bEnableCollision = true; + Info.bCastShadow = true; + StaticMesh->GetSectionInfoMap().Set( ModelLODIndex, MaterialIndex, Info ); + } + } + + // Assign generation parameters for this static mesh. + HoudiniAssetComponent->SetStaticMeshGenerationParameters( StaticMesh ); + + // Copy custom lightmap resolution if it is set. + if( InStaticMesh->LightMapResolution != StaticMesh->LightMapResolution ) + StaticMesh->LightMapResolution = InStaticMesh->LightMapResolution; + + if( HoudiniGeoPartObject.IsCollidable() || HoudiniGeoPartObject.IsRenderCollidable() ) + { + UBodySetup * BodySetup = StaticMesh->BodySetup; + if (BodySetup && !BodySetup->IsPendingKill()) + { + // Enable collisions for this static mesh. + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + } + + FHoudiniScopedGlobalSilence ScopedGlobalSilence; + StaticMesh->Build( true ); + StaticMesh->MarkPackageDirty(); + +#endif + + return StaticMesh; +} + +UBlueprint * +FHoudiniEngineBakeUtils::BakeBlueprint( UHoudiniAssetComponent * HoudiniAssetComponent ) +{ + UBlueprint * Blueprint = nullptr; + + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return nullptr; + +#if WITH_EDITOR + + // Create package for our Blueprint. + FString BlueprintName = TEXT( "" ); + UPackage * Package = FHoudiniEngineBakeUtils::BakeCreateBlueprintPackageForComponent( + HoudiniAssetComponent, BlueprintName ); + + //Bake the asset's landscape + BakeLandscape(HoudiniAssetComponent); + + if( Package && !Package->IsPendingKill() ) + { + AActor * Actor = HoudiniAssetComponent->CloneComponentsAndCreateActor(); + if( Actor && !Actor->IsPendingKill() ) + { + Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor( *BlueprintName, Package, Actor, false ); + + // If actor is rooted, unroot it. We can also delete intermediate actor. + Actor->RemoveFromRoot(); + Actor->ConditionalBeginDestroy(); + + if( Blueprint && !Blueprint->IsPendingKill() ) + FAssetRegistryModule::AssetCreated( Blueprint ); + } + } + +#endif + + return Blueprint; +} + +AActor * +FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithBlueprint( UHoudiniAssetComponent * HoudiniAssetComponent ) +{ + AActor * Actor = nullptr; + + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return nullptr; + +#if WITH_EDITOR + + // Create package for our Blueprint. + FString BlueprintName = TEXT( "" ); + UPackage * Package = FHoudiniEngineBakeUtils::BakeCreateBlueprintPackageForComponent( HoudiniAssetComponent, BlueprintName ); + if (!Package || Package->IsPendingKill()) + return nullptr; + + //Bake the asset's landscape + BakeLandscape( HoudiniAssetComponent ); + + AActor * ClonedActor = HoudiniAssetComponent->CloneComponentsAndCreateActor(); + if (!ClonedActor || ClonedActor->IsPendingKill()) + return nullptr; + + UBlueprint * Blueprint = FKismetEditorUtilities::CreateBlueprint( + ClonedActor->GetClass(), Package, *BlueprintName, + EBlueprintType::BPTYPE_Normal, UBlueprint::StaticClass(), + UBlueprintGeneratedClass::StaticClass(), FName( "CreateFromActor" ) ); + + if( Blueprint && !Blueprint->IsPendingKill() ) + { + Package->MarkPackageDirty(); + + if( ClonedActor->GetInstanceComponents().Num() > 0 ) + FKismetEditorUtilities::AddComponentsToBlueprint( Blueprint, ClonedActor->GetInstanceComponents() ); + + if( Blueprint->GeneratedClass ) + { + AActor * CDO = Cast< AActor >( Blueprint->GeneratedClass->GetDefaultObject() ); + if (!CDO || CDO->IsPendingKill()) + return nullptr; + + const auto CopyOptions = ( EditorUtilities::ECopyOptions::Type ) + ( EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | + EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ); + + EditorUtilities::CopyActorProperties( ClonedActor, CDO, CopyOptions ); + + USceneComponent * Scene = CDO->GetRootComponent(); + if (Scene && !Scene->IsPendingKill()) + { + Scene->SetRelativeLocation(FVector::ZeroVector); + Scene->SetRelativeRotation(FRotator::ZeroRotator); + + // Clear out the attachment info after having copied the properties from the source actor + Scene->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + while ( true ) + { + const int32 ChildCount = Scene->GetAttachChildren().Num(); + if (ChildCount < 1) + break; + + USceneComponent * Component = Scene->GetAttachChildren()[ChildCount - 1]; + if ( Component && !Component->IsPendingKill() ) + Component->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + } + check(Scene->GetAttachChildren().Num() == 0); + + // Ensure the light mass information is cleaned up + Scene->InvalidateLightingCache(); + } + } + + // Compile our blueprint and notify asset system about blueprint. + FKismetEditorUtilities::CompileBlueprint( Blueprint ); + FAssetRegistryModule::AssetCreated( Blueprint ); + + // Retrieve actor transform. + FVector Location = ClonedActor->GetActorLocation(); + FRotator Rotator = ClonedActor->GetActorRotation(); + + // Replace cloned actor with Blueprint instance. + { + TArray< AActor * > Actors; + Actors.Add( ClonedActor ); + + ClonedActor->RemoveFromRoot(); + Actor = FKismetEditorUtilities::CreateBlueprintInstanceFromSelection( Blueprint, Actors, Location, Rotator ); + } + + // We can initiate Houdini actor deletion. + DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + } + else + { + ClonedActor->RemoveFromRoot(); + ClonedActor->ConditionalBeginDestroy(); + } + +#endif + + return Actor; +} + +UStaticMesh * +FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( + const UStaticMesh * StaticMesh, UHoudiniAssetComponent * Component, + const FHoudiniGeoPartObject & HoudiniGeoPartObject, EBakeMode BakeMode ) +{ + UStaticMesh * DuplicatedStaticMesh = nullptr; +#if WITH_EDITOR + if( !HoudiniGeoPartObject.IsCurve() && !HoudiniGeoPartObject.IsInstancer() && !HoudiniGeoPartObject.IsPackedPrimitiveInstancer() && !HoudiniGeoPartObject.IsVolume() ) + { + // Create package for this duplicated mesh. + FHoudiniCookParams HoudiniCookParams( Component ); + // Transferring the CookMode to the materials + // We're either using the default one, or a custom one + HoudiniCookParams.StaticMeshBakeMode = BakeMode; + if( BakeMode == FHoudiniCookParams::GetDefaultStaticMeshesCookMode() ) + HoudiniCookParams.MaterialAndTextureBakeMode = FHoudiniCookParams::GetDefaultMaterialAndTextureCookMode(); + else + HoudiniCookParams.MaterialAndTextureBakeMode = BakeMode; + + FString MeshName; + FGuid MeshGuid; + + UPackage * MeshPackage = FHoudiniEngineBakeUtils::BakeCreateStaticMeshPackageForComponent( + HoudiniCookParams, HoudiniGeoPartObject, MeshName, MeshGuid ); + if( !MeshPackage || MeshPackage->IsPendingKill() ) + return nullptr; + + // We need to be sure the package has been fully loaded before calling DuplicateObject + if (!MeshPackage->IsFullyLoaded()) + { + FlushAsyncLoading(); + if (!MeshPackage->GetOuter()) + { + MeshPackage->FullyLoad(); + } + else + { + MeshPackage->GetOutermost()->FullyLoad(); + } + } + + // Duplicate mesh for this new copied component. + DuplicatedStaticMesh = DuplicateObject< UStaticMesh >( StaticMesh, MeshPackage, *MeshName ); + if ( !DuplicatedStaticMesh || DuplicatedStaticMesh->IsPendingKill() ) + return nullptr; + + if( BakeMode != EBakeMode::Intermediate ) + DuplicatedStaticMesh->SetFlags( RF_Public | RF_Standalone ); + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MeshPackage, DuplicatedStaticMesh, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT( "true" ) ); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MeshPackage, DuplicatedStaticMesh, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MeshName ); + + // See if we need to duplicate materials and textures. + TArray< FStaticMaterial > DuplicatedMaterials; + TArray< FStaticMaterial > & Materials = DuplicatedStaticMesh->StaticMaterials; + + for( int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx ) + { + UMaterialInterface* MaterialInterface = Materials[MaterialIdx].MaterialInterface; + if( MaterialInterface ) + { + UPackage * MaterialPackage = Cast< UPackage >( MaterialInterface->GetOuter() ); + if( MaterialPackage && !MaterialPackage->IsPendingKill() ) + { + FString MaterialName; + if( FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( + MaterialPackage, MaterialInterface, MaterialName ) ) + { + // We only deal with materials. + UMaterial * Material = Cast< UMaterial >( MaterialInterface ); + if( Material && !Material->IsPendingKill() ) + { + // Duplicate material resource. + UMaterial * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( + Material, HoudiniCookParams, MaterialName ); + + if( !DuplicatedMaterial || DuplicatedMaterial->IsPendingKill() ) + continue; + + // Store duplicated material. + FStaticMaterial DupeStaticMaterial = Materials[MaterialIdx]; + DupeStaticMaterial.MaterialInterface = DuplicatedMaterial; + DuplicatedMaterials.Add( DupeStaticMaterial ); + continue; + } + } + } + } + + DuplicatedMaterials.Add( Materials[MaterialIdx] ); + } + + /* + // We need to be sure the package has been fully loaded before calling DuplicateObject + if (!MeshPackage->IsFullyLoaded()) + { + FlushAsyncLoading(); + if (!MeshPackage->GetOuter()) + { + MeshPackage->FullyLoad(); + } + else + { + MeshPackage->GetOutermost()->FullyLoad(); + } + } + */ + + // Assign duplicated materials. + DuplicatedStaticMesh->StaticMaterials = DuplicatedMaterials; + + // Notify registry that we have created a new duplicate mesh. + FAssetRegistryModule::AssetCreated( DuplicatedStaticMesh ); + + // Dirty the static mesh package. + DuplicatedStaticMesh->MarkPackageDirty(); + } +#endif + return DuplicatedStaticMesh; +} + +bool +FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(UHoudiniAssetComponent * HoudiniAssetComponent, bool SelectNewActors) +{ + bool bSuccess = false; +#if WITH_EDITOR + if (FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(HoudiniAssetComponent, SelectNewActors)) + { + bSuccess = FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + } +#endif + return bSuccess; +} + + +bool +FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( UHoudiniAssetComponent * HoudiniAssetComponent, bool SelectNewActors ) +{ + bool bSuccess = false; +#if WITH_EDITOR + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return bSuccess; + + const FScopedTransaction Transaction( LOCTEXT( "BakeToActors", "Bake To Actors" ) ); + + auto SMComponentToPart = HoudiniAssetComponent->CollectAllStaticMeshComponents(); + TArray< AActor* > NewActors = BakeHoudiniActorToActors_StaticMeshes( HoudiniAssetComponent, SMComponentToPart ); + + auto IAComponentToPart = HoudiniAssetComponent->CollectAllInstancedActorComponents(); + NewActors.Append( BakeHoudiniActorToActors_InstancedActors( HoudiniAssetComponent, IAComponentToPart ) ); + + auto SplitMeshInstancerComponentToPart = HoudiniAssetComponent->CollectAllMeshSplitInstancerComponents(); + NewActors.Append( BakeHoudiniActorToActors_SplitMeshInstancers( HoudiniAssetComponent, SplitMeshInstancerComponentToPart ) ); + + bSuccess = NewActors.Num() > 0; + + if( GEditor && SelectNewActors && bSuccess ) + { + GEditor->SelectNone( false, true ); + for( AActor* NewActor : NewActors ) + { + if ( NewActor && !NewActor->IsPendingKill() ) + GEditor->SelectActor( NewActor, true, false ); + } + GEditor->NoteSelectionChange(); + } +#endif + + return bSuccess; +} + +TArray< AActor* > +FHoudiniEngineBakeUtils::BakeHoudiniActorToActors_InstancedActors( + UHoudiniAssetComponent * HoudiniAssetComponent, + TMap< const UHoudiniInstancedActorComponent*, FHoudiniGeoPartObject >& ComponentToPart ) +{ + TArray< AActor* > NewActors; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return NewActors; + +#if WITH_EDITOR + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + FName BaseName( *( HoudiniAssetComponent->GetOwner()->GetName() + TEXT( "_Baked" ) ) ); + + for( const auto& Iter : ComponentToPart ) + { + const UHoudiniInstancedActorComponent * OtherSMC = Iter.Key; + if ( !OtherSMC || OtherSMC->IsPendingKill() ) + continue; + + for( AActor* InstActor : OtherSMC->Instances ) + { + if ( !InstActor || InstActor->IsPendingKill() ) + continue; + + FName NewName = MakeUniqueObjectName( DesiredLevel, OtherSMC->InstancedAsset->StaticClass(), BaseName ); + FString NewNameStr = NewName.ToString(); + + FTransform CurrentTransform = InstActor->GetTransform(); + AActor* NewActor = OtherSMC->SpawnInstancedActor(CurrentTransform); + if( NewActor && !NewActor->IsPendingKill() ) + { + NewActor->SetActorLabel( NewNameStr ); + NewActor->SetFolderPath( BaseName ); + NewActor->SetActorTransform( CurrentTransform ); + NewActors.Add(NewActor); + } + } + } +#endif + return NewActors; +} + +void +FHoudiniEngineBakeUtils::CheckedBakeStaticMesh( + UHoudiniAssetComponent* HoudiniAssetComponent, + TMap< const UStaticMesh*, UStaticMesh* >& OriginalToBakedMesh, + const FHoudiniGeoPartObject & HoudiniGeoPartObject, UStaticMesh* OriginalSM) +{ + UStaticMesh* BakedSM = nullptr; + if( UStaticMesh ** FoundMeshPtr = OriginalToBakedMesh.Find(OriginalSM) ) + { + // We've already baked this mesh, use it + BakedSM = *FoundMeshPtr; + } + else + { + if( FHoudiniEngineBakeUtils::StaticMeshRequiresBake(OriginalSM) ) + { + // Bake the found mesh into the project + BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( + OriginalSM, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::CreateNewAssets); + + if( ensure(BakedSM) ) + { + FAssetRegistryModule::AssetCreated(BakedSM); + } + } + else + { + // We didn't bake this mesh, but it's already baked so we will just use it as is + BakedSM = OriginalSM; + } + OriginalToBakedMesh.Add(OriginalSM, BakedSM); + } +} + +TArray< AActor* > +FHoudiniEngineBakeUtils::BakeHoudiniActorToActors_StaticMeshes( + UHoudiniAssetComponent * HoudiniAssetComponent, + TMap< const UStaticMeshComponent*, FHoudiniGeoPartObject >& SMComponentToPart ) +{ + TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; + + TArray< AActor* > NewActors; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return NewActors; + +#if WITH_EDITOR + // Loop over all comps, bake static mesh if not already baked, and create an actor for every one of them + for( const auto& Iter : SMComponentToPart ) + { + const FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Value; + const UStaticMeshComponent * OtherSMC = Iter.Key; + if ( !OtherSMC || OtherSMC->IsPendingKill() ) + continue; + + UStaticMesh * OtherSM = OtherSMC->GetStaticMesh(); + if( !OtherSM || OtherSM->IsPendingKill() ) + continue; + + CheckedBakeStaticMesh(HoudiniAssetComponent, OriginalToBakedMesh, HoudiniGeoPartObject, OtherSM); + } + + // Finished baking, now spawn the actors + + for( const auto& Iter : SMComponentToPart ) + { + const UStaticMeshComponent * OtherSMC = Iter.Key; + if ( !OtherSMC || OtherSMC->IsPendingKill() ) + continue; + + const FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Value; + UStaticMesh* BakedSM = OriginalToBakedMesh[OtherSMC->GetStaticMesh()]; + + if( !BakedSM || BakedSM->IsPendingKill() ) + continue; + + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + FName BaseName( *( HoudiniAssetComponent->GetOwner()->GetName() + TEXT( "_Baked" ) ) ); + UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass( UActorFactoryStaticMesh::StaticClass() ) : nullptr; + if (!Factory) + continue; + + auto PrepNewStaticMeshActor = [&]( AActor* NewActor ) + { + if ( !NewActor || NewActor->IsPendingKill() ) + return; + + // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset + FName NewName = MakeUniqueObjectName( DesiredLevel, Factory->NewActorClass, BaseName ); + FString NewNameStr = NewName.ToString(); + NewActor->Rename( *NewNameStr ); + NewActor->SetActorLabel( NewNameStr ); + NewActor->SetFolderPath( BaseName ); + + // Copy properties to new actor + AStaticMeshActor* SMActor = Cast< AStaticMeshActor>(NewActor); + if ( !SMActor || SMActor->IsPendingKill() ) + return; + UStaticMeshComponent* SMC = SMActor->GetStaticMeshComponent(); + if (!SMC || SMC->IsPendingKill()) + return; + + UStaticMeshComponent* OtherSMC_NonConst = const_cast( OtherSMC ); + + SMC->SetCollisionProfileName( OtherSMC_NonConst->GetCollisionProfileName() ); + SMC->SetCollisionEnabled( OtherSMC->GetCollisionEnabled() ); + SMC->LightmassSettings = OtherSMC->LightmassSettings; + SMC->CastShadow = OtherSMC->CastShadow; + SMC->SetMobility( OtherSMC->Mobility ); + + if (OtherSMC_NonConst->GetBodySetup() && SMC->GetBodySetup()) + { + // Copy the BodySetup + SMC->GetBodySetup()->CopyBodyPropertiesFrom(OtherSMC_NonConst->GetBodySetup()); + + // We need to recreate the physics mesh for the new body setup + SMC->GetBodySetup()->InvalidatePhysicsData(); + SMC->GetBodySetup()->CreatePhysicsMeshes(); + + // Only copy the physical material if it's different from the default one, + // As this was causing crashes on BakeToActors in some cases + if (GEngine != NULL && OtherSMC_NonConst->GetBodySetup()->GetPhysMaterial() != GEngine->DefaultPhysMaterial) + SMC->SetPhysMaterialOverride(OtherSMC_NonConst->GetBodySetup()->GetPhysMaterial()); + } + SMActor->SetActorHiddenInGame( OtherSMC->bHiddenInGame ); + SMC->SetVisibility( OtherSMC->IsVisible() ); + + // Reapply the uproperties modified by attributes on the new component + FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject( SMC, HoudiniGeoPartObject ); + + SMC->PostEditChange(); + }; + + const UInstancedStaticMeshComponent* OtherISMC = Cast< const UInstancedStaticMeshComponent>(OtherSMC); + if( OtherISMC && !OtherISMC->IsPendingKill() ) + { +#ifdef BAKE_TO_INSTANCEDSTATICMESHCOMPONENT_ACTORS + // This is an instanced static mesh component - we will create a generic AActor with a UInstancedStaticMeshComponent root + FActorSpawnParameters SpawnInfo; + SpawnInfo.OverrideLevel = DesiredLevel; + SpawnInfo.ObjectFlags = RF_Transactional; + SpawnInfo.Name = MakeUniqueObjectName( DesiredLevel, AActor::StaticClass(), BaseName ); + SpawnInfo.bDeferConstruction = true; + + if( AActor* NewActor = DesiredLevel->OwningWorld->SpawnActor( SpawnInfo ) ) + { + NewActor->SetActorLabel( NewActor->GetName() ); + NewActor->SetActorHiddenInGame( OtherISMC->bHiddenInGame ); + + // Do we need to create a HISMC? + const UHierarchicalInstancedStaticMeshComponent* OtherHISMC = Cast< const UHierarchicalInstancedStaticMeshComponent>( OtherSMC ); + UInstancedStaticMeshComponent* NewISMC = nullptr; + if ( OtherHISMC ) + NewISMC = DuplicateObject< UHierarchicalInstancedStaticMeshComponent >(OtherHISMC, NewActor, *OtherHISMC->GetName() ) ); + else + NewISMC = DuplicateObject< UInstancedStaticMeshComponent >( OtherISMC, NewActor, *OtherISMC->GetName() ) ); + + if( NewISMC ) + { + NewISMC->SetupAttachment( nullptr ); + NewISMC->SetStaticMesh( BakedSM ); + NewActor->AddInstanceComponent( NewISMC ); + NewActor->SetRootComponent( NewISMC ); + NewISMC->SetWorldTransform( OtherISMC->GetComponentTransform() ); + NewISMC->RegisterComponent(); + + NewActor->SetFolderPath( BaseName ); + NewActor->FinishSpawning( OtherISMC->GetComponentTransform() ); + + NewActors.Add( NewActor ); + NewActor->InvalidateLightingCache(); + NewActor->PostEditMove( true ); + NewActor->MarkPackageDirty(); + } + } +#else + // This is an instanced static mesh component - we will split it up into StaticMeshActors + for( int32 InstanceIx = 0; InstanceIx < OtherISMC->GetInstanceCount(); ++InstanceIx ) + { + FTransform InstanceTransform; + OtherISMC->GetInstanceTransform( InstanceIx, InstanceTransform, true ); + AActor* NewActor = Factory->CreateActor(BakedSM, DesiredLevel, InstanceTransform, RF_Transactional); + if (!NewActor || NewActor->IsPendingKill()) + continue; + + PrepNewStaticMeshActor( NewActor ); + + // We need to set the modified uproperty on the created actor + AStaticMeshActor* SMActor = Cast< AStaticMeshActor>(NewActor); + if ( !SMActor || SMActor->IsPendingKill() ) + continue; + + UStaticMeshComponent* SMC = SMActor->GetStaticMeshComponent(); + if ( !SMC || SMC->IsPendingKill() ) + continue; + FHoudiniGeoPartObject GeoPartObject = HoudiniAssetComponent->LocateGeoPartObject( OtherSMC->GetStaticMesh() ); + + // Set the part id to 0 so we can access the instancer + GeoPartObject.PartId = 0; + FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject( SMC, GeoPartObject ); + NewActors.Add(NewActor); + } +#endif + } + else + { + AActor* NewActor = Factory->CreateActor(BakedSM, DesiredLevel, OtherSMC->GetComponentTransform(), RF_Transactional); + if( NewActor && !NewActor->IsPendingKill() ) + { + PrepNewStaticMeshActor( NewActor ); + NewActors.Add(NewActor); + } + } + } +#endif + return NewActors; +} + +TArray +FHoudiniEngineBakeUtils::BakeHoudiniActorToActors_SplitMeshInstancers(UHoudiniAssetComponent * HoudiniAssetComponent, + TMap SplitMeshInstancerComponentToPart) +{ + TArray< AActor* > NewActors; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return NewActors; + +#if WITH_EDITOR + TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; + + for( auto&& Iter : SplitMeshInstancerComponentToPart ) + { + auto&& HoudiniGeoPartObject = Iter.Value; + auto&& OtherMSIC = Iter.Key; + + if ( !OtherMSIC || OtherMSIC->IsPendingKill() ) + continue; + + UStaticMesh * OtherSM = OtherMSIC->GetStaticMesh(); + + if( !OtherSM || OtherSM->IsPendingKill() ) + continue; + + CheckedBakeStaticMesh(HoudiniAssetComponent, OriginalToBakedMesh, HoudiniGeoPartObject, OtherSM); + } + // Done baking, now spawn the actors + for( auto&& Iter : SplitMeshInstancerComponentToPart ) + { + auto&& OtherMSIC = Iter.Key; + + UStaticMesh* BakedSM = OriginalToBakedMesh[OtherMSIC->GetStaticMesh()]; + if( !BakedSM || BakedSM->IsPendingKill() ) + continue; + + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + FName BaseName(*( HoudiniAssetComponent->GetOwner()->GetName() + TEXT("_Baked") )); + + // This is a split mesh instancer component - we will create a generic AActor with a bunch of SMC + FActorSpawnParameters SpawnInfo; + SpawnInfo.OverrideLevel = DesiredLevel; + SpawnInfo.ObjectFlags = RF_Transactional; + SpawnInfo.Name = MakeUniqueObjectName(DesiredLevel, AActor::StaticClass(), BaseName); + SpawnInfo.bDeferConstruction = true; + + AActor* NewActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); + if (!NewActor || NewActor->IsPendingKill()) + continue; + + NewActor->SetActorLabel(NewActor->GetName()); + NewActor->SetActorHiddenInGame(OtherMSIC->bHiddenInGame); + + for( UStaticMeshComponent* OtherSMC : OtherMSIC->GetInstances() ) + { + if ( !OtherSMC || OtherSMC->IsPendingKill() ) + continue; + + UStaticMeshComponent* NewSMC = DuplicateObject< UStaticMeshComponent >(OtherSMC, NewActor, *OtherSMC->GetName()); + if (!NewSMC || NewSMC->IsPendingKill()) + continue; + + NewSMC->SetupAttachment(nullptr); + NewSMC->SetStaticMesh(BakedSM); + NewActor->AddInstanceComponent(NewSMC); + NewSMC->SetWorldTransform(OtherSMC->GetComponentTransform()); + NewSMC->RegisterComponent(); + } + + NewActor->SetFolderPath(BaseName); + NewActor->FinishSpawning(OtherMSIC->GetComponentTransform()); + + NewActors.Add(NewActor); + NewActor->InvalidateLightingCache(); + NewActor->PostEditMove(true); + NewActor->MarkPackageDirty(); + } +#endif + return NewActors; +} + +UHoudiniAssetInput* +FHoudiniEngineBakeUtils::GetInputForBakeHoudiniActorToOutlinerInput( const UHoudiniAssetComponent * HoudiniAssetComponent ) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return nullptr; + + UHoudiniAssetInput *FirstInput = nullptr, *Result = nullptr; +#if WITH_EDITOR + if( HoudiniAssetComponent->GetInputs().Num() && HoudiniAssetComponent->GetInputs()[0] ) + { + FirstInput = HoudiniAssetComponent->GetInputs()[0]; + } + else + { + TMap< FString, UHoudiniAssetParameter* > InputParams; + HoudiniAssetComponent->CollectAllParametersOfType( UHoudiniAssetInput::StaticClass(), InputParams ); + TArray< UHoudiniAssetParameter* > InputParamsArray; + InputParams.GenerateValueArray( InputParamsArray ); + if( InputParamsArray.Num() > 0 ) + { + FirstInput = Cast( InputParamsArray[0] ); + } + } + + if( FirstInput && !FirstInput->IsPendingKill() ) + { + if( FirstInput->GetChoiceIndex() == EHoudiniAssetInputType::WorldInput && FirstInput->GetWorldOutlinerInputs().Num() == 1 ) + { + const FHoudiniAssetInputOutlinerMesh& InputOutlinerMesh = FirstInput->GetWorldOutlinerInputs()[0]; + if( InputOutlinerMesh.ActorPtr.IsValid() && InputOutlinerMesh.StaticMeshComponent ) + { + Result = FirstInput; + } + } + } +#endif + return Result; +} + +bool +FHoudiniEngineBakeUtils::CanComponentBakeToOutlinerInput( const UHoudiniAssetComponent * HoudiniAssetComponent ) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + +#if WITH_EDITOR + // TODO: Cache this + auto SMComponentToPart = HoudiniAssetComponent->CollectAllStaticMeshComponents(); + if( SMComponentToPart.Num() == 1 ) + { + return nullptr != GetInputForBakeHoudiniActorToOutlinerInput( HoudiniAssetComponent ); + } +#endif + return false; +} + +bool +FHoudiniEngineBakeUtils::CanComponentBakeToFoliage(const UHoudiniAssetComponent * HoudiniAssetComponent) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + const TArray< UHoudiniAssetInstanceInputField * > InstanceInputFields = HoudiniAssetComponent->GetAllInstanceInputFields(); + + return InstanceInputFields.Num() > 0; +} + +void +FHoudiniEngineBakeUtils::BakeHoudiniActorToOutlinerInput( UHoudiniAssetComponent * HoudiniAssetComponent ) +{ + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + return; + +#if WITH_EDITOR + TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; + TMap< const UStaticMeshComponent*, FHoudiniGeoPartObject > SMComponentToPart = HoudiniAssetComponent->CollectAllStaticMeshComponents(); + + for( const auto& Iter : SMComponentToPart ) + { + const FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Value; + const UStaticMeshComponent * OtherSMC = Iter.Key; + if ( !OtherSMC || OtherSMC->IsPendingKill() ) + continue; + + UStaticMesh* OtherSM = OtherSMC->GetStaticMesh(); + if( !OtherSM || OtherSM->IsPendingKill() ) + continue; + + UStaticMesh* BakedSM = nullptr; + if( UStaticMesh ** FoundMeshPtr = OriginalToBakedMesh.Find( OtherSM ) ) + { + // We've already baked this mesh, use it + BakedSM = *FoundMeshPtr; + } + else + { + // Bake the found mesh into the project + BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( + OtherSM, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::CreateNewAssets ); + + if( BakedSM ) + { + OriginalToBakedMesh.Add(OtherSM, BakedSM ); + FAssetRegistryModule::AssetCreated( BakedSM ); + } + } + } + + { + const FScopedTransaction Transaction( LOCTEXT( "BakeToInput", "Bake To Input" ) ); + + for( auto Iter : OriginalToBakedMesh ) + { + // Get the first outliner input + UHoudiniAssetInput* FirstInput = GetInputForBakeHoudiniActorToOutlinerInput(HoudiniAssetComponent); + if ( !FirstInput || FirstInput->IsPendingKill() ) + continue; + + const FHoudiniAssetInputOutlinerMesh& InputOutlinerMesh = FirstInput->GetWorldOutlinerInputs()[0]; + if( InputOutlinerMesh.ActorPtr.IsValid() && InputOutlinerMesh.StaticMeshComponent ) + { + UStaticMeshComponent* InOutSMC = InputOutlinerMesh.StaticMeshComponent; + if ( InOutSMC && !InOutSMC->IsPendingKill() ) + { + InputOutlinerMesh.ActorPtr->Modify(); + InOutSMC->SetStaticMesh(Iter.Value); + InOutSMC->InvalidateLightingCache(); + InOutSMC->MarkPackageDirty(); + } + + // Disconnect the input from the asset - InputOutlinerMesh now garbage + FirstInput->RemoveWorldOutlinerInput( 0 ); + } + + // Only handle the first Baked Mesh + break; + } + } +#endif +} + +// Bakes output instanced meshes to the level's foliage actor and removes the Houdini actor +bool +FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithFoliage( UHoudiniAssetComponent * HoudiniAssetComponent ) +{ + bool bSuccess = false; + +#if WITH_EDITOR + if ( FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(HoudiniAssetComponent) ) + { + bSuccess = FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + } +#endif + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent * HoudiniAssetComponent ) +{ +#if WITH_EDITOR + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + const FScopedTransaction Transaction(LOCTEXT("BakeToFoliage", "Bake To Foliage")); + + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return false; + + // Map storing original and baked Static Meshes + TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; + + int32 BakedCount = 0; + const TArray< UHoudiniAssetInstanceInputField * > InstanceInputFields = HoudiniAssetComponent->GetAllInstanceInputFields(); + for ( int32 Idx = 0; Idx < InstanceInputFields.Num(); ++Idx ) + { + const UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField = InstanceInputFields[ Idx ]; + if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) + continue; + + for ( int32 VariationIdx = 0; VariationIdx < HoudiniAssetInstanceInputField->InstanceVariationCount(); VariationIdx++ ) + { + UInstancedStaticMeshComponent * ISMC = + Cast( HoudiniAssetInstanceInputField->GetInstancedComponent( VariationIdx ) ); + + if (!ISMC || ISMC->IsPendingKill()) + continue; + + // If the original static mesh is used (the cooked mesh for HAPI), then we need to bake it to a file + // If not, we don't need to bake a new mesh as we're using a SM override, so can use the existing unreal asset + UStaticMesh* OutStaticMesh = Cast( HoudiniAssetInstanceInputField->GetInstanceVariation( VariationIdx ) ); + if ( HoudiniAssetInstanceInputField->IsOriginalObjectUsed( VariationIdx ) ) + { + UStaticMesh* OriginalSM = Cast(HoudiniAssetInstanceInputField->GetOriginalObject()); + if (!OriginalSM || OriginalSM->IsPendingKill()) + continue; + + // We're instancing a mesh created by the plugin, we need to bake it first + auto&& ItemGeoPartObject = HoudiniAssetComponent->LocateGeoPartObject(OutStaticMesh); + FHoudiniEngineBakeUtils::CheckedBakeStaticMesh(HoudiniAssetComponent, OriginalToBakedMesh, ItemGeoPartObject, OriginalSM); + OutStaticMesh = OriginalToBakedMesh[ OutStaticMesh ]; + } + + // See if we already have a FoliageType for that mesh + UFoliageType* FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(OutStaticMesh); + if ( !FoliageType || FoliageType->IsPendingKill() ) + { + // We need to create a new FoliageType for this Static Mesh + InstancedFoliageActor->AddMesh(OutStaticMesh, &FoliageType, HoudiniAssetComponent->GeneratedFoliageDefaultSettings); + } + + // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it + FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); + if ( !FoliageInfo ) + continue; + + // Get the transforms for this instance + TArray< FTransform > ProcessedTransforms; + HoudiniAssetInstanceInputField->GetProcessedTransforms(ProcessedTransforms, VariationIdx); + FTransform HoudiniAssetTransform = HoudiniAssetComponent->GetComponentTransform(); + + FFoliageInstance FoliageInstance; + for (auto CurrentTransform : ProcessedTransforms) + { + FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); + FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); + FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + } + + // TODO: This was due to a bug in UE4.22-20, check if still needed! + if ( FoliageInfo->GetComponent() ) + FoliageInfo->GetComponent()->BuildTreeIfOutdated(true, true); + + // Notify the user that we succesfully bake the instances to foliage + FString Notification = TEXT("Successfully baked ") + FString::FromInt(ProcessedTransforms.Num()) + TEXT(" instances of ") + OutStaticMesh->GetName() + TEXT(" to Foliage"); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + BakedCount += ProcessedTransforms.Num(); + } + } + + if (BakedCount > 0) + return true; +#endif + + return false; +} + +bool +FHoudiniEngineBakeUtils::StaticMeshRequiresBake( const UStaticMesh * StaticMesh ) +{ +#if WITH_EDITOR + if( !StaticMesh || StaticMesh->IsPendingKill() ) + return false; + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( "AssetRegistry" ); + + FAssetData BackingAssetData = AssetRegistryModule.Get().GetAssetByObjectPath( *StaticMesh->GetPathName() ); + if( !BackingAssetData.IsUAsset() ) + return true; + + for( const auto& StaticMaterial : StaticMesh->StaticMaterials ) + { + if( !StaticMaterial.MaterialInterface || StaticMaterial.MaterialInterface->IsPendingKill() ) + continue; + + BackingAssetData = AssetRegistryModule.Get().GetAssetByObjectPath(*StaticMaterial.MaterialInterface->GetPathName()); + if (!BackingAssetData.IsUAsset()) + return true; + } +#endif + return false; +} + +UMaterial * +FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( + UMaterial * Material, FHoudiniCookParams& HoudiniCookParams, const FString & SubMaterialName ) +{ + UMaterial * DuplicatedMaterial = nullptr; +#if WITH_EDITOR + // Create material package. + FString MaterialName; + UPackage * MaterialPackage = FHoudiniEngineBakeUtils::BakeCreateTextureOrMaterialPackageForComponent( + HoudiniCookParams, SubMaterialName, MaterialName ); + + if( !MaterialPackage || MaterialPackage->IsPendingKill() ) + return nullptr; + + // Clone material. + DuplicatedMaterial = DuplicateObject< UMaterial >( Material, MaterialPackage, *MaterialName ); + if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + return nullptr; + + if( HoudiniCookParams.MaterialAndTextureBakeMode != EBakeMode::Intermediate ) + DuplicatedMaterial->SetFlags( RF_Public | RF_Standalone ); + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, DuplicatedMaterial, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT( "true" ) ); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, DuplicatedMaterial, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialName ); + + // Retrieve and check various sampling expressions. If they contain textures, duplicate (and bake) them. + + for( auto& Expression : DuplicatedMaterial->Expressions ) + { + FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( + Expression, HoudiniCookParams ); + } + + // Notify registry that we have created a new duplicate material. + FAssetRegistryModule::AssetCreated( DuplicatedMaterial ); + + // Dirty the material package. + DuplicatedMaterial->MarkPackageDirty(); + + // Reset any derived state + DuplicatedMaterial->ForceRecompileForRendering(); +#endif + return DuplicatedMaterial; +} + +void +FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( + UMaterialExpression * MaterialExpression, FHoudiniCookParams& HoudiniCookParams ) +{ +#if WITH_EDITOR + UMaterialExpressionTextureSample * TextureSample = Cast< UMaterialExpressionTextureSample >( MaterialExpression ); + if( !TextureSample || TextureSample->IsPendingKill() ) + return; + + UTexture2D * Texture = Cast< UTexture2D >( TextureSample->Texture ); + if ( !Texture || Texture->IsPendingKill() ) + return; + + UPackage * TexturePackage = Cast< UPackage >( Texture->GetOuter() ); + if( !TexturePackage || TexturePackage->IsPendingKill() ) + return; + + FString GeneratedTextureName; + if( FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( + TexturePackage, Texture, GeneratedTextureName ) ) + { + // Duplicate texture. + UTexture2D * DuplicatedTexture = FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( + Texture, HoudiniCookParams, GeneratedTextureName ); + + // Re-assign generated texture. + TextureSample->Texture = DuplicatedTexture; + } +#endif +} + +UTexture2D * +FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( + UTexture2D * Texture, FHoudiniCookParams& HoudiniCookParams, const FString & SubTextureName ) +{ + UTexture2D* DuplicatedTexture = nullptr; +#if WITH_EDITOR + // Retrieve original package of this texture. + UPackage * TexturePackage = Cast< UPackage >( Texture->GetOuter() ); + if( !TexturePackage || TexturePackage->IsPendingKill() ) + return nullptr; + + FString GeneratedTextureName; + if( FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( TexturePackage, Texture, GeneratedTextureName ) ) + { + UMetaData * MetaData = TexturePackage->GetMetaData(); + if ( !MetaData || MetaData->IsPendingKill() ) + return nullptr; + + // Retrieve texture type. + const FString & TextureType = + MetaData->GetValue( Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE ); + + // Create texture package. + FString TextureName; + UPackage * NewTexturePackage = FHoudiniEngineBakeUtils::BakeCreateTextureOrMaterialPackageForComponent( + HoudiniCookParams, SubTextureName, TextureName ); + + if( !NewTexturePackage || NewTexturePackage->IsPendingKill() ) + return nullptr; + + // Clone texture. + DuplicatedTexture = DuplicateObject< UTexture2D >( Texture, NewTexturePackage, *TextureName ); + if ( !DuplicatedTexture || DuplicatedTexture->IsPendingKill() ) + return nullptr; + + if( HoudiniCookParams.MaterialAndTextureBakeMode != EBakeMode::Intermediate ) + DuplicatedTexture->SetFlags( RF_Public | RF_Standalone ); + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT( "true" ) ); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *TextureName ); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType ); + + // Notify registry that we have created a new duplicate texture. + FAssetRegistryModule::AssetCreated( DuplicatedTexture ); + + // Dirty the texture package. + DuplicatedTexture->MarkPackageDirty(); + } +#endif + return DuplicatedTexture; +} + +FHoudiniCookParams::FHoudiniCookParams( UHoudiniAssetComponent* HoudiniAssetComponent ) +{ +#if WITH_EDITOR + HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset(); + HoudiniCookManager = HoudiniAssetComponent; + PackageGUID = HoudiniAssetComponent->GetComponentGuid(); + BakedStaticMeshPackagesForParts = &HoudiniAssetComponent->BakedStaticMeshPackagesForParts; + BakedMaterialPackagesForIds = &HoudiniAssetComponent->BakedMaterialPackagesForIds; + CookedTemporaryStaticMeshPackages = &HoudiniAssetComponent->CookedTemporaryStaticMeshPackages; + CookedTemporaryPackages = &HoudiniAssetComponent->CookedTemporaryPackages; + CookedTemporaryLandscapeLayers = &HoudiniAssetComponent->CookedTemporaryLandscapeLayers; + TempCookFolder = HoudiniAssetComponent->GetTempCookFolder(); + BakeFolder = HoudiniAssetComponent->GetBakeFolder(); + BakeNameOverrides = &HoudiniAssetComponent->BakeNameOverrides; + IntermediateOuter = HoudiniAssetComponent->GetComponentLevel(); + GeneratedDistanceFieldResolutionScale = HoudiniAssetComponent->GeneratedDistanceFieldResolutionScale; +#endif +} + +bool +FHoudiniEngineBakeUtils::BakeLandscape( UHoudiniAssetComponent* HoudiniAssetComponent, ALandscapeProxy * OnlyBakeThisLandscape ) +{ +#if WITH_EDITOR + if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) + return false; + + if ( !HoudiniAssetComponent->HasLandscape() ) + return false; + + TMap< FHoudiniGeoPartObject, TWeakObjectPtr> * LandscapeComponentsPtr = HoudiniAssetComponent->GetLandscapeComponents(); + if ( !LandscapeComponentsPtr ) + return false; + + TArray LayerPackages; + bool bNeedToUpdateProperties = false; + for ( TMap< FHoudiniGeoPartObject, TWeakObjectPtr >::TIterator Iter(* LandscapeComponentsPtr ); Iter; ++Iter) + { + ALandscapeProxy * CurrentLandscape = Iter.Value().Get(); + if ( !CurrentLandscape || CurrentLandscape->IsPendingKill() || !CurrentLandscape->IsValidLowLevel() ) + continue; + + // If we only want to bake a single landscape + if ( OnlyBakeThisLandscape && CurrentLandscape != OnlyBakeThisLandscape ) + continue; + + // Simply remove the landscape from the map + FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key(); + LandscapeComponentsPtr->Remove( HoudiniGeoPartObject ); + + CurrentLandscape->DetachFromActor( FDetachmentTransformRules::KeepWorldTransform ); + + // And save its layers to prevent them from being removed + for ( TMap< TWeakObjectPtr< UPackage >, FHoudiniGeoPartObject > ::TIterator IterPackage( HoudiniAssetComponent->CookedTemporaryLandscapeLayers ); IterPackage; ++IterPackage ) + { + if ( !( HoudiniGeoPartObject == IterPackage.Value() ) ) + continue; + + UPackage * Package = IterPackage.Key().Get(); + if ( Package && !Package->IsPendingKill() ) + LayerPackages.Add( Package ); + } + + bNeedToUpdateProperties = true; + + // If we only wanted to bake a single landscape, we're done + if ( OnlyBakeThisLandscape ) + break; + } + + if ( LayerPackages.Num() > 0 ) + { + // Save the layer info's package + FEditorFileUtils::PromptForCheckoutAndSave( LayerPackages, true, false ); + + // Remove the packages from the asset component, or the asset component might + // destroy them when it is being destroyed + for ( int32 n = 0; n < LayerPackages.Num(); n++ ) + HoudiniAssetComponent->CookedTemporaryLandscapeLayers.Remove( LayerPackages[n] ); + } + + return bNeedToUpdateProperties; +#else + return false; +#endif +} + +UPackage * +FHoudiniEngineBakeUtils::BakeCreateMaterialPackageForComponent( + FHoudiniCookParams& HoudiniCookParams, + const HAPI_MaterialInfo & MaterialInfo, FString & MaterialName ) +{ + UHoudiniAsset * HoudiniAsset = HoudiniCookParams.HoudiniAsset; + if ( !HoudiniAsset || HoudiniAsset->IsPendingKill() ) + return nullptr; + + FString MaterialDescriptor; + if( HoudiniCookParams.MaterialAndTextureBakeMode != EBakeMode::Intermediate ) + MaterialDescriptor = HoudiniAsset->GetName() + TEXT( "_material_" ) + FString::FromInt( MaterialInfo.nodeId ) + TEXT( "_" ); + else + MaterialDescriptor = HoudiniAsset->GetName() + TEXT( "_" ) + FString::FromInt( MaterialInfo.nodeId ) + TEXT( "_" ); + + return FHoudiniEngineBakeUtils::BakeCreateTextureOrMaterialPackageForComponent( + HoudiniCookParams, MaterialDescriptor, MaterialName ); +} + +UPackage * +FHoudiniEngineBakeUtils::BakeCreateTexturePackageForComponent( + FHoudiniCookParams& HoudiniCookParams, + const HAPI_MaterialInfo & MaterialInfo, const FString & TextureType, + FString & TextureName ) +{ + UHoudiniAsset * HoudiniAsset = HoudiniCookParams.HoudiniAsset; + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + return nullptr; + + FString TextureInfoDescriptor; + if ( HoudiniCookParams.MaterialAndTextureBakeMode != EBakeMode::Intermediate ) + { + TextureInfoDescriptor = HoudiniAsset->GetName() + TEXT( "_texture_" ) + FString::FromInt( MaterialInfo.nodeId ) + + TEXT( "_" ) + TextureType + TEXT( "_" ); + } + else + { + TextureInfoDescriptor = HoudiniAsset->GetName() + TEXT( "_" ) + FString::FromInt( MaterialInfo.nodeId ) + TEXT( "_" ) + + TextureType + TEXT( "_" ); + } + + return FHoudiniEngineBakeUtils::BakeCreateTextureOrMaterialPackageForComponent( + HoudiniCookParams, TextureInfoDescriptor, TextureName ); +} + +UPackage * +FHoudiniEngineBakeUtils::BakeCreateTextureOrMaterialPackageForComponent( + FHoudiniCookParams& HoudiniCookParams, + const FString & MaterialInfoDescriptor, + FString & MaterialName ) +{ + UPackage * PackageNew = nullptr; + +#if WITH_EDITOR + EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + UHoudiniAsset * HoudiniAsset = HoudiniCookParams.HoudiniAsset; + FGuid BakeGUID; + FString PackageName; + + const FGuid & ComponentGUID = HoudiniCookParams.PackageGUID; + FString ComponentGUIDString = ComponentGUID.ToString().Left( FHoudiniEngineUtils::PackageGUIDComponentNameLength ); + + if ( ( BakeMode == EBakeMode::ReplaceExisitingAssets ) || ( BakeMode == EBakeMode::CookToTemp ) ) + { + bool bRemovePackageFromCache = false; + + UPackage* FoundPackage = nullptr; + if (BakeMode == EBakeMode::ReplaceExisitingAssets) + { + TWeakObjectPtr< UPackage > * FoundPointer = HoudiniCookParams.BakedMaterialPackagesForIds->Find(MaterialInfoDescriptor); + if ( FoundPointer ) + { + if ( (*FoundPointer).IsValid() ) + FoundPackage = (*FoundPointer).Get(); + } + else + { + bRemovePackageFromCache = true; + } + } + else + { + TWeakObjectPtr< UPackage > * FoundPointer = HoudiniCookParams.CookedTemporaryPackages->Find(MaterialInfoDescriptor); + if (FoundPointer) + { + if ( (*FoundPointer).IsValid() ) + FoundPackage = (*FoundPointer).Get(); + } + else + { + bRemovePackageFromCache = true; + } + } + + // Find a previously baked / cooked asset + if ( FoundPackage && !FoundPackage->IsPendingKill() ) + { + if ( UPackage::IsEmptyPackage( FoundPackage ) ) + { + // This happens when the prior baked output gets renamed, we can delete this + // orphaned package so that we can re-use the name + FoundPackage->ClearFlags( RF_Standalone ); + FoundPackage->ConditionalBeginDestroy(); + + bRemovePackageFromCache = true; + } + else + { + if ( CheckPackageSafeForBake( FoundPackage, MaterialName ) && !MaterialName.IsEmpty() ) + { + return FoundPackage; + } + else + { + // Found the package but we can't update it. We already issued an error, but should popup the standard reference error dialog + //::ErrorPopup( TEXT( "Baking Failed: Could not overwrite %s, because it is being referenced" ), *(*FoundPackage)->GetPathName() ); + + // If we're cooking, we'll create a new package, if baking, fail + if ( BakeMode != EBakeMode::CookToTemp ) + return nullptr; + } + } + + bRemovePackageFromCache = true; + } + + if ( bRemovePackageFromCache ) + { + // Package is either invalid / not found so we need to remove it from the cache + if ( BakeMode == EBakeMode::ReplaceExisitingAssets ) + HoudiniCookParams.BakedMaterialPackagesForIds->Remove( MaterialInfoDescriptor ); + else + HoudiniCookParams.CookedTemporaryPackages->Remove( MaterialInfoDescriptor ); + } + } + + while ( true ) + { + if ( !BakeGUID.IsValid() ) + BakeGUID = FGuid::NewGuid(); + + // We only want half of generated guid string. + FString BakeGUIDString = BakeGUID.ToString().Left( FHoudiniEngineUtils::PackageGUIDItemNameLength ); + + // Generate material name. + MaterialName = MaterialInfoDescriptor; + MaterialName += BakeGUIDString; + + switch (BakeMode) + { + case EBakeMode::Intermediate: + { + // Generate unique package name. + PackageName = FPackageName::GetLongPackagePath( HoudiniAsset->GetOuter()->GetName() ) + + TEXT("/") + + HoudiniAsset->GetName() + + TEXT("_") + + ComponentGUIDString + + TEXT("/") + + MaterialName; + } + break; + + case EBakeMode::CookToTemp: + { + PackageName = HoudiniCookParams.TempCookFolder.ToString() + TEXT("/") + MaterialName; + } + break; + + default: + { + // Generate unique package name. + PackageName = HoudiniCookParams.BakeFolder.ToString() + TEXT("/") + MaterialName; + } + break; + } + + // Sanitize package name. + PackageName = UPackageTools::SanitizePackageName( PackageName ); + + UObject * OuterPackage = nullptr; + if ( BakeMode == EBakeMode::Intermediate ) + { + // If we are not baking, then use outermost package, since objects within our package need to be visible + // to external operations, such as copy paste. + OuterPackage = HoudiniCookParams.IntermediateOuter; + } + + // See if package exists, if it does, we need to regenerate the name. + UPackage * Package = FindPackage( OuterPackage, *PackageName ); + if ( Package && !Package->IsPendingKill() ) + { + // Package does exist, there's a collision, we need to generate a new name. + BakeGUID.Invalidate(); + } + else + { + // Create actual package. + PackageNew = CreatePackage( OuterPackage, *PackageName ); + PackageNew->SetPackageFlags(RF_Standalone); + break; + } + } + + if( PackageNew && !PackageNew->IsPendingKill() + && ( ( BakeMode == EBakeMode::ReplaceExisitingAssets ) || ( BakeMode == EBakeMode::CookToTemp ) ) ) + { + // Add the new package to the cache + if ( BakeMode == EBakeMode::ReplaceExisitingAssets ) + HoudiniCookParams.BakedMaterialPackagesForIds->Add( MaterialInfoDescriptor, PackageNew ); + else + HoudiniCookParams.CookedTemporaryPackages->Add( MaterialInfoDescriptor, PackageNew ); + } +#endif + return PackageNew; +} + +bool +FHoudiniEngineBakeUtils::CheckPackageSafeForBake( UPackage* Package, FString& FoundAssetName ) +{ + if (!Package || Package->IsPendingKill()) + return false; + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( "AssetRegistry" ); + TArray AssetsInPackage; + AssetRegistryModule.Get().GetAssetsByPackageName( Package->GetFName(), AssetsInPackage ); + for( const auto& AssetInfo : AssetsInPackage ) + { + UObject* AssetInPackage = AssetInfo.GetAsset(); + if (!AssetInPackage || AssetInPackage->IsPendingKill()) + continue; + + // Check and see whether we are referenced by any objects that won't be garbage collected (*including* the undo buffer) + FReferencerInformationList ReferencesIncludingUndo; + bool bReferencedInMemoryOrUndoStack = IsReferenced( AssetInPackage, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, &ReferencesIncludingUndo ); + if( bReferencedInMemoryOrUndoStack ) + { + // warn + HOUDINI_LOG_ERROR( TEXT( "Could not bake to %s because it is being referenced" ), *Package->GetPathName() ); + return false; + } + FoundAssetName = AssetInfo.AssetName.ToString(); + } + + return true; +} + +UPackage * +FHoudiniEngineBakeUtils::BakeCreateStaticMeshPackageForComponent( + FHoudiniCookParams& HoudiniCookParams, + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + FString & MeshName, FGuid & BakeGUID ) +{ + UPackage * PackageNew = nullptr; + +#if WITH_EDITOR + EBakeMode BakeMode = HoudiniCookParams.StaticMeshBakeMode; + FString PackageName; + int32 BakeCount = 0; + const FGuid & ComponentGUID = HoudiniCookParams.PackageGUID; + FString ComponentGUIDString = ComponentGUID.ToString().Left( + FHoudiniEngineUtils::PackageGUIDComponentNameLength ); + + while ( true ) + { + if( ( BakeMode == EBakeMode::ReplaceExisitingAssets ) || ( BakeMode == EBakeMode::CookToTemp ) ) + { + bool bRemovePackageFromCache = false; + + UPackage* FoundPackage = nullptr; + if (BakeMode == EBakeMode::ReplaceExisitingAssets) + { + TWeakObjectPtr< UPackage > * FoundPointer = HoudiniCookParams.BakedStaticMeshPackagesForParts->Find( HoudiniGeoPartObject ); + if ( FoundPointer ) + { + if ( ( *FoundPointer ).IsValid() ) + FoundPackage = ( *FoundPointer ).Get(); + } + else + { + bRemovePackageFromCache = true; + } + } + else + { + TWeakObjectPtr< UPackage > * FoundPointer = HoudiniCookParams.CookedTemporaryStaticMeshPackages->Find( HoudiniGeoPartObject ); + if ( FoundPointer ) + { + if ( ( *FoundPointer ).IsValid() ) + FoundPackage = ( *FoundPointer ).Get(); + } + else + { + bRemovePackageFromCache = true; + } + } + + // Find a previously baked / cooked asset + if ( FoundPackage && !FoundPackage->IsPendingKill() ) + { + if ( UPackage::IsEmptyPackage( FoundPackage ) ) + { + // This happens when the prior baked output gets renamed, we can delete this + // orphaned package so that we can re-use the name + FoundPackage->ClearFlags( RF_Standalone ); + FoundPackage->ConditionalBeginDestroy(); + + bRemovePackageFromCache = true; + } + else + { + if ( FHoudiniEngineBakeUtils::CheckPackageSafeForBake( FoundPackage, MeshName ) && !MeshName.IsEmpty() ) + { + return FoundPackage; + } + else + { + // Found the package but we can't update it. We already issued an error, but should popup the standard reference error dialog + //::ErrorPopup( TEXT( "Baking Failed: Could not overwrite %s, because it is being referenced" ), *(*FoundPackage)->GetPathName() ); + + // If we're cooking, we'll create a new package, if baking, fail + if ( BakeMode != EBakeMode::CookToTemp ) + return nullptr; + } + } + + bRemovePackageFromCache = true; + } + + if ( bRemovePackageFromCache ) + { + // Package is either invalid / not found so we need to remove it from the cache + if ( BakeMode == EBakeMode::ReplaceExisitingAssets ) + HoudiniCookParams.BakedStaticMeshPackagesForParts->Remove( HoudiniGeoPartObject ); + else + HoudiniCookParams.CookedTemporaryStaticMeshPackages->Remove( HoudiniGeoPartObject ); + } + } + + if ( !BakeGUID.IsValid() ) + BakeGUID = FGuid::NewGuid(); + + MeshName = HoudiniCookParams.HoudiniCookManager->GetBakingBaseName( HoudiniGeoPartObject ); + + if( BakeCount > 0 ) + { + MeshName += FString::Printf( TEXT( "_%02d" ), BakeCount ); + } + + switch ( BakeMode ) + { + case EBakeMode::Intermediate: + { + // We only want half of generated guid string. + FString BakeGUIDString = BakeGUID.ToString().Left( FHoudiniEngineUtils::PackageGUIDItemNameLength ); + + MeshName += TEXT("_") + + FString::FromInt(HoudiniGeoPartObject.ObjectId) + TEXT("_") + + FString::FromInt(HoudiniGeoPartObject.GeoId) + TEXT("_") + + FString::FromInt(HoudiniGeoPartObject.PartId) + TEXT("_") + + FString::FromInt(HoudiniGeoPartObject.SplitId) + TEXT("_") + + HoudiniGeoPartObject.SplitName + TEXT("_") + + BakeGUIDString; + + PackageName = FPackageName::GetLongPackagePath( HoudiniCookParams.HoudiniAsset->GetOuter()->GetName() ) + + TEXT("/") + + HoudiniCookParams.HoudiniAsset->GetName() + + TEXT("_") + + ComponentGUIDString + + TEXT("/") + + MeshName; + } + break; + + case EBakeMode::CookToTemp: + { + PackageName = HoudiniCookParams.TempCookFolder.ToString() + TEXT("/") + MeshName; + } + break; + + default: + { + PackageName = HoudiniCookParams.BakeFolder.ToString() + TEXT("/") + MeshName; + } + break; + } + + // Santize package name. + PackageName = UPackageTools::SanitizePackageName( PackageName ); + + UObject * OuterPackage = nullptr; + + if ( BakeMode == EBakeMode::Intermediate ) + { + // If we are not baking, then use outermost package, since objects within our package need to be visible + // to external operations, such as copy paste. + OuterPackage = HoudiniCookParams.IntermediateOuter; + } + + // See if package exists, if it does, we need to regenerate the name. + UPackage * Package = FindPackage( OuterPackage, *PackageName ); + + if ( Package && !Package->IsPendingKill() ) + { + if ( BakeMode != EBakeMode::Intermediate ) + { + // Increment bake counter + BakeCount++; + } + else + { + // Package does exist, there's a collision, we need to generate a new name. + BakeGUID.Invalidate(); + } + } + else + { + // Create actual package. + PackageNew = CreatePackage( OuterPackage, *PackageName ); + break; + } + } + + if ( PackageNew && !PackageNew->IsPendingKill() + && ( ( BakeMode == EBakeMode::ReplaceExisitingAssets ) || ( BakeMode == EBakeMode::CookToTemp ) ) ) + { + // Add the new package to the cache + if ( BakeMode == EBakeMode::ReplaceExisitingAssets ) + HoudiniCookParams.BakedStaticMeshPackagesForParts->Add( HoudiniGeoPartObject, PackageNew ); + else + HoudiniCookParams.CookedTemporaryStaticMeshPackages->Add( HoudiniGeoPartObject, PackageNew ); + } +#endif + return PackageNew; +} + + +void +FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + UPackage * Package, UObject * Object, const TCHAR * Key, + const TCHAR * Value) +{ + if ( !Package || Package->IsPendingKill() ) + return; + + UMetaData * MetaData = Package->GetMetaData(); + if ( MetaData && !MetaData->IsPendingKill() ) + MetaData->SetValue( Object, Key, Value ); +} + +bool +FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( + UPackage * Package, UObject * Object, FString & HoudiniName ) +{ + if (!Package || Package->IsPendingKill()) + return false; + + UMetaData * MetaData = Package->GetMetaData(); + if ( !MetaData || MetaData->IsPendingKill() ) + return false; + + if ( MetaData->HasValue( Object, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT ) ) + { + // Retrieve name used for package generation. + const FString NameFull = MetaData->GetValue( Object, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME ); + HoudiniName = NameFull.Left( NameFull.Len() - FHoudiniEngineUtils::PackageGUIDItemNameLength ); + + return true; + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent * HoudiniAssetComponent) +{ + // Helper function used by the replace function to delete the Houdini Asset Actor after it's been baked + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + bool bSuccess = false; +#if WITH_EDITOR + // We can initiate Houdini actor deletion. + AActor * ActorOwner = HoudiniAssetComponent->GetOwner(); + if (!ActorOwner || ActorOwner->IsPendingKill()) + return bSuccess; + + // Remove Houdini actor from active selection in editor and delete it. + if (GEditor) + { + GEditor->SelectActor(ActorOwner, false, false); + ULayersSubsystem* LayerSubSystem = GEditor->GetEditorSubsystem(); + if (LayerSubSystem) + LayerSubSystem->DisassociateActorFromLayers(ActorOwner); + } + + UWorld * World = ActorOwner->GetWorld(); + if (!World) + World = GWorld; + + bSuccess = World->EditorDestroyActor(ActorOwner, false); +#endif + + return bSuccess; +} \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineBakeUtils.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineBakeUtils.h new file mode 100644 index 00000000..4b58ced8 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineBakeUtils.h @@ -0,0 +1,200 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniGeoPartObject.h" +#include "HoudiniCookHandler.h" + +class UHoudiniAssetComponent; +struct FHoudiniCookParams; +class UStaticMesh; +class AActor; +class UStaticMeshComponent; +class UTexture2D; + +struct HOUDINIENGINERUNTIME_API FHoudiniEngineBakeUtils +{ +public: + + /** Bake static mesh. **/ + static UStaticMesh * BakeStaticMesh( + UHoudiniAssetComponent * HoudiniAssetComponent, + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + UStaticMesh * StaticMesh ); + + + /** Bake blueprint. **/ + static class UBlueprint * BakeBlueprint( + UHoudiniAssetComponent * HoudiniAssetComponent ); + + /** Bake blueprint, instantiate and replace Houdini actor. **/ + static AActor * ReplaceHoudiniActorWithBlueprint( + UHoudiniAssetComponent * HoudiniAssetComponent ); + + /** Create a package for given component for blueprint baking. **/ + static UPackage * BakeCreateBlueprintPackageForComponent( + UHoudiniAssetComponent * HoudiniAssetComponent, + FString & BlueprintName ); + + + /** Bake output meshes and materials to packages and create corresponding actors in the scene */ + static bool BakeHoudiniActorToActors( + UHoudiniAssetComponent * HoudiniAssetComponent, + bool SelectNewActors); + + /** Bake to actor, and replace Houdini actor. **/ + static bool ReplaceHoudiniActorWithActors( + UHoudiniAssetComponent * HoudiniAssetComponent, + bool SelectNewActors); + + /** Helper for baking static meshes to actors */ + static TArray< AActor* > BakeHoudiniActorToActors_StaticMeshes( + UHoudiniAssetComponent * HoudiniAssetComponent, + TMap< const UStaticMeshComponent*, FHoudiniGeoPartObject >& SMComponentToPart ); + + /** Helper for baking Instanced Actors to actors */ + static TArray< AActor* > BakeHoudiniActorToActors_InstancedActors( + UHoudiniAssetComponent * HoudiniAssetComponent, + TMap< const class UHoudiniInstancedActorComponent*, + FHoudiniGeoPartObject >& ComponentToPart ); + + /** Helper for baking MeshSplitInstancers to actors */ + static TArray< AActor* > BakeHoudiniActorToActors_SplitMeshInstancers( + UHoudiniAssetComponent * HoudiniAssetComponent, + TMap SplitMeshInstancerComponentToPart); + + + /** Helper for baking an SM only if necessary */ + static void CheckedBakeStaticMesh( + class UHoudiniAssetComponent* HoudiniAssetComponent, + TMap< const UStaticMesh*, UStaticMesh* >& OriginalToBakedMesh, + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + UStaticMesh* OriginalSM); + + /** Duplicate a given material. This will create a new package for it. This will also create necessary textures **/ + /** and their corresponding packages. **/ + static class UMaterial * DuplicateMaterialAndCreatePackage( + class UMaterial * Material, + FHoudiniCookParams& HoudiniCookParams, + const FString & SubMaterialName ); + + /** Duplicate a given texture. This will create a new package for it. **/ + static UTexture2D * DuplicateTextureAndCreatePackage( + UTexture2D * Texture, + FHoudiniCookParams& HoudiniCookParams, + const FString & SubTextureName ); + + /** Replace duplicated texture with a new copy within a given sampling expression. **/ + static void ReplaceDuplicatedMaterialTextureSample( + class UMaterialExpression * MaterialExpression, + FHoudiniCookParams& HoudiniCookParams ); + + /** Returns true if the supplied static mesh has unbaked (not backed by a .uasset) mesh or material */ + static bool StaticMeshRequiresBake( + const UStaticMesh * StaticMesh ); + + /** Duplicate a given static mesh. This will create a new package for it. This will also create necessary **/ + /** materials and textures and their corresponding packages. **/ + static UStaticMesh * DuplicateStaticMeshAndCreatePackage( + const UStaticMesh * StaticMesh, + UHoudiniAssetComponent * Component, + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + EBakeMode BakeMode ); + + /** Get a candidate for baking to outliner input workflow */ + static class UHoudiniAssetInput* GetInputForBakeHoudiniActorToOutlinerInput( + const UHoudiniAssetComponent * HoudiniAssetComponent ); + + /** Returns true if the conditions are met for Bake to Input action ( 1 static mesh output and first input is world outliner with a static mesh) */ + static bool CanComponentBakeToOutlinerInput( + const UHoudiniAssetComponent * HoudiniAssetComponent ); + + /** Return true if we can bake to foliage (we need at least 1 instancer component) **/ + static bool CanComponentBakeToFoliage( + const UHoudiniAssetComponent * HoudiniAssetComponent ); + + /** Bakes output meshes and materials to packages and sets them on an input */ + static void BakeHoudiniActorToOutlinerInput( + UHoudiniAssetComponent * HoudiniAssetComponent ); + + /** Bakes output instanced meshes to the level's foliage actor */ + static bool BakeHoudiniActorToFoliage( + UHoudiniAssetComponent * HoudiniAssetComponent ); + + /** Bakes output instanced meshes to the level's foliage actor and removes the Houdini actor */ + static bool ReplaceHoudiniActorWithFoliage( + UHoudiniAssetComponent * HoudiniAssetComponent); + + /** Bakes landscape (detach them from the asset), if OnlyBakeThisLandscape is null, all landscapes will be baked **/ + static bool BakeLandscape( + UHoudiniAssetComponent* HoudiniAssetComponent, + class ALandscapeProxy * OnlyBakeThisLandscape = nullptr ); + + /** Create a package for a given component for material. **/ + static UPackage * BakeCreateMaterialPackageForComponent( + FHoudiniCookParams& HoudiniCookParams, + const HAPI_MaterialInfo & MaterialInfo, + FString & MaterialName ); + + /** Create a package for a given component for texture. **/ + static UPackage * BakeCreateTexturePackageForComponent( + FHoudiniCookParams& HoudiniCookParams, + const HAPI_MaterialInfo & MaterialInfo, + const FString & TextureType, + FString & TextureName ); + + /** Create a package for a given component for either a texture or material **/ + static UPackage * BakeCreateTextureOrMaterialPackageForComponent( + FHoudiniCookParams& HoudiniCookParams, + const FString & MaterialInfoDescriptor, + FString & MaterialName ); + + /** Create a package for given component for static mesh baking. **/ + static UPackage * BakeCreateStaticMeshPackageForComponent( + FHoudiniCookParams& HoudiniCookParams, + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + FString & MeshName, + FGuid & BakeGUID ); + + /** Checks the package is not referenced or marked for garbage collection **/ + static bool CheckPackageSafeForBake( + UPackage* Package, FString& FoundAssetName ); + + /** Add Houdini meta information to package for a given object. **/ + static void AddHoudiniMetaInformationToPackage( + UPackage * Package, + UObject * Object, + const TCHAR * Key, + const TCHAR * Value ); + + /** Retrieve item name from Houdini meta information. **/ + static bool GetHoudiniGeneratedNameFromMetaInformation( + UPackage * Package, + UObject * Object, + FString & HoudiniName ); + + /** Used by the replace function to delete the Houdini Asset Actor after it's been baked */ + static bool DeleteBakedHoudiniAssetActor( + UHoudiniAssetComponent * HoudiniAssetComponent); +}; \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineCommandlet.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineCommandlet.cpp new file mode 100644 index 00000000..5021a52e --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineCommandlet.cpp @@ -0,0 +1,572 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniEngineCommandlet.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "CoreMinimal.h" +#include "Engine/StaticMesh.h" +#include "HAL/PlatformFilemanager.h" +#include "HAL/FileManagerGeneric.h" + + +const FString LocalAutoBakeFolder = TEXT("/HoudiniEngine/AutoBake/"); + +UHoudiniEngineConvertBgeoCommandlet::UHoudiniEngineConvertBgeoCommandlet() +{ + IsClient = false; + IsEditor = true; + IsServer = false; + LogToConsole = true; +} + +int32 UHoudiniEngineConvertBgeoCommandlet::Main( const FString& Params ) +{ + // Run me via UE4editor.exe my.uproject -run=HoudiniEngineConvertBgeo BGEO_IN (UASSET_OUT) + HOUDINI_LOG_MESSAGE( TEXT( "Houdini Engine Convert BGEO Commandlet" ) ); + + // Parse the params to a string arrays + TArray ArgumentsArray; + Params.ParseIntoArray(ArgumentsArray, TEXT(" "), true); + + // We're expecting at least one param (the bgeo in) and a maximum of 2 params (bgeo in, uasset out) + // The first param is the Commandlet name, so ignore that + if ( ( ArgumentsArray.Num() < 2 ) || ( ArgumentsArray.Num() > 3 ) ) + { + // Invalid number of arguments, Print usage and error out + HOUDINI_LOG_MESSAGE( TEXT( "HoudiniEngineConvertBgeoCommandlet" ) ); + HOUDINI_LOG_MESSAGE( TEXT( "Converts a .bgeo file to Static Meshes .uasset files." ) ); + + HOUDINI_LOG_MESSAGE( TEXT( "Usage: -run=HoudiniEngineConvertBgeo BGEO_IN UASSET_OUT" ) ); + + HOUDINI_LOG_MESSAGE( TEXT( "BGEO_IN" ) ); + HOUDINI_LOG_MESSAGE( TEXT( "\tPath to the the source .bgeo file to convert." ) ); + + HOUDINI_LOG_MESSAGE( TEXT( "UASSET_OUT (optional)" ) ); + HOUDINI_LOG_MESSAGE( TEXT( "\tPath for the converted uasset file. If not present, the directory/name of the bgeo file will be used" ) ); + + return 1; + } + + FString BGEOFilePath = ArgumentsArray[ 1 ]; + FString UASSETFilePath = ArgumentsArray.Num() > 2 ? ArgumentsArray[ 2 ] : FString(); + + if ( !FHoudiniCommandletUtils::ConvertBGEOFileToUAsset( BGEOFilePath, UASSETFilePath ) ) + return 1; + + return 0; +} + +UHoudiniEngineConvertBgeoDirCommandlet::UHoudiniEngineConvertBgeoDirCommandlet() +{ + IsClient = false; + IsEditor = true; + IsServer = false; + LogToConsole = true; +} + +int32 UHoudiniEngineConvertBgeoDirCommandlet::Main(const FString& Params) +{ + // Run me via UE4editor.exe my.uproject -run=HoudiniEngineConvertBgeoDir BGEO_DIR_IN (UASSET_DIR_OUT) + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Convert BGEO directory")); + + // Parse the params to a string arrays + TArray ArgumentsArray; + Params.ParseIntoArray(ArgumentsArray, TEXT(" "), true); + + // We're expecting at least one param (the bgeo dir in) and a maximum of 3 params (bgeo dir in, uasset dir out, timeout) + // The first param is the Commandlet name, so ignore that + if ( (ArgumentsArray.Num() < 1 ) || ( ArgumentsArray.Num() > 3 ) ) + { + // Invalid number of arguments, Print usage and error out + HOUDINI_LOG_MESSAGE(TEXT("HoudiniEngineTestCommandlet:")); + HOUDINI_LOG_MESSAGE(TEXT("Converts .bgeo files in directory to Static Meshes .uasset files in a Out directory.")); + + HOUDINI_LOG_MESSAGE(TEXT("Usage: -run=HoudiniEngineConvertBgeoDir BGEO_DIR_IN UASSET_DIR_OUT TIMEOUT")); + + HOUDINI_LOG_MESSAGE(TEXT("BGEO_DIR_IN")); + HOUDINI_LOG_MESSAGE(TEXT("\tPath to a directory containing the .bgeo files to convert.")); + + HOUDINI_LOG_MESSAGE(TEXT("UASSET_DIR_OUT (optional)")); + HOUDINI_LOG_MESSAGE(TEXT("\tPath for the converted uasset files.")); + + HOUDINI_LOG_MESSAGE(TEXT("TIMEOUT (optional)")); + HOUDINI_LOG_MESSAGE(TEXT("\tAfter this amount of time of inactivity, the commandlet will exit.")); + + return 1; + } + + FString BGEODirPath = ArgumentsArray[ 0 ]; + FString UASSETDirPath = ArgumentsArray.Num() > 1 ? ArgumentsArray[ 1 ] : BGEODirPath; + FString InactivityTimeOutStr = ArgumentsArray.Num() > 3 ? ArgumentsArray[ 2 ] : FString(); + float InactivityTimeOut = 1000.0f; + if ( InactivityTimeOutStr.IsNumeric() ) + InactivityTimeOut = FCString::Atof( *InactivityTimeOutStr ); + + // First check the source directory is valid + if ( !FPaths::DirectoryExists( BGEODirPath ) ) + { + // Cant find input BGEO dir + HOUDINI_LOG_ERROR( TEXT( "The source BGEO directory does not exist: %s" ), *BGEODirPath ); + return false; + } + + // Then the output directory + if ( !FPaths::DirectoryExists( UASSETDirPath ) ) + { + // Cant find Output dir + HOUDINI_LOG_ERROR( TEXT( "The output UASSET directory does not exist: %s" ), *UASSETDirPath ); + return false; + } + + HOUDINI_LOG_MESSAGE( TEXT( "Looking for .bgeo files in %s ." ), *BGEODirPath ); + + // Sleep time in seconds before listing the files again + const float SleepTime = 1.0f; + // Maximum number of conversion for a file + const int32 NumConvertAttempts = 2; + // If true, will literally try to erase all files before overwriting them + // Will also empty the "local" directory + const bool CrushFiles = true; + + // If true, source bgeo files will be deleted upon conversion + const bool DeleteFileAfterConversion = false; + + if ( CrushFiles ) + { + // Nuke everything in our temporary bake folder + FFileManagerGeneric::Get().DeleteDirectory( *LocalAutoBakeFolder, false, true ); + } + + // Map tracking the number of failures for a given file + TMap< FString, int32 > FailingFileMap; + + // M UndeletedFileMap; + + bool KeepLookingForFile = true; + float currentInactivity = 0.0f; + while ( KeepLookingForFile ) + { + // List all the .bgeo files in the directory + TArray< FString > CurrentFileList; + FFileManagerGeneric::Get().FindFiles( CurrentFileList, *BGEODirPath, TEXT( ".bgeo" ) ); + + // Convert the files we found + int32 ConversionCount = 0; + for ( int32 n = CurrentFileList.Num() - 1; n >= 0; n-- ) + { + FString CurrentFile = CurrentFileList[ n ]; + + // Skip undeleted files + if ( UndeletedFileMap.Contains( CurrentFile ) ) + continue; + + // Skip failing files + if ( FailingFileMap.Contains(CurrentFile) && FailingFileMap[ CurrentFile ] >= NumConvertAttempts ) + continue; + + // Build the in / out file names + FString BGEOFile = BGEODirPath + TEXT("/") + CurrentFileList[ n ]; + FString UASSETFile = UASSETDirPath + TEXT("/") + CurrentFileList[ n ].LeftChop(5) + TEXT(".uasset"); + + if ( CrushFiles ) + { + if ( FFileManagerGeneric::Get().FileExists( *UASSETFile ) ) + { + // Erase the file if it already exists! + FFileManagerGeneric::Get().Delete( *UASSETFile, false, true, true ); + } + } + + // Attempting to convert the file + ConversionCount++; + if ( !FHoudiniCommandletUtils::ConvertBGEOFileToUAsset( BGEOFile, UASSETFile ) ) + { + if ( FailingFileMap.Contains( CurrentFile ) ) + FailingFileMap[ CurrentFile ]++; + else + FailingFileMap.Add( CurrentFile, 1 ); + + continue; + } + + HOUDINI_LOG_MESSAGE(TEXT("Successfully converted BGEO file: %s to %s"), *BGEOFile, *UASSETFile ); + + // Delete the source BGEO + if ( !DeleteFileAfterConversion || !FFileManagerGeneric::Get().Delete( *BGEOFile, false, true, true ) ) + UndeletedFileMap.Add( CurrentFile ); + } + + // Update the inactivity counter + if ( ConversionCount == 0 ) + { + currentInactivity += SleepTime; + if ( (InactivityTimeOut > 0.0f ) && ( currentInactivity > InactivityTimeOut ) ) + KeepLookingForFile = false; + } + else + { + // reset the inactivity counter + currentInactivity = 0.0f; + } + + if ( CrushFiles ) + { + // Nuke everything in our temporary bake folder + FFileManagerGeneric::Get().DeleteDirectory(*LocalAutoBakeFolder, false, true); + } + + // Go to bed for a while... + FPlatformProcess::Sleep( SleepTime ); + } + + return 0; +} + + +bool FHoudiniCommandletUtils::ConvertBGEOFileToUAsset( const FString& InBGEOFilePath, const FString& OutUAssetFilePath ) +{ +#if WITH_EDITOR + //--------------------------------------------------------------------------------------------- + // 1. Handle the BGEO param + //--------------------------------------------------------------------------------------------- + + FString BGEOFilePath = InBGEOFilePath; + if ( !FPaths::FileExists( BGEOFilePath ) ) + { + // Cant find BGEO file + HOUDINI_LOG_ERROR( TEXT( "BGEO file %s could not be found!" ), *BGEOFilePath ); + return false; + } + + // Make sure we're using absolute path! + BGEOFilePath = FPaths::ConvertRelativePathToFull( BGEOFilePath ); + + // Split the file path + FString BGEOPath, BGEOFileName, BGEOExtension; + FPaths::Split( BGEOFilePath, BGEOPath, BGEOFileName, BGEOExtension ); + if ( BGEOExtension.IsEmpty() ) + BGEOExtension = TEXT("bgeo"); + + if ( BGEOExtension.Compare( TEXT("bgeo"), ESearchCase::IgnoreCase ) != 0 ) + { + // Not a bgeo file! + HOUDINI_LOG_ERROR( TEXT( "First argument %s is not a .bgeo FILE!"), *BGEOFilePath ); + return false; + } + + BGEOFilePath = BGEOPath + TEXT("/") + BGEOFileName + TEXT(".") + BGEOExtension; + + //--------------------------------------------------------------------------------------------- + // 2. Handle the UASSET param + //--------------------------------------------------------------------------------------------- + + FString UASSETFilePath = OutUAssetFilePath; + if ( UASSETFilePath.IsEmpty() ) + { + // No OUT parameter, build it from the bgeo + UASSETFilePath = BGEOPath + TEXT("/") + BGEOFileName + TEXT(".uasset"); + HOUDINI_LOG_MESSAGE( TEXT( "UASSET_OUT argument missing! Will use %s.") , *UASSETFilePath); + } + + // Make sure we're using absolute path! + UASSETFilePath = FPaths::ConvertRelativePathToFull( UASSETFilePath ); + if ( FPaths::FileExists( UASSETFilePath ) ) + { + // UAsset already exists, overwrite? + HOUDINI_LOG_MESSAGE( TEXT( "UASSET file : %s already exists, overwriting!"), *UASSETFilePath ); + } + + // Split the file path + FString UASSETPath, UASSETFileName, UASSETExtension; + FPaths::Split( UASSETFilePath, UASSETPath, UASSETFileName, UASSETExtension ); + + UASSETFilePath = UASSETPath + TEXT("/") + UASSETFileName + TEXT(".") + UASSETExtension; + + HOUDINI_LOG_MESSAGE( TEXT( "Converting BGEO file: %s to UASSET file : %s" ), *BGEOFilePath, *UASSETFilePath ); + + // ERROR Lambda + // Saves a debug HIP file through HEngine and returns an error (in case of HAPI/HEngine issue). + auto SaveDebugHipFileAndReturnError = [&]() + { + FString HipFileName = BGEOPath + BGEOFileName + TEXT(".hip"); + std::string HipFileNameStr = TCHAR_TO_ANSI(*HipFileName); + FHoudiniApi::SaveHIPFile(FHoudiniEngine::Get().GetSession(), HipFileNameStr.c_str(), false); + + return false; + }; + + //--------------------------------------------------------------------------------------------- + // 3. Load the bgeo file in HAPI + //--------------------------------------------------------------------------------------------- + + HAPI_NodeId NodeId = -1; + if ( !FHoudiniCommandletUtils::LoadBGEOFileInHAPI( BGEOFilePath, NodeId) ) + return SaveDebugHipFileAndReturnError(); + + //--------------------------------------------------------------------------------------------- + // 4. Create a package for the result + //--------------------------------------------------------------------------------------------- + + FString PackagePath = LocalAutoBakeFolder + UASSETFileName; + FString PackageFilePath = UPackageTools::SanitizePackageName( PackagePath ); + UPackage * Package = FindPackage( nullptr, *PackageFilePath ); + if ( !Package ) + { + // Create actual package. + Package = CreatePackage( nullptr, *PackageFilePath ); + if ( !Package ) + { + // Couldn't create the package + HOUDINI_LOG_ERROR( TEXT( "Could not create local Package for the UASSET !!!" ) ); + return false; + } + } + else + { + // Package already exists, overwrite? + HOUDINI_LOG_MESSAGE( TEXT( "Package %s already exists, overwriting!" ), *PackageFilePath ); + } + + // ERROR Lambda + // Tries to delete the local package and creates an error ( in case of UE4/Package error) + auto DeleteLocalPackageAndReturnError = [&]() + { + FFileManagerGeneric::Get().Delete( *PackageFilePath, false, true, true ); + return SaveDebugHipFileAndReturnError(); + }; + + //--------------------------------------------------------------------------------------------- + // 5. Create the Static Meshes from the result of the bgeo cooking + //--------------------------------------------------------------------------------------------- + + TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesOut; + if ( !FHoudiniCommandletUtils::CreateStaticMeshes( + BGEOFileName, NodeId, Package, StaticMeshesOut ) ) + { + // There was some cook errors + HOUDINI_LOG_ERROR(TEXT("Could not create Static Meshes from the bgeo!!!")); + return SaveDebugHipFileAndReturnError(); + } + + //--------------------------------------------------------------------------------------------- + // 6. Bake the resulting Static Meshes to the package + //--------------------------------------------------------------------------------------------- + + if ( !FHoudiniCommandletUtils::BakeStaticMeshesToPackage( BGEOFileName, StaticMeshesOut, Package ) ) + return SaveDebugHipFileAndReturnError(); + + //--------------------------------------------------------------------------------------------- + // 7. Delete the node in Houdini + //--------------------------------------------------------------------------------------------- + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( FHoudiniEngine::Get().GetSession(), NodeId ) ) + { + // Could not delete the bgeo's file sop ! + HOUDINI_LOG_WARNING( TEXT( "Could not delete the bgeo file sop for %s"), * BGEOFileName ); + } + + //--------------------------------------------------------------------------------------------- + // 8. Save the package + //--------------------------------------------------------------------------------------------- + + Package->SetDirtyFlag( true ); + Package->FullyLoad(); + + FString LocalPackageFileName = FPackageName::LongPackageNameToFilename( PackageFilePath, FPackageName::GetAssetPackageExtension() ); + if (!UPackage::SavePackage(Package, NULL, RF_Standalone, *LocalPackageFileName, GError, nullptr, false, true, SAVE_NoError)) + { + // There was some cook errors + HOUDINI_LOG_ERROR( TEXT( "Could not save the local package %s"), *PackageFilePath ); + return DeleteLocalPackageAndReturnError(); + } + + //--------------------------------------------------------------------------------------------- + // 9. Move the local package to its final destination + //--------------------------------------------------------------------------------------------- + + LocalPackageFileName = FPaths::ConvertRelativePathToFull( LocalPackageFileName ); + if (!FFileManagerGeneric::Get().Move(*UASSETFilePath, *LocalPackageFileName, true, true, false, false ) ) + { + HOUDINI_LOG_ERROR( TEXT("Could not move local package %s to %s"), *LocalPackageFileName, *UASSETFilePath); + return DeleteLocalPackageAndReturnError(); + } +#endif + return true; +} + + +bool FHoudiniCommandletUtils::LoadBGEOFileInHAPI( const FString& InputFilePath, HAPI_NodeId& NodeId ) +{ + NodeId = -1; + + // Check HoudiniEngine / HAPI init? + if ( !FHoudiniEngine::IsInitialized() ) + { + HOUDINI_LOG_ERROR( TEXT( "Couldn't initialize HoudiniEngine!") ); + return false; + } + + // Create a file SOP + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), -1, + "SOP/file", "bgeo", true, &NodeId ), false ); + + // Set the file path parameter + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, "file", &ParmId), false ); + + std::string ConvertedString = TCHAR_TO_UTF8( *InputFilePath ); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NodeId, ConvertedString.c_str(), ParmId, 0 ), false ); + + // Cook the node + HAPI_CookOptions CookOptions; + FHoudiniApi::CookOptions_Init(&CookOptions); + //FMemory::Memzero< HAPI_CookOptions >( CookOptions ); + CookOptions.curveRefineLOD = 8.0f; + CookOptions.clearErrorsAndWarnings = false; + CookOptions.maxVerticesPerPrimitive = 3; + CookOptions.splitGeosByGroup = false; + CookOptions.splitGeosByAttribute = false; + CookOptions.splitAttrSH = 0; + CookOptions.refineCurveToLinear = true; + CookOptions.handleBoxPartTypes = false; + CookOptions.handleSpherePartTypes = false; + CookOptions.splitPointsByVertexAttributes = false; + CookOptions.packedPrimInstancingMode = HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), NodeId, &CookOptions ), false ); + + // Wait for the cook to finish + int status = HAPI_STATE_MAX_READY_STATE + 1; + while ( status > HAPI_STATE_MAX_READY_STATE ) + { + // Retrieve the status + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), + HAPI_STATUS_COOK_STATE, &status ), false ); + + FString StatusString = FHoudiniEngineUtils::GetStatusString( HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS ); + HOUDINI_LOG_MESSAGE( TEXT( "Still Cooking, current status: %s." ), *StatusString ); + + // Go to bed.. + if ( status > HAPI_STATE_MAX_READY_STATE ) + FPlatformProcess::Sleep( 0.5f ); + } + + if ( status != HAPI_STATE_READY ) + { + // There was some cook errors + HOUDINI_LOG_ERROR( TEXT("Finished Cooking with errors!") ); + return false; + } + + HOUDINI_LOG_MESSAGE( TEXT( "Finished Cooking!" ) ); + + return true; +} + +bool FHoudiniCommandletUtils::CreateStaticMeshes( + const FString& InputName, HAPI_NodeId& NodeId, UPackage* OuterPackage, + TMap& StaticMeshesOut ) +{ + if (!OuterPackage || OuterPackage->IsPendingKill()) + return false; + + // Create a "fake" HoudiniAssetComponent + FName HACName( *InputName ); + UHoudiniAssetComponent* HoudiniAssetComponent = NewObject< UHoudiniAssetComponent >( OuterPackage, HACName ); + + // Create the CookParams + FHoudiniCookParams HoudiniCookParams( HoudiniAssetComponent ); + HoudiniCookParams.StaticMeshBakeMode = EBakeMode::CookToTemp; // EBakeMode::CreateNewAssets? + HoudiniCookParams.MaterialAndTextureBakeMode = EBakeMode::CookToTemp; // FHoudiniCookParams::GetDefaultMaterialAndTextureCookMode()? + + FTransform ComponentTransform; + TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesIn; + //TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesOut; + + // Create the Static Meshes from the cook result + bool ProcessResult = FHoudiniEngineUtils::CreateStaticMeshesFromHoudiniAsset( + NodeId, HoudiniCookParams, true, true, + StaticMeshesIn, StaticMeshesOut, ComponentTransform ); + + if ( !ProcessResult || StaticMeshesOut.Num() <= 0 ) + { + // There was some cook errors + HOUDINI_LOG_ERROR( TEXT( "Could not create Static Meshes from the bgeo!!!" ) ); + return false; + } + + return true; +} + +bool FHoudiniCommandletUtils::BakeStaticMeshesToPackage( + const FString& InputName, TMap& StaticMeshes, UPackage* OutPackage ) +{ + bool BakedSomething = false; + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshes ); Iter; ++Iter ) + { + // Get the Static Mesh + const FHoudiniGeoPartObject HoudiniGeoPartObject = Iter.Key(); + UStaticMesh* CurrentSM = Iter.Value(); + if ( !CurrentSM ) + continue; + + // Build a name for this Static Mesh + FName StaticMeshName = FName( *( InputName + CurrentSM->GetName() ) ); + + // Duplicate the Static MEsh to copy it to the package. + UStaticMesh* DuplicatedStaticMesh = DuplicateObject< UStaticMesh >( CurrentSM, OutPackage, StaticMeshName ); + if ( !DuplicatedStaticMesh ) + continue; + + // Set the proper flags on the duplicated mesh + DuplicatedStaticMesh->SetFlags( RF_Public | RF_Standalone ); + + // Notify the registry that we have created a new duplicate mesh. + FAssetRegistryModule::AssetCreated( DuplicatedStaticMesh ); + + // Dirty the static mesh package. + DuplicatedStaticMesh->MarkPackageDirty(); + BakedSomething = true; + } + + if ( !BakedSomething ) + { + // There was some cook errors + HOUDINI_LOG_ERROR( TEXT( "Could not create Static Meshes from the bgeo!!!" ) ); + return false; + } + + return true; +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineCommandlet.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineCommandlet.h new file mode 100644 index 00000000..f95f6026 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineCommandlet.h @@ -0,0 +1,79 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HAPI.h" +#include "Commandlets/Commandlet.h" +#include "Logging/LogMacros.h" +#include "HoudiniEngineCommandlet.generated.h" + +/** Declares a log category for this module. */ +//DECLARE_LOG_CATEGORY_EXTERN( LogHoudiniEngineCommandlet, Log, All ); + +class UStaticMesh; +struct FHoudiniGeoPartObject; + +struct FHoudiniCommandletUtils +{ + static bool ConvertBGEOFileToUAsset( const FString& InBGEOFilePath, const FString& OutUAssetFilePath ); + + static bool LoadBGEOFileInHAPI( const FString& InputFilePath, HAPI_NodeId& NodeId ); + + static bool CreateStaticMeshes( + const FString& InputName, HAPI_NodeId& NodeId, UPackage* OuterPackage, + TMap& StaticMeshesOut ); + + static bool BakeStaticMeshesToPackage( + const FString& InputName, TMap& StaticMeshes, UPackage* OutPackage ); +}; + +UCLASS() +class UHoudiniEngineConvertBgeoCommandlet : public UCommandlet +{ + GENERATED_BODY() + public: + + /** Default constructor. */ + UHoudiniEngineConvertBgeoCommandlet(); + + public: + + //~ UCommandlet interface + virtual int32 Main(const FString& Params) override; +}; + +UCLASS() +class UHoudiniEngineConvertBgeoDirCommandlet : public UCommandlet +{ + GENERATED_BODY() +public: + + /** Default constructor. */ + UHoudiniEngineConvertBgeoDirCommandlet(); + +public: + + //~ UCommandlet interface + virtual int32 Main(const FString& Params) override; +}; \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineInstancerUtils.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineInstancerUtils.cpp new file mode 100644 index 00000000..b6c3889f --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineInstancerUtils.cpp @@ -0,0 +1,549 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniEngineInstancerUtils.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineString.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" + +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +bool +FHoudiniEngineInstancerUtils::CreateAllInstancers( + FHoudiniCookParams& HoudiniCookParams, + const HAPI_NodeId& AssetId, + const TArray< FHoudiniGeoPartObject > & FoundInstancers, + TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshes, + USceneComponent* ParentComponent, + TMap< FHoudiniGeoPartObject, USceneComponent * >& Instancers, + TMap< FHoudiniGeoPartObject, USceneComponent * >& NewInstancers ) +{ + for ( const FHoudiniGeoPartObject& HoudiniGeoPartObject : FoundInstancers ) + { + if ( !HoudiniGeoPartObject.IsVisible() ) + continue; + + // Get object to be instanced. + HAPI_NodeId ObjectToInstance = HoudiniGeoPartObject.HapiObjectGetToInstanceId(); + + // We only handle packed prim and attribute overrides instancers for now. + bool bIsPackedPrimitiveInstancer = HoudiniGeoPartObject.IsPackedPrimitiveInstancer(); + bool bAttributeInstancerOverride = HoudiniGeoPartObject.IsAttributeOverrideInstancer(); + + // This is invalid combination, no object to instance and input is not an attribute instancer. + if ( !bIsPackedPrimitiveInstancer && !bAttributeInstancerOverride && ObjectToInstance == -1 ) + continue; + + // Extract the objects to instance and their transforms + TArray< FHoudiniGeoPartObject > InstancedGeoParts; + TArray< TArray< FTransform > > InstancedGeoPartsTransforms; + TArray< UObject *> InstancedObjects; + TArray< TArray< FTransform > > InstancedObjectsTransforms; + if ( !FHoudiniEngineInstancerUtils::GetInstancedObjectsAndTransforms( + HoudiniGeoPartObject, AssetId, + InstancedGeoParts, InstancedGeoPartsTransforms, + InstancedObjects, InstancedObjectsTransforms ) ) + continue; + + // We are instantiating HoudiniGeoPartObjects + // We need to find their corresponding meshes, and add them and their transform to the instanced Object arrays + if ( InstancedGeoParts.Num() > 0 && InstancedGeoPartsTransforms.Num() > 0 ) + { + // Handle Instanced GeoPartObjects here + for ( int32 InstancedGeoIndex = 0; InstancedGeoIndex < InstancedGeoParts.Num(); InstancedGeoIndex++ ) + { + const FHoudiniGeoPartObject& InstancedGeoPartObject = InstancedGeoParts[ InstancedGeoIndex ]; + + if ( !InstancedGeoPartsTransforms.IsValidIndex( InstancedGeoIndex ) ) + continue; + + const TArray< FTransform >& InstancedGeoPartTransforms = InstancedGeoPartsTransforms[ InstancedGeoIndex ]; + + // We need to find the mesh that corresponds to the GeoPartObject + UStaticMesh** FoundStaticMesh = StaticMeshes.Find( InstancedGeoPartObject ); + if ( FoundStaticMesh && *FoundStaticMesh ) + { + // We can add the static mesh and its transform + InstancedObjects.Add( *FoundStaticMesh ); + InstancedObjectsTransforms.Add( InstancedGeoPartTransforms ); + } + else if ( InstancedGeoPartObject.IsPackedPrimitiveInstancer() ) + { + // We are instantiating a packed primitive + TArray< FHoudiniGeoPartObject > AllInstancedPPGeoParts; + TArray< TArray< FTransform > > AllInstancedPPTransforms; + if ( FHoudiniEngineInstancerUtils::GetPackedPrimInstancerObjectsAndTransforms( + InstancedGeoPartObject, AllInstancedPPGeoParts, AllInstancedPPTransforms ) ) + { + for ( int32 InstancedPPIndex = 0; InstancedPPIndex < AllInstancedPPGeoParts.Num(); InstancedPPIndex++ ) + { + if ( !AllInstancedPPTransforms.IsValidIndex( InstancedPPIndex ) ) + continue; + + FHoudiniGeoPartObject InstancedPPGeoPart = AllInstancedPPGeoParts[ InstancedPPIndex ]; + TArray< FTransform > InstancedPPTransforms = AllInstancedPPTransforms[ InstancedPPIndex ]; + + // Try to find the static mesh corresponding to that PP GeoPart + UStaticMesh** FoundPPStaticMesh = StaticMeshes.Find( InstancedPPGeoPart ); + if ( !FoundPPStaticMesh || ( *FoundPPStaticMesh == nullptr ) ) + continue; + + // Build a combined list of all the transforms of the instancer and the packed prim's transform + TArray< FTransform > CombinedTransforms; + CombinedTransforms.Empty( InstancedPPTransforms.Num() * InstancedGeoPartTransforms.Num() ); + for ( const FTransform& InstancedTransform : InstancedGeoPartTransforms ) + { + for ( const FTransform& PPTransform : InstancedPPTransforms ) + { + CombinedTransforms.Add(PPTransform * InstancedTransform); + } + } + + // We can add the static mesh and its transform + InstancedObjects.Add( *FoundPPStaticMesh ); + InstancedObjectsTransforms.Add( CombinedTransforms ); + } + } + else + { + HOUDINI_LOG_WARNING( + TEXT("CreateAllInstancers for Packed Primitive: Could not find static mesh for object [%d %s], geo %d, part %d]"), + InstancedGeoPartObject.ObjectId, *InstancedGeoPartObject.ObjectName, InstancedGeoPartObject.GeoId, InstancedGeoPartObject.PartId); + } + } + } + } + + if ( InstancedObjects.Num() > 0 && InstancedObjectsTransforms.Num() > 0 ) + { + // Handle Instanced Unreal Objects here + for ( int32 InstancedObjectIndex = 0; InstancedObjectIndex < InstancedObjects.Num(); InstancedObjectIndex++ ) + { + UObject* InstancedObject = InstancedObjects[InstancedObjectIndex]; + + if (!InstancedObjectsTransforms.IsValidIndex(InstancedObjectIndex)) + continue; + + const TArray< FTransform >& InstancedObjectTransforms = InstancedObjectsTransforms[InstancedObjectIndex]; + + USceneComponent* CreatedSceneComponent = nullptr; + if (!CreateInstancerComponent(InstancedObject, InstancedObjectTransforms, HoudiniGeoPartObject, ParentComponent, CreatedSceneComponent)) + continue; + + if (!CreatedSceneComponent) + continue; + + NewInstancers.Add(HoudiniGeoPartObject, CreatedSceneComponent); + } + } + } + + return true; +} + +bool +FHoudiniEngineInstancerUtils::GetInstancedObjectsAndTransforms( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, const HAPI_NodeId& AssetId, + TArray< FHoudiniGeoPartObject >& InstancedGeoParts, TArray< TArray< FTransform > >& InstancedGeoPartsTransforms, + TArray< UObject *>& InstancedObjects, TArray< TArray< FTransform > >& InstancedObjectsTransforms ) +{ + // Get the instancer flags + bool bIsPackedPrimitiveInstancer = HoudiniGeoPartObject.IsPackedPrimitiveInstancer(); + bool bAttributeInstancerOverride = HoudiniGeoPartObject.IsAttributeOverrideInstancer(); + + if ( bIsPackedPrimitiveInstancer ) + { + return FHoudiniEngineInstancerUtils::GetPackedPrimInstancerObjectsAndTransforms( + HoudiniGeoPartObject, InstancedGeoParts, InstancedGeoPartsTransforms ); + } + else if ( bAttributeInstancerOverride ) + { + return FHoudiniEngineInstancerUtils::GetUnrealAttributeInstancerObjectsAndTransforms( + HoudiniGeoPartObject, AssetId, InstancedObjects, InstancedObjectsTransforms ); + } + + return false; + +} + +bool +FHoudiniEngineInstancerUtils::GetPackedPrimInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, + TArray< FHoudiniGeoPartObject >& InstancedGeoPart, + TArray< TArray< FTransform > >& InstancedTransforms ) +{ + if ( !HoudiniGeoPartObject.IsPackedPrimitiveInstancer() ) + return false; + + // This is using packed primitives + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, &PartInfo ), false ); + + /*// Retrieve part name. + FString PartName; + FHoudiniEngineString HoudiniEngineStringPartName( PartInfo.nameSH ); + HoudiniEngineStringPartName.ToFString( PartName );*/ + + // Get transforms for each instance + TArray InstancerPartTransforms; + InstancerPartTransforms.SetNumZeroed( PartInfo.instanceCount ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstancerPartTransforms( + FHoudiniEngine::Get().GetSession(), HoudiniGeoPartObject.GeoId, PartInfo.id, + HAPI_RSTORDER_DEFAULT, InstancerPartTransforms.GetData(), 0, PartInfo.instanceCount ), false ); + + // Get the part ids for parts being instanced + TArray InstancedPartIds; + InstancedPartIds.SetNumZeroed( PartInfo.instancedPartCount ); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstancedPartIds( + FHoudiniEngine::Get().GetSession(), HoudiniGeoPartObject.GeoId, PartInfo.id, + InstancedPartIds.GetData(), 0, PartInfo.instancedPartCount ), false ); + + // Convert the transform to Unreal's coordinate system + TArray InstancerUnrealTransforms; + InstancerUnrealTransforms.SetNumUninitialized( InstancerPartTransforms.Num() ); + for ( int32 InstanceIdx = 0; InstanceIdx < InstancerPartTransforms.Num(); ++InstanceIdx ) + { + const auto& InstanceTransform = InstancerPartTransforms[InstanceIdx]; + FHoudiniEngineUtils::TranslateHapiTransform( InstanceTransform, InstancerUnrealTransforms[ InstanceIdx ] ); + } + + for ( auto InstancedPartId : InstancedPartIds ) + { + // Create the GeoPartObject correspondin to the instanced part + FHoudiniGeoPartObject InstancedPart( HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId, HoudiniGeoPartObject.GeoId, InstancedPartId ); + InstancedPart.TransformMatrix = HoudiniGeoPartObject.TransformMatrix; + + InstancedGeoPart.Add( InstancedPart ); + InstancedTransforms.Add( InstancerUnrealTransforms ); + } + + return true; +} + +bool +FHoudiniEngineInstancerUtils::GetUnrealAttributeInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, + const HAPI_NodeId& AssetId, + TArray< UObject *>& InstancedObjects, + TArray< TArray< FTransform > >& InstancedTransforms ) +{ + if ( !HoudiniGeoPartObject.IsAttributeOverrideInstancer() ) + return false; + + // This is an attribute override. Unreal mesh is specified through an attribute and we use points. + std::string MarshallingAttributeInstanceOverride = HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE; + UHoudiniRuntimeSettings::GetSettingsValue( TEXT( "MarshallingAttributeInstanceOverride" ), MarshallingAttributeInstanceOverride ); + + HAPI_AttributeInfo ResultAttributeInfo; + FHoudiniApi:: AttributeInfo_Init(&ResultAttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >( ResultAttributeInfo ); + if ( !HoudiniGeoPartObject.HapiGetAttributeInfo( AssetId, MarshallingAttributeInstanceOverride, ResultAttributeInfo ) ) + { + // We had an error while retrieving the attribute info. + return false; + } + + // Attribute does not exist. + if ( !ResultAttributeInfo.exists ) + return false; + + // The Attribute can only be on points or details + if ( ( ResultAttributeInfo.owner != HAPI_ATTROWNER_DETAIL ) + && ( ResultAttributeInfo.owner != HAPI_ATTROWNER_POINT ) ) + return false; + + // Retrieve instance transforms (for each point). + TArray< FTransform > AllTransforms; + HoudiniGeoPartObject.HapiGetInstanceTransforms( AssetId, AllTransforms ); + + if ( ResultAttributeInfo.owner == HAPI_ATTROWNER_DETAIL ) + { + // Attribute is on detail, this means it gets applied to all points. + TArray< FString > DetailInstanceValues; + + if ( !HoudiniGeoPartObject.HapiGetAttributeDataAsString( + AssetId, MarshallingAttributeInstanceOverride, + HAPI_ATTROWNER_DETAIL, ResultAttributeInfo, DetailInstanceValues ) ) + { + // This should not happen - attribute exists, but there was an error retrieving it. + return false; + } + + if ( DetailInstanceValues.Num() <= 0 ) + { + // No values specified. + return false; + } + + // Attempt to load specified asset. + const FString & AssetName = DetailInstanceValues[ 0 ]; + UObject * AttributeObject = StaticLoadObject( UObject::StaticClass(), nullptr, *AssetName, nullptr, LOAD_None, nullptr ); + + // Couldnt load the referenced object + if ( !AttributeObject ) + return false; + + InstancedObjects.Add( AttributeObject ); + InstancedTransforms.Add( AllTransforms ); + } + else + { + // Attribute is on points, so we may have different values for each of them + TArray< FString > PointInstanceValues; + if ( !HoudiniGeoPartObject.HapiGetAttributeDataAsString( + AssetId, MarshallingAttributeInstanceOverride, + HAPI_ATTROWNER_POINT, ResultAttributeInfo, PointInstanceValues ) ) + { + // This should not happen - attribute exists, but there was an error retrieving it. + return false; + } + + // Attribute is on points, number of points must match number of transforms. + if ( !ensure( PointInstanceValues.Num() == AllTransforms.Num() ) ) + { + // This should not happen, we have mismatch between number of instance values and transforms. + return false; + } + + // If instance attribute exists on points, we need to get all unique values. + // This will give us all the unique object we want to instance + TMap< FString, UObject * > ObjectsToInstance; + for ( auto Iter : PointInstanceValues ) + { + const FString & UniqueName = *Iter; + + if ( !ObjectsToInstance.Contains( UniqueName ) ) + { + // To avoid trying to load an object that fails multiple times, still add it to the array so we skip further attempts + UObject * AttributeObject = StaticLoadObject( + UObject::StaticClass(), nullptr, *UniqueName, nullptr, LOAD_None, nullptr ); + + ObjectsToInstance.Add( UniqueName, AttributeObject ); + } + } + + // Iterates through all the unique objects and get their corresponding transforms + bool Success = false; + for( auto Iter : ObjectsToInstance ) + { + // Check we managed to load this object + UObject * AttributeObject = Iter.Value; + if ( !AttributeObject ) + continue; + + // Extract the transform values that correspond to this object + const FString & InstancePath = Iter.Key; + TArray< FTransform > ObjectTransforms; + for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) + { + if ( InstancePath.Equals( PointInstanceValues[ Idx ] ) ) + ObjectTransforms.Add( AllTransforms[ Idx ] ); + } + + InstancedObjects.Add( AttributeObject ); + InstancedTransforms.Add( ObjectTransforms ); + Success = true; + } + + if ( !Success ) + return false; + } + + return true; +} + +bool +FHoudiniEngineInstancerUtils::CreateInstancerComponent( + UObject* InstancedObject, + const TArray< FTransform >& InstancedObjectTransforms, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent ) +{ + check( InstancedObject && ( InstancedObjectTransforms.Num() > 0 ) ); + + if ( UStaticMesh * StaticMesh = Cast( InstancedObject) ) + { + UMaterialInterface * InstancerMaterial = nullptr; + /* + // We check attribute material first. + if(InstancerGeoPartObject.bInstancerAttributeMaterialAvailable ) + { + InstancerMaterial = Comp->GetAssignmentMaterial( + InstancerGeoPartObject.InstancerAttributeMaterialName); + } + + // If attribute material was not found, we check for presence of shop instancer material. + if( !InstancerMaterial && InstancerHoudiniGeoPartObject.bInstancerMaterialAvailable ) + InstancerMaterial = Comp->GetAssignmentMaterial( + InstancerHoudiniGeoPartObject.InstancerMaterialName); + */ + + if ( !FHoudiniEngineInstancerUtils::CreateInstancedStaticMeshComponent( + StaticMesh, InstancedObjectTransforms, InstancerGeoPartObject, ParentComponent, CreatedInstancedComponent, InstancerMaterial ) ) + return false; + + } + else + { + // Create the actor instancer component + if (!FHoudiniEngineInstancerUtils::CreateInstancedActorComponent( + InstancedObject, InstancedObjectTransforms, InstancerGeoPartObject, ParentComponent, CreatedInstancedComponent ) ) + return false; + } + + // Update the component's UProperties + FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject( CreatedInstancedComponent, InstancerGeoPartObject ); + //FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject(CreatedInstancedComponent, HoudiniGeoPartObject); + + + // Update the component's relative Transform + // UpdateRelativeTransform(); + CreatedInstancedComponent->SetRelativeTransform( InstancerGeoPartObject.TransformMatrix ); + + return true; +} + + +bool +FHoudiniEngineInstancerUtils::CreateInstancedStaticMeshComponent( + UStaticMesh* StaticMesh, + const TArray< FTransform >& InstancedObjectTransforms, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/ ) +{ + if ( !StaticMesh ) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + if (!ParentComponent->GetOwner() || ParentComponent->GetOwner()->IsPendingKill()) + return false; + + UInstancedStaticMeshComponent * InstancedStaticMeshComponent = nullptr; + if ( StaticMesh->GetNumLODs() > 1 ) + { + // If the mesh has LODs, use Hierarchical ISMC + InstancedStaticMeshComponent = NewObject< UHierarchicalInstancedStaticMeshComponent >( + ParentComponent->GetOwner(), UHierarchicalInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + } + else + { + // If the mesh doesnt have LOD, we can use a regular ISMC + InstancedStaticMeshComponent = NewObject< UInstancedStaticMeshComponent >( + ParentComponent->GetOwner(), UInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + } + + if ( !InstancedStaticMeshComponent ) + return false; + + InstancedStaticMeshComponent->SetStaticMesh( StaticMesh ); + InstancedStaticMeshComponent->GetBodyInstance()->bAutoWeld = false; + if (InstancerMaterial) + { + InstancedStaticMeshComponent->OverrideMaterials.Empty(); + + int32 MeshMaterialCount = StaticMesh->StaticMaterials.Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + InstancedStaticMeshComponent->SetMaterial(Idx, InstancerMaterial); + } + + // Now add the instances themselves + // TODO: We should be calling UHoudiniInstancedActorComponent::UpdateInstancerComponentInstances( ... ) + InstancedStaticMeshComponent->ClearInstances(); + for (const auto& Transform : InstancedObjectTransforms ) + { + InstancedStaticMeshComponent->AddInstance( Transform ); + } + + // Assign the ISMC / HISMC to the SceneCOmponent we return + CreatedInstancedComponent = InstancedStaticMeshComponent; + + CreatedInstancedComponent->SetMobility( ParentComponent->Mobility ); + CreatedInstancedComponent->AttachToComponent( ParentComponent, FAttachmentTransformRules::KeepRelativeTransform ); + CreatedInstancedComponent->RegisterComponent(); + + // We want to make this invisible if it's a collision instancer. + CreatedInstancedComponent->SetVisibility( !InstancerGeoPartObject.bIsCollidable ); + + /* + FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject(CreatedInstancedComponent, InstancerGeoPartObject); + // UpdateRelativeTransform(); + CreatedInstancerComponent->SetRelativeTransform(InstancerGeoPartObject.TransformMatrix); + */ + return true; +} + +bool +FHoudiniEngineInstancerUtils::CreateInstancedActorComponent( + UObject* InstancedObject, + const TArray< FTransform >& InstancedObjectTransforms, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent ) +{ + // Create the actor instancer component + UHoudiniInstancedActorComponent * InstancedObjectComponent = NewObject< UHoudiniInstancedActorComponent >( + ParentComponent->GetOwner(), UHoudiniInstancedActorComponent::StaticClass(), NAME_None, RF_Transactional); + + if ( !InstancedObjectComponent || InstancedObjectComponent->IsPendingKill() ) + return false; + + InstancedObjectComponent->InstancedAsset = InstancedObject; + + // Now add the instances themselves + // TODO: We should be calling UHoudiniInstancedActorComponent::UpdateInstancerComponentInstances( ... ) + InstancedObjectComponent->SetInstances( InstancedObjectTransforms ); + + // Assign the HIAC to the SceneCOmponent we return + CreatedInstancedComponent = InstancedObjectComponent; + + CreatedInstancedComponent->SetMobility(ParentComponent->Mobility); + CreatedInstancedComponent->AttachToComponent( ParentComponent, FAttachmentTransformRules::KeepRelativeTransform ); + CreatedInstancedComponent->RegisterComponent(); + + /* + FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject( InstancedObjectComponent, HoudiniGeoPartObject ); + UpdateRelativeTransform(); + */ + + return true; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineInstancerUtils.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineInstancerUtils.h new file mode 100644 index 00000000..5e003ad3 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineInstancerUtils.h @@ -0,0 +1,83 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniGeoPartObject.h" +#include "HoudiniCookHandler.h" + +class UStaticMesh; +class AActor; +class USceneComponent; + +struct HOUDINIENGINERUNTIME_API FHoudiniEngineInstancerUtils +{ + public: + + static bool CreateAllInstancers( + FHoudiniCookParams& HoudiniCookParams, + const HAPI_NodeId& AssetId, + const TArray< FHoudiniGeoPartObject > & FoundInstancers, + TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshes, + USceneComponent* ParentComponent, + TMap< FHoudiniGeoPartObject, USceneComponent * >& Instancers, + TMap< FHoudiniGeoPartObject, USceneComponent * >& NewInstancers ); + + static bool GetInstancedObjectsAndTransforms( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, const HAPI_NodeId& AssetId, + TArray< FHoudiniGeoPartObject >& InstancedGeoParts, TArray< TArray< FTransform > >& InstancedGeoPartsTransforms, + TArray< UObject *>& InstancedObjects, TArray< TArray< FTransform > >& InstancedObjectsTransforms ); + + static bool GetPackedPrimInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, + TArray< FHoudiniGeoPartObject >& InstancedGeoPart, + TArray< TArray< FTransform > >& InstancedTransforms ); + + static bool GetUnrealAttributeInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, + const HAPI_NodeId& AssetId, + TArray< UObject *>& InstancedObjects, + TArray< TArray< FTransform > >& InstancedTransforms ); + + static bool CreateInstancerComponent( + UObject* InstancedObject, + const TArray< FTransform >& InstancedObjectTransforms, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent ); + + static bool CreateInstancedStaticMeshComponent( + UStaticMesh* StaticMesh, + const TArray< FTransform >& InstancedObjectTransforms, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancerComponent, + UMaterialInterface * InstancerMaterial = nullptr ); + + static bool CreateInstancedActorComponent( + UObject* InstancedObject, + const TArray< FTransform >& InstancedObjectTransforms, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent ); +}; \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineMaterialUtils.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineMaterialUtils.cpp new file mode 100644 index 00000000..3734ca4d --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineMaterialUtils.cpp @@ -0,0 +1,3046 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniEngineMaterialUtils.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineString.h" + +#include "Materials/Material.h" +#include "Materials/MaterialInstance.h" +#include "Materials/MaterialInstanceConstant.h" +#include "PhysicalMaterials/PhysicalMaterial.h" +#include "UObject/MetaData.h" +#if WITH_EDITOR + #include "Materials/Material.h" + #include "Materials/MaterialInstance.h" + #include "Materials/MaterialExpressionTextureSample.h" + #include "Materials/MaterialExpressionTextureCoordinate.h" + #include "Materials/MaterialExpressionConstant4Vector.h" + #include "Materials/MaterialExpressionConstant.h" + #include "Materials/MaterialExpressionMultiply.h" + #include "Materials/MaterialExpressionVertexColor.h" + #include "Materials/MaterialExpressionTextureSampleParameter2D.h" + #include "Materials/MaterialExpressionVectorParameter.h" + #include "Materials/MaterialExpressionScalarParameter.h" + #include "Factories/MaterialFactoryNew.h" + #include "Factories/MaterialInstanceConstantFactoryNew.h" +#endif + +const int32 +FHoudiniEngineMaterialUtils::MaterialExpressionNodeX = -400; + +const int32 +FHoudiniEngineMaterialUtils::MaterialExpressionNodeY = -150; + +const int32 +FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepX = 220; + +const int32 +FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY = 220; + +void +FHoudiniEngineMaterialUtils::HapiCreateMaterials( + HAPI_NodeId AssetId, + FHoudiniCookParams& HoudiniCookParams, + const HAPI_AssetInfo & AssetInfo, + const TSet< HAPI_NodeId > & UniqueMaterialIds, + const TSet< HAPI_NodeId > & UniqueInstancerMaterialIds, + TMap< FString, UMaterialInterface * > & Materials, + const bool& bForceRecookAll ) +{ +#if WITH_EDITOR + + // Empty returned materials. + Materials.Empty(); + + if ( UniqueMaterialIds.Num() == 0 ) + return; + + // Update context for generated materials (will trigger when object goes out of scope). + FMaterialUpdateContext MaterialUpdateContext; + + // Default Houdini material. + UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + Materials.Add( HAPI_UNREAL_DEFAULT_MATERIAL_NAME, DefaultMaterial ); + + // Factory to create materials. + UMaterialFactoryNew * MaterialFactory = NewObject< UMaterialFactoryNew >(); + MaterialFactory->AddToRoot(); + + for ( TSet< HAPI_NodeId >::TConstIterator IterMaterialId( UniqueMaterialIds ); IterMaterialId; ++IterMaterialId ) + { + HAPI_NodeId MaterialId = *IterMaterialId; + + // Get material information. + HAPI_MaterialInfo MaterialInfo; + FHoudiniApi::MaterialInfo_Init(&MaterialInfo); + if ( FHoudiniApi::GetMaterialInfo( + FHoudiniEngine::Get().GetSession(), + MaterialId, &MaterialInfo ) != HAPI_RESULT_SUCCESS ) + { + continue; + } + + // Get node information. + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + if ( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), MaterialInfo.nodeId, &NodeInfo ) != HAPI_RESULT_SUCCESS ) + { + continue; + } + + if ( MaterialInfo.exists ) + { + FString MaterialShopName = TEXT( "" ); + if ( !FHoudiniEngineMaterialUtils::GetUniqueMaterialShopName( AssetId, MaterialId, MaterialShopName ) ) + continue; + + bool bCreatedNewMaterial = false; + UMaterial * Material = HoudiniCookParams.HoudiniCookManager ? Cast< UMaterial >( HoudiniCookParams.HoudiniCookManager->GetAssignmentMaterial( MaterialShopName ) ) : nullptr; + if ( Material && !Material->IsPendingKill() ) + { + // If cached material exists and has not changed, we can reuse it. + if ( !MaterialInfo.hasChanged && !bForceRecookAll ) + { + // We found cached material, we can reuse it. + Materials.Add( MaterialShopName, Material ); + continue; + } + } + else + { + // Material was not found, we need to create it. + FString MaterialName = TEXT( "" ); + EObjectFlags ObjFlags = ( HoudiniCookParams.MaterialAndTextureBakeMode == EBakeMode::Intermediate ) ? RF_Transactional : RF_Public | RF_Standalone; + + // Create material package and get material name. + UPackage * MaterialPackage = FHoudiniEngineBakeUtils::BakeCreateMaterialPackageForComponent( + HoudiniCookParams, MaterialInfo, MaterialName ); + + // Create new material. + Material = (UMaterial *) MaterialFactory->FactoryCreateNew( + UMaterial::StaticClass(), MaterialPackage, + *MaterialName, ObjFlags, NULL, GWarn ); + + // Add meta information to this package. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT( "true" ) ); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialName ); + + bCreatedNewMaterial = true; + } + + if ( !Material || Material->IsPendingKill() ) + continue; + + // If this is an instancer material, enable the instancing flag. + if ( UniqueInstancerMaterialIds.Contains( MaterialId ) ) + Material->bUsedWithInstancedStaticMeshes = true; + + // Reset material expressions. + Material->Expressions.Empty(); + + // Generate various components for this material. + bool bMaterialComponentCreated = false; + int32 MaterialNodeY = FHoudiniEngineMaterialUtils::MaterialExpressionNodeY; + + // By default we mark material as opaque. Some of component creators can change this. + Material->BlendMode = BLEND_Opaque; + + // Extract diffuse plane. + bMaterialComponentCreated |= FHoudiniEngineMaterialUtils::CreateMaterialComponentDiffuse( + HoudiniCookParams, AssetId, Material, MaterialInfo, NodeInfo, MaterialNodeY ); + + // Extract opacity plane. + bMaterialComponentCreated |= FHoudiniEngineMaterialUtils::CreateMaterialComponentOpacity( + HoudiniCookParams, AssetId, Material, MaterialInfo, NodeInfo, MaterialNodeY ); + + // Extract opacity mask plane. + bMaterialComponentCreated |= FHoudiniEngineMaterialUtils::CreateMaterialComponentOpacityMask( + HoudiniCookParams, AssetId, Material, MaterialInfo, NodeInfo, MaterialNodeY ); + + // Extract normal plane. + bMaterialComponentCreated |= FHoudiniEngineMaterialUtils::CreateMaterialComponentNormal( + HoudiniCookParams, AssetId, Material, MaterialInfo, NodeInfo, MaterialNodeY ); + + // Extract specular plane. + bMaterialComponentCreated |= FHoudiniEngineMaterialUtils::CreateMaterialComponentSpecular( + HoudiniCookParams, AssetId, Material, MaterialInfo, NodeInfo, MaterialNodeY ); + + // Extract roughness plane. + bMaterialComponentCreated |= FHoudiniEngineMaterialUtils::CreateMaterialComponentRoughness( + HoudiniCookParams, AssetId, Material, MaterialInfo, NodeInfo, MaterialNodeY ); + + // Extract metallic plane. + bMaterialComponentCreated |= FHoudiniEngineMaterialUtils::CreateMaterialComponentMetallic( + HoudiniCookParams, AssetId, Material, MaterialInfo, NodeInfo, MaterialNodeY ); + + // Extract emissive plane. + bMaterialComponentCreated |= FHoudiniEngineMaterialUtils::CreateMaterialComponentEmissive( + HoudiniCookParams, AssetId, Material, MaterialInfo, NodeInfo, MaterialNodeY ); + + // Set other material properties. + Material->TwoSided = true; + Material->SetShadingModel( MSM_DefaultLit ); + + // Schedule this material for update. + MaterialUpdateContext.AddMaterial( Material ); + + // Cache material. + Materials.Add( MaterialShopName, Material ); + + // Propagate and trigger material updates. + if ( bCreatedNewMaterial ) + FAssetRegistryModule::AssetCreated( Material ); + + Material->PreEditChange( nullptr ); + Material->PostEditChange(); + Material->MarkPackageDirty(); + } + else + { + // Material does not exist, we will use default Houdini material in this case. + } + } + + MaterialFactory->RemoveFromRoot(); + +#endif +} + + +bool +FHoudiniEngineMaterialUtils::HapiExtractImage( + HAPI_ParmId NodeParmId, const HAPI_MaterialInfo & MaterialInfo, + TArray< char > & ImageBuffer, const char * PlaneType, HAPI_ImageDataFormat ImageDataFormat, + HAPI_ImagePacking ImagePacking, bool bRenderToImage ) +{ + if ( bRenderToImage ) + { + if ( FHoudiniApi::RenderTextureToImage( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, NodeParmId ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + } + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + if ( FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + ImageInfo.dataFormat = ImageDataFormat; + ImageInfo.interleaved = true; + ImageInfo.packing = ImagePacking; + + if ( FHoudiniApi::SetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + int32 ImageBufferSize = 0; + if ( FHoudiniApi::ExtractImageToMemory( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, HAPI_RAW_FORMAT_NAME, + PlaneType, &ImageBufferSize ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + if ( !ImageBufferSize ) + return false; + + ImageBuffer.SetNumUninitialized( ImageBufferSize ); + + if ( FHoudiniApi::GetImageMemoryBuffer( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageBuffer[ 0 ], + ImageBufferSize ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + return true; +} + +bool +FHoudiniEngineMaterialUtils::HapiGetImagePlanes( + HAPI_ParmId NodeParmId, const HAPI_MaterialInfo & MaterialInfo, + TArray< FString > & ImagePlanes ) +{ + ImagePlanes.Empty(); + int32 ImagePlaneCount = 0; + + if ( FHoudiniApi::RenderTextureToImage( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, NodeParmId ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + if ( FHoudiniApi::GetImagePlaneCount( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImagePlaneCount ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + if ( !ImagePlaneCount ) + return true; + + TArray< HAPI_StringHandle > ImagePlaneStringHandles; + ImagePlaneStringHandles.SetNumZeroed( ImagePlaneCount ); + + if ( FHoudiniApi::GetImagePlanes( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImagePlaneStringHandles[ 0 ], ImagePlaneCount ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + for ( int32 IdxPlane = 0, IdxPlaneMax = ImagePlaneStringHandles.Num(); IdxPlane < IdxPlaneMax; ++IdxPlane ) + { + FString ValueString = TEXT( "" ); + FHoudiniEngineString FHoudiniEngineString( ImagePlaneStringHandles[ IdxPlane ] ); + FHoudiniEngineString.ToFString( ValueString ); + ImagePlanes.Add( ValueString ); + } + + return true; +} + +#if WITH_EDITOR + +bool +FHoudiniEngineMaterialUtils::CreateMaterialComponentDiffuse( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ) +{ + if (!Material || Material->IsPendingKill()) + return false; + + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + EObjectFlags ObjectFlag = ( BakeMode == EBakeMode::CookToTemp ) ? RF_NoFlags : RF_Standalone; + + // Names of generating Houdini parameters. + FString GeneratingParameterNameDiffuseTexture = TEXT( "" ); + FString GeneratingParameterNameUniformColor = TEXT( "" ); + FString GeneratingParameterNameVertexColor = TEXT( HAPI_UNREAL_ATTRIB_COLOR ); + + // Diffuse texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Default; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // Attempt to look up previously created expressions. + UMaterialExpression * MaterialExpression = Material->BaseColor.Expression; + + // Locate sampling expression. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureSample = + Cast< UMaterialExpressionTextureSampleParameter2D >( FHoudiniEngineMaterialUtils::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass() ) ); + + // If texture sampling expression does exist, attempt to look up corresponding texture. + UTexture2D * TextureDiffuse = nullptr; + if ( ExpressionTextureSample && !ExpressionTextureSample->IsPendingKill() ) + TextureDiffuse = Cast< UTexture2D >( ExpressionTextureSample->Texture ); + + // Locate uniform color expression. + UMaterialExpressionVectorParameter * ExpressionConstant4Vector = + Cast< UMaterialExpressionVectorParameter >(FHoudiniEngineMaterialUtils::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionVectorParameter::StaticClass() ) ); + + // If uniform color expression does not exist, create it. + if ( !ExpressionConstant4Vector || ExpressionConstant4Vector->IsPendingKill() ) + { + ExpressionConstant4Vector = NewObject< UMaterialExpressionVectorParameter >( + Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag ); + ExpressionConstant4Vector->DefaultValue = FLinearColor::White; + } + + // Add expression. + Material->Expressions.Add( ExpressionConstant4Vector ); + + // Locate vertex color expression. + UMaterialExpressionVertexColor * ExpressionVertexColor = + Cast< UMaterialExpressionVertexColor >(FHoudiniEngineMaterialUtils::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionVertexColor::StaticClass() ) ); + + // If vertex color expression does not exist, create it. + if ( !ExpressionVertexColor || ExpressionVertexColor->IsPendingKill() ) + { + ExpressionVertexColor = NewObject< UMaterialExpressionVertexColor >( + Material, UMaterialExpressionVertexColor::StaticClass(), NAME_None, ObjectFlag ); + ExpressionVertexColor->Desc = GeneratingParameterNameVertexColor; + } + + // Add expression. + Material->Expressions.Add( ExpressionVertexColor ); + + // Material should have at least one multiply expression. + UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast< UMaterialExpressionMultiply >( MaterialExpression ); + if ( !MaterialExpressionMultiply || MaterialExpressionMultiply->IsPendingKill() ) + MaterialExpressionMultiply = NewObject< UMaterialExpressionMultiply >( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag ); + + // Add expression. + Material->Expressions.Add( MaterialExpressionMultiply ); + + // See if primary multiplication has secondary multiplication as A input. + UMaterialExpressionMultiply * MaterialExpressionMultiplySecondary = nullptr; + if ( MaterialExpressionMultiply->A.Expression ) + MaterialExpressionMultiplySecondary = + Cast< UMaterialExpressionMultiply >( MaterialExpressionMultiply->A.Expression ); + + // See if a diffuse texture is available. + HAPI_ParmId ParmDiffuseTextureId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_DIFFUSE_0 ); + + if ( ParmDiffuseTextureId >= 0 ) + { + GeneratingParameterNameDiffuseTexture = TEXT( HAPI_UNREAL_PARAM_MAP_DIFFUSE_0 ); + } + else + { + ParmDiffuseTextureId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_DIFFUSE_1 ); + + if ( ParmDiffuseTextureId >= 0 ) + GeneratingParameterNameDiffuseTexture = TEXT( HAPI_UNREAL_PARAM_MAP_DIFFUSE_1 ); + } + + // See if uniform color is available. + HAPI_ParmInfo ParmInfoDiffuseColor; + FHoudiniApi::ParmInfo_Init(&ParmInfoDiffuseColor); + HAPI_ParmId ParmDiffuseColorId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0, ParmInfoDiffuseColor ); + + if ( ParmDiffuseColorId >= 0 ) + { + GeneratingParameterNameUniformColor = TEXT( HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0 ); + } + else + { + ParmDiffuseColorId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1, ParmInfoDiffuseColor ); + + if ( ParmDiffuseColorId >= 0 ) + GeneratingParameterNameUniformColor = TEXT( HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1 ); + } + + // If we have diffuse texture parameter. + if ( ParmDiffuseTextureId >= 0 ) + { + TArray< char > ImageBuffer; + + // Get image planes of diffuse map. + TArray< FString > DiffuseImagePlanes; + bool bFoundImagePlanes = FHoudiniEngineMaterialUtils::HapiGetImagePlanes( + ParmDiffuseTextureId, MaterialInfo, DiffuseImagePlanes ); + + HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; + const char * PlaneType = ""; + + if ( bFoundImagePlanes && DiffuseImagePlanes.Contains( TEXT( HAPI_UNREAL_MATERIAL_TEXTURE_COLOR ) ) ) + { + if ( DiffuseImagePlanes.Contains( TEXT( HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA ) ) ) + { + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + + // Material does use alpha. + CreateTexture2DParameters.bUseAlpha = true; + } + else + { + // We still need to have the Alpha plane, just not the CreateTexture2DParameters + // alpha option. This is because all texture data from Houdini Engine contains + // the alpha plane by default. + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + } + } + else + { + bFoundImagePlanes = false; + } + + // Retrieve color plane. + if ( bFoundImagePlanes && FHoudiniEngineMaterialUtils::HapiExtractImage( + ParmDiffuseTextureId, MaterialInfo, ImageBuffer, PlaneType, + HAPI_IMAGE_DATA_INT8, ImagePacking, false ) ) + { + UPackage * TextureDiffusePackage = nullptr; + if ( TextureDiffuse && !TextureDiffuse->IsPendingKill() ) + TextureDiffusePackage = Cast< UPackage >( TextureDiffuse->GetOuter() ); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo ); + + if ( Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0 ) + { + // Create texture. + FString TextureDiffuseName; + bool bCreatedNewTextureDiffuse = false; + + // Create diffuse texture package, if this is a new diffuse texture. + if ( !TextureDiffusePackage ) + { + TextureDiffusePackage = FHoudiniEngineBakeUtils::BakeCreateTexturePackageForComponent( + HoudiniCookParams, + MaterialInfo, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, + TextureDiffuseName ); + } + + // Create diffuse texture, if we need to create one. + if ( !TextureDiffuse || TextureDiffuse->IsPendingKill() ) + bCreatedNewTextureDiffuse = true; + + // Get the node path to add it to the meta data + FString NodePath; + GetUniqueMaterialShopName( AssetId, MaterialInfo.nodeId, NodePath ); + + // Reuse existing diffuse texture, or create new one. + TextureDiffuse = FHoudiniEngineMaterialUtils::CreateUnrealTexture( + TextureDiffuse, ImageInfo, + TextureDiffusePackage, TextureDiffuseName, ImageBuffer, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, + CreateTexture2DParameters, TEXTUREGROUP_World, NodePath ); + + if ( BakeMode == EBakeMode::CookToTemp ) + TextureDiffuse->SetFlags( RF_Public | RF_Standalone ); + + // Create diffuse sampling expression, if needed. + if ( !ExpressionTextureSample ) + { + ExpressionTextureSample = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag ); + } + + // Record generating parameter. + ExpressionTextureSample->Desc = GeneratingParameterNameDiffuseTexture; + ExpressionTextureSample->ParameterName = *GeneratingParameterNameDiffuseTexture; + ExpressionTextureSample->Texture = TextureDiffuse; + ExpressionTextureSample->SamplerType = SAMPLERTYPE_Color; + + // Add expression. + Material->Expressions.Add( ExpressionTextureSample ); + + // Propagate and trigger diffuse texture updates. + if ( bCreatedNewTextureDiffuse ) + FAssetRegistryModule::AssetCreated( TextureDiffuse ); + + TextureDiffuse->PreEditChange( nullptr ); + TextureDiffuse->PostEditChange(); + TextureDiffuse->MarkPackageDirty(); + } + } + } + + // If we have uniform color parameter. + if ( ParmDiffuseColorId >= 0 ) + { + FLinearColor Color = FLinearColor::White; + + if ( FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), NodeInfo.id, (float *) &Color.R, + ParmInfoDiffuseColor.floatValuesIndex, ParmInfoDiffuseColor.size ) == HAPI_RESULT_SUCCESS ) + { + if ( ParmInfoDiffuseColor.size == 3 ) + Color.A = 1.0f; + + // Record generating parameter. + ExpressionConstant4Vector->Desc = GeneratingParameterNameUniformColor; + ExpressionConstant4Vector->ParameterName = *GeneratingParameterNameUniformColor; + ExpressionConstant4Vector->DefaultValue = Color; + } + } + + // If we have have texture sample expression present, we need a secondary multiplication expression. + if ( ExpressionTextureSample ) + { + if ( !MaterialExpressionMultiplySecondary ) + { + MaterialExpressionMultiplySecondary = NewObject< UMaterialExpressionMultiply >( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag ); + + // Add expression. + Material->Expressions.Add( MaterialExpressionMultiplySecondary ); + } + } + else + { + // If secondary multiplication exists, but we have no sampling, we can free it. + if ( MaterialExpressionMultiplySecondary ) + { + MaterialExpressionMultiplySecondary->A.Expression = nullptr; + MaterialExpressionMultiplySecondary->B.Expression = nullptr; + MaterialExpressionMultiplySecondary->ConditionalBeginDestroy(); + } + } + + float SecondaryExpressionScale = 1.0f; + if ( MaterialExpressionMultiplySecondary ) + SecondaryExpressionScale = 1.5f; + + // Create multiplication expression which has uniform color and vertex color. + MaterialExpressionMultiply->A.Expression = ExpressionConstant4Vector; + MaterialExpressionMultiply->B.Expression = ExpressionVertexColor; + + ExpressionConstant4Vector->MaterialExpressionEditorX = + FHoudiniEngineMaterialUtils::MaterialExpressionNodeX - + FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionConstant4Vector->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + ExpressionVertexColor->MaterialExpressionEditorX = + FHoudiniEngineMaterialUtils::MaterialExpressionNodeX - + FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionVertexColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + MaterialExpressionMultiply->MaterialExpressionEditorY = + ( ExpressionVertexColor->MaterialExpressionEditorY - ExpressionConstant4Vector->MaterialExpressionEditorY ) / 2; + + // Hook up secondary multiplication expression to first one. + if ( MaterialExpressionMultiplySecondary ) + { + MaterialExpressionMultiplySecondary->A.Expression = MaterialExpressionMultiply; + MaterialExpressionMultiplySecondary->B.Expression = ExpressionTextureSample; + + ExpressionTextureSample->MaterialExpressionEditorX = + FHoudiniEngineMaterialUtils::MaterialExpressionNodeX - + FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionTextureSample->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + MaterialExpressionMultiplySecondary->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + MaterialExpressionMultiplySecondary->MaterialExpressionEditorY = + MaterialExpressionMultiply->MaterialExpressionEditorY + FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + // Assign expression. + Material->BaseColor.Expression = MaterialExpressionMultiplySecondary; + } + else + { + // Assign expression. + Material->BaseColor.Expression = MaterialExpressionMultiply; + + MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + MaterialExpressionMultiply->MaterialExpressionEditorY = + ( ExpressionVertexColor->MaterialExpressionEditorY - + ExpressionConstant4Vector->MaterialExpressionEditorY ) / 2; + } + + return true; +} + +bool +FHoudiniEngineMaterialUtils::CreateMaterialComponentOpacityMask( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + // Name of generating Houdini parameters. + FString GeneratingParameterNameTexture = TEXT( "" ); + + UMaterialExpression * MaterialExpression = Material->OpacityMask.Expression; + + EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + EObjectFlags ObjectFlag = ( BakeMode == EBakeMode::CookToTemp ) ? RF_NoFlags : RF_Standalone; + + // Opacity expressions. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; + UTexture2D * TextureOpacity = nullptr; + + // Opacity texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // See if opacity texture is available. + HAPI_ParmId ParmOpacityTextureId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_OPACITY_0 ); + + if ( ParmOpacityTextureId >= 0 ) + { + GeneratingParameterNameTexture = TEXT( HAPI_UNREAL_PARAM_MAP_OPACITY_0 ); + } + else + { + ParmOpacityTextureId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_OPACITY_1 ); + + if ( ParmOpacityTextureId >= 0 ) + GeneratingParameterNameTexture = TEXT( HAPI_UNREAL_PARAM_MAP_OPACITY_1 ); + } + + // If we have opacity texture parameter. + if ( ParmOpacityTextureId >= 0 ) + { + TArray< char > ImageBuffer; + + // Get image planes of opacity map. + TArray< FString > OpacityImagePlanes; + bool bFoundImagePlanes = FHoudiniEngineMaterialUtils::HapiGetImagePlanes( + ParmOpacityTextureId, MaterialInfo, OpacityImagePlanes ); + + HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; + const char * PlaneType = ""; + + bool bColorAlphaFound = ( OpacityImagePlanes.Contains( TEXT( HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA ) ) && OpacityImagePlanes.Contains( TEXT( HAPI_UNREAL_MATERIAL_TEXTURE_COLOR ) ) ); + + if ( bFoundImagePlanes && bColorAlphaFound ) + { + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + CreateTexture2DParameters.bUseAlpha = true; + } + else + { + bFoundImagePlanes = false; + } + + if ( bFoundImagePlanes && FHoudiniEngineMaterialUtils::HapiExtractImage( + ParmOpacityTextureId, MaterialInfo, ImageBuffer, PlaneType, + HAPI_IMAGE_DATA_INT8, ImagePacking, false ) ) + { + // Locate sampling expression. + ExpressionTextureOpacitySample = Cast< UMaterialExpressionTextureSampleParameter2D >( + FHoudiniEngineMaterialUtils::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass() ) ); + + // Locate opacity texture, if valid. + if ( ExpressionTextureOpacitySample ) + TextureOpacity = Cast< UTexture2D >( ExpressionTextureOpacitySample->Texture ); + + UPackage * TextureOpacityPackage = nullptr; + if ( TextureOpacity ) + TextureOpacityPackage = Cast< UPackage >( TextureOpacity->GetOuter() ); + + HAPI_ImageInfo ImageInfo; + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo ); + + if ( Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0 ) + { + // Create texture. + FString TextureOpacityName; + bool bCreatedNewTextureOpacity = false; + + // Create opacity texture package, if this is a new opacity texture. + if ( !TextureOpacityPackage ) + { + TextureOpacityPackage = FHoudiniEngineBakeUtils::BakeCreateTexturePackageForComponent( + HoudiniCookParams, + MaterialInfo, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, + TextureOpacityName ); + } + + // Create opacity texture, if we need to create one. + if ( !TextureOpacity ) + bCreatedNewTextureOpacity = true; + + // Get the node path to add it to the meta data + FString NodePath; + GetUniqueMaterialShopName( AssetId, MaterialInfo.nodeId, NodePath ); + + // Reuse existing opacity texture, or create new one. + TextureOpacity = FHoudiniEngineMaterialUtils::CreateUnrealTexture( + TextureOpacity, ImageInfo, + TextureOpacityPackage, TextureOpacityName, ImageBuffer, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, + CreateTexture2DParameters, + TEXTUREGROUP_World, NodePath ); + + if ( BakeMode == EBakeMode::CookToTemp ) + TextureOpacity->SetFlags(RF_Public | RF_Standalone); + + // Create opacity sampling expression, if needed. + if ( !ExpressionTextureOpacitySample ) + { + ExpressionTextureOpacitySample = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag ); + } + + // Record generating parameter. + ExpressionTextureOpacitySample->Desc = GeneratingParameterNameTexture; + ExpressionTextureOpacitySample->ParameterName = *GeneratingParameterNameTexture; + ExpressionTextureOpacitySample->Texture = TextureOpacity; + ExpressionTextureOpacitySample->SamplerType = SAMPLERTYPE_Grayscale; + + // Offset node placement. + ExpressionTextureOpacitySample->MaterialExpressionEditorX = + FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + ExpressionTextureOpacitySample->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + // Add expression. + Material->Expressions.Add( ExpressionTextureOpacitySample ); + + // We need to set material type to masked. + TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput* ExpressionOutput = ExpressionOutputs.GetData(); + + Material->OpacityMask.Expression = ExpressionTextureOpacitySample; + Material->BlendMode = BLEND_Masked; + + Material->OpacityMask.Mask = ExpressionOutput->Mask; + Material->OpacityMask.MaskR = 1; + Material->OpacityMask.MaskG = 0; + Material->OpacityMask.MaskB = 0; + Material->OpacityMask.MaskA = 0; + + // Propagate and trigger opacity texture updates. + if ( bCreatedNewTextureOpacity ) + FAssetRegistryModule::AssetCreated( TextureOpacity ); + + TextureOpacity->PreEditChange( nullptr ); + TextureOpacity->PostEditChange(); + TextureOpacity->MarkPackageDirty(); + + bExpressionCreated = true; + } + } + } + + return bExpressionCreated; +} + +bool +FHoudiniEngineMaterialUtils::CreateMaterialComponentOpacity( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + float OpacityValue = 1.0f; + bool bNeedsTranslucency = false; + + EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + EObjectFlags ObjectFlag = ( BakeMode == EBakeMode::CookToTemp ) ? RF_NoFlags : RF_Standalone; + + // Name of generating Houdini parameters. + FString GeneratingParameterNameScalar = TEXT( "" ); + FString GeneratingParameterNameTexture = TEXT( "" ); + + UMaterialExpression * MaterialExpression = Material->Opacity.Expression; + + // Opacity expressions. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; + UMaterialExpressionScalarParameter * ExpressionScalarOpacity = nullptr; + UTexture2D * TextureOpacity = nullptr; + + // Opacity texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // If opacity sampling expression was not created, check if diffuse contains an alpha plane. + if ( !ExpressionTextureOpacitySample ) + { + UMaterialExpression * MaterialExpressionDiffuse = Material->BaseColor.Expression; + if ( MaterialExpressionDiffuse ) + { + // Locate diffuse sampling expression. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureDiffuseSample = + Cast< UMaterialExpressionTextureSampleParameter2D >( + FHoudiniEngineMaterialUtils::MaterialLocateExpression( + MaterialExpressionDiffuse, + UMaterialExpressionTextureSampleParameter2D::StaticClass() ) ); + + // See if there's an alpha plane in this expression's texture. + if ( ExpressionTextureDiffuseSample ) + { + UTexture2D * DiffuseTexture = Cast< UTexture2D >( ExpressionTextureDiffuseSample->Texture ); + if ( DiffuseTexture && !DiffuseTexture->CompressionNoAlpha ) + { + // The diffuse texture has an alpha channel (that wasn't discarded), so we can use it + ExpressionTextureOpacitySample = ExpressionTextureDiffuseSample; + bNeedsTranslucency = true; + } + } + } + } + + // Retrieve opacity uniform parameter. + HAPI_ParmInfo ParmInfoOpacityValue; + FHoudiniApi::ParmInfo_Init(&ParmInfoOpacityValue); + HAPI_ParmId ParmOpacityValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_ALPHA_0, ParmInfoOpacityValue ); + + if ( ParmOpacityValueId >= 0 ) + { + GeneratingParameterNameScalar = TEXT( HAPI_UNREAL_PARAM_ALPHA_0 ); + } + else + { + ParmOpacityValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_ALPHA_1, ParmInfoOpacityValue ); + + if ( ParmOpacityValueId >= 0 ) + GeneratingParameterNameScalar = TEXT( HAPI_UNREAL_PARAM_ALPHA_1 ); + } + + if ( ParmOpacityValueId >= 0 ) + { + if (ParmInfoOpacityValue.size > 0 && ParmInfoOpacityValue.floatValuesIndex >= 0 ) + { + float OpacityValueRetrieved = 1.0f; + if ( FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), NodeInfo.id, + (float *) &OpacityValue, ParmInfoOpacityValue.floatValuesIndex, 1 ) == HAPI_RESULT_SUCCESS ) + { + if ( !ExpressionScalarOpacity ) + { + ExpressionScalarOpacity = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag ); + } + + // Clamp retrieved value. + OpacityValueRetrieved = FMath::Clamp< float >( OpacityValueRetrieved, 0.0f, 1.0f ); + OpacityValue = OpacityValueRetrieved; + + // Set expression fields. + ExpressionScalarOpacity->DefaultValue = OpacityValue; + ExpressionScalarOpacity->SliderMin = 0.0f; + ExpressionScalarOpacity->SliderMax = 1.0f; + ExpressionScalarOpacity->Desc = GeneratingParameterNameScalar; + ExpressionScalarOpacity->ParameterName = *GeneratingParameterNameScalar; + + // Add expression. + Material->Expressions.Add( ExpressionScalarOpacity ); + + // If alpha is less than 1, we need translucency. + bNeedsTranslucency |= ( OpacityValue != 1.0f ); + } + } + } + + if ( bNeedsTranslucency ) + Material->BlendMode = BLEND_Translucent; + + if ( ExpressionScalarOpacity && ExpressionTextureOpacitySample ) + { + // We have both alpha and alpha uniform, attempt to locate multiply expression. + UMaterialExpressionMultiply * ExpressionMultiply = + Cast< UMaterialExpressionMultiply >( + FHoudiniEngineMaterialUtils::MaterialLocateExpression( + MaterialExpression, + UMaterialExpressionMultiply::StaticClass() ) ); + + if ( !ExpressionMultiply ) + ExpressionMultiply = NewObject< UMaterialExpressionMultiply >( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag ); + + Material->Expressions.Add( ExpressionMultiply ); + + TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); + + ExpressionMultiply->A.Expression = ExpressionTextureOpacitySample; + ExpressionMultiply->B.Expression = ExpressionScalarOpacity; + + Material->Opacity.Expression = ExpressionMultiply; + Material->Opacity.Mask = ExpressionOutput->Mask; + Material->Opacity.MaskR = 0; + Material->Opacity.MaskG = 0; + Material->Opacity.MaskB = 0; + Material->Opacity.MaskA = 1; + + ExpressionMultiply->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + ExpressionMultiply->MaterialExpressionEditorY = MaterialNodeY; + + ExpressionScalarOpacity->MaterialExpressionEditorX = + FHoudiniEngineMaterialUtils::MaterialExpressionNodeX - FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepX; + ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + bExpressionCreated = true; + } + else if ( ExpressionScalarOpacity ) + { + Material->Opacity.Expression = ExpressionScalarOpacity; + + ExpressionScalarOpacity->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + bExpressionCreated = true; + } + else if ( ExpressionTextureOpacitySample ) + { + TArray ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); + + Material->Opacity.Expression = ExpressionTextureOpacitySample; + Material->Opacity.Mask = ExpressionOutput->Mask; + Material->Opacity.MaskR = 0; + Material->Opacity.MaskG = 0; + Material->Opacity.MaskB = 0; + Material->Opacity.MaskA = 1; + + bExpressionCreated = true; + } + + return bExpressionCreated; +} + +bool +FHoudiniEngineMaterialUtils::CreateMaterialComponentNormal( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + bool bTangentSpaceNormal = true; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + EObjectFlags ObjectFlag = ( BakeMode == EBakeMode::CookToTemp ) ? RF_NoFlags : RF_Standalone; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT( "" ); + + // Normal texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Normalmap; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if separate normal texture is available. + HAPI_ParmId ParmNameNormalId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_NORMAL_0 ); + + if ( ParmNameNormalId >= 0 ) + { + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_MAP_NORMAL_0 ); + } + else + { + ParmNameNormalId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_NORMAL_1 ); + + if ( ParmNameNormalId >= 0 ) + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_MAP_NORMAL_1 ); + } + + if ( ParmNameNormalId >= 0 ) + { + // Retrieve space for this normal texture. + HAPI_ParmInfo ParmInfoNormalType; + FHoudiniApi::ParmInfo_Init(&ParmInfoNormalType); + int32 ParmNormalTypeId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE, ParmInfoNormalType ); + + // Retrieve value for normal type choice list (if exists). + + if ( ParmNormalTypeId >= 0 ) + { + FString NormalType = TEXT( HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT ); + + if ( ParmInfoNormalType.size > 0 && ParmInfoNormalType.stringValuesIndex >= 0 ) + { + HAPI_StringHandle StringHandle; + if ( FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, false, &StringHandle, ParmInfoNormalType.stringValuesIndex, ParmInfoNormalType.size ) == HAPI_RESULT_SUCCESS ) + { + // Get the actual string value. + FString NormalTypeString = TEXT( "" ); + FHoudiniEngineString HoudiniEngineString( StringHandle ); + if ( HoudiniEngineString.ToFString( NormalTypeString ) ) + NormalType = NormalTypeString; + } + } + + // Check if we require world space normals. + if ( NormalType.Equals( TEXT( HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD ), ESearchCase::IgnoreCase ) ) + bTangentSpaceNormal = false; + } + + TArray< char > ImageBuffer; + + // Retrieve color plane. + if (FHoudiniEngineMaterialUtils::HapiExtractImage( + ParmNameNormalId, MaterialInfo, ImageBuffer, + HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true ) ) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = + Cast< UMaterialExpressionTextureSampleParameter2D >( Material->Normal.Expression ); + + UTexture2D * TextureNormal = nullptr; + if ( ExpressionNormal ) + { + TextureNormal = Cast< UTexture2D >( ExpressionNormal->Texture ); + } + else + { + // Otherwise new expression is of a different type. + if ( Material->Normal.Expression ) + { + Material->Normal.Expression->ConditionalBeginDestroy(); + Material->Normal.Expression = nullptr; + } + } + + UPackage * TextureNormalPackage = nullptr; + if ( TextureNormal ) + TextureNormalPackage = Cast< UPackage >( TextureNormal->GetOuter() ); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo ); + + if ( Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0 ) + { + // Create texture. + FString TextureNormalName; + bool bCreatedNewTextureNormal = false; + + // Create normal texture package, if this is a new normal texture. + if ( !TextureNormalPackage ) + { + TextureNormalPackage = + FHoudiniEngineBakeUtils::BakeCreateTexturePackageForComponent( + HoudiniCookParams, + MaterialInfo, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + TextureNormalName ); + } + + // Create normal texture, if we need to create one. + if ( !TextureNormal ) + bCreatedNewTextureNormal = true; + + // Get the node path to add it to the meta data + FString NodePath; + GetUniqueMaterialShopName( AssetId, MaterialInfo.nodeId, NodePath ); + + // Reuse existing normal texture, or create new one. + TextureNormal = FHoudiniEngineMaterialUtils::CreateUnrealTexture( + TextureNormal, ImageInfo, + TextureNormalPackage, TextureNormalName, ImageBuffer, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + CreateTexture2DParameters, + TEXTUREGROUP_WorldNormalMap, + NodePath ); + + if ( BakeMode == EBakeMode::CookToTemp ) + TextureNormal->SetFlags(RF_Public | RF_Standalone); + + // Create normal sampling expression, if needed. + if ( !ExpressionNormal ) + ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag ); + + // Record generating parameter. + ExpressionNormal->Desc = GeneratingParameterName; + ExpressionNormal->ParameterName = *GeneratingParameterName; + + ExpressionNormal->Texture = TextureNormal; + ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; + + // Offset node placement. + ExpressionNormal->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + // Set normal space. + Material->bTangentSpaceNormal = bTangentSpaceNormal; + + // Assign expression to material. + Material->Expressions.Add(ExpressionNormal); + Material->Normal.Expression = ExpressionNormal; + + bExpressionCreated = true; + + // Propagate and trigger normal texture updates. + if (bCreatedNewTextureNormal) + FAssetRegistryModule::AssetCreated(TextureNormal); + + TextureNormal->PreEditChange(nullptr); + TextureNormal->PostEditChange(); + TextureNormal->MarkPackageDirty(); + } + } + } + + // If separate normal map was not found, see if normal plane exists in diffuse map. + if ( !bExpressionCreated ) + { + // See if diffuse texture is available. + HAPI_ParmId ParmNameBaseId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_DIFFUSE_0 ); + + if ( ParmNameBaseId >= 0 ) + { + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_MAP_DIFFUSE_0 ); + } + else + { + ParmNameBaseId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_DIFFUSE_1 ); + + if ( ParmNameBaseId >= 0 ) + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_MAP_DIFFUSE_1 ); + } + + if ( ParmNameBaseId >= 0 ) + { + // Normal plane is available in diffuse map. + + TArray< char > ImageBuffer; + + // Retrieve color plane - this will contain normal data. + if ( FHoudiniEngineMaterialUtils::HapiExtractImage( + ParmNameBaseId, MaterialInfo, ImageBuffer, + HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGB, true ) ) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = + Cast< UMaterialExpressionTextureSampleParameter2D >( Material->Normal.Expression ); + + UTexture2D * TextureNormal = nullptr; + if ( ExpressionNormal ) + { + TextureNormal = Cast< UTexture2D >( ExpressionNormal->Texture ); + } + else + { + // Otherwise new expression is of a different type. + if ( Material->Normal.Expression ) + { + Material->Normal.Expression->ConditionalBeginDestroy(); + Material->Normal.Expression = nullptr; + } + } + + UPackage * TextureNormalPackage = nullptr; + if ( TextureNormal ) + TextureNormalPackage = Cast< UPackage >( TextureNormal->GetOuter() ); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo ); + + if ( Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0 ) + { + // Create texture. + FString TextureNormalName; + bool bCreatedNewTextureNormal = false; + + // Create normal texture package, if this is a new normal texture. + if ( !TextureNormalPackage ) + { + TextureNormalPackage = FHoudiniEngineBakeUtils::BakeCreateTexturePackageForComponent( + HoudiniCookParams, + MaterialInfo, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + TextureNormalName ); + } + + // Create normal texture, if we need to create one. + if ( !TextureNormal ) + bCreatedNewTextureNormal = true; + + // Get the node path to add it to the meta data + FString NodePath; + GetUniqueMaterialShopName( AssetId, MaterialInfo.nodeId, NodePath ); + + // Reuse existing normal texture, or create new one. + TextureNormal = FHoudiniEngineMaterialUtils::CreateUnrealTexture( + TextureNormal, ImageInfo, + TextureNormalPackage, TextureNormalName, ImageBuffer, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, CreateTexture2DParameters, + TEXTUREGROUP_WorldNormalMap, NodePath ); + + if ( BakeMode == EBakeMode::CookToTemp ) + TextureNormal->SetFlags( RF_Public | RF_Standalone ); + + // Create normal sampling expression, if needed. + if ( !ExpressionNormal ) + ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag ); + + // Record generating parameter. + ExpressionNormal->Desc = GeneratingParameterName; + ExpressionNormal->ParameterName = *GeneratingParameterName; + + ExpressionNormal->Texture = TextureNormal; + ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; + + // Offset node placement. + ExpressionNormal->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + // Set normal space. + Material->bTangentSpaceNormal = bTangentSpaceNormal; + + // Assign expression to material. + Material->Expressions.Add( ExpressionNormal ); + Material->Normal.Expression = ExpressionNormal; + + // Propagate and trigger diffuse texture updates. + if ( bCreatedNewTextureNormal ) + FAssetRegistryModule::AssetCreated( TextureNormal ); + + TextureNormal->PreEditChange( nullptr ); + TextureNormal->PostEditChange(); + TextureNormal->MarkPackageDirty(); + + bExpressionCreated = true; + } + } + } + } + + return bExpressionCreated; +} + +bool +FHoudiniEngineMaterialUtils::CreateMaterialComponentSpecular( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + EObjectFlags ObjectFlag = ( BakeMode == EBakeMode::CookToTemp ) ? RF_NoFlags : RF_Standalone; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT( "" ); + + // Specular texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if specular texture is available. + HAPI_ParmId ParmNameSpecularId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_SPECULAR_0 ); + + if ( ParmNameSpecularId >= 0 ) + { + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_MAP_SPECULAR_0 ); + } + else + { + ParmNameSpecularId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_SPECULAR_1 ); + + if ( ParmNameSpecularId >= 0 ) + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_MAP_SPECULAR_1 ); + } + + if ( ParmNameSpecularId >= 0 ) + { + TArray< char > ImageBuffer; + + // Retrieve color plane. + if ( FHoudiniEngineMaterialUtils::HapiExtractImage( + ParmNameSpecularId, MaterialInfo, ImageBuffer, + HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true ) ) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionSpecular = + Cast< UMaterialExpressionTextureSampleParameter2D >( Material->Specular.Expression ); + + UTexture2D * TextureSpecular = nullptr; + if ( ExpressionSpecular ) + { + TextureSpecular = Cast< UTexture2D >( ExpressionSpecular->Texture ); + } + else + { + // Otherwise new expression is of a different type. + if ( Material->Specular.Expression ) + { + Material->Specular.Expression->ConditionalBeginDestroy(); + Material->Specular.Expression = nullptr; + } + } + + UPackage * TextureSpecularPackage = nullptr; + if ( TextureSpecular ) + TextureSpecularPackage = Cast< UPackage >( TextureSpecular->GetOuter() ); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo ); + + if ( Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0 ) + { + // Create texture. + FString TextureSpecularName; + bool bCreatedNewTextureSpecular = false; + + // Create specular texture package, if this is a new specular texture. + if ( !TextureSpecularPackage ) + { + TextureSpecularPackage = FHoudiniEngineBakeUtils::BakeCreateTexturePackageForComponent( + HoudiniCookParams, + MaterialInfo, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, + TextureSpecularName ); + } + + // Create specular texture, if we need to create one. + if ( !TextureSpecular ) + bCreatedNewTextureSpecular = true; + + // Get the node path to add it to the meta data + FString NodePath; + GetUniqueMaterialShopName( AssetId, MaterialInfo.nodeId, NodePath ); + + // Reuse existing specular texture, or create new one. + TextureSpecular = FHoudiniEngineMaterialUtils::CreateUnrealTexture( + TextureSpecular, ImageInfo, + TextureSpecularPackage, TextureSpecularName, ImageBuffer, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, + CreateTexture2DParameters, + TEXTUREGROUP_World, NodePath ); + + if ( BakeMode == EBakeMode::CookToTemp ) + TextureSpecular->SetFlags( RF_Public | RF_Standalone ); + + // Create specular sampling expression, if needed. + if ( !ExpressionSpecular ) + { + ExpressionSpecular = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag ); + } + + // Record generating parameter. + ExpressionSpecular->Desc = GeneratingParameterName; + ExpressionSpecular->ParameterName = *GeneratingParameterName; + + ExpressionSpecular->Texture = TextureSpecular; + ExpressionSpecular->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionSpecular->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + ExpressionSpecular->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add( ExpressionSpecular ); + Material->Specular.Expression = ExpressionSpecular; + + bExpressionCreated = true; + + // Propagate and trigger specular texture updates. + if (bCreatedNewTextureSpecular) + FAssetRegistryModule::AssetCreated(TextureSpecular); + + TextureSpecular->PreEditChange(nullptr); + TextureSpecular->PostEditChange(); + TextureSpecular->MarkPackageDirty(); + } + } + } + + HAPI_ParmInfo ParmInfoSpecularColor; + FHoudiniApi::ParmInfo_Init(&ParmInfoSpecularColor); + HAPI_ParmId ParmNameSpecularColorId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_COLOR_SPECULAR_0, ParmInfoSpecularColor ); + + if( ParmNameSpecularColorId >= 0 ) + { + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_COLOR_SPECULAR_0 ); + } + else + { + ParmNameSpecularColorId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_COLOR_SPECULAR_1, ParmInfoSpecularColor ); + + if ( ParmNameSpecularColorId >= 0 ) + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_COLOR_SPECULAR_1 ); + } + + if ( !bExpressionCreated && ParmNameSpecularColorId >= 0 ) + { + // Specular color is available. + + FLinearColor Color = FLinearColor::White; + + if ( FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), NodeInfo.id, (float*) &Color.R, + ParmInfoSpecularColor.floatValuesIndex, ParmInfoSpecularColor.size ) == HAPI_RESULT_SUCCESS ) + { + if (ParmInfoSpecularColor.size == 3 ) + Color.A = 1.0f; + + UMaterialExpressionVectorParameter * ExpressionSpecularColor = + Cast< UMaterialExpressionVectorParameter >( Material->Specular.Expression ); + + // Create color const expression and add it to material, if we don't have one. + if ( !ExpressionSpecularColor ) + { + // Otherwise new expression is of a different type. + if ( Material->Specular.Expression ) + { + Material->Specular.Expression->ConditionalBeginDestroy(); + Material->Specular.Expression = nullptr; + } + + ExpressionSpecularColor = NewObject< UMaterialExpressionVectorParameter >( + Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag ); + } + + // Record generating parameter. + ExpressionSpecularColor->Desc = GeneratingParameterName; + ExpressionSpecularColor->ParameterName = *GeneratingParameterName; + + ExpressionSpecularColor->DefaultValue = Color; + + // Offset node placement. + ExpressionSpecularColor->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + ExpressionSpecularColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add( ExpressionSpecularColor ); + Material->Specular.Expression = ExpressionSpecularColor; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +bool +FHoudiniEngineMaterialUtils::CreateMaterialComponentRoughness( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + EObjectFlags ObjectFlag = ( BakeMode == EBakeMode::CookToTemp ) ? RF_NoFlags : RF_Standalone; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT( "" ); + + // Roughness texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if roughness texture is available. + HAPI_ParmId ParmNameRoughnessId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0 ); + + if ( ParmNameRoughnessId >= 0 ) + { + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0 ); + } + else + { + ParmNameRoughnessId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1 ); + + if ( ParmNameRoughnessId >= 0 ) + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1 ); + } + + if ( ParmNameRoughnessId >= 0 ) + { + TArray< char > ImageBuffer; + + // Retrieve color plane. + if ( FHoudiniEngineMaterialUtils::HapiExtractImage( + ParmNameRoughnessId, MaterialInfo, ImageBuffer, + HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true ) ) + { + UMaterialExpressionTextureSampleParameter2D* ExpressionRoughness = + Cast< UMaterialExpressionTextureSampleParameter2D >( Material->Roughness.Expression ); + + UTexture2D* TextureRoughness = nullptr; + if ( ExpressionRoughness ) + { + TextureRoughness = Cast< UTexture2D >( ExpressionRoughness->Texture ); + } + else + { + // Otherwise new expression is of a different type. + if ( Material->Roughness.Expression ) + { + Material->Roughness.Expression->ConditionalBeginDestroy(); + Material->Roughness.Expression = nullptr; + } + } + + UPackage * TextureRoughnessPackage = nullptr; + if ( TextureRoughness ) + TextureRoughnessPackage = Cast< UPackage >( TextureRoughness->GetOuter() ); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo ); + + if ( Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0 ) + { + // Create texture. + FString TextureRoughnessName; + bool bCreatedNewTextureRoughness = false; + + // Create roughness texture package, if this is a new roughness texture. + if ( !TextureRoughnessPackage ) + { + TextureRoughnessPackage = FHoudiniEngineBakeUtils::BakeCreateTexturePackageForComponent( + HoudiniCookParams, + MaterialInfo, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, + TextureRoughnessName ); + } + + // Create roughness texture, if we need to create one. + if ( !TextureRoughness ) + bCreatedNewTextureRoughness = true; + + // Get the node path to add it to the meta data + FString NodePath; + GetUniqueMaterialShopName( AssetId, MaterialInfo.nodeId, NodePath ); + + // Reuse existing roughness texture, or create new one. + TextureRoughness = FHoudiniEngineMaterialUtils::CreateUnrealTexture( + TextureRoughness, ImageInfo, + TextureRoughnessPackage, TextureRoughnessName, ImageBuffer, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, + CreateTexture2DParameters, + TEXTUREGROUP_World, NodePath ); + + if ( BakeMode == EBakeMode::CookToTemp ) + TextureRoughness->SetFlags( RF_Public | RF_Standalone ); + + // Create roughness sampling expression, if needed. + if ( !ExpressionRoughness ) + ExpressionRoughness = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag ); + + // Record generating parameter. + ExpressionRoughness->Desc = GeneratingParameterName; + ExpressionRoughness->ParameterName = *GeneratingParameterName; + + ExpressionRoughness->Texture = TextureRoughness; + ExpressionRoughness->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionRoughness->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + ExpressionRoughness->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add( ExpressionRoughness ); + Material->Roughness.Expression = ExpressionRoughness; + + bExpressionCreated = true; + + // Propagate and trigger roughness texture updates. + if (bCreatedNewTextureRoughness) + FAssetRegistryModule::AssetCreated(TextureRoughness); + + TextureRoughness->PreEditChange(nullptr); + TextureRoughness->PostEditChange(); + TextureRoughness->MarkPackageDirty(); + } + } + } + + HAPI_ParmInfo ParmInfoRoughnessValue; + FHoudiniApi::ParmInfo_Init(&ParmInfoRoughnessValue); + HAPI_ParmId ParmNameRoughnessValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0, ParmInfoRoughnessValue ); + + if ( ParmNameRoughnessValueId >= 0 ) + { + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0 ); + } + else + { + ParmNameRoughnessValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1, ParmInfoRoughnessValue ); + + if ( ParmNameRoughnessValueId >= 0 ) + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1 ); + } + + if ( !bExpressionCreated && ParmNameRoughnessValueId >= 0 ) + { + // Roughness value is available. + + float RoughnessValue = 0.0f; + + if ( FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), NodeInfo.id, (float *) &RoughnessValue, + ParmInfoRoughnessValue.floatValuesIndex, 1 ) == HAPI_RESULT_SUCCESS ) + { + UMaterialExpressionScalarParameter * ExpressionRoughnessValue = + Cast< UMaterialExpressionScalarParameter >( Material->Roughness.Expression ); + + // Clamp retrieved value. + RoughnessValue = FMath::Clamp< float >( RoughnessValue, 0.0f, 1.0f ); + + // Create color const expression and add it to material, if we don't have one. + if ( !ExpressionRoughnessValue ) + { + // Otherwise new expression is of a different type. + if ( Material->Roughness.Expression ) + { + Material->Roughness.Expression->ConditionalBeginDestroy(); + Material->Roughness.Expression = nullptr; + } + + ExpressionRoughnessValue = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag ); + } + + // Record generating parameter. + ExpressionRoughnessValue->Desc = GeneratingParameterName; + ExpressionRoughnessValue->ParameterName = *GeneratingParameterName; + + ExpressionRoughnessValue->DefaultValue = RoughnessValue; + ExpressionRoughnessValue->SliderMin = 0.0f; + ExpressionRoughnessValue->SliderMax = 1.0f; + + // Offset node placement. + ExpressionRoughnessValue->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + ExpressionRoughnessValue->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add( ExpressionRoughnessValue ); + Material->Roughness.Expression = ExpressionRoughnessValue; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +bool +FHoudiniEngineMaterialUtils::CreateMaterialComponentMetallic( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + EObjectFlags ObjectFlag = ( BakeMode == EBakeMode::CookToTemp ) ? RF_NoFlags : RF_Standalone; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT( "" ); + + // Metallic texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if metallic texture is available. + HAPI_ParmId ParmNameMetallicId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_METALLIC ); + + if ( ParmNameMetallicId >= 0 ) + { + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_MAP_METALLIC ); + } + + if ( ParmNameMetallicId >= 0 ) + { + TArray< char > ImageBuffer; + + // Retrieve color plane. + if ( FHoudiniEngineMaterialUtils::HapiExtractImage( + ParmNameMetallicId, MaterialInfo, ImageBuffer, + HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true ) ) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionMetallic = + Cast< UMaterialExpressionTextureSampleParameter2D >( Material->Metallic.Expression ); + + UTexture2D * TextureMetallic = nullptr; + if ( ExpressionMetallic ) + { + TextureMetallic = Cast< UTexture2D >( ExpressionMetallic->Texture ); + } + else + { + // Otherwise new expression is of a different type. + if ( Material->Metallic.Expression ) + { + Material->Metallic.Expression->ConditionalBeginDestroy(); + Material->Metallic.Expression = nullptr; + } + } + + UPackage * TextureMetallicPackage = nullptr; + if ( TextureMetallic ) + TextureMetallicPackage = Cast< UPackage >( TextureMetallic->GetOuter() ); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo ); + + if ( Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0 ) + { + // Create texture. + FString TextureMetallicName; + bool bCreatedNewTextureMetallic = false; + + // Create metallic texture package, if this is a new metallic texture. + if ( !TextureMetallicPackage ) + { + TextureMetallicPackage = FHoudiniEngineBakeUtils::BakeCreateTexturePackageForComponent( + HoudiniCookParams, + MaterialInfo, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, + TextureMetallicName ); + } + + // Create metallic texture, if we need to create one. + if ( !TextureMetallic ) + bCreatedNewTextureMetallic = true; + + // Get the node path to add it to the meta data + FString NodePath; + GetUniqueMaterialShopName( AssetId, MaterialInfo.nodeId, NodePath ); + + // Reuse existing metallic texture, or create new one. + TextureMetallic = FHoudiniEngineMaterialUtils::CreateUnrealTexture( + TextureMetallic, ImageInfo, + TextureMetallicPackage, TextureMetallicName, ImageBuffer, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, + CreateTexture2DParameters, + TEXTUREGROUP_World, NodePath ); + + if ( BakeMode == EBakeMode::CookToTemp ) + TextureMetallic->SetFlags( RF_Public | RF_Standalone ); + + // Create metallic sampling expression, if needed. + if ( !ExpressionMetallic ) + ExpressionMetallic = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag ); + + // Record generating parameter. + ExpressionMetallic->Desc = GeneratingParameterName; + ExpressionMetallic->ParameterName = *GeneratingParameterName; + + ExpressionMetallic->Texture = TextureMetallic; + ExpressionMetallic->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionMetallic->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + ExpressionMetallic->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add( ExpressionMetallic ); + Material->Metallic.Expression = ExpressionMetallic; + + bExpressionCreated = true; + + // Propagate and trigger metallic texture updates. + if (bCreatedNewTextureMetallic) + FAssetRegistryModule::AssetCreated(TextureMetallic); + + TextureMetallic->PreEditChange(nullptr); + TextureMetallic->PostEditChange(); + TextureMetallic->MarkPackageDirty(); + } + } + } + + HAPI_ParmInfo ParmInfoMetallic; + FHoudiniApi::ParmInfo_Init(&ParmInfoMetallic); + HAPI_ParmId ParmNameMetallicValueIdx = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_VALUE_METALLIC, ParmInfoMetallic ); + + if ( ParmNameMetallicValueIdx >= 0 ) + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_VALUE_METALLIC ); + + if ( !bExpressionCreated && ParmNameMetallicValueIdx >= 0 ) + { + // Metallic value is available. + + float MetallicValue = 0.0f; + + if ( FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), NodeInfo.id, (float *) &MetallicValue, + ParmInfoMetallic.floatValuesIndex, 1 ) == HAPI_RESULT_SUCCESS ) + { + UMaterialExpressionScalarParameter * ExpressionMetallicValue = + Cast< UMaterialExpressionScalarParameter >( Material->Metallic.Expression ); + + // Clamp retrieved value. + MetallicValue = FMath::Clamp< float >( MetallicValue, 0.0f, 1.0f ); + + // Create color const expression and add it to material, if we don't have one. + if ( !ExpressionMetallicValue ) + { + // Otherwise new expression is of a different type. + if ( Material->Metallic.Expression ) + { + Material->Metallic.Expression->ConditionalBeginDestroy(); + Material->Metallic.Expression = nullptr; + } + + ExpressionMetallicValue = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag ); + } + + // Record generating parameter. + ExpressionMetallicValue->Desc = GeneratingParameterName; + ExpressionMetallicValue->ParameterName = *GeneratingParameterName; + + ExpressionMetallicValue->DefaultValue = MetallicValue; + ExpressionMetallicValue->SliderMin = 0.0f; + ExpressionMetallicValue->SliderMax = 1.0f; + + // Offset node placement. + ExpressionMetallicValue->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + ExpressionMetallicValue->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add( ExpressionMetallicValue ); + Material->Metallic.Expression = ExpressionMetallicValue; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + + +bool +FHoudiniEngineMaterialUtils::CreateMaterialComponentEmissive( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + EObjectFlags ObjectFlag = ( BakeMode == EBakeMode::CookToTemp ) ? RF_NoFlags : RF_Standalone; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Emissive texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if emissive texture is available. + HAPI_ParmId ParmNameEmissiveId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_MAP_EMISSIVE ); + + if ( ParmNameEmissiveId >= 0 ) + { + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_MAP_EMISSIVE ); + } + + if ( ParmNameEmissiveId >= 0 ) + { + TArray< char > ImageBuffer; + + // Retrieve color plane. + if ( FHoudiniEngineMaterialUtils::HapiExtractImage( + ParmNameEmissiveId, MaterialInfo, ImageBuffer, + HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true ) ) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionEmissive = + Cast< UMaterialExpressionTextureSampleParameter2D >( Material->EmissiveColor.Expression ); + + UTexture2D * TextureEmissive = nullptr; + if ( ExpressionEmissive ) + { + TextureEmissive = Cast< UTexture2D >( ExpressionEmissive->Texture ); + } + else + { + // Otherwise new expression is of a different type. + if ( Material->EmissiveColor.Expression ) + { + Material->EmissiveColor.Expression->ConditionalBeginDestroy(); + Material->EmissiveColor.Expression = nullptr; + } + } + + UPackage * TextureEmissivePackage = nullptr; + if ( TextureEmissive ) + TextureEmissivePackage = Cast< UPackage >( TextureEmissive->GetOuter() ); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo ); + + if ( Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0 ) + { + // Create texture. + FString TextureEmissiveName; + bool bCreatedNewTextureEmissive = false; + + // Create emissive texture package, if this is a new emissive texture. + if ( !TextureEmissivePackage ) + { + TextureEmissivePackage = FHoudiniEngineBakeUtils::BakeCreateTexturePackageForComponent( + HoudiniCookParams, + MaterialInfo, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, + TextureEmissiveName ); + } + + // Create emissive texture, if we need to create one. + if ( !TextureEmissive ) + bCreatedNewTextureEmissive = true; + + // Get the node path to add it to the meta data + FString NodePath; + GetUniqueMaterialShopName( AssetId, MaterialInfo.nodeId, NodePath ); + + // Reuse existing emissive texture, or create new one. + TextureEmissive = FHoudiniEngineMaterialUtils::CreateUnrealTexture( + TextureEmissive, ImageInfo, + TextureEmissivePackage, TextureEmissiveName, ImageBuffer, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, + CreateTexture2DParameters, + TEXTUREGROUP_World, NodePath ); + + if ( BakeMode == EBakeMode::CookToTemp ) + TextureEmissive->SetFlags( RF_Public | RF_Standalone ); + + // Create emissive sampling expression, if needed. + if ( !ExpressionEmissive ) + ExpressionEmissive = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag ); + + // Record generating parameter. + ExpressionEmissive->Desc = GeneratingParameterName; + ExpressionEmissive->ParameterName = *GeneratingParameterName; + + ExpressionEmissive->Texture = TextureEmissive; + ExpressionEmissive->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionEmissive->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + ExpressionEmissive->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add( ExpressionEmissive ); + Material->EmissiveColor.Expression = ExpressionEmissive; + + bExpressionCreated = true; + + // Propagate and trigger metallic texture updates. + if (bCreatedNewTextureEmissive) + FAssetRegistryModule::AssetCreated(TextureEmissive); + + TextureEmissive->PreEditChange(nullptr); + TextureEmissive->PostEditChange(); + TextureEmissive->MarkPackageDirty(); + } + } + } + + HAPI_ParmInfo ParmInfoEmissive; + FHoudiniApi::ParmInfo_Init(&ParmInfoEmissive); + HAPI_ParmId ParmNameEmissiveValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0, ParmInfoEmissive ); + + if ( ParmNameEmissiveValueId >= 0 ) + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0 ); + else + { + ParmNameEmissiveValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeInfo.id, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1, ParmInfoEmissive ); + + if ( ParmNameEmissiveValueId >= 0 ) + GeneratingParameterName = TEXT( HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1 ); + } + + if ( !bExpressionCreated && ParmNameEmissiveValueId >= 0 ) + { + // Emissive color is available. + + FLinearColor Color = FLinearColor::White; + + if ( FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), NodeInfo.id, (float*)&Color.R, + ParmInfoEmissive.floatValuesIndex, ParmInfoEmissive.size ) == HAPI_RESULT_SUCCESS ) + { + if ( ParmInfoEmissive.size == 3 ) + Color.A = 1.0f; + + UMaterialExpressionConstant4Vector * ExpressionEmissiveColor = + Cast< UMaterialExpressionConstant4Vector >( Material->EmissiveColor.Expression ); + + // Create color const expression and add it to material, if we don't have one. + if ( !ExpressionEmissiveColor ) + { + // Otherwise new expression is of a different type. + if ( Material->EmissiveColor.Expression ) + { + Material->EmissiveColor.Expression->ConditionalBeginDestroy(); + Material->EmissiveColor.Expression = nullptr; + } + + ExpressionEmissiveColor = NewObject< UMaterialExpressionConstant4Vector >( + Material, UMaterialExpressionConstant4Vector::StaticClass(), NAME_None, ObjectFlag ); + } + + // Record generating parameter. + ExpressionEmissiveColor->Desc = GeneratingParameterName; + if ( ExpressionEmissiveColor->CanRenameNode() ) + ExpressionEmissiveColor->SetEditableName( *GeneratingParameterName ); + + ExpressionEmissiveColor->Constant = Color; + + // Offset node placement. + ExpressionEmissiveColor->MaterialExpressionEditorX = FHoudiniEngineMaterialUtils::MaterialExpressionNodeX; + ExpressionEmissiveColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniEngineMaterialUtils::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add( ExpressionEmissiveColor ); + Material->EmissiveColor.Expression = ExpressionEmissiveColor; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +UTexture2D * +FHoudiniEngineMaterialUtils::CreateUnrealTexture( + UTexture2D * ExistingTexture, const HAPI_ImageInfo & ImageInfo, + UPackage * Package, const FString & TextureName, + const TArray< char > & ImageBuffer, const FString & TextureType, + const FCreateTexture2DParameters & TextureParameters, TextureGroup LODGroup, const FString& NodePath ) +{ + if (!Package || Package->IsPendingKill()) + return nullptr; + + UTexture2D * Texture = nullptr; + if ( ExistingTexture ) + { + Texture = ExistingTexture; + } + else + { + // Create new texture object. + Texture = NewObject< UTexture2D >( + Package, UTexture2D::StaticClass(), *TextureName, + RF_Transactional ); + + // Assign texture group. + Texture->LODGroup = LODGroup; + } + + // Add/Update meta information to package. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT( "true" ) ); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *TextureName ); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType ); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_NODE_PATH, *NodePath ); + + // Initialize texture source. + Texture->Source.Init( ImageInfo.xRes, ImageInfo.yRes, 1, 1, TSF_BGRA8 ); + + // Lock the texture. + uint8 * MipData = Texture->Source.LockMip( 0 ); + + // Create base map. + uint8* DestPtr = nullptr; + uint32 SrcWidth = ImageInfo.xRes; + uint32 SrcHeight = ImageInfo.yRes; + const char * SrcData = &ImageBuffer[ 0 ]; + + for ( uint32 y = 0; y < SrcHeight; y++ ) + { + DestPtr = &MipData[ ( SrcHeight - 1 - y ) * SrcWidth * sizeof( FColor ) ]; + + for ( uint32 x = 0; x < SrcWidth; x++ ) + { + uint32 DataOffset = y * SrcWidth * 4 + x * 4; + + *DestPtr++ = *(uint8*)( SrcData + DataOffset + 2 ); // B + *DestPtr++ = *(uint8*)( SrcData + DataOffset + 1 ); // G + *DestPtr++ = *(uint8*)( SrcData + DataOffset + 0 ); // R + + if ( TextureParameters.bUseAlpha ) + *DestPtr++ = *(uint8*)( SrcData + DataOffset + 3 ); // A + else + *DestPtr++ = 0xFF; + } + } + + bool bHasAlphaValue = false; + if ( TextureParameters.bUseAlpha ) + { + // See if there is an actual alpha value in the texture or if we can ignore the texture alpha + for ( uint32 y = 0; y < SrcHeight; y++ ) + { + for ( uint32 x = 0; x < SrcWidth; x++ ) + { + uint32 DataOffset = y * SrcWidth * 4 + x * 4; + if (*(uint8*)(SrcData + DataOffset + 3) != 0xFF) + { + bHasAlphaValue = true; + break; + } + } + + if ( bHasAlphaValue ) + break; + } + } + + // Unlock the texture. + Texture->Source.UnlockMip( 0 ); + + // Texture creation parameters. + Texture->SRGB = TextureParameters.bSRGB; + Texture->CompressionSettings = TextureParameters.CompressionSettings; + Texture->CompressionNoAlpha = !bHasAlphaValue; + Texture->DeferCompression = TextureParameters.bDeferCompression; + + // Set the Source Guid/Hash if specified. + /* + if ( TextureParameters.SourceGuidHash.IsValid() ) + { + Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); + } + */ + + Texture->PostEditChange(); + + return Texture; +} + +#endif + +bool +FHoudiniEngineMaterialUtils::GetUniqueMaterialShopName( HAPI_NodeId AssetId, HAPI_NodeId MaterialId, FString & Name ) +{ + if ( AssetId < 0 || MaterialId < 0 ) + return false; + + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ), false ); + + HAPI_MaterialInfo MaterialInfo; + FHoudiniApi::MaterialInfo_Init(&MaterialInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetMaterialInfo( + FHoudiniEngine::Get().GetSession(), MaterialId, + &MaterialInfo ), false ); + + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, + &AssetNodeInfo ), false ); + + HAPI_NodeInfo MaterialNodeInfo; + FHoudiniApi::NodeInfo_Init(&MaterialNodeInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), MaterialInfo.nodeId, + &MaterialNodeInfo ), false ); + + FString AssetNodeName = TEXT( "" ); + FString MaterialNodeName = TEXT( "" ); + + { + FHoudiniEngineString HoudiniEngineString( AssetNodeInfo.internalNodePathSH ); + if ( !HoudiniEngineString.ToFString( AssetNodeName ) ) + return false; + } + + { + FHoudiniEngineString HoudiniEngineString( MaterialNodeInfo.internalNodePathSH ); + if ( !HoudiniEngineString.ToFString( MaterialNodeName ) ) + return false; + } + + if ( AssetNodeName.Len() > 0 && MaterialNodeName.Len() > 0 ) + { + // Remove AssetNodeName part from MaterialNodeName. Extra position is for separator. + Name = MaterialNodeName.Mid( AssetNodeName.Len() + 1 ); + return true; + } + + return false; +} + +UMaterialExpression * +FHoudiniEngineMaterialUtils::MaterialLocateExpression( UMaterialExpression * Expression, UClass * ExpressionClass ) +{ + if ( !Expression ) + return nullptr; +#if WITH_EDITOR + if ( ExpressionClass == Expression->GetClass() ) + return Expression; + + + // If this is a channel multiply expression, we can recurse. + UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast< UMaterialExpressionMultiply >( Expression ); + if ( MaterialExpressionMultiply ) + { + { + UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->A.Expression; + if ( MaterialExpression ) + { + if ( MaterialExpression->GetClass() == ExpressionClass ) + return MaterialExpression; + + MaterialExpression = FHoudiniEngineMaterialUtils::MaterialLocateExpression( + Cast< UMaterialExpressionMultiply >( MaterialExpression ), ExpressionClass ); + + if ( MaterialExpression ) + return MaterialExpression; + } + } + + { + UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->B.Expression; + if ( MaterialExpression ) + { + if ( MaterialExpression->GetClass() == ExpressionClass ) + return MaterialExpression; + + MaterialExpression = FHoudiniEngineMaterialUtils::MaterialLocateExpression( + Cast< UMaterialExpressionMultiply >( MaterialExpression ), ExpressionClass ); + + if ( MaterialExpression ) + return MaterialExpression; + } + } + } +#endif + + return nullptr; +} + + +bool +FHoudiniEngineMaterialUtils::CreateMaterialInstances( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, FHoudiniCookParams& CookParams, + UMaterialInstance*& CreatedMaterialInstance, UMaterialInterface*& OriginalMaterialInterface, + std::string AttributeName, int32 MaterialIndex) +{ +#if WITH_EDITOR + if ( !HoudiniGeoPartObject.IsValid() ) + return false; + + // First, make sure this geopartObj has a material instance attribute + if ( !FHoudiniEngineUtils::HapiCheckAttributeExists( + HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId, + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, + AttributeName.c_str() ) ) + return false; + + // Get the material instance attribute info + HAPI_AttributeInfo AttribMaterialInstances; + FHoudiniApi::AttributeInfo_Init(&AttribMaterialInstances); + //FMemory::Memzero< HAPI_AttributeInfo >( AttribMaterialInstances ); + + TArray< FString > MaterialInstances; + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId, + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, + AttributeName.c_str(), + AttribMaterialInstances, MaterialInstances ); + + // No material instance attribute + if ( !AttribMaterialInstances.exists || MaterialInstances.Num() <= 0 ) + return false; + + // Get the material name from the material_instance attribute + // Since the material instance attribute can be set per primitive, it's going to be very difficult to know + // exactly where to look for the nth material instance, so we'll have to iterate on them to find it. + // In order for the material slot to be created, the material instance attribute had to be different, + // so we'll use this to hopefully fetch the right value. + // This is pretty hacky and we should probably require an extra material_instance_index attribute instead. + // but still a better solution than ignore all the material slots but the first + int32 MaterialIndexToAttributeIndex = 0; + if ( MaterialIndex > 0 && AttribMaterialInstances.owner == HAPI_ATTROWNER_PRIM ) + { + int32 CurrentMaterialIndex = 0; + FString CurrentMatName = MaterialInstances[ 0 ]; + for ( int32 n = 0; n < MaterialInstances.Num(); n++ ) + { + if ( MaterialInstances[ n ].Equals( CurrentMatName ) ) + continue; + + CurrentMatName = MaterialInstances[ n ]; + CurrentMaterialIndex++; + + if ( CurrentMaterialIndex == MaterialIndex ) + { + MaterialIndexToAttributeIndex = n; + break; + } + } + } + + const FString & MaterialName = MaterialInstances[ MaterialIndexToAttributeIndex ]; + if ( MaterialName.IsEmpty() ) + return false; + + // Trying to find the material we want to create an instance of + OriginalMaterialInterface = Cast< UMaterialInterface >( + StaticLoadObject( UMaterialInterface::StaticClass(), nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr ) ); + + // The source material wasn't found + if ( !OriginalMaterialInterface || OriginalMaterialInterface->IsPendingKill() ) + return false; + + // Create/Retrieve the package for the MI + FString MaterialInstanceName; + FString MaterialInstanceNamePrefix = UPackageTools::SanitizePackageName(OriginalMaterialInterface->GetName() + TEXT("_instance_") + FString::FromInt(MaterialIndex) ); + + // See if we can find the package in the cooked temp package cache + UPackage * MaterialInstancePackage = nullptr; + TWeakObjectPtr< UPackage > * FoundPointer = CookParams.CookedTemporaryPackages->Find( MaterialInstanceNamePrefix ); + if ( FoundPointer && (*FoundPointer).IsValid() ) + { + // We found an already existing package for the M_I + MaterialInstancePackage = (*FoundPointer).Get(); + MaterialInstanceName = MaterialInstancePackage->GetName(); + } + else + { + // We Couldnt find the corresponding M_I package, so create a new one + MaterialInstancePackage = FHoudiniEngineBakeUtils::BakeCreateTextureOrMaterialPackageForComponent( + CookParams, MaterialInstanceNamePrefix, MaterialInstanceName ); + } + + // Couldn't create a package for the Material Instance + if ( !MaterialInstancePackage ) + return false; + + //UMaterialInterface * MaterialInstanceInterface = Cast< UMaterialInterface >( + // StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *MaterialInstanceNameString, nullptr, LOAD_NoWarn, nullptr)); + // Trying to load the material instance from the package + bool bNewMaterialCreated = false; + UMaterialInstanceConstant* NewMaterialInstance = LoadObject( MaterialInstancePackage, *MaterialInstanceName, nullptr, LOAD_None, nullptr ); + if ( !NewMaterialInstance ) + { + // Factory to create materials. + UMaterialInstanceConstantFactoryNew* MaterialInstanceFactory = NewObject< UMaterialInstanceConstantFactoryNew >(); + if ( !MaterialInstanceFactory ) + return false; + + // Create the new material instance + MaterialInstanceFactory->AddToRoot(); + MaterialInstanceFactory->InitialParent = OriginalMaterialInterface; + NewMaterialInstance = ( UMaterialInstanceConstant* )MaterialInstanceFactory->FactoryCreateNew( + UMaterialInstanceConstant::StaticClass(), MaterialInstancePackage, FName( *MaterialInstanceName ), + RF_Public | RF_Standalone, NULL, GWarn ); + + if ( NewMaterialInstance ) + bNewMaterialCreated = true; + + MaterialInstanceFactory->RemoveFromRoot(); + + /* + // Creating a new material instance + NewMaterialInstance = NewObject< UMaterialInstanceConstant >( MaterialInstancePackage, FName( *MaterialInstanceName ), RF_Public ); + checkf( NewMaterialInstance, TEXT( "Failed to create instanced material" ) ); + NewMaterialInstance->Parent = Material; + + // Notify registry that we have created a new duplicate material. + FAssetRegistryModule::AssetCreated( NewMaterialInstance ); + + // Dirty the material package. + NewMaterialInstance->MarkPackageDirty(); + + // Reset any derived state + //MaterialInstance->ForceRecompileForRendering(); + */ + } + + if ( !OriginalMaterialInterface || !NewMaterialInstance ) + return false; + + // Update context for generated materials (will trigger when object goes out of scope). + FMaterialUpdateContext MaterialUpdateContext; + + bool bModifiedMaterialParameters = false; + // See if we need to override some of the material instance's parameters + TArray< UGenericAttribute > AllMatParams; + // Get the detail material parameters + int ParamCount = FHoudiniEngineUtils::GetGenericAttributeList( HoudiniGeoPartObject, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, AllMatParams, HAPI_ATTROWNER_DETAIL ); + // Then the primitive material parameters + ParamCount += FHoudiniEngineUtils::GetGenericAttributeList( HoudiniGeoPartObject, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, AllMatParams, HAPI_ATTROWNER_PRIM, MaterialIndexToAttributeIndex ); + for ( int32 ParamIdx = 0; ParamIdx < AllMatParams.Num(); ParamIdx++ ) + { + // Try to update the material instance parameter corresponding to the attribute + if ( UpdateMaterialInstanceParameter( AllMatParams[ ParamIdx ], NewMaterialInstance, CookParams ) ) + bModifiedMaterialParameters = true; + } + + // Schedule this material for update if needed. + if ( bNewMaterialCreated || bModifiedMaterialParameters ) + MaterialUpdateContext.AddMaterialInstance( NewMaterialInstance ); + + if ( bNewMaterialCreated ) + { + // Add meta information to this package. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT( "true" ) ); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialInstanceName ); + + // Notify registry that we have created a new duplicate material. + FAssetRegistryModule::AssetCreated( NewMaterialInstance ); + } + + if ( bNewMaterialCreated || bModifiedMaterialParameters ) + { + // Dirty the material + NewMaterialInstance->MarkPackageDirty(); + + // Update the material instance + NewMaterialInstance->InitStaticPermutation(); + NewMaterialInstance->PreEditChange(nullptr); + NewMaterialInstance->PostEditChange(); + /* + // Automatically save the package to avoid further issue + MaterialInstancePackage->SetDirtyFlag( true ); + MaterialInstancePackage->FullyLoad(); + UPackage::SavePackage( + MaterialInstancePackage, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, + *FPackageName::LongPackageNameToFilename( MaterialInstancePackage->GetName(), FPackageName::GetAssetPackageExtension() ) ); + */ + } + + // Update the return pointers + CreatedMaterialInstance = NewMaterialInstance; + + return true; +#else + return false; +#endif +} + +bool +FHoudiniEngineMaterialUtils::UpdateMaterialInstanceParameter( UGenericAttribute MaterialParameter, UMaterialInstanceConstant* MaterialInstance, FHoudiniCookParams& CookParams ) +{ + bool bParameterUpdated = false; + +#if WITH_EDITOR + if ( !MaterialInstance ) + return false; + + if ( MaterialParameter.AttributeName.IsEmpty() ) + return false; + + // The default material instance parameters needs to be handled manually as they cant be changed via generic SetParameters functions + if ( MaterialParameter.AttributeName.Compare( "CastShadowAsMasked", ESearchCase::IgnoreCase ) == 0 ) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if ( MaterialInstance->GetOverrideCastShadowAsMasked() && ( MaterialInstance->GetCastShadowAsMasked() == Value ) ) + return false; + + MaterialInstance->SetOverrideCastShadowAsMasked( true ); + MaterialInstance->SetCastShadowAsMasked( Value ); + bParameterUpdated = true; + } + else if ( MaterialParameter.AttributeName.Compare( "EmissiveBoost", ESearchCase::IgnoreCase ) == 0 ) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if ( MaterialInstance->GetOverrideEmissiveBoost() && ( MaterialInstance->GetEmissiveBoost() == Value ) ) + return false; + + MaterialInstance->SetOverrideEmissiveBoost( true ); + MaterialInstance->SetEmissiveBoost( Value ); + bParameterUpdated = true; + } + else if ( MaterialParameter.AttributeName.Compare( "DiffuseBoost", ESearchCase::IgnoreCase ) == 0 ) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if ( MaterialInstance->GetOverrideDiffuseBoost() && ( MaterialInstance->GetDiffuseBoost() == Value ) ) + return false; + + MaterialInstance->SetOverrideDiffuseBoost( true ); + MaterialInstance->SetDiffuseBoost( Value ); + bParameterUpdated = true; + } + else if ( MaterialParameter.AttributeName.Compare( "ExportResolutionScale", ESearchCase::IgnoreCase ) == 0 ) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideExportResolutionScale() && ( MaterialInstance->GetExportResolutionScale() == Value ) ) + return false; + + MaterialInstance->SetOverrideExportResolutionScale( true ); + MaterialInstance->SetExportResolutionScale( Value ); + bParameterUpdated = true; + } + else if ( MaterialParameter.AttributeName.Compare( "OpacityMaskClipValue", ESearchCase::IgnoreCase ) == 0 ) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if ( MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue && ( MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue == Value ) ) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue = true; + MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue = Value; + bParameterUpdated = true; + } + else if ( MaterialParameter.AttributeName.Compare( "BlendMode", ESearchCase::IgnoreCase ) == 0 ) + { + EBlendMode EnumValue = (EBlendMode)MaterialParameter.GetIntValue(); + if ( MaterialParameter.AttributeType == HAPI_STORAGETYPE_STRING ) + { + FString StringValue = MaterialParameter.GetStringValue(); + if ( StringValue.Compare( "Opaque", ESearchCase::IgnoreCase ) == 0 ) + EnumValue = EBlendMode::BLEND_Opaque; + else if ( StringValue.Compare( "Masked", ESearchCase::IgnoreCase ) == 0 ) + EnumValue = EBlendMode::BLEND_Masked; + else if ( StringValue.Compare( "Translucent", ESearchCase::IgnoreCase ) == 0 ) + EnumValue = EBlendMode::BLEND_Translucent; + else if ( StringValue.Compare( "Additive", ESearchCase::IgnoreCase ) == 0 ) + EnumValue = EBlendMode::BLEND_Additive; + else if ( StringValue.Compare( "Modulate", ESearchCase::IgnoreCase ) == 0 ) + EnumValue = EBlendMode::BLEND_Modulate; + else if ( StringValue.StartsWith( "Alpha", ESearchCase::IgnoreCase ) ) + EnumValue = EBlendMode::BLEND_AlphaComposite; + } + + // Update the parameter value only if necessary + if ( MaterialInstance->BasePropertyOverrides.bOverride_BlendMode && ( MaterialInstance->BasePropertyOverrides.BlendMode == EnumValue ) ) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_BlendMode = true; + MaterialInstance->BasePropertyOverrides.BlendMode = EnumValue; + bParameterUpdated = true; + } + else if ( MaterialParameter.AttributeName.Compare( "ShadingModel", ESearchCase::IgnoreCase ) == 0 ) + { + EMaterialShadingModel EnumValue = (EMaterialShadingModel)MaterialParameter.GetIntValue(); + if ( MaterialParameter.AttributeType == HAPI_STORAGETYPE_STRING ) + { + FString StringValue = MaterialParameter.GetStringValue(); + if ( StringValue.Compare( "Unlit", ESearchCase::IgnoreCase ) == 0 ) + EnumValue = EMaterialShadingModel::MSM_Unlit; + else if ( StringValue.StartsWith( "Default", ESearchCase::IgnoreCase ) ) + EnumValue = EMaterialShadingModel::MSM_DefaultLit; + else if ( StringValue.Compare( "Subsurface", ESearchCase::IgnoreCase ) == 0 ) + EnumValue = EMaterialShadingModel::MSM_Subsurface; + else if ( StringValue.StartsWith( "Preintegrated", ESearchCase::IgnoreCase ) ) + EnumValue = EMaterialShadingModel::MSM_PreintegratedSkin; + else if ( StringValue.StartsWith( "Clear", ESearchCase::IgnoreCase ) ) + EnumValue = EMaterialShadingModel::MSM_ClearCoat; + else if ( StringValue.Compare( "SubsurfaceProfile", ESearchCase::IgnoreCase ) == 0 ) + EnumValue = EMaterialShadingModel::MSM_SubsurfaceProfile; + else if ( StringValue.Compare( "TwoSidedFoliage", ESearchCase::IgnoreCase ) == 0 ) + EnumValue = EMaterialShadingModel::MSM_TwoSidedFoliage; + else if ( StringValue.Compare( "Hair", ESearchCase::IgnoreCase ) == 0 ) + EnumValue = EMaterialShadingModel::MSM_Hair; + else if ( StringValue.Compare( "Cloth", ESearchCase::IgnoreCase ) == 0 ) + EnumValue = EMaterialShadingModel::MSM_Cloth; + else if ( StringValue.Compare( "Eye", ESearchCase::IgnoreCase ) == 0 ) + EnumValue = EMaterialShadingModel::MSM_Eye; + } + + // Update the parameter value only if necessary + if ( MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel && ( MaterialInstance->BasePropertyOverrides.ShadingModel == EnumValue ) ) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel = true; + MaterialInstance->BasePropertyOverrides.ShadingModel = EnumValue; + bParameterUpdated = true; + } + else if ( MaterialParameter.AttributeName.Compare( "TwoSided", ESearchCase::IgnoreCase ) == 0 ) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if ( MaterialInstance->BasePropertyOverrides.bOverride_TwoSided && ( MaterialInstance->BasePropertyOverrides.TwoSided == Value ) ) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_TwoSided = true; + MaterialInstance->BasePropertyOverrides.TwoSided = Value; + bParameterUpdated = true; + } + else if ( MaterialParameter.AttributeName.Compare( "DitheredLODTransition", ESearchCase::IgnoreCase ) == 0 ) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if ( MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition && ( MaterialInstance->BasePropertyOverrides.DitheredLODTransition == Value ) ) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition = true; + MaterialInstance->BasePropertyOverrides.DitheredLODTransition = Value; + bParameterUpdated = true; + } + else if ( MaterialParameter.AttributeName.Compare( "PhysMaterial", ESearchCase::IgnoreCase ) == 0 ) + { + // Try to load a Material corresponding to the parameter value + FString ParamValue = MaterialParameter.GetStringValue(); + UPhysicalMaterial* FoundPhysMaterial = Cast< UPhysicalMaterial >( + StaticLoadObject( UPhysicalMaterial::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr ) ); + + // Update the parameter value if necessary + if (!FoundPhysMaterial || (MaterialInstance->PhysMaterial == FoundPhysMaterial)) + return false; + + MaterialInstance->PhysMaterial = FoundPhysMaterial; + bParameterUpdated = true; + } + + if (bParameterUpdated) + return true; + + // Handling custom parameters + FName CurrentMatParamName = FName( *MaterialParameter.AttributeName ); + if ( MaterialParameter.AttributeType == HAPI_STORAGETYPE_STRING ) + { + // String attributes are used for textures parameters + // We need to find the texture corresponding to the param + UTexture* FoundTexture = nullptr; + FString ParamValue = MaterialParameter.GetStringValue(); + + // Texture can either be already existing texture assets in UE4, or a newly generated textures by this asset + // Try to find the texture corresponding to the param value in the existing assets first. + FoundTexture = Cast< UTexture >( + StaticLoadObject( UTexture::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr ) ); + + if ( !FoundTexture ) + { + // We couldn't find a texture corresponding to the parameter in the existing UE4 assets + // Try to find the corresponding texture in the cooked temporary package we just generated + FoundTexture = FHoudiniEngineMaterialUtils::FindGeneratedTexture( ParamValue, CookParams ); + } + + // Do not update if unnecessary + + if ( FoundTexture ) + { + // Do not update if unnecessary + UTexture* OldTexture = nullptr; + bool FoundOldParam = MaterialInstance->GetTextureParameterValue( CurrentMatParamName, OldTexture ); + if ( FoundOldParam && ( OldTexture == FoundTexture ) ) + return false; + + MaterialInstance->SetTextureParameterValueEditorOnly( CurrentMatParamName, FoundTexture ); + bParameterUpdated = true; + } + } + else if ( MaterialParameter.AttributeTupleSize == 1 ) + { + // Single attributes are either for scalar parameters or static switches + float OldValue; + bool FoundOldScalarParam = MaterialInstance->GetScalarParameterValue( CurrentMatParamName, OldValue ); + if (FoundOldScalarParam) + { + // The material parameter is a scalar + float NewValue = (float)MaterialParameter.GetDoubleValue(); + + // Do not update if unnecessary + if (OldValue == NewValue) + return false; + + MaterialInstance->SetScalarParameterValueEditorOnly(CurrentMatParamName, NewValue); + bParameterUpdated = true; + } + else + { + // See if the underlying parameter is a static switch + bool NewBoolValue = MaterialParameter.GetBoolValue(); + + // We need to iterate over the material's static parameter set + FStaticParameterSet StaticParameters; + MaterialInstance->GetStaticParameterValues(StaticParameters); + + for (int32 SwitchParameterIdx = 0; SwitchParameterIdx < StaticParameters.StaticSwitchParameters.Num(); ++SwitchParameterIdx) + { + FStaticSwitchParameter& SwitchParameter = StaticParameters.StaticSwitchParameters[SwitchParameterIdx]; + if (SwitchParameter.ParameterInfo.Name != CurrentMatParamName) + continue; + + if (SwitchParameter.Value == NewBoolValue) + return false; + + SwitchParameter.Value = NewBoolValue; + SwitchParameter.bOverride = true; + + MaterialInstance->UpdateStaticPermutation(StaticParameters); + bParameterUpdated = true; + break; + } + } + } + else + { + // Tuple attributes are for vector parameters + FLinearColor NewLinearColor; + // if the attribute is stored in an int, we'll have to convert a color to a linear color + if ( MaterialParameter.AttributeType == HAPI_STORAGETYPE_INT || MaterialParameter.AttributeType == HAPI_STORAGETYPE_INT64 ) + { + FColor IntColor; + IntColor.R = (int8)MaterialParameter.GetIntValue( 0 ); + IntColor.G = (int8)MaterialParameter.GetIntValue( 1 ); + IntColor.B = (int8)MaterialParameter.GetIntValue( 2 ); + if ( MaterialParameter.AttributeTupleSize >= 4 ) + IntColor.A = (int8)MaterialParameter.GetIntValue( 3 ); + else + IntColor.A = 1; + + NewLinearColor = FLinearColor( IntColor ); + } + else + { + NewLinearColor.R = (float)MaterialParameter.GetDoubleValue( 0 ); + NewLinearColor.G = (float)MaterialParameter.GetDoubleValue( 1 ); + NewLinearColor.B = (float)MaterialParameter.GetDoubleValue( 2 ); + if ( MaterialParameter.AttributeTupleSize >= 4 ) + NewLinearColor.A = (float)MaterialParameter.GetDoubleValue( 3 ); + } + + // Do not update if unnecessary + FLinearColor OldValue; + bool FoundOldParam = MaterialInstance->GetVectorParameterValue( CurrentMatParamName, OldValue ); + if ( FoundOldParam && ( OldValue == NewLinearColor ) ) + return false; + + MaterialInstance->SetVectorParameterValueEditorOnly( CurrentMatParamName, NewLinearColor ); + bParameterUpdated = true; + } +#endif + + return bParameterUpdated; +} + +UTexture* +FHoudiniEngineMaterialUtils::FindGeneratedTexture( const FString& TextureString, FHoudiniCookParams& CookParams ) +{ + if ( TextureString.IsEmpty() ) + return nullptr; + + // Try to find the corresponding texture in the cooked temporary package generated by an HDA + UTexture* FoundTexture = nullptr; + for ( TMap >::TIterator IterPackage( *(CookParams.CookedTemporaryPackages) ); IterPackage; ++IterPackage ) + { + // Iterate through the cooked packages + UPackage * CurrentPackage = IterPackage.Value().Get(); + if ( !CurrentPackage ) + continue; + + // First, check if the package contains a texture + FString CurrentPackageName = CurrentPackage->GetName(); + UTexture* PackageTexture = LoadObject< UTexture >( CurrentPackage, *CurrentPackageName, nullptr, LOAD_None, nullptr ); + if ( !PackageTexture ) + continue; + + // Then check if the package's metadata match what we're looking for + // Make sure this texture was generated by Houdini Engine + UMetaData * MetaData = CurrentPackage->GetMetaData(); + if ( !MetaData || !MetaData->HasValue( PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT ) ) + continue; + + // Get the texture type from the meta data + // Texture type store has meta data will be C_A, N, S, R etc.. + const FString TextureTypeString = MetaData->GetValue( PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE ); + if ( TextureTypeString.Compare( TextureString, ESearchCase::IgnoreCase ) == 0 ) + { + FoundTexture = PackageTexture; + break; + } + + // Convert the texture type to a "friendly" version + // C_A to diffuse, N to Normal, S to Specular etc... + FString TextureTypeFriendlyString = TextureTypeString; + FString TextureTypeFriendlyAlternateString = TEXT(""); + if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, ESearchCase::IgnoreCase) == 0) + { + TextureTypeFriendlyString = TEXT("diffuse"); + TextureTypeFriendlyAlternateString = TEXT("basecolor"); + } + else if ( TextureTypeString.Compare( HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, ESearchCase::IgnoreCase) == 0 ) + TextureTypeFriendlyString = TEXT( "normal" ); + else if ( TextureTypeString.Compare( HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, ESearchCase::IgnoreCase) == 0 ) + TextureTypeFriendlyString = TEXT( "emissive" ); + else if ( TextureTypeString.Compare( HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, ESearchCase::IgnoreCase) == 0 ) + TextureTypeFriendlyString = TEXT( "specular" ); + else if ( TextureTypeString.Compare( HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, ESearchCase::IgnoreCase) == 0 ) + TextureTypeFriendlyString = TEXT( "roughness" ); + else if ( TextureTypeString.Compare( HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, ESearchCase::IgnoreCase) == 0 ) + TextureTypeFriendlyString = TEXT( "metallic" ); + else if ( TextureTypeString.Compare( HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, ESearchCase::IgnoreCase) == 0 ) + TextureTypeFriendlyString = TEXT( "opacity" ); + + // See if we have a match between the texture string and the friendly name + if ( ( TextureTypeFriendlyString.Compare( TextureString, ESearchCase::IgnoreCase ) == 0 ) + || ( !TextureTypeFriendlyAlternateString.IsEmpty() && TextureTypeFriendlyAlternateString.Compare( TextureString, ESearchCase::IgnoreCase ) == 0 ) ) + { + FoundTexture = PackageTexture; + break; + } + + // Get the node path from the meta data + const FString NodePath = MetaData->GetValue( PackageTexture, HAPI_UNREAL_PACKAGE_META_NODE_PATH ); + if ( NodePath.IsEmpty() ) + continue; + + // See if we have a match with the path and texture type + FString PathAndType = NodePath + TEXT("/") + TextureTypeString; + if ( PathAndType.Compare( TextureString, ESearchCase::IgnoreCase ) == 0 ) + { + FoundTexture = PackageTexture; + break; + } + + // See if we have a match with the friendly path and texture type + FString PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyString; + if ( PathAndFriendlyType.Compare( TextureString, ESearchCase::IgnoreCase ) == 0 ) + { + FoundTexture = PackageTexture; + break; + } + + // Try the alternate friendly string + if ( !TextureTypeFriendlyAlternateString.IsEmpty() ) + { + PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyAlternateString; + if ( PathAndFriendlyType.Compare( TextureString, ESearchCase::IgnoreCase ) == 0 ) + { + FoundTexture = PackageTexture; + break; + } + } + } + + return FoundTexture; +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineMaterialUtils.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineMaterialUtils.h new file mode 100644 index 00000000..ab082677 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineMaterialUtils.h @@ -0,0 +1,133 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniGeoPartObject.h" +#include "HoudiniCookHandler.h" +#include "ImageUtils.h" + +class UTexture2D; +class UMaterialExpression; +class UMaterialInstanceConstant; + +struct UGenericAttribute; + +struct HOUDINIENGINERUNTIME_API FHoudiniEngineMaterialUtils +{ +public: + /** HAPI : Create Unreal materials and necessary textures. Reuse existing materials, if they are not updated. **/ + static void HapiCreateMaterials( + HAPI_NodeId AssetId, + FHoudiniCookParams& HoudiniCookParams, const HAPI_AssetInfo & AssetInfo, + const TSet< HAPI_NodeId > & UniqueMaterialIds, const TSet< HAPI_NodeId > & UniqueInstancerMaterialIds, + TMap< FString, UMaterialInterface * > & Materials, const bool& bForceRecookAll ); + + /** HAPI : Retrieve a list of image planes. **/ + static bool HapiGetImagePlanes( + HAPI_ParmId NodeParmId, const HAPI_MaterialInfo & MaterialInfo, + TArray< FString > & ImagePlanes ); + + /** HAPI : Extract image data. **/ + static bool HapiExtractImage( + HAPI_ParmId NodeParmId, const HAPI_MaterialInfo & MaterialInfo, + TArray< char > & ImageBuffer, const char * PlaneType, HAPI_ImageDataFormat ImageDataFormat, + HAPI_ImagePacking ImagePacking, bool bRenderToImage ); + + /** HAPI : Get unique material SHOP name. **/ + static bool GetUniqueMaterialShopName( HAPI_NodeId AssetId, HAPI_NodeId MaterialId, FString & Name ); + + /** Helper function to locate first Material expression of given class within given expression subgraph. **/ + static UMaterialExpression * MaterialLocateExpression( UMaterialExpression * Expression, UClass * ExpressionClass ); + + /** Creates Material Instance from attributes **/ + static bool CreateMaterialInstances( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, FHoudiniCookParams& CookParams, + UMaterialInstance *& CreatedMaterialInstance, UMaterialInterface*& OriginalMaterialInterface, + std::string AttributeName, int32 MaterialIndex = 0 ); + + /** Updates the material instance parameter corresponding to the generic parameter found in the asset **/ + static bool UpdateMaterialInstanceParameter( UGenericAttribute MaterialParam, UMaterialInstanceConstant* MaterialInstance, FHoudiniCookParams& CookParams ); + + /** Try to find a texture generated by HoudiniEngine that matches the texture string **/ + static UTexture* FindGeneratedTexture( const FString& TextureString, FHoudiniCookParams& CookParams ); + +#if WITH_EDITOR + + /** Create a texture from given information. **/ + static UTexture2D * CreateUnrealTexture( + UTexture2D * ExistingTexture, const HAPI_ImageInfo & ImageInfo, + UPackage * Package, const FString & TextureName, + const TArray< char > & ImageBuffer, const FString & TextureType, + const FCreateTexture2DParameters & TextureParameters, TextureGroup LODGroup, const FString& NodePath ); + + /** Create various material components. **/ + static bool CreateMaterialComponentDiffuse( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ); + + static bool CreateMaterialComponentNormal( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ); + + static bool CreateMaterialComponentSpecular( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ); + + static bool CreateMaterialComponentRoughness( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ); + + static bool CreateMaterialComponentMetallic( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ); + + static bool CreateMaterialComponentEmissive( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ); + + static bool CreateMaterialComponentOpacity( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ); + + static bool CreateMaterialComponentOpacityMask( + FHoudiniCookParams& HoudiniCookParams, const HAPI_NodeId& AssetId, + UMaterial * Material, const HAPI_MaterialInfo & MaterialInfo, + const HAPI_NodeInfo & NodeInfo, int32 & MaterialNodeY ); + +#endif + + /** Material node construction offsets. **/ + static const int32 MaterialExpressionNodeX; + static const int32 MaterialExpressionNodeY; + static const int32 MaterialExpressionNodeStepX; + static const int32 MaterialExpressionNodeStepY; + +}; \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeLocalization.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeLocalization.h new file mode 100644 index 00000000..8b812f85 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeLocalization.h @@ -0,0 +1,33 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#pragma once + diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h new file mode 100644 index 00000000..0a770dd2 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h @@ -0,0 +1,494 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +/** Unreal Editor headers. **/ +#if WITH_EDITOR +#include "CoreMinimal.h" +#include "ObjectTools.h" +#include "AssetTypeActions_Base.h" +#include "ComponentAssetBroker.h" +#include "PackageTools.h" +#include "ThumbnailHelpers.h" +#include "LevelEditor.h" +#include "Interfaces/IMainFrameModule.h" +#include "ClassIconFinder.h" +#include "ScopedTransaction.h" +#include "RawMesh.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "Interfaces/IPluginManager.h" +#include "ISettingsModule.h" +#include "DesktopPlatformModule.h" +#include "Editor.h" +#endif + +/** Other Unreal headers. **/ +//#include "CoreUObject.h" +#include "Modules/ModuleManager.h" +#include "EngineModule.h" +#include "Engine/TextureDefines.h" +#include "Engine/EngineTypes.h" +#include "MaterialShared.h" +#include "CollisionQueryParams.h" +#include "Engine/CollisionProfile.h" +#include "PhysicsEngine/BodyInstance.h" +#include "PhysicsEngine/BodySetup.h" +#include "AssetData.h" +#include "AssetRegistryModule.h" +#include "HitProxies.h" +#include "Components.h" +#include "ComponentReregisterContext.h" +#include "Engine/StaticMesh.h" +#include "LandscapeProxy.h" +#include "LandscapeDataAccess.h" +#include "Engine/Level.h" +#include "Curves/CurveBase.h" +#include "Curves/CurveFloat.h" +#include "Curves/CurveLinearColor.h" +#include "ImageUtils.h" +#include "Internationalization/Internationalization.h" + +/** Houdini Engine Runtime Module Localization. **/ +#include "HoudiniEngineRuntimeLocalization.h" + +/** Plugin version enum and CustomVersionRegistration */ +#include "HoudiniPluginSerializationVersion.h" + +/** Houdini Engine headers. **/ +#include +#include +#include + +#include "HAPI.h" +#include "HAPI_Version.h" + +/** Whether to enable logging. **/ +#define HOUDINI_ENGINE_LOGGING 1 + +/** Define module names. **/ +#define HOUDINI_MODULE_EDITOR "HoudiniEngineEditor" +#define HOUDINI_MODULE_RUNTIME "HoudiniEngine" + +/** See whether we are in Editor or Engine module. **/ +#ifdef HOUDINI_ENGINE_EDITOR + #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_EDITOR + DECLARE_LOG_CATEGORY_EXTERN( LogHoudiniEngineEditor, Log, All ); +#else +#define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_RUNTIME + DECLARE_LOG_CATEGORY_EXTERN( LogHoudiniEngine, Log, All ); +#endif + +/** Definitions coming from UBT. **/ +#ifndef HOUDINI_ENGINE_HFS_PATH_DEFINE +#define HOUDINI_ENGINE_HFS_PATH "" +#else +#define HOUDINI_ENGINE_STRINGIFY_HELPER(X) #X +#define HOUDINI_ENGINE_STRINGIFY(X) HOUDINI_ENGINE_STRINGIFY_HELPER(X) +#define HOUDINI_ENGINE_HFS_PATH HOUDINI_ENGINE_STRINGIFY(HOUDINI_ENGINE_HFS_PATH_DEFINE) +#endif + +/** Some additional logging macros. **/ +#ifdef HOUDINI_ENGINE_LOGGING + +#ifdef HOUDINI_ENGINE_EDITOR +#define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngineEditor, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) +#else +#define HOUDINI_LOG_HELPER( VERBOSITY, HOUDINI_LOG_TEXT, ... ) \ + do \ + { \ + UE_LOG( LogHoudiniEngine, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) +#endif + +#define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + +#define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + +#define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + +#define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + +#define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Display, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + +#else + +#define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) +#define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) +#define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) +#define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) +#define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) + +#endif // HOUDINI_ENGINE_LOGGING + + +/** Error checking - this macro will check the status and return specified parameter. **/ +#define HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HAPI_LOG_ROUTINE ) \ + do \ + { \ + HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ + if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ + { \ + HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ + return HAPI_PARAM_RETURN; \ + } \ + } \ + while ( 0 ) + +#define HOUDINI_CHECK_ERROR_RETURN( HAPI_PARAM_CALL, HAPI_PARAM_RETURN ) \ + HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HOUDINI_LOG_ERROR ) + +/* Error checking - this macro will check the status. **/ +#define HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ + do \ + { \ + *HAPI_PARAM_RESULT = HAPI_PARAM_CALL; \ + if ( *HAPI_PARAM_RESULT != HAPI_RESULT_SUCCESS ) \ + { \ + HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ + } \ + } \ + while ( 0 ) + +#define HOUDINI_CHECK_ERROR( HAPI_PARAM_RESULT, HAPI_PARAM_CALL ) \ + HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) + +/** HAPI related attribute definitions. **/ + +#define HAPI_UNREAL_CLIENT_NAME "unreal" + +/** Names of attributes used for data exchange between Unreal and Houdini Engine. **/ +#define HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE "unreal_instance" +#define HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES "unreal_split_instances" +#define HAPI_UNREAL_ATTRIB_MATERIAL "unreal_material" +#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE "unreal_material_hole" +#define HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK "unreal_face_material" +#define HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE "unreal_material_instance" +#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE "unreal_material_hole_instance" +#define HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK "unreal_face_smoothing_mask" +#define HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION "unreal_lightmap_resolution" +#define HAPI_UNREAL_ATTRIB_GENERATED_MESH_NAME "unreal_generated_mesh_name" +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME HAPI_ATTRIB_NAME +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX "unreal_vertex_index" +//#define HAPI_UNREAL_ATTRIB_LANDSCAPE_NAME "unreal_landscape" +#define HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME "unreal_input_mesh_name" +#define HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE "unreal_input_source_file" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX "mesh_socket" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME "mesh_socket_name" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD "unreal_mesh_socket_name" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR "mesh_socket_actor" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD "unreal_mesh_socket_actor" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG "mesh_socket_tag" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD "unreal_mesh_socket_tag" + +#define HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX "unreal_uproperty_" +#define HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX "unreal_material_parameter_" +#define HAPI_UNREAL_ATTRIB_INSTANCE_COLOR "unreal_instance_color" + +#define HAPI_UNREAL_ATTRIB_BAKE_FOLDER "unreal_bake_folder" +#define HAPI_UNREAL_ATTRIB_BAKE_NAME "unreal_bake_name" + +/** Names of other Houdini Engine attributes and parameters. **/ +#define HAPI_UNREAL_ATTRIB_INSTANCE "instance" +#define HAPI_UNREAL_ATTRIB_INSTANCE_ROTATION "rot" +#define HAPI_UNREAL_ATTRIB_INSTANCE_SCALE "scale" +#define HAPI_UNREAL_ATTRIB_INSTANCE_POSITION HAPI_ATTRIB_POSITION +#define HAPI_UNREAL_ATTRIB_POSITION HAPI_ATTRIB_POSITION +#define HAPI_UNREAL_ATTRIB_ROTATION "rot" +#define HAPI_UNREAL_ATTRIB_SCALE "scale" +#define HAPI_UNREAL_ATTRIB_UNIFORM_SCALE "pscale" +#define HAPI_UNREAL_ATTRIB_COLOR HAPI_ATTRIB_COLOR +#define HAPI_UNREAL_ATTRIB_ALPHA "Alpha" +#define HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR "unreal_lightmap_color" +#define HAPI_UNREAL_ATTRIB_NORMAL HAPI_ATTRIB_NORMAL +#define HAPI_UNREAL_ATTRIB_TANGENTU HAPI_ATTRIB_TANGENT +#define HAPI_UNREAL_ATTRIB_TANGENTV HAPI_ATTRIB_TANGENT2 + +#define HAPI_UNREAL_ATTRIB_UV HAPI_ATTRIB_UV +#define HAPI_UNREAL_ATTRIB_UV2 HAPI_ATTRIB_UV2 +//#define HAPI_UNREAL_ATTRIB_UV_WEIGHTMAP "unreal_weightmap_uv" + +#define HAPI_UNREAL_PARAM_CURVE_TYPE "type" +#define HAPI_UNREAL_PARAM_CURVE_METHOD "method" +#define HAPI_UNREAL_PARAM_CURVE_COORDS "coords" +#define HAPI_UNREAL_PARAM_CURVE_CLOSED "close" + +#define HAPI_UNREAL_PARAM_TRANSLATE "t" +#define HAPI_UNREAL_PARAM_ROTATE "r" +#define HAPI_UNREAL_PARAM_SCALE "s" +#define HAPI_UNREAL_PARAM_PIVOT "p" +#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" + +#define HAPI_UNREAL_PARAM_FILE "file" + +/** Handle types. **/ +#define HAPI_UNREAL_HANDLE_TRANSFORM "xform" +#define HAPI_UNREAL_HANDLE_BOUNDER "bound" + +/** Ramp related defines. **/ +#define HAPI_UNREAL_RAMP_FLOAT_AXIS_X "position" +#define HAPI_UNREAL_RAMP_FLOAT_AXIS_Y "value" +#define HAPI_UNREAL_RAMP_COLOR_AXIS_X "position" +#define HAPI_UNREAL_RAMP_COLOR_AXIS_Y "color" + +/** Ramp Key interpolation values. **/ +#define HAPI_UNREAL_RAMP_KEY_INTERPOLATION_CONSTANT "Constant" +#define HAPI_UNREAL_RAMP_KEY_INTERPOLATION_LINEAR "Linear" +#define HAPI_UNREAL_RAMP_KEY_INTERPOLATION_CATMULL_ROM "Catmull-Rom" +#define HAPI_UNREAL_RAMP_KEY_INTERPOLATION_MONOTONE_CUBIC "Monotone Cubic" +#define HAPI_UNREAL_RAMP_KEY_INTERPOLATION_BEZIER "Bezier" +#define HAPI_UNREAL_RAMP_KEY_INTERPOLATION_B_SPLINE "B-Spline" +#define HAPI_UNREAL_RAMP_KEY_INTERPOLATION_HERMITE "Hermite" + +/** Texture planes. **/ +#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA "C A" +#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR "C" +#define HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA "A" +#define HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL "N" + +/** Materials Diffuse. **/ +#define HAPI_UNREAL_PARAM_TEXTURE_LAYERS_NUM "ogl_numtex" + +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_0 "ogl_tex1" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_1 "basecolor_texture" + +#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0 "ogl_diff" +#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1 "basecolor" + +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_COLOR_SPACE "basecolor_textureColorSpace" + +/** Materials Normal. **/ +#define HAPI_UNREAL_PARAM_MAP_NORMAL_0 "ogl_normalmap" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_1 "normalTexture" + +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE "ogl_normalmap_type" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT "Tangent Space" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD "World Space" + +#define HAPI_UNREAL_PARAM_MAP_NORMAL_COLOR_SPACE "normalTexColorSpace" + +/** Materials Specular. **/ +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_0 "ogl_specmap" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_1 "reflect_texture" + +#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_0 "ogl_spec" +#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_1 "reflect" + +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_COLOR_SPACE "reflect_textureColorSpace" + +/** Materials Roughness. **/ +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0 "ogl_roughmap" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1 "rough_texture" + +#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0 "ogl_rough" +#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1 "rough" + +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_COLOR_SPACE "rough_textureColorSpace" + +/** Materials Metallic. **/ +#define HAPI_UNREAL_PARAM_MAP_METALLIC "metallic_texture" +#define HAPI_UNREAL_PARAM_VALUE_METALLIC "metallic" +#define HAPI_UNREAL_PARAM_MAP_METALLIC_COLOR_SPACE "metallic_textureColorSpace" + +/** Materials Emissive. **/ +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE "emitcolor_texture" +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_COLOR_SPACE "emitcolor_textureColorSpace" + +#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0 "ogl_emit" +#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1 "emitcolor" + +/** Materials Opacity. **/ +#define HAPI_UNREAL_PARAM_ALPHA_0 "ogl_alpha" +#define HAPI_UNREAL_PARAM_ALPHA_1 "opac" + +#define HAPI_UNREAL_PARAM_MAP_OPACITY_0 "ogl_opacitymap" +#define HAPI_UNREAL_PARAM_MAP_OPACITY_1 "opaccolor_texture" + +/** Default values for new curves. **/ +#define HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT "0.0, 0.0, 3.0 3.0, 0.0, 3.0" +#define HAPI_UNREAL_PARAM_SPLINE_RESOLUTION_DEFAULT 50.0f + +/** Default values for certain UI min and max parameter values **/ +#define HAPI_UNREAL_PARAM_INT_UI_MIN 0 +#define HAPI_UNREAL_PARAM_INT_UI_MAX 10 +#define HAPI_UNREAL_PARAM_FLOAT_UI_MIN 0.0f +#define HAPI_UNREAL_PARAM_FLOAT_UI_MAX 10.0f + +/** Suffix for all Unreal materials which are generated from Houdini. **/ +#define HAPI_UNREAL_GENERATED_MATERIAL_SUFFIX TEXT("_houdini_material") + +/** Group name prefix used for collision geometry generation. **/ +#define HAPI_UNREAL_GROUP_GEOMETRY_COLLISION "collision_geo" + +/** Group name prefix used for rendered collision geometry generation. **/ +#define HAPI_UNREAL_GROUP_GEOMETRY_RENDERED_COLLISION "rendered_collision_geo" + +/** Group name prefix used for UCX collision geometry generation **/ +#define HAPI_UNREAL_GROUP_GEOMETRY_COLLISION_UCX "collision_geo_ucx" +#define HAPI_UNREAL_GROUP_GEOMETRY_RENDERED_COLLISION_UCX "rendered_collision_geo_ucx" + +#define HAPI_UNREAL_GROUP_GEOMETRY_SIMPLE_COLLISION "collision_geo_simple" +#define HAPI_UNREAL_GROUP_GEOMETRY_SIMPLE_RENDERED_COLLISION "rendered_collision_geo_simple" + +/** Group name used to mark everything that is not a member of collision or rendered collision group. **/ +#define HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION "main_geo" + +/** Group name prefix used to mark mesh sockets **/ +#define HAPI_UNREAL_GROUP_MESH_SOCKETS "mesh_socket" +#define HAPI_UNREAL_GROUP_MESH_SOCKETS_OLD "socket" + +/** Details panel desired sizes. **/ +#define HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH 270 +#define HAPI_UNREAL_DESIRED_ROW_FULL_WIDGET_WIDTH 310 +#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH 350 +#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_FULL_WIDGET_WIDTH 400 + +/** Various variable names used to store meta information in generated packages. **/ +#define HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT TEXT( "HoudiniGeneratedObject" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_NAME TEXT( "HoudiniGeneratedName" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE TEXT( "HoudiniGeneratedTextureType" ) +#define HAPI_UNREAL_PACKAGE_META_NODE_PATH TEXT( "HoudiniNodePath" ) + +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL TEXT( "N" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE TEXT( "C_A" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR TEXT( "S" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS TEXT( "R" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC TEXT( "M" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE TEXT( "E" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK TEXT( "O" ) + +/** Various session related settings. **/ +#define HAPI_UNREAL_SESSION_SERVER_HOST TEXT( "localhost" ) +#define HAPI_UNREAL_SESSION_SERVER_PORT 9090 + +#if PLATFORM_MAC +#define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "/tmp/hapi" ) +#else +#define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "hapi" ) +#endif + +#define HAPI_UNREAL_SESSION_SERVER_AUTOSTART true +#define HAPI_UNREAL_SESSION_SERVER_TIMEOUT 3000.0f + +/** Default position and transformation scaling options. **/ +#define HAPI_UNREAL_SCALE_FACTOR_POSITION 100.0f +#define HAPI_UNREAL_SCALE_FACTOR_TRANSLATION 100.0f + +/** Small value used for comparisons. **/ +#define HAPI_UNREAL_SCALE_SMALL_VALUE KINDA_SMALL_NUMBER * 2.0f + +/** Default material name. **/ +#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) + +/** Threshold alpha. **/ +#define HAPI_UNREAL_ALPHA_THRESHOLD 0.95f + +/** Defines used for Substance processing. **/ +#define HAPI_UNREAL_PARAM_SUBSTANCE_PREFIX TEXT( "_substanceInput" ) +#define HAPI_UNREAL_PARAM_SUBSTANCE_LABEL TEXT( "Substance" ) +#define HAPI_UNREAL_PARAM_SUBSTANCE_FILENAME TEXT( "filename" ) + +/** Names for Substance classes which we are retrieving through RTTI. **/ +#define HAPI_UNREAL_SUBSTANCE_CLASS_INSTANCE_FACTORY TEXT( "SubstanceInstanceFactory" ) +#define HAPI_UNREAL_SUBSTANCE_CLASS_GRAPH_INSTANCE TEXT( "SubstanceGraphInstance" ) +#define HAPI_UNREAL_SUBSTANCE_CLASS_UTILITY TEXT( "SubstanceUtility" ) + +/** Names of Substance class properties we are using. **/ +#define HAPI_UNREAL_SUBSTANCE_PROPERTY_FACTORY_PARENT TEXT( "Parent" ) + +/** Names of HAPI libraries on different platforms. **/ +#define HAPI_LIB_OBJECT_WINDOWS TEXT( "libHAPIL.dll" ) +#define HAPI_LIB_OBJECT_MAC TEXT( "libHAPIL.dylib" ) +#define HAPI_LIB_OBJECT_LINUX TEXT( "libHAPIL.so" ) + +/** HFS subfolder containing HAPI lib. **/ +#define HAPI_HFS_SUBFOLDER_WINDOWS TEXT( "bin" ) +#define HAPI_HFS_SUBFOLDER_MAC TEXT( "dsolib" ) +#define HAPI_HFS_SUBFOLDER_LINUX TEXT( "dsolib" ) + +/** Unreal HAPI Resources. **/ +#define HAPI_UNREAL_RESOURCE_HOUDINI_LOGO TEXT( "/HoudiniEngine/houdini_logo.houdini_logo" ) +#define HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL TEXT( "/HoudiniEngine/houdini_default_material.houdini_default_material" ) +#define HAPI_UNREAL_RESOURCE_BGEO_IMPORT TEXT( "/HoudiniEngine/houdini_bgeo_import.houdini_bgeo_import" ) + +/** Helper function to serialize enumerations. **/ +template < typename TEnum > +FORCEINLINE FArchive & +SerializeEnumeration( FArchive & Ar, TEnum & E ) +{ + uint8 B = (uint8) E; + Ar << B; + + if ( Ar.IsLoading() ) + E = (TEnum) B; + + return Ar; +} + +/** Struct to enable global silent flag - this will force dialogs to not show up. **/ +struct FHoudiniScopedGlobalSilence +{ + FHoudiniScopedGlobalSilence() + { + bGlobalSilent = GIsSilent; + GIsSilent = true; + } + + ~FHoudiniScopedGlobalSilence() + { + GIsSilent = bGlobalSilent; + } + + bool bGlobalSilent; +}; + +/** Struct to disable transactional buffer serialization. This is used to avoid including undo reference count. **/ +struct FHoudiniScopedGlobalTransactionDisable +{ + FHoudiniScopedGlobalTransactionDisable() + { +#if WITH_EDITOR + if ( GEditor && GEditor->Trans ) + GEditor->Trans->DisableObjectSerialization(); +#endif + } + + ~FHoudiniScopedGlobalTransactionDisable() + { +#if WITH_EDITOR + if (GEditor && GEditor->Trans) + GEditor->Trans->EnableObjectSerialization(); +#endif + } +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineScheduler.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineScheduler.cpp new file mode 100644 index 00000000..787f4dd2 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineScheduler.cpp @@ -0,0 +1,542 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniEngineScheduler.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniEngineString.h" +#include "Misc/ScopeLock.h" + +const uint32 +FHoudiniEngineScheduler::InitialTaskSize = 256u; + +const float +FHoudiniEngineScheduler::UpdateFrequency = 0.1f; + +FHoudiniEngineScheduler::FHoudiniEngineScheduler() + : Tasks( nullptr ) + , PositionWrite( 0u ) + , PositionRead( 0u ) + , bStopping( false ) +{ + // Make sure size is power of two. + TaskCount = FPlatformMath::RoundUpToPowerOfTwo( FHoudiniEngineScheduler::InitialTaskSize ); + + if ( TaskCount ) + { + // Allocate buffer to store all tasks. + Tasks = static_cast< FHoudiniEngineTask * >( FMemory::Malloc( TaskCount * sizeof( FHoudiniEngineTask ) ) ); + + if ( Tasks ) + { + // Zero memory. + FMemory::Memset( Tasks, 0x0, TaskCount * sizeof( FHoudiniEngineTask ) ); + } + } +} + +FHoudiniEngineScheduler::~FHoudiniEngineScheduler() +{ + if ( TaskCount ) + { + FMemory::Free( Tasks ); + Tasks = nullptr; + } +} + +void +FHoudiniEngineScheduler::TaskDescription( + FHoudiniEngineTaskInfo & TaskInfo, + const FString & ActorName, + const FString & StatusString ) +{ + FFormatNamedArguments Args; + + if ( !ActorName.IsEmpty() ) + { + Args.Add( TEXT( "AssetName" ), FText::FromString( ActorName ) ); + Args.Add( TEXT( "AssetStatus" ), FText::FromString( StatusString ) ); + TaskInfo.StatusText = + FText::Format( NSLOCTEXT( "TaskDescription", "TaskDescriptionProgress", "({AssetName}) : ({AssetStatus})"), Args ); + } + else + { + Args.Add( TEXT( "AssetStatus" ), FText::FromString( StatusString ) ); + TaskInfo.StatusText = + FText::Format( NSLOCTEXT( "TaskDescription", "TaskDescriptionProgress", "({AssetStatus})"), Args ); + } +} + +void +FHoudiniEngineScheduler::TaskInstantiateAsset( const FHoudiniEngineTask & Task ) +{ + FString AssetN; + FHoudiniEngineString( Task.AssetHapiName ).ToFString( AssetN ); + + HOUDINI_LOG_MESSAGE( + TEXT( "HAPI Asynchronous Instantiation Started for %s: Asset=%s, HoudiniAsset = 0x%x" ), + *Task.ActorName, *AssetN, Task.Asset.Get() ); + + if ( !FHoudiniEngineUtils::IsInitialized() ) + { + HOUDINI_LOG_ERROR( + TEXT( "TaskInstantiateAsset failed for %s: %s" ), + *Task.ActorName, + *FHoudiniEngineUtils::GetErrorDescription( HAPI_RESULT_NOT_INITIALIZED ) ); + + AddResponseMessageTaskInfo( + HAPI_RESULT_NOT_INITIALIZED, EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedInstantiationWithErrors, + -1, Task, TEXT( "HAPI is not initialized." ) ); + + return; + } + + if ( !Task.Asset.IsValid() ) + { + // Asset is no longer valid, return. + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedInstantiationWithErrors, + -1, Task, TEXT( "Asset is no longer valid." ) ); + + return; + } + + if ( Task.AssetHapiName < 0 ) + { + // Asset is no longer valid, return. + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedInstantiationWithErrors, + -1, Task, TEXT( "Asset name is invalid." ) ); + + return; + } + + HAPI_Result Result = HAPI_RESULT_SUCCESS; + int32 AssetCount = 0; + HAPI_NodeId AssetId = -1; + std::string AssetNameString; + double LastUpdateTime; + + FHoudiniEngineString HoudiniEngineString( Task.AssetHapiName ); + if ( HoudiniEngineString.ToStdString( AssetNameString ) ) + { + // Translate asset name into Unreal string. + FString AssetName = ANSI_TO_TCHAR( AssetNameString.c_str() ); + + // Initialize last update time. + LastUpdateTime = FPlatformTime::Seconds(); + + // We instantiate without cooking. + Result = FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), -1, &AssetNameString[ 0 ], nullptr, false, &AssetId ); + if ( Result != HAPI_RESULT_SUCCESS ) + { + AddResponseMessageTaskInfo( + Result, EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedInstantiationWithErrors, + -1, Task, TEXT( "Error instantiating asset." ) ); + + return; + } + + // Add processing notification. + FHoudiniEngineTaskInfo TaskInfo( + HAPI_RESULT_SUCCESS, -1, EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::Processing ); + + TaskInfo.bLoadedComponent = Task.bLoadedComponent; + TaskDescription( TaskInfo, Task.ActorName, TEXT( "Started Instantiation" ) ); + FHoudiniEngine::Get().AddTaskInfo( Task.HapiGUID, TaskInfo ); + + // We need to spin until instantiation is finished. + while( true ) + { + int Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR( &Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status ) ); + + if ( Status == HAPI_STATE_READY ) + { + // Cooking has been successful. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedInstantiation, AssetId, Task, + TEXT( "Finished Instantiation." ) ); + + break; + } + else if ( Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS ) + { + // There was an error while instantiating. + FString CookResultString = FHoudiniEngineUtils::GetCookResult(); + int32 CookResult = static_cast(HAPI_RESULT_SUCCESS); + FHoudiniApi::GetStatus( FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_RESULT, &CookResult ); + + AddResponseMessageTaskInfo( + static_cast(CookResult), EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedInstantiationWithErrors, AssetId, Task, + FString::Printf(TEXT( "Finished Instantiation with Errors: %s" ), *CookResultString )); + + break; + } + + static const double NotificationUpdateFrequency = 0.5; + if ( ( FPlatformTime::Seconds() - LastUpdateTime ) >= NotificationUpdateFrequency ) + { + // Reset update time. + LastUpdateTime = FPlatformTime::Seconds(); + + const FString& CookStateMessage = FHoudiniEngineUtils::GetCookState(); + + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::Processing, AssetId, Task, + CookStateMessage ); + } + + // We want to yield. + FPlatformProcess::Sleep( UpdateFrequency ); + } + } + else + { + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedInstantiationWithErrors, + -1, Task, TEXT( "Error retrieving asset name." ) ); + + return; + } +} + +void +FHoudiniEngineScheduler::TaskCookAsset( const FHoudiniEngineTask & Task ) +{ + if ( !FHoudiniEngineUtils::IsInitialized() ) + { + HOUDINI_LOG_ERROR( + TEXT( "TaskCookAsset failed for %s: %s"), + *Task.ActorName, + *FHoudiniEngineUtils::GetErrorDescription( HAPI_RESULT_NOT_INITIALIZED ) ); + + AddResponseMessageTaskInfo( + HAPI_RESULT_NOT_INITIALIZED, EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedCookingWithErrors, + -1, Task, TEXT( "HAPI is not initialized." ) ); + + return; + } + + // Retrieve asset id. + HAPI_NodeId AssetId = Task.AssetId; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + HOUDINI_LOG_MESSAGE( + TEXT( "HAPI Asynchronous Cooking Started for %s., AssetId = %d" ), + *Task.ActorName, AssetId ); + + if ( AssetId == -1 ) + { + // We have an invalid asset id. + HOUDINI_LOG_ERROR( TEXT( "TaskCookAsset failed for %s: Invalid Asset Id." ), *Task.ActorName ); + + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedCookingWithErrors, + -1, Task, TEXT( "Asset has invalid id." ) ); + + return; + } + + Result = FHoudiniApi::CookNode( FHoudiniEngine::Get().GetSession(), AssetId, nullptr ); + if ( Result != HAPI_RESULT_SUCCESS ) + { + AddResponseMessageTaskInfo( + Result, EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedCookingWithErrors, + AssetId, Task, TEXT( "Error cooking asset." ) ); + + return; + } + + // Add processing notification. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Processing, AssetId, Task, TEXT( "Started Cooking" ) ); + + // Initialize last update time. + double LastUpdateTime = FPlatformTime::Seconds(); + + // We need to spin until cooking is finished. + while ( true ) + { + int32 Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR( &Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status ) ); + + if ( Status == HAPI_STATE_READY ) + { + // Cooking has been successful. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedCooking, AssetId, Task, + TEXT( "Finished Cooking" ) ); + + break; + } + else if ( Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS ) + { + // There was an error while instantiating. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedCookingWithErrors, AssetId, Task, + TEXT( "Finished Cooking with Errors" ) ); + + break; + } + + static const double NotificationUpdateFrequency = 0.5; + if ( FPlatformTime::Seconds() - LastUpdateTime >= NotificationUpdateFrequency ) + { + // Reset update time. + LastUpdateTime = FPlatformTime::Seconds(); + + // Retrieve status string. + const FString & CookStateMessage = FHoudiniEngineUtils::GetCookState(); + + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Processing, AssetId, Task, + CookStateMessage ); + } + + // We want to yield. + FPlatformProcess::Sleep( UpdateFrequency ); + } +} + +void +FHoudiniEngineScheduler::TaskDeleteAsset( const FHoudiniEngineTask & Task ) +{ + HOUDINI_LOG_MESSAGE( + TEXT( "HAPI Asynchronous Destruction Started for %s. " ) + TEXT( "AssetId = %d" ), + *Task.ActorName, Task.AssetId ); + + if ( FHoudiniEngineUtils::IsHoudiniNodeValid( Task.AssetId ) ) + FHoudiniEngineUtils::DestroyHoudiniAsset( Task.AssetId ); + + // We do not insert task info as this is a fire and forget operation. + // At this point component most likely does not exist. +} + +void +FHoudiniEngineScheduler::AddResponseTaskInfo( + HAPI_Result Result, EHoudiniEngineTaskType::Type TaskType, EHoudiniEngineTaskState::Type TaskState, + HAPI_NodeId AssetId, const FHoudiniEngineTask & Task ) +{ + FHoudiniEngineTaskInfo TaskInfo( Result, AssetId, TaskType, TaskState ); + FString StatusString = FHoudiniEngineUtils::GetErrorDescription(); + + TaskInfo.bLoadedComponent = Task.bLoadedComponent; + TaskDescription( TaskInfo, Task.ActorName, StatusString ); + FHoudiniEngine::Get().AddTaskInfo( Task.HapiGUID, TaskInfo ); +} + +void +FHoudiniEngineScheduler::AddResponseMessageTaskInfo( + HAPI_Result Result, EHoudiniEngineTaskType::Type TaskType, EHoudiniEngineTaskState::Type TaskState, + HAPI_NodeId AssetId, const FHoudiniEngineTask & Task, const FString & ErrorMessage ) +{ + FHoudiniEngineTaskInfo TaskInfo( Result, AssetId, TaskType, TaskState ); + + TaskInfo.bLoadedComponent = Task.bLoadedComponent; + TaskDescription( TaskInfo, Task.ActorName, ErrorMessage ); + FHoudiniEngine::Get().AddTaskInfo( Task.HapiGUID, TaskInfo ); +} + +void +FHoudiniEngineScheduler::ProcessQueuedTasks() +{ + while( !bStopping ) + { + while ( true ) + { + FHoudiniEngineTask Task; + + { + FScopeLock ScopeLock( &CriticalSection ); + + // We have no tasks left. + if ( PositionWrite == PositionRead ) + break; + + // Retrieve task. + Task = Tasks[ PositionRead ]; + PositionRead++; + + // Wrap around if required. + PositionRead &= ( TaskCount - 1 ); + } + + bool bTaskProcessed = true; + + switch ( Task.TaskType ) + { + case EHoudiniEngineTaskType::AssetInstantiation: + { + TaskInstantiateAsset( Task ); + break; + } + + case EHoudiniEngineTaskType::AssetCooking: + { + TaskCookAsset( Task ); + break; + } + + case EHoudiniEngineTaskType::AssetDeletion: + { + TaskDeleteAsset( Task ); + break; + } + + default: + { + bTaskProcessed = false; + break; + } + } + + if ( !bTaskProcessed ) + break; + } + + if ( FPlatformProcess::SupportsMultithreading() ) + { + // We want to yield for a bit. + FPlatformProcess::Sleep( UpdateFrequency ); + } + else + { + // If we are running in single threaded mode, return so we don't block everything else. + return; + } + } +} + +void +FHoudiniEngineScheduler::AddTask( const FHoudiniEngineTask & Task ) +{ + FScopeLock ScopeLock( &CriticalSection ); + + // Check if we need to grow our circular buffer. + if ( PositionWrite + 1 == PositionRead ) + { + // Calculate next size (next power of two). + uint32 NextTaskCount = FPlatformMath::RoundUpToPowerOfTwo( TaskCount + 1 ); + + // Allocate new buffer. + FHoudiniEngineTask * Buffer = static_cast< FHoudiniEngineTask * >( + FMemory::Malloc( NextTaskCount * sizeof( FHoudiniEngineTask ) ) ); + + if( !Buffer ) + return; + + // Zero memory. + FMemory::Memset( Buffer, 0x0, NextTaskCount * sizeof( FHoudiniEngineTask ) ); + + // Copy elements from old buffer to new one. + if ( PositionRead < PositionWrite ) + { + FMemory::Memcpy( Buffer, Tasks + PositionRead, sizeof( FHoudiniEngineTask ) * ( PositionWrite - PositionRead ) ); + + // Update index positions. + PositionRead = 0; + PositionWrite = PositionWrite - PositionRead; + } + else + { + FMemory::Memcpy( Buffer, Tasks + PositionRead, sizeof( FHoudiniEngineTask ) * ( TaskCount - PositionRead ) ); + FMemory::Memcpy( Buffer + TaskCount - PositionRead, Tasks, sizeof( FHoudiniEngineTask ) * PositionWrite ); + + // Update index positions. + PositionRead = 0; + PositionWrite = TaskCount - PositionRead + PositionWrite; + } + + // Deallocate old buffer. + FMemory::Free( Tasks ); + + // Bookkeeping. + Tasks = Buffer; + TaskCount = NextTaskCount; + } + + // Store task. + Tasks[ PositionWrite ] = Task; + PositionWrite++; + + // Wrap around if required. + PositionWrite &= ( TaskCount - 1 ); +} + +uint32 +FHoudiniEngineScheduler::Run() +{ + ProcessQueuedTasks(); + return 0; +} + +void +FHoudiniEngineScheduler::Stop() +{ + bStopping = true; +} + +void +FHoudiniEngineScheduler::Tick() +{ + ProcessQueuedTasks(); +} + +FSingleThreadRunnable * +FHoudiniEngineScheduler::GetSingleThreadInterface() +{ + return this; +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineScheduler.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineScheduler.h new file mode 100644 index 00000000..5538fb8e --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineScheduler.h @@ -0,0 +1,120 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#pragma once + +#include "HoudiniEngineTask.h" +#include "HoudiniEngineTaskInfo.h" +#include "HAL/Runnable.h" +#include "HAL/RunnableThread.h" +#include "Misc/SingleThreadRunnable.h" + + +class FHoudiniEngineScheduler : public FRunnable, FSingleThreadRunnable +{ + public: + + FHoudiniEngineScheduler(); + virtual ~FHoudiniEngineScheduler(); + + /** FRunnable methods. **/ + public: + + virtual uint32 Run() override; + virtual void Stop() override; + FSingleThreadRunnable * GetSingleThreadInterface() override; + + /** FSingleThreadRunnable methods. **/ + public: + + virtual void Tick() override; + + public: + + /** Add a task. **/ + void AddTask( const FHoudiniEngineTask & Task ); + + /** Add instantiation response task info. **/ + void AddResponseTaskInfo( + HAPI_Result Result, EHoudiniEngineTaskType::Type TaskType, + EHoudiniEngineTaskState::Type TaskState, + HAPI_NodeId AssetId, const FHoudiniEngineTask & Task ); + + void AddResponseMessageTaskInfo( + HAPI_Result Result, EHoudiniEngineTaskType::Type TaskType, + EHoudiniEngineTaskState::Type TaskState, HAPI_NodeId AssetId, const FHoudiniEngineTask & Task, + const FString & ErrorMessage ); + + protected: + + /** Process queued tasks. **/ + void ProcessQueuedTasks(); + + /** Task : instantiate an asset. **/ + void TaskInstantiateAsset( const FHoudiniEngineTask & Task ); + + /** Task : cook an asset. **/ + void TaskCookAsset( const FHoudiniEngineTask & Task ); + + /** Create description of task's state. **/ + void TaskDescription( FHoudiniEngineTaskInfo & Task, const FString & ActorName, const FString & StatusString ); + + /** Delete an asset. **/ + void TaskDeleteAsset( const FHoudiniEngineTask & Task ); + + protected: + + /** Initial number of tasks in our circular queue. **/ + static const uint32 InitialTaskSize; + + protected: + + /** Synchronization primitive. **/ + FCriticalSection CriticalSection; + + /** List of scheduled tasks. **/ + FHoudiniEngineTask* Tasks; + + /** Head of the circular queue. **/ + uint32 PositionWrite; + + /** Tail of the circular queue. **/ + uint32 PositionRead; + + /** Size of the circular queue. **/ + uint32 TaskCount; + + /** Stopping flag. **/ + bool bStopping; + + // Frequency update (sleep time between each update) + static const float UpdateFrequency; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineString.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineString.cpp new file mode 100644 index 00000000..7179a1f3 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineString.cpp @@ -0,0 +1,156 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniEngineString.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngine.h" + +#include + +FHoudiniEngineString::FHoudiniEngineString() + : StringId( -1 ) +{} + +FHoudiniEngineString::FHoudiniEngineString( int32 InStringId ) + : StringId( InStringId ) +{} + +FHoudiniEngineString::FHoudiniEngineString( const FHoudiniEngineString & Other ) + : StringId( Other.StringId ) +{} + +FHoudiniEngineString & +FHoudiniEngineString::operator=( const FHoudiniEngineString & Other ) +{ + if ( this != &Other ) + StringId = Other.StringId; + + return *this; +} + +bool +FHoudiniEngineString::operator==( const FHoudiniEngineString & Other ) const +{ + return Other.StringId == StringId; +} + +bool +FHoudiniEngineString::operator!=( const FHoudiniEngineString & Other ) const +{ + return Other.StringId != StringId; +} + +int32 +FHoudiniEngineString::GetId() const +{ + return StringId; +} + +bool +FHoudiniEngineString::HasValidId() const +{ + return StringId > 0; +} + +bool +FHoudiniEngineString::ToStdString( std::string & String ) const +{ + String = ""; + + if ( StringId >= 0 ) + { + int32 NameLength = 0; + if ( FHoudiniApi::GetStringBufLength( + FHoudiniEngine::Get().GetSession(), StringId, &NameLength ) == HAPI_RESULT_SUCCESS ) + { + if ( NameLength ) + { + std::vector< char > NameBuffer( NameLength, '\0' ); + if ( FHoudiniApi::GetString( + FHoudiniEngine::Get().GetSession(), StringId, + &NameBuffer[ 0 ], NameLength ) == HAPI_RESULT_SUCCESS ) + { + String = std::string( NameBuffer.begin(), NameBuffer.end() ); + return true; + } + } + } + } + + return false; +} + +bool +FHoudiniEngineString::ToFName( FName & Name ) const +{ + Name = NAME_None; + FString NameString = TEXT( "" ); + if ( ToFString( NameString ) ) + { + Name = FName( *NameString ); + return true; + } + + return false; +} + +bool +FHoudiniEngineString::ToFString( FString & String ) const +{ + String = TEXT( "" ); + std::string NamePlain = ""; + + if ( ToStdString( NamePlain ) ) + { + String = UTF8_TO_TCHAR( NamePlain.c_str() ); + return true; + } + + return false; +} + +bool +FHoudiniEngineString::ToFText( FText & Text ) const +{ + Text = FText::GetEmpty(); + FString NameString = TEXT( "" ); + + if ( ToFString( NameString ) ) + { + Text = FText::FromString( NameString ); + return true; + } + + return false; +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineSubstance.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineSubstance.cpp new file mode 100644 index 00000000..3783f341 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineSubstance.cpp @@ -0,0 +1,201 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniEngineSubstance.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" + +bool +FHoudiniEngineSubstance::GetSubstanceMaterialName( + const HAPI_MaterialInfo & MaterialInfo, + FString & SubstanceMaterialName ) +{ + SubstanceMaterialName = TEXT( "" ); + + // Get the node info + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), MaterialInfo.nodeId, &NodeInfo ), false ); + + // Get the parm infos + TArray< HAPI_ParmInfo > ParmInfos; + if ( NodeInfo.parmCount > 0 ) + { + ParmInfos.SetNumZeroed(NodeInfo.parmCount); + if (FHoudiniApi::GetParameters( FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + } + + // Look for the substance filename parameter on the material node + for (int32 Idx = 0, Num = ParmInfos.Num(); Idx < Num; ++Idx) + { + // Get the param name + FHoudiniEngineString HoudiniEngineString( ParmInfos[ Idx ].nameSH ); + FString ParamName; + HoudiniEngineString.ToFString( ParamName ); + + // Check the param name + if ( !ParamName.Equals( HAPI_UNREAL_PARAM_SUBSTANCE_FILENAME ) ) + continue; + + // Check the param is a path parameter + if ( ParmInfos[ Idx ].type < HAPI_PARMTYPE_PATH_START || ParmInfos[ Idx ].type > HAPI_PARMTYPE_PATH_END ) + continue; + + // Get the substance filename parameter string value + HAPI_StringHandle StringHandle = -1; + if ( FHoudiniApi::GetParmStringValues( FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, false, &StringHandle, ParmInfos[ Idx ].stringValuesIndex, 1 ) != HAPI_RESULT_SUCCESS ) + { + continue; + } + + FHoudiniEngineString StringValue = FHoudiniEngineString( StringHandle ); + if ( !StringValue.ToFString( SubstanceMaterialName ) ) + continue; + + // We found the substance material name + return true; + } + + return false; +} + +#if WITH_EDITOR + +UObject * +FHoudiniEngineSubstance::LoadSubstanceInstanceFactory( + UClass * InstanceFactoryClass, + const FString & SubstanceMaterialName ) +{ + UObject * SubstanceInstanceFactory = nullptr; + TArray< FAssetData > SubstanceInstaceFactories; + + FAssetRegistryModule & AssetRegistryModule = + FModuleManager::LoadModuleChecked< FAssetRegistryModule >( TEXT( "AssetRegistry" ) ); + AssetRegistryModule.Get().GetAssetsByClass( InstanceFactoryClass->GetFName(), SubstanceInstaceFactories ); + + for ( int32 FactoryIdx = 0, FactoryNum = SubstanceInstaceFactories.Num(); FactoryIdx < FactoryNum; ++FactoryIdx ) + { + const FAssetData & AssetData = SubstanceInstaceFactories[ FactoryIdx ]; + const FString & AssetName = AssetData.AssetName.ToString(); + if ( AssetName.Equals( SubstanceMaterialName ) ) + { + const FString & AssetObjectPath = AssetData.ObjectPath.ToString(); + SubstanceInstanceFactory = StaticLoadObject( + InstanceFactoryClass, nullptr, *AssetObjectPath, nullptr, + LOAD_None, nullptr ); + if ( SubstanceInstanceFactory ) + break; + } + } + + return SubstanceInstanceFactory; +} + +UObject * +FHoudiniEngineSubstance::LoadSubstanceGraphInstance( UClass * GraphInstanceClass, UObject * InstanceFactory ) +{ + UObject * MatchedSubstanceGraphInstance = nullptr; + TArray< FAssetData > SubstanceGraphInstances; + + // Get Substance instance factory pointer property. + FObjectProperty * FactoryParentProperty = + CastField< FObjectProperty >( GraphInstanceClass->FindPropertyByName( HAPI_UNREAL_SUBSTANCE_PROPERTY_FACTORY_PARENT ) ); + if ( !FactoryParentProperty ) + return nullptr; + + FAssetRegistryModule & AssetRegistryModule = + FModuleManager::LoadModuleChecked< FAssetRegistryModule >( TEXT( "AssetRegistry" ) ); + AssetRegistryModule.Get().GetAssetsByClass( GraphInstanceClass->GetFName(), SubstanceGraphInstances ); + + for ( int32 GraphInstanceIdx = 0, GraphInstanceNum = SubstanceGraphInstances.Num(); + GraphInstanceIdx < GraphInstanceNum; ++GraphInstanceIdx ) + { + const FAssetData & AssetData = SubstanceGraphInstances[GraphInstanceIdx]; + const FString & AssetObjectPath = AssetData.ObjectPath.ToString(); + UObject * GraphInstanceObject = + StaticLoadObject( GraphInstanceClass, nullptr, *AssetObjectPath, nullptr, LOAD_None, nullptr ); + if ( GraphInstanceObject ) + { + void * FactoryParentPropertyValue = FactoryParentProperty->ContainerPtrToValuePtr< void >( GraphInstanceObject ); + UObject * InstanceFactoryObject = FactoryParentProperty->GetObjectPropertyValue( FactoryParentPropertyValue ); + if ( InstanceFactory == InstanceFactoryObject ) + { + MatchedSubstanceGraphInstance = GraphInstanceObject; + break; + } + } + } + + return MatchedSubstanceGraphInstance; +} + +#endif + +bool +FHoudiniEngineSubstance::RetrieveSubstanceRTTIClasses( + UClass *& InstanceFactoryClass, + UClass *& GraphInstanceClass, + UClass *& UtilityClass ) +{ + InstanceFactoryClass = nullptr; + GraphInstanceClass = nullptr; + UtilityClass = nullptr; + + UClass * SubstanceInstaceFactoryClass = FindObject< UClass >( + ANY_PACKAGE, HAPI_UNREAL_SUBSTANCE_CLASS_INSTANCE_FACTORY ); + if ( !SubstanceInstaceFactoryClass ) + return false; + + UClass * SubstanceGraphInstanceClass = + FindObject< UClass >( ANY_PACKAGE, HAPI_UNREAL_SUBSTANCE_CLASS_GRAPH_INSTANCE ); + if ( !SubstanceGraphInstanceClass ) + return false; + + UClass * SubstanceUtilityClass = FindObject< UClass >( ANY_PACKAGE, HAPI_UNREAL_SUBSTANCE_CLASS_UTILITY ); + if ( !SubstanceUtilityClass ) + return false; + + InstanceFactoryClass = SubstanceInstaceFactoryClass; + GraphInstanceClass = SubstanceGraphInstanceClass; + UtilityClass = SubstanceUtilityClass; + + return true; +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineSubstance.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineSubstance.h new file mode 100644 index 00000000..064aa3f1 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineSubstance.h @@ -0,0 +1,62 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#pragma once + +class UClass; +class FString; +class UObject; +struct HAPI_MaterialInfo; + + +struct HOUDINIENGINERUNTIME_API FHoudiniEngineSubstance +{ +#if WITH_EDITOR + + public: + + /** Used to locate and load (if found) Substance instance factory object. **/ + static UObject * LoadSubstanceInstanceFactory( UClass * InstanceFactoryClass, const FString & SubstanceMaterialName ); + + /** Used to locate and load (if found) Substance graph instance object. **/ + static UObject * LoadSubstanceGraphInstance( UClass * GraphInstanceClass, UObject * InstanceFactory ); + +#endif // WITH_EDITOR + + /** Retrieve Substance RTTI classes we are interested in. **/ + static bool RetrieveSubstanceRTTIClasses( + UClass *& InstanceFactoryClass, + UClass *& GraphInstanceClass, + UClass *& UtilityClass ); + + /** HAPI: Check if material is a Substance material. If it is, return its name by reference. **/ + static bool GetSubstanceMaterialName( const HAPI_MaterialInfo & MaterialInfo, FString & SubstanceMaterialName ); +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineTask.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineTask.cpp new file mode 100644 index 00000000..fa80fb9a --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineTask.cpp @@ -0,0 +1,55 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniEngineTask.h" + +#include "HoudiniApi.h" + +FHoudiniEngineTask::FHoudiniEngineTask() + : TaskType( EHoudiniEngineTaskType::None ) + , ActorName( TEXT( "" ) ) + , AssetId( -1 ) + , AssetLibraryId( -1 ) + , AssetHapiName( -1 ) + , bLoadedComponent( false ) +{ + HapiGUID.Invalidate(); +} + +FHoudiniEngineTask::FHoudiniEngineTask( EHoudiniEngineTaskType::Type InTaskType, FGuid InHapiGUID ) + : HapiGUID( InHapiGUID ) + , TaskType( InTaskType ) + , ActorName( TEXT( "" ) ) + , AssetId( -1 ) + , AssetLibraryId( -1 ) + , AssetHapiName( -1 ) + , bLoadedComponent( false ) +{} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineTaskInfo.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineTaskInfo.cpp new file mode 100644 index 00000000..775b990d --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineTaskInfo.cpp @@ -0,0 +1,54 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniEngineTaskInfo.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo() + : Result( HAPI_RESULT_SUCCESS ) + , AssetId( -1 ) + , TaskType( EHoudiniEngineTaskType::None ) + , TaskState( EHoudiniEngineTaskState::None ) + , bLoadedComponent( false ) +{} + +FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo( + HAPI_Result InResult, HAPI_NodeId InAssetId, + EHoudiniEngineTaskType::Type InTaskType, + EHoudiniEngineTaskState::Type InTaskState ) + : Result( InResult ) + , AssetId( InAssetId ) + , TaskType( InTaskType ) + , TaskState( InTaskState ) + , bLoadedComponent( false ) +{} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineUtils.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineUtils.cpp new file mode 100644 index 00000000..697bd423 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineUtils.cpp @@ -0,0 +1,10837 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniEngineUtils.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniAssetActor.h" +#include "HoudiniEngine.h" +#include "HoudiniAssetComponentMaterials.h" +#include "HoudiniAsset.h" +#include "HoudiniEngineString.h" +#include "HoudiniAttributeDataComponent.h" +#include "HoudiniLandscapeUtils.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineMaterialUtils.h" +#include "Components/SplineComponent.h" +#include "LandscapeInfo.h" +#include "LandscapeComponent.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" + +#include "CoreMinimal.h" +#include "AI/Navigation/NavCollisionBase.h" +#include "Engine/StaticMeshSocket.h" +#if WITH_EDITOR + #include "Editor.h" + #include "EditorFramework/AssetImportData.h" + #include "Interfaces/ITargetPlatform.h" + #include "Interfaces/ITargetPlatformManagerModule.h" + #include "Editor/UnrealEd/Private/GeomFitUtils.h" + #include "UnrealEd/Private/ConvexDecompTool.h" + #include "PackedNormal.h" + #include "Widgets/Notifications/SNotificationList.h" + #include "Framework/Notifications/NotificationManager.h" +#endif + +#include "EngineUtils.h" +#include "PhysicsEngine/BodySetup.h" +#include "StaticMeshResources.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "Engine/SkeletalMesh.h" +#include "Rendering/SkeletalMeshModel.h" +#include "SkeletalMeshTypes.h" +#include "Misc/Paths.h" +#include "Materials/MaterialInterface.h" +#include "Materials/Material.h" + +#if PLATFORM_WINDOWS + #include "Windows/WindowsHWrapper.h" + + // Of course, Windows defines its own GetGeoInfo, + // So we need to undefine that before including HoudiniApi.h to avoid collision... + #ifdef GetGeoInfo + #undef GetGeoInfo + #endif +#endif + +#include + +#include "HAL/PlatformMisc.h" +#include "HAL/PlatformApplicationMisc.h" + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +DECLARE_CYCLE_STAT( TEXT( "Houdini: Build Static Mesh" ), STAT_BuildStaticMesh, STATGROUP_HoudiniEngine ); + +const FString kResultStringSuccess( TEXT( "Success" ) ); +const FString kResultStringFailure( TEXT( "Generic Failure" ) ); +const FString kResultStringAlreadyInitialized( TEXT( "Already Initialized" ) ); +const FString kResultStringNotInitialized( TEXT( "Not Initialized" ) ); +const FString kResultStringCannotLoadFile( TEXT( "Unable to Load File" ) ); +const FString kResultStringParmSetFailed( TEXT( "Failed Setting Parameter" ) ); +const FString kResultStringInvalidArgument( TEXT( "Invalid Argument" ) ); +const FString kResultStringCannotLoadGeo( TEXT( "Uneable to Load Geometry" ) ); +const FString kResultStringCannotGeneratePreset( TEXT( "Uneable to Generate Preset" ) ); +const FString kResultStringCannotLoadPreset( TEXT( "Uneable to Load Preset" ) ); +const FString kResultStringAssetDefAlrealdyLoaded(TEXT("Asset definition already loaded")); +const FString kResultStringNoLicenseFound(TEXT("No License Found")); +const FString kResultStringDisallowedNCLicenseFound(TEXT("Disallowed Non Commercial License found")); +const FString kResultStringDisallowedNCAssetWithCLicense(TEXT("Disallowed Non Commercial Asset With Commercial License")); +const FString kResultStringDisallowedNCAssetWithLCLicense(TEXT("Disallowed Non Commercial Asset With Limited Commercial License")); +const FString kResultStringDisallowedLCAssetWithCLicense(TEXT("Disallowed Limited Commercial Asset With Commercial License")); +const FString kResultStringDisallowedHengineIndieWith3PartyPlugin(TEXT("Disallowed Houdini Engine Indie With 3rd Party Plugin")); +const FString kResultStringAssetInvalid(TEXT("Invalid Asset")); +const FString kResultStringNodeInvalid(TEXT("Invalid Node")); +const FString kResultStringUserInterrupted(TEXT("User Interrupt")); +const FString kResultStringInvalidSession(TEXT("Invalid Session")); +const FString kResultStringUnknowFailure(TEXT("Unknown Failure")); + +const int32 +FHoudiniEngineUtils::PackageGUIDComponentNameLength = 12; + +const int32 +FHoudiniEngineUtils::PackageGUIDItemNameLength = 8; + +const FString +FHoudiniEngineUtils::GetErrorDescription( HAPI_Result Result ) +{ + if ( Result == HAPI_RESULT_SUCCESS ) + { + return kResultStringSuccess; + } + else + { + switch ( Result ) + { + case HAPI_RESULT_FAILURE: + { + return kResultStringFailure; + } + + case HAPI_RESULT_ALREADY_INITIALIZED: + { + return kResultStringAlreadyInitialized; + } + + case HAPI_RESULT_NOT_INITIALIZED: + { + return kResultStringNotInitialized; + } + + case HAPI_RESULT_CANT_LOADFILE: + { + return kResultStringCannotLoadFile; + } + + case HAPI_RESULT_PARM_SET_FAILED: + { + return kResultStringParmSetFailed; + } + + case HAPI_RESULT_INVALID_ARGUMENT: + { + return kResultStringInvalidArgument; + } + + case HAPI_RESULT_CANT_LOAD_GEO: + { + return kResultStringCannotLoadGeo; + } + + case HAPI_RESULT_CANT_GENERATE_PRESET: + { + return kResultStringCannotGeneratePreset; + } + + case HAPI_RESULT_CANT_LOAD_PRESET: + { + return kResultStringCannotLoadPreset; + } + + case HAPI_RESULT_ASSET_DEF_ALREADY_LOADED: + { + return kResultStringAssetDefAlrealdyLoaded; + } + + case HAPI_RESULT_NO_LICENSE_FOUND: + { + return kResultStringNoLicenseFound; + } + + case HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND: + { + return kResultStringDisallowedNCLicenseFound; + } + + case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE: + { + return kResultStringDisallowedNCAssetWithCLicense; + } + + case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE: + { + return kResultStringDisallowedNCAssetWithLCLicense; + } + + case HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE: + { + return kResultStringDisallowedLCAssetWithCLicense; + } + + case HAPI_RESULT_DISALLOWED_HENGINEINDIE_W_3PARTY_PLUGIN: + { + return kResultStringDisallowedHengineIndieWith3PartyPlugin; + } + + case HAPI_RESULT_ASSET_INVALID: + { + return kResultStringAssetInvalid; + } + + case HAPI_RESULT_NODE_INVALID: + { + return kResultStringNodeInvalid; + } + + case HAPI_RESULT_USER_INTERRUPTED: + { + return kResultStringUserInterrupted; + } + + case HAPI_RESULT_INVALID_SESSION: + { + return kResultStringInvalidSession; + } + + default: + { + return kResultStringUnknowFailure; + } + }; + } +} + +const FString +FHoudiniEngineUtils::GetErrorDescription() +{ + return FHoudiniEngineUtils::GetStatusString( HAPI_STATUS_CALL_RESULT, HAPI_STATUSVERBOSITY_ERRORS ); +} + +const FString +FHoudiniEngineUtils::GetCookState() +{ + return FHoudiniEngineUtils::GetStatusString( HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS ); +} + +const FString +FHoudiniEngineUtils::GetCookResult() +{ + return FHoudiniEngineUtils::GetStatusString( HAPI_STATUS_COOK_RESULT, HAPI_STATUSVERBOSITY_MESSAGES ); +} + +const FString +FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId) +{ + int32 NodeErrorLength = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeNodeCookResult( + FHoudiniEngine::Get().GetSession(), InNodeId, HAPI_StatusVerbosity::HAPI_STATUSVERBOSITY_ALL, &NodeErrorLength)) + { + NodeErrorLength = 0; + } + + FString NodeError; + if (NodeErrorLength > 0) + { + TArray< char > NodeErrorBuffer; + NodeErrorBuffer.SetNumZeroed(NodeErrorLength); + FHoudiniApi::GetComposedNodeCookResult(FHoudiniEngine::Get().GetSession(), &NodeErrorBuffer[0], NodeErrorLength); + + NodeError = FString(UTF8_TO_TCHAR(&NodeErrorBuffer[0])); + } + + return NodeError; +} + +bool +FHoudiniEngineUtils::IsInitialized() +{ + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid( SessionPtr ) ) + return false; + + return ( FHoudiniApi::IsInitialized( SessionPtr ) == HAPI_RESULT_SUCCESS ); +} + +bool +FHoudiniEngineUtils::GetLicenseType( FString & LicenseType ) +{ + LicenseType = TEXT( "" ); + HAPI_License LicenseTypeValue = HAPI_LICENSE_NONE; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetSessionEnvInt( + FHoudiniEngine::Get().GetSession(), HAPI_SESSIONENVINT_LICENSE, + (int32 *) &LicenseTypeValue ), false ); + + switch ( LicenseTypeValue ) + { + case HAPI_LICENSE_NONE: + { + LicenseType = TEXT( "No License Acquired" ); + break; + } + + case HAPI_LICENSE_HOUDINI_ENGINE: + { + LicenseType = TEXT( "Houdini Engine" ); + break; + } + + case HAPI_LICENSE_HOUDINI: + { + LicenseType = TEXT( "Houdini" ); + break; + } + + case HAPI_LICENSE_HOUDINI_FX: + { + LicenseType = TEXT( "Houdini FX" ); + break; + } + + case HAPI_LICENSE_HOUDINI_ENGINE_INDIE: + { + LicenseType = TEXT( "Houdini Engine Indie" ); + break; + } + + case HAPI_LICENSE_HOUDINI_INDIE: + { + LicenseType = TEXT( "Houdini Indie" ); + break; + } + + case HAPI_LICENSE_MAX: + default: + { + return false; + } + } + + return true; +} + +bool +FHoudiniEngineUtils::IsLicenseHoudiniEngineIndie() +{ + HAPI_License LicenseTypeValue = HAPI_LICENSE_NONE; + + if ( FHoudiniApi::GetSessionEnvInt( FHoudiniEngine::Get().GetSession(), + HAPI_SESSIONENVINT_LICENSE, (int32 *) &LicenseTypeValue ) == HAPI_RESULT_SUCCESS ) + { + return HAPI_LICENSE_HOUDINI_ENGINE_INDIE == LicenseTypeValue; + } + + return false; +} + +bool +FHoudiniEngineUtils::ComputeAssetPresetBufferLength( HAPI_NodeId AssetId, int32 & OutBufferLength ) +{ + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + OutBufferLength = 0; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ), false ); + + int32 BufferLength = 0; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPresetBufLength( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, + HAPI_PRESETTYPE_BINARY, NULL, &BufferLength ), false ); + + OutBufferLength = BufferLength; + return true; +} + +bool +FHoudiniEngineUtils::SetAssetPreset( HAPI_NodeId AssetId, const TArray< char > & PresetBuffer ) +{ + if ( PresetBuffer.Num() > 0 ) + { + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetPreset( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, + HAPI_PRESETTYPE_BINARY, NULL, &PresetBuffer[ 0 ], + PresetBuffer.Num() ), false ); + + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::GetAssetPreset( HAPI_NodeId AssetId, TArray< char > & PresetBuffer ) +{ + PresetBuffer.Empty(); + + HAPI_NodeId NodeId; + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo)) + { + NodeId = AssetInfo.nodeId; + } + else + NodeId = AssetId; + + int32 BufferLength = 0; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPresetBufLength( + FHoudiniEngine::Get().GetSession(), NodeId, + HAPI_PRESETTYPE_BINARY, NULL, &BufferLength ), false ); + + PresetBuffer.SetNumZeroed( BufferLength ); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPreset( + FHoudiniEngine::Get().GetSession(), NodeId, + &PresetBuffer[ 0 ], PresetBuffer.Num() ), false ); + + return true; +} + +bool +FHoudiniEngineUtils::IsHoudiniNodeValid( const HAPI_NodeId& NodeId ) +{ + if ( NodeId < 0 ) + return false; + + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + bool ValidationAnswer = 0; + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo ) ) + return false; + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::IsNodeValid( + FHoudiniEngine::Get().GetSession(), NodeId, + NodeInfo.uniqueHoudiniNodeId, &ValidationAnswer ) ) + return false; + + return ValidationAnswer; +} + +bool +FHoudiniEngineUtils::DestroyHoudiniAsset( HAPI_NodeId AssetId ) +{ + return FHoudiniApi::DeleteNode( FHoudiniEngine::Get().GetSession(), AssetId ) == HAPI_RESULT_SUCCESS; +} + +void +FHoudiniEngineUtils::ConvertUnrealString( const FString & UnrealString, std::string & String ) +{ + String = TCHAR_TO_UTF8( *UnrealString ); +} + +void +FHoudiniEngineUtils::TranslateHapiTransform( const HAPI_Transform & HapiTransform, FTransform & UnrealTransform ) +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + float TransformScaleFactor = HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + + if ( HoudiniRuntimeSettings ) + { + TransformScaleFactor = HoudiniRuntimeSettings->TransformScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + } + + if ( ImportAxis == HRSAI_Unreal ) + { + FQuat ObjectRotation( + HapiTransform.rotationQuaternion[ 0 ], HapiTransform.rotationQuaternion[ 1 ], + HapiTransform.rotationQuaternion[ 2 ], -HapiTransform.rotationQuaternion[ 3 ]); + Swap( ObjectRotation.Y, ObjectRotation.Z ); + + FVector ObjectTranslation( HapiTransform.position[ 0 ], HapiTransform.position[ 1 ], HapiTransform.position[ 2 ] ); + ObjectTranslation *= TransformScaleFactor; + Swap( ObjectTranslation[ 2 ], ObjectTranslation[ 1 ] ); + + FVector ObjectScale3D( HapiTransform.scale[ 0 ], HapiTransform.scale[ 1 ], HapiTransform.scale[ 2 ] ); + Swap( ObjectScale3D.Y, ObjectScale3D.Z ); + + UnrealTransform.SetComponents( ObjectRotation, ObjectTranslation, ObjectScale3D ); + } + else if ( ImportAxis == HRSAI_Houdini ) + { + FQuat ObjectRotation( + HapiTransform.rotationQuaternion[ 0 ], HapiTransform.rotationQuaternion[ 1 ], + HapiTransform.rotationQuaternion[ 2 ], HapiTransform.rotationQuaternion[ 3 ] ); + + FVector ObjectTranslation( + HapiTransform.position[ 0 ], HapiTransform.position[ 1 ], HapiTransform.position[ 2 ] ); + ObjectTranslation *= TransformScaleFactor; + + FVector ObjectScale3D( HapiTransform.scale[ 0 ], HapiTransform.scale[ 1 ], HapiTransform.scale[ 2 ] ); + + UnrealTransform.SetComponents( ObjectRotation, ObjectTranslation, ObjectScale3D ); + } + else + { + // Not valid enum value. + check( 0 ); + } +} + +void +FHoudiniEngineUtils::TranslateHapiTransform( const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform ) +{ + float HapiMatrix[ 16 ]; + FHoudiniApi::ConvertTransformEulerToMatrix( FHoudiniEngine::Get().GetSession(), &HapiTransformEuler, HapiMatrix ); + + HAPI_Transform HapiTransformQuat; + FHoudiniApi::Transform_Init(&HapiTransformQuat); + //FMemory::Memzero< HAPI_Transform >( HapiTransformQuat ); + FHoudiniApi::ConvertMatrixToQuat( FHoudiniEngine::Get().GetSession(), HapiMatrix, HAPI_SRT, &HapiTransformQuat ); + + FHoudiniEngineUtils::TranslateHapiTransform( HapiTransformQuat, UnrealTransform ); +} + +void +FHoudiniEngineUtils::TranslateUnrealTransform( const FTransform & UnrealTransform, HAPI_Transform & HapiTransform ) +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + float TransformScaleFactor = HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + + if ( HoudiniRuntimeSettings ) + { + TransformScaleFactor = HoudiniRuntimeSettings->TransformScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + } + + //FMemory::Memzero< HAPI_Transform >( HapiTransform ); + FHoudiniApi::Transform_Init(&HapiTransform); + + HapiTransform.rstOrder = HAPI_SRT; + + FQuat UnrealRotation = UnrealTransform.GetRotation(); + FVector UnrealTranslation = UnrealTransform.GetTranslation(); + FVector UnrealScale = UnrealTransform.GetScale3D(); + + if ( ImportAxis == HRSAI_Unreal ) + { + Swap( UnrealRotation.Y, UnrealRotation.Z ); + HapiTransform.rotationQuaternion[ 0 ] = -UnrealRotation.X; + HapiTransform.rotationQuaternion[ 1 ] = -UnrealRotation.Y; + HapiTransform.rotationQuaternion[ 2 ] = -UnrealRotation.Z; + HapiTransform.rotationQuaternion[ 3 ] = UnrealRotation.W; + + UnrealTranslation /= TransformScaleFactor; + Swap( UnrealTranslation.Y, UnrealTranslation.Z ); + HapiTransform.position[ 0 ] = UnrealTranslation.X; + HapiTransform.position[ 1 ] = UnrealTranslation.Y; + HapiTransform.position[ 2 ] = UnrealTranslation.Z; + + Swap( UnrealScale.Y, UnrealScale.Z ); + HapiTransform.scale[ 0 ] = UnrealScale.X; + HapiTransform.scale[ 1 ] = UnrealScale.Y; + HapiTransform.scale[ 2 ] = UnrealScale.Z; + } + else if ( ImportAxis == HRSAI_Houdini ) + { + HapiTransform.rotationQuaternion[ 0 ] = UnrealRotation.X; + HapiTransform.rotationQuaternion[ 1 ] = UnrealRotation.Y; + HapiTransform.rotationQuaternion[ 2 ] = UnrealRotation.Z; + HapiTransform.rotationQuaternion[ 3 ] = UnrealRotation.W; + + HapiTransform.position[ 0 ] = UnrealTranslation.X; + HapiTransform.position[ 1 ] = UnrealTranslation.Y; + HapiTransform.position[ 2 ] = UnrealTranslation.Z; + + HapiTransform.scale[ 0 ] = UnrealScale.X; + HapiTransform.scale[ 1 ] = UnrealScale.Y; + HapiTransform.scale[ 2 ] = UnrealScale.Z; + } + else + { + // Not valid enum value. + check( 0 ); + } +} + +void +FHoudiniEngineUtils::TranslateUnrealTransform( + const FTransform & UnrealTransform, + HAPI_TransformEuler & HapiTransformEuler ) +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + float TransformScaleFactor = HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + + if ( HoudiniRuntimeSettings ) + { + TransformScaleFactor = HoudiniRuntimeSettings->TransformScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + } + + //FMemory::Memzero< HAPI_TransformEuler >( HapiTransformEuler ); + FHoudiniApi::TransformEuler_Init(&HapiTransformEuler); + + HapiTransformEuler.rstOrder = HAPI_SRT; + HapiTransformEuler.rotationOrder = HAPI_XYZ; + + FQuat UnrealRotation = UnrealTransform.GetRotation(); + FVector UnrealTranslation = UnrealTransform.GetTranslation(); + UnrealTranslation /= TransformScaleFactor; + + FVector UnrealScale = UnrealTransform.GetScale3D(); + + if ( ImportAxis == HRSAI_Unreal ) + { + // switch quat to Y-up, LHR + Swap( UnrealRotation.Y, UnrealRotation.Z ); + UnrealRotation.W = -UnrealRotation.W; + const FRotator Rotator = UnrealRotation.Rotator(); + // negate roll and pitch since they are actually RHR + HapiTransformEuler.rotationEuler[ 0 ] = -Rotator.Roll; + HapiTransformEuler.rotationEuler[ 1 ] = -Rotator.Pitch; + HapiTransformEuler.rotationEuler[ 2 ] = Rotator.Yaw; + + Swap( UnrealTranslation.Y, UnrealTranslation.Z ); + HapiTransformEuler.position[ 0 ] = UnrealTranslation.X; + HapiTransformEuler.position[ 1 ] = UnrealTranslation.Y; + HapiTransformEuler.position[ 2 ] = UnrealTranslation.Z; + + Swap( UnrealScale.Y, UnrealScale.Z ); + HapiTransformEuler.scale[ 0 ] = UnrealScale.X; + HapiTransformEuler.scale[ 1 ] = UnrealScale.Y; + HapiTransformEuler.scale[ 2 ] = UnrealScale.Z; + } + else if ( ImportAxis == HRSAI_Houdini ) + { + const FRotator Rotator = UnrealRotation.Rotator(); + HapiTransformEuler.rotationEuler[ 0 ] = Rotator.Roll; + HapiTransformEuler.rotationEuler[ 1 ] = Rotator.Yaw; + HapiTransformEuler.rotationEuler[ 2 ] = Rotator.Pitch; + + HapiTransformEuler.position[ 0 ] = UnrealTranslation.X; + HapiTransformEuler.position[ 1 ] = UnrealTranslation.Y; + HapiTransformEuler.position[ 2 ] = UnrealTranslation.Z; + + HapiTransformEuler.scale[ 0 ] = UnrealScale.X; + HapiTransformEuler.scale[ 1 ] = UnrealScale.Y; + HapiTransformEuler.scale[ 2 ] = UnrealScale.Z; + } + else + { + // Not valid enum value. + check( 0 ); + } +} + +bool +FHoudiniEngineUtils::SetCurrentTime( float CurrentTime ) +{ + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetTime( + FHoudiniEngine::Get().GetSession(), CurrentTime ), false ); + return true; +} + +bool +FHoudiniEngineUtils::GetHoudiniAssetName( HAPI_NodeId AssetId, FString & NameString ) +{ + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + if ( FHoudiniApi::GetAssetInfo( FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ) == HAPI_RESULT_SUCCESS ) + { + FHoudiniEngineString HoudiniEngineString( AssetInfo.nameSH ); + return HoudiniEngineString.ToFString( NameString ); + } + + return false; +} + +int32 +FHoudiniEngineUtils::HapiGetGroupCountByType( HAPI_GroupType GroupType, HAPI_GeoInfo & GeoInfo ) +{ + switch ( GroupType ) + { + case HAPI_GROUPTYPE_POINT: return GeoInfo.pointGroupCount; + case HAPI_GROUPTYPE_PRIM: return GeoInfo.primitiveGroupCount; + default: break; + } + + return 0; +} + +int32 +FHoudiniEngineUtils::HapiGetElementCountByGroupType( HAPI_GroupType GroupType, HAPI_PartInfo & PartInfo ) +{ + switch ( GroupType ) + { + case HAPI_GROUPTYPE_POINT: return PartInfo.pointCount; + case HAPI_GROUPTYPE_PRIM: return PartInfo.faceCount; + default: break; + } + + return 0; +} + +bool +FHoudiniEngineUtils::HapiGetGroupNames( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, HAPI_PartId PartId, + HAPI_GroupType GroupType, TArray< FString > & GroupNames, const bool& isPackedPrim ) +{ + int32 GroupCount = 0; + if ( !isPackedPrim ) + { + // Get group count on the geo + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), GeoId, &GeoInfo), false); + + GroupCount = FHoudiniEngineUtils::HapiGetGroupCountByType(GroupType, GeoInfo); + } + else + { + // We need the group count for this packed prim + int32 PointGroupCount = 0, PrimGroupCount = 0; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetGroupCountOnPackedInstancePart( FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PointGroupCount, &PrimGroupCount ), false ); + + if ( GroupType == HAPI_GROUPTYPE_POINT ) + GroupCount = PointGroupCount; + else + GroupCount = PrimGroupCount; + } + + if ( GroupCount <= 0 ) + return true; + + std::vector< int32 > GroupNameHandles( GroupCount, 0 ); + if ( !isPackedPrim ) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetGroupNames( + FHoudiniEngine::Get().GetSession(), + GeoId, GroupType, &GroupNameHandles[ 0 ], GroupCount ), false ); + } + else + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetGroupNamesOnPackedInstancePart( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, GroupType, &GroupNameHandles[ 0 ], GroupCount ), false ); + } + + for ( int32 NameIdx = 0; NameIdx < GroupCount; ++NameIdx ) + { + FString GroupName = TEXT( "" ); + FHoudiniEngineString HoudiniEngineString( GroupNameHandles[ NameIdx ] ); + + HoudiniEngineString.ToFString( GroupName ); + GroupNames.Add( GroupName ); + } + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetGroupMembership( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, HAPI_PartId PartId, + HAPI_GroupType GroupType, const FString & GroupName, TArray< int32 > & GroupMembership ) +{ + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PartInfo ), false ); + + int32 ElementCount = FHoudiniEngineUtils::HapiGetElementCountByGroupType( GroupType, PartInfo ); + std::string ConvertedGroupName = TCHAR_TO_UTF8( *GroupName ); + if ( ElementCount < 1 ) + return false; + + GroupMembership.SetNumUninitialized( ElementCount ); + + if ( !PartInfo.isInstanced ) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetGroupMembership( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, GroupType, + ConvertedGroupName.c_str(), NULL, &GroupMembership[ 0 ], 0, ElementCount ), false ); + } + else + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetGroupMembershipOnPackedInstancePart( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, GroupType, + ConvertedGroupName.c_str(), NULL, &GroupMembership[ 0 ], 0, ElementCount ), false ); + } + + return true; +} + +bool +FHoudiniEngineUtils::HapiCheckGroupMembership( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, HAPI_GroupType GroupType, const FString & GroupName ) +{ + return FHoudiniEngineUtils::HapiCheckGroupMembership( + HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId, HoudiniGeoPartObject.GeoId, + HoudiniGeoPartObject.PartId, GroupType, GroupName ); +} + +bool +FHoudiniEngineUtils::HapiCheckGroupMembership( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, HAPI_PartId PartId, + HAPI_GroupType GroupType, const FString & GroupName ) +{ + TArray< int32 > GroupMembership; + if ( FHoudiniEngineUtils::HapiGetGroupMembership( AssetId, ObjectId, GeoId, PartId, GroupType, GroupName, GroupMembership ) ) + { + int32 GroupSum = 0; + for ( int32 Idx = 0; Idx < GroupMembership.Num(); ++Idx ) + GroupSum += GroupMembership[ Idx ]; + + return GroupSum > 0; + } + + return false; +} + +void +FHoudiniEngineUtils::HapiRetrieveParameterNames( + const TArray< HAPI_ParmInfo > & ParmInfos, + TArray< std::string > & Names) +{ + static const std::string InvalidParameterName( "Invalid Parameter Name" ); + + Names.Empty(); + + for ( int32 ParmIdx = 0; ParmIdx < ParmInfos.Num(); ++ParmIdx ) + { + const HAPI_ParmInfo& NodeParmInfo = ParmInfos[ ParmIdx ]; + HAPI_StringHandle NodeParmHandle = NodeParmInfo.nameSH; + + int32 NodeParmNameLength = 0; + FHoudiniApi::GetStringBufLength( FHoudiniEngine::Get().GetSession(), NodeParmHandle, &NodeParmNameLength ); + + if ( NodeParmNameLength ) + { + std::vector< char > NodeParmName( NodeParmNameLength, '\0' ); + + HAPI_Result Result = FHoudiniApi::GetString( + FHoudiniEngine::Get().GetSession(), NodeParmHandle, + &NodeParmName[ 0 ], NodeParmNameLength ); + if ( Result == HAPI_RESULT_SUCCESS ) + Names.Add( std::string( NodeParmName.begin(), NodeParmName.end() - 1 ) ); + else + Names.Add( InvalidParameterName ); + } + else + { + Names.Add( InvalidParameterName ); + } + } +} + +void +FHoudiniEngineUtils::HapiRetrieveParameterNames( const TArray< HAPI_ParmInfo > & ParmInfos, TArray< FString > & Names ) +{ + TArray< std::string > IntermediateNames; + FHoudiniEngineUtils::HapiRetrieveParameterNames( ParmInfos, IntermediateNames ); + + for ( int32 Idx = 0, Num = IntermediateNames.Num(); Idx < Num; ++Idx ) + Names.Add( UTF8_TO_TCHAR( IntermediateNames[ Idx ].c_str() ) ); +} + +bool +FHoudiniEngineUtils::HapiCheckAttributeExists( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, const char * Name) +{ + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + if (HapiCheckAttributeExists(AssetId, ObjectId, GeoId, + PartId, Name, (HAPI_AttributeOwner)AttrIdx)) + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::HapiCheckAttributeExists( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, const char * Name, HAPI_AttributeOwner Owner ) +{ + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + //FMemory::Memzero< HAPI_AttributeInfo >( AttribInfo ); + if ( FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, Name, Owner, &AttribInfo ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + return AttribInfo.exists; +} + +bool +FHoudiniEngineUtils::HapiCheckAttributeExists( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, const char * Name, + HAPI_AttributeOwner Owner ) +{ + if ( Owner == HAPI_ATTROWNER_INVALID ) + { + return FHoudiniEngineUtils::HapiCheckAttributeExists( + HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId, + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, Name ); + } + else + { + return FHoudiniEngineUtils::HapiCheckAttributeExists( + HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId, + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, Name, Owner); + } +} + + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, const char * Name, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & Data, int32 TupleSize, HAPI_AttributeOwner Owner ) +{ + ResultAttributeInfo.exists = false; + + // Reset container size. + Data.SetNumUninitialized( 0 ); + + int32 OriginalTupleSize = TupleSize; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfo ); + + if ( Owner == HAPI_ATTROWNER_INVALID ) + { + for ( int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx ) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, Name, + (HAPI_AttributeOwner) AttrIdx, &AttributeInfo ), false ); + + if ( AttributeInfo.exists ) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, Name, + Owner, &AttributeInfo ), false ); + } + + if ( !AttributeInfo.exists ) + return false; + + if ( OriginalTupleSize > 0 ) + AttributeInfo.tupleSize = OriginalTupleSize; + + // Allocate sufficient buffer for data. + Data.SetNumUninitialized( AttributeInfo.count * AttributeInfo.tupleSize ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, Name, + &AttributeInfo, -1, &Data[ 0 ], 0, AttributeInfo.count ), false ); + + // Store the retrieved attribute information. + ResultAttributeInfo = AttributeInfo; + return true; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, const char * Name, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< float > & Data, int32 TupleSize, HAPI_AttributeOwner Owner ) +{ + return FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId, + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, Name, + ResultAttributeInfo, Data, TupleSize, Owner ); +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, const char * Name, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & Data, int32 TupleSize, HAPI_AttributeOwner Owner ) +{ + ResultAttributeInfo.exists = false; + + // Reset container size. + Data.SetNumUninitialized( 0 ); + + int32 OriginalTupleSize = TupleSize; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfo ); + + if ( Owner == HAPI_ATTROWNER_INVALID ) + { + for ( int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx ) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, Name, (HAPI_AttributeOwner) AttrIdx, &AttributeInfo ), false ); + + if ( AttributeInfo.exists ) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, Name, Owner, &AttributeInfo ), false ); + } + + if ( !AttributeInfo.exists ) + return false; + + if ( OriginalTupleSize > 0 ) + AttributeInfo.tupleSize = OriginalTupleSize; + + // Allocate sufficient buffer for data. + Data.SetNumUninitialized( AttributeInfo.count * AttributeInfo.tupleSize ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, Name, &AttributeInfo, -1, &Data[ 0 ], 0, AttributeInfo.count ), false ); + + // Store the retrieved attribute information. + ResultAttributeInfo = AttributeInfo; + return true; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + const char * Name, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & Data, int32 TupleSize, HAPI_AttributeOwner Owner ) +{ + return FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId, + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, Name, + ResultAttributeInfo, Data, TupleSize, Owner ); +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, const char * Name, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & Data, int32 TupleSize, HAPI_AttributeOwner Owner ) +{ + ResultAttributeInfo.exists = false; + + // Reset container size. + Data.Empty(); + + int32 OriginalTupleSize = TupleSize; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfo ); + + if ( Owner == HAPI_ATTROWNER_INVALID ) + { + for ( int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx ) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, Name, (HAPI_AttributeOwner) AttrIdx, &AttributeInfo ), false ); + + if ( AttributeInfo.exists ) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, Name, Owner, &AttributeInfo ), false ); + } + + if ( !AttributeInfo.exists ) + return false; + + if ( OriginalTupleSize > 0 ) + AttributeInfo.tupleSize = OriginalTupleSize; + + TArray< HAPI_StringHandle > StringHandles; + StringHandles.Init( -1, AttributeInfo.count * AttributeInfo.tupleSize ); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, Name, &AttributeInfo, + &StringHandles[ 0 ], 0, AttributeInfo.count ), false ); + + // Set the output data size + Data.SetNum(AttributeInfo.count); + + // Use a map to minimize the number of HAPI calls for performance! + TMap StringHandleToStringMap; + for ( int32 Idx = 0; Idx < StringHandles.Num(); ++Idx ) + { + const HAPI_StringHandle& CurrentSH = StringHandles[Idx]; + FString* FoundString = StringHandleToStringMap.Find(CurrentSH); + if ( FoundString ) + { + Data[Idx] = *FoundString; + } + else + { + FString HapiString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(CurrentSH); + HoudiniEngineString.ToFString(HapiString); + + StringHandleToStringMap.Add(CurrentSH, HapiString); + Data[Idx] = HapiString; + } + } + + // Store the retrieved attribute information. + ResultAttributeInfo = AttributeInfo; + return true; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsString( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, const char * Name, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< FString > & Data, int32 TupleSize, HAPI_AttributeOwner Owner ) +{ + return FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HoudiniGeoPartObject.AssetId, + HoudiniGeoPartObject.ObjectId, + HoudiniGeoPartObject.GeoId, + HoudiniGeoPartObject.PartId, Name, + ResultAttributeInfo, Data, TupleSize, Owner ); +} + +bool +FHoudiniEngineUtils::HapiGetInstanceTransforms( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, TArray< FTransform > & Transforms ) +{ + Transforms.Empty(); + + // Number of instances is number of points. + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), GeoId, + PartId, &PartInfo ), false ); + + if ( PartInfo.pointCount == 0 ) + return false; + + TArray< HAPI_Transform > InstanceTransforms; + InstanceTransforms.SetNumUninitialized( PartInfo.pointCount ); + for (int32 Idx = 0; Idx < InstanceTransforms.Num(); Idx++) + FHoudiniApi::Transform_Init(&(InstanceTransforms[Idx])); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstanceTransformsOnPart( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, HAPI_SRT, &InstanceTransforms[ 0 ], 0, PartInfo.pointCount ), false ); + + for ( int32 Idx = 0; Idx < PartInfo.pointCount; ++Idx ) + { + const HAPI_Transform& HapiInstanceTransform = InstanceTransforms[ Idx ]; + FTransform TransformMatrix; + FHoudiniEngineUtils::TranslateHapiTransform( HapiInstanceTransform, TransformMatrix ); + Transforms.Add( TransformMatrix ); + } + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetInstanceTransforms( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + TArray< FTransform > & Transforms ) +{ + return FHoudiniEngineUtils::HapiGetInstanceTransforms( + HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId, + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, Transforms ); +} + +FColor +FHoudiniEngineUtils::PickVertexColorFromTextureMip( + const uint8 * MipBytes, FVector2D & UVCoord, int32 MipWidth, int32 MipHeight ) +{ + check( MipBytes ); + + FColor ResultColor( 0, 0, 0, 255 ); + + if ( UVCoord.X >= 0.0f && UVCoord.X < 1.0f && UVCoord.Y >= 0.0f && UVCoord.Y < 1.0f ) + { + const int32 X = MipWidth * UVCoord.X; + const int32 Y = MipHeight * UVCoord.Y; + + const int32 Index = ( ( Y * MipWidth ) + X ) * 4; + + ResultColor.B = MipBytes[ Index + 0 ]; + ResultColor.G = MipBytes[ Index + 1 ]; + ResultColor.R = MipBytes[ Index + 2 ]; + ResultColor.A = MipBytes[ Index + 3 ]; + } + + return ResultColor; +} + +#if WITH_EDITOR + +void +FHoudiniEngineUtils::ResetRawMesh( FRawMesh & RawMesh ) +{ + // Unlike Empty this will not change memory allocations. + + RawMesh.FaceMaterialIndices.Reset(); + RawMesh.FaceSmoothingMasks.Reset(); + RawMesh.VertexPositions.Reset(); + RawMesh.WedgeIndices.Reset(); + RawMesh.WedgeTangentX.Reset(); + RawMesh.WedgeTangentY.Reset(); + RawMesh.WedgeTangentZ.Reset(); + RawMesh.WedgeColors.Reset(); + + for ( int32 Idx = 0; Idx < MAX_MESH_TEXTURE_COORDS; ++Idx ) + RawMesh.WedgeTexCoords[ Idx ].Reset(); +} + +#endif + +HAPI_ParmId FHoudiniEngineUtils::HapiFindParameterByNameOrTag( const HAPI_NodeId& NodeId, const std::string ParmName, HAPI_ParmInfo& FoundParmInfo ) +{ + FHoudiniApi::ParmInfo_Init(&FoundParmInfo); + + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + FHoudiniApi::GetNodeInfo( FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo ); + if ( NodeInfo.parmCount <= 0 ) + return -1; + + HAPI_ParmId ParmId = HapiFindParameterByNameOrTag( NodeInfo.id, ParmName ); + if ( ( ParmId < 0 ) || ( ParmId >= NodeInfo.parmCount ) ) + return -1; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParmInfo ), -1 ); + + return ParmId; +} + +HAPI_ParmId FHoudiniEngineUtils::HapiFindParameterByNameOrTag( const HAPI_NodeId& NodeId, const std::string ParmName ) +{ + // First, try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN ( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId ), -1 ); + + if ( ParmId >= 0 ) + return ParmId; + + // Second, try to find it by its tag + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmWithTag( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId ), -1 ); + + if ( ParmId >= 0 ) + return ParmId; + + return -1; +} + + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsFloat( + HAPI_NodeId NodeId, const std::string ParmName, float DefaultValue, float & OutValue ) +{ + float Value = DefaultValue; + bool bComputed = false; + + HAPI_ParmInfo FoundParamInfo; + FHoudiniApi::ParmInfo_Init(&FoundParamInfo); + HAPI_ParmId ParmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeId, ParmName, FoundParamInfo ); + if ( ParmId > -1 ) + { + if ( FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), NodeId, &Value, + FoundParamInfo.floatValuesIndex, 1 ) == HAPI_RESULT_SUCCESS ) + { + bComputed = true; + } + } + + OutValue = Value; + return bComputed; +} + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + HAPI_NodeId NodeId, const std::string ParmName, int32 DefaultValue, int32 & OutValue) +{ + int32 Value = DefaultValue; + bool bComputed = false; + + HAPI_ParmInfo FoundParamInfo; + FHoudiniApi::ParmInfo_Init(&FoundParamInfo); + HAPI_ParmId ParmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeId, ParmName, FoundParamInfo ); + if ( ParmId > -1 ) + { + if ( FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), NodeId, &Value, + FoundParamInfo.intValuesIndex, 1 ) == HAPI_RESULT_SUCCESS ) + { + bComputed = true; + } + } + + OutValue = Value; + return bComputed; +} + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsString( + HAPI_NodeId NodeId, const std::string ParmName, + const FString & DefaultValue, FString & OutValue ) +{ + FString Value; + bool bComputed = false; + + HAPI_ParmInfo FoundParamInfo; + FHoudiniApi::ParmInfo_Init(&FoundParamInfo); + HAPI_ParmId ParmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeId, ParmName, FoundParamInfo ); + if ( ParmId > -1 ) + { + HAPI_StringHandle StringHandle; + if ( FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), NodeId, false, + &StringHandle, FoundParamInfo.stringValuesIndex, 1 ) == HAPI_RESULT_SUCCESS ) + { + FHoudiniEngineString HoudiniEngineString( StringHandle ); + if ( HoudiniEngineString.ToFString( Value ) ) + bComputed = true; + } + } + + if ( bComputed ) + OutValue = Value; + else + OutValue = DefaultValue; + + return bComputed; +} + +bool +FHoudiniEngineUtils::HapiGetParameterUnit( const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, FString& OutUnitString ) +{ + // + OutUnitString = TEXT(""); + + // We're looking for the parameter unit tag + FString UnitTag = "units"; + bool HasUnit = false; + + // Does the parameter has the "units" tag? + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ParmHasTag( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*UnitTag), &HasUnit ), false ); + + if ( !HasUnit ) + return false; + + // Get the unit string value + HAPI_StringHandle StringHandle; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmTagValue( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*UnitTag), &StringHandle ), false ); + + // Get the actual string value. + FString UnitString = TEXT(""); + FHoudiniEngineString HoudiniEngineString( StringHandle ); + if ( HoudiniEngineString.ToFString( UnitString ) ) + { + // We need to do some replacement in the string here in order to be able to get the + // proper unit type when calling FUnitConversion::UnitFromString(...) after. + + // Per second and per hour are the only "per" unit that unreal recognize + UnitString.ReplaceInline( TEXT( "s-1" ), TEXT( "/s" ) ); + UnitString.ReplaceInline( TEXT( "h-1" ), TEXT( "/h" ) ); + + // Houdini likes to add '1' on all the unit, so we'll remove all of them + // except the '-1' that still needs to remain. + UnitString.ReplaceInline( TEXT( "-1" ), TEXT( "--" ) ); + UnitString.ReplaceInline( TEXT( "1" ), TEXT( "" ) ); + UnitString.ReplaceInline( TEXT( "--" ), TEXT( "-1" ) ); + + OutUnitString = UnitString; + + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::HapiGetParameterNoSwapTag( const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, bool& NoSwapValue ) +{ + // Default noswap to false + NoSwapValue = false; + + // We're looking for the parameter noswap tag + FString NoSwapTag = "hengine_noswap"; + + // Does the parameter has the "hengine_noswap" tag? + bool HasNoSwap = false; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ParmHasTag( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI( *NoSwapTag ), &HasNoSwap ), false ); + + if ( !HasNoSwap ) + return true; + + // Set NoSwap to true + NoSwapValue = true; + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetParameterTag( const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag, FString& TagValue ) +{ + // Default + TagValue = FString(); + + // Does the parameter has the tag? + bool HasTag = false; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ParmHasTag( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*Tag), &HasTag ), false ); + + if ( !HasTag ) + return false; + + // Get the tag string value + HAPI_StringHandle StringHandle; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmTagValue( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI( *Tag ), &StringHandle ), false ); + + FHoudiniEngineString HoudiniEngineString( StringHandle ); + if ( HoudiniEngineString.ToFString( TagValue ) ) + { + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::IsValidNodeId( HAPI_NodeId NodeId ) +{ + return NodeId != -1; +} + +bool +FHoudiniEngineUtils::HapiCreateCurveNode( HAPI_NodeId & ConnectedAssetId ) +{ +#if WITH_EDITOR + + // Create the curve SOP Node + HAPI_NodeId NodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), -1, + "SOP/curve", nullptr, false, &NodeId), false); + + ConnectedAssetId = NodeId; + + // Submit default points to curve. + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), NodeId, + HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NodeId, + HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT, ParmId, 0), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), NodeId, nullptr), false); + +#endif // WITH_EDITOR + + return true; +} + +bool +FHoudiniEngineUtils::HapiCreateCurveInputNodeForData( + HAPI_NodeId HostAssetId, + HAPI_NodeId& ConnectedAssetId, + TArray* Positions, + TArray* Rotations /*= nullptr*/, + TArray* Scales3d /*= nullptr*/, + TArray* UniformScales /*= nullptr*/, + bool ForceClose /*=false*/) +{ +#if WITH_EDITOR + + // Positions are required + if (!Positions) + return false; + + // We also need a valid host asset and 2 points to make a curve + int32 NumberOfCVs = Positions->Num(); + if ( ( NumberOfCVs < 2 ) || !FHoudiniEngineUtils::IsHoudiniNodeValid( HostAssetId ) ) + return false; + + // Check if connected asset id is valid, if it is not, we need to create an input asset. + if (ConnectedAssetId < 0) + { + HAPI_NodeId NodeId = -1; + // Create the curve SOP Node + if (!HapiCreateCurveNode(NodeId)) + return false; + + // Check if we have a valid id for this new input asset. + if ( !FHoudiniEngineUtils::IsHoudiniNodeValid( NodeId ) ) + return false; + + // We now have a valid id. + ConnectedAssetId = NodeId; + } + else + { + // We have to revert the Geo to its original state so we can use the Curve SOP: + // adding parameters to the Curve SOP locked it, preventing its parameters (type, method, isClosed) from working + FHoudiniApi::RevertGeo(FHoudiniEngine::Get().GetSession(), ConnectedAssetId); + } + + // + // In order to be able to add rotations and scale attributes to the curve SOP, we need to cook it twice: + // + // - First, we send the positions string to it, and cook it without refinement. + // this will allow us to get the proper curve CVs, part attributes and curve info to create the desired curve. + // + // - We then need to send back all the info extracted from the curve SOP to it, and add the rotation + // and scale attributes to it. This will lock the curve SOP, and prevent the curve type and method + // parameters from functioning properly (hence why we needed the first cook to set that up) + // + + // Reading the curve parameters + int32 CurveTypeValue, CurveMethodValue, CurveClosed; + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + ConnectedAssetId, HAPI_UNREAL_PARAM_CURVE_TYPE, + 0, CurveTypeValue); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + ConnectedAssetId, HAPI_UNREAL_PARAM_CURVE_METHOD, + 0, CurveMethodValue); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + ConnectedAssetId, HAPI_UNREAL_PARAM_CURVE_CLOSED, + 1, CurveClosed); + + if ( ForceClose ) + { + // We need to update the closed parameter + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); + + CurveClosed = 1; + } + + // For closed NURBS (CVs and Breakpoints), we have to close the curve manually, by duplicating its last point + // in order to be able to set the rotations and scales attributes properly. + bool bCloseCurveManually = false; + if ( CurveClosed && (CurveTypeValue == HAPI_CURVETYPE_NURBS) && (CurveMethodValue != 2) ) + { + // The curve is not closed anymore + if (HAPI_RESULT_SUCCESS == FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 0)) + { + bCloseCurveManually = true; + + // Duplicating the first point to the end of the curve + // This needs to be done before sending the position string + FVector pos = (*Positions)[0]; + Positions->Add(pos); + + CurveClosed = false; + } + } + + // Creating the position string + FString PositionString = TEXT(""); + FHoudiniEngineUtils::CreatePositionsString(*Positions, PositionString); + + // Get param id for the PositionString and modify it + HAPI_ParmId ParmId = -1; + if (FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, + HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId) != HAPI_RESULT_SUCCESS) + { + return false; + } + + std::string ConvertedString = TCHAR_TO_UTF8(*PositionString); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, + ConvertedString.c_str(), ParmId, 0), false); + + // If we don't want to add rotations or scale attributes to the curve, + // we can just cook the node normally and stop here. + bool bAddRotations = (Rotations != nullptr); + bool bAddScales3d = (Scales3d != nullptr); + bool bAddUniformScales = (UniformScales != nullptr); + + if (!bAddRotations && !bAddScales3d && !bAddUniformScales) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, nullptr), false); + + return true; + } + + // Setting up the first cook, without the curve refinement + HAPI_CookOptions CookOptions; + FHoudiniApi::CookOptions_Init(&CookOptions); + //FMemory::Memzero< HAPI_CookOptions >(CookOptions); + CookOptions.curveRefineLOD = 8.0f; + CookOptions.clearErrorsAndWarnings = false; + CookOptions.maxVerticesPerPrimitive = -1; + CookOptions.splitGeosByGroup = false; + CookOptions.splitGeosByAttribute = false; + CookOptions.splitAttrSH = 0; + CookOptions.handleBoxPartTypes = false; + CookOptions.handleSpherePartTypes = false; + CookOptions.packedPrimInstancingMode = HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT; + CookOptions.refineCurveToLinear = false; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &CookOptions), false); + + // We can now read back the Part infos from the cooked curve. + HAPI_PartInfo PartInfos; + FHoudiniApi::PartInfo_Init(&PartInfos); + //FMemory::Memzero< HAPI_PartInfo >(PartInfos); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, 0, &PartInfos), false); + + // + // Depending on the curve type and method, additionnal control points might have been created. + // We now have to interpolate the rotations and scale attributes for these. + // + + // Lambda function that interpolates rotation, scale and uniform scales values + // between two points using fCoeff as a weight, and insert the interpolated value at nInsertIndex + auto InterpolateRotScaleUScale = [&](const int32& nIndex1, const int32& nIndex2, const float& fCoeff, const int32& nInsertIndex) + { + if ( Rotations && Rotations->IsValidIndex(nIndex1) && Rotations->IsValidIndex(nIndex2) ) + { + FQuat interpolation = FQuat::Slerp((*Rotations)[nIndex1], (*Rotations)[nIndex2], fCoeff); + if (Rotations->IsValidIndex(nInsertIndex)) + Rotations->Insert(interpolation, nInsertIndex); + else + Rotations->Add(interpolation); + } + + if ( Scales3d && Scales3d->IsValidIndex(nIndex1) && Scales3d->IsValidIndex(nIndex2) ) + { + FVector interpolation = fCoeff * (*Scales3d)[nIndex1] + (1.0f - fCoeff) * (*Scales3d)[nIndex2]; + if (Scales3d->IsValidIndex(nInsertIndex)) + Scales3d->Insert( interpolation, nInsertIndex); + else + Scales3d->Add(interpolation); + } + + if ( UniformScales && UniformScales->IsValidIndex(nIndex1) && UniformScales->IsValidIndex(nIndex2) ) + { + float interpolation = fCoeff * (*UniformScales)[nIndex1] + (1.0f - fCoeff) * (*UniformScales)[nIndex2]; + if (UniformScales->IsValidIndex(nInsertIndex)) + UniformScales->Insert(interpolation, nInsertIndex); + else + UniformScales->Add(interpolation); + } + }; + + // Lambda function that duplicates rotation, scale and uniform scales values + // at nIndex and insert/adds it at nInsertIndex + auto DuplicateRotScaleUScale = [&](const int32& nIndex, const int32& nInsertIndex) + { + if ( Rotations && Rotations->IsValidIndex(nIndex) ) + { + FQuat value = (*Rotations)[nIndex]; + if ( Rotations->IsValidIndex(nInsertIndex) ) + Rotations->Insert(value, nInsertIndex); + else + Rotations->Add(value); + } + + if ( Scales3d && Scales3d->IsValidIndex(nIndex) ) + { + FVector value = (*Scales3d)[nIndex]; + if ( Scales3d->IsValidIndex(nInsertIndex) ) + Scales3d->Insert(value, nInsertIndex); + else + Scales3d->Add(value); + } + + if ( UniformScales && UniformScales->IsValidIndex(nIndex) ) + { + float value = (*UniformScales)[nIndex]; + if ( UniformScales->IsValidIndex(nInsertIndex) ) + UniformScales->Insert(value, nInsertIndex); + else + UniformScales->Add(value); + } + }; + + // Do we want to close the curve by ourselves? + if (bCloseCurveManually) + { + // We need to duplicate the info of the first point to the last + DuplicateRotScaleUScale(0, NumberOfCVs++); + + // We need to update the closed parameter + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); + } + + // INTERPOLATION + if (CurveTypeValue == HAPI_CURVETYPE_NURBS) + { + // Closed NURBS have additional points reproducing the first ones + if (CurveClosed) + { + // Only the first one if the method is freehand ... + DuplicateRotScaleUScale(0, NumberOfCVs++); + + if (CurveMethodValue != 2) + { + // ... but also the 2nd and 3rd if the method is CVs or Breakpoints. + DuplicateRotScaleUScale(1, NumberOfCVs++); + DuplicateRotScaleUScale(2, NumberOfCVs++); + } + } + else if (CurveMethodValue == 1) + { + // Open NURBS have 2 new points if the method is breakpoint: + // One between the 1st and 2nd ... + InterpolateRotScaleUScale(0, 1, 0.5f, 1); + + // ... and one before the last one. + InterpolateRotScaleUScale(NumberOfCVs, NumberOfCVs - 1, 0.5f, NumberOfCVs); + NumberOfCVs += 2; + } + } + else if (CurveTypeValue == HAPI_CURVETYPE_BEZIER) + { + // Bezier curves requires additional point if the method is Breakpoints + if (CurveMethodValue == 1) + { + // 2 interpolated control points are added per points (except the last one) + int32 nOffset = 0; + for (int32 n = 0; n < NumberOfCVs - 1; n++) + { + int nIndex1 = n + nOffset; + int nIndex2 = n + nOffset + 1; + + InterpolateRotScaleUScale(nIndex1, nIndex2, 0.33f, nIndex2); + nIndex2++; + InterpolateRotScaleUScale(nIndex1, nIndex2, 0.66f, nIndex2); + + nOffset += 2; + } + NumberOfCVs += nOffset; + + if (CurveClosed) + { + // If the curve is closed, we need to add 2 points after the last, + // interpolated between the last and the first one + int nIndex = NumberOfCVs - 1; + InterpolateRotScaleUScale(nIndex, 0, 0.33f, NumberOfCVs++); + InterpolateRotScaleUScale(nIndex, 0, 0.66f, NumberOfCVs++); + + // and finally, the last point is the first.. + DuplicateRotScaleUScale(0, NumberOfCVs++); + } + } + else if (CurveClosed) + { + // For the other methods, if the bezier curve is closed, the last point is the 1st + DuplicateRotScaleUScale(0, NumberOfCVs++); + } + } + + // Even after interpolation, additional points might still be missing: + // Bezier curves require a certain number of points regarding their order, + // if points are lacking then HAPI duplicates the last one. + if (NumberOfCVs < PartInfos.pointCount) + { + int nToAdd = PartInfos.pointCount - NumberOfCVs; + for (int n = 0; n < nToAdd; n++) + { + DuplicateRotScaleUScale(NumberOfCVs-1, NumberOfCVs); + NumberOfCVs++; + } + } + + // To avoid crashes, attributes will only be added if we now have the correct number of them + bAddRotations = bAddRotations && (Rotations->Num() == PartInfos.pointCount); + bAddScales3d = bAddScales3d && (Scales3d->Num() == PartInfos.pointCount); + bAddUniformScales = bAddUniformScales && (UniformScales->Num() == PartInfos.pointCount); + + // We need to increase the point attributes count for points in the Part Infos + HAPI_AttributeOwner NewAttributesOwner = HAPI_ATTROWNER_POINT; + HAPI_AttributeOwner OriginalAttributesOwner = HAPI_ATTROWNER_POINT; + + int OriginalPointParametersCount = PartInfos.attributeCounts[NewAttributesOwner]; + if (bAddRotations) + PartInfos.attributeCounts[NewAttributesOwner] += 1; + if (bAddScales3d) + PartInfos.attributeCounts[NewAttributesOwner] += 1; + if (bAddUniformScales) + PartInfos.attributeCounts[NewAttributesOwner] += 1; + + // Sending the updated PartInfos + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, &PartInfos), false); + + // We need now to reproduce ALL the curves attributes for ALL the Owners.. + for (int nOwner = 0; nOwner < HAPI_ATTROWNER_MAX; nOwner++) + { + int nOwnerAttributeCount = nOwner == NewAttributesOwner ? OriginalPointParametersCount : PartInfos.attributeCounts[nOwner]; + if (nOwnerAttributeCount == 0) + continue; + + std::vector< HAPI_StringHandle > sh_attributes(nOwnerAttributeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + (HAPI_AttributeOwner)nOwner, + &sh_attributes.front(), sh_attributes.size()), false); + + for (int nAttribute = 0; nAttribute < sh_attributes.size(); nAttribute++) + { + const HAPI_StringHandle sh = sh_attributes[nAttribute]; + if (sh == 0) + continue; + + // Get the attribute name + FHoudiniEngineString sName(sh); + std::string attr_name; + sName.ToStdString(attr_name); + + if (strcmp(attr_name.c_str(), "__topology") == 0) + continue; + + // and the attribute infos + HAPI_AttributeInfo attr_info; + FHoudiniApi::AttributeInfo_Init(&attr_info); + //FMemory::Memzero< HAPI_AttributeInfo >( attr_info ); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + attr_name.c_str(), + ( HAPI_AttributeOwner )nOwner, + &attr_info ), false ); + + switch (attr_info.storage) + { + case HAPI_STORAGETYPE_INT: + { + // Storing IntData + TArray< int > IntData; + IntData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + attr_name.c_str(), &attr_info, -1, + IntData.GetData(), 0, attr_info.count), false); + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + attr_name.c_str(), &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + attr_name.c_str(), + &attr_info, IntData.GetData(), + 0, attr_info.count), false); + } + break; + + case HAPI_STORAGETYPE_FLOAT: + { + // Storing Float Data + TArray< float > FloatData; + FloatData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + attr_name.c_str(), &attr_info, -1, + FloatData.GetData(), + 0, attr_info.count), false); + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + attr_name.c_str(), + &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + attr_name.c_str(), &attr_info, + FloatData.GetData(), + 0, attr_info.count), false); + } + break; + + case HAPI_STORAGETYPE_STRING: + { + // Storing String Data + TArray StringHandleData; + StringHandleData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + attr_name.c_str(), &attr_info, + StringHandleData.GetData(), + 0, attr_info.count), false); + + // Convert the SH to const char * + TArray StringData; + StringData.SetNumUninitialized(attr_info.count); + for (int n = 0; n < StringHandleData.Num(); n++) + { + // Converting the string + FHoudiniEngineString strHE(sh); + std::string strSTD; + strHE.ToStdString(strSTD); + + StringData[n] = strSTD.c_str(); + } + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + attr_name.c_str(), + &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + attr_name.c_str(), &attr_info, + StringData.GetData(), + 0, attr_info.count), false); + } + break; + + default: + continue; + } + } + } + + // Only GET/SET curve infos if the part is a curve... + // (Closed linear curves are actually not considered as curves...) + if (PartInfos.type == HAPI_PARTTYPE_CURVE) + { + // We need to read the curve infos ... + HAPI_CurveInfo CurveInfo; + FHoudiniApi::CurveInfo_Init(&CurveInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveInfo( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + &CurveInfo), false); + + // ... the curve counts + TArray< int > CurveCounts; + CurveCounts.SetNumUninitialized(CurveInfo.curveCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveCounts( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + CurveCounts.GetData(), + 0, CurveInfo.curveCount), false); + + // .. the curve orders + TArray< int > CurveOrders; + CurveOrders.SetNumUninitialized(CurveInfo.curveCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveOrders( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + CurveOrders.GetData(), + 0, CurveInfo.curveCount), false); + + // .. And the Knots if they exist. + TArray< float > KnotsArray; + if (CurveInfo.hasKnots) + { + KnotsArray.SetNumUninitialized(CurveInfo.knotCount); + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetCurveKnots( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + KnotsArray.GetData(), + 0, CurveInfo.knotCount), false); + } + + // To set them back in HAPI + // CurveInfo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveInfo( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + &CurveInfo), false); + + // CurveCounts + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveCounts( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + CurveCounts.GetData(), + 0, CurveInfo.curveCount), false); + + // CurveOrders + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveOrders( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + CurveOrders.GetData(), + 0, CurveInfo.curveCount), false); + + // And Knots if they exist + if (CurveInfo.hasKnots) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveKnots( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + KnotsArray.GetData(), + 0, CurveInfo.knotCount), false); + } + } + + if (PartInfos.faceCount > 0) + { + // getting the face counts + TArray< int > FaceCounts; + FaceCounts.SetNumUninitialized(PartInfos.faceCount); + + if (FHoudiniApi::GetFaceCounts( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + FaceCounts.GetData(), 0, + PartInfos.faceCount) == HAPI_RESULT_SUCCESS) + { + // Set the face count + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + FaceCounts.GetData(), + 0, PartInfos.faceCount), false); + } + } + + if (PartInfos.vertexCount > 0) + { + // the vertex list + TArray< int > VertexList; + VertexList.SetNumUninitialized(PartInfos.vertexCount); + + if (FHoudiniApi::GetVertexList( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + VertexList.GetData(), + 0, PartInfos.vertexCount) == HAPI_RESULT_SUCCESS) + { + // setting the vertex list + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + VertexList.GetData(), + 0, PartInfos.vertexCount), false); + } + } + + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + if (HoudiniRuntimeSettings) + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + + // We can add attributes to the curve now that all the curves attributes + // and properties have been reset. + if (bAddRotations) + { + // Create ROTATION attribute info + HAPI_AttributeInfo AttributeInfoRotation; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoRotation ); + AttributeInfoRotation.count = NumberOfCVs; + AttributeInfoRotation.tupleSize = 4; + AttributeInfoRotation.exists = true; + AttributeInfoRotation.owner = NewAttributesOwner; + AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRotation.originalOwner = OriginalAttributesOwner; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRotation), false); + + // Convert the rotation infos + TArray< float > CurveRotations; + CurveRotations.SetNumZeroed(NumberOfCVs * 4); + for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) + { + // Get current quaternion + const FQuat& RotationQuaternion = (*Rotations)[Idx]; + + if (ImportAxis == HRSAI_Unreal) + { + CurveRotations[Idx * 4 + 0] = RotationQuaternion.X; + CurveRotations[Idx * 4 + 1] = RotationQuaternion.Z; + CurveRotations[Idx * 4 + 2] = RotationQuaternion.Y; + CurveRotations[Idx * 4 + 3] = -RotationQuaternion.W; + } + else if (ImportAxis == HRSAI_Houdini) + { + CurveRotations[Idx * 4 + 0] = RotationQuaternion.X; + CurveRotations[Idx * 4 + 1] = RotationQuaternion.Y; + CurveRotations[Idx * 4 + 2] = RotationQuaternion.Z; + CurveRotations[Idx * 4 + 3] = RotationQuaternion.W; + } + else + { + // Not valid enum value. + check(0); + } + } + + //we can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRotation, + CurveRotations.GetData(), + 0, AttributeInfoRotation.count), false); + } + + // Create SCALE attribute info. + if (bAddScales3d) + { + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoScale); + AttributeInfoScale.count = NumberOfCVs; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = NewAttributesOwner; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = OriginalAttributesOwner; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale), false); + + // Convert the scale + TArray< float > CurveScales; + CurveScales.SetNumZeroed(NumberOfCVs * 3); + for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) + { + // Get current scale + FVector ScaleVector = (*Scales3d)[Idx]; + if (ImportAxis == HRSAI_Unreal) + { + CurveScales[Idx * 3 + 0] = ScaleVector.X; + CurveScales[Idx * 3 + 1] = ScaleVector.Z; + CurveScales[Idx * 3 + 2] = ScaleVector.Y; + } + else if (ImportAxis == HRSAI_Houdini) + { + CurveScales[Idx * 3 + 0] = ScaleVector.X; + CurveScales[Idx * 3 + 1] = ScaleVector.Y; + CurveScales[Idx * 3 + 2] = ScaleVector.Z; + } + else + { + // Not valid enum value. + check(0); + } + } + + // We can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale, + CurveScales.GetData(), + 0, AttributeInfoScale.count), false); + } + + // Create PSCALE attribute info. + if (bAddUniformScales) + { + HAPI_AttributeInfo AttributeInfoPScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPScale); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoPScale); + AttributeInfoPScale.count = NumberOfCVs; + AttributeInfoPScale.tupleSize = 1; + AttributeInfoPScale.exists = true; + AttributeInfoPScale.owner = NewAttributesOwner; + AttributeInfoPScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPScale.originalOwner = OriginalAttributesOwner; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + HAPI_UNREAL_ATTRIB_UNIFORM_SCALE, + &AttributeInfoPScale), false); + + // Get the current uniform scale. + TArray CurvePScales; + CurvePScales.SetNumZeroed(NumberOfCVs); + for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) + { + CurvePScales[Idx] = (*UniformScales)[Idx]; + } + + // We can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, + HAPI_UNREAL_ATTRIB_UNIFORM_SCALE, + &AttributeInfoPScale, + CurvePScales.GetData(), + 0, AttributeInfoPScale.count), false); + } + + // Finally, commit the geo ... + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId), false); +#endif + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetAssetTransform( HAPI_NodeId AssetId, FTransform & InTransform ) +{ + HAPI_NodeInfo LocalAssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&LocalAssetNodeInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetId, + &LocalAssetNodeInfo ), false ); + + HAPI_Transform LocalHapiTransform; + FHoudiniApi::Transform_Init(&LocalHapiTransform); + //FMemory::Memzero< HAPI_Transform >( LocalHapiTransform ); + + if ( LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP ) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetObjectTransform( + FHoudiniEngine::Get().GetSession(), LocalAssetNodeInfo.parentId, -1, + HAPI_SRT, &LocalHapiTransform ), false ); + } + else if ( LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ ) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetObjectTransform( + FHoudiniEngine::Get().GetSession(), AssetId, -1, + HAPI_SRT, &LocalHapiTransform ), false ); + } + else + return false; + + // Convert HAPI transform to Unreal one. + FHoudiniEngineUtils::TranslateHapiTransform( LocalHapiTransform, InTransform ); + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetNodeId( HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, HAPI_NodeId & NodeId ) +{ + if ( FHoudiniEngineUtils::IsValidNodeId( AssetId ) ) + { + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + if ( FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), GeoId, &GeoInfo ) == HAPI_RESULT_SUCCESS ) + { + NodeId = GeoInfo.nodeId; + return true; + } + } + + NodeId = -1; + return false; +} + +bool +FHoudiniEngineUtils::HapiGetNodePath( HAPI_NodeId NodeId, HAPI_NodeId RelativeToNodeId, FString & OutPath ) +{ + if ( ( NodeId == -1 ) || ( RelativeToNodeId == -1 ) ) + return false; + + if ( !FHoudiniEngineUtils::IsHoudiniNodeValid( NodeId ) ) + return false; + + HAPI_StringHandle StringHandle; + if ( FHoudiniApi::GetNodePath( + FHoudiniEngine::Get().GetSession(), + NodeId, RelativeToNodeId, &StringHandle ) == HAPI_RESULT_SUCCESS ) + { + FHoudiniEngineString HoudiniEngineString( StringHandle ); + if ( HoudiniEngineString.ToFString( OutPath ) ) + { + return true; + } + } + return false; +} + +bool +FHoudiniEngineUtils::HapiGetObjectInfos( HAPI_NodeId AssetId, TArray< HAPI_ObjectInfo > & ObjectInfos ) +{ + HAPI_NodeInfo LocalAssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&LocalAssetNodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetId, + &LocalAssetNodeInfo), false); + + int32 ObjectCount = 0; + if (LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP) + { + ObjectCount = 1; + ObjectInfos.SetNumUninitialized(1); + FHoudiniApi::ObjectInfo_Init(&(ObjectInfos[0])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), LocalAssetNodeInfo.parentId, + &ObjectInfos[0]), false); + } + else if (LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( + FHoudiniEngine::Get().GetSession(), AssetId, nullptr, &ObjectCount), false); + + if (ObjectCount <= 0) + { + ObjectCount = 1; + ObjectInfos.SetNumUninitialized(1); + FHoudiniApi::ObjectInfo_Init(&(ObjectInfos[0])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), AssetId, + &ObjectInfos[0]), false); + } + else + { + ObjectInfos.SetNumUninitialized(ObjectCount); + for (int32 Idx = 0; Idx < ObjectInfos.Num(); Idx++ ) + FHoudiniApi::ObjectInfo_Init(&(ObjectInfos[0])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectList( + FHoudiniEngine::Get().GetSession(), AssetId, + &ObjectInfos[0], 0, ObjectCount), false); + } + } + else + return false; + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetObjectTransforms( HAPI_NodeId AssetId, TArray< HAPI_Transform > & ObjectTransforms ) +{ + HAPI_NodeInfo LocalAssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&LocalAssetNodeInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetId, + &LocalAssetNodeInfo ), false ); + + int32 ObjectCount = 1; + ObjectTransforms.SetNumUninitialized( 1 ); + FHoudiniApi::Transform_Init(&(ObjectTransforms[0])); + + //FMemory::Memzero< HAPI_Transform >( ObjectTransforms[ 0 ] ); + ObjectTransforms[ 0 ].rotationQuaternion[ 3 ] = 1.0f; + ObjectTransforms[ 0 ].scale[ 0 ] = 1.0f; + ObjectTransforms[ 0 ].scale[ 1 ] = 1.0f; + ObjectTransforms[ 0 ].scale[ 2 ] = 1.0f; + ObjectTransforms[ 0 ].rstOrder = HAPI_SRT; + + if ( LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP ) + { + // Do nothing. Identity transform will be used for the main parent object. + } + else if ( LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ ) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ComposeObjectList( + FHoudiniEngine::Get().GetSession(), AssetId, nullptr, &ObjectCount ), false ); + + if ( ObjectCount <= 0 ) + { + // Do nothing. Identity transform will be used for the main asset object. + } + else + { + ObjectTransforms.SetNumUninitialized( ObjectCount ); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetComposedObjectTransforms( + FHoudiniEngine::Get().GetSession(), AssetId, + HAPI_SRT, &ObjectTransforms[ 0 ], 0, ObjectCount ), false ); + } + } + else + return false; + + return true; +} + +bool +FHoudiniEngineUtils::HapiCreateInputNodeForLandscape( + const HAPI_NodeId& HostAssetId, ALandscapeProxy * LandscapeProxy, + HAPI_NodeId & ConnectedAssetId, TArray< HAPI_NodeId >& OutCreatedNodeIds, + const bool& bExportOnlySelected, const bool& bExportCurves, + const bool& bExportMaterials, const bool& bExportGeometryAsMesh, + const bool& bExportLighting, const bool& bExportNormalizedUVs, + const bool& bExportTileUVs, const FBox& AssetBounds, + const bool& bExportAsHeighfield, const bool& bAutoSelectComponents ) +{ +#if WITH_EDITOR + + // If we don't have any landscapes or host asset is invalid then there's nothing to do. + if ( !LandscapeProxy || LandscapeProxy->IsPendingKill() || !FHoudiniEngineUtils::IsHoudiniNodeValid( HostAssetId ) ) + return false; + + // Get runtime settings. + float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION; + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if ( HoudiniRuntimeSettings ) + { + GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + } + + TSet< ULandscapeComponent * > SelectedComponents; + if ( bExportOnlySelected ) + { + const ULandscapeInfo * LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if ( LandscapeInfo && !LandscapeInfo->IsPendingKill() ) + { + // Get the currently selected components + SelectedComponents = LandscapeInfo->GetSelectedComponents(); + } + + if ( bAutoSelectComponents && SelectedComponents.Num() <= 0 && AssetBounds.IsValid ) + { + // We'll try to use the asset bounds to automatically "select" the components + for ( int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++ ) + { + ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ ComponentIdx ]; + if ( !LandscapeComponent || LandscapeComponent->IsPendingKill() ) + continue; + + FBoxSphereBounds WorldBounds = LandscapeComponent->CalcBounds( LandscapeComponent->GetComponentTransform()); + + if ( AssetBounds.IntersectXY( WorldBounds.GetBox() ) ) + SelectedComponents.Add( LandscapeComponent ); + } + + int32 Num = SelectedComponents.Num(); + HOUDINI_LOG_MESSAGE( TEXT("Landscape input: automatically selected %d components within the asset's bounds."), Num ); + } + } + else + { + // Add all the components to the selected set + for ( int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++ ) + { + ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ ComponentIdx ]; + if ( !LandscapeComponent || LandscapeComponent->IsPendingKill() ) + continue; + + SelectedComponents.Add( LandscapeComponent ); + } + } + + //-------------------------------------------------------------------------------------------------- + // EXPORT TO HEIGHTFIELD + //-------------------------------------------------------------------------------------------------- + if ( bExportAsHeighfield ) + { + bool bSuccess = false; + HAPI_NodeId CreatedHeightfieldNodeId = -1; + int32 NumComponents = LandscapeProxy->LandscapeComponents.Num(); + if ( !bExportOnlySelected || ( SelectedComponents.Num() == NumComponents ) ) + { + // Export the whole landscape and its layer as a single heightfield node + bSuccess = FHoudiniLandscapeUtils::CreateHeightfieldFromLandscape( LandscapeProxy, CreatedHeightfieldNodeId ); + } + else + { + // Each selected landscape component will be exported as separate volumes in a single heightfield + bSuccess = FHoudiniLandscapeUtils::CreateHeightfieldFromLandscapeComponentArray( LandscapeProxy, SelectedComponents, CreatedHeightfieldNodeId ); + } + + // Add the Heightfield's parent OBJ node to the created nodes + OutCreatedNodeIds.AddUnique( FHoudiniEngineUtils::HapiGetParentNodeId( CreatedHeightfieldNodeId ) ); + ConnectedAssetId = CreatedHeightfieldNodeId; + + return bSuccess; + } + + //-------------------------------------------------------------------------------------------------- + // EXPORT TO MESH / POINTS + //-------------------------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------------------------- + // 1. Create an input node if needed + //-------------------------------------------------------------------------------------------------- + + // Check if connected asset id is invalid, if it is not, we need to create an input node. + if ( ConnectedAssetId < 0 ) + { + HAPI_NodeId InputNodeId = -1; + // Create the curve SOP Node + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, nullptr ), false ); + + // Check if we have a valid id for this new input asset. + if ( !FHoudiniEngineUtils::IsHoudiniNodeValid( InputNodeId ) ) + return false; + + // We now have a valid id. + ConnectedAssetId = InputNodeId; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InputNodeId, nullptr ), false ); + } + + //-------------------------------------------------------------------------------------------------- + // 2. Set the part info + //-------------------------------------------------------------------------------------------------- + int32 ComponentSizeQuads = ( ( LandscapeProxy->ComponentSizeQuads + 1 ) >> LandscapeProxy->ExportLOD ) - 1; + float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; + + int32 NumComponents = bExportOnlySelected ? SelectedComponents.Num() : LandscapeProxy->LandscapeComponents.Num(); + int32 VertexCountPerComponent = FMath::Square( ComponentSizeQuads + 1 ); + int32 VertexCount = NumComponents * VertexCountPerComponent; + if ( !VertexCount ) + return false; + + int32 TriangleCount = NumComponents * FMath::Square( ComponentSizeQuads ) * 2; + int32 QuadCount = NumComponents * FMath::Square( ComponentSizeQuads ); + int32 IndexCount = QuadCount * 4; + + // Create part info + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + //FMemory::Memzero< HAPI_PartInfo >(Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[ HAPI_ATTROWNER_POINT ] = 0; + Part.attributeCounts[ HAPI_ATTROWNER_PRIM ] = 0; + Part.attributeCounts[ HAPI_ATTROWNER_VERTEX ] = 0; + Part.attributeCounts[ HAPI_ATTROWNER_DETAIL ] = 0; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = VertexCount; + Part.type = HAPI_PARTTYPE_MESH; + + // If we are exporting to a mesh, we need vertices and faces + if ( bExportGeometryAsMesh ) + { + Part.vertexCount = IndexCount; + Part.faceCount = QuadCount; + } + + // Set the part infos + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &DisplayGeoInfo ), false ); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, 0, &Part ), false ); + + //-------------------------------------------------------------------------------------------------- + // 3. Extract the landscape data + //-------------------------------------------------------------------------------------------------- + // Array for the position data + TArray LandscapePositionArray; + // Array for the normals + TArray LandscapeNormalArray; + // Array for the UVs + TArray LandscapeUVArray; + // Array for the vertex index of each point in its component + TArray LandscapeComponentVertexIndicesArray; + // Array for the tile names per point + TArray LandscapeComponentNameArray; + // Array for the lightmap values + TArray LandscapeLightmapValues; + + // Extract all the data from the landscape to the arrays + if ( !FHoudiniLandscapeUtils::ExtractLandscapeData( + LandscapeProxy, SelectedComponents, + bExportLighting, bExportTileUVs, bExportNormalizedUVs, + LandscapePositionArray, LandscapeNormalArray, + LandscapeUVArray, LandscapeComponentVertexIndicesArray, + LandscapeComponentNameArray, LandscapeLightmapValues ) ) + return false; + + //-------------------------------------------------------------------------------------------------- + // 3. Set the corresponding attributes in Houdini + //-------------------------------------------------------------------------------------------------- + + // Create point attribute info containing positions. + if ( !FHoudiniLandscapeUtils::AddLandscapePositionAttribute( DisplayGeoInfo.nodeId, LandscapePositionArray ) ) + return false; + + // Create point attribute info containing normals. + if ( !FHoudiniLandscapeUtils::AddLandscapeNormalAttribute( DisplayGeoInfo.nodeId, LandscapeNormalArray ) ) + return false; + + // Create point attribute info containing UVs. + if ( !FHoudiniLandscapeUtils::AddLandscapeUVAttribute( DisplayGeoInfo.nodeId, LandscapeUVArray ) ) + return false; + + // Create point attribute containing landscape component vertex indices (indices of vertices within the grid - x,y). + if ( !FHoudiniLandscapeUtils::AddLandscapeComponentVertexIndicesAttribute( DisplayGeoInfo.nodeId, LandscapeComponentVertexIndicesArray ) ) + return false; + + // Create point attribute containing landscape component name. + if ( !FHoudiniLandscapeUtils::AddLandscapeComponentNameAttribute( DisplayGeoInfo.nodeId, LandscapeComponentNameArray ) ) + return false; + + // Create point attribute info containing lightmap information. + if ( bExportLighting ) + { + if ( !FHoudiniLandscapeUtils::AddLandscapeLightmapColorAttribute( DisplayGeoInfo.nodeId, LandscapeLightmapValues ) ) + return false; + } + + // Set indices if we are exporting full geometry. + if ( bExportGeometryAsMesh ) + { + if (!FHoudiniLandscapeUtils::AddLandscapeMeshIndicesAndMaterialsAttribute( + DisplayGeoInfo.nodeId, bExportMaterials, + ComponentSizeQuads, QuadCount, + LandscapeProxy, SelectedComponents ) ) + return false; + } + + // If we are marshalling material information. + if ( bExportMaterials ) + { + if ( !FHoudiniLandscapeUtils::AddLandscapeGlobalMaterialAttribute( DisplayGeoInfo.nodeId, LandscapeProxy ) ) + return false; + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId ), false ); + +#endif + + return true; +} + + +bool +FHoudiniEngineUtils::HapiCreateInputNodeForSpline( + HAPI_NodeId HostAssetId, + USplineComponent * SplineComponent, + HAPI_NodeId & ConnectedAssetId, + FHoudiniAssetInputOutlinerMesh& OutlinerMesh, + const float& SplineResolution ) +{ +#if WITH_EDITOR + + // If we don't have a spline component, or host asset is invalid, there's nothing to do. + if ( !SplineComponent || SplineComponent->IsPendingKill() || !FHoudiniEngineUtils::IsHoudiniNodeValid( HostAssetId ) ) + return false; + + float fSplineResolution = SplineResolution; + if (fSplineResolution == -1.0f) + { + // Get runtime settings and extract the spline resolution from it + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + fSplineResolution = HoudiniRuntimeSettings->MarshallingSplineResolution; + else + fSplineResolution = HAPI_UNREAL_PARAM_SPLINE_RESOLUTION_DEFAULT; + } + + int32 nNumberOfControlPoints = SplineComponent->GetNumberOfSplinePoints(); + float fSplineLength = SplineComponent->GetSplineLength(); + + // Calculate the number of refined point we want + int32 nNumberOfRefinedSplinePoints = fSplineResolution > 0.0f ? ceil(fSplineLength / fSplineResolution) + 1 : nNumberOfControlPoints; + + // Array that will store the attributes we want to add to the curves + TArray tRefinedSplinePositions; + TArray tRefinedSplineRotations; + // Scale on Unreal's spline will require some tweaking, as the XScale is always 1 + TArray tRefinedSplineScales; + + if ( (nNumberOfRefinedSplinePoints < nNumberOfControlPoints) || (fSplineResolution <= 0.0f) ) + { + // There's not enough refined points, so we'll use the Spline CVs instead + tRefinedSplinePositions.SetNumZeroed(nNumberOfControlPoints); + tRefinedSplineRotations.SetNumZeroed(nNumberOfControlPoints); + tRefinedSplineScales.SetNumZeroed(nNumberOfControlPoints); + + FVector Scale; + for (int32 n = 0; n < nNumberOfControlPoints; n++) + { + tRefinedSplinePositions[n] = SplineComponent->GetLocationAtSplinePoint(n, ESplineCoordinateSpace::Local); + tRefinedSplineRotations[n] = SplineComponent->GetQuaternionAtSplinePoint(n, ESplineCoordinateSpace::World); + + Scale = SplineComponent->GetScaleAtSplinePoint(n); + tRefinedSplineScales[n] = Scale; + } + } + else + { + // Calculating the refined spline points + tRefinedSplinePositions.SetNumZeroed(nNumberOfRefinedSplinePoints); + tRefinedSplineRotations.SetNumZeroed(nNumberOfRefinedSplinePoints); + tRefinedSplineScales.SetNumZeroed(nNumberOfRefinedSplinePoints); + + FVector Scale; + float fCurrentDistance = 0.0f; + for (int32 n = 0; n < nNumberOfRefinedSplinePoints; n++) + { + tRefinedSplinePositions[n] = SplineComponent->GetLocationAtDistanceAlongSpline(fCurrentDistance, ESplineCoordinateSpace::Local); + tRefinedSplineRotations[n] = SplineComponent->GetQuaternionAtDistanceAlongSpline(fCurrentDistance, ESplineCoordinateSpace::World); + + Scale = SplineComponent->GetScaleAtDistanceAlongSpline(fCurrentDistance); + + tRefinedSplineScales[n] = Scale; + + fCurrentDistance += fSplineResolution; + } + } + + if ( !HapiCreateCurveInputNodeForData( + HostAssetId, + ConnectedAssetId, + &tRefinedSplinePositions, + &tRefinedSplineRotations, + &tRefinedSplineScales, + nullptr, + SplineComponent->IsClosedLoop() ) ) + return false; + + // Updating the OutlinerMesh's struct infos + OutlinerMesh.SplineResolution = fSplineResolution; + OutlinerMesh.SplineLength = fSplineLength; + OutlinerMesh.NumberOfSplineControlPoints = nNumberOfControlPoints; + + // We also need to extract all the CV's Transform to check for modifications + OutlinerMesh.SplineControlPointsTransform.SetNum( nNumberOfControlPoints ); + for ( int32 n = 0; n < nNumberOfControlPoints; n++ ) + { + // Getting the Local Transform for positions and scale + OutlinerMesh.SplineControlPointsTransform[ n ] = SplineComponent->GetTransformAtSplinePoint( n, ESplineCoordinateSpace::Local, true ); + + // ... but we used we used the world rotation + OutlinerMesh.SplineControlPointsTransform[ n ].SetRotation( SplineComponent->GetQuaternionAtSplinePoint(n, ESplineCoordinateSpace::World ) ); + } + + // Add the spline component's tag if it has some + bool NeedToCommit = FHoudiniEngineUtils::CreateGroupOrAttributeFromTags( ConnectedAssetId, 0, SplineComponent->ComponentTags, false); + + // Add the parent actor's tag if it has some + AActor* ParentActor = SplineComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + // Try to create groups for the parent Actor's tags + if ( FHoudiniEngineUtils::CreateGroupOrAttributeFromTags( ConnectedAssetId, 0, ParentActor->Tags, false ) ) + NeedToCommit = true; + } + + if ( NeedToCommit ) + { + // We successfully added tags to the geo, so we need to commit the changes + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), ConnectedAssetId) ) + HOUDINI_LOG_WARNING( TEXT("Could not create groups for the spline input's tags!") ); + } + + // Cook the spline node. + FHoudiniApi::CookNode( FHoudiniEngine::Get().GetSession(), ConnectedAssetId, nullptr ); + +#endif + + return true; +} + +bool +FHoudiniEngineUtils::HapiCreateInputNodeForStaticMesh( + UStaticMesh * StaticMesh, + HAPI_NodeId & ConnectedAssetId, + TArray< HAPI_NodeId >& OutCreatedNodeIds, + UStaticMeshComponent* StaticMeshComponent /* = nullptr */, + const bool& ExportAllLODs /* = false */, + const bool& ExportSockets /* = false */) +{ +#if WITH_EDITOR + + // If we don't have a static mesh there's nothing to do. + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + return false; + + // Export sockets if there are some + bool DoExportSockets = ExportSockets && ( StaticMesh->Sockets.Num() > 0 ); + + // Export LODs if there are some + bool DoExportLODs = ExportAllLODs && ( StaticMesh->GetNumLODs() > 1 ); + + // We need to use a merge node if we export lods OR sockets + bool UseMergeNode = DoExportLODs || DoExportSockets; + + if ( UseMergeNode ) + { + // Create a merge SOP asset. This will be our "ConnectedAssetId". + // All the different LOD meshes will be plugged into it + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), -1, + "SOP/merge", "input", true, &ConnectedAssetId ), false ); + } + else if ( ConnectedAssetId < 0 ) + { + // No LOD, we need a single input node + // If connected asset id is invalid, we need to create an input node. + HAPI_NodeId InputNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, nullptr ), false ); + + // Check if we have a valid id for this new input asset. + if ( !FHoudiniEngineUtils::IsHoudiniNodeValid( InputNodeId ) ) + return false; + + // We now have a valid id. + ConnectedAssetId = InputNodeId; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InputNodeId, nullptr ), false ); + } + + // Get the input's parent OBJ node + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId( ConnectedAssetId ); + OutCreatedNodeIds.AddUnique( ParentId ); + + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + check( HoudiniRuntimeSettings ); + + float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION; + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + int32 GeneratedLightMapResolution = 32; + + if ( HoudiniRuntimeSettings ) + { + GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; + } + + int32 NumLODsToExport = DoExportLODs ? StaticMesh->GetNumLODs() : 1; + for ( int32 LODIndex = 0; LODIndex < NumLODsToExport; LODIndex++ ) + { + // Grab the LOD level. + FStaticMeshSourceModel & SrcModel = StaticMesh->GetSourceModel(LODIndex); + + // If we're using a merge node, we need to create a new input null + HAPI_NodeId CurrentLODNodeId = -1; + if ( UseMergeNode ) + { + // Create a new input node for the current LOD + const char * LODName = ""; + { + FString LOD = TEXT( "lod" ) + FString::FromInt( LODIndex ); + LODName = TCHAR_TO_UTF8( *LOD ); + } + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), ParentId, "null", LODName, false, &CurrentLODNodeId ), false ); + } + else + { + // No merge node, just use the input node we created before + CurrentLODNodeId = ConnectedAssetId; + } + + // Load the existing raw mesh. + FRawMesh RawMesh; + SrcModel.LoadRawMesh(RawMesh); + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + //FMemory::Memzero< HAPI_PartInfo >( Part ); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[ HAPI_ATTROWNER_POINT ] = 0; + Part.attributeCounts[ HAPI_ATTROWNER_PRIM ] = 0; + Part.attributeCounts[ HAPI_ATTROWNER_VERTEX ] = 0; + Part.attributeCounts[ HAPI_ATTROWNER_DETAIL ] = 0; + Part.vertexCount = RawMesh.WedgeIndices.Num(); + Part.faceCount = RawMesh.WedgeIndices.Num() / 3; + Part.pointCount = RawMesh.VertexPositions.Num(); + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, 0, &Part ), false ); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = RawMesh.VertexPositions.Num(); + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint ), false ); + + // Grab the build scale + FVector BuildScaleVector = SrcModel.BuildSettings.BuildScale3D; + + // Extract vertices from static mesh. + TArray< float > StaticMeshVertices; + StaticMeshVertices.SetNumZeroed( RawMesh.VertexPositions.Num() * 3 ); + for ( int32 VertexIdx = 0; VertexIdx < RawMesh.VertexPositions.Num(); ++VertexIdx ) + { + // Grab vertex at this index. + const FVector & PositionVector = RawMesh.VertexPositions[ VertexIdx ]; + + if ( ImportAxis == HRSAI_Unreal ) + { + StaticMeshVertices[ VertexIdx * 3 + 0 ] = PositionVector.X / GeneratedGeometryScaleFactor * BuildScaleVector.X; + StaticMeshVertices[ VertexIdx * 3 + 1 ] = PositionVector.Z / GeneratedGeometryScaleFactor * BuildScaleVector.Z; + StaticMeshVertices[ VertexIdx * 3 + 2 ] = PositionVector.Y / GeneratedGeometryScaleFactor * BuildScaleVector.Y; + } + else if ( ImportAxis == HRSAI_Houdini ) + { + StaticMeshVertices[ VertexIdx * 3 + 0 ] = PositionVector.X / GeneratedGeometryScaleFactor * BuildScaleVector.X; + StaticMeshVertices[ VertexIdx * 3 + 1 ] = PositionVector.Y / GeneratedGeometryScaleFactor * BuildScaleVector.Y; + StaticMeshVertices[ VertexIdx * 3 + 2 ] = PositionVector.Z / GeneratedGeometryScaleFactor * BuildScaleVector.Z; + } + else + { + // Not valid enum value. + check( 0 ); + } + } + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, + 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + StaticMeshVertices.GetData(), 0, + AttributeInfoPoint.count ), false ); + + // See if we have texture coordinates to upload. + for ( int32 MeshTexCoordIdx = 0; MeshTexCoordIdx < MAX_STATIC_TEXCOORDS; ++MeshTexCoordIdx ) + { + int32 StaticMeshUVCount = RawMesh.WedgeTexCoords[ MeshTexCoordIdx ].Num(); + + if ( StaticMeshUVCount > 0 ) + { + const TArray< FVector2D > & RawMeshUVs = RawMesh.WedgeTexCoords[ MeshTexCoordIdx ]; + TArray< FVector > StaticMeshUVs; + StaticMeshUVs.Reserve( StaticMeshUVCount ); + + // Transfer UV data. + for ( int32 UVIdx = 0; UVIdx < StaticMeshUVCount; ++UVIdx ) + StaticMeshUVs.Emplace( RawMeshUVs[ UVIdx ].X, 1.0 - RawMeshUVs[ UVIdx ].Y, 0 ); + + if ( ImportAxis == HRSAI_Unreal ) + { + // We need to re-index UVs for wedges we swapped (due to winding differences). + for ( int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3 ) + { + // We do not touch wedge 0 of this triangle. + StaticMeshUVs.SwapMemory( WedgeIdx + 1, WedgeIdx + 2 ); + } + } + else if ( ImportAxis == HRSAI_Houdini ) + { + // Do nothing, data is in proper format. + } + else + { + // Not valid enum value. + check( 0 ); + } + + // Construct attribute name for this index. + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + + if ( MeshTexCoordIdx > 0 ) + UVAttributeName += FString::Printf(TEXT("%d"), MeshTexCoordIdx + 1); + + // Create attribute for UVs + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoVertex ); + AttributeInfoVertex.count = StaticMeshUVCount; + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, + 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurrentLODNodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex, + (const float *) StaticMeshUVs.GetData(), 0, AttributeInfoVertex.count ), false ); + } + } + + // See if we have normals to upload. + if ( RawMesh.WedgeTangentZ.Num() > 0 ) + { + TArray< FVector > ChangedNormals( RawMesh.WedgeTangentZ ); + if ( ImportAxis == HRSAI_Unreal ) + { + // We need to re-index normals for wedges we swapped (due to winding differences). + for ( int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3 ) + { + FVector TangentZ1 = ChangedNormals[ WedgeIdx + 1 ]; + FVector TangentZ2 = ChangedNormals[ WedgeIdx + 2 ]; + + ChangedNormals[ WedgeIdx + 1 ] = TangentZ2; + ChangedNormals[ WedgeIdx + 2 ] = TangentZ1; + } + + // We also need to swap the vector's Y and Z components + for ( int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); ++WedgeIdx ) + Swap( ChangedNormals[ WedgeIdx ].Y, ChangedNormals[ WedgeIdx ].Z ); + } + + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoVertex ); + AttributeInfoVertex.count = ChangedNormals.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, + 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurrentLODNodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex, + (const float *) ChangedNormals.GetData(), + 0, AttributeInfoVertex.count ), false ); + } + + // See if we have tangentu to upload. + if (RawMesh.WedgeTangentX.Num() > 0) + { + TArray< FVector > ChangedTangentU(RawMesh.WedgeTangentX); + if (ImportAxis == HRSAI_Unreal) + { + // We need to re-index tangents for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + FVector TangentU1 = ChangedTangentU[WedgeIdx + 1]; + FVector TangentU2 = ChangedTangentU[WedgeIdx + 2]; + + ChangedTangentU[WedgeIdx + 1] = TangentU2; + ChangedTangentU[WedgeIdx + 2] = TangentU1; + } + + // We also need to swap the vector's Y and Z components + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); ++WedgeIdx) + Swap(ChangedTangentU[WedgeIdx].Y, ChangedTangentU[WedgeIdx].Z); + } + + // Create attribute for tangentu. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoVertex); + AttributeInfoVertex.count = ChangedTangentU.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, + 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurrentLODNodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, + (const float *)ChangedTangentU.GetData(), + 0, AttributeInfoVertex.count), false); + } + + // See if we have tangentv to upload. + if (RawMesh.WedgeTangentY.Num() > 0) + { + TArray< FVector > ChangedTangentV(RawMesh.WedgeTangentY); + if (ImportAxis == HRSAI_Unreal) + { + // We need to re-index normals for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + FVector TangentV1 = ChangedTangentV[WedgeIdx + 1]; + FVector TangentV2 = ChangedTangentV[WedgeIdx + 2]; + + ChangedTangentV[WedgeIdx + 1] = TangentV2; + ChangedTangentV[WedgeIdx + 2] = TangentV1; + } + + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); ++WedgeIdx) + Swap(ChangedTangentV[WedgeIdx].Y, ChangedTangentV[WedgeIdx].Z); + } + + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoVertex); + AttributeInfoVertex.count = ChangedTangentV.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, + 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurrentLODNodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, + (const float *)ChangedTangentV.GetData(), + 0, AttributeInfoVertex.count), false); + } + + { + // If we have instance override vertex colors, first propagate them to our copy of + // the RawMesh Vert Colors + TArray< FLinearColor > ChangedColors; + if ( StaticMeshComponent && + StaticMeshComponent->LODData.IsValidIndex( LODIndex ) && + StaticMeshComponent->LODData[LODIndex].OverrideVertexColors && + StaticMesh->RenderData && + StaticMesh->RenderData->LODResources.IsValidIndex( LODIndex ) ) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[LODIndex]; + FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; + FStaticMeshLODResources& RenderModel = RenderData.LODResources[LODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices() ) + { + // Use the wedge map if it is available as it is lossless. + int32 NumWedges = RawMesh.WedgeIndices.Num(); + if ( RenderModel.WedgeMap.Num() == NumWedges ) + { + int32 NumExistingColors = RawMesh.WedgeColors.Num(); + if ( NumExistingColors < NumWedges ) + { + RawMesh.WedgeColors.AddUninitialized( NumWedges - NumExistingColors ); + } + + // Replace mesh colors with override colors + for ( int32 i = 0; i < NumWedges; ++i ) + { + FColor WedgeColor = FColor::White; + int32 Index = RenderModel.WedgeMap[i]; + if ( Index != INDEX_NONE ) + { + WedgeColor = ColorVertexBuffer.VertexColor( Index ); + } + RawMesh.WedgeColors[i] = WedgeColor; + } + } + } + } + + // See if we have colors to upload. + if ( RawMesh.WedgeColors.Num() > 0 ) + { + ChangedColors.SetNumUninitialized( RawMesh.WedgeColors.Num() ); + + if ( ImportAxis == HRSAI_Unreal ) + { + // We need to re-index colors for wedges we swapped (due to winding differences). + for ( int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3 ) + { + ChangedColors[WedgeIdx + 0] = RawMesh.WedgeColors[WedgeIdx + 0].ReinterpretAsLinear(); + ChangedColors[WedgeIdx + 1] = RawMesh.WedgeColors[WedgeIdx + 2].ReinterpretAsLinear(); + ChangedColors[WedgeIdx + 2] = RawMesh.WedgeColors[WedgeIdx + 1].ReinterpretAsLinear(); + } + } + else if ( ImportAxis == HRSAI_Houdini ) + { + for ( int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); ++WedgeIdx ) + { + ChangedColors[WedgeIdx] = RawMesh.WedgeColors[WedgeIdx].ReinterpretAsLinear(); + } + } + else + { + // Not valid enum value. + check( 0 ); + } + } + + if ( ChangedColors.Num() > 0 ) + { + // Extract the RGB colors + TArray ColorValues; + ColorValues.SetNum(ChangedColors.Num() * 3); + for (int32 colorIndex = 0; colorIndex < ChangedColors.Num(); colorIndex++) + { + ColorValues[colorIndex * 3] = ChangedColors[colorIndex].R; + ColorValues[colorIndex * 3 + 1] = ChangedColors[colorIndex].G; + ColorValues[colorIndex * 3 + 2] = ChangedColors[colorIndex].B; + } + + // Create attribute for colors. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoVertex ); + AttributeInfoVertex.count = ChangedColors.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, + 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurrentLODNodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, + ColorValues.GetData(), 0, AttributeInfoVertex.count ), false ); + + // Create the attribute for Alpha + TArray AlphaValues; + AlphaValues.SetNum(ChangedColors.Num()); + for (int32 alphaIndex = 0; alphaIndex < ChangedColors.Num(); alphaIndex++) + AlphaValues[alphaIndex] = ChangedColors[alphaIndex].A; + + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoVertex); + AttributeInfoVertex.count = AlphaValues.Num(); + AttributeInfoVertex.tupleSize = 1; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, + 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurrentLODNodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, + AlphaValues.GetData(), 0, AttributeInfoVertex.count), false); + + } + } + + // Extract indices from static mesh. + if ( RawMesh.WedgeIndices.Num() > 0 ) + { + TArray< int32 > StaticMeshIndices; + StaticMeshIndices.SetNumUninitialized( RawMesh.WedgeIndices.Num() ); + + if ( ImportAxis == HRSAI_Unreal ) + { + for ( int32 IndexIdx = 0; IndexIdx < RawMesh.WedgeIndices.Num(); IndexIdx += 3 ) + { + // Swap indices to fix winding order. + StaticMeshIndices[ IndexIdx + 0 ] = RawMesh.WedgeIndices[ IndexIdx + 0 ]; + StaticMeshIndices[ IndexIdx + 1 ] = RawMesh.WedgeIndices[ IndexIdx + 2 ]; + StaticMeshIndices[ IndexIdx + 2 ] = RawMesh.WedgeIndices[ IndexIdx + 1 ]; + } + } + else if ( ImportAxis == HRSAI_Houdini ) + { + StaticMeshIndices = TArray< int32 >( RawMesh.WedgeIndices ); + } + else + { + // Not valid enum value. + check( 0 ); + } + + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, + 0, StaticMeshIndices.GetData(), 0, StaticMeshIndices.Num() ), false ); + + // We need to generate array of face counts. + TArray< int32 > StaticMeshFaceCounts; + StaticMeshFaceCounts.Init( 3, Part.faceCount ); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, + 0, StaticMeshFaceCounts.GetData(), 0, StaticMeshFaceCounts.Num() ), false ); + } + + // Marshall face material indices. + if ( RawMesh.FaceMaterialIndices.Num() > 0 ) + { + // TODO: FIX ME PROPERLY + // In some cases, deleted/unused materials could cause crashes in FHoudiniEngineUtils::CreateFaceMaterialArray() later + // To avoid this, we need to make sure that the MaterialInterfaces array size matches the face material indexes.. + // Proper fix would be to export the mesh via section... + int32 MaxMatIndex = 0; + TArray< UMaterialInterface * > MaterialInterfaces; + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel() ) + { + //StaticMeshComponent->GetUsedMaterials(MaterialInterfaces, false); + { + // Get the map of material used for the static mesh component + TMap MapOfMaterials; + for (int32 LODIdx = 0; LODIdx < StaticMeshComponent->GetStaticMesh()->RenderData->LODResources.Num(); LODIdx++) + { + FStaticMeshLODResources& LODResources = StaticMeshComponent->GetStaticMesh()->RenderData->LODResources[LODIdx]; + for (int32 SectionIndex = 0; SectionIndex < LODResources.Sections.Num(); SectionIndex++) + { + // Get the material for each element at the current lod index + int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; + if (!MapOfMaterials.Contains(MaterialIndex)) + { + MapOfMaterials.Add(MaterialIndex, StaticMeshComponent->GetMaterial(MaterialIndex)); + + // Keep track of the max material index + if (MaxMatIndex < MaterialIndex) + MaxMatIndex = MaterialIndex; + } + } + } + + // We need to assign enough materials for our index + MaterialInterfaces.SetNum(MaxMatIndex + 1); + for (const auto& Kvp : MapOfMaterials) + { + MaterialInterfaces[Kvp.Key] = Kvp.Value; + } + } + + // TODO: FIX ME PROPERLY + // Trying to fix up inconsistencies between the RawMesh / StaticMesh material indexes by using the meshes sections... + int NumMeshBasedMtrls = StaticMeshComponent->GetNumMaterials(); + TArray< UMaterialInterface * > MeshBasedMaterialInterfaces; + MeshBasedMaterialInterfaces.SetNumUninitialized(NumMeshBasedMtrls); + for (int i = 0; i < NumMeshBasedMtrls; i++) + MeshBasedMaterialInterfaces[i] = StaticMeshComponent->GetMaterial(i); + + int32 NumSections = StaticMesh->GetNumSections(LODIndex); + if ( NumSections > NumMeshBasedMtrls ) + MaterialInterfaces.SetNumUninitialized(NumSections); + + for (int SectionIndex = 0; SectionIndex < NumSections; SectionIndex++) + { + FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex); + check(Info.MaterialIndex < NumMeshBasedMtrls); + MaterialInterfaces[SectionIndex] = MeshBasedMaterialInterfaces[Info.MaterialIndex]; + } + } + else + { + // Query the Static mesh's materials + for (int32 MatIdx = 0; MatIdx < StaticMesh->StaticMaterials.Num(); MatIdx++) + { + MaterialInterfaces.Add( StaticMesh->GetMaterial(MatIdx) ); + } + + // Try to fix up inconsistencies between the RawMesh / StaticMesh material indexes + // by using the meshes sections... + // TODO: Fix me properly! + // Proper fix would be to export the meshes via the FStaticMeshLODResources obtained + // by GetLODForExport(), and then export the mesh by sections. + if ( StaticMesh->RenderData && StaticMesh->RenderData->LODResources.IsValidIndex(LODIndex) ) + { + TMap MapOfMaterials; + FStaticMeshLODResources& LODResources = StaticMesh->RenderData->LODResources[LODIndex]; + for (int32 SectionIndex = 0; SectionIndex < LODResources.Sections.Num(); SectionIndex++) + { + // Get the material for each element at the current lod index + int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; + if (!MapOfMaterials.Contains(MaterialIndex)) + { + MapOfMaterials.Add(MaterialIndex, StaticMesh->GetMaterial(MaterialIndex)); + } + } + + if ( MapOfMaterials.Num() > 0 ) + { + // Sort the output material in the correct order (by material index) + MapOfMaterials.KeySort( [](int32 A, int32 B) { return A < B; }); + + // Set the value in the correct order + // Do not reduce the array of materials, this could cause crahses in some weird cases.. + if (MapOfMaterials.Num() > MaterialInterfaces.Num()) + MaterialInterfaces.SetNumZeroed(MapOfMaterials.Num()); + + int32 MaterialIndex = 0; + for (auto Kvp : MapOfMaterials) + { + MaterialInterfaces[MaterialIndex++] = Kvp.Value; + } + } + } + } + + // Create list of materials, one for each face. + TArray< char * > StaticMeshFaceMaterials; + FHoudiniEngineUtils::CreateFaceMaterialArray( + MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials ); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeName = HAPI_UNREAL_ATTRIB_MATERIAL; + if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeMaterial.IsEmpty() ) + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeMaterial, MarshallingAttributeName ); + + // Create attribute for materials. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoMaterial ); + AttributeInfoMaterial.count = RawMesh.FaceMaterialIndices.Num(); + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + bool bAttributeError = false; + + if ( FHoudiniApi::AddAttribute( FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, 0, + MarshallingAttributeName.c_str(), &AttributeInfoMaterial ) != HAPI_RESULT_SUCCESS ) + { + bAttributeError = true; + } + + if ( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + CurrentLODNodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfoMaterial, + (const char **) StaticMeshFaceMaterials.GetData(), 0, + StaticMeshFaceMaterials.Num() ) != HAPI_RESULT_SUCCESS ) + { + bAttributeError = true; + } + + // Delete material names. + FHoudiniEngineUtils::DeleteFaceMaterialArray( StaticMeshFaceMaterials ); + + if ( bAttributeError ) + { + check( 0 ); + return false; + } + } + + // Marshall face smoothing masks. + if ( RawMesh.FaceSmoothingMasks.Num() > 0 ) + { + // Get name of attribute used for marshalling face smoothing masks. + std::string MarshallingAttributeName = HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK; + if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeMaterial.IsEmpty() ) + { + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeFaceSmoothingMask, + MarshallingAttributeName ); + } + + HAPI_AttributeInfo AttributeInfoSmoothingMasks; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoSmoothingMasks ); + AttributeInfoSmoothingMasks.count = RawMesh.FaceSmoothingMasks.Num(); + AttributeInfoSmoothingMasks.tupleSize = 1; + AttributeInfoSmoothingMasks.exists = true; + AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; + AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, + 0, MarshallingAttributeName.c_str(), &AttributeInfoSmoothingMasks ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + CurrentLODNodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfoSmoothingMasks, + (const int32 *) RawMesh.FaceSmoothingMasks.GetData(), 0, RawMesh.FaceSmoothingMasks.Num() ), false ); + } + + // Marshall lightmap resolution. + if ( StaticMesh->LightMapResolution != GeneratedLightMapResolution ) + { + std::string MarshallingAttributeName = HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION; + if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeLightmapResolution.IsEmpty() ) + { + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeLightmapResolution, + MarshallingAttributeName ); + } + + TArray< int32 > LightMapResolutions; + LightMapResolutions.Add( StaticMesh->LightMapResolution ); + + HAPI_AttributeInfo AttributeInfoLightMapResolution; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoLightMapResolution ); + AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); + AttributeInfoLightMapResolution.tupleSize = 1; + AttributeInfoLightMapResolution.exists = true; + AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; + AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, + 0, MarshallingAttributeName.c_str(), &AttributeInfoLightMapResolution ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + CurrentLODNodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfoLightMapResolution, + (const int32 *) LightMapResolutions.GetData(), 0, LightMapResolutions.Num() ), false ); + } + + if ( !HoudiniRuntimeSettings->MarshallingAttributeInputMeshName.IsEmpty() ) + { + // Create primitive attribute with mesh asset path + const FString MeshAssetPath = StaticMesh->GetPathName(); + std::string MeshAssetPathCStr = TCHAR_TO_ANSI( *MeshAssetPath ); + const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized( Part.faceCount ); + for ( int32 Ix = 0; Ix < Part.faceCount; ++Ix ) + { + PrimitiveAttrs[ Ix ] = MeshAssetPathRaw; + } + + std::string MarshallingAttributeName; + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeInputMeshName, + MarshallingAttributeName ); + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfo ); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, + 0, MarshallingAttributeName.c_str(), &AttributeInfo ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + CurrentLODNodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num() ), false ); + } + + if( !HoudiniRuntimeSettings->MarshallingAttributeInputSourceFile.IsEmpty() ) + { + FString Filename; + // Create primitive attribute with mesh asset path + if( UAssetImportData* ImportData = StaticMesh->AssetImportData ) + { + for( const auto& SourceFile : ImportData->SourceData.SourceFiles ) + { + Filename = UAssetImportData::ResolveImportFilename( SourceFile.RelativeFilename, ImportData->GetOutermost() ); + break; + } + } + + if( !Filename.IsEmpty() ) + { + std::string FilenameCStr = TCHAR_TO_ANSI( *Filename ); + const char* FilenameCStrRaw = FilenameCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized( Part.faceCount ); + for( int32 Ix = 0; Ix < Part.faceCount; ++Ix ) + { + PrimitiveAttrs[Ix] = FilenameCStrRaw; + } + + std::string MarshallingAttributeName; + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeInputSourceFile, + MarshallingAttributeName ); + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfo ); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, + 0, MarshallingAttributeName.c_str(), &AttributeInfo ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + CurrentLODNodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num() ), false ); + } + } + + // Check if we have vertex attribute data to add + if ( StaticMeshComponent && StaticMeshComponent->GetOwner() ) + { + if ( UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass() ) + { + bool bSuccess = DataComponent->Upload( CurrentLODNodeId, StaticMeshComponent ); + if ( !bSuccess ) + { + HOUDINI_LOG_ERROR( TEXT( "Upload of attribute data for %s failed" ), *StaticMeshComponent->GetOwner()->GetName() ); + } + } + } + + if ( DoExportLODs ) + { + // LOD Group + const char * LODGroupStr = ""; + { + FString LODGroup = TEXT("lod") + FString::FromInt(LODIndex); + LODGroupStr = TCHAR_TO_UTF8(*LODGroup); + } + + // Add a LOD group + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, 0, + HAPI_GROUPTYPE_PRIM, LODGroupStr), false); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, 0, + HAPI_GROUPTYPE_PRIM, LODGroupStr, GroupArray.GetData(), 0, Part.faceCount ), false ); + + if ( !StaticMesh->bAutoComputeLODScreenSize ) + { + // Add the lodX_screensize + FString LODAttributeName = TEXT("lod") + FString::FromInt( LODIndex ) + TEXT("_screensize"); + + // Create screensize detail attribute info. + HAPI_AttributeInfo AttributeInfoLODScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoLODScreenSize ); + AttributeInfoLODScreenSize.count = 1; + AttributeInfoLODScreenSize.tupleSize = 1; + AttributeInfoLODScreenSize.exists = true; + AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, 0, + TCHAR_TO_UTF8( *LODAttributeName ), &AttributeInfoLODScreenSize), false); + + float lodscreensize = SrcModel.ScreenSize.Default; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, 0, + TCHAR_TO_UTF8( *LODAttributeName ), &AttributeInfoLODScreenSize, + &lodscreensize, 0, 1 ), false); + } + } + + if ( StaticMeshComponent && !StaticMeshComponent->IsPendingKill() ) + { + // Try to create groups for the static mesh component's tags + if ( StaticMeshComponent->ComponentTags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupOrAttributeFromTags( CurrentLODNodeId, 0, StaticMeshComponent->ComponentTags, false ) ) + HOUDINI_LOG_WARNING( TEXT("Could not create groups from the Static Mesh Component's tags!") ); + + AActor* ParentActor = StaticMeshComponent->GetOwner(); + if ( ParentActor && !ParentActor->IsPendingKill() ) + { + // Try to create groups for the parent Actor's tags + if ( ParentActor->Tags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupOrAttributeFromTags( CurrentLODNodeId, 0, ParentActor->Tags, false ) ) + HOUDINI_LOG_WARNING( TEXT("Could not create groups from the Static Mesh Component's parent actor tags!") ); + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), CurrentLODNodeId), false ); + + if ( UseMergeNode ) + { + // Now we can connect the LOD node to the input node. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, LODIndex, + CurrentLODNodeId, 0), false); + } + } + + if ( DoExportSockets ) + { + int32 NumSockets = StaticMesh->Sockets.Num(); + HAPI_NodeId SocketsNodeId = -1; + // Create a new input node for the sockets + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), ParentId, "null", "sockets", false, &SocketsNodeId ), false ); + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + //FMemory::Memzero< HAPI_PartInfo >( Part ); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[ HAPI_ATTROWNER_POINT ] = 0; + Part.attributeCounts[ HAPI_ATTROWNER_PRIM ] = 0; + Part.attributeCounts[ HAPI_ATTROWNER_VERTEX ] = 0; + Part.attributeCounts[ HAPI_ATTROWNER_DETAIL ] = 0; + Part.pointCount = NumSockets; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0, &Part), false); + + // Create POS point attribute info. + HAPI_AttributeInfo AttributeInfoPos; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPos ); + AttributeInfoPos.count = NumSockets; + AttributeInfoPos.tupleSize = 3; + AttributeInfoPos.exists = true; + AttributeInfoPos.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos ), false ); + + // Create Rot point attribute Info + HAPI_AttributeInfo AttributeInfoRot; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoRot ); + AttributeInfoRot.count = NumSockets; + AttributeInfoRot.tupleSize = 4; + AttributeInfoRot.exists = true; + AttributeInfoRot.owner = HAPI_ATTROWNER_POINT; + AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot ), false ); + + // Create scale point attribute Info + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoScale ); + AttributeInfoScale.count = NumSockets; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0, + HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale ), false ); + + // Create the name attrib info + HAPI_AttributeInfo AttributeInfoName; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoName ); + AttributeInfoName.count = NumSockets; + AttributeInfoName.tupleSize = 1; + AttributeInfoName.exists = true; + AttributeInfoName.owner = HAPI_ATTROWNER_POINT; + AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0, + HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName ), false ); + + // Create the tag attrib info + HAPI_AttributeInfo AttributeInfoTag; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoTag ); + AttributeInfoTag.count = NumSockets; + AttributeInfoTag.tupleSize = 1; + AttributeInfoTag.exists = true; + AttributeInfoTag.owner = HAPI_ATTROWNER_POINT; + AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0, + HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag ), false ); + + // Extract the sockets transform values + TArray< float > SocketPos; + SocketPos.SetNumZeroed( NumSockets * 3 ); + TArray< float > SocketRot; + SocketRot.SetNumZeroed( NumSockets * 4 ); + TArray< float > SocketScale; + SocketScale.SetNumZeroed( NumSockets * 3 ); + TArray< const char * > SocketNames; + TArray< const char * > SocketTags; + + for ( int32 Idx = 0; Idx < NumSockets; ++Idx ) + { + UStaticMeshSocket* CurrentSocket = StaticMesh->Sockets[ Idx ]; + if ( !CurrentSocket || CurrentSocket->IsPendingKill() ) + continue; + + // Get the socket's transform and convert it to HapiTransform + FTransform SocketTransform( CurrentSocket->RelativeRotation, CurrentSocket->RelativeLocation, CurrentSocket->RelativeScale ); + HAPI_Transform HapiSocketTransform; + FHoudiniApi::Transform_Init(&HapiSocketTransform); + TranslateUnrealTransform( SocketTransform, HapiSocketTransform ); + + // Fill the attribute values + SocketPos[ 3 * Idx + 0 ] = HapiSocketTransform.position[ 0 ]; + SocketPos[ 3 * Idx + 1 ] = HapiSocketTransform.position[ 1 ]; + SocketPos[ 3 * Idx + 2 ] = HapiSocketTransform.position[ 2 ]; + + SocketRot[ 4 * Idx + 0 ] = HapiSocketTransform.rotationQuaternion[ 0 ]; + SocketRot[ 4 * Idx + 1 ] = HapiSocketTransform.rotationQuaternion[ 1 ]; + SocketRot[ 4 * Idx + 2 ] = HapiSocketTransform.rotationQuaternion[ 2 ]; + SocketRot[ 4 * Idx + 3 ] = HapiSocketTransform.rotationQuaternion[ 3 ]; + + SocketScale[ 3 * Idx + 0 ] = HapiSocketTransform.scale[ 0 ]; + SocketScale[ 3 * Idx + 1 ] = HapiSocketTransform.scale[ 1 ]; + SocketScale[ 3 * Idx + 2 ] = HapiSocketTransform.scale[ 2 ]; + + FString CurrentSocketName; + if ( !CurrentSocket->SocketName.IsNone() ) + CurrentSocketName = CurrentSocket->SocketName.ToString(); + else + CurrentSocketName = TEXT("Socket") + FString::FromInt( Idx ); + SocketNames.Add( FHoudiniEngineUtils::ExtractRawName( CurrentSocketName ) ); + + if ( !CurrentSocket->Tag.IsEmpty() ) + SocketTags.Add( FHoudiniEngineUtils::ExtractRawName( CurrentSocket->Tag ) ); + else + SocketTags.Add( "" ); + } + + //we can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + SocketsNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, + &AttributeInfoPos, + SocketPos.GetData(), + 0, AttributeInfoPos.count ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + SocketsNodeId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRot, + SocketRot.GetData(), + 0, AttributeInfoRot.count ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + SocketsNodeId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale, + SocketScale.GetData(), + 0, AttributeInfoScale.count ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + SocketsNodeId, 0, + HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, + &AttributeInfoName, + SocketNames.GetData(), + 0, AttributeInfoName.count ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + SocketsNodeId, 0, + HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, + &AttributeInfoTag, + SocketTags.GetData(), + 0, AttributeInfoTag.count ), false ); + + // We will also create the socket_details attributes + for ( int32 Idx = 0; Idx < NumSockets; ++Idx ) + { + // Build the current socket's prefix + FString SocketAttrPrefix = TEXT( HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX ) + FString::FromInt( Idx ); + + // Create mesh_socketX_pos attribute info. + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPos ); + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); + AttributeInfoPos.count = 1; + AttributeInfoPos.tupleSize = 3; + AttributeInfoPos.exists = true; + AttributeInfoPos.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; + + FString PosAttr = SocketAttrPrefix + TEXT("_pos"); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0, + TCHAR_TO_ANSI( *PosAttr ), &AttributeInfoPos ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + SocketsNodeId, 0, + TCHAR_TO_ANSI( *PosAttr ), + &AttributeInfoPos, + &(SocketPos[ 3 * Idx ]), + 0, AttributeInfoPos.count ), false ); + + // Create mesh_socketX_rot point attribute Info + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoRot ); + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); + AttributeInfoRot.count = 1; + AttributeInfoRot.tupleSize = 4; + AttributeInfoRot.exists = true; + AttributeInfoRot.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; + + FString RotAttr = SocketAttrPrefix + TEXT("_rot"); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0, + TCHAR_TO_ANSI( *RotAttr ), &AttributeInfoRot ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + SocketsNodeId, 0, + TCHAR_TO_ANSI( *RotAttr ), + &AttributeInfoRot, + &(SocketRot[ 4 * Idx ]), + 0, AttributeInfoRot.count ), false ); + + // Create mesh_socketX_scale point attribute Info + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoScale ); + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = 1; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + FString ScaleAttr = SocketAttrPrefix + TEXT("_scale"); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0, + TCHAR_TO_ANSI( *ScaleAttr ), &AttributeInfoScale ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + SocketsNodeId, 0, + TCHAR_TO_ANSI( *ScaleAttr ), + &AttributeInfoScale, + &(SocketScale[ 3 * Idx ]), + 0, AttributeInfoScale.count ), false ); + + // Create the mesh_socketX_name attrib info + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoName ); + FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); + AttributeInfoName.count = 1; + AttributeInfoName.tupleSize = 1; + AttributeInfoName.exists = true; + AttributeInfoName.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; + + FString NameAttr = SocketAttrPrefix + TEXT("_name"); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0, + TCHAR_TO_ANSI( *NameAttr ), &AttributeInfoName ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + SocketsNodeId, 0, + TCHAR_TO_ANSI( *NameAttr ), + &AttributeInfoName, + &(SocketNames[ Idx ]), + 0, AttributeInfoName.count ), false ); + + // Create the mesh_socketX_tag attrib info + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoTag ); + FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); + AttributeInfoTag.count = 1; + AttributeInfoTag.tupleSize = 1; + AttributeInfoTag.exists = true; + AttributeInfoTag.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; + + FString TagAttr = SocketAttrPrefix + TEXT("_tag"); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0, + TCHAR_TO_ANSI( *TagAttr ), &AttributeInfoTag ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + SocketsNodeId, 0, + TCHAR_TO_ANSI( *TagAttr ), + &AttributeInfoTag, + & ( SocketTags[ Idx ] ), + 0, AttributeInfoTag.count ), false ); + } + + // Now add the sockets group + const char * SocketGroupStr = "socket_imported"; + // Add a LOD group + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0, + HAPI_GROUPTYPE_POINT, SocketGroupStr ), false ); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init( 1, NumSockets ); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0, + HAPI_GROUPTYPE_POINT, SocketGroupStr, GroupArray.GetData(), 0, NumSockets ), false ); + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), SocketsNodeId ), false); + + // Now we can connect the socket node to the merge node's last input. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, NumLODsToExport, + SocketsNodeId, 0), false ); + } +#endif + + return true; +} + +bool +FHoudiniEngineUtils::HapiCreateInputNodeForWorldOutliner( + HAPI_NodeId HostAssetId, + TArray< FHoudiniAssetInputOutlinerMesh > & OutlinerMeshArray, + HAPI_NodeId & ConnectedAssetId, + TArray< HAPI_NodeId >& OutCreatedNodeIds, + const float& SplineResolution, + const bool& ExportAllLODs /* = false */, + const bool& ExportSockets /* = false */) +{ +#if WITH_EDITOR + if ( OutlinerMeshArray.Num() <= 0 ) + return false; + + bool UseMerge = OutlinerMeshArray.Num() > 0; + + if ( UseMerge ) + { + // If we have more than one input, create the merge SOP asset. + // This will be our "ConnectedAssetId". + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), -1, + "SOP/merge", nullptr, true, &ConnectedAssetId ), false ); + + // Add the merge node's parent OBJ to the created nodes + OutCreatedNodeIds.AddUnique( FHoudiniEngineUtils::HapiGetParentNodeId( ConnectedAssetId ) ); + } + + for ( int32 InputIdx = 0; InputIdx < OutlinerMeshArray.Num(); ++InputIdx ) + { + auto & OutlinerMesh = OutlinerMeshArray[ InputIdx ]; + + bool bInputCreated = false; + if ( OutlinerMesh.StaticMesh && !OutlinerMesh.StaticMesh->IsPendingKill() ) + { + // Creating an Input Node for Mesh Data + bInputCreated = HapiCreateInputNodeForStaticMesh( + OutlinerMesh.StaticMesh, + OutlinerMesh.AssetId, + OutCreatedNodeIds, + OutlinerMesh.StaticMeshComponent, + ExportAllLODs, ExportSockets ); + } + else if ( OutlinerMesh.SplineComponent && !OutlinerMesh.SplineComponent->IsPendingKill() ) + { + // Creating an input node for spline data + bInputCreated = HapiCreateInputNodeForSpline( + ConnectedAssetId, + OutlinerMesh.SplineComponent, + OutlinerMesh.AssetId, + OutlinerMesh, + SplineResolution ); + + OutCreatedNodeIds.AddUnique( FHoudiniEngineUtils::HapiGetParentNodeId( OutlinerMesh.AssetId ) ); + } + + if ( !bInputCreated ) + { + OutlinerMesh.AssetId = -1; + continue; + } + + if ( UseMerge ) + { + // Now we can connect the input node to the merge node. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, InputIdx, + OutlinerMesh.AssetId, 0), false ); + } + else + { + // We only have one input, use its Id as our "ConnectedAssetId" + ConnectedAssetId = OutlinerMesh.AssetId; + } + + // Updating the Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + //FMemory::Memzero< HAPI_TransformEuler >( HapiTransform ); + FHoudiniEngineUtils::TranslateUnrealTransform( OutlinerMesh.ComponentTransform, HapiTransform ); + + // Set it on the input mesh's parent OBJ node + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId( OutlinerMesh.AssetId ); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), ParentId, &HapiTransform ), false ); + } +#endif + return true; +} + +bool +FHoudiniEngineUtils::HapiCreateInputNodeForObjects( + HAPI_NodeId HostAssetId, TArray& InputObjects, const TArray< FTransform >& InputTransforms, + HAPI_NodeId & ConnectedAssetId, TArray< HAPI_NodeId >& OutCreatedNodeIds, + const bool& bExportSkeleton, const bool& bExportAllLODs /* = false */, const bool& bExportSockets /* = false */) +{ +#if WITH_EDITOR + if ( InputObjects.Num() <= 0 ) + return true; + + bool UseMergeNode = InputObjects.Num() > 1; + if ( UseMergeNode ) + { + // If we have more than one input mesh, create a merge SOP asset. This will be our "ConnectedAssetId". + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), -1, + "SOP/merge", nullptr, true, &ConnectedAssetId ), false ); + } + + for ( int32 InputIdx = 0; InputIdx < InputObjects.Num(); ++InputIdx ) + { + HAPI_NodeId MeshAssetNodeId = -1; + FTransform InputTransform = FTransform::Identity; + if ( InputTransforms.IsValidIndex( InputIdx ) ) + InputTransform = InputTransforms[ InputIdx ]; + + UStaticMesh* InputStaticMesh = Cast< UStaticMesh >( InputObjects[InputIdx] ); + USkeletalMesh* InputSkeletalMesh = Cast< USkeletalMesh >( InputObjects[InputIdx] ); + if ( InputStaticMesh && !InputStaticMesh->IsPendingKill() ) + { + // Creating an Input Node for Static Mesh Data + if ( !HapiCreateInputNodeForStaticMesh( InputStaticMesh, MeshAssetNodeId, OutCreatedNodeIds, nullptr, bExportAllLODs, bExportSockets ) ) + { + HOUDINI_LOG_WARNING( TEXT( "Error creating input index %d on %d" ), InputIdx, ConnectedAssetId ); + } + } + else if ( InputSkeletalMesh && !InputSkeletalMesh->IsPendingKill() ) + { + // Creating an Input Node for Skeletal Mesh Data + if ( !HapiCreateInputNodeForSkeletalMesh( ConnectedAssetId, InputSkeletalMesh, MeshAssetNodeId, OutCreatedNodeIds, bExportSkeleton ) ) + { + HOUDINI_LOG_WARNING( TEXT( "Error creating input index %d on %d" ), InputIdx, ConnectedAssetId ); + } + } + + if ( MeshAssetNodeId < 0 ) + continue; + + if ( UseMergeNode ) + { + // Now we can connect the input node to the merge node. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, InputIdx, + MeshAssetNodeId, 0), false ); + } + else + { + // We only have one input, use the MeshNodeId as our "ConnectedAssetId". + ConnectedAssetId = MeshAssetNodeId; + } + + // If the Geometry Input has a Transform offset + if ( !InputTransform.Equals( FTransform::Identity ) ) + { + // Updating the Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + //FMemory::Memzero< HAPI_TransformEuler >( HapiTransform ); + FHoudiniEngineUtils::TranslateUnrealTransform( InputTransform, HapiTransform ); + + HAPI_NodeInfo LocalAssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&LocalAssetNodeInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), MeshAssetNodeId, &LocalAssetNodeInfo ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), LocalAssetNodeInfo.parentId, &HapiTransform ), false ); + } + } +#endif + return true; +} + +bool +FHoudiniEngineUtils::HapiCreateInputNodeForSkeletalMesh( + HAPI_NodeId HostAssetId, USkeletalMesh * SkeletalMesh, + HAPI_NodeId & ConnectedAssetId, TArray< HAPI_NodeId >& OutCreatedNodeIds, + const bool& bExportSkeleton ) +{ +#if WITH_EDITOR + // If we don't have a skeletal mesh there's nothing to do. + if ( !SkeletalMesh || SkeletalMesh->IsPendingKill() ) + return false; + + // Check if connected asset id is valid, if it is not, we need to create an input asset. + if ( ConnectedAssetId < 0 ) + { + HAPI_NodeId AssetId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &AssetId, nullptr ), false ); + + // Check if we have a valid id for this new input asset. + if ( !FHoudiniEngineUtils::IsHoudiniNodeValid( AssetId ) ) + return false; + + // We now have a valid id. + ConnectedAssetId = AssetId; + + // Get the input's parent OBJ node + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId( ConnectedAssetId ); + OutCreatedNodeIds.AddUnique( ParentId ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), AssetId, nullptr ), false ); + } + + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + check( HoudiniRuntimeSettings ); + + float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION; + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + int32 GeneratedLightMapResolution = 32; + + if ( HoudiniRuntimeSettings ) + { + GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; + } + + // Grab base LOD level. + const FSkeletalMeshModel* SkelMeshResource = SkeletalMesh->GetImportedModel(); + if ( !SkelMeshResource ) + return false; + + const FSkeletalMeshLODModel& SourceModel = SkelMeshResource->LODModels[0]; + const int32 VertexCount = SourceModel.GetNumNonClothingVertices(); + + // Verify the integrity of the mesh. + if ( VertexCount <= 0 ) + return false; + + // Extract the vertices buffer (this also contains normals, uvs, colors...) + TArray SoftSkinVertices; + SourceModel.GetNonClothVertices( SoftSkinVertices ); + if ( SoftSkinVertices.Num() != VertexCount ) + return false; + + // Extract the indices + TArray Indices = SourceModel.IndexBuffer; + int32 FaceCount = Indices.Num() / 3; + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + //FMemory::Memzero< HAPI_PartInfo >(Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[ HAPI_ATTROWNER_POINT ] = 0; + Part.attributeCounts[ HAPI_ATTROWNER_PRIM ] = 0; + Part.attributeCounts[ HAPI_ATTROWNER_VERTEX ] = 0; + Part.attributeCounts[ HAPI_ATTROWNER_DETAIL ] = 0; + Part.vertexCount = Indices.Num(); + Part.faceCount = FaceCount; + Part.pointCount = SoftSkinVertices.Num(); + Part.type = HAPI_PARTTYPE_MESH; + + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &DisplayGeoInfo ), false ); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, 0, &Part ), false ); + + + //------------------------------------------------------------------------- + // POSITIONS + //------------------------------------------------------------------------- + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = SoftSkinVertices.Num(); + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint ), false ); + + // Convert vertices from the skeletal mesh to a float array + TArray< float > SkelMeshVertices; + SkelMeshVertices.SetNumZeroed( SoftSkinVertices.Num() * 3 ); + for ( int32 VertexIdx = 0; VertexIdx < SoftSkinVertices.Num(); ++VertexIdx ) + { + // Grab vertex at this index. + const FVector & PositionVector = SoftSkinVertices[ VertexIdx ].Position; + if ( ImportAxis == HRSAI_Unreal ) + { + SkelMeshVertices[ VertexIdx * 3 + 0 ] = PositionVector.X / GeneratedGeometryScaleFactor; + SkelMeshVertices[ VertexIdx * 3 + 1 ] = PositionVector.Z / GeneratedGeometryScaleFactor; + SkelMeshVertices[ VertexIdx * 3 + 2 ] = PositionVector.Y / GeneratedGeometryScaleFactor; + } + else + { + SkelMeshVertices[ VertexIdx * 3 + 0 ] = PositionVector.X / GeneratedGeometryScaleFactor; + SkelMeshVertices[ VertexIdx * 3 + 1 ] = PositionVector.Y / GeneratedGeometryScaleFactor; + SkelMeshVertices[ VertexIdx * 3 + 2 ] = PositionVector.Z / GeneratedGeometryScaleFactor; + } + } + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, + 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + SkelMeshVertices.GetData(), 0, + AttributeInfoPoint.count ), false ); + + + //------------------------------------------------------------------------- + // UVS + //------------------------------------------------------------------------- + // See if we have texture coordinates to upload. + for ( uint32 MeshTexCoordIdx = 0; MeshTexCoordIdx < SourceModel.NumTexCoords; ++MeshTexCoordIdx ) + { + TArray< FVector > MeshUVs; + MeshUVs.SetNumUninitialized( Indices.Num() ); + + // Transfer UV data. + for ( int32 UVIdx = 0; UVIdx < Indices.Num(); ++UVIdx ) + { + // Grab uv for this coordSet at this index. + const FVector2D & UV = SoftSkinVertices[ Indices[UVIdx] ].UVs[ MeshTexCoordIdx ]; + MeshUVs[ UVIdx ] = FVector(UV.X, 1.0 - UV.Y, 0); + } + + if ( ImportAxis == HRSAI_Unreal ) + { + // We need to re-index UVs due to swapped indices in the faces (due to winding order differences) + for ( int32 WedgeIdx = 0; WedgeIdx < Indices.Num(); WedgeIdx += 3 ) + { + // Swap second and third values for the vertices of the face + MeshUVs.SwapMemory( WedgeIdx + 1, WedgeIdx + 2 ); + } + } + + // Construct attribute name for this index. + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if ( MeshTexCoordIdx > 0 ) + UVAttributeName += FString::Printf(TEXT("%d"), MeshTexCoordIdx + 1); + + // Create attribute for UVs + HAPI_AttributeInfo AttributeInfoUVS; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoUVS); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoUVS ); + AttributeInfoUVS.count = MeshUVs.Num(); + AttributeInfoUVS.tupleSize = 3; + AttributeInfoUVS.exists = true; + AttributeInfoUVS.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoUVS.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoUVS.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, + 0, TCHAR_TO_ANSI( *UVAttributeName ), &AttributeInfoUVS), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + DisplayGeoInfo.nodeId, 0, TCHAR_TO_ANSI( *UVAttributeName ), &AttributeInfoUVS, + (const float *) MeshUVs.GetData(), 0, AttributeInfoUVS.count ), false ); + } + + + //------------------------------------------------------------------------- + // NORMALS + //------------------------------------------------------------------------- + TArray< FVector > MeshNormals; + MeshNormals.SetNumUninitialized( Indices.Num() ); + + // Transfer Normal data. + for (int32 NormalIdx = 0; NormalIdx < Indices.Num(); ++NormalIdx) + { + FPackedNormal PackedNormal = SoftSkinVertices[Indices[NormalIdx]].TangentZ; + MeshNormals[NormalIdx] = PackedNormal.ToFVector(); + + // Doesnt work on MacOS ... + //MeshNormals[ NormalIdx ] = FVector( SoftSkinVertices[ Indices[NormalIdx] ].TangentZ.Vector. ); + } + + if ( ImportAxis == HRSAI_Unreal ) + { + // We need to re-index normals due to swapped indices on the faces (due to winding differences). + for ( int32 WedgeIdx = 0; WedgeIdx < Indices.Num(); WedgeIdx += 3 ) + { + // Swap second and third values for the vertices of the face + MeshNormals.SwapMemory( WedgeIdx + 1, WedgeIdx + 2 ); + } + + // Also swap the normal's Y and Z + for ( int32 WedgeIdx = 0; WedgeIdx < Indices.Num(); ++WedgeIdx ) + Swap( MeshNormals[ WedgeIdx ].Y, MeshNormals[ WedgeIdx ].Z ); + } + + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoNormals; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoNormals); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoNormals ); + AttributeInfoNormals.count = MeshNormals.Num(); + AttributeInfoNormals.tupleSize = 3; + AttributeInfoNormals.exists = true; + AttributeInfoNormals.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoNormals.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoNormals.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, + 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoNormals ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoNormals, + (const float *)MeshNormals.GetData(), + 0, AttributeInfoNormals.count ), false ); + + + //------------------------------------------------------------------------- + // Vertex Colors + //------------------------------------------------------------------------- + TArray< FLinearColor > MeshColors; + MeshColors.SetNumUninitialized( Indices.Num() ); + + // Transfer Color data. + for (int32 ColorIdx = 0; ColorIdx < Indices.Num(); ++ColorIdx) + { + MeshColors[ ColorIdx ] = SoftSkinVertices[ Indices[ColorIdx] ].Color.ReinterpretAsLinear(); + } + + if ( ImportAxis == HRSAI_Unreal ) + { + // We need to re-index colors due to swapped indices on the faces (due to winding differences). + for ( int32 WedgeIdx = 0; WedgeIdx < Indices.Num(); WedgeIdx += 3 ) + { + // Swap second and third values for the vertices of the face + MeshColors.SwapMemory(WedgeIdx + 1, WedgeIdx + 2); + } + } + + // Create attribute for colors. + HAPI_AttributeInfo AttributeInfoColors; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoColors); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoColors ); + AttributeInfoColors.count = MeshColors.Num(); + AttributeInfoColors.tupleSize = 4; + AttributeInfoColors.exists = true; + AttributeInfoColors.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoColors.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoColors.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, + 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoColors), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoColors, + (const float *)MeshColors.GetData(), 0, AttributeInfoColors.count ), false ); + + //------------------------------------------------------------------------- + // Indices + //------------------------------------------------------------------------- + + // Extract indices from static mesh. + TArray< int32 > MeshIndices; + MeshIndices.SetNumUninitialized( Indices.Num() ); + + if ( ImportAxis == HRSAI_Unreal ) + { + // We have to swap indices to fix the winding order. + for ( int32 IndexIdx = 0; IndexIdx < Indices.Num(); IndexIdx += 3 ) + { + MeshIndices[ IndexIdx + 0 ] = Indices[ IndexIdx + 0 ]; + MeshIndices[ IndexIdx + 1 ] = Indices[ IndexIdx + 2 ]; + MeshIndices[ IndexIdx + 2 ] = Indices[ IndexIdx + 1 ]; + } + } + else + { + // Direct copy, no need to change the winding order + MeshIndices = TArray< int32 >( Indices ); + } + + // We can now set the vertex list. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, + 0, MeshIndices.GetData(), 0, MeshIndices.Num() ), false ); + + // We need to generate the array of face counts. + TArray< int32 > MeshFaceCounts; + MeshFaceCounts.Init( 3, Part.faceCount ); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, + 0, MeshFaceCounts.GetData(), 0, MeshFaceCounts.Num() ), false ); + + //------------------------------------------------------------------------- + // Face Materials + //------------------------------------------------------------------------- + TArray FaceMaterialIds; + FaceMaterialIds.SetNumUninitialized( FaceCount ); + int32 FaceIdx = 0; + int32 SectionCount = SourceModel.NumNonClothingSections(); + for (int32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex) + { + const FSkelMeshSection& Section = SourceModel.Sections[SectionIndex]; + + // Copy the material sections for all the faces of the current section + int32 CurrentMatIndex = Section.MaterialIndex; + for (int32 TriangleIndex = 0; TriangleIndex < (int32)Section.NumTriangles; ++TriangleIndex) + { + FaceMaterialIds[FaceIdx++] = CurrentMatIndex; + } + } + + // Create an array of Material Interfaces + TArray< UMaterialInterface * > MaterialInterfaces; + MaterialInterfaces.SetNum( SkeletalMesh->Materials.Num() ); + for( int32 MatIdx = 0; MatIdx < SkeletalMesh->Materials.Num(); MatIdx++ ) + MaterialInterfaces[MatIdx] = SkeletalMesh->Materials[ MatIdx ].MaterialInterface; + + // Create list of materials, one for each face. + TArray< char * > MeshFaceMaterials; + FHoudiniEngineUtils::CreateFaceMaterialArray( + MaterialInterfaces, FaceMaterialIds, MeshFaceMaterials ); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeName = HAPI_UNREAL_ATTRIB_MATERIAL; + if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeMaterial.IsEmpty() ) + FHoudiniEngineUtils::ConvertUnrealString( HoudiniRuntimeSettings->MarshallingAttributeMaterial, MarshallingAttributeName ); + + // Create attribute for materials. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoMaterial ); + AttributeInfoMaterial.count = FaceMaterialIds.Num(); + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + bool bAttributeError = false; + if ( FHoudiniApi::AddAttribute( FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, 0, + MarshallingAttributeName.c_str(), &AttributeInfoMaterial ) != HAPI_RESULT_SUCCESS ) + { + bAttributeError = true; + } + + if ( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + DisplayGeoInfo.nodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfoMaterial, + (const char **) MeshFaceMaterials.GetData(), 0, + MeshFaceMaterials.Num() ) != HAPI_RESULT_SUCCESS ) + { + bAttributeError = true; + } + + // Delete material names. + FHoudiniEngineUtils::DeleteFaceMaterialArray( MeshFaceMaterials ); + + if ( bAttributeError ) + { + check( 0 ); + return false; + } + + //------------------------------------------------------------------------- + // Mesh name + //------------------------------------------------------------------------- + if ( !HoudiniRuntimeSettings->MarshallingAttributeInputMeshName.IsEmpty() ) + { + // Create primitive attribute with mesh asset path + const FString MeshAssetPath = SkeletalMesh->GetPathName(); + std::string MeshAssetPathCStr = TCHAR_TO_ANSI( *MeshAssetPath ); + const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); + + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized( Part.faceCount ); + for ( int32 Ix = 0; Ix < Part.faceCount; ++Ix ) + { + PrimitiveAttrs[ Ix ] = MeshAssetPathRaw; + } + + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeInputMeshName, MarshallingAttributeName ); + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfo ); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, + 0, MarshallingAttributeName.c_str(), &AttributeInfo ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + DisplayGeoInfo.nodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num() ), false ); + } + + //------------------------------------------------------------------------- + // Mesh asset path + //------------------------------------------------------------------------- + if( !HoudiniRuntimeSettings->MarshallingAttributeInputSourceFile.IsEmpty() ) + { + // Create primitive attribute with mesh asset path + FString Filename; + if( UAssetImportData* ImportData = SkeletalMesh->AssetImportData ) + { + for( const auto& SourceFile : ImportData->SourceData.SourceFiles ) + { + Filename = UAssetImportData::ResolveImportFilename( SourceFile.RelativeFilename, ImportData->GetOutermost() ); + break; + } + } + + if( !Filename.IsEmpty() ) + { + std::string FilenameCStr = TCHAR_TO_ANSI( *Filename ); + const char* FilenameCStrRaw = FilenameCStr.c_str(); + + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized( Part.faceCount ); + for( int32 Ix = 0; Ix < Part.faceCount; ++Ix ) + { + PrimitiveAttrs[ Ix ] = FilenameCStrRaw; + } + + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeInputSourceFile, MarshallingAttributeName ); + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfo ); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, + 0, MarshallingAttributeName.c_str(), &AttributeInfo ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + DisplayGeoInfo.nodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num() ), false ); + } + } + + // Commit the geo before doing the skeleton. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId), false); + + if ( bExportSkeleton ) + { + // Export the Skeleton! + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, &NodeInfo), false ); + + FHoudiniEngineUtils::HapiCreateSkeletonFromData( HostAssetId, SkeletalMesh, NodeInfo, OutCreatedNodeIds ); + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId ), false ); + } +#endif + + return true; +} + +bool +FHoudiniEngineUtils::HapiCreateSkeletonFromData( + HAPI_NodeId HostAssetId, + USkeletalMesh * SkeletalMesh, + const HAPI_NodeInfo& SkelMeshNodeInfo, + TArray< HAPI_NodeId >& OutCreatedNodeIds ) +{ +#if WITH_EDITOR + if ( !SkeletalMesh || SkeletalMesh->IsPendingKill() ) + return false; + + // We need to have an input asset already! + if ( SkelMeshNodeInfo.parentId < 0 || SkelMeshNodeInfo.id < 0 ) + return false; + + // Get the skeleton from the skeletal mesh + const FReferenceSkeleton& RefSkeleton = SkeletalMesh->RefSkeleton; + int32 BoneCount = RefSkeleton.GetRawBoneNum(); + if ( BoneCount <= 0 ) + return false; + + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + check( HoudiniRuntimeSettings ); + + float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION; + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + int32 GeneratedLightMapResolution = 32; + + if ( HoudiniRuntimeSettings ) + { + GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; + } + + // First, we need to create an objnet node in the input that will contains the nulls & bones + HAPI_NodeId ObjnetNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), SkelMeshNodeInfo.parentId, "objnet", "skeleton", true, &ObjnetNodeId ), false ); + + // We also have to create an object merge node inside the objnet, to attach the skeletal mesh's geometry to the skeleton + HAPI_NodeId GeoNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), ObjnetNodeId, "geo", "mesh", true, &GeoNodeId ), false ); + + // For now, we dont export skinning info!! + // TODO: new HAPI functions are needed to properly set the skinning weights on the vertices + + /* + ///////// DPT: Deactivated skinning export for now + + // We're going to need a capture, capture override and deform node after the object merge, + // So we'll create them now. + + // Create the bone deform + HAPI_NodeId DeformNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), GeoNodeId, "bonedeform", "mesh_deform", true, &DeformNodeId), false); + + // We can delete the default file node created by the geometry node, + // This wll set the display flag top the deform node, which is exactly what we want + HAPI_GeoInfo GeoInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), GeoNodeId, &GeoInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId), false); + + // Create the capture attribute pack and unpack nodes + HAPI_NodeId CaptureAttrPackNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), GeoNodeId, "captureattribpack", "mesh_capture_pack", true, &CaptureAttrPackNodeId ), false); + + HAPI_NodeId CaptureAttrUnpackNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), GeoNodeId, "captureattribunpack", "mesh_capture_unpack", true, &CaptureAttrUnpackNodeId ), false); + + // Create the capture node + HAPI_NodeId CaptureNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), GeoNodeId, "capture", "mesh_capture", true, &CaptureNodeId), false); + + // Then create an object merge in the geo node... + HAPI_NodeId ObjMergeNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), GeoNodeId, "object_merge", "mesh", true, &ObjMergeNodeId), false); + + // ... and set it's object path parameter to the skeletal mesh's geometry + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmNodeValue( + FHoudiniEngine::Get().GetSession(), ObjMergeNodeId, "objpath1", SkelMeshNodeInfo.id), false); + + // We can now wire all those nodes together + // Oject Merge > Capture > Capture Override > Capture Unpack > Capture Pack > Deform + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CaptureNodeId, 0, ObjMergeNodeId, 0), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CaptureAttrUnpackNodeId, 0, CaptureNodeId, 0 ), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CaptureAttrPackNodeId, 0, CaptureAttrUnpackNodeId, 0 ), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), DeformNodeId, 0, CaptureAttrPackNodeId, 0 ), false); + + */ + + // Create an object merge in the geo node... + HAPI_NodeId ObjMergeNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), GeoNodeId, "object_merge", "mesh", true, &ObjMergeNodeId ), false ); + + // ... and set it's object path parameter to the skeletal mesh's geometry + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmNodeValue( + FHoudiniEngine::Get().GetSession(), ObjMergeNodeId, "objpath1", SkelMeshNodeInfo.id ), false ); + + // We can delete the default file node created by the geometry node, + // This will set the display flag to the object merge node + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), GeoNodeId, &GeoInfo ), false ); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId ), false ); + + // Arrays keeping track of the created nulls / bone node IDs + TArray NullNodeIds; + NullNodeIds.Init(-1, BoneCount ); + TArray BoneNodeIds; + BoneNodeIds.Init(-1, BoneCount ); + + // String containing all the relative path to the joints cregion for the capture SOP + int32 NumCapture = 0; + FString Capture_Region_Paths; + + HAPI_NodeId RootNullNodeId = -1; + for ( int32 BoneIndex = 0; BoneIndex < RefSkeleton.GetRawBoneNum(); ++BoneIndex ) + { + // Get the current bone's info + const FMeshBoneInfo& CurrentBone = RefSkeleton.GetRefBoneInfo()[ BoneIndex ]; + const FTransform& BoneTransform = RefSkeleton.GetRefBonePose()[ BoneIndex ]; + const FString & BoneName = CurrentBone.ExportName; + + // Create a joint (null node) for this bone + HAPI_NodeId NullNodeId = -1; + float BoneLength = 0.0f; + { + std::string NameStr; + FHoudiniEngineUtils::ConvertUnrealString( BoneName, NameStr ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), ObjnetNodeId, "null", NameStr.c_str(), true, &NullNodeId ), false ); + + // Check if we have a valid id for this new input asset. + if ( NullNodeId == -1 ) + return false; + + // We have a valid node id. + NullNodeIds[ BoneIndex ] = NullNodeId; + + // Convert the joint's transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform( BoneTransform, HapiTransform ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), NullNodeId, &HapiTransform), false); + + // Calc the bone's length + BoneLength = FVector( HapiTransform.position[0], HapiTransform.position[1], HapiTransform.position[2] ).Size(); + + // We also need to create a cregion node inside the null node + HAPI_NodeId CRegionId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), NullNodeId, "cregion", "cregion", true, &CRegionId ), false ); + + /* + ///////// DPT: Deactivated skinning export for now + + // Set the CRegion to XAxis?? + // Set the squatch and csquatch param to (0, 0, 0) for faster capture cook + // (Since we'll be discarding the capture values) + + // Add a path to the cregion of the joint to the paths to be set for capture sop + FString NodePathTemp; + if ( !FHoudiniEngineUtils::HapiGetNodePath( CRegionId, CaptureNodeId, NodePathTemp ) ) + continue; + + Capture_Region_Paths += NodePathTemp + TEXT(" "); + NumCapture++; + */ + } + + // If we're the root node, we don't need to creating bones + if ( CurrentBone.ParentIndex < 0 ) + { + RootNullNodeId = NullNodeId; + continue; + } + + // Get our parent's id + HAPI_NodeId ParentNullNodeId = -1; + ParentNullNodeId = NullNodeIds[ CurrentBone.ParentIndex ]; + + // Connect the joints + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), NullNodeId, 0, ParentNullNodeId, 0 ), false ); + + // Now we need to create the bone + // It has to be named by our parents, and looking at the current null node + HAPI_NodeId BoneNodeId = -1; + { + const FString & ParentName = RefSkeleton.GetRefBoneInfo()[ CurrentBone.ParentIndex ].ExportName + TEXT("_bone"); + std::string NameStr; + FHoudiniEngineUtils::ConvertUnrealString( ParentName, NameStr ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), ObjnetNodeId, "bone", NameStr.c_str(), true, &BoneNodeId), false ); + + // Check if we have a valid id for this new input asset. + if ( BoneNodeId == -1 ) + return false; + + // We have a valid node id. + //NullNodeIds[ BoneIndex ] = BoneNodeId; + + // We need to change the length, lookatpath attributes + // The bone looks at the current null + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmNodeValue( + FHoudiniEngine::Get().GetSession(), BoneNodeId, "lookatpath", NullNodeId ), false ); + + // Set the length parameter + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoneNodeId, "length", 0, BoneLength), false); + } + + // Connect the bone to its parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), BoneNodeId, 0, ParentNullNodeId, 0 ), false); + } + + // Now that the skeleton has been created, we can connect the Geometry node containing the geometry to the skeleton's root + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GeoNodeId, 0, RootNullNodeId, 0), false); + + /* + ///////// DPT: Deactivated skinning export for now + + // We can also set the all the cregion path to the capture sop extraregions parameter + // Get param id. + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), CaptureNodeId, "extraregions", &ParmId ), false ); + + std::string ConvertedString = TCHAR_TO_UTF8(*Capture_Region_Paths); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), CaptureNodeId, ConvertedString.c_str(), ParmId, 0 ) , false ); + + + //----------------------------------------------------------------------------------------------------------------- + // Extract Skinning info on the skeletal mesh + // Grab base LOD level. + const FSkeletalMeshResource* SkelMeshResource = SkeletalMesh->GetImportedResource(); + const FStaticLODModel& SourceModel = SkelMeshResource->LODModels[0]; + const int32 VertexCount = SourceModel.GetNumNonClothingVertices(); + + // Extract the vertices buffer (this also contains normals, uvs, colors...) + TArray SoftSkinVertices; + SourceModel.GetNonClothVertices(SoftSkinVertices); + if ( SoftSkinVertices.Num() != VertexCount ) + return false; + + // Array containing the weight values for each vertex, this will be converted to boneCapture_data attribute + TArray SkinningWeights; + SkinningWeights.SetNum( VertexCount * MAX_TOTAL_INFLUENCES ); + + // Array containing the index of the bones used for the weight values, this will be converted to boneCapture_index attribute + TArray SkinningIndexes; + SkinningIndexes.SetNum( VertexCount * MAX_TOTAL_INFLUENCES ); + + for (int32 BoneIndex = 0; BoneIndex < BoneCount; ++BoneIndex) + { + // Add all the vertices that are weighted to the current skeletal bone to the cluster + // NOTE: the bone influence indices contained in the vertex data are based on a per-chunk + // list of verts. The convert the chunk bone index to the mesh bone index, the chunk's boneMap is needed + int32 VertIndex = 0; + const int32 SectionCount = SourceModel.Sections.Num(); + for (int32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex) + { + const FSkelMeshSection& Section = SourceModel.Sections[SectionIndex]; + + for (int32 SoftIndex = 0; SoftIndex < Section.SoftVertices.Num(); ++SoftIndex) + { + const FSoftSkinVertex& Vert = Section.SoftVertices[SoftIndex]; + + //SkinningIndexes[VertIndex].SetNum( MAX_TOTAL_INFLUENCES ) + + for (int32 InfluenceIndex = 0; InfluenceIndex < MAX_TOTAL_INFLUENCES; ++InfluenceIndex) + { + SkinningIndexes[ VertIndex * MAX_TOTAL_INFLUENCES + InfluenceIndex ] = Section.BoneMap[ Vert.InfluenceBones[ InfluenceIndex ] ]; + SkinningWeights[ VertIndex * MAX_TOTAL_INFLUENCES + InfluenceIndex ] = Vert.InfluenceWeights[ InfluenceIndex ] / 255.f; + } + + ++VertIndex; + } + } + } + + // We need to make sure the capture/capture unpack SOP are cooked before doing this + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), CaptureNodeId, nullptr ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), CaptureAttrUnpackNodeId, nullptr), false); + + // Convert weight to the boneCapture_data attribute + HAPI_AttributeInfo DataAttrInfo; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), CaptureAttrUnpackNodeId, 0, "boneCapture_data", HAPI_ATTROWNER_POINT, &DataAttrInfo ), false ); + + DataAttrInfo.tupleSize = MAX_TOTAL_INFLUENCES; + + // Get the old values + TArray Array; + Array.SetNum(DataAttrInfo.count * DataAttrInfo.tupleSize); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), CaptureAttrUnpackNodeId, 0, "boneCapture_data", &DataAttrInfo, 0, Array.GetData(), 0, DataAttrInfo.count), false); + + // Add the updated attribute + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), SkelMeshNodeInfo.id,//CaptureAttrUnpackNodeId, + 0, "boneCapture_data", &DataAttrInfo ), false ); + + // Set the new values + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), SkelMeshNodeInfo.id,//CaptureAttrUnpackNodeId, + 0, "boneCapture_data", &DataAttrInfo, SkinningWeights.GetData(), 0, VertexCount ), false ); + + // Convert indexes to the boneCapture_index attribute + HAPI_AttributeInfo IndexAttrInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), CaptureAttrUnpackNodeId, 0, "boneCapture_index", HAPI_ATTROWNER_POINT, &IndexAttrInfo ), false); + + IndexAttrInfo.tupleSize = MAX_TOTAL_INFLUENCES; + + // Add the updated attribute to the mesh node + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), SkelMeshNodeInfo.id,//CaptureAttrUnpackNodeId, + 0, "boneCapture_index", &IndexAttrInfo), false ); + + // Set the new values + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), SkelMeshNodeInfo.id,//CaptureAttrUnpackNodeId, + 0, "boneCapture_index", &IndexAttrInfo, SkinningIndexes.GetData(), 0, VertexCount ), false); + */ + + // Finally, we'll add a detail attribute with the path to the skeleton root node + // This is an OBJ asset, return the path to this geo relative to the asset + FString RootNodePath; + if ( FHoudiniEngineUtils::HapiGetNodePath( RootNullNodeId, HostAssetId, RootNodePath ) ) + { + const char * NodePathStr = TCHAR_TO_UTF8(*RootNodePath); + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfo ); + AttributeInfo.count = 1; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), SkelMeshNodeInfo.id, + 0, "unreal_skeleton_root_node", &AttributeInfo), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), SkelMeshNodeInfo.id, + 0, "unreal_skeleton_root_node", &AttributeInfo, (const char**)&NodePathStr, 0, 1 ), false ); + } + +#endif + return true; +} + +bool +FHoudiniEngineUtils::HapiDisconnectAsset( HAPI_NodeId HostAssetId, int32 InputIndex ) +{ +#if WITH_EDITOR + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), HostAssetId, InputIndex ), false ); + +#endif // WITH_EDITOR + + return true; +} + +bool +FHoudiniEngineUtils::HapiSetAssetTransform( HAPI_NodeId AssetId, const FTransform & Transform ) +{ + if (!FHoudiniEngineUtils::IsValidNodeId(AssetId)) + return false; + + // Translate Unreal transform to HAPI Euler one. + HAPI_TransformEuler TransformEuler; + FHoudiniApi::TransformEuler_Init(&TransformEuler); + //FMemory::Memzero< HAPI_TransformEuler >( TransformEuler ); + FHoudiniEngineUtils::TranslateUnrealTransform( Transform, TransformEuler ); + + // Get the NodeInfo + HAPI_NodeInfo LocalAssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&LocalAssetNodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetId, + &LocalAssetNodeInfo), false); + + if (LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + LocalAssetNodeInfo.parentId, + &TransformEuler), false); + } + else if (LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + AssetId, &TransformEuler), false); + } + else + return false; + + return true; +} + +bool FHoudiniEngineUtils::CreateStaticMeshesFromHoudiniAsset( + HAPI_NodeId AssetId, + FHoudiniCookParams& HoudiniCookParams, + bool ForceRebuildStaticMesh, bool ForceRecookAll, + const TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesIn, + TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesOut, + FTransform & ComponentTransform ) +{ +#if WITH_EDITOR + /* + // When called via commandlet, this might be perfectly valid + if ( !FHoudiniEngineUtils::IsHoudiniAssetValid( AssetId ) || !HoudiniCookParams.HoudiniAsset ) + return false; + */ + + // Make sure rendering is done - so we are not changing data being used by collision drawing. + FlushRenderingCommands(); + + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + check( HoudiniRuntimeSettings ); + + float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION; + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + + // Attribute marshalling names. + std::string MarshallingAttributeNameLightmapResolution = HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION; + std::string MarshallingAttributeNameMaterial = HAPI_UNREAL_ATTRIB_MATERIAL; + std::string MarshallingAttributeNameMaterialFallback = HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK; + std::string MarshallingAttributeNameMaterialInstance = HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE; + std::string MarshallingAttributeNameFaceSmoothingMask = HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK; + + // Group name prefix used for collision geometry generation. + FString CollisionGroupNamePrefix = TEXT("collision_geo"); + FString RenderedCollisionGroupNamePrefix = TEXT("rendered_collision_geo"); + FString UCXCollisionGroupNamePrefix = TEXT("collision_geo_ucx"); + FString UCXRenderedCollisionGroupNamePrefix = TEXT("rendered_collision_geo_ucx"); + FString SimpleCollisionGroupNamePrefix = TEXT("collision_geo_simple"); + FString SimpleRenderedCollisionGroupNamePrefix = TEXT("rendered_collision_geo_simple"); + + // Add runtime setting for those? + FString LodGroupNamePrefix = TEXT("lod"); + FString SocketGroupNamePrefix = TEXT("socket"); + + if ( HoudiniRuntimeSettings ) + { + GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + + if ( !HoudiniRuntimeSettings->MarshallingAttributeLightmapResolution.IsEmpty() ) + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeLightmapResolution, MarshallingAttributeNameLightmapResolution ); + + if ( !HoudiniRuntimeSettings->MarshallingAttributeMaterial.IsEmpty() ) + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeMaterial, MarshallingAttributeNameMaterial ); + + if ( !HoudiniRuntimeSettings->MarshallingAttributeFaceSmoothingMask.IsEmpty() ) + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeFaceSmoothingMask, MarshallingAttributeNameFaceSmoothingMask ); + + if ( !HoudiniRuntimeSettings->CollisionGroupNamePrefix.IsEmpty() ) + CollisionGroupNamePrefix = HoudiniRuntimeSettings->CollisionGroupNamePrefix; + + if ( !HoudiniRuntimeSettings->RenderedCollisionGroupNamePrefix.IsEmpty() ) + RenderedCollisionGroupNamePrefix = HoudiniRuntimeSettings->RenderedCollisionGroupNamePrefix; + + if ( !HoudiniRuntimeSettings->UCXCollisionGroupNamePrefix.IsEmpty() ) + UCXCollisionGroupNamePrefix = HoudiniRuntimeSettings->UCXCollisionGroupNamePrefix; + + if ( !HoudiniRuntimeSettings->UCXRenderedCollisionGroupNamePrefix.IsEmpty() ) + UCXRenderedCollisionGroupNamePrefix = HoudiniRuntimeSettings->UCXRenderedCollisionGroupNamePrefix; + + if ( !HoudiniRuntimeSettings->SimpleCollisionGroupNamePrefix.IsEmpty() ) + SimpleCollisionGroupNamePrefix = HoudiniRuntimeSettings->SimpleCollisionGroupNamePrefix; + + if ( !HoudiniRuntimeSettings->SimpleRenderedCollisionGroupNamePrefix.IsEmpty() ) + SimpleRenderedCollisionGroupNamePrefix = HoudiniRuntimeSettings->SimpleRenderedCollisionGroupNamePrefix; + } + + // Get platform manager LOD specific information. + ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); + check( CurrentPlatform ); + FStaticMeshLODGroup LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup( NAME_None ); + int32 DefaultNumLODs = LODGroup.GetDefaultNumLODs(); + + // Get the AssetInfo + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ), false ); + + // Retrieve asset transform. + FTransform AssetUnrealTransform; + if ( !FHoudiniEngineUtils::HapiGetAssetTransform( AssetId, AssetUnrealTransform ) ) + return false; + ComponentTransform = AssetUnrealTransform; + + // Retrieve information about each object contained within our asset. + TArray< HAPI_ObjectInfo > ObjectInfos; + if ( !FHoudiniEngineUtils::HapiGetObjectInfos( AssetId, ObjectInfos ) ) + return false; + const int32 ObjectCount = ObjectInfos.Num(); + + // Retrieve transforms for each object in this asset. + TArray< HAPI_Transform > ObjectTransforms; + if ( !FHoudiniEngineUtils::HapiGetObjectTransforms( AssetId, ObjectTransforms ) ) + return false; + // Retrieve all used unique material ids. + TSet< HAPI_NodeId > UniqueMaterialIds; + TSet< HAPI_NodeId > UniqueInstancerMaterialIds; + TMap< FHoudiniGeoPartObject, HAPI_NodeId > InstancerMaterialMap; + FHoudiniEngineUtils::ExtractUniqueMaterialIds( + AssetInfo, UniqueMaterialIds, UniqueInstancerMaterialIds, InstancerMaterialMap ); + + // Create All the materials found on the asset + TMap< FString, UMaterialInterface * > Materials; + FHoudiniEngineMaterialUtils::HapiCreateMaterials( + AssetId, HoudiniCookParams, AssetInfo, UniqueMaterialIds, + UniqueInstancerMaterialIds, Materials, ForceRecookAll ); + + // Update all material assignments + HoudiniCookParams.HoudiniCookManager->ClearAssignmentMaterials(); + for( const auto& AssPair : Materials ) + { + HoudiniCookParams.HoudiniCookManager->AddAssignmentMaterial( AssPair.Key, AssPair.Value ); + } + + // Iterate through all objects. + for ( int32 ObjectIdx = 0; ObjectIdx < ObjectInfos.Num(); ++ObjectIdx ) + { + // Retrieve object at this index. + const HAPI_ObjectInfo & ObjectInfo = ObjectInfos[ ObjectIdx ]; + + // Retrieve object name. + FString ObjectName = TEXT( "" ); + FHoudiniEngineString HoudiniEngineString( ObjectInfo.nameSH ); + HoudiniEngineString.ToFString( ObjectName ); + + // Get transformation for this object. + const HAPI_Transform & ObjectTransform = ObjectTransforms[ ObjectIdx ]; + FTransform TransformMatrix; + FHoudiniEngineUtils::TranslateHapiTransform( ObjectTransform, TransformMatrix ); + + // Handle Editable nodes + // First, get the number of editable nodes + int32 EditableNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), ObjectInfo.nodeId, + HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, true, &EditableNodeCount ), false ); + + if ( EditableNodeCount > 0 ) + { + TArray< HAPI_NodeId > EditableNodeIds; + EditableNodeIds.SetNumUninitialized( EditableNodeCount ); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), AssetId, + EditableNodeIds.GetData(), EditableNodeCount ), false ); + + for ( int nEditable = 0; nEditable < EditableNodeCount; nEditable++ ) + { + HAPI_GeoInfo CurrentEditableGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), EditableNodeIds[ nEditable ], &CurrentEditableGeoInfo ), false ); + + // do not process the main display geo + if ( CurrentEditableGeoInfo.isDisplayGeo ) + continue; + + // We only handle editable curves + if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE ) + continue; + + // Add the editable curve to the output array + FHoudiniGeoPartObject HoudiniGeoPartObject( + TransformMatrix, ObjectName, ObjectName, AssetId, + ObjectInfo.nodeId, CurrentEditableGeoInfo.nodeId, 0 ); + + HoudiniGeoPartObject.bIsVisible = ObjectInfo.isVisible; + HoudiniGeoPartObject.bIsInstancer = false; + HoudiniGeoPartObject.bIsCurve = true; + HoudiniGeoPartObject.bIsEditable = CurrentEditableGeoInfo.isEditable; + HoudiniGeoPartObject.bHasGeoChanged = CurrentEditableGeoInfo.hasGeoChanged; + + StaticMeshesOut.Add( HoudiniGeoPartObject, nullptr ); + } + } + + // Get the Display Geo's info + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), ObjectInfo.nodeId, &GeoInfo ) ) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s] unable to retrieve GeoInfo, - skipping."), + ObjectInfo.nodeId, *ObjectName ); + continue; + } + + // Get object / geo group memberships for primitives. + TArray< FString > ObjectGeoGroupNames; + if( ! FHoudiniEngineUtils::HapiGetGroupNames( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, 0, HAPI_GROUPTYPE_PRIM, ObjectGeoGroupNames, false ) ) + { + HOUDINI_LOG_MESSAGE( TEXT( "Creating Static Meshes: Object [%d %s] non-fatal error reading group names" ), + ObjectInfo.nodeId, *ObjectName ); + } + + // Prepare the object that will store UCX/UBX/USP Collision geo + FKAggregateGeom AggregateCollisionGeo; + bool bHasAggregateGeometryCollision = false; + + // Prepare the object that will store the mesh sockets and their names + TArray< FTransform > AllSockets; + TArray< FString > AllSocketsNames; + TArray< FString > AllSocketsActors; + TArray< FString > AllSocketsTags; + + for ( int32 PartIdx = 0; PartIdx < GeoInfo.partCount; ++PartIdx ) + { + // Get part information. + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId, PartIdx, &PartInfo ) ) + { + // Error retrieving part info. + HOUDINI_LOG_MESSAGE( + TEXT( "Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d] unable to retrieve PartInfo - skipping." ), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx ); + continue; + } + + // Retrieve part name. + FString PartName = TEXT(""); + FHoudiniEngineString HoudiniEngineStringPartName( PartInfo.nameSH ); + HoudiniEngineStringPartName.ToFString( PartName ); + + // Unsupported/Invalid part + if( PartInfo.type == HAPI_PARTTYPE_INVALID ) + continue; + + // Create geo part object identifier. + FHoudiniGeoPartObject HoudiniGeoPartObject( + TransformMatrix, ObjectName, PartName, AssetId, + ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id ); + + HoudiniGeoPartObject.bIsVisible = ObjectInfo.isVisible && !PartInfo.isInstanced; + HoudiniGeoPartObject.bIsInstancer = ObjectInfo.isInstancer; + HoudiniGeoPartObject.bIsCurve = ( PartInfo.type == HAPI_PARTTYPE_CURVE ); + HoudiniGeoPartObject.bIsEditable = GeoInfo.isEditable; + HoudiniGeoPartObject.bHasGeoChanged = GeoInfo.hasGeoChanged; + HoudiniGeoPartObject.bIsBox = ( PartInfo.type == HAPI_PARTTYPE_BOX ); + HoudiniGeoPartObject.bIsSphere = ( PartInfo.type == HAPI_PARTTYPE_SPHERE ); + HoudiniGeoPartObject.bIsVolume = ( PartInfo.type == HAPI_PARTTYPE_VOLUME ); + + // See if a custom name for the mesh was assigned via the GeneratedMeshName attribute + HoudiniGeoPartObject.UpdateCustomName(); + + // See if a custom bake folder override for the mesh was assigned via the "unreal_bake_folder" attribute + TArray< FString > BakeFolderOverrides; + { + HAPI_AttributeInfo AttribBakeFolderOverride; + FHoudiniApi::AttributeInfo_Init(&AttribBakeFolderOverride); + + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, + HAPI_UNREAL_ATTRIB_BAKE_FOLDER, AttribBakeFolderOverride, BakeFolderOverrides ); + + if ( BakeFolderOverrides.Num() > 0 ) + { + const FString & BakeFolderOverride = BakeFolderOverrides[ 0 ]; + if ( !BakeFolderOverride.IsEmpty() ) + HoudiniCookParams.BakeFolder = FText::FromString( BakeFolderOverride ); + } + } + + // Extracting Sockets points on the current part and add them to the list + AddMeshSocketToList( AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, AllSockets, AllSocketsNames, AllSocketsActors, AllSocketsTags, PartInfo.isInstanced ); + + if ( PartInfo.type == HAPI_PARTTYPE_INSTANCER ) + { + // This is a Packed Primitive instancer + HoudiniGeoPartObject.bIsVisible = ObjectInfo.isVisible && !ObjectInfo.isInstanced; + HoudiniGeoPartObject.bIsInstancer = false; + HoudiniGeoPartObject.bIsPackedPrimitiveInstancer = true; + + StaticMeshesOut.Add( HoudiniGeoPartObject, nullptr ); + continue; + } + else if ( PartInfo.type == HAPI_PARTTYPE_VOLUME ) + { + // Volume Data, this is probably a Heightfield + HoudiniGeoPartObject.bIsVisible = ObjectInfo.isVisible && !ObjectInfo.isInstanced; + HoudiniGeoPartObject.bIsInstancer = false; + HoudiniGeoPartObject.bIsPackedPrimitiveInstancer = false; + HoudiniGeoPartObject.bIsVolume = true; + + // We need to set the GeoChanged flag to true if we want to force the landscape reimport + HoudiniGeoPartObject.bHasGeoChanged = ( GeoInfo.hasGeoChanged || ForceRebuildStaticMesh || ForceRecookAll ); + + StaticMeshesOut.Add( HoudiniGeoPartObject, nullptr ); + + continue; + } + else if ( PartInfo.type == HAPI_PARTTYPE_CURVE ) + { + // This is a curve part. + StaticMeshesOut.Add( HoudiniGeoPartObject, nullptr ); + continue; + } + else if ( !ObjectInfo.isInstancer && PartInfo.vertexCount <= 0 ) + { + // This is not an instancer, but we do not have vertices, so maybe this is a point cloud with attribute override instancing + // If it is, add it to the out list, if not skip it + if ( HoudiniGeoPartObject.IsAttributeInstancer() || HoudiniGeoPartObject.IsAttributeOverrideInstancer() ) + { + HoudiniGeoPartObject.bIsInstancer = true; + StaticMeshesOut.Add( HoudiniGeoPartObject, nullptr ); + } + continue; + } + + // There are no vertices AND no points. + if ( PartInfo.vertexCount <= 0 && PartInfo.pointCount <= 0 ) + { + HOUDINI_LOG_MESSAGE( + TEXT( "Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] no points or vertices found - skipping." ), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName ); + continue; + } + + // This is an instancer with no points. + if ( ObjectInfo.isInstancer && PartInfo.pointCount <= 0 ) + { + HOUDINI_LOG_MESSAGE( + TEXT( "Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is instancer but has 0 points - skipping." ), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName ); + continue; + } + + // Retrieve material information for this geo part. + TArray< HAPI_NodeId > PartFaceMaterialIds; + HAPI_Bool bSingleFaceMaterial = false; + bool bPartHasMaterials = false; + bool bMaterialsChanged = false; + + if ( PartInfo.faceCount > 0 ) + { + PartFaceMaterialIds.SetNumUninitialized( PartInfo.faceCount ); + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialNodeIdsOnFaces( + FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId, PartInfo.id, + &bSingleFaceMaterial, &PartFaceMaterialIds[ 0 ], 0, PartInfo.faceCount ) ) + { + // Error retrieving material face assignments. + HOUDINI_LOG_MESSAGE( + TEXT( "Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material face assignments, " ) + TEXT( "- skipping." ), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName ); + continue; + } + + // Set flag if we have materials. + TArray< HAPI_NodeId > PartUniqueMaterialIds; + for ( int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx ) + PartUniqueMaterialIds.AddUnique( PartFaceMaterialIds[ MaterialIdx ] ); + + PartUniqueMaterialIds.RemoveSingle( -1 ); + bPartHasMaterials = PartUniqueMaterialIds.Num() > 0; + + // Set flag if any of the materials have changed. + if ( bPartHasMaterials ) + { + for ( int32 MaterialIdx = 0; MaterialIdx < PartUniqueMaterialIds.Num(); ++MaterialIdx ) + { + HAPI_MaterialInfo MaterialInfo; + FHoudiniApi::MaterialInfo_Init(&MaterialInfo); + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialInfo( + FHoudiniEngine::Get().GetSession(), PartUniqueMaterialIds[ MaterialIdx ], &MaterialInfo ) ) + continue; + + if ( MaterialInfo.hasChanged ) + { + bMaterialsChanged = true; + break; + } + } + } + } + + // We do not create mesh for instancers. + if ( ObjectInfo.isInstancer && PartInfo.pointCount > 0 ) + { + // We need to check whether this instancer has a material. + HAPI_NodeId const * FoundInstancerMaterialId = InstancerMaterialMap.Find( HoudiniGeoPartObject ); + if ( FoundInstancerMaterialId ) + { + HAPI_NodeId InstancerMaterialId = *FoundInstancerMaterialId; + + FString InstancerMaterialShopName = TEXT( "" ); + if ( InstancerMaterialId > -1 && FHoudiniEngineMaterialUtils::GetUniqueMaterialShopName( AssetId, InstancerMaterialId, InstancerMaterialShopName ) ) + { + HoudiniGeoPartObject.bInstancerMaterialAvailable = true; + HoudiniGeoPartObject.InstancerMaterialName = InstancerMaterialShopName; + } + } + + // See if we have instancer attribute material present. + { + HAPI_AttributeInfo AttribInstancerAttribMaterials; + FHoudiniApi::AttributeInfo_Init(&AttribInstancerAttribMaterials); + //FMemory::Memset< HAPI_AttributeInfo >( AttribInstancerAttribMaterials, 0 ); + + TArray< FString > InstancerAttribMaterials; + + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, + PartInfo.id, MarshallingAttributeNameMaterial.c_str(), AttribInstancerAttribMaterials, + InstancerAttribMaterials ); + + if ( AttribInstancerAttribMaterials.exists && InstancerAttribMaterials.Num() > 0 ) + { + const FString & InstancerAttribMaterialName = InstancerAttribMaterials[ 0 ]; + if ( !InstancerAttribMaterialName.IsEmpty() ) + { + HoudiniGeoPartObject.bInstancerAttributeMaterialAvailable = true; + HoudiniGeoPartObject.InstancerAttributeMaterialName = InstancerAttribMaterialName; + } + } + } + + // Instancer objects have no mesh assigned. + StaticMeshesOut.Add( HoudiniGeoPartObject, nullptr ); + continue; + } + + // Containers used for raw data extraction. + + // Vertex Positions + TArray< float > PartPositions; + HAPI_AttributeInfo AttribInfoPositions; + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + //FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoPositions ); + + // Vertex Normals + TArray< float > PartNormals; + HAPI_AttributeInfo AttribInfoNormals; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + // FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoNormals ); + + // Vertex TangentU + TArray< float > PartTangentU; + HAPI_AttributeInfo AttribInfoTangentU; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentU); + //FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoTangentU ); + + // Vertex TangentV + TArray< float > PartTangentV; + HAPI_AttributeInfo AttribInfoTangentV; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentV); + //FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoTangentV ); + + // Vertex Colors + TArray< float > PartColors; + HAPI_AttributeInfo AttribInfoColors; + FHoudiniApi::AttributeInfo_Init(&AttribInfoColors); + //FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoColors ); + + // Vertex Alpha values + TArray< float > PartAlphas; + HAPI_AttributeInfo AttribInfoAlpha; + FHoudiniApi::AttributeInfo_Init(&AttribInfoAlpha); + //FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoAlpha ); + + // UVs + TArray< TArray< float > > PartUVs; + PartUVs.SetNumZeroed( MAX_STATIC_TEXCOORDS ); + TArray< HAPI_AttributeInfo > AttribInfoUVs; + AttribInfoUVs.SetNumUninitialized( MAX_STATIC_TEXCOORDS ); + for ( int32 Idx = 0; Idx < AttribInfoUVs.Num(); Idx++ ) + FHoudiniApi::AttributeInfo_Init(&(AttribInfoUVs[Idx])); + + // Material Overrides per face + TArray< FString > PartFaceMaterialAttributeOverrides; + HAPI_AttributeInfo AttribFaceMaterials; + FHoudiniApi::AttributeInfo_Init(&AttribFaceMaterials); + //FMemory::Memzero< HAPI_AttributeInfo >( AttribFaceMaterials ); + + // Face Smoothing masks + TArray< int32 > PartFaceSmoothingMasks; + HAPI_AttributeInfo AttribInfoFaceSmoothingMasks; + FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceSmoothingMasks); + //FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoFaceSmoothingMasks ); + + // Lightmap resolution + TArray< int32 > PartLightMapResolutions; + HAPI_AttributeInfo AttribLightmapResolution; + FHoudiniApi::AttributeInfo_Init(&AttribLightmapResolution); + //FMemory::Memzero< HAPI_AttributeInfo >( AttribLightmapResolution ); + + // Vertex Indices + TArray< int32 > PartVertexList; + PartVertexList.SetNumUninitialized( PartInfo.vertexCount ); + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetVertexList( + FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId, PartInfo.id, + &PartVertexList[ 0 ], 0, PartInfo.vertexCount ) ) + { + // Error getting the vertex list. + HOUDINI_LOG_MESSAGE( + TEXT( "Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list - skipping." ), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName ); + + continue; + } + + // Array Storing the GroupNames for the current part + TArray< FString > GroupNames; + if ( !PartInfo.isInstanced ) + { + GroupNames = ObjectGeoGroupNames; + } + else + { + // We're a packed primitive, so we want to get the group on the actual + // packed geo, not on the main display geo + if (!FHoudiniEngineUtils::HapiGetGroupNames( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, HAPI_GROUPTYPE_PRIM, GroupNames, true)) + GroupNames = ObjectGeoGroupNames; + } + + // Sort the Group name array so the LODs are ordered + GroupNames.Sort(); + + // See if we require splitting. + TArray SplitGroupNames; + int32 nLODInsertPos = 0; + int32 NumberOfLODs = 0; + for ( int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx ) + { + const FString & GroupName = GroupNames[ GeoGroupNameIdx ]; + + // We're going to order the groups: + // Simple and convex invisible colliders should be created first as they will have to be attached to the visible meshes + // LODs should be created then, ordered by their names + // Visible colliders should be created then, and finally, invisible complex colliders + if ( GroupName.StartsWith( SimpleCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) ) + { + // Invisible simple colliders should be treated first + SplitGroupNames.Insert( GroupName, 0 ); + nLODInsertPos++; + } + else if ( GroupName.StartsWith( UCXCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) ) + { + // Invisible complex colliders should be treated first + SplitGroupNames.Insert( GroupName, 0 ); + nLODInsertPos++; + } + else if ( GroupName.StartsWith( LodGroupNamePrefix, ESearchCase::IgnoreCase ) ) + { + // LOD meshes should be next + SplitGroupNames.Insert( GroupName, nLODInsertPos++ ); + NumberOfLODs++; + } + else if ( GroupName.StartsWith( RenderedCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) ) + { + // Visible colliders (simple, convex or complex) should be treated after LODs + SplitGroupNames.Insert( GroupName, nLODInsertPos ); + } + else if ( GroupName.StartsWith(CollisionGroupNamePrefix, ESearchCase::IgnoreCase ) ) + { + // Invisible complex colliders should be treated last + SplitGroupNames.Add( GroupName ); + } + } + + bool bRequireSplit = SplitGroupNames.Num() > 0; + + TMap< FString, TArray< int32 > > GroupSplitFaces; + TMap< FString, int32 > GroupSplitFaceCounts; + TMap< FString, TArray< int32 > > GroupSplitFaceIndices; + + int32 GroupVertexListCount = 0; + static const FString RemainingGroupName = TEXT( HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION ); + + if ( bRequireSplit ) + { + // Buffer for all vertex indices used for split groups. + // We need this to figure out all vertex indices that are not part of them. + TArray< int32 > AllSplitVertexList; + AllSplitVertexList.SetNumZeroed( PartVertexList.Num() ); + + // Buffer for all face indices used for split groups. + // We need this to figure out all face indices that are not part of them. + TArray< int32 > AllSplitFaceIndices; + AllSplitFaceIndices.SetNumZeroed( PartFaceMaterialIds.Num() ); + + // Some of the groups may contain invalid geometry + // Store them here so we can remove them afterwards + TArray< int32 > InvalidGroupNameIndices; + + // Extract the vertices/faces for each of the split groups + for ( int32 SplitIdx = 0; SplitIdx < SplitGroupNames.Num(); SplitIdx++ ) + { + FString GroupName = SplitGroupNames[ SplitIdx ]; + + // New vertex list just for this group. + TArray< int32 > GroupVertexList; + TArray< int32 > AllFaceList; + + // Extract vertex indices for this split. + GroupVertexListCount = FHoudiniEngineUtils::HapiGetVertexListForGroup( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, GroupName, PartVertexList, GroupVertexList, + AllSplitVertexList, AllFaceList, AllSplitFaceIndices, PartInfo.isInstanced ); + + if ( GroupVertexListCount <= 0 ) + { + // This group doesn't have vertices/faces, mark it as invalid + InvalidGroupNameIndices.Add( SplitIdx ); + + // Error getting the vertex list. + HOUDINI_LOG_MESSAGE( + TEXT( "Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list for group %s - skipping." ), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, *GroupName ); + + continue; + } + + // If list is not empty, we store it for this group - this will define new mesh. + GroupSplitFaces.Add( GroupName, GroupVertexList ); + GroupSplitFaceCounts.Add( GroupName, GroupVertexListCount ); + GroupSplitFaceIndices.Add( GroupName, AllFaceList ); + } + + if ( InvalidGroupNameIndices.Num() > 0 ) + { + // Remove all invalid split groups + for (int32 InvalIdx = InvalidGroupNameIndices.Num() - 1; InvalIdx >= 0; InvalIdx--) + { + int32 Index = InvalidGroupNameIndices[ InvalIdx ]; + + if ( SplitGroupNames[Index].StartsWith(LodGroupNamePrefix, ESearchCase::IgnoreCase) ) + NumberOfLODs--; + + SplitGroupNames.RemoveAt( Index ); + + if (Index <= nLODInsertPos && ( nLODInsertPos > 0 ) ) + nLODInsertPos--; + } + } + + // We also need to figure out / construct vertex list for everything that's not in a split group + TArray< int32 > GroupSplitFacesRemaining; + GroupSplitFacesRemaining.Init( -1, PartVertexList.Num() ); + bool bMainSplitGroup = false; + GroupVertexListCount = 0; + + TArray< int32 > GroupSplitFaceIndicesRemaining; + for ( int32 SplitVertexIdx = 0; SplitVertexIdx < AllSplitVertexList.Num(); SplitVertexIdx++ ) + { + if ( AllSplitVertexList[ SplitVertexIdx ] == 0 ) + { + // This is unused index, we need to add it to unused vertex list. + GroupSplitFacesRemaining[ SplitVertexIdx ] = PartVertexList[ SplitVertexIdx ]; + bMainSplitGroup = true; + GroupVertexListCount++; + } + } + + for ( int32 SplitFaceIdx = 0; SplitFaceIdx < AllSplitFaceIndices.Num(); SplitFaceIdx++ ) + { + if ( AllSplitFaceIndices[ SplitFaceIdx ] == 0 ) + { + // This is unused face, we need to add it to unused faces list. + GroupSplitFaceIndicesRemaining.Add( SplitFaceIdx ); + } + } + + // We store the remaining geo vertex list as a special name (main geo) + // and make sure its treated before the collider meshes + if ( bMainSplitGroup ) + { + SplitGroupNames.Insert( RemainingGroupName, nLODInsertPos ); + GroupSplitFaces.Add( RemainingGroupName, GroupSplitFacesRemaining ); + GroupSplitFaceCounts.Add( RemainingGroupName, GroupVertexListCount ); + GroupSplitFaceIndices.Add( RemainingGroupName, GroupSplitFaceIndicesRemaining ); + } + } + else + { + // No splitting required + SplitGroupNames.Add( RemainingGroupName ); + GroupSplitFaces.Add( RemainingGroupName, PartVertexList ); + GroupSplitFaceCounts.Add( RemainingGroupName, PartVertexList.Num() ); + + TArray AllFaces; + for ( int32 FaceIdx = 0; FaceIdx < PartInfo.faceCount; ++FaceIdx ) + AllFaces.Add( FaceIdx ); + + GroupSplitFaceIndices.Add( RemainingGroupName, AllFaces ); + } + + // Look for LOD Specific attributes, "lod_screensize" by default + TArray< float > LODScreenSizes; + HAPI_AttributeInfo AttribInfoLODScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttribInfoLODScreenSize); + //FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoLODScreenSize ); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, + "lod_screensize", AttribInfoLODScreenSize, LODScreenSizes ); + + // Keep track of the LOD Index + int32 LodIndex = 0; + int32 LodSplitId = -1; + + // Map of Houdini Material IDs to Unreal Material Indices + TMap< HAPI_NodeId, int32 > MapHoudiniMatIdToUnrealIndex; + // Map of Houdini Material Attributes to Unreal Material Indices + TMap< FString, int32 > MapHoudiniMatAttributesToUnrealIndex; + + // Iterate through all detected split groups we care about and split geometry. + // The split are ordered in the following way: + // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders + for ( int32 SplitId = 0; SplitId < SplitGroupNames.Num(); SplitId++ ) + { + // Get split group name + const FString & SplitGroupName = SplitGroupNames[ SplitId ]; + + // Get the vertex indices for this group + TArray< int32 > & SplitGroupVertexList = GroupSplitFaces[ SplitGroupName ]; + + // Get valid count of vertex indices for this split. + int32 SplitGroupVertexListCount = GroupSplitFaceCounts[ SplitGroupName ]; + + // Make sure we have a valid vertex count for this split + if (SplitGroupVertexListCount % 3 != 0 || SplitGroupVertexList.Num() % 3 != 0 ) + { + // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") + TEXT("- skipping."), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName); + + continue; + } + + // Get face indices for this split. + TArray< int32 > & SplitGroupFaceIndices = GroupSplitFaceIndices[ SplitGroupName ]; + + // LOD meshes need to use the same SplitID (as they will be on the same static mesh) + bool IsLOD = SplitGroupName.StartsWith( LodGroupNamePrefix, ESearchCase::IgnoreCase ); + if ( IsLOD && LodSplitId == -1 ) + LodSplitId = SplitId; + + // Materials maps (Houdini to Unreal) needs to be reset for each static mesh generated + // Only the first LOD resets those maps + if ( !IsLOD || ( IsLOD && LodIndex == 0 ) ) + { + MapHoudiniMatIdToUnrealIndex.Empty(); + MapHoudiniMatAttributesToUnrealIndex.Empty(); + } + + // Record split id in geo part. + // LODs must use the same SplitID since they belong to the same static mesh + HoudiniGeoPartObject.SplitId = !IsLOD ? SplitId : LodSplitId; + + // Reset collision flags for the current GeoPartObj + HoudiniGeoPartObject.bIsRenderCollidable = false; + HoudiniGeoPartObject.bIsCollidable = false; + HoudiniGeoPartObject.bIsSimpleCollisionGeo = false; + HoudiniGeoPartObject.bIsUCXCollisionGeo = false; + + // Reset the collision/socket added flags if needed + // For LODs, only reset the collision flag for the first LOD level + if ( !IsLOD || ( IsLOD && LodIndex == 0 ) ) + { + HoudiniGeoPartObject.bHasCollisionBeenAdded = false; + HoudiniGeoPartObject.bHasSocketBeenAdded = false; + } + + // Determining the type of collision: + // UCX and simple collisions need to be checked first as they both start in the same way + // as their non UCX/non simple equivalent!} + if ( SplitGroupName.StartsWith( UCXCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) ) + { + HoudiniGeoPartObject.bIsUCXCollisionGeo = true; + } + else if ( SplitGroupName.StartsWith( UCXRenderedCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) ) + { + HoudiniGeoPartObject.bIsRenderCollidable = true; + HoudiniGeoPartObject.bIsUCXCollisionGeo = true; + } + else if ( SplitGroupName.StartsWith( SimpleRenderedCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) ) + { + HoudiniGeoPartObject.bIsRenderCollidable = true; + HoudiniGeoPartObject.bIsSimpleCollisionGeo = true; + } + else if ( SplitGroupName.StartsWith( SimpleCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) ) + { + HoudiniGeoPartObject.bIsCollidable = true; + HoudiniGeoPartObject.bIsSimpleCollisionGeo = true; + } + else if ( SplitGroupName.StartsWith( RenderedCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) ) + { + HoudiniGeoPartObject.bIsRenderCollidable = true; + } + else if ( SplitGroupName.StartsWith( HoudiniRuntimeSettings->CollisionGroupNamePrefix, ESearchCase::IgnoreCase ) ) + { + HoudiniGeoPartObject.bIsCollidable = true; + } + + // Handling UCX/Convex Hull colliders + if ( HoudiniGeoPartObject.bIsUCXCollisionGeo ) + { + // Retrieve the vertices positions if necessary + if ( PartPositions.Num() <= 0 ) + { + if ( !FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, + PartInfo.id, HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, PartPositions ) ) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] unable to retrieve position data ") + TEXT("- skipping."), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName ); + + break; + } + } + + // Use multiple convex hulls? + bool MultiHullDecomp = false; + if ( SplitGroupName.Contains( TEXT("ucx_multi"), ESearchCase::IgnoreCase ) ) + MultiHullDecomp = true; + + // Create the convex hull colliders and add them to the Aggregate + if ( AddConvexCollisionToAggregate( PartPositions, SplitGroupVertexList, MultiHullDecomp, AggregateCollisionGeo ) ) + { + // We'll add the collision after all the meshes are generated unless this a rendered_collision_geo_ucx + bHasAggregateGeometryCollision = true; + } + + // No need to create a mesh if the colliders is not visible + if ( !HoudiniGeoPartObject.bIsRenderCollidable ) + continue; + } + + // Record split group name. + HoudiniGeoPartObject.SplitName = SplitGroupName; + + // Attempt to locate a static mesh from previous instantiation to reuse it + UStaticMesh * const * FoundStaticMesh = nullptr; + if (IsLOD && LodIndex > 0) + { + // LODs levels other than the first one need to reuse StaticMesh from the output! + // as we want the additional LODs to be added to the newly created Static Mesh + FoundStaticMesh = StaticMeshesOut.Find(HoudiniGeoPartObject); + if (!FoundStaticMesh) + { + // If we failed, try to find the mesh via names + for (TMap< FHoudiniGeoPartObject, UStaticMesh * >::TConstIterator tIt(StaticMeshesOut); tIt; ++tIt) + { + const FHoudiniGeoPartObject& key = tIt.Key(); + if (key.CompareNames(HoudiniGeoPartObject)) + { + FoundStaticMesh = &tIt.Value(); + break; + } + } + } + } + else + { + // We're not an LOD, try to locate a corresponding SM in the previously cooked objects + FoundStaticMesh = StaticMeshesIn.Find(HoudiniGeoPartObject); + if (!FoundStaticMesh) + { + // If we failed, try to find the previous mesh via names to help reuse of components + // the GUID doesnt always match even though its the same part on same HDA + for (TMap< FHoudiniGeoPartObject, UStaticMesh * >::TConstIterator tIt(StaticMeshesIn); tIt; ++tIt) + { + const FHoudiniGeoPartObject& key = tIt.Key(); + if (key.CompareNames(HoudiniGeoPartObject)) + { + FoundStaticMesh = &tIt.Value(); + break; + } + } + } + } + + // Flag whether we need to rebuild the mesh. + bool bRebuildStaticMesh = false; + + // If the geometry and scaling factor have changed or if the user asked for a cook manually, + // we will need to rebuild the static mesh. If not, then we can reuse the corresponding static mesh. + if ( GeoInfo.hasGeoChanged || ForceRebuildStaticMesh || ForceRecookAll ) + bRebuildStaticMesh = true; + + // The geometry has not changed, + if ( !bRebuildStaticMesh ) + { + // No mesh located, unless this split is a simple/convex collider, this is an error. + if ( !FoundStaticMesh && ( !HoudiniGeoPartObject.bIsSimpleCollisionGeo && !HoudiniGeoPartObject.bIsUCXCollisionGeo ) ) + { + HOUDINI_LOG_ERROR( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] geometry has not changed ") + TEXT("but static mesh does not exist - skipping."), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName); + continue; + } + + // If any of the materials on corresponding geo part object have not changed. + if ( !bMaterialsChanged && FoundStaticMesh && *FoundStaticMesh ) + { + // We can reuse previously created geometry. + StaticMeshesOut.Add( HoudiniGeoPartObject, *FoundStaticMesh ); + continue; + } + } + + // If the static mesh was not located, we need to create a new one. + bool bStaticMeshCreated = false; + UStaticMesh * StaticMesh = nullptr; + if ( !FoundStaticMesh || *FoundStaticMesh == nullptr ) + { + FGuid MeshGuid; + MeshGuid.Invalidate(); + + FString MeshName; + UPackage * MeshPackage = FHoudiniEngineBakeUtils::BakeCreateStaticMeshPackageForComponent( + HoudiniCookParams, HoudiniGeoPartObject, MeshName, MeshGuid ); + + if( !MeshPackage || MeshPackage->IsPendingKill() ) + continue; + + StaticMesh = NewObject< UStaticMesh >( + MeshPackage, FName( *MeshName ), + ( HoudiniCookParams.StaticMeshBakeMode == EBakeMode::Intermediate ) ? RF_NoFlags : RF_Public | RF_Standalone ); + + // Add meta information to this package. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MeshPackage, MeshPackage, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT( "true" ) ); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MeshPackage, MeshPackage, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MeshName ); + + // Notify system that new asset has been created. + //FAssetRegistryModule::AssetCreated( StaticMesh ); + + bStaticMeshCreated = true; + } + else + { + // Found the corresponding Static Mesh, just reuse it. + StaticMesh = *FoundStaticMesh; + // Try to reuse the SM's LOD groupd instead of the default one + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(StaticMesh->LODGroup); + } + + // Free any RHI resources for existing mesh before we re-create in place. + StaticMesh->PreEditChange(NULL); + + if ( !IsLOD || LodIndex == 0 ) + { + // We need to initialize the LODs used by this mesh + int32 NeededLODs = IsLOD ? NumberOfLODs : 1; + while (StaticMesh->GetNumSourceModels() < NeededLODs) + StaticMesh->AddSourceModel(); + + // We may have to remove excessive LOD levels + if ( StaticMesh->GetNumSourceModels() > NeededLODs ) + StaticMesh->SetNumSourceModels(NeededLODs); + } + + // Grab the corresponding SourceModel + int32 SrcModelIdx = IsLOD ? LodIndex : 0; + FStaticMeshSourceModel* SrcModel = (StaticMesh->IsSourceModelValid(SrcModelIdx)) ? &(StaticMesh->GetSourceModel(SrcModelIdx)) : nullptr; + + if ( !SrcModel ) + { + HOUDINI_LOG_ERROR( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName, IsLOD ? LodIndex : 0 ); + continue; + } + + // Load existing raw model. This will be empty as we are constructing a new mesh. + FRawMesh RawMesh; + + int32 SplitGroupFaceCount = SplitGroupFaceIndices.Num(); + if (!bRebuildStaticMesh) + { + // We dont need to rebuild the mesh (because the geometry hasn't changed, but the materials have) + // So we can just load the old data into the Raw mesh and reuse it. + SrcModel->LoadRawMesh(RawMesh); + } + else + { + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS AND TANGENTS + //--------------------------------------------------------------------------------------------------------------------- + TArray< float > SplitGroupNormals; + // No need to read the normals if we'll recompute them after + bool bReadNormals = HoudiniRuntimeSettings->RecomputeNormalsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; + if ( bReadNormals ) + { + if ( PartNormals.Num() <= 0 ) + { + // Retrieve normal data for this part + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, + PartInfo.id, HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, PartNormals ); + } + + // See if we need to transfer normal point attributes to vertex attributes. + FHoudiniEngineUtils::TransferRegularPointAttributesToVertices( + SplitGroupVertexList, AttribInfoNormals, PartNormals, SplitGroupNormals ); + } + + TArray< float > SplitGroupTangentU; + TArray< float > SplitGroupTangentV; + // No need to read the tangents if we always want to recompute them + bool bReadTangents = HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; + if (bReadTangents) + { + if (PartTangentU.Num() <= 0) + { + // Retrieve TangentU data for this part + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, + HAPI_UNREAL_ATTRIB_TANGENTU, AttribInfoTangentU, PartTangentU ); + } + + // Transfer tangentu point attributes to the vertices if needed. + FHoudiniEngineUtils::TransferRegularPointAttributesToVertices( + SplitGroupVertexList, AttribInfoTangentU, PartTangentU, SplitGroupTangentU); + + if (PartTangentV.Num() <= 0) + { + // Retrieve TangentV data for this part + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, + HAPI_UNREAL_ATTRIB_TANGENTV, AttribInfoTangentV, PartTangentV); + } + + // Transfer tangentv point attributes to the vertices if needed. + FHoudiniEngineUtils::TransferRegularPointAttributesToVertices( + SplitGroupVertexList, AttribInfoTangentV, PartTangentV, SplitGroupTangentV); + } + + // We need to generate tangents if we have normals but we dont have tangentu or tangentv attributes + bool bGenerateTangents = ( SplitGroupNormals.Num() > 0 ) && ( SplitGroupTangentU.Num() <= 0 || SplitGroupTangentV.Num() <= 0 ); + if ( bGenerateTangents && ( HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always ) ) + { + // No need to generate tangents if unreal will recompute them after + bGenerateTangents = false; + } + + // Transfer normals. + int32 WedgeNormalCount = SplitGroupNormals.Num() / 3; + + // Ensure the number of Normal values is correct + if ( SplitGroupNormals.Num() > 0 + && !SplitGroupNormals.IsValidIndex( (WedgeNormalCount - 1) * 3 + 2 ) ) + { + // Ignore normals + WedgeNormalCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); + } + + // Transfer the normals and generate the tangents if needed + RawMesh.WedgeTangentZ.SetNumZeroed( WedgeNormalCount ); + for ( int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx ) + { + FVector WedgeTangentZ; + WedgeTangentZ.X = SplitGroupNormals[ WedgeTangentZIdx * 3 + 0 ]; + if ( ImportAxis == HRSAI_Unreal ) + { + // We need to flip Z and Y coordinate + WedgeTangentZ.Y = SplitGroupNormals[ WedgeTangentZIdx * 3 + 2 ]; + WedgeTangentZ.Z = SplitGroupNormals[ WedgeTangentZIdx * 3 + 1 ]; + } + else + { + WedgeTangentZ.Y = SplitGroupNormals[ WedgeTangentZIdx * 3 + 1 ]; + WedgeTangentZ.Z = SplitGroupNormals[ WedgeTangentZIdx * 3 + 2 ]; + } + + RawMesh.WedgeTangentZ[ WedgeTangentZIdx ] = WedgeTangentZ; + + // If we need to generate tangents. + if ( bGenerateTangents ) + { + FVector TangentX, TangentY; + WedgeTangentZ.FindBestAxisVectors( TangentX, TangentY ); + + RawMesh.WedgeTangentX.Add( TangentX ); + RawMesh.WedgeTangentY.Add( TangentY ); + } + } + + // Only add tangents if we have some and do not plan on generating them. + // We also need to make sure that the number of tangents matches the number of normals + bool bAddTangents = !bGenerateTangents && bReadTangents; + int32 WedgeTangentUCount = SplitGroupTangentU.Num() / 3; + int32 WedgeTangentVCount = SplitGroupTangentV.Num() / 3; + if ( WedgeTangentUCount != WedgeNormalCount || WedgeTangentVCount != WedgeNormalCount ) + bAddTangents = false; + + if ( bAddTangents ) + { + // Transfer tangents if we have them and they're valid + RawMesh.WedgeTangentX.SetNumZeroed(WedgeTangentUCount); + for (int32 WedgeTangentUIdx = 0; WedgeTangentUIdx < WedgeTangentUCount; ++WedgeTangentUIdx) + { + FVector WedgeTangentX; + WedgeTangentX.X = SplitGroupTangentU[WedgeTangentUIdx * 3 + 0]; + if (ImportAxis == HRSAI_Unreal) + { + // We need to flip Z and Y coordinate + WedgeTangentX.Y = SplitGroupTangentU[WedgeTangentUIdx * 3 + 2]; + WedgeTangentX.Z = SplitGroupTangentU[WedgeTangentUIdx * 3 + 1]; + } + else + { + WedgeTangentX.Y = SplitGroupTangentU[WedgeTangentUIdx * 3 + 1]; + WedgeTangentX.Z = SplitGroupTangentU[WedgeTangentUIdx * 3 + 2]; + } + + RawMesh.WedgeTangentX[WedgeTangentUIdx] = WedgeTangentX; + } + + RawMesh.WedgeTangentY.SetNumZeroed(WedgeTangentVCount); + for (int32 WedgeTangentVIdx = 0; WedgeTangentVIdx < WedgeTangentVCount; ++WedgeTangentVIdx) + { + FVector WedgeTangentY; + WedgeTangentY.X = SplitGroupTangentV[WedgeTangentVIdx * 3 + 0]; + if (ImportAxis == HRSAI_Unreal) + { + // We need to flip Z and Y coordinate + WedgeTangentY.Y = SplitGroupTangentV[WedgeTangentVIdx * 3 + 2]; + WedgeTangentY.Z = SplitGroupTangentV[WedgeTangentVIdx * 3 + 1]; + } + else + { + WedgeTangentY.Y = SplitGroupTangentV[WedgeTangentVIdx * 3 + 1]; + WedgeTangentY.Z = SplitGroupTangentV[WedgeTangentVIdx * 3 + 2]; + } + + RawMesh.WedgeTangentY[WedgeTangentVIdx] = WedgeTangentY; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // VERTEX COLORS AND ALPHAS + //--------------------------------------------------------------------------------------------------------------------- + TArray< float > SplitGroupColors; + if ( PartColors.Num() <= 0 ) + { + // Retrieve color data + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, + PartInfo.id, HAPI_UNREAL_ATTRIB_COLOR, AttribInfoColors, PartColors ); + } + + // See if we need to transfer color point attributes to vertex attributes. + FHoudiniEngineUtils::TransferRegularPointAttributesToVertices( + SplitGroupVertexList, AttribInfoColors, PartColors, SplitGroupColors ); + + TArray< float > SplitGroupAlphas; + if ( PartAlphas.Num() <= 0 ) + { + // Retrieve alpha data + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, + PartInfo.id, HAPI_UNREAL_ATTRIB_ALPHA, AttribInfoAlpha, PartAlphas ); + } + + // See if we need to transfer alpha point attributes to vertex attributes. + FHoudiniEngineUtils::TransferRegularPointAttributesToVertices( + SplitGroupVertexList, AttribInfoAlpha, PartAlphas, SplitGroupAlphas ); + + // Transfer colors and alphas to the raw mesh + if ( AttribInfoColors.exists && ( AttribInfoColors.tupleSize > 0 ) ) + { + int32 WedgeColorsCount = SplitGroupColors.Num() / AttribInfoColors.tupleSize; + + // Ensure the number of color values is correct + if (!SplitGroupColors.IsValidIndex( (WedgeColorsCount - 1) * 3 + 2) ) + { + // Ignore colors + WedgeColorsCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid vertex color count detected - Skipping colors.")); + } + + RawMesh.WedgeColors.SetNumZeroed( WedgeColorsCount ); + for ( int32 WedgeColorIdx = 0; WedgeColorIdx < WedgeColorsCount; ++WedgeColorIdx ) + { + FLinearColor WedgeColor; + WedgeColor.R = FMath::Clamp( + SplitGroupColors[ WedgeColorIdx * AttribInfoColors.tupleSize + 0 ], 0.0f, 1.0f ); + WedgeColor.G = FMath::Clamp( + SplitGroupColors[ WedgeColorIdx * AttribInfoColors.tupleSize + 1 ], 0.0f, 1.0f ); + WedgeColor.B = FMath::Clamp( + SplitGroupColors[ WedgeColorIdx * AttribInfoColors.tupleSize + 2 ], 0.0f, 1.0f ); + + if( AttribInfoAlpha.exists ) + { + WedgeColor.A = FMath::Clamp( SplitGroupAlphas[ WedgeColorIdx ], 0.0f, 1.0f ); + } + else if ( AttribInfoColors.tupleSize == 4 ) + { + // We have alpha. + WedgeColor.A = FMath::Clamp( + SplitGroupColors[ WedgeColorIdx * AttribInfoColors.tupleSize + 3 ], 0.0f, 1.0f ); + } + else + { + WedgeColor.A = 1.0f; + } + + // Convert linear color to fixed color. + RawMesh.WedgeColors[ WedgeColorIdx ] = WedgeColor.ToFColor( false ); + } + } + else + { + // No Colors or Alphas, init colors to White + FColor DefaultWedgeColor = FLinearColor::White.ToFColor( false ); + int32 WedgeColorsCount = RawMesh.WedgeIndices.Num(); + if ( WedgeColorsCount > 0 ) + RawMesh.WedgeColors.Init( DefaultWedgeColor, WedgeColorsCount ); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE SMOOTHING + //--------------------------------------------------------------------------------------------------------------------- + + // Retrieve face smoothing data. + if ( PartFaceSmoothingMasks.Num() <= 0 ) + { + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, + PartInfo.id, MarshallingAttributeNameFaceSmoothingMask.c_str(), + AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks ); + } + + // Set face smoothing masks. + RawMesh.FaceSmoothingMasks.SetNumZeroed( SplitGroupFaceCount ); + if ( PartFaceSmoothingMasks.Num() ) + { + int32 ValidFaceIdx = 0; + for ( int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx += 3 ) + { + int32 WedgeCheck = SplitGroupVertexList[ VertexIdx + 0 ]; + if ( WedgeCheck == -1 ) + continue; + + RawMesh.FaceSmoothingMasks[ ValidFaceIdx ] = PartFaceSmoothingMasks[ VertexIdx / 3 ]; + ValidFaceIdx++; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // UVS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract all UV sets + TArray< TArray< float > > SplitGroupUVs; + SplitGroupUVs.SetNumZeroed( MAX_STATIC_TEXCOORDS ); + + if ( PartUVs.Num() && PartUVs[0].Num() <= 0 ) + { + // Retrieve all the UVs sets for this part + FHoudiniEngineUtils::GetAllUVAttributesInfoAndTexCoords( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, + AttribInfoUVs, PartUVs ); + } + + // See if we need to transfer uv point attributes to vertex attributes. + for ( int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx ) + { + FHoudiniEngineUtils::TransferRegularPointAttributesToVertices( + SplitGroupVertexList, AttribInfoUVs[ TexCoordIdx ], PartUVs[ TexCoordIdx ], SplitGroupUVs[ TexCoordIdx ] ); + } + + // Transfer UVs to the Raw Mesh + int32 UVChannelCount = 0; + int32 LightMapUVChannel = 0; + for ( int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx ) + { + TArray< float > & TextureCoordinate = SplitGroupUVs[ TexCoordIdx ]; + int32 WedgeUVCount = TextureCoordinate.Num() / 2; + + if ( TextureCoordinate.Num() > 0 && TextureCoordinate.IsValidIndex((WedgeUVCount - 1) * 2 + 1) ) + { + RawMesh.WedgeTexCoords[ TexCoordIdx ].SetNumZeroed( WedgeUVCount ); + for ( int32 WedgeUVIdx = 0; WedgeUVIdx < WedgeUVCount; ++WedgeUVIdx ) + { + // We need to flip V coordinate when it's coming from HAPI. + FVector2D WedgeUV; + WedgeUV.X = TextureCoordinate[ WedgeUVIdx * 2 + 0 ]; + WedgeUV.Y = 1.0f - TextureCoordinate[ WedgeUVIdx * 2 + 1 ]; + + RawMesh.WedgeTexCoords[ TexCoordIdx ][ WedgeUVIdx ] = WedgeUV; + } + + UVChannelCount++; + + if ( UVChannelCount <= 2 ) + LightMapUVChannel = TexCoordIdx; + } + else + { + RawMesh.WedgeTexCoords[ TexCoordIdx ].Empty(); + } + } + + // We have to have at least one UV channel. If there's none, create one with zero data. + if (UVChannelCount == 0) + RawMesh.WedgeTexCoords[ 0 ].SetNumZeroed( SplitGroupVertexListCount ); + + // Set the lightmap Coordinate Index + // If we have more than one UV set, the 2nd set will be used for lightmaps by convention + // If not, the first UV set will be used + StaticMesh->LightMapCoordinateIndex = LightMapUVChannel; + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // Get lightmap resolution (if present). + if ( PartLightMapResolutions.Num() <= 0 ) + { + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, + PartInfo.id, MarshallingAttributeNameLightmapResolution.c_str(), + AttribLightmapResolution, PartLightMapResolutions ); + } + + // make sure the mesh has a new lighting guid + StaticMesh->LightingGuid = FGuid::NewGuid(); + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES + //--------------------------------------------------------------------------------------------------------------------- + + // + // Because of the splits, we don't need to declare all the vertices in the Part, + // but only the one that are currently used by the split's faces. + // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. + // We also keep track of the needed vertices index to declare them easily afterwards. + // + + // IndicesMapper: + // Maps index values for all vertices in the Part: + // - Vertices unused by the split will be set to -1 + // - Used vertices will have their value set to the "NewIndex" + // So that IndicesMapper[ oldIndex ] => newIndex + TArray< int32 > IndicesMapper; + IndicesMapper.Init( -1, SplitGroupVertexList.Num() ); + int32 CurrentMapperIndex = 0; + + // Neededvertices: + // Contains the old index of the needed vertices for the current split + // NeededVertices[ newIndex ] => oldIndex + TArray< int32 > NeededVertices; + RawMesh.WedgeIndices.SetNumZeroed( SplitGroupVertexListCount ); + + int32 ValidVertexId = 0; + for ( int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx += 3 ) + { + int32 WedgeCheck = SplitGroupVertexList[ VertexIdx + 0 ]; + if ( WedgeCheck == -1 ) + continue; + + int32 WedgeIndices[ 3 ] = + { + SplitGroupVertexList[ VertexIdx + 0 ], + SplitGroupVertexList[ VertexIdx + 1 ], + SplitGroupVertexList[ VertexIdx + 2 ] + }; + + // Ensure the indices are valid + if ( !IndicesMapper.IsValidIndex( WedgeIndices[0] ) + || !IndicesMapper.IsValidIndex( WedgeIndices[1] ) + || !IndicesMapper.IsValidIndex( WedgeIndices[2] ) ) + { + // Invalid face index. + HOUDINI_LOG_MESSAGE( + TEXT( "Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face index "), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName ); + + continue; + } + + // Converting Old (Part) Indices to New (Split) Indices: + for ( int32 i = 0; i < 3; i++ ) + { + if ( IndicesMapper[ WedgeIndices[ i ] ] < 0 ) + { + // This old index was not yet "converted" to a new index + NeededVertices.Add( WedgeIndices[ i ] ); + + IndicesMapper[ WedgeIndices[ i ] ] = CurrentMapperIndex; + CurrentMapperIndex++; + } + + // Replace the old index with the new one + WedgeIndices[ i ] = IndicesMapper[ WedgeIndices[ i ] ]; + } + + if ( !RawMesh.WedgeIndices.IsValidIndex(ValidVertexId + 2) ) + break; + + if ( ImportAxis == HRSAI_Unreal ) + { + // Flip wedge indices to fix the winding order. + RawMesh.WedgeIndices[ ValidVertexId + 0 ] = WedgeIndices[ 0 ]; + RawMesh.WedgeIndices[ ValidVertexId + 1 ] = WedgeIndices[ 2 ]; + RawMesh.WedgeIndices[ ValidVertexId + 2 ] = WedgeIndices[ 1 ]; + + // Check if we need to patch UVs. + for ( int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx ) + { + if ( RawMesh.WedgeTexCoords[ TexCoordIdx ].IsValidIndex( ValidVertexId + 2) ) + { + Swap( RawMesh.WedgeTexCoords[ TexCoordIdx ][ ValidVertexId + 1 ], + RawMesh.WedgeTexCoords[ TexCoordIdx ][ ValidVertexId + 2 ] ); + } + } + + // Check if we need to patch colors. + if ( RawMesh.WedgeColors.IsValidIndex(ValidVertexId + 2) ) + Swap( RawMesh.WedgeColors[ ValidVertexId + 1 ], RawMesh.WedgeColors[ ValidVertexId + 2 ] ); + + // Check if we need to patch Normals and tangents. + if ( RawMesh.WedgeTangentZ.IsValidIndex(ValidVertexId + 2) ) + Swap( RawMesh.WedgeTangentZ[ ValidVertexId + 1 ], RawMesh.WedgeTangentZ[ ValidVertexId + 2 ] ); + + if ( RawMesh.WedgeTangentX.IsValidIndex(ValidVertexId + 2) ) + Swap( RawMesh.WedgeTangentX[ ValidVertexId + 1 ], RawMesh.WedgeTangentX[ ValidVertexId + 2 ] ); + + if ( RawMesh.WedgeTangentY.IsValidIndex(ValidVertexId + 2) ) + Swap ( RawMesh.WedgeTangentY[ ValidVertexId + 1 ], RawMesh.WedgeTangentY[ ValidVertexId + 2 ] ); + } + else if ( ImportAxis == HRSAI_Houdini ) + { + // Dont flip the wedge indices + RawMesh.WedgeIndices[ ValidVertexId + 0 ] = WedgeIndices[ 0 ]; + RawMesh.WedgeIndices[ ValidVertexId + 1 ] = WedgeIndices[ 1 ]; + RawMesh.WedgeIndices[ ValidVertexId + 2 ] = WedgeIndices[ 2 ]; + } + + ValidVertexId += 3; + } + + //--------------------------------------------------------------------------------------------------------------------- + // POSITIONS + //--------------------------------------------------------------------------------------------------------------------- + + // We may already have gotten the positions when creating the ucx collisions + if ( PartPositions.Num() <= 0 ) + { + // Retrieve position data. + if ( !FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, + PartInfo.id, HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, PartPositions ) ) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unable to retrieve position data ") + TEXT("- skipping."), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName ); + + if ( bStaticMeshCreated ) + StaticMesh->MarkPendingKill(); + + break; + } + } + + // + // Transfer vertex positions: + // + // Because of the split, we're only interested in the needed vertices. + // Instead of declaring all the Positions, we'll only declare the vertices + // needed by the current split. + // + int32 VertexPositionsCount = NeededVertices.Num(); + RawMesh.VertexPositions.SetNumZeroed( VertexPositionsCount ); + for ( int32 VertexPositionIdx = 0; VertexPositionIdx < VertexPositionsCount; ++VertexPositionIdx ) + { + int32 NeededVertexIndex = NeededVertices[ VertexPositionIdx ]; + if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName); + } + + FVector VertexPosition; + VertexPosition.X = PartPositions[ NeededVertexIndex * 3 + 0 ] * GeneratedGeometryScaleFactor; + if ( ImportAxis == HRSAI_Unreal ) + { + // We need to swap Z and Y coordinate here. + VertexPosition.Y = PartPositions[ NeededVertexIndex * 3 + 2 ] * GeneratedGeometryScaleFactor; + VertexPosition.Z = PartPositions[ NeededVertexIndex * 3 + 1 ] * GeneratedGeometryScaleFactor; + } + else if ( ImportAxis == HRSAI_Houdini ) + { + // No swap required. + VertexPosition.Y = PartPositions[ NeededVertexIndex * 3 + 1 ] * GeneratedGeometryScaleFactor; + VertexPosition.Z = PartPositions[ NeededVertexIndex * 3 + 2 ] * GeneratedGeometryScaleFactor; + } + + RawMesh.VertexPositions[ VertexPositionIdx ] = VertexPosition; + } + + // We need to check if this mesh contains only degenerate triangles. + if ( FHoudiniEngineUtils::CountDegenerateTriangles( RawMesh ) == SplitGroupFaceCount ) + { + // This mesh contains only degenerate triangles, there's nothing we can do. + if ( bStaticMeshCreated ) + StaticMesh->MarkPendingKill(); + + continue; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL ATTRIBUTE OVERRIDES + //--------------------------------------------------------------------------------------------------------------------- + + // See if we have material override attributes + if ( PartFaceMaterialAttributeOverrides.Num() <= 0 ) + { + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, + MarshallingAttributeNameMaterial.c_str(), + AttribFaceMaterials, PartFaceMaterialAttributeOverrides ); + + // If material attribute was not found, check fallback compatibility attribute. + if ( !AttribFaceMaterials.exists ) + { + PartFaceMaterialAttributeOverrides.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, + MarshallingAttributeNameMaterialFallback.c_str(), + AttribFaceMaterials, PartFaceMaterialAttributeOverrides ); + } + + // If material attribute and fallbacks were not found, check the material instance attribute. + if ( !AttribFaceMaterials.exists ) + { + PartFaceMaterialAttributeOverrides.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, + MarshallingAttributeNameMaterialInstance.c_str(), + AttribFaceMaterials, PartFaceMaterialAttributeOverrides); + } + + if ( AttribFaceMaterials.exists && AttribFaceMaterials.owner != HAPI_ATTROWNER_PRIM && AttribFaceMaterials.owner != HAPI_ATTROWNER_DETAIL ) + { + HOUDINI_LOG_WARNING( TEXT( "Static Mesh [%d %s], Geo [%d], Part [%d %s]: unreal_material must be a primitive or detail attribute, ignoring attribute." ), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName); + AttribFaceMaterials.exists = false; + PartFaceMaterialAttributeOverrides.Empty(); + } + + // If the material name was assigned per detail we replicate it for each primitive. + if ( PartFaceMaterialAttributeOverrides.Num() > 0 && AttribFaceMaterials.owner == HAPI_ATTROWNER_DETAIL ) + { + FString SingleFaceMaterial = PartFaceMaterialAttributeOverrides[ 0 ]; + PartFaceMaterialAttributeOverrides.Init( SingleFaceMaterial, SplitGroupVertexList.Num() / 3 ); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + + // Process material overrides first. + if ( PartFaceMaterialAttributeOverrides.Num() > 0 ) + { + // Clear the previously generated materials ( unless we're not the first lod level ) + if ( !IsLOD || ( IsLOD && LodIndex == 0 ) ) + StaticMesh->StaticMaterials.Empty(); + + RawMesh.FaceMaterialIndices.SetNumZeroed( SplitGroupFaceCount ); + for ( int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx ) + { + int32 SplitFaceIndex = SplitGroupFaceIndices[ FaceIdx ]; + if ( !PartFaceMaterialAttributeOverrides.IsValidIndex( SplitFaceIndex ) ) + continue; + + const FString & MaterialName = PartFaceMaterialAttributeOverrides[ SplitFaceIndex ]; + int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find( MaterialName ); + int32 CurrentFaceMaterialIdx = 0; + if ( FoundFaceMaterialIdx ) + { + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Try to locate the corresponding material interface + UMaterialInterface * MaterialInterface = nullptr; + if (!MaterialName.IsEmpty()) + { + // Only try to load a material if has a chance to be valid! + MaterialInterface = Cast< UMaterialInterface >( + StaticLoadObject(UMaterialInterface::StaticClass(), + nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + } + + if ( MaterialInterface ) + { + // Make sure this material is in the assignments before replacing it. + if( !HoudiniCookParams.HoudiniCookManager->GetAssignmentMaterial( MaterialInterface->GetName() ) ) + HoudiniCookParams.HoudiniCookManager->AddAssignmentMaterial( MaterialInterface->GetName(), MaterialInterface ); + + // See if we have a replacement material for this. + UMaterialInterface * ReplacementMaterialInterface = HoudiniCookParams.HoudiniCookManager->GetReplacementMaterial( HoudiniGeoPartObject, MaterialInterface->GetName() ); + if( ReplacementMaterialInterface ) + MaterialInterface = ReplacementMaterialInterface; + + // Add this material to the map + CurrentFaceMaterialIdx = StaticMesh->StaticMaterials.Add( FStaticMaterial( MaterialInterface ) ); + MapHoudiniMatAttributesToUnrealIndex.Add( MaterialName, CurrentFaceMaterialIdx ); + } + else + { + // The Attribute Material and its replacement do not exist + // See if we can fallback to the Houdini material assigned on the face + + // Get the unreal material corresponding to this houdini one + HAPI_NodeId MaterialId = PartFaceMaterialIds[ SplitFaceIndex ]; + + // See if we have already treated that material + int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find( MaterialId ); + if ( FoundUnrealMatIndex ) + { + // This material has been mapped already, just assign the mat index + CurrentFaceMaterialIdx = *FoundUnrealMatIndex; + } + else + { + // If everything fails, we'll use the default material + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + + // We need to add this material to the map + FString MaterialShopName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniEngineMaterialUtils::GetUniqueMaterialShopName( AssetId, MaterialId, MaterialShopName ); + UMaterialInterface * const * FoundMaterial = Materials.Find( MaterialShopName ); + if ( FoundMaterial ) + MaterialInterface = *FoundMaterial; + + // If we have a replacement material for this geo part object and this shop material name. + UMaterialInterface * ReplacementMaterial = + HoudiniCookParams.HoudiniCookManager->GetReplacementMaterial( HoudiniGeoPartObject, MaterialShopName ); + + if ( ReplacementMaterial ) + MaterialInterface = ReplacementMaterial; + + // Add the material to the Static mesh + CurrentFaceMaterialIdx = StaticMesh->StaticMaterials.Add( FStaticMaterial( MaterialInterface ) ); + + // Map the Houdini ID to the unreal one + MapHoudiniMatIdToUnrealIndex.Add( MaterialId, CurrentFaceMaterialIdx ); + } + } + } + + // Update the Face Material on the mesh + RawMesh.FaceMaterialIndices[ FaceIdx ] = CurrentFaceMaterialIdx; + } + } + else + { + if ( bPartHasMaterials ) + { + if ( bSingleFaceMaterial ) + { + // Use default Houdini material if no valid material is assigned to any of the faces. + UMaterialInterface * Material = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + + // We have only one material. + RawMesh.FaceMaterialIndices.SetNumZeroed( SplitGroupFaceCount ); + + // Get id of this single material. + FString MaterialShopName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniEngineMaterialUtils::GetUniqueMaterialShopName( AssetId, PartFaceMaterialIds[ 0 ], MaterialShopName ); + UMaterialInterface * const * FoundMaterial = Materials.Find( MaterialShopName ); + + if ( FoundMaterial ) + Material = *FoundMaterial; + + // If we have replacement material for this geo part object and this shop material name. + UMaterialInterface * ReplacementMaterial = + HoudiniCookParams.HoudiniCookManager->GetReplacementMaterial( HoudiniGeoPartObject, MaterialShopName ); + + if ( ReplacementMaterial ) + Material = ReplacementMaterial; + + StaticMesh->StaticMaterials.Empty(); + StaticMesh->StaticMaterials.Add( FStaticMaterial(Material) ); + } + else + { + // We have multiple materials + // Clear the previously generated materials ( unless we're not the first lod level ) + if ( !IsLOD || ( IsLOD && LodIndex == 0 ) ) + StaticMesh->StaticMaterials.Empty(); + + // Get default Houdini material. + UMaterial * MaterialDefault = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + + // Reset Rawmesh material face assignments. + RawMesh.FaceMaterialIndices.SetNumZeroed( SplitGroupFaceCount ); + for ( int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx ) + { + int32 SplitFaceIndex = SplitGroupFaceIndices[ FaceIdx ]; + if ( !PartFaceMaterialIds.IsValidIndex( SplitFaceIndex ) ) + continue; + + // Get material id for this face. + HAPI_NodeId MaterialId = PartFaceMaterialIds[ SplitFaceIndex ]; + + // See if we have already treated that material + int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find( MaterialId ); + if ( FoundUnrealMatIndex ) + { + // This material has been mapped already, just assign the mat index + RawMesh.FaceMaterialIndices[ FaceIdx ] = *FoundUnrealMatIndex; + continue; + } + + UMaterialInterface * Material = Cast(MaterialDefault); + + FString MaterialShopName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniEngineMaterialUtils::GetUniqueMaterialShopName( AssetId, MaterialId, MaterialShopName ); + UMaterialInterface * const * FoundMaterial = Materials.Find( MaterialShopName ); + if ( FoundMaterial ) + Material = *FoundMaterial; + + // See if we have replacement material for this geo part object and this shop material name. + UMaterialInterface * ReplacementMaterial = + HoudiniCookParams.HoudiniCookManager->GetReplacementMaterial( HoudiniGeoPartObject, MaterialShopName ); + + if ( ReplacementMaterial ) + Material = ReplacementMaterial; + + // Add the material to the Static mesh + int32 UnrealMatIndex = StaticMesh->StaticMaterials.Add( FStaticMaterial( Material ) ); + + // Map the houdini ID to the unreal one + MapHoudiniMatIdToUnrealIndex.Add( MaterialId, UnrealMatIndex ); + + // Update the face index + RawMesh.FaceMaterialIndices[ FaceIdx ] = UnrealMatIndex; + } + } + } + else + { + // No materials were found, we need to use default Houdini material. + RawMesh.FaceMaterialIndices.SetNumZeroed( SplitGroupFaceCount ); + + UMaterialInterface * Material = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + FString MaterialShopName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + + // If we have replacement material for this geo part object and this shop material name. + UMaterialInterface * ReplacementMaterial = + HoudiniCookParams.HoudiniCookManager->GetReplacementMaterial( HoudiniGeoPartObject, MaterialShopName ); + + if ( ReplacementMaterial ) + Material = ReplacementMaterial; + + StaticMesh->StaticMaterials.Empty(); + StaticMesh->StaticMaterials.Add( FStaticMaterial(Material) ); + } + } + + // Some mesh generation settings. + HoudiniRuntimeSettings->SetMeshBuildSettings( SrcModel->BuildSettings, RawMesh ); + + // By default the distance field resolution should be set to 2.0 + SrcModel->BuildSettings.DistanceFieldResolutionScale = HoudiniCookParams.GeneratedDistanceFieldResolutionScale; + + // We need to check light map uv set for correctness. Unreal seems to have occasional issues with + // zero UV sets when building lightmaps. + int32 LightMapResolutionOverride = -1; + if ( SrcModel->BuildSettings.bGenerateLightmapUVs ) + { + // See if we need to disable lightmap generation because of bad UVs. + if ( FHoudiniEngineUtils::ContainsInvalidLightmapFaces( RawMesh, StaticMesh->LightMapCoordinateIndex ) ) + { + SrcModel->BuildSettings.bGenerateLightmapUVs = false; + + HOUDINI_LOG_MESSAGE( + TEXT( "Skipping Lightmap Generation: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] invalid face detected " ) + TEXT( "- skipping." ), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName ); + } + + if( PartLightMapResolutions.Num() > 0 ) + LightMapResolutionOverride = PartLightMapResolutions[ 0 ]; + + // Apply lightmap resolution override if it has been specified + if ( LightMapResolutionOverride > 0 ) + StaticMesh->LightMapResolution = LightMapResolutionOverride; + } + + if ( !RawMesh.IsValidOrFixable() ) + { + HOUDINI_LOG_WARNING( + TEXT("Static Mesh Generated from Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] is invalid!") + TEXT("- skipping."), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName); + + if ( bStaticMeshCreated ) + StaticMesh->MarkPendingKill(); + + continue; + } + + // This is required due to the impeding deprecation of FRawMesh + // If we dont update this UE4 will crash upon deleting an asset. + SrcModel->StaticMeshOwner = StaticMesh; + // Store the new raw mesh. + SrcModel->SaveRawMesh(RawMesh); + + // Lambda for initializing a LOD level + auto InitLODLevel = [ & ]( const int32& LODLevelIndex ) + { + // Ensure that this LOD level exisits + while (StaticMesh->GetNumSourceModels() < (LODLevelIndex + 1)) + StaticMesh->AddSourceModel(); + + // Set its reduction settings to the default + StaticMesh->GetSourceModel(LODLevelIndex).ReductionSettings = LODGroup.GetDefaultSettings( LODLevelIndex ); + + for ( int32 MaterialIndex = 0; MaterialIndex < StaticMesh->StaticMaterials.Num(); ++MaterialIndex ) + { + FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get( LODLevelIndex, MaterialIndex ); + Info.MaterialIndex = MaterialIndex; + Info.bEnableCollision = true; + Info.bCastShadow = true; + StaticMesh->GetSectionInfoMap().Set( LODLevelIndex, MaterialIndex, Info ); + } + }; + + if ( !IsLOD ) + { + // For non LODed mesh, init the default number of LODs + for ( int32 ModelLODIndex = 0; ModelLODIndex < DefaultNumLODs; ++ModelLODIndex ) + InitLODLevel( ModelLODIndex ); + } + else + { + // Init the current LOD level + InitLODLevel( LodIndex ); + + bool InvalidateLODAttr = false; + + // If the "lod_screensize" attribute was not found, fallback to the "lodX_screensize" attribute + if ( !AttribInfoLODScreenSize.exists ) + { + LODScreenSizes.Empty(); + FString LODAttributeName = SplitGroupName + TEXT("_screensize"); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, + TCHAR_TO_ANSI(*LODAttributeName), AttribInfoLODScreenSize, LODScreenSizes); + + InvalidateLODAttr = AttribInfoLODScreenSize.exists; + } + + // finally, look for a potential uproperty style attribute + if ( !AttribInfoLODScreenSize.exists ) + { + LODScreenSizes.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, + "unreal_uproperty_screensize", AttribInfoLODScreenSize, LODScreenSizes); + + InvalidateLODAttr = AttribInfoLODScreenSize.exists; + } + + if ( AttribInfoLODScreenSize.exists ) + { + float screensize = -1.0; + if ( AttribInfoLODScreenSize.owner == HAPI_ATTROWNER_PRIM ) + { + int32 n = 0; + for( ; n < SplitGroupVertexList.Num(); n++ ) + { + if ( SplitGroupVertexList[ n ] > 0 ) + break; + } + + screensize = LODScreenSizes[ n / 3 ]; + } + else + { + // Handle single screensize attributes + if ( LODScreenSizes.Num() == 1 ) + screensize = LODScreenSizes[ 0 ]; + else + { + // Handle tuple screensize attributes + if ( LODScreenSizes.IsValidIndex( LodIndex ) ) + screensize = LODScreenSizes[ LodIndex ]; + else + screensize = 0.0f; + } + } + + // Make sure the LOD Screensize is a percent, so if its above 1, divide by 100 + if ( screensize > 1.0f ) + screensize /= 100.0f; + + // Only apply the LOD screensize if it's valid + if ( screensize >= 0.0f ) + { + StaticMesh->GetSourceModel(LodIndex).ScreenSize = screensize; + StaticMesh->bAutoComputeLODScreenSize = false; + } + + if ( InvalidateLODAttr ) + AttribInfoLODScreenSize.exists = false; + } + + // Increment the LODIndex + LodIndex++; + } + + // The following actions needs to be done only once per Static Mesh, + // So if we are a LOD level other than the last one, skip this! + if ( IsLOD && ( LodIndex != NumberOfLODs ) ) + { + // The First LOD still needs to add the mesh to the out list so we can reuse it for the next LOD levels + if ( LodIndex == 1 ) + StaticMeshesOut.Add( HoudiniGeoPartObject, StaticMesh ); + + continue; + } + + // Assign generation parameters for this static mesh. + HoudiniCookParams.HoudiniCookManager->SetStaticMeshGenerationParameters( StaticMesh ); + + // Make sure we remove the old simple colliders if needed + if( UBodySetup * BodySetup = StaticMesh->BodySetup ) + { + // Simple colliders are from a previous cook, remove them! + if( !HoudiniGeoPartObject.bHasCollisionBeenAdded ) + BodySetup->RemoveSimpleCollision(); + + // See if we need to enable collisions on the whole mesh. + if( ( HoudiniGeoPartObject.IsCollidable() || HoudiniGeoPartObject.IsRenderCollidable() ) + && ( !HoudiniGeoPartObject.bIsSimpleCollisionGeo && !HoudiniGeoPartObject.bIsUCXCollisionGeo ) ) + { + // Enable collisions for this static mesh. + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + else if ( !HoudiniGeoPartObject.bIsSimpleCollisionGeo && !HoudiniGeoPartObject.bIsUCXCollisionGeo ) + { + // We dont have collider meshes, or simple colliders, if the LODForCollision uproperty attribute is set + // we need to activate complex collision for that lod to be picked up as collider + if ( HapiCheckAttributeExists( HoudiniGeoPartObject, "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL ) ) + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + } + + // See if a custom bake name override for this mesh was assigned via the "unreal_bake_name" attribute + TArray< FString > BakeNameOverrides; + { + HAPI_AttributeInfo AttribBakeNameOverride; + FHoudiniApi::AttributeInfo_Init(&AttribBakeNameOverride); + + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, + HAPI_UNREAL_ATTRIB_BAKE_NAME, AttribBakeNameOverride, BakeNameOverrides); + + if (BakeNameOverrides.Num() > 0) + { + int32 AttrIdx = 0; + if (AttribBakeNameOverride.owner == HAPI_ATTROWNER_PRIM) + { + // If the attribute is on primitives, we need to find + // the index of the prim that correspond to our split + for (int32 n = 0; n < SplitGroupVertexList.Num(); n++) + { + if (SplitGroupVertexList[n] <= 0) + continue; + + AttrIdx = n / 3; + break; + } + + if (!BakeNameOverrides.IsValidIndex(AttrIdx)) + AttrIdx = 0; + } + + FString BakeNameOverride = BakeNameOverrides[AttrIdx]; + if (!BakeNameOverride.IsEmpty()) + { + // If the name override was set on the details and we have multiple split + // append the split name to the override to avoid collisions on bake + if (AttribBakeNameOverride.owner == HAPI_ATTROWNER_DETAIL + && SplitGroupNames.Num() > 1) + { + if (!SplitGroupName.Equals("main_geo", ESearchCase::IgnoreCase)) + BakeNameOverride += "_" + SplitGroupName; + } + + FString& OverrideStrRef = HoudiniCookParams.BakeNameOverrides->FindOrAdd(HoudiniGeoPartObject); + OverrideStrRef = BakeNameOverride; + } + } + } + + // Try to update the uproperties of the StaticMesh + UpdateUPropertyAttributesOnObject( StaticMesh, HoudiniGeoPartObject); + + // BUILD the Static Mesh + FHoudiniScopedGlobalSilence ScopedGlobalSilence; + TArray< FText > BuildErrors; + { + SCOPE_CYCLE_COUNTER( STAT_BuildStaticMesh ); + StaticMesh->Build( true, &BuildErrors ); + } + + for ( int32 BuildErrorIdx = 0; BuildErrorIdx < BuildErrors.Num(); ++BuildErrorIdx ) + { + const FText & TextError = BuildErrors[ BuildErrorIdx ]; + HOUDINI_LOG_MESSAGE( + TEXT( "Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] build error " ) + TEXT( "- %s." ), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName, *( TextError.ToString() ) ); + } + + // Skip Invalid static meshes + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid Static Mesh created! ") + TEXT("- skipping."), + ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName ); + + continue; + } + + StaticMesh->GetOnMeshChanged().Broadcast(); + + // Do we need to add simple collisions ? + bool bSimpleCollisionAddedToAggregate = false; + if ( HoudiniGeoPartObject.bIsSimpleCollisionGeo ) + { + if ( AddSimpleCollision( SplitGroupName, StaticMesh, HoudiniGeoPartObject, AggregateCollisionGeo, bSimpleCollisionAddedToAggregate ) ) + { + if ( bSimpleCollisionAddedToAggregate ) + { + // The colliders are invisible and have been added to the aggregate, + // we can skip to the next object and this collider will be added after + bHasAggregateGeometryCollision = true; + continue; + } + else + { + // We don't want these collisions to be removed afterwards + HoudiniGeoPartObject.bHasCollisionBeenAdded = true; + } + } + } + + // If any simple collider was added to the aggregate, and this mesh is visible, add the colliders now + if ( bHasAggregateGeometryCollision ) + { + // Add the aggregate collision geo to the static mesh + if ( AddAggregateCollisionGeometryToStaticMesh( StaticMesh, HoudiniGeoPartObject, AggregateCollisionGeo ) ) + bHasAggregateGeometryCollision = false; + } + + // Sockets are attached to the mesh after, for now, clear the existing one ( as they are from a previous cook ) + if ( !HoudiniGeoPartObject.bHasSocketBeenAdded ) + StaticMesh->Sockets.Empty(); + + // Notify that we created a new Static Mesh + if ( bStaticMeshCreated ) + FAssetRegistryModule::AssetCreated( StaticMesh ); + + // Try to find the outer package so we can dirty it up + if (StaticMesh->GetOuter()) + { + StaticMesh->GetOuter()->MarkPackageDirty(); + } + else + { + StaticMesh->MarkPackageDirty(); + } + + StaticMeshesOut.Add( HoudiniGeoPartObject, StaticMesh ); + + } // end for SplitId + + // Add the sockets we found to that part's meshes + if ( AllSockets.Num() > 0 ) + { + bool SocketsAdded = false; + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshesOut ); Iter; ++Iter ) + { + FHoudiniGeoPartObject * CurrentHoudiniGeoPartObject = &(Iter.Key()); + if ( (CurrentHoudiniGeoPartObject->ObjectId != ObjectInfo.nodeId ) + || (CurrentHoudiniGeoPartObject->GeoId != GeoInfo.nodeId ) + || (CurrentHoudiniGeoPartObject->PartId != PartIdx ) ) + continue; + + // This GeoPartObject is from the same object/geo, so we can add the sockets to it + if ( AddMeshSocketsToStaticMesh( Iter.Value(), *CurrentHoudiniGeoPartObject, AllSockets, AllSocketsNames, AllSocketsActors, AllSocketsTags ) ) + SocketsAdded = true; + } + + if ( SocketsAdded ) + { + // Clean up the sockets for this part + AllSockets.Empty(); + AllSocketsNames.Empty(); + AllSocketsActors.Empty(); + AllSocketsTags.Empty(); + } + } + } // end for PartId + + // There should be no UCX/Simple colliders left now + if ( bHasAggregateGeometryCollision ) + HOUDINI_LOG_ERROR( TEXT("All Simple Colliders found in the HDA were not attached to a static mesh!!") ); + + // Add the sockets we found in the parts of this geo to the meshes it has generated + if ( AllSockets.Num() > 0 ) + { + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshesOut ); Iter; ++Iter ) + { + FHoudiniGeoPartObject * HoudiniGeoPartObject = &( Iter.Key() ); + if ( ( HoudiniGeoPartObject->ObjectId != ObjectInfo.nodeId ) || ( HoudiniGeoPartObject->GeoId != GeoInfo.nodeId ) ) + continue; + + // This GeoPartObject is from the same object/geo, so we can add the sockets to it + AddMeshSocketsToStaticMesh( Iter.Value(), *HoudiniGeoPartObject, AllSockets, AllSocketsNames, AllSocketsActors, AllSocketsTags ); + } + } + + // Clean up the sockets for this geo + AllSockets.Empty(); + AllSocketsNames.Empty(); + AllSocketsActors.Empty(); + AllSocketsTags.Empty(); + + } // end for ObjectId + + // Now that all the meshes are built and their collisions meshes and primitives updated, + // we need to update their pre-built navigation collision used by the navmesh + for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshesOut ); Iter; ++Iter ) + { + FHoudiniGeoPartObject HoudiniGeoPartObject = Iter.Key(); + + // Only update for collidable objects + if ( HoudiniGeoPartObject.IsCollidable() || HoudiniGeoPartObject.IsRenderCollidable() ) + { + UStaticMesh* StaticMesh = Iter.Value(); + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + continue; + + UBodySetup * BodySetup = StaticMesh->BodySetup; + if ( BodySetup && !BodySetup->IsPendingKill() ) + { + // Unreal caches the Navigation Collision and never updates it for StaticMeshes, + // so we need to manually flush and recreate the data to have proper navigation collision + if ( StaticMesh->NavCollision ) + { + BodySetup->InvalidatePhysicsData(); + BodySetup->CreatePhysicsMeshes(); + + //StaticMesh->NavCollision->InvalidatePhysicsData(); + //StaticMesh->NavCollision->InvalidateCollision(); + //StaticMesh->NavCollision->CookedFormatData.FlushData(); + //StaticMesh->NavCollision->GatherCollision(); + StaticMesh->NavCollision->Setup(BodySetup); + } + } + } + } + +#endif + + return true; +} + +#if WITH_EDITOR + +bool +FHoudiniEngineUtils::ContainsDegenerateTriangles( const FRawMesh & RawMesh ) +{ + int32 WedgeCount = RawMesh.WedgeIndices.Num(); + for ( int32 WedgeIdx = 0; WedgeIdx < WedgeCount; WedgeIdx += 3 ) + { + const FVector & Vertex0 = RawMesh.VertexPositions[ RawMesh.WedgeIndices[ WedgeIdx + 0 ] ]; + const FVector & Vertex1 = RawMesh.VertexPositions[ RawMesh.WedgeIndices[ WedgeIdx + 1 ] ]; + const FVector & Vertex2 = RawMesh.VertexPositions[ RawMesh.WedgeIndices[ WedgeIdx + 2 ] ]; + + // Strict equality will not detect properly all the degenerated triangles, we need to use Equals here + if ( Vertex0.Equals(Vertex1, THRESH_POINTS_ARE_SAME) || Vertex0.Equals(Vertex2, THRESH_POINTS_ARE_SAME) || Vertex1.Equals(Vertex2, THRESH_POINTS_ARE_SAME) ) + return true; + } + + return false; +} + +int32 +FHoudiniEngineUtils::CountDegenerateTriangles( const FRawMesh & RawMesh ) +{ + int32 DegenerateTriangleCount = 0; + int32 WedgeCount = RawMesh.WedgeIndices.Num(); + for ( int32 WedgeIdx = 0; WedgeIdx < WedgeCount; WedgeIdx += 3 ) + { + const FVector & Vertex0 = RawMesh.VertexPositions[ RawMesh.WedgeIndices[ WedgeIdx + 0 ] ]; + const FVector & Vertex1 = RawMesh.VertexPositions[ RawMesh.WedgeIndices[ WedgeIdx + 1 ] ]; + const FVector & Vertex2 = RawMesh.VertexPositions[ RawMesh.WedgeIndices[ WedgeIdx + 2 ] ]; + + // Strict equality will not detect properly all the degenerated triangles, we need to use Equals here + if( Vertex0.Equals(Vertex1, THRESH_POINTS_ARE_SAME) || Vertex0.Equals(Vertex2, THRESH_POINTS_ARE_SAME) || Vertex1.Equals(Vertex2, THRESH_POINTS_ARE_SAME) ) + DegenerateTriangleCount++; + } + + return DegenerateTriangleCount; +} + +#endif + +int32 +FHoudiniEngineUtils::TransferRegularPointAttributesToVertices( + const TArray< int32 > & VertexList, const HAPI_AttributeInfo & AttribInfo, TArray< float > & Data) +{ + TArray< float > VertexData; + int32 ValidWedgeCount = TransferRegularPointAttributesToVertices( VertexList, AttribInfo, Data, VertexData ); + + if ( ValidWedgeCount > 0 ) + Data = VertexData; + + return ValidWedgeCount; +} + +int32 +FHoudiniEngineUtils::TransferRegularPointAttributesToVertices( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutVertexData) +{ + if (!InAttribInfo.exists || InAttribInfo.tupleSize <= 0) + return 0; + + int32 ValidWedgeCount = 0; + + // Future optimization - see if we can do direct vertex transfer. + int32 WedgeCount = InVertexList.Num(); + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + + int32 LastValidWedgeIdx = 0; + if (InAttribInfo.owner == HAPI_ATTROWNER_POINT) + { + // Point attribute transfer + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + int32 VertexIdx = InVertexList[WedgeIdx]; + if (VertexIdx < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[VertexIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_PRIM) + { + // Primitive attribute transfer + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 PrimIdx = WedgeIdx / 3; + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[PrimIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_DETAIL) + { + // Detail attribute transfer + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_VERTEX) + { + // Vertex attribute transfer + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[WedgeIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else + { + // Invalid attribute owner, shouldn't happen + check(false); + } + + OutVertexData.SetNumZeroed(ValidWedgeCount * InAttribInfo.tupleSize); + + return ValidWedgeCount; +} + +char * +FHoudiniEngineUtils::ExtractRawName( const FString & Name ) +{ + if ( !Name.IsEmpty() ) + { + std::string ConvertedString = TCHAR_TO_UTF8( *Name ); + + // Allocate space for unique string. + int32 UniqueNameBytes = ConvertedString.size() + 1; + char * UniqueName = static_cast< char * >( FMemory::Malloc( UniqueNameBytes ) ); + + FMemory::Memzero( UniqueName, UniqueNameBytes ); + FMemory::Memcpy( UniqueName, ConvertedString.c_str(), ConvertedString.size() ); + + return UniqueName; + } + + return nullptr; +} + +#if WITH_EDITOR +void +FHoudiniEngineUtils::CreateFaceMaterialArray( + const TArray< UMaterialInterface * >& Materials, const TArray< int32 > & FaceMaterialIndices, TArray< char * > & OutStaticMeshFaceMaterials ) +{ + // We need to create list of unique materials. + TArray< char * > UniqueMaterialList; + UMaterialInterface * MaterialInterface; + char * UniqueName = nullptr; + + if ( Materials.Num() ) + { + // We have materials. + for ( int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx ) + { + UniqueName = nullptr; + MaterialInterface = Materials[ MaterialIdx ]; + if ( !MaterialInterface ) + { + // Null material interface found, add default instead. + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + } + + FString FullMaterialName = MaterialInterface->GetPathName(); + UniqueName = FHoudiniEngineUtils::ExtractRawName( FullMaterialName ); + UniqueMaterialList.Add( UniqueName ); + } + } + else + { + // We do not have any materials, add default. + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + FString FullMaterialName = MaterialInterface->GetPathName(); + UniqueName = FHoudiniEngineUtils::ExtractRawName( FullMaterialName ); + UniqueMaterialList.Add( UniqueName ); + } + + for ( int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx ) + { + int32 FaceMaterialIdx = FaceMaterialIndices[ FaceIdx ]; + check( UniqueMaterialList.IsValidIndex(FaceMaterialIdx) ); + + OutStaticMeshFaceMaterials.Add( UniqueMaterialList[ FaceMaterialIdx ] ); + } +} + +void +FHoudiniEngineUtils::DeleteFaceMaterialArray( TArray< char * > & OutStaticMeshFaceMaterials ) +{ + TSet< char * > UniqueMaterials( OutStaticMeshFaceMaterials ); + for ( TSet< char * >::TIterator Iter = UniqueMaterials.CreateIterator(); Iter; ++Iter ) + { + char* MaterialName = *Iter; + FMemory::Free( MaterialName ); + } + + OutStaticMeshFaceMaterials.Empty(); +} + +#endif // WITH_EDITOR + +void +FHoudiniEngineUtils::ExtractStringPositions( const FString & Positions, TArray< FVector > & OutPositions ) +{ + TArray< FString > PointStrings; + + static const TCHAR * PositionSeparators[] = + { + TEXT( " " ), + TEXT( "," ), + }; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION; + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + + if ( HoudiniRuntimeSettings ) + { + GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + } + + int32 NumCoords = Positions.ParseIntoArray( PointStrings, PositionSeparators, 2 ); + for ( int32 CoordIdx = 0; CoordIdx < NumCoords; CoordIdx += 3 ) + { + FVector Position; + + Position.X = FCString::Atof( *PointStrings[ CoordIdx + 0 ] ); + Position.Y = FCString::Atof( *PointStrings[ CoordIdx + 1 ] ); + Position.Z = FCString::Atof( *PointStrings[ CoordIdx + 2 ] ); + + Position *= GeneratedGeometryScaleFactor; + + if ( ImportAxis == HRSAI_Unreal ) + { + Swap( Position.Y, Position.Z ); + } + else if ( ImportAxis == HRSAI_Houdini ) + { + // No action required. + } + else + { + // Not valid enum value. + check( 0 ); + } + + OutPositions.Add( Position ); + } +} + +void +FHoudiniEngineUtils::CreatePositionsString( const TArray< FVector > & Positions, FString & PositionString ) +{ + PositionString = TEXT( "" ); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION; + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + + if ( HoudiniRuntimeSettings ) + { + GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + } + + for ( int32 Idx = 0; Idx < Positions.Num(); ++Idx ) + { + FVector Position = Positions[ Idx ]; + + if ( ImportAxis == HRSAI_Unreal ) + { + Swap( Position.Z, Position.Y ); + } + else if ( ImportAxis == HRSAI_Houdini ) + { + // No action required. + } + else + { + // Not valid enum value. + check( 0 ); + } + + if ( GeneratedGeometryScaleFactor != 0.0f ) + Position /= GeneratedGeometryScaleFactor; + + PositionString += FString::Printf( TEXT( "%f, %f, %f " ), Position.X, Position.Y, Position.Z ); + } +} + +void +FHoudiniEngineUtils::ConvertScaleAndFlipVectorData( const TArray< float > & DataRaw, TArray< FVector > & DataOut ) +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION; + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + + if ( HoudiniRuntimeSettings ) + { + GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + } + + for ( int32 Idx = 0; Idx < DataRaw.Num(); Idx += 3 ) + { + FVector Point( DataRaw[ Idx + 0 ], DataRaw[ Idx + 1 ], DataRaw[ Idx + 2 ] ); + + Point *= GeneratedGeometryScaleFactor; + + if ( ImportAxis == HRSAI_Unreal ) + { + Swap(Point.Z, Point.Y); + } + else if ( ImportAxis == HRSAI_Houdini ) + { + // No action required. + } + else + { + // Not valid enum value. + check( 0 ); + } + + DataOut.Add( Point ); + } +} + +FString +FHoudiniEngineUtils::HoudiniGetLibHAPIName() +{ + static const FString LibHAPIName = + +#if PLATFORM_WINDOWS + + HAPI_LIB_OBJECT_WINDOWS; + +#elif PLATFORM_MAC + + HAPI_LIB_OBJECT_MAC; + +#elif PLATFORM_LINUX + + HAPI_LIB_OBJECT_LINUX; + +#else + + TEXT( "" ); + +#endif + + return LibHAPIName; +} + +#if PLATFORM_WINDOWS + +void * +FHoudiniEngineUtils::LocateLibHAPIInRegistry( + const FString & HoudiniInstallationType, + FString & StoredLibHAPILocation, + bool LookIn32bitRegistry) +{ + auto FindDll = [&](const FString& InHoudiniInstallationPath) + { + FString HFSPath = FString::Printf(TEXT("%s/%s"), *InHoudiniInstallationPath, HAPI_HFS_SUBFOLDER_WINDOWS); + + // Create full path to libHAPI binary. + FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, HAPI_LIB_OBJECT_WINDOWS); + + if (FPaths::FileExists(LibHAPIPath)) + { + FPlatformProcess::PushDllDirectory(*HFSPath); + void* HAPILibraryHandle = FPlatformProcess::GetDllHandle(HAPI_LIB_OBJECT_WINDOWS); + FPlatformProcess::PopDllDirectory(*HFSPath); + + if (HAPILibraryHandle) + { + HOUDINI_LOG_MESSAGE( + TEXT("Loaded %s from Registry path %s"), HAPI_LIB_OBJECT_WINDOWS, + *HFSPath); + + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + return (void*)0; + }; + FString HoudiniInstallationPath; + FString HoudiniVersionString = ComputeVersionString(true); + FString RegistryKey = FString::Printf( + TEXT("Software\\%sSide Effects Software\\%s"), + (LookIn32bitRegistry ? TEXT("WOW6432Node\\") : TEXT("")), *HoudiniInstallationType); + + if (FWindowsPlatformMisc::QueryRegKey( + HKEY_LOCAL_MACHINE, *RegistryKey, *HoudiniVersionString, HoudiniInstallationPath)) + { + FPaths::NormalizeDirectoryName(HoudiniInstallationPath); + return FindDll(HoudiniInstallationPath); + } + + return nullptr; +} + +#endif + +FString +FHoudiniEngineUtils::ComputeVersionString(bool ExtraDigit) +{ + // Compute Houdini version string. + FString HoudiniVersionString = FString::Printf( + TEXT("%d.%d.%s%d"), HAPI_VERSION_HOUDINI_MAJOR, + HAPI_VERSION_HOUDINI_MINOR, + (ExtraDigit ? (TEXT("0.")) : TEXT("")), + HAPI_VERSION_HOUDINI_BUILD); + + // If we have a patch version, we need to append it. + if (HAPI_VERSION_HOUDINI_PATCH > 0) + HoudiniVersionString = FString::Printf(TEXT("%s.%d"), *HoudiniVersionString, HAPI_VERSION_HOUDINI_PATCH); + return HoudiniVersionString; +} + +void * +FHoudiniEngineUtils::LoadLibHAPI( FString & StoredLibHAPILocation ) +{ + FString HFSPath = TEXT( "" ); + void * HAPILibraryHandle = nullptr; + + // Look up HAPI_PATH environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . + FString HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable( TEXT( "HAPI_PATH" ) ); + if (!HFS_ENV_VAR.IsEmpty()) + HFSPath = HFS_ENV_VAR; + + // Look up environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . + HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable( TEXT( "HFS" )); + if (!HFS_ENV_VAR.IsEmpty()) + HFSPath = HFS_ENV_VAR; + + // Get platform specific name of libHAPI. + FString LibHAPIName = FHoudiniEngineUtils::HoudiniGetLibHAPIName(); + + // If we have a custom location specified through settings, attempt to use that. + bool bCustomPathFound = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if ( HoudiniRuntimeSettings && HoudiniRuntimeSettings->bUseCustomHoudiniLocation ) + { + // Create full path to libHAPI binary. + FString CustomHoudiniLocationPath = HoudiniRuntimeSettings->CustomHoudiniLocation.Path; + if ( !CustomHoudiniLocationPath.IsEmpty() ) + { + // Convert path to absolute if it is relative. + if ( FPaths::IsRelative( CustomHoudiniLocationPath ) ) + CustomHoudiniLocationPath = FPaths::ConvertRelativePathToFull( CustomHoudiniLocationPath ); + + FString LibHAPICustomPath = FString::Printf( TEXT( "%s/%s" ), *CustomHoudiniLocationPath, *LibHAPIName ); + + if ( FPaths::FileExists( LibHAPICustomPath ) ) + { + HFSPath = CustomHoudiniLocationPath; + bCustomPathFound = true; + } + } + } + + // We have HFS environment variable defined (or custom location), attempt to load libHAPI from it. + if ( !HFSPath.IsEmpty() ) + { + if ( !bCustomPathFound ) + { + +#if PLATFORM_WINDOWS + + HFSPath += FString::Printf( TEXT( "/%s" ), HAPI_HFS_SUBFOLDER_WINDOWS ); + +#elif PLATFORM_MAC + + HFSPath += FString::Printf( TEXT( "/%s" ), HAPI_HFS_SUBFOLDER_MAC ); + +#elif PLATFORM_LINUX + + HFSPath += FString::Printf( TEXT( "/%s" ), HAPI_HFS_SUBFOLDER_LINUX ); + +#endif + } + + // Create full path to libHAPI binary. + FString LibHAPIPath = FString::Printf( TEXT( "%s/%s" ), *HFSPath, *LibHAPIName ); + + if ( FPaths::FileExists( LibHAPIPath ) ) + { + // libHAPI binary exists at specified location, attempt to load it. + FPlatformProcess::PushDllDirectory( *HFSPath ); + +#if PLATFORM_WINDOWS + + HAPILibraryHandle = FPlatformProcess::GetDllHandle( *LibHAPIName ); + +#elif PLATFORM_MAC || PLATFORM_LINUX + + HAPILibraryHandle = FPlatformProcess::GetDllHandle( *LibHAPIPath ); + +#endif + + FPlatformProcess::PopDllDirectory( *HFSPath ); + + // If library has been loaded successfully we can stop. + if ( HAPILibraryHandle ) + { + if ( bCustomPathFound ) + HOUDINI_LOG_MESSAGE( TEXT( "Loaded %s from custom path %s" ), *LibHAPIName, *HFSPath ); + else + HOUDINI_LOG_MESSAGE( TEXT( "Loaded %s from HFS environment path %s" ), *LibHAPIName, *HFSPath ); + + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + } + + // Otherwise, we will attempt to detect Houdini installation. + FString HoudiniLocation = TEXT( HOUDINI_ENGINE_HFS_PATH ); + FString LibHAPIPath; + + // Compute Houdini version string. + FString HoudiniVersionString = ComputeVersionString(false); + +#if PLATFORM_WINDOWS + + // On Windows, we have also hardcoded HFS path in plugin configuration file; attempt to load from it. + HFSPath = FString::Printf( TEXT( "%s/%s" ), *HoudiniLocation, HAPI_HFS_SUBFOLDER_WINDOWS ); + + // Create full path to libHAPI binary. + LibHAPIPath = FString::Printf( TEXT( "%s/%s" ), *HFSPath, *LibHAPIName ); + + if ( FPaths::FileExists( LibHAPIPath ) ) + { + FPlatformProcess::PushDllDirectory( *HFSPath ); + HAPILibraryHandle = FPlatformProcess::GetDllHandle( *LibHAPIName ); + FPlatformProcess::PopDllDirectory( *HFSPath ); + + if ( HAPILibraryHandle ) + { + HOUDINI_LOG_MESSAGE( TEXT( "Loaded %s from Plugin defined HFS path %s" ), *LibHAPIName, *HFSPath ); + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + + // As a second attempt, on Windows, we try to look up location of Houdini Engine in the registry. + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini Engine"), StoredLibHAPILocation, false); + if ( HAPILibraryHandle ) + return HAPILibraryHandle; + + // As a third attempt, we try to look up location of Houdini installation (not Houdini Engine) in the registry. + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini"), StoredLibHAPILocation, false); + if ( HAPILibraryHandle ) + return HAPILibraryHandle; + + // Do similar registry lookups for the 32 bits registry + // Look for the Houdini Engine registry install path + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini Engine"), StoredLibHAPILocation, true); + if ( HAPILibraryHandle ) + return HAPILibraryHandle; + + // ... and for the Houdini registry install path + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini"), StoredLibHAPILocation, true); + if ( HAPILibraryHandle ) + return HAPILibraryHandle; + + // Finally, try to load from a hardcoded program files path. + HoudiniLocation = FString::Printf( + TEXT( "C:\\Program Files\\Side Effects Software\\Houdini %s\\%s" ), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_WINDOWS ); + +#else + +# if PLATFORM_MAC + + // Attempt to load from standard Mac OS X installation. + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/Current/Libraries"), *HoudiniVersionString ); + + // Fallback in case the previous one doesnt exist + if ( !FPaths::DirectoryExists( HoudiniLocation ) ) + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/%s/Libraries"), *HoudiniVersionString, *HoudiniVersionString); + + // Fallback in case we're using the steam version + if (!FPaths::DirectoryExists(HoudiniLocation)) + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); + + // Fallback in case we're using the steam version + if ( !FPaths::DirectoryExists( HoudiniLocation ) ) + HoudiniLocation = FString::Printf( + TEXT("/Users/Shared/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); + +# elif PLATFORM_LINUX + + // Attempt to load from standard Linux installation. + HoudiniLocation = FString::Printf( + TEXT( "/opt/hfs%s/%s" ), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_LINUX ); + +# endif + +#endif + + // Create full path to libHAPI binary. + LibHAPIPath = FString::Printf( TEXT( "%s/%s" ), *HoudiniLocation, *LibHAPIName ); + + if ( FPaths::FileExists( LibHAPIPath ) ) + { + FPlatformProcess::PushDllDirectory( *HoudiniLocation ); + HAPILibraryHandle = FPlatformProcess::GetDllHandle( *LibHAPIPath ); + FPlatformProcess::PopDllDirectory( *HoudiniLocation ); + + if ( HAPILibraryHandle ) + { + HOUDINI_LOG_MESSAGE( TEXT( "Loaded %s from expected installation %s" ), *LibHAPIName, *HoudiniLocation ); + StoredLibHAPILocation = HoudiniLocation; + return HAPILibraryHandle; + } + } + + StoredLibHAPILocation = TEXT( "" ); + return HAPILibraryHandle; +} + +int32 +FHoudiniEngineUtils::HapiGetVertexListForGroup( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, const FString & GroupName, + const TArray< int32 > & FullVertexList, TArray< int32 > & NewVertexList, + TArray< int32 > & AllVertexList, TArray< int32 > & AllFaceList, + TArray< int32 > & AllCollisionFaceIndices, const bool& isPackedPrim ) +{ + NewVertexList.Init( -1, FullVertexList.Num() ); + int32 ProcessedWedges = 0; + + AllFaceList.Empty(); + + TArray< int32 > PartGroupMembership; + FHoudiniEngineUtils::HapiGetGroupMembership( + AssetId, ObjectId, GeoId, PartId, HAPI_GROUPTYPE_PRIM, GroupName, PartGroupMembership ); + + // Go through all primitives. + for ( int32 FaceIdx = 0; FaceIdx < PartGroupMembership.Num(); ++FaceIdx ) + { + if ( PartGroupMembership[ FaceIdx ] > 0 ) + { + // Add face. + AllFaceList.Add( FaceIdx ); + + // This face is a member of specified group. + if (FullVertexList.IsValidIndex(FaceIdx * 3 + 2 ) ) + { + NewVertexList[ FaceIdx * 3 + 0 ] = FullVertexList[ FaceIdx * 3 + 0 ]; + NewVertexList[ FaceIdx * 3 + 1 ] = FullVertexList[ FaceIdx * 3 + 1 ]; + NewVertexList[ FaceIdx * 3 + 2 ] = FullVertexList[ FaceIdx * 3 + 2 ]; + } + + // Mark these vertex indices as used. + if ( AllVertexList.IsValidIndex( FaceIdx * 3 + 2 ) ) + { + AllVertexList[ FaceIdx * 3 + 0 ] = 1; + AllVertexList[ FaceIdx * 3 + 1 ] = 1; + AllVertexList[ FaceIdx * 3 + 2 ] = 1; + } + + // Mark this face as used. + if ( AllCollisionFaceIndices.IsValidIndex( FaceIdx ) ) + AllCollisionFaceIndices[ FaceIdx ] = 1; + + ProcessedWedges += 3; + } + } + + return ProcessedWedges; +} + + +#if WITH_EDITOR + +bool +FHoudiniEngineUtils::ContainsInvalidLightmapFaces( const FRawMesh & RawMesh, int32 LightmapSourceIdx ) +{ + const TArray< FVector2D > & LightmapUVs = RawMesh.WedgeTexCoords[ LightmapSourceIdx ]; + const TArray< uint32 > & Indices = RawMesh.WedgeIndices; + + if ( LightmapUVs.Num() != Indices.Num() ) + { + // This is invalid raw mesh; by design we consider that it contains invalid lightmap faces. + return true; + } + + for ( int32 Idx = 0; Idx < Indices.Num(); Idx += 3 ) + { + const FVector2D & uv0 = LightmapUVs[ Idx + 0 ]; + const FVector2D & uv1 = LightmapUVs[ Idx + 1 ]; + const FVector2D & uv2 = LightmapUVs[ Idx + 2 ]; + + if ( uv0 == uv1 && uv1 == uv2 ) + { + // Detect invalid lightmap face, can stop. + return true; + } + } + + // Otherwise there are no invalid lightmap faces. + return false; +} + +#endif + +int32 +FHoudiniEngineUtils::CountUVSets( const FRawMesh & RawMesh ) +{ + int32 UVSetCount = 0; + +#if WITH_EDITOR + + for ( int32 TexCoordIdx = 0; TexCoordIdx < MAX_MESH_TEXTURE_COORDS; ++TexCoordIdx ) + { + const TArray< FVector2D > & WedgeTexCoords = RawMesh.WedgeTexCoords[ TexCoordIdx ]; + if ( WedgeTexCoords.Num() > 0 ) + UVSetCount++; + } + +#endif // WITH_EDITOR + + return UVSetCount; +} + +const FString +FHoudiniEngineUtils::GetStatusString( HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity ) +{ + int32 StatusBufferLength = 0; + FHoudiniApi::GetStatusStringBufLength( FHoudiniEngine::Get().GetSession(), status_type, verbosity, &StatusBufferLength ); + + if ( StatusBufferLength > 0 ) + { + TArray< char > StatusStringBuffer; + StatusStringBuffer.SetNumZeroed( StatusBufferLength ); + FHoudiniApi::GetStatusString( FHoudiniEngine::Get().GetSession(), status_type, &StatusStringBuffer[ 0 ], StatusBufferLength ); + + return FString( UTF8_TO_TCHAR( &StatusStringBuffer[ 0 ] ) ); + } + + return FString( TEXT( "" )); +} + +bool +FHoudiniEngineUtils::ExtractUniqueMaterialIds( + const HAPI_AssetInfo & AssetInfo, + TSet< HAPI_NodeId > & MaterialIds, + TSet< HAPI_NodeId > & InstancerMaterialIds, + TMap< FHoudiniGeoPartObject, HAPI_NodeId > & InstancerMaterialMap ) +{ + // Empty passed incontainers. + MaterialIds.Empty(); + InstancerMaterialIds.Empty(); + InstancerMaterialMap.Empty(); + + TArray< HAPI_ObjectInfo > ObjectInfos; + if ( !HapiGetObjectInfos( AssetInfo.nodeId, ObjectInfos ) ) + return false; + + // Iterate through all objects. + for ( int32 ObjectIdx = 0; ObjectIdx < ObjectInfos.Num(); ++ObjectIdx ) + { + // Retrieve object at this index. + const HAPI_ObjectInfo & ObjectInfo = ObjectInfos[ ObjectIdx ]; + + // Get Geo information. + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( FHoudiniEngine::Get().GetSession(), ObjectInfo.nodeId, &GeoInfo ) ) + continue; + + // Iterate through all parts. + for ( int32 PartIdx = 0; PartIdx < GeoInfo.partCount; ++PartIdx ) + { + // Get part information. + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + FString PartName = TEXT( "" ); + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId, PartIdx, &PartInfo ) ) + continue; + + // Retrieve material information for this geo part. + HAPI_Bool bSingleFaceMaterial = false; + bool bMaterialsFound = false; + + if ( PartInfo.faceCount > 0 ) + { + TArray< HAPI_NodeId > FaceMaterialIds; + FaceMaterialIds.SetNumUninitialized( PartInfo.faceCount ); + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialNodeIdsOnFaces( + FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId, PartInfo.id, + &bSingleFaceMaterial, &FaceMaterialIds[ 0 ], 0, PartInfo.faceCount ) ) + { + continue; + } + + MaterialIds.Append( FaceMaterialIds ); + } + else + { + // If this is an instancer, attempt to look up instancer material. + if ( ObjectInfo.isInstancer ) + { + HAPI_NodeId InstanceMaterialId = -1; + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialNodeIdsOnFaces( + FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId, PartInfo.id, + &bSingleFaceMaterial, &InstanceMaterialId, 0, 1 ) ) + { + continue; + } + + MaterialIds.Add( InstanceMaterialId ); + + if ( InstanceMaterialId != -1 ) + { + FHoudiniGeoPartObject GeoPartObject( AssetInfo.nodeId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id ); + InstancerMaterialMap.Add( GeoPartObject, InstanceMaterialId ); + + InstancerMaterialIds.Add( InstanceMaterialId ); + } + } + } + } + } + + MaterialIds.Remove( -1 ); + + return true; +} + +AHoudiniAssetActor * +FHoudiniEngineUtils::LocateClipboardActor( const AActor* IgnoreActor, const FString & ClipboardText ) +{ + const TCHAR * Paste = nullptr; + + if ( ClipboardText.IsEmpty() ) + { + FString PasteString; +#if WITH_EDITOR + FPlatformApplicationMisc::ClipboardPaste( PasteString ); +#endif + Paste = *PasteString; + } + else + { + Paste = *ClipboardText; + } + + AHoudiniAssetActor * HoudiniAssetActor = nullptr; + + // Extract the Name/Label of the from the clipboard string ... + FString ActorName = TEXT( "" ); + FString StrLine; + while ( FParse::Line( &Paste, StrLine ) ) + { + StrLine = StrLine.TrimStart(); + + const TCHAR * Str = *StrLine; + FString ClassName; + if (StrLine.StartsWith(TEXT("Begin Actor")) && FParse::Value(Str, TEXT("Class="), ClassName)) + { + if (ClassName == *AHoudiniAssetActor::StaticClass()->GetFName().ToString()) + { + if (FParse::Value(Str, TEXT("Name="), ActorName)) + break; + } + } + else if (StrLine.StartsWith(TEXT("ActorLabel"))) + { + if (FParse::Value(Str, TEXT("ActorLabel="), ActorName)) + break; + } + } + +#if WITH_EDITOR + if (GEditor) + { + // Try to find the corresponding HoudiniAssetActor in the editor world + // to avoid finding "deleted" assets with the same name + UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); + for (TActorIterator ActorItr(editorWorld); ActorItr; ++ActorItr) + { + if (*ActorItr != IgnoreActor && (ActorItr->GetActorLabel() == ActorName || ActorItr->GetName() == ActorName)) + HoudiniAssetActor = *ActorItr; + } + } +#endif + + return HoudiniAssetActor; +} + +bool +FHoudiniEngineUtils::GetAssetNames( + UHoudiniAsset * HoudiniAsset, HAPI_AssetLibraryId & OutAssetLibraryId, + TArray< HAPI_StringHandle > & OutAssetNames ) +{ + OutAssetLibraryId = -1; + OutAssetNames.Empty(); + + if ( FHoudiniEngineUtils::IsInitialized() && HoudiniAsset && !HoudiniAsset->IsPendingKill() ) + { + FString AssetFileName = HoudiniAsset->GetAssetFileName(); + HAPI_Result Result = HAPI_RESULT_FAILURE; + HAPI_AssetLibraryId AssetLibraryId = -1; + int32 AssetCount = 0; + TArray< HAPI_StringHandle > AssetNames; + + if ( FPaths::IsRelative( AssetFileName ) && ( FHoudiniEngine::Get().GetSession()->type != HAPI_SESSION_INPROCESS ) ) + AssetFileName = FPaths::ConvertRelativePathToFull( AssetFileName ); + + if ( !AssetFileName.IsEmpty() && FPaths::FileExists( AssetFileName ) ) + { + // We'll need to modify the file name for expanded .hda + FString FileExtension = FPaths::GetExtension( AssetFileName ); + if ( FileExtension.Compare( TEXT( "hdalibrary" ), ESearchCase::IgnoreCase ) == 0 ) + { + // the .hda directory is what we're interested in loading + AssetFileName = FPaths::GetPath( AssetFileName ); + } + + // File does exist, we can load asset from file. + std::string AssetFileNamePlain; + FHoudiniEngineUtils::ConvertUnrealString( AssetFileName, AssetFileNamePlain ); + + Result = FHoudiniApi::LoadAssetLibraryFromFile( + FHoudiniEngine::Get().GetSession(), AssetFileNamePlain.c_str(), true, &AssetLibraryId ); + } + + // Try to load the asset from memory if loading from file failed + if ( Result != HAPI_RESULT_SUCCESS ) + { + // Expanded hdas cannot be loaded from Memory + FString FileExtension = FPaths::GetExtension( AssetFileName ); + if ( FileExtension.Compare( TEXT( "hdalibrary" ), ESearchCase::IgnoreCase ) == 0 ) + { + HOUDINI_LOG_ERROR( TEXT( "Error loading expanded Asset %s: source asset file not found." ), *AssetFileName ); + return false; + } + else + { + // Warn the user that we are loading from memory + HOUDINI_LOG_WARNING( TEXT( "Asset %s, loading from Memory: source asset file not found."), *AssetFileName ); + + // Otherwise we will try to load from buffer we've cached. + Result = FHoudiniApi::LoadAssetLibraryFromMemory( + FHoudiniEngine::Get().GetSession(), + reinterpret_cast( HoudiniAsset->GetAssetBytes() ), + HoudiniAsset->GetAssetBytesCount(), true, &AssetLibraryId ); + } + } + + if ( Result != HAPI_RESULT_SUCCESS ) + { + HOUDINI_LOG_MESSAGE( TEXT( "Error loading asset library for %s: %s" ), *AssetFileName, *FHoudiniEngineUtils::GetErrorDescription() ); + return false; + } + + Result = FHoudiniApi::GetAvailableAssetCount( FHoudiniEngine::Get().GetSession(), AssetLibraryId, &AssetCount ); + if ( Result != HAPI_RESULT_SUCCESS ) + { + HOUDINI_LOG_MESSAGE( TEXT( "Error getting asset count for %s: %s" ), *AssetFileName, *FHoudiniEngineUtils::GetErrorDescription() ); + return false; + } + + if( AssetCount <= 0 ) + { + HOUDINI_LOG_MESSAGE( TEXT( "Could not find an asset in library %s" ), *AssetFileName ); + return false; + } + + AssetNames.SetNumUninitialized( AssetCount ); + + Result = FHoudiniApi::GetAvailableAssets( FHoudiniEngine::Get().GetSession(), AssetLibraryId, &AssetNames[ 0 ], AssetCount ); + if ( Result != HAPI_RESULT_SUCCESS ) + { + HOUDINI_LOG_MESSAGE( TEXT( "Unable to retrieve asset names for %s: %s" ), *AssetFileName, *FHoudiniEngineUtils::GetErrorDescription() ); + return false; + } + + if ( !AssetCount ) + { + HOUDINI_LOG_MESSAGE( TEXT( "No assets found within %s" ), *AssetFileName ); + return false; + } + + OutAssetLibraryId = AssetLibraryId; + OutAssetNames = AssetNames; + + return true; + } + + return false; +} + + +int32 +FHoudiniEngineUtils::AddMeshSocketToList( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, + HAPI_NodeId GeoId, HAPI_PartId PartId, + TArray< FTransform >& AllSockets, + TArray< FString >& AllSocketsNames, + TArray< FString >& AllSocketsActors, + TArray< FString >& AllSocketsTags, + const bool& isPackedPrim ) +{ + // Attributes we are interested in. + // Position + TArray< float > Positions; + HAPI_AttributeInfo AttribInfoPositions; + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + //FMemory::Memset< HAPI_AttributeInfo >( AttribInfoPositions, 0 ); + + // Rotation + bool bHasRotation = false; + TArray< float > Rotations; + HAPI_AttributeInfo AttribInfoRotations; + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + // FMemory::Memset< HAPI_AttributeInfo >( AttribInfoRotations, 0 ); + + // Scale + bool bHasScale = false; + TArray< float > Scales; + HAPI_AttributeInfo AttribInfoScales; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + // FMemory::Memset< HAPI_AttributeInfo >( AttribInfoScales, 0 ); + + // When using socket groups, we can also get the sockets rotation from the normal + bool bHasNormals = false; + TArray< float > Normals; + HAPI_AttributeInfo AttribInfoNormals; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + // FMemory::Memset< HAPI_AttributeInfo >( AttribInfoNormals, 0 ); + + // Socket Name + bool bHasNames = false; + TArray< FString > Names; + HAPI_AttributeInfo AttribInfoNames; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + // FMemory::Memset< HAPI_AttributeInfo >( AttribInfoNames, 0 ); + + // Socket Actor + bool bHasActors = false; + TArray< FString > Actors; + HAPI_AttributeInfo AttribInfoActors; + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + // FMemory::Memset< HAPI_AttributeInfo >( AttribInfoActors, 0 ); + + // Socket Tags + bool bHasTags = false; + TArray< FString > Tags; + HAPI_AttributeInfo AttribInfoTags; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + // FMemory::Memset< HAPI_AttributeInfo >( AttribInfoTags, 0 ); + + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + check( HoudiniRuntimeSettings ); + float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION; + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + if ( HoudiniRuntimeSettings ) + { + GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + } + + // Lambda function for creating the socket and adding it to the array + // Shared between the by Attribute / by Group methods + int32 FoundSocketCount = 0; + auto AddSocketToArray = [&]( const int32& PointIdx ) + { + FTransform currentSocketTransform; + FVector currentPosition = FVector::ZeroVector; + FVector currentScale = FVector( 1.0f, 1.0f, 1.0f ); + FQuat currentRotation = FQuat::Identity; + + if ( ImportAxis == HRSAI_Unreal ) + { + if ( Positions.IsValidIndex( PointIdx * 3 + 2 ) ) + { + currentPosition.X = Positions[ PointIdx * 3 ] * GeneratedGeometryScaleFactor; + currentPosition.Y = Positions[ PointIdx * 3 + 2 ] * GeneratedGeometryScaleFactor; + currentPosition.Z = Positions[ PointIdx * 3 + 1 ] * GeneratedGeometryScaleFactor; + } + + if ( bHasScale && Scales.IsValidIndex( PointIdx * 3 + 2 ) ) + { + currentScale.X = Scales[ PointIdx * 3 ]; + currentScale.Y = Scales[ PointIdx * 3 + 2 ]; + currentScale.Z = Scales[ PointIdx * 3 + 1 ]; + } + + if ( bHasRotation && Rotations.IsValidIndex( PointIdx * 4 + 3 ) ) + { + currentRotation.X = Rotations[ PointIdx * 4 ]; + currentRotation.Y = Rotations[ PointIdx * 4 + 2 ]; + currentRotation.Z = Rotations[ PointIdx * 4 + 1 ]; + currentRotation.W = -Rotations[ PointIdx * 4 + 3 ]; + } + else if ( bHasNormals && Normals.IsValidIndex( PointIdx * 3 + 2 ) ) + { + FVector vNormal; + vNormal.X = Normals[ PointIdx * 3 ]; + vNormal.Y = Normals[ PointIdx * 3 + 2 ]; + vNormal.Z = Normals[ PointIdx * 3 + 1 ]; + + if ( vNormal != FVector::ZeroVector ) + currentRotation = FQuat::FindBetween( FVector::UpVector, vNormal ); + } + } + else + { + if ( Positions.IsValidIndex( PointIdx * 3 + 2 ) ) + { + currentPosition.X = Positions[ PointIdx * 3 ] * GeneratedGeometryScaleFactor; + currentPosition.Y = Positions[ PointIdx * 3 + 1 ] * GeneratedGeometryScaleFactor; + currentPosition.Z = Positions[ PointIdx * 3 + 2 ] * GeneratedGeometryScaleFactor; + } + + if ( bHasScale && Scales.IsValidIndex( PointIdx * 3 + 2 ) ) + { + currentScale.X = Scales[ PointIdx * 3 ]; + currentScale.Y = Scales[ PointIdx * 3 + 1 ]; + currentScale.Z = Scales[ PointIdx * 3 + 2 ]; + } + + if ( bHasRotation && Rotations.IsValidIndex( PointIdx * 4 + 3 ) ) + { + currentRotation.X = Rotations[ PointIdx * 4 ]; + currentRotation.Y = Rotations[ PointIdx * 4 + 1 ]; + currentRotation.Z = Rotations[ PointIdx * 4 + 2 ]; + currentRotation.W = Rotations[ PointIdx * 4 + 3 ]; + } + else if ( bHasNormals && Normals.IsValidIndex( PointIdx * 3 + 2 ) ) + { + FVector vNormal; + vNormal.X = Normals[ PointIdx * 3 ]; + vNormal.Y = Normals[ PointIdx * 3 + 1 ]; + vNormal.Z = Normals[ PointIdx * 3 + 2 ]; + + if ( vNormal != FVector::ZeroVector ) + currentRotation = FQuat::FindBetween( FVector::UpVector, vNormal ); + } + } + + FString currentName; + if ( bHasNames && Names.IsValidIndex( PointIdx ) ) + currentName = Names[ PointIdx ]; + + FString currentActors; + if ( bHasActors && Actors.IsValidIndex( PointIdx ) ) + currentActors = Actors[ PointIdx ]; + + FString currentTag; + if ( bHasTags && Tags.IsValidIndex( PointIdx ) ) + currentTag = Tags[ PointIdx ]; + + // If the scale attribute wasn't set on all socket, we might end up + // with a zero scale socket, avoid that. + if ( currentScale == FVector::ZeroVector ) + currentScale = FVector( 1.0f, 1.0f, 1.0f ); + + currentSocketTransform.SetLocation( currentPosition ); + currentSocketTransform.SetRotation( currentRotation ); + currentSocketTransform.SetScale3D( currentScale ); + + // We want to make sure we're not adding the same socket multiple times + int32 FoundIx = AllSockets.IndexOfByPredicate( + [ currentSocketTransform ]( const FTransform InTransform ){ return InTransform.Equals( currentSocketTransform); } ); + + if ( FoundIx >= 0 ) + { + // If the transform, names and actors are identical, skip this duplicate + if ( ( AllSocketsNames[ FoundIx ] == currentName ) && ( AllSocketsActors[ FoundIx ] == currentActors ) ) + return false; + } + + AllSockets.Add( currentSocketTransform ); + AllSocketsNames.Add( currentName ); + AllSocketsActors.Add( currentActors ); + AllSocketsTags.Add( currentTag ); + + FoundSocketCount++; + + return true; + }; + + + // Lambda function for reseting the arrays/attributes + auto ResetArraysAndAttr = [&]() + { + // Position + Positions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + //FMemory::Memset< HAPI_AttributeInfo >( AttribInfoPositions, 0 ); + + // Rotation + bHasRotation = false; + Rotations.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + //FMemory::Memset< HAPI_AttributeInfo >( AttribInfoRotations, 0 ); + + // Scale + bHasScale = false; + Scales.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + //FMemory::Memset< HAPI_AttributeInfo >( AttribInfoScales, 0 ); + + // When using socket groups, we can also get the sockets rotation from the normal + bHasNormals = false; + Normals.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + //FMemory::Memset< HAPI_AttributeInfo >( AttribInfoNormals, 0 ); + + // Socket Name + bHasNames = false; + Names.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + //FMemory::Memset< HAPI_AttributeInfo >( AttribInfoNames, 0 ); + + // Socket Actor + bHasActors = false; + Actors.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + //FMemory::Memset< HAPI_AttributeInfo >( AttribInfoActors, 0 ); + + // Socket Tags + bHasTags = false; + Tags.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + //FMemory::Memset< HAPI_AttributeInfo >( AttribInfoTags, 0 ); + }; + + //------------------------------------------------------------------------- + // FIND SOCKETS BY DETAIL ATTRIBUTES + //------------------------------------------------------------------------- + + bool HasSocketAttributes = true; + int32 SocketIdx = 0; + while ( HasSocketAttributes ) + { + // Build the current socket's prefix + FString SocketAttrPrefix = TEXT( HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX ) + FString::FromInt( SocketIdx ); + + // Reset the arrays and attributes + ResetArraysAndAttr(); + + // Retrieve position data. + FString SocketPosAttr = SocketAttrPrefix + TEXT("_pos"); + if ( !FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectId, GeoId, PartId, + TCHAR_TO_ANSI(*SocketPosAttr), AttribInfoPositions, Positions, 0, HAPI_ATTROWNER_DETAIL ) ) + break; + + if ( !AttribInfoPositions.exists ) + { + // No need to keep looking for socket attributes + HasSocketAttributes = false; + break; + } + + // Retrieve rotation data. + FString SocketRotAttr = SocketAttrPrefix + TEXT("_rot"); + if ( FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectId, GeoId, PartId, + TCHAR_TO_ANSI(*SocketRotAttr), AttribInfoRotations, Rotations, 0, HAPI_ATTROWNER_DETAIL ) ) + bHasRotation = true; + + // Retrieve scale data. + FString SocketScaleAttr = SocketAttrPrefix + TEXT("_scale"); + if ( FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectId, GeoId, PartId, + TCHAR_TO_ANSI(*SocketScaleAttr), AttribInfoScales, Scales, 0, HAPI_ATTROWNER_DETAIL ) ) + bHasScale = true; + + // Retrieve mesh socket names. + FString SocketNameAttr = SocketAttrPrefix + TEXT("_name"); + if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectId, GeoId, PartId, + TCHAR_TO_ANSI(*SocketNameAttr), AttribInfoNames, Names ) ) + bHasNames = true; + + // Retrieve mesh socket actor. + FString SocketActorAttr = SocketAttrPrefix + TEXT("_actor"); + if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectId, GeoId, PartId, + TCHAR_TO_ANSI(*SocketActorAttr), AttribInfoActors, Actors ) ) + bHasActors = true; + + // Retrieve mesh socket tags. + FString SocketTagAttr = SocketAttrPrefix + TEXT("_tag"); + if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectId, GeoId, PartId, + TCHAR_TO_ANSI(*SocketTagAttr), AttribInfoTags, Tags ) ) + bHasTags = true; + + // Add the socket to the array + AddSocketToArray( 0 ); + + // Try to find the next socket + SocketIdx++; + } + + //------------------------------------------------------------------------- + // FIND SOCKETS BY POINT GROUPS + //------------------------------------------------------------------------- + + // Get object / geo group memberships for primitives. + TArray< FString > GroupNames; + if ( !FHoudiniEngineUtils::HapiGetGroupNames( + AssetId, ObjectId, GeoId, PartId, HAPI_GROUPTYPE_POINT, GroupNames, isPackedPrim ) ) + { + HOUDINI_LOG_MESSAGE( TEXT( "GetMeshSocketList: Object [%d] non-fatal error reading group names" ), ObjectId ); + } + + // First, we want to make sure we have at least one socket group before continuing + bool bHasSocketGroup = false; + for ( int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx ) + { + const FString & GroupName = GroupNames[ GeoGroupNameIdx ]; + if ( GroupName.StartsWith( TEXT( HAPI_UNREAL_GROUP_MESH_SOCKETS ), ESearchCase::IgnoreCase ) + || GroupName.StartsWith( TEXT( HAPI_UNREAL_GROUP_MESH_SOCKETS_OLD ), ESearchCase::IgnoreCase )) + { + bHasSocketGroup = true; + break; + } + } + + if ( !bHasSocketGroup ) + return FoundSocketCount; + + // Reset the data arrays and attributes + ResetArraysAndAttr(); + + // Retrieve position data. + if ( !FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectId, GeoId, PartId, + HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, Positions ) ) + return false; + + // Retrieve rotation data. + if ( FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectId, GeoId, PartId, + HAPI_UNREAL_ATTRIB_ROTATION, AttribInfoRotations, Rotations ) ) + bHasRotation = true; + + // Retrieve normal data. + if ( FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectId, GeoId, PartId, + HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, Normals ) ) + bHasNormals = true; + + // Retrieve scale data. + if ( FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectId, GeoId, PartId, + HAPI_UNREAL_ATTRIB_SCALE, AttribInfoScales, Scales ) ) + bHasScale = true; + + // Retrieve mesh socket names. + if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectId, GeoId, PartId, + HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, AttribInfoNames, Names ) ) + bHasNames = true; + else if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectId, GeoId, PartId, + HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD, AttribInfoNames, Names ) ) + bHasNames = true; + + // Retrieve mesh socket actor. + if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectId, GeoId, PartId, + HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR, AttribInfoActors, Actors ) ) + bHasActors = true; + else if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectId, GeoId, PartId, + HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD, AttribInfoActors, Actors ) ) + bHasActors = true; + + // Retrieve mesh socket tags. + if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectId, GeoId, PartId, + HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, AttribInfoTags, Tags ) ) + bHasTags = true; + else if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectId, GeoId, PartId, + HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD, AttribInfoTags, Tags ) ) + bHasTags = true; + + // Extracting Sockets vertices + for ( int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx ) + { + const FString & GroupName = GroupNames[ GeoGroupNameIdx ]; + if ( !GroupName.StartsWith ( TEXT ( HAPI_UNREAL_GROUP_MESH_SOCKETS ) , ESearchCase::IgnoreCase ) + && !GroupName.StartsWith ( TEXT ( HAPI_UNREAL_GROUP_MESH_SOCKETS_OLD ) , ESearchCase::IgnoreCase ) ) + continue; + + TArray< int32 > PointGroupMembership; + FHoudiniEngineUtils::HapiGetGroupMembership( + AssetId, ObjectId, GeoId, PartId, + HAPI_GROUPTYPE_POINT, GroupName, PointGroupMembership ); + + // Go through all primitives. + for ( int32 PointIdx = 0; PointIdx < PointGroupMembership.Num(); ++PointIdx ) + { + if ( PointGroupMembership[ PointIdx ] == 0 ) + continue; + + // Add the corresponding socket to the array + AddSocketToArray( PointIdx ); + } + } + + return FoundSocketCount; +} + + +bool +FHoudiniEngineUtils::AddMeshSocketsToStaticMesh( + UStaticMesh* StaticMesh, + FHoudiniGeoPartObject& HoudiniGeoPartObject, + TArray< FTransform >& AllSockets, + TArray< FString >& AllSocketsNames, + TArray< FString >& AllSocketsActors, + TArray< FString >& AllSocketsTags ) +{ + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + return false; + + if ( AllSockets.Num() <= 0 ) + return false; + + // Remove the sockets from the previous cook! + if ( !HoudiniGeoPartObject.bHasSocketBeenAdded ) + StaticMesh->Sockets.Empty(); + + for ( int32 nSocket = 0; nSocket < AllSockets.Num(); nSocket++ ) + { + // Create a new Socket + UStaticMeshSocket* Socket = NewObject( StaticMesh ); + if ( !Socket || Socket->IsPendingKill() ) + continue; + + Socket->RelativeLocation = AllSockets[ nSocket ].GetLocation(); + Socket->RelativeRotation = FRotator(AllSockets[ nSocket ].GetRotation()); + Socket->RelativeScale = AllSockets[ nSocket ].GetScale3D(); + + if ( AllSocketsNames.IsValidIndex( nSocket ) && !AllSocketsNames[ nSocket ].IsEmpty() ) + { + Socket->SocketName = FName( *AllSocketsNames[ nSocket ] ); + } + else + { + // Having sockets with empty names can lead to various issues, so we'll create one now + FString SocketName = TEXT("Socket ") + FString::FromInt( nSocket ); + Socket->SocketName = FName( *SocketName ); + } + + // Socket Tag + FString Tag; + if ( AllSocketsTags.IsValidIndex( nSocket ) && !AllSocketsTags[ nSocket ].IsEmpty() ) + Tag = AllSocketsTags[ nSocket ]; + + // The actor will be stored temporarily in the socket's Tag as we need a StaticMeshComponent to add an actor to the socket + if ( AllSocketsActors.IsValidIndex( nSocket ) && !AllSocketsActors[ nSocket ].IsEmpty() ) + Tag += TEXT("|") + AllSocketsActors[ nSocket ]; + + Socket->Tag = Tag; + + StaticMesh->Sockets.Add( Socket ); + } + + // We don't want these new socket to be removed until next cook + HoudiniGeoPartObject.bHasSocketBeenAdded = true; + + return true; +} + + +bool +FHoudiniEngineUtils::AddAggregateCollisionGeometryToStaticMesh( + UStaticMesh* StaticMesh, + FHoudiniGeoPartObject& HoudiniGeoPartObject, + FKAggregateGeom& AggregateCollisionGeo ) +{ + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + return false; + + UBodySetup * BodySetup = StaticMesh->BodySetup; + if ( !BodySetup || BodySetup->IsPendingKill() ) + return false; + + // Do we need to remove the old collisions from the previous cook + if ( !HoudiniGeoPartObject.bHasCollisionBeenAdded ) + BodySetup->RemoveSimpleCollision(); + + BodySetup->AddCollisionFrom( AggregateCollisionGeo ); + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; + + BodySetup->ClearPhysicsMeshes(); + BodySetup->InvalidatePhysicsData(); + +#if WITH_EDITOR + RefreshCollisionChange( *StaticMesh ); +#endif + + // This geo part will have to be considered as rendered collision + if ( !HoudiniGeoPartObject.bIsCollidable ) + HoudiniGeoPartObject.bIsRenderCollidable = true; + + // We don't want these collisions to be removed before the next cook + HoudiniGeoPartObject.bHasCollisionBeenAdded = true; + + // Clean the added collisions + AggregateCollisionGeo.EmptyElements(); + + return true; +} + +bool +FHoudiniEngineUtils::AddActorsToMeshSocket( UStaticMeshSocket* Socket, UStaticMeshComponent* StaticMeshComponent ) +{ + if ( !Socket || Socket->IsPendingKill() + || !StaticMeshComponent || StaticMeshComponent->IsPendingKill() ) + return false; + + // The actor to assign is stored is the socket's tag + FString ActorString = Socket->Tag; + if ( ActorString.IsEmpty() ) + return false; + + // The actor to assign are listed after a | + TArray ActorStringArray; + ActorString.ParseIntoArray( ActorStringArray, TEXT("|"), false ); + + // The "real" Tag is the first + if ( ActorStringArray.Num() > 0 ) + Socket->Tag = ActorStringArray[ 0 ]; + + // We just add a Tag, no Actor + if ( ActorStringArray.Num() == 1 ) + return false; + + // Extract the parsed actor string to split it further + ActorString = ActorStringArray[ 1 ]; + + // Converting the string to a string array using delimiters + const TCHAR* Delims[] = { TEXT(","), TEXT(";") }; + ActorString.ParseIntoArray( ActorStringArray, Delims, 2 ); + + if ( ActorStringArray.Num() <= 0 ) + return false; + +#if WITH_EDITOR + // And try to find the corresponding HoudiniAssetActor in the editor world + // to avoid finding "deleted" assets with the same name + //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); + UWorld* editorWorld = StaticMeshComponent->GetOwner() ? StaticMeshComponent->GetOwner()->GetWorld() : nullptr; + if ( !editorWorld || editorWorld->IsPendingKill() ) + return false; + + for ( TActorIterator ActorItr( editorWorld ); ActorItr; ++ActorItr ) + { + // Same as with the Object Iterator, access the subclass instance with the * or -> operators. + AActor *Actor = *ActorItr; + if ( !Actor || Actor->IsPendingKillOrUnreachable() ) + continue; + + for ( int32 StringIdx = 0; StringIdx < ActorStringArray.Num(); StringIdx++ ) + { + if ( Actor->GetName() != ActorStringArray[ StringIdx ] + && Actor->GetActorLabel() != ActorStringArray[ StringIdx ] ) + continue; + + Socket->AttachActor( Actor, StaticMeshComponent ); + } + } +#endif + + return true; +} + +int32 +FHoudiniEngineUtils::GetUPropertyAttributeList( const FHoudiniGeoPartObject& GeoPartObject, TArray< UGenericAttribute >& AllUProps ) +{ + // Get the detail uprop attributes + int nUPropsCount = FHoudiniEngineUtils::GetGenericAttributeList( GeoPartObject, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, AllUProps, HAPI_ATTROWNER_DETAIL ); + + // Then the primitive uprop attributes + nUPropsCount += FHoudiniEngineUtils::GetGenericAttributeList( GeoPartObject, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, AllUProps, HAPI_ATTROWNER_PRIM ); + + return nUPropsCount; +} + +int32 +FHoudiniEngineUtils::GetGenericAttributeList( + const FHoudiniGeoPartObject& GeoPartObject, + const FString& GenericAttributePrefix, + TArray< UGenericAttribute >& AllUProps, + const HAPI_AttributeOwner& AttributeOwner, + int32 PrimitiveIndex ) +{ + HAPI_NodeId NodeId = GeoPartObject.HapiGeoGetNodeId(); + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + if ( !GeoPartObject.HapiPartGetInfo( PartInfo ) ) + return 0; + + HAPI_PartId PartId = GeoPartObject.GetPartId(); + + int32 nUPropCount = 0; + + // Get All attribute names for that part + int32 nAttribCount = PartInfo.attributeCounts[ AttributeOwner ]; + + TArray AttribNameSHArray; + AttribNameSHArray.SetNum( nAttribCount ); + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + NodeId, PartInfo.id, AttributeOwner, + AttribNameSHArray.GetData(), nAttribCount ) ) + return 0; + + // Since generic attributes can be on primitives, we may have to identify if a split occured during the mesh creation. + // If the primitive index was not specified, we need to a suitable primitive corresponding to that split + bool HandleSplit = false; + int PrimIndexForSplit = -1; + if ( AttributeOwner != HAPI_ATTROWNER_DETAIL ) + { + if ( PrimitiveIndex != -1 ) + { + // The index has already been specified so we'll use it + HandleSplit = true; + PrimIndexForSplit = PrimitiveIndex; + } + else if ( !GeoPartObject.SplitName.IsEmpty() && ( GeoPartObject.SplitName != TEXT("main_geo") ) ) + { + HandleSplit = true; + + // Since the meshes have been split, we need to find a primitive that belongs to the proper group + // so we can read the proper value for its generic attribute + TArray< int32 > PartGroupMembership; + FHoudiniEngineUtils::HapiGetGroupMembership( + GeoPartObject.AssetId, GeoPartObject.GetObjectId(), GeoPartObject.GetGeoId(), GeoPartObject.GetPartId(), + HAPI_GROUPTYPE_PRIM, GeoPartObject.SplitName, PartGroupMembership ); + + for ( int32 n = 0; n < PartGroupMembership.Num(); n++ ) + { + if ( PartGroupMembership[ n ] > 0 ) + { + PrimIndexForSplit = n; + break; + } + } + } + + if ( PrimIndexForSplit < 0 ) + PrimIndexForSplit = 0; + } + + for ( int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx ) + { + FString HapiString = TEXT(""); + FHoudiniEngineString HoudiniEngineString( AttribNameSHArray[ Idx ] ); + HoudiniEngineString.ToFString( HapiString ); + + if ( HapiString.StartsWith( GenericAttributePrefix, ESearchCase::IgnoreCase ) ) + { + UGenericAttribute CurrentUProperty; + + // Extract the name of the UProperty from the attribute name + CurrentUProperty.AttributeName = HapiString.Right( HapiString.Len() - GenericAttributePrefix.Len() ); + + // Get the Attribute Info + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + //FMemory::Memzero< HAPI_AttributeInfo >( AttribInfo ); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_UTF8( *HapiString ), + AttributeOwner, &AttribInfo ), false ); + + // Get the attribute type and tuple size + CurrentUProperty.AttributeType = AttribInfo.storage; + CurrentUProperty.AttributeCount = AttribInfo.count; + CurrentUProperty.AttributeTupleSize = AttribInfo.tupleSize; + + if ( CurrentUProperty.AttributeType == HAPI_STORAGETYPE_FLOAT64 ) + { + // Initialize the value array + CurrentUProperty.DoubleValues.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize ); + + // Get the value(s) + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeFloat64Data( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_UTF8( *HapiString ) ,&AttribInfo, + 0, CurrentUProperty.DoubleValues.GetData(), 0, AttribInfo.count ), false ); + + if ( HandleSplit ) + { + // For split primitives, we'll keep only one value from the proper split prim + TArray SplitValues; + CurrentUProperty.GetDoubleTuple( SplitValues, PrimIndexForSplit ); + + CurrentUProperty.DoubleValues.Empty(); + for ( int32 n = 0; n < SplitValues.Num(); n++ ) + CurrentUProperty.DoubleValues.Add( SplitValues[ n ] ); + + CurrentUProperty.AttributeCount = 1; + } + } + else if ( CurrentUProperty.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_FLOAT ) + { + // Initialize the value array + TArray< float > FloatValues; + FloatValues.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize ); + + // Get the value(s) + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_UTF8( *HapiString ) ,&AttribInfo, + 0, FloatValues.GetData(), 0, AttribInfo.count ), false ); + + // Convert them to double + CurrentUProperty.DoubleValues.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize ); + for ( int32 n = 0; n < FloatValues.Num(); n++ ) + CurrentUProperty.DoubleValues[ n ] = (double)FloatValues[ n ]; + + } + else if ( CurrentUProperty.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_INT64 ) + { + // Initialize the value array + CurrentUProperty.IntValues.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize ); + + // Get the value(s) + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_UTF8( *HapiString) ,&AttribInfo, + 0, CurrentUProperty.IntValues.GetData(), 0, AttribInfo.count ), false ); + } + else if ( CurrentUProperty.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_INT ) + { + // Initialize the value array + TArray< int32 > IntValues; + IntValues.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize ); + + // Get the value(s) + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_UTF8( *HapiString) ,&AttribInfo, + 0, IntValues.GetData(), 0, AttribInfo.count ), false ); + + // Convert them to int64 + CurrentUProperty.IntValues.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize ); + for( int32 n = 0; n < IntValues.Num(); n++ ) + CurrentUProperty.IntValues[ n ] = (int64)IntValues[ n ]; + + } + else if ( CurrentUProperty.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING ) + { + // Initialize a string handle array + TArray< HAPI_StringHandle > HapiSHArray; + HapiSHArray.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize ); + + // Get the string handle(s) + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_UTF8( *HapiString) ,&AttribInfo, + HapiSHArray.GetData(), 0, AttribInfo.count ), false ); + + // Convert them to FString + CurrentUProperty.StringValues.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize ); + + for( int32 IdxSH = 0; IdxSH < HapiSHArray.Num(); IdxSH++ ) + { + FString CurrentString; + FHoudiniEngineString HEngineString( HapiSHArray[ IdxSH ] ); + HEngineString.ToFString( CurrentString ); + + CurrentUProperty.StringValues[ IdxSH ] = CurrentString; + } + } + else + { + // Unsupported type, skipping! + continue; + } + + // Handling Split + // For split primitives, we'll keep only one value from the proper split prim + if ( HandleSplit ) + { + if ( ( CurrentUProperty.AttributeType == HAPI_STORAGETYPE_FLOAT64 ) + || ( CurrentUProperty.AttributeType == HAPI_STORAGETYPE_FLOAT ) ) + { + TArray< double > SplitValues; + CurrentUProperty.GetDoubleTuple( SplitValues, PrimIndexForSplit ); + + CurrentUProperty.DoubleValues.Empty(); + for ( int32 n = 0; n < SplitValues.Num(); n++ ) + CurrentUProperty.DoubleValues.Add( SplitValues[ n ] ); + } + else if ( ( CurrentUProperty.AttributeType == HAPI_STORAGETYPE_INT64 ) + || ( CurrentUProperty.AttributeType == HAPI_STORAGETYPE_INT ) ) + { + TArray< int64 > SplitValues; + CurrentUProperty.GetIntTuple( SplitValues, PrimIndexForSplit ); + + CurrentUProperty.IntValues.Empty(); + for ( int32 n = 0; n < SplitValues.Num(); n++ ) + CurrentUProperty.IntValues.Add( SplitValues[ n ] ); + } + else if ( CurrentUProperty.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING ) + { + TArray< FString > SplitValues; + CurrentUProperty.GetStringTuple( SplitValues, PrimIndexForSplit ); + + CurrentUProperty.StringValues.Empty(); + for ( int32 n = 0; n < SplitValues.Num(); n++ ) + CurrentUProperty.StringValues.Add( SplitValues[ n ] ); + } + + CurrentUProperty.AttributeCount = 1; + } + + // We can add the UPropertyAttribute to the array + AllUProps.Add( CurrentUProperty ); + nUPropCount++; + } + } + + return nUPropCount; +} + +void +FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject( + UObject* MeshComponent, const FHoudiniGeoPartObject& HoudiniGeoPartObject ) +{ + if ( !MeshComponent || MeshComponent->IsPendingKill() ) + return; + + // Get the list of uproperties to modify from the geopartobject's attributes + TArray< UGenericAttribute > UPropertiesAttributesToModify; + if ( !FHoudiniEngineUtils::GetUPropertyAttributeList( HoudiniGeoPartObject, UPropertiesAttributesToModify ) ) + return; + + // Iterate over the Found UProperty attributes + for ( int32 nAttributeIdx = 0; nAttributeIdx < UPropertiesAttributesToModify.Num(); nAttributeIdx++ ) + { + // Get the current Uproperty Attribute + UGenericAttribute CurrentPropAttribute = UPropertiesAttributesToModify[ nAttributeIdx ]; + FString CurrentUPropertyName = CurrentPropAttribute.AttributeName; + if ( CurrentUPropertyName.IsEmpty() ) + continue; + + // Some UProperties need to be modified manually... + if ( CurrentUPropertyName == "CollisionProfileName" ) + { + UPrimitiveComponent* PC = Cast< UPrimitiveComponent >( MeshComponent ); + if ( PC && !PC->IsPendingKill() ) + { + FString StringValue = CurrentPropAttribute.GetStringValue(); + FName Value = FName( *StringValue ); + PC->SetCollisionProfileName( Value ); + + continue; + } + } + else if ( CurrentUPropertyName == "Visible" ) + { + USceneComponent* SC = Cast< USceneComponent >(MeshComponent); + if (SC && !SC->IsPendingKill()) + { + bool bVal = CurrentPropAttribute.GetBoolValue(); + SC->SetVisibility(bVal); + continue; + } + } + + // Handle Component Tags manually here + if ( CurrentUPropertyName.Contains("Tags") ) + { + UActorComponent* AC = Cast< UActorComponent >( MeshComponent ); + if ( AC && !AC->IsPendingKill() ) + { + for ( int nIdx = 0; nIdx < CurrentPropAttribute.AttributeCount; nIdx++ ) + { + FName NameAttr = FName( *CurrentPropAttribute.GetStringValue( nIdx ) ); + + if ( !AC->ComponentTags.Contains( NameAttr ) ) + AC->ComponentTags.Add( NameAttr ); + } + + continue; + } + } + + // Try to find the corresponding UProperty + FProperty* FoundProperty = nullptr; + void* StructContainer = nullptr; + UObject* FoundPropertyObject = nullptr; + + if ( !FindUPropertyAttributesOnObject( MeshComponent, CurrentPropAttribute, FoundProperty, FoundPropertyObject, StructContainer ) ) + continue; + + if ( !ModifyUPropertyValueOnObject( FoundPropertyObject, CurrentPropAttribute, FoundProperty, StructContainer ) ) + continue; + + // Sucess! + FString ClassName = MeshComponent->GetClass() ? MeshComponent->GetClass()->GetName() : TEXT("Object"); + FString ObjectName = MeshComponent->GetName(); + + // Couldn't find or modify the Uproperty! + HOUDINI_LOG_MESSAGE( TEXT( "Modified FProperty %s on %s named %s" ), *CurrentUPropertyName, *ClassName, * ObjectName ); + } +} +/* +bool +FHoudiniEngineUtils::TryToFindInStructProperty(UObject* Object, FString UPropertyNameToFind, UStructProperty* StructProperty, UProperty*& FoundProperty, void*& StructContainer ) +{ + if ( !StructProperty || !Object ) + return false; + + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + for (TFieldIterator< UProperty > It(Struct); It; ++It) + { + UProperty* Property = *It; + if ( !Property ) + continue; + + FString DisplayName = It->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = It->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + FoundProperty = Property; + StructContainer = StructProperty->ContainerPtrToValuePtr< void >( Object, 0); + + // If it's an equality, we dont need to keep searching + if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) + return true; + } + + if ( FoundProperty ) + continue; + + UStructProperty* NestedStruct = Cast( Property ); + if ( NestedStruct && TryToFindInStructProperty( Object, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) + return true; + + UArrayProperty* ArrayProp = Cast( Property ); + if ( ArrayProp && TryToFindInArrayProperty( Object, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::TryToFindInArrayProperty(UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ) +{ + if ( !ArrayProperty || !Object ) + return false; + + UProperty* Property = ArrayProperty->Inner; + if ( !Property ) + return false; + + FString DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = Property->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + FoundProperty = Property; + StructContainer = ArrayProperty->ContainerPtrToValuePtr< void >( Object, 0); + + // If it's an equality, we dont need to keep searching + if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) + return true; + } + + if ( !FoundProperty ) + { + UStructProperty* NestedStruct = Cast( Property ); + if ( NestedStruct && TryToFindInStructProperty( ArrayProperty, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) + return true; + + UArrayProperty* ArrayProp = Cast( Property ); + if ( ArrayProp && TryToFindInArrayProperty( ArrayProperty, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) + return true; + } + + return false; +} +*/ + + +bool FHoudiniEngineUtils::FindUPropertyAttributesOnObject( + UObject* ParentObject, const UGenericAttribute& UPropertiesToFind, + FProperty*& FoundProperty, UObject*& FoundPropertyObject, void*& StructContainer ) +{ +#if WITH_EDITOR + if ( !ParentObject || ParentObject->IsPendingKill() ) + return false; + + // Get the name of the uprop we're looking for + FString CurrentUPropertyName = UPropertiesToFind.AttributeName; + if ( CurrentUPropertyName.IsEmpty() ) + return false; + + UClass* MeshClass = ParentObject->GetClass(); + if ( !MeshClass || MeshClass->IsPendingKill() ) + return false; + + // Set the result pointer to null + StructContainer = nullptr; + FoundProperty = nullptr; + + FoundPropertyObject = ParentObject; + + bool bPropertyHasBeenFound = false; + + /* + // Try to find the uprop using a TPropValueIterator?? + for ( TPropertyValueIterator It( MeshClass, ParentObject, EPropertyValueIteratorFlags::FullRecursion ); It; ++It ) + { + UProperty* CurrentProperty = It.Key(); + void* CurrentPropertyValue = (void*)It.Value(); + UObject* ObjectValue = *((UObject**)CurrentPropertyValue); + + FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = CurrentProperty->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( CurrentUPropertyName ) || DisplayName.Contains( CurrentUPropertyName ) ) + { + FoundProperty = CurrentProperty; + + // If it's an equality, we dont need to keep searching + if ( ( Name == CurrentUPropertyName ) || ( DisplayName == CurrentUPropertyName ) ) + { + bPropertyHasBeenFound = true; + break; + } + } + } + + if ( bPropertyHasBeenFound ) + return true; + */ + + // Iterate manually on the properties, in order to handle structProperties correctly + for ( TFieldIterator< FProperty > PropIt( MeshClass, EFieldIteratorFlags::IncludeSuper ); PropIt; ++PropIt ) + { + FProperty* CurrentProperty = *PropIt; + if ( !CurrentProperty ) + continue; + + FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace( TEXT(" "), TEXT("") ); + FString Name = CurrentProperty->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( CurrentUPropertyName ) || DisplayName.Contains( CurrentUPropertyName ) ) + { + FoundProperty = CurrentProperty; + + // If it's an equality, we dont need to keep searching + if ( ( Name == CurrentUPropertyName ) || ( DisplayName == CurrentUPropertyName ) ) + { + bPropertyHasBeenFound = true; + break; + } + } + + /* + // StructProperty need to be a nested struct + if (FStructProperty* StructProperty = CastFiled< FStructProperty >(CurrentProperty)) + bPropertyHasBeenFound = TryToFindInStructProperty(MeshComponent, CurrentUPropertyName, StructProperty, FoundProperty, StructContainer); + else if (FArrayProperty* ArrayProperty = CastField< FArrayProperty >(CurrentProperty)) + bPropertyHasBeenFound = TryToFindInArrayProperty(MeshComponent, CurrentUPropertyName, ArrayProperty, FoundProperty, StructContainer); + */ + + // StructProperty need to be a nested struct + FStructProperty* StructProperty = CastField< FStructProperty >(CurrentProperty); + if ( StructProperty ) + { + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + if (!Struct || Struct->IsPendingKill()) + continue; + + for ( TFieldIterator< FProperty > It( Struct ); It; ++It ) + { + FProperty* Property = *It; + if (!Property ) + continue; + + DisplayName = Property->GetDisplayNameText().ToString().Replace( TEXT(" "), TEXT("") ); + Name = Property->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( CurrentUPropertyName ) || DisplayName.Contains( CurrentUPropertyName ) ) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + FoundProperty = Property; + StructContainer = StructProperty->ContainerPtrToValuePtr< void >( ParentObject, 0 ); + + // If it's an equality, we dont need to keep searching + if ( ( Name == CurrentUPropertyName ) || ( DisplayName == CurrentUPropertyName ) ) + { + bPropertyHasBeenFound = true; + break; + } + } + } + } + + if ( bPropertyHasBeenFound ) + break; + } + + if ( bPropertyHasBeenFound ) + return true; + + // Try with FindField?? + if ( !FoundProperty ) + FoundProperty = FindFProperty( MeshClass, *CurrentUPropertyName ); + + // Try with FindPropertyByName ?? + if ( !FoundProperty ) + FoundProperty = MeshClass->FindPropertyByName( *CurrentUPropertyName ); + + // We found the UProperty we were looking for + if ( FoundProperty ) + return true; + + // Handle common properties nested in classes + // Static Meshes + UStaticMesh* SM = Cast< UStaticMesh >( ParentObject ); + if ( SM && !SM->IsPendingKill() ) + { + if ( SM->BodySetup && FindUPropertyAttributesOnObject( + SM->BodySetup, UPropertiesToFind, FoundProperty, FoundPropertyObject, StructContainer ) ) + { + return true; + } + + if ( SM->AssetImportData && FindUPropertyAttributesOnObject( + SM->AssetImportData, UPropertiesToFind, FoundProperty, FoundPropertyObject, StructContainer ) ) + { + return true; + } + + if ( SM->NavCollision && FindUPropertyAttributesOnObject( + SM->NavCollision, UPropertiesToFind, FoundProperty, FoundPropertyObject, StructContainer ) ) + { + return true; + } + } + + // We found the UProperty we were looking for + if ( FoundProperty ) + return true; + +#endif + return false; +} + +bool FHoudiniEngineUtils::ModifyUPropertyValueOnObject( + UObject* MeshComponent, UGenericAttribute CurrentPropAttribute, + FProperty* FoundProperty, void * StructContainer ) +{ + if ( !MeshComponent || MeshComponent->IsPendingKill() || !FoundProperty ) + return false; + + FProperty* InnerProperty = FoundProperty; + int32 NumberOfProperties = 1; + + FArrayProperty* ArrayProperty = CastField< FArrayProperty >(FoundProperty); + if ( ArrayProperty ) + { + InnerProperty = ArrayProperty->Inner; + NumberOfProperties = ArrayProperty->ArrayDim; + + // Do we need to add values to the array? + FScriptArrayHelper_InContainer ArrayHelper( ArrayProperty, CurrentPropAttribute.GetData() ); + + //ArrayHelper.ExpandForIndex( CurrentPropAttribute.AttributeTupleSize - 1 ); + if ( CurrentPropAttribute.AttributeTupleSize > NumberOfProperties ) + { + ArrayHelper.Resize( CurrentPropAttribute.AttributeTupleSize ); + NumberOfProperties = CurrentPropAttribute.AttributeTupleSize; + } + } + + for ( int32 nPropIdx = 0; nPropIdx < NumberOfProperties; nPropIdx++ ) + { + if ( FFloatProperty* FloatProperty = CastField< FFloatProperty >( InnerProperty ) ) + { + // FLOAT PROPERTY + if ( CurrentPropAttribute.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING ) + { + FString Value = CurrentPropAttribute.GetStringValue( nPropIdx ); + void * ValuePtr = StructContainer ? + InnerProperty->ContainerPtrToValuePtr< FString >( (uint8 *)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx ); + + if ( ValuePtr ) + FloatProperty->SetNumericPropertyValueFromString( ValuePtr, *Value ); + } + else + { + double Value = CurrentPropAttribute.GetDoubleValue( nPropIdx ); + void * ValuePtr = StructContainer ? + InnerProperty->ContainerPtrToValuePtr< float >( (uint8 *)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< float >( MeshComponent, nPropIdx ); + + if ( ValuePtr ) + FloatProperty->SetFloatingPointPropertyValue( ValuePtr, Value ); + } + } + else if ( FIntProperty* IntProperty = CastField< FIntProperty >( InnerProperty ) ) + { + // INT PROPERTY + if ( CurrentPropAttribute.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING ) + { + FString Value = CurrentPropAttribute.GetStringValue( nPropIdx ); + void * ValuePtr = StructContainer ? + InnerProperty->ContainerPtrToValuePtr< FString >( (uint8 *)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx ); + + if ( ValuePtr ) + IntProperty->SetNumericPropertyValueFromString( ValuePtr, *Value ); + } + else + { + int64 Value = CurrentPropAttribute.GetIntValue( nPropIdx ); + void * ValuePtr = StructContainer ? + InnerProperty->ContainerPtrToValuePtr< int64 >( (uint8 *)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< int64 >( MeshComponent, nPropIdx ); + + if ( ValuePtr ) + IntProperty->SetIntPropertyValue( ValuePtr, Value ); + } + } + else if ( FBoolProperty* BoolProperty = CastField< FBoolProperty >( InnerProperty ) ) + { + // BOOL PROPERTY + bool Value = CurrentPropAttribute.GetBoolValue( nPropIdx ); + void * ValuePtr = StructContainer ? + InnerProperty->ContainerPtrToValuePtr< bool >( (uint8*)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< bool >( MeshComponent, nPropIdx ); + + if ( ValuePtr ) + BoolProperty->SetPropertyValue( ValuePtr, Value ); + } + else if ( FStrProperty* StringProperty = CastField< FStrProperty >( InnerProperty ) ) + { + // STRING PROPERTY + FString Value = CurrentPropAttribute.GetStringValue( nPropIdx ); + void * ValuePtr = StructContainer ? + InnerProperty->ContainerPtrToValuePtr< FString >( (uint8*)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx ); + + if ( ValuePtr ) + StringProperty->SetPropertyValue( ValuePtr, Value ); + } + else if ( FNumericProperty *NumericProperty = CastField< FNumericProperty >( InnerProperty ) ) + { + // NUMERIC PROPERTY + if ( CurrentPropAttribute.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING ) + { + FString Value = CurrentPropAttribute.GetStringValue( nPropIdx ); + void * ValuePtr = StructContainer ? + InnerProperty->ContainerPtrToValuePtr< FString >( (uint8 *)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx ); + + if ( ValuePtr ) + NumericProperty->SetNumericPropertyValueFromString( ValuePtr, *Value ); + } + else if ( NumericProperty->IsInteger() ) + { + int64 Value = CurrentPropAttribute.GetIntValue( nPropIdx ); + void * ValuePtr = StructContainer ? + InnerProperty->ContainerPtrToValuePtr< int64 >( (uint8*)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< int64 >( MeshComponent, nPropIdx ); + + if ( ValuePtr ) + NumericProperty->SetIntPropertyValue(ValuePtr, (int64)Value ); + } + else if ( NumericProperty->IsFloatingPoint() ) + { + double Value = CurrentPropAttribute.GetDoubleValue( nPropIdx ); + void * ValuePtr = StructContainer ? + InnerProperty->ContainerPtrToValuePtr< float >( (uint8 *)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< float >( MeshComponent, nPropIdx ); + + if ( ValuePtr ) + NumericProperty->SetFloatingPointPropertyValue( ValuePtr, Value ); + } + else + { + // Numeric Property was found, but is of an unsupported type + HOUDINI_LOG_MESSAGE( TEXT("Unsupported Numeric UProperty") ); + } + } + else if ( FNameProperty* NameProperty = CastField< FNameProperty >( InnerProperty ) ) + { + // NAME PROPERTY + FString StringValue = CurrentPropAttribute.GetStringValue( nPropIdx ); + FName Value = FName( *StringValue ); + + void * ValuePtr = StructContainer ? + InnerProperty->ContainerPtrToValuePtr< FName >( (uint8*)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< FName >( MeshComponent, nPropIdx ); + + if ( ValuePtr ) + NameProperty->SetPropertyValue( ValuePtr, Value ); + } + else if ( FStructProperty* StructProperty = CastField< FStructProperty >( InnerProperty ) ) + { + // STRUCT PROPERTY + const FName PropertyName = StructProperty->Struct->GetFName(); + if ( PropertyName == NAME_Vector ) + { + FVector* PropertyValue = StructContainer ? + InnerProperty->ContainerPtrToValuePtr< FVector >( (uint8 *)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< FVector >( MeshComponent, nPropIdx ); + + if ( PropertyValue ) + { + // Found a vector property, fill it with the 3 tuple values + PropertyValue->X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 0 ); + PropertyValue->Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 1 ); + PropertyValue->Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 2 ); + } + } + else if ( PropertyName == NAME_Transform ) + { + FTransform* PropertyValue = StructContainer ? + InnerProperty->ContainerPtrToValuePtr< FTransform >( (uint8 *)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< FTransform >( MeshComponent, nPropIdx ); + + if ( PropertyValue ) + { + // Found a transform property fill it with the attribute tuple values + FVector Translation; + Translation.X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 0 ); + Translation.Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 1 ); + Translation.Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 2 ); + + FQuat Rotation; + Rotation.W = CurrentPropAttribute.GetDoubleValue( nPropIdx + 3 ); + Rotation.X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 4 ); + Rotation.Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 5 ); + Rotation.Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 6 ); + + FVector Scale; + Scale.X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 7 ); + Scale.Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 8 ); + Scale.Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 9 ); + + PropertyValue->SetTranslation( Translation ); + PropertyValue->SetRotation( Rotation ); + PropertyValue->SetScale3D( Scale ); + } + } + else if ( PropertyName == NAME_Color ) + { + FColor* PropertyValue = StructContainer ? + InnerProperty->ContainerPtrToValuePtr< FColor >( (uint8 *)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< FColor >( MeshComponent, nPropIdx ); + + if ( PropertyValue ) + { + PropertyValue->R = (int8)CurrentPropAttribute.GetIntValue( nPropIdx ); + PropertyValue->G = (int8)CurrentPropAttribute.GetIntValue( nPropIdx + 1 ); + PropertyValue->B = (int8)CurrentPropAttribute.GetIntValue( nPropIdx + 2 ); + if ( CurrentPropAttribute.AttributeTupleSize == 4 ) + PropertyValue->A = (int8)CurrentPropAttribute.GetIntValue( nPropIdx + 3 ); + } + } + else if ( PropertyName == NAME_LinearColor ) + { + FLinearColor* PropertyValue = StructContainer ? + InnerProperty->ContainerPtrToValuePtr< FLinearColor >( (uint8 *)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< FLinearColor >( MeshComponent, nPropIdx ); + + if ( PropertyValue ) + { + PropertyValue->R = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx ); + PropertyValue->G = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx + 1 ); + PropertyValue->B = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx + 2 ); + if ( CurrentPropAttribute.AttributeTupleSize == 4 ) + PropertyValue->A = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx + 3 ); + } + } + } + else + { + // Property was found, but is of an unsupported type + FString PropertyClass = FoundProperty->GetClass() ? FoundProperty->GetClass()->GetName() : TEXT("Unknown"); + HOUDINI_LOG_MESSAGE( TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClass, *CurrentPropAttribute.AttributeName ); + return false; + } + } + + return true; +} + +/* +void +FHoudiniEngineUtils::ApplyUPropertyAttributesOnObject( + UObject* MeshComponent, const TArray< UGenericAttribute >& UPropertiesToModify ) +{ +#if WITH_EDITOR + if ( !MeshComponent ) + return; + + int nUPropsCount = UPropertiesToModify.Num(); + if ( nUPropsCount <= 0 ) + return; + + // MeshComponent should be either a StaticMeshComponent, an InstancedStaticMeshComponent or an InstancedActorComponent + UStaticMeshComponent* SMC = Cast< UStaticMeshComponent >( MeshComponent ); + UInstancedStaticMeshComponent* ISMC = Cast< UInstancedStaticMeshComponent >( MeshComponent ); + UHierarchicalInstancedStaticMeshComponent* HISMC = Cast< UHierarchicalInstancedStaticMeshComponent >( MeshComponent ); + UHoudiniInstancedActorComponent* IAC = Cast< UHoudiniInstancedActorComponent >( MeshComponent ); + UHoudiniMeshSplitInstancerComponent* MSPIC = Cast(MeshComponent); + UStaticMesh* SM = Cast< UStaticMesh >( MeshComponent ); + UBodySetup* BS = Cast< UBodySetup >( MeshComponent ); + if ( !SMC && !HISMC && !ISMC && !IAC && !MSPIC && !SM ) + return; + + UClass* MeshClass = IAC ? IAC->StaticClass() + : HISMC ? HISMC->StaticClass() + : ISMC ? ISMC->StaticClass() + : MSPIC ? MSPIC->StaticClass() + : SMC ? SMC->StaticClass() + : SM ? SM->StaticClass() + : BS->StaticClass(); + + // Trying to find the UProps in the object + for ( int32 nAttributeIdx = 0; nAttributeIdx < nUPropsCount; nAttributeIdx++ ) + { + UGenericAttribute CurrentPropAttribute = UPropertiesToModify[ nAttributeIdx ]; + FString CurrentUPropertyName = CurrentPropAttribute.AttributeName; + if ( CurrentUPropertyName.IsEmpty() ) + continue; + + // We have to iterate manually on the properties, in order to handle structProperties correctly + void* StructContainer = nullptr; + UProperty* FoundProperty = nullptr; + bool bPropertyHasBeenFound = false; + for ( TFieldIterator< UProperty > PropIt( MeshClass ); PropIt; ++PropIt ) + { + UProperty* CurrentProperty = *PropIt; + + FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace( TEXT(" "), TEXT("") ); + FString Name = CurrentProperty->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( CurrentUPropertyName ) || DisplayName.Contains( CurrentUPropertyName ) ) + { + FoundProperty = CurrentProperty; + + // If it's an equality, we dont need to keep searching + if ( ( Name == CurrentUPropertyName ) || ( DisplayName == CurrentUPropertyName ) ) + { + bPropertyHasBeenFound = true; + break; + } + } + + // StructProperty need to be a nested struct + if ( FStructProperty* StructProperty = CastField< UStructProperty >( CurrentProperty ) ) + { + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + for ( TFieldIterator< UProperty > It( Struct ); It; ++It ) + { + UProperty* Property = *It; + + DisplayName = It->GetDisplayNameText().ToString().Replace( TEXT(" "), TEXT("") ); + Name = It->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( CurrentUPropertyName ) || DisplayName.Contains( CurrentUPropertyName ) ) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + FoundProperty = Property; + StructContainer = StructProperty->ContainerPtrToValuePtr< void >( MeshComponent, 0 ); + + // If it's an equality, we dont need to keep searching + if ( ( Name == CurrentUPropertyName ) || ( DisplayName == CurrentUPropertyName ) ) + { + bPropertyHasBeenFound = true; + break; + } + } + } + } + + if ( bPropertyHasBeenFound ) + break; + } + + if ( !FoundProperty ) + { + // Property not found + HOUDINI_LOG_MESSAGE( TEXT( "Could not find UProperty: %s"), *( CurrentPropAttribute.AttributeName ) ); + continue; + } + + // The found property's class (TODO Remove me! for debug only) + UClass* PropertyClass = FoundProperty->GetClass(); + + UProperty* InnerProperty = FoundProperty; + int32 NumberOfProperties = 1; + + if ( FArrayProperty* ArrayProperty = CastField< FArrayProperty >( FoundProperty ) ) + { + InnerProperty = ArrayProperty->Inner; + NumberOfProperties = ArrayProperty->ArrayDim; + + // Do we need to add values to the array? + FScriptArrayHelper_InContainer ArrayHelper( ArrayProperty, CurrentPropAttribute.GetData() ); + + //ArrayHelper.ExpandForIndex( CurrentPropAttribute.AttributeTupleSize - 1 ); + + if ( CurrentPropAttribute.AttributeTupleSize > NumberOfProperties ) + { + ArrayHelper.Resize( CurrentPropAttribute.AttributeTupleSize ); + NumberOfProperties = CurrentPropAttribute.AttributeTupleSize; + } + } + + for( int32 nPropIdx = 0; nPropIdx < NumberOfProperties; nPropIdx++ ) + { + if ( FFloatProperty* FloatProperty = CastField< FFloatProperty >( InnerProperty ) ) + { + // FLOAT PROPERTY + if ( CurrentPropAttribute.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING ) + { + FString Value = CurrentPropAttribute.GetStringValue( nPropIdx ); + FloatProperty->SetNumericPropertyValueFromString( + StructContainer ? + InnerProperty->ContainerPtrToValuePtr< FString >( ( uint8 * )StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx ), + *Value ); + } + else + { + double Value = CurrentPropAttribute.GetDoubleValue( nPropIdx ); + FloatProperty->SetFloatingPointPropertyValue( + StructContainer ? + InnerProperty->ContainerPtrToValuePtr< float >( ( uint8 * )StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< float >( MeshComponent, nPropIdx ), + Value ); + } + } + else if ( FIntProperty* IntProperty = CastField< FIntProperty >( InnerProperty ) ) + { + // INT PROPERTY + if ( CurrentPropAttribute.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING ) + { + FString Value = CurrentPropAttribute.GetStringValue( nPropIdx ); + IntProperty->SetNumericPropertyValueFromString( + StructContainer ? + InnerProperty->ContainerPtrToValuePtr< FString >( ( uint8 * )StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx ), + *Value ); + } + else + { + int64 Value = CurrentPropAttribute.GetIntValue( nPropIdx ); + IntProperty->SetIntPropertyValue( + StructContainer ? + InnerProperty->ContainerPtrToValuePtr< int64 >( ( uint8 * )StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< int64 >( MeshComponent, nPropIdx ), + Value ); + } + } + else if ( FBoolProperty* BoolProperty = CastField< FBoolProperty >( InnerProperty ) ) + { + // BOOL PROPERTY + bool Value = CurrentPropAttribute.GetBoolValue( nPropIdx ); + + BoolProperty->SetPropertyValue( + StructContainer ? + InnerProperty->ContainerPtrToValuePtr< bool >( ( uint8* )StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< bool >( MeshComponent, nPropIdx ), + Value ); + } + else if ( FStrProperty* StringProperty = CastField< FStrProperty >( InnerProperty ) ) + { + // STRING PROPERTY + FString Value = CurrentPropAttribute.GetStringValue( nPropIdx ); + + StringProperty->SetPropertyValue( + StructContainer ? + InnerProperty->ContainerPtrToValuePtr< FString >( ( uint8* )StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx ), + Value ); + } + else if ( FNumericProperty *NumericProperty = CastField< FNumericProperty >( InnerProperty ) ) + { + // NUMERIC PROPERTY + if ( CurrentPropAttribute.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING ) + { + FString Value = CurrentPropAttribute.GetStringValue( nPropIdx ); + NumericProperty->SetNumericPropertyValueFromString( + StructContainer ? + InnerProperty->ContainerPtrToValuePtr< FString >( ( uint8 * )StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx ), + *Value ); + } + else if ( NumericProperty->IsInteger() ) + { + int64 Value = CurrentPropAttribute.GetIntValue( nPropIdx ); + + NumericProperty->SetIntPropertyValue( + StructContainer ? + InnerProperty->ContainerPtrToValuePtr< int64 >( (uint8*)StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< int64 >( MeshComponent, nPropIdx ), + (int64)Value ); + } + else if ( NumericProperty->IsFloatingPoint() ) + { + double Value = CurrentPropAttribute.GetDoubleValue( nPropIdx ); + + NumericProperty->SetFloatingPointPropertyValue( + StructContainer ? + InnerProperty->ContainerPtrToValuePtr< float >( ( uint8 * )StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< float >( MeshComponent, nPropIdx ), + Value ); + } + else + { + // Numeric Property was found, but is of an unsupported type + HOUDINI_LOG_MESSAGE( TEXT( "Unsupported Numeric UProperty" ) ); + } + } + else if ( FNameProperty* NameProperty = CastField< FNameProperty >( InnerProperty ) ) + { + // NAME PROPERTY + FString StringValue = CurrentPropAttribute.GetStringValue( nPropIdx ); + + FName Value = FName( *StringValue ); + + NameProperty->SetPropertyValue( + StructContainer ? + InnerProperty->ContainerPtrToValuePtr< FName >( ( uint8* )StructContainer, nPropIdx ) + : InnerProperty->ContainerPtrToValuePtr< FName >( MeshComponent, nPropIdx ), + Value ); + } + else if ( FStructProperty* StructProperty = CastField< FStructProperty >( InnerProperty ) ) + { + // STRUCT PROPERTY + const FName PropertyName = StructProperty->Struct->GetFName(); + if ( PropertyName == NAME_Vector ) + { + FVector& PropertyValue = StructContainer ? + *( InnerProperty->ContainerPtrToValuePtr< FVector >( (uint8 *) StructContainer, nPropIdx ) ) + : *( InnerProperty->ContainerPtrToValuePtr< FVector >( MeshComponent, nPropIdx ) ); + + // Found a vector property, fill it with the 3 tuple values + PropertyValue.X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 0 ); + PropertyValue.Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 1 ); + PropertyValue.Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 2 ); + } + else if ( PropertyName == NAME_Transform ) + { + FTransform& PropertyValue = StructContainer ? + *( InnerProperty->ContainerPtrToValuePtr< FTransform >( (uint8 *) StructContainer, nPropIdx ) ) + : *( InnerProperty->ContainerPtrToValuePtr< FTransform >( MeshComponent, nPropIdx ) ); + + // Found a transform property fill it with the attribute tuple values + FVector Translation; + Translation.X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 0 ); + Translation.Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 1 ); + Translation.Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 2 ); + + FQuat Rotation; + Rotation.W = CurrentPropAttribute.GetDoubleValue( nPropIdx + 3 ); + Rotation.X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 4 ); + Rotation.Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 5 ); + Rotation.Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 6 ); + + FVector Scale; + Scale.X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 7 ); + Scale.Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 8 ); + Scale.Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 9 ); + + PropertyValue.SetTranslation( Translation ); + PropertyValue.SetRotation( Rotation ); + PropertyValue.SetScale3D( Scale ); + } + else if ( PropertyName == NAME_Color ) + { + FColor& PropertyValue = StructContainer ? + *(InnerProperty->ContainerPtrToValuePtr< FColor >( (uint8 *)StructContainer, nPropIdx ) ) + : *(InnerProperty->ContainerPtrToValuePtr< FColor >( MeshComponent, nPropIdx ) ); + + PropertyValue.R = (int8)CurrentPropAttribute.GetIntValue( nPropIdx ); + PropertyValue.G = (int8)CurrentPropAttribute.GetIntValue( nPropIdx + 1 ); + PropertyValue.B = (int8)CurrentPropAttribute.GetIntValue( nPropIdx + 2 ); + if ( CurrentPropAttribute.AttributeTupleSize == 4 ) + PropertyValue.A = (int8)CurrentPropAttribute.GetIntValue( nPropIdx + 3 ); + } + else if ( PropertyName == NAME_LinearColor ) + { + FLinearColor& PropertyValue = StructContainer ? + *(InnerProperty->ContainerPtrToValuePtr< FLinearColor >( (uint8 *)StructContainer, nPropIdx ) ) + : *(InnerProperty->ContainerPtrToValuePtr< FLinearColor >( MeshComponent, nPropIdx ) ); + + PropertyValue.R = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx ); + PropertyValue.G = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx + 1 ); + PropertyValue.B = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx + 2 ); + if ( CurrentPropAttribute.AttributeTupleSize == 4 ) + PropertyValue.A = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx + 3 ); + } + } + else + { + // Property was found, but is of an unsupported type + HOUDINI_LOG_MESSAGE( TEXT("Unsupported UProperty Class: %s found for uproperty %s" ), *PropertyClass->GetName(), *CurrentPropAttribute.AttributeName ); + continue; + } + + // Some UProperties might need some additional tweaks... + if ( CurrentUPropertyName == "CollisionProfileName" ) + { + FString StringValue = CurrentPropAttribute.GetStringValue( nPropIdx ); + FName Value = FName( *StringValue ); + + if ( SMC ) + SMC->SetCollisionProfileName( Value ); + else if ( ISMC ) + ISMC->SetCollisionProfileName( Value ); + } + } + } +#endif +} + +*/ + +int32 +FHoudiniEngineUtils::HapiGetAttributeOfType( + const HAPI_NodeId& AssetId, + const HAPI_NodeId& ObjectId, + const HAPI_NodeId& GeoId, + const HAPI_NodeId& PartId, + const HAPI_AttributeOwner& AttributeOwner, + const HAPI_AttributeTypeInfo& AttributeType, + TArray< HAPI_AttributeInfo >& MatchingAttributesInfo, + TArray< FString >& MatchingAttributesName ) +{ + int32 NumberOfAttributeFound = 0; + + // Get the part infos + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, &PartInfo ), NumberOfAttributeFound ); + + // Get All attribute names for that part + int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; + + TArray AttribNameSHArray; + AttribNameSHArray.SetNum( nAttribCount ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + GeoId, PartInfo.id, AttributeOwner, + AttribNameSHArray.GetData(), nAttribCount ), NumberOfAttributeFound ); + + // Iterate on all the attributes, and get their part infos to get their type + for ( int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx ) + { + // Get the name ... + FString HapiString = TEXT(""); + FHoudiniEngineString HoudiniEngineString( AttribNameSHArray[ Idx ] ); + HoudiniEngineString.ToFString( HapiString ); + + // ... then the attribute info + HAPI_AttributeInfo AttrInfo; + FHoudiniApi::AttributeInfo_Init(&AttrInfo); + //FMemory::Memzero< HAPI_AttributeInfo >( AttrInfo ); + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartInfo.id, TCHAR_TO_UTF8( *HapiString ), + AttributeOwner, &AttrInfo ) ) + continue; + + if ( !AttrInfo.exists ) + continue; + + // ... check the type + if ( AttrInfo.typeInfo != AttributeType ) + continue; + + MatchingAttributesInfo.Add( AttrInfo ); + MatchingAttributesName.Add( HapiString ); + + NumberOfAttributeFound++; + } + + return NumberOfAttributeFound; +} + +void +FHoudiniEngineUtils::GetAllUVAttributesInfoAndTexCoords( + const HAPI_NodeId& AssetId, const HAPI_NodeId& ObjectId, + const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId, + TArray< HAPI_AttributeInfo >& AttribInfoUVs, + TArray< TArray< float > >& TextureCoordinates ) +{ + // The second UV set should be called uv2, but we will still check if need to look for a uv1 set. + // If uv1 exists, we'll look for uv, uv1, uv2 etc.. if not we'll look for uv, uv2, uv3 etc.. + bool bUV1Exists = FHoudiniEngineUtils::HapiCheckAttributeExists(AssetId, ObjectId, GeoId, PartId, "uv1"); + + // Retrieve UVs. + for ( int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx ) + { + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + + if ( TexCoordIdx > 0 ) + UVAttributeName += FString::Printf( TEXT("%d"), bUV1Exists ? TexCoordIdx : TexCoordIdx + 1 ); + + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + AssetId, ObjectId, GeoId, PartId, TCHAR_TO_ANSI(*UVAttributeName), + AttribInfoUVs[ TexCoordIdx ], TextureCoordinates[ TexCoordIdx ], 2 ); + } + + // Also look for 16.5 uvs (attributes with a Texture type) + // For that, we'll have to iterate through ALL the attributes and check their types + TArray< HAPI_AttributeInfo > FoundAttributeInfos; + TArray< FString > FoundAttributeNames; + for ( int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx ) + { + HapiGetAttributeOfType( AssetId, ObjectId, GeoId, PartId, + ( HAPI_AttributeOwner ) AttrIdx, HAPI_ATTRIBUTE_TYPE_TEXTURE, + FoundAttributeInfos, FoundAttributeNames ); + } + + if ( FoundAttributeInfos.Num() <= 0 ) + return; + + // We found some additionnal uv attributes + int32 AvailableIdx = 0; + for( int32 attrIdx = 0; attrIdx < FoundAttributeInfos.Num(); attrIdx++ ) + { + // Ignore the old uvs + if ( FoundAttributeNames[ attrIdx ] == TEXT("uv") + || FoundAttributeNames[ attrIdx ] == TEXT("uv1") + || FoundAttributeNames[ attrIdx ] == TEXT("uv2") + || FoundAttributeNames[ attrIdx ] == TEXT("uv3") + || FoundAttributeNames[ attrIdx ] == TEXT("uv4") + || FoundAttributeNames[ attrIdx ] == TEXT("uv5") + || FoundAttributeNames[ attrIdx ] == TEXT("uv6") + || FoundAttributeNames[ attrIdx ] == TEXT("uv7") + || FoundAttributeNames[ attrIdx ] == TEXT("uv8") ) + continue; + + HAPI_AttributeInfo CurrentAttrInfo = FoundAttributeInfos[ attrIdx ]; + if ( !CurrentAttrInfo.exists ) + continue; + + // Look for the next available index in the return arrays + for ( ; AvailableIdx < AttribInfoUVs.Num(); AvailableIdx++ ) + { + if ( !AttribInfoUVs[ AvailableIdx ].exists ) + break; + } + + // We are limited to MAX_STATIC_TEXCOORDS uv sets! + // If we already have too many uv sets, skip the rest + if ( ( AvailableIdx >= MAX_STATIC_TEXCOORDS ) || ( AvailableIdx >= AttribInfoUVs.Num() ) ) + { + HOUDINI_LOG_WARNING( TEXT( "Too many UV sets found. Unreal only supports %d , skipping the remaining uv sets." ), (int32)MAX_STATIC_TEXCOORDS ); + break; + } + + /* + // We need to add a new attribute info and a new float array for the texcoords + if ( AvailableIdx > AttribInfoUVs.Num() ) + { + HAPI_AttributeInfo NewAttrInfo; + FMemory::MemZero( NewAttrInfo ); + AttribInfoUVs.Add( NewAttrInfo ); + + TArray< float > NewTexCoords; + TextureCoordinates.Add( NewTexCoords ); + } + */ + + // Force the tuple size to 2 ? + CurrentAttrInfo.tupleSize = 2; + + // Add the attribute infos we found + AttribInfoUVs[ AvailableIdx ] = CurrentAttrInfo; + + // Allocate sufficient buffer for the attribute's data. + TextureCoordinates[ AvailableIdx ].SetNumUninitialized( CurrentAttrInfo.count * CurrentAttrInfo.tupleSize ); + + // Get the texture coordinates + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, + TCHAR_TO_UTF8( *( FoundAttributeNames[ attrIdx ] ) ), + &AttribInfoUVs[ AvailableIdx ], -1, + &TextureCoordinates[ AvailableIdx ][0], 0, CurrentAttrInfo.count ) ) + { + // Something went wrong when trying to access the uv values, invalidate this set + AttribInfoUVs[ AvailableIdx ].exists = false; + } + } +} + +bool +FHoudiniEngineUtils::AddConvexCollisionToAggregate( + const TArray& Positions, const TArray& SplitGroupVertexList, + const bool& MultiHullDecomp, FKAggregateGeom& AggregateCollisionGeo ) +{ +#if WITH_EDITOR + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + check( HoudiniRuntimeSettings ); + + float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION; + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + + if ( HoudiniRuntimeSettings ) + { + GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + } + + // We're only interested in the unique vertices + TArray UniqueVertexIndexes; + for ( int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++ ) + { + int32 Index = SplitGroupVertexList[ VertexIdx ]; + if ( Index < 0 || ( Index >= Positions.Num() ) ) + continue; + + UniqueVertexIndexes.AddUnique( Index ); + } + + // Extract the collision geo's vertices + TArray< FVector > VertexArray; + VertexArray.SetNum( UniqueVertexIndexes.Num() ); + for ( int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++ ) + { + int32 VertexIndex = UniqueVertexIndexes[ Idx ]; + if ( !Positions.IsValidIndex( VertexIndex * 3 + 2 ) ) + continue; + + VertexArray[ Idx ].X = Positions[ VertexIndex * 3 + 0 ] * GeneratedGeometryScaleFactor; + if ( ImportAxis == HRSAI_Unreal ) + { + VertexArray[ Idx ].Y = Positions[ VertexIndex * 3 + 2 ] * GeneratedGeometryScaleFactor; + VertexArray[ Idx ].Z = Positions[ VertexIndex * 3 + 1 ] * GeneratedGeometryScaleFactor; + } + else + { + VertexArray[ Idx ].Y = Positions[ VertexIndex * 3 + 1 ] * GeneratedGeometryScaleFactor; + VertexArray[ Idx ].Z = Positions[ VertexIndex * 3 + 2 ] * GeneratedGeometryScaleFactor; + } + } + + if ( MultiHullDecomp && ( VertexArray.Num() >= 3 || UniqueVertexIndexes.Num() >= 3 ) ) + { + // creating multiple convex hull collision + // ... this might take a while + + // We're only interested in the valid indices! + TArray Indices; + for ( int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++ ) + { + int32 Index = SplitGroupVertexList[ VertexIdx ]; + if ( Index < 0 || ( Index >= Positions.Num() ) ) + continue; + + Indices.Add( Index ); + } + + // But we need all the positions as vertex + TArray< FVector > Vertices; + Vertices.SetNum(Positions.Num() / 3); + for ( int32 Idx = 0; Idx < Vertices.Num(); Idx++ ) + { + Vertices[ Idx ].X = Positions[ Idx * 3 + 0 ] * GeneratedGeometryScaleFactor; + if ( ImportAxis == HRSAI_Unreal ) + { + Vertices[ Idx ].Y = Positions[ Idx * 3 + 2 ] * GeneratedGeometryScaleFactor; + Vertices[ Idx ].Z = Positions[ Idx * 3 + 1 ] * GeneratedGeometryScaleFactor; + } + else + { + Vertices[ Idx ].Y = Positions[ Idx * 3 + 1 ] * GeneratedGeometryScaleFactor; + Vertices[ Idx ].Z = Positions[ Idx * 3 + 2 ] * GeneratedGeometryScaleFactor; + } + } + + // We are using Unreal's DecomposeMeshToHulls() so we have to create a fake BodySetup + UBodySetup* BodySetup = NewObject(); + + // Run actual util to do the work (if we have some valid input) + DecomposeMeshToHulls( BodySetup, Vertices, Indices, 8, 16 ); + + // If we succeed, return here + // If not, keep going and we'll try to do a single hull decomposition + if ( BodySetup->AggGeom.ConvexElems.Num() > 0 ) + { + // Copy the convex elem to our aggregate + for ( int32 n = 0; n < BodySetup->AggGeom.ConvexElems.Num(); n++ ) + AggregateCollisionGeo.ConvexElems.Add( BodySetup->AggGeom.ConvexElems[ n ] ); + + return true; + } + } + + // Creating a single Convex collision + FKConvexElem ConvexCollision; + ConvexCollision.VertexData = VertexArray; + ConvexCollision.UpdateElemBox(); + + AggregateCollisionGeo.ConvexElems.Add( ConvexCollision ); +#endif + return true; +} + +bool +FHoudiniEngineUtils::AddSimpleCollision( + const FString& SplitGroupName, UStaticMesh* StaticMesh, + FHoudiniGeoPartObject& HoudiniGeoPartObject, FKAggregateGeom& AggregateCollisionGeo, bool& bSimpleCollisionAddedToAggregate ) + +{ + if ( !StaticMesh || StaticMesh->IsPendingKill() ) + return false; + +#if WITH_EDITOR + int32 PrimIndex = INDEX_NONE; + if ( SplitGroupName.Contains( "Box" ) ) + { + PrimIndex = GenerateBoxAsSimpleCollision( StaticMesh ); + if ( !HoudiniGeoPartObject.bIsRenderCollidable ) + { + // If this part is not renderCollidable, we want to extract the Box collider + // and add it to the AggregateCollisionGeo to avoid creating extra meshes + UBodySetup* bs = StaticMesh->BodySetup; + if (bs && !bs->IsPendingKill() && bs->AggGeom.BoxElems.IsValidIndex( PrimIndex ) ) + { + AggregateCollisionGeo.BoxElems.Add( bs->AggGeom.BoxElems[ PrimIndex ] ); + bSimpleCollisionAddedToAggregate = true; + } + } + } + else if ( SplitGroupName.Contains( "Sphere" ) ) + { + PrimIndex = GenerateSphereAsSimpleCollision( StaticMesh ); + if (!HoudiniGeoPartObject.bIsRenderCollidable) + { + // If this part is not renderCollidable, we want to extract the Sphere collider + // and add it to the AggregateCollisionGeo to avoid creating extra meshes + UBodySetup* bs = StaticMesh->BodySetup; + if ( bs && !bs->IsPendingKill() && bs->AggGeom.SphereElems.IsValidIndex( PrimIndex ) ) + { + AggregateCollisionGeo.SphereElems.Add(bs->AggGeom.SphereElems[PrimIndex]); + bSimpleCollisionAddedToAggregate = true; + } + } + } + else if ( SplitGroupName.Contains( "Capsule" ) ) + { + PrimIndex = GenerateSphylAsSimpleCollision( StaticMesh ); + if (!HoudiniGeoPartObject.bIsRenderCollidable) + { + // If this part is not renderCollidable, we want to extract the Capsule collider + // and add it to the AggregateCollisionGeo to avoid creating extra meshes + UBodySetup* bs = StaticMesh->BodySetup; + if ( bs && !bs->IsPendingKill() && bs->AggGeom.SphylElems.IsValidIndex( PrimIndex ) ) + { + AggregateCollisionGeo.SphylElems.Add(bs->AggGeom.SphylElems[PrimIndex]); + bSimpleCollisionAddedToAggregate = true; + } + } + } + else + { + // We need to see what type of collision the user wants + // by default, a kdop26 will be created + uint32 NumDirections = 26; + const FVector* Directions = KDopDir26; + + if ( SplitGroupName.Contains( "kdop10X" ) ) + { + NumDirections = 10; + Directions = KDopDir10X; + } + else if (SplitGroupName.Contains( "kdop10Y" ) ) + { + NumDirections = 10; + Directions = KDopDir10Y; + } + else if (SplitGroupName.Contains( "kdop10Z" ) ) + { + NumDirections = 10; + Directions = KDopDir10Z; + } + else if (SplitGroupName.Contains( "kdop18" ) ) + { + NumDirections = 18; + Directions = KDopDir18; + } + + // Converting the directions to a TArray + TArray DirArray; + for ( uint32 DirectionIndex = 0; DirectionIndex < NumDirections; DirectionIndex++ ) + { + DirArray.Add( Directions[DirectionIndex] ); + } + + PrimIndex = GenerateKDopAsSimpleCollision( StaticMesh, DirArray ); + if (!HoudiniGeoPartObject.bIsRenderCollidable) + { + // If this part is not renderCollidable, we want to extract the KDOP collider + // and add it to the AggregateCollisionGeo to avoid creating extra meshes + UBodySetup* bs = StaticMesh->BodySetup; + if (bs && !bs->IsPendingKill() && bs->AggGeom.ConvexElems.IsValidIndex( PrimIndex ) ) + { + AggregateCollisionGeo.ConvexElems.Add(bs->AggGeom.ConvexElems[PrimIndex]); + bSimpleCollisionAddedToAggregate = true; + } + } + } + + // We somehow couldnt create the simple collider + if ( PrimIndex == INDEX_NONE ) + { + HoudiniGeoPartObject.bIsSimpleCollisionGeo = false; + HoudiniGeoPartObject.bIsCollidable = false; + HoudiniGeoPartObject.bIsRenderCollidable = false; + + return false; + } +#endif + return true; +} + + +void +FHoudiniEngineUtils::CreateSlateNotification( const FString& NotificationString ) +{ +#if WITH_EDITOR + // Check whether we want to display Slate notifications. + bool bDisplaySlateCookingNotifications = true; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (!bDisplaySlateCookingNotifications) + return; + + static float NotificationFadeOutDuration = 2.0f; + static float NotificationExpireDuration = 2.0f; + + FText NotificationText = FText::FromString(NotificationString); + FNotificationInfo Info(NotificationText); + + Info.bFireAndForget = true; + Info.FadeOutDuration = NotificationFadeOutDuration; + Info.ExpireDuration = NotificationExpireDuration; + + TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniLogoBrush(); + if (HoudiniBrush.IsValid()) + Info.Image = HoudiniBrush.Get(); + + FSlateNotificationManager::Get().AddNotification(Info); +#endif + + return; +} + +HAPI_NodeId +FHoudiniEngineUtils::HapiGetParentNodeId( const HAPI_NodeId& NodeId ) +{ + HAPI_NodeId ParentId = -1; + if ( NodeId >= 0 ) + { + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo( FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo ) ) + ParentId = NodeInfo.parentId; + } + + return ParentId; +} + +bool +FHoudiniEngineUtils::CreateGroupOrAttributeFromTags( const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags, const bool& bCreateAttributes ) +{ + if ( Tags.Num() <= 0 ) + return false; + + HAPI_Result Result = HAPI_RESULT_FAILURE; + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetPartInfo( FHoudiniEngine::Get().GetSession(), + NodeId, PartId, &PartInfo), false); + + bool NeedToCommitGeo = false; + for ( int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) + { + FString TagString; + Tags[TagIdx].ToString(TagString); + if ( bCreateAttributes ) + { + // Create a primitive attribute for the tag + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfo ); + AttributeInfo.count = PartInfo.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; + + FString AttributeName = TEXT("unreal_tag_") + FString::FromInt(TagIdx); + AttributeName.RemoveSpacesInline(); + + Result = FHoudiniApi::AddAttribute( FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo ); + + if ( Result != HAPI_RESULT_SUCCESS ) + continue; + + TArray TagSHArray; + TArray TagStr; + TagStr.Add( FHoudiniEngineUtils::ExtractRawName(TagString) ); + Result = FHoudiniApi::SetAttributeStringData( FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo, + TagStr.GetData(), 0, AttributeInfo.count ); + + if ( HAPI_RESULT_SUCCESS == Result ) + NeedToCommitGeo = true; + } + else + { + // Create a primitive group for this tag + Result = FHoudiniApi::AddGroup( FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, + FHoudiniEngineUtils::ExtractRawName( TagString ) ); + + if ( Result != HAPI_RESULT_SUCCESS ) + continue; + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init( 1, PartInfo.faceCount ); + Result = FHoudiniApi::SetGroupMembership( FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_GROUPTYPE_PRIM, + FHoudiniEngineUtils::ExtractRawName( TagString ), + GroupArray.GetData(), 0, PartInfo.faceCount ); + + if ( HAPI_RESULT_SUCCESS == Result ) + NeedToCommitGeo = true; + } + } + + //if (NeedToCommitGeo) + // Result = FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), NodeId); + + return NeedToCommitGeo; +} + +bool +FHoudiniEngineUtils::GetUnrealTagAttributes(const FHoudiniGeoPartObject& HoudiniGeoPartObject, TArray& OutTags) +{ + FString TagAttribBase = TEXT("unreal_tag_"); + bool bAttributeFound = true; + int32 TagIdx = 0; + while (bAttributeFound) + { + FString CurrentTagAttr = TagAttribBase + FString::FromInt(TagIdx++); + bAttributeFound = HapiCheckAttributeExists(HoudiniGeoPartObject, TCHAR_TO_UTF8(*CurrentTagAttr), HAPI_ATTROWNER_PRIM); + if (!bAttributeFound) + break; + + // found the unreal_tag_X attribute, get its value and add it to the array + FString TagValue = FString(); + + // Create an AttributeInfo + { + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + TArray StringData; + if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HoudiniGeoPartObject, TCHAR_TO_UTF8(*CurrentTagAttr), AttributeInfo, StringData, 1, HAPI_ATTROWNER_PRIM) ) + { + TagValue = StringData[0]; + } + } + + FName NameTag = *TagValue; + OutTags.Add(NameTag); + } + + return true; +} + +FHoudiniCookParams::FHoudiniCookParams( class UHoudiniAsset* InHoudiniAsset ) +: HoudiniAsset( InHoudiniAsset ) +{ + PackageGUID = FGuid::NewGuid(); + TempCookFolder = LOCTEXT( "Temp", "/Game/HoudiniEngine/Temp" ); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineUtils.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineUtils.h new file mode 100644 index 00000000..9243b9b2 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineUtils.h @@ -0,0 +1,681 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniAssetInput.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniCookHandler.h" +#include "Engine/StaticMesh.h" +#include "PhysicsEngine/AggregateGeom.h" +#include "Engine/StaticMeshSocket.h" + +class UStaticMesh; +class UHoudiniAsset; +class ALandscapeProxy; +class AHoudiniAssetActor; +class USplineComponent; +class USkeletalMesh; + +struct FRawMesh; + +DECLARE_STATS_GROUP( TEXT( "HoudiniEngine" ), STATGROUP_HoudiniEngine, STATCAT_Advanced ); + +struct HOUDINIENGINERUNTIME_API UGenericAttribute +{ + FString AttributeName; + + HAPI_StorageType AttributeType; + int32 AttributeCount; + int32 AttributeTupleSize; + + TArray< double > DoubleValues; + TArray< int64 > IntValues; + TArray< FString > StringValues; + + double GetDoubleValue( int32 index = 0 ) + { + if ( ( AttributeType == HAPI_STORAGETYPE_FLOAT ) || ( AttributeType == HAPI_STORAGETYPE_FLOAT64 ) ) + { + if ( DoubleValues.IsValidIndex( index ) ) + return DoubleValues[ index ]; + } + else if ( ( AttributeType == HAPI_STORAGETYPE_INT ) || ( AttributeType == HAPI_STORAGETYPE_INT64 ) ) + { + if ( IntValues.IsValidIndex( index ) ) + return (double)IntValues[ index ]; + } + else if ( AttributeType == HAPI_STORAGETYPE_STRING ) + { + if ( StringValues.IsValidIndex( index ) ) + return FCString::Atod( *StringValues[ index ] ); + } + + return 0.0f; + } + + void GetDoubleTuple( TArray& TupleValues, int32 index = 0 ) + { + TupleValues.SetNumZeroed( AttributeTupleSize ); + + for ( int32 n = 0; n < AttributeTupleSize; n++ ) + TupleValues[ n ] = GetDoubleValue( index * AttributeTupleSize + n ); + } + + int64 GetIntValue( int32 index = 0 ) + { + if ( ( AttributeType == HAPI_STORAGETYPE_INT ) || ( AttributeType == HAPI_STORAGETYPE_INT64 ) ) + { + if ( IntValues.IsValidIndex( index ) ) + return IntValues[ index ]; + } + else if ( ( AttributeType == HAPI_STORAGETYPE_FLOAT ) || ( AttributeType == HAPI_STORAGETYPE_FLOAT64 ) ) + { + if ( DoubleValues.IsValidIndex( index ) ) + return (int64)DoubleValues[ index ]; + } + else if ( AttributeType == HAPI_STORAGETYPE_STRING ) + { + if ( StringValues.IsValidIndex( index ) ) + return FCString::Atoi64( *StringValues[ index ] ); + } + + return 0; + } + + void GetIntTuple( TArray& TupleValues, int32 index = 0 ) + { + TupleValues.SetNumZeroed( AttributeTupleSize ); + + for ( int32 n = 0; n < AttributeTupleSize; n++ ) + TupleValues[ n ] = GetIntValue( index * AttributeTupleSize + n ); + } + + FString GetStringValue( int32 index = 0 ) + { + if ( AttributeType == HAPI_STORAGETYPE_STRING ) + { + if ( StringValues.IsValidIndex( index ) ) + return StringValues[ index ]; + } + else if ( ( AttributeType == HAPI_STORAGETYPE_INT ) || ( AttributeType == HAPI_STORAGETYPE_INT64 ) ) + { + if ( IntValues.IsValidIndex( index ) ) + return FString::FromInt( (int32) IntValues[ index ] ); + } + else if ( ( AttributeType == HAPI_STORAGETYPE_FLOAT ) || ( AttributeType == HAPI_STORAGETYPE_FLOAT64 ) ) + { + if ( DoubleValues.IsValidIndex( index ) ) + return FString::SanitizeFloat( DoubleValues[ index ] ); + } + + return FString(); + } + + void GetStringTuple( TArray& TupleValues, int32 index = 0 ) + { + TupleValues.SetNumZeroed( AttributeTupleSize ); + + for ( int32 n = 0; n < AttributeTupleSize; n++ ) + TupleValues[ n ] = GetStringValue( index * AttributeTupleSize + n ); + } + + bool GetBoolValue( int32 index = 0 ) + { + if ( (AttributeType == HAPI_STORAGETYPE_FLOAT ) || ( AttributeType == HAPI_STORAGETYPE_FLOAT64 ) ) + { + if ( DoubleValues.IsValidIndex( index ) ) + return DoubleValues[ index ] == 0.0 ? false : true; + } + else if ( ( AttributeType == HAPI_STORAGETYPE_INT ) || ( AttributeType == HAPI_STORAGETYPE_INT64 ) ) + { + if ( IntValues.IsValidIndex( index ) ) + return IntValues[ index ] == 0 ? false : true; + } + else if ( AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING ) + { + if ( StringValues.IsValidIndex( index ) ) + return StringValues[ index ].Equals( TEXT( "true" ), ESearchCase::IgnoreCase ) ? true : false; + } + + return false; + } + + void GetBoolTuple( TArray& TupleValues, int32 index = 0 ) + { + TupleValues.SetNumZeroed( AttributeTupleSize ); + + for ( int32 n = 0; n < AttributeTupleSize; n++ ) + TupleValues[ n ] = GetBoolValue( index * AttributeTupleSize + n ); + } + + void* GetData() + { + if ( AttributeType == HAPI_STORAGETYPE_STRING ) + { + if ( StringValues.Num() > 0 ) + return StringValues.GetData(); + } + else if ( ( AttributeType == HAPI_STORAGETYPE_INT ) || ( AttributeType == HAPI_STORAGETYPE_INT64 ) ) + { + if ( IntValues.Num() > 0 ) + return IntValues.GetData(); + } + else if ( ( AttributeType == HAPI_STORAGETYPE_FLOAT ) || ( AttributeType == HAPI_STORAGETYPE_FLOAT64 ) ) + { + if ( DoubleValues.Num() > 0 ) + return DoubleValues.GetData(); + } + + return nullptr; + } +}; + +struct HOUDINIENGINERUNTIME_API FHoudiniEngineUtils +{ + public: + + /** Return a string description of error from a given error code. **/ + static const FString GetErrorDescription( HAPI_Result Result ); + + /** Return a string error description. **/ + static const FString GetErrorDescription(); + + /** Return a string indicating cook state. **/ + static const FString GetCookState(); + + /** Return a string representing cooking result. **/ + static const FString GetCookResult(); + + /** Return the errors, warning and message on a specified node **/ + static const FString GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId); + + /** Helper function for creating a temporary Slate notification. **/ + static void CreateSlateNotification( const FString& NotificationString ); + + /** Return true if module has been properly initialized. **/ + static bool IsInitialized(); + + /** Return type of license used. **/ + static bool GetLicenseType( FString & LicenseType ); + + /** Return true if we are running Houdini Engine Indie license. **/ + static bool IsLicenseHoudiniEngineIndie(); + + /** Return necessary buffer size to store preset information for a given asset. **/ + static bool ComputeAssetPresetBufferLength( HAPI_NodeId AssetId, int32 & OutBufferLength ); + + /** Sets preset data for a given asset. **/ + static bool SetAssetPreset( HAPI_NodeId AssetId, const TArray< char > & PresetBuffer ); + + /** Gets preset data for a given asset. **/ + static bool GetAssetPreset( HAPI_NodeId AssetId, TArray< char > & PresetBuffer ); + + /** Return true if asset is valid. **/ + static bool IsHoudiniNodeValid( const HAPI_NodeId& AssetId ); + + /** Destroy asset, returns the status. **/ + static bool DestroyHoudiniAsset( HAPI_NodeId AssetId ); + + /** HAPI : Convert Unreal string to ascii one. **/ + static void ConvertUnrealString( const FString & UnrealString, std::string & String ); + + /** HAPI : Translate HAPI transform to Unreal one. **/ + static void TranslateHapiTransform( const HAPI_Transform & HapiTransform, FTransform & UnrealTransform ); + + /** HAPI : Translate HAPI Euler transform to Unreal one. **/ + static void TranslateHapiTransform( const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform ); + + /** HAPI : Translate Unreal transform to HAPI one. **/ + static void TranslateUnrealTransform( const FTransform & UnrealTransform, HAPI_Transform & HapiTransform ); + + /** HAPI : Translate Unreal transform to HAPI Euler one. **/ + static void TranslateUnrealTransform( const FTransform & UnrealTransform, HAPI_TransformEuler & HapiTransformEuler ); + + /** HAPI : Set current HAPI time. **/ + static bool SetCurrentTime( float CurrentTime ); + + /** Return name of Houdini asset. **/ + static bool GetHoudiniAssetName( HAPI_NodeId AssetId, FString & NameString ); + + /** Construct static meshes for a given Houdini asset. **/ + static bool CreateStaticMeshesFromHoudiniAsset( + HAPI_NodeId AssetId, FHoudiniCookParams& HoudiniCookParams, + bool ForceRebuildStaticMesh, bool ForceRecookAll, + const TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesIn, + TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesOut, FTransform & ComponentTransform ); + + /** Extract position information from coords string. **/ + static void ExtractStringPositions( const FString & Positions, TArray< FVector > & OutPositions ); + + /** Create string containing positions from a given vector of positions. **/ + static void CreatePositionsString( const TArray< FVector > & Positions, FString & PositionString ); + + /** Given raw positions incoming from HAPI, convert them to Unreal's FVector and perform necessary flipping and **/ + /** scaling. **/ + static void ConvertScaleAndFlipVectorData( const TArray< float > & DataRaw, TArray< FVector > & DataOut ); + + /** Returns platform specific name of libHAPI. **/ + static FString HoudiniGetLibHAPIName(); + + /** Load libHAPI and return handle to it, also store location of loaded libHAPI in passed argument. **/ + static void* LoadLibHAPI( FString & StoredLibHAPILocation ); + + /** Helper function to count number of UV sets in raw mesh. **/ + static int32 CountUVSets( const FRawMesh & RawMesh ); + + /** Helper function to extract copied Houdini actor from clipboard. **/ + static AHoudiniAssetActor * LocateClipboardActor( const AActor* IgnoreActor, const FString & ClipboardText ); + + /** Retrieves list of asset names contained within the HDA. **/ + static bool GetAssetNames( + UHoudiniAsset * HoudiniAsset, HAPI_AssetLibraryId & AssetLibraryId, + TArray< HAPI_StringHandle > & AssetNames ); + + /** HAPI : Return true if given asset id is valid. **/ + static bool IsValidNodeId( HAPI_NodeId AssetId ); + + /** HAPI : Create curve for input. **/ + static bool HapiCreateCurveNode( HAPI_NodeId & CurveNodeId ); + + /** HAPI : Retrieve the asset node's object transform. **/ + static bool HapiGetAssetTransform( HAPI_NodeId AssetId, FTransform & InTransform ); + + /** HAPI : Retrieve Node id from given parameters. **/ + static bool HapiGetNodeId( HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, HAPI_NodeId & NodeId ); + + /** HAPI: Retrieve Path to the given Node, relative to the given Node */ + static bool HapiGetNodePath( HAPI_NodeId NodeId, HAPI_NodeId RelativeToNodeId, FString & OutPath ); + + /** HAPI : Retrieve HAPI_ObjectInfo's from given asset node id. **/ + static bool HapiGetObjectInfos( HAPI_NodeId AssetId, TArray< HAPI_ObjectInfo > & ObjectInfos ); + + /** HAPI : Retrieve object transforms from given asset node id. **/ + static bool HapiGetObjectTransforms( HAPI_NodeId AssetId, TArray< HAPI_Transform > & ObjectTransforms ); + + /** HAPI : Marshalling, extract landscape geometry and upload it. Return true on success. **/ + static bool HapiCreateInputNodeForLandscape( + const HAPI_NodeId& HostAssetId, ALandscapeProxy * LandscapeProxy, + HAPI_NodeId & ConnectedAssetId, TArray< HAPI_NodeId >& OutCreatedNodeIds, + const bool& bExportOnlySelected, const bool& bExportCurves, const bool& bExportMaterials, + const bool& bExportAsMesh, const bool& bExportLighting, const bool& bExportNormalizedUVs, + const bool& bExportTileUVs, const FBox& AssetBounds, const bool& bExportAsHeightfield, + const bool& bAutoSelectComponents ); + + /** HAPI : Marshaling, extract geometry and create input asset for it - return true on success **/ + static bool HapiCreateInputNodeForStaticMesh( + UStaticMesh * Mesh, + HAPI_NodeId & ConnectedAssetId, + TArray< HAPI_NodeId >& OutCreatedNodeIds, + class UStaticMeshComponent* StaticMeshComponent = nullptr, + const bool& ExportAllLODs = false, + const bool& ExportSockets = false ); + + /** HAPI : Marshaling, extract geometry and create input asset for it - return true on success **/ + static bool HapiCreateInputNodeForObjects( + HAPI_NodeId HostAssetId, + TArray& InputObjects, + const TArray< FTransform >& InputTransforms, + HAPI_NodeId & ConnectedAssetId, + TArray< HAPI_NodeId >& OutCreatedNodeIds, + const bool& bExportSkeleton, + const bool& ExportAllLODs = false, + const bool& ExportSockets = false ); + + /** HAPI : Marshaling, extract geometry and create input asset for it - return true on success **/ + static bool HapiCreateInputNodeForWorldOutliner( + HAPI_NodeId HostAssetId, + TArray< FHoudiniAssetInputOutlinerMesh > & OutlinerMeshArray, + HAPI_NodeId & ConnectedAssetId, + TArray< HAPI_NodeId >& OutCreatedNodeIds, + const float& SplineResolution = -1.0f, + const bool& ExportAllLODs = false, + const bool& ExportSockets = false ); + + /** HAPI : Marshaling, extract points from the Unreal Spline and create an input curve for it - return true on success **/ + static bool HapiCreateInputNodeForSpline( + HAPI_NodeId HostAssetId, + USplineComponent * SplineComponent, + HAPI_NodeId & ConnectedAssetId, + FHoudiniAssetInputOutlinerMesh& OutlinerMesh, + const float& fSplineResolution = -1.0f); + + static bool HapiCreateCurveInputNodeForData( + HAPI_NodeId HostAssetId, + HAPI_NodeId & ConnectedAssetId, + TArray* Positions, + TArray* Rotations = nullptr, + TArray* Scales3d = nullptr, + TArray* UniformScales = nullptr, + bool ForceClose = false ); + + /** HAPI : Marshaling, extract geometry and skeleton and create input asset for it - return true on success **/ + static bool HapiCreateInputNodeForSkeletalMesh( + HAPI_NodeId HostAssetId, USkeletalMesh * SkeletalMesh, + HAPI_NodeId & ConnectedAssetId, TArray< HAPI_NodeId >& OutCreatedNodeIds, + const bool& bExportSkeleton = true ); + + /** HAPI : Marshaling, extract skeleton and creates its Houdini equivalent - return true on success **/ + static bool HapiCreateSkeletonFromData( + HAPI_NodeId HostAssetId, + USkeletalMesh * SkeletalMesh, + const HAPI_NodeInfo& SkelMeshNodeInfo, + TArray< HAPI_NodeId >& OutCreatedNodeIds ); + + /** HAPI : Marshaling, disconnect input asset from a given slot. **/ + static bool HapiDisconnectAsset( HAPI_NodeId HostAssetId, int32 InputIndex ); + + /** HAPI : Set asset transform. **/ + static bool HapiSetAssetTransform( HAPI_NodeId AssetId, const FTransform & Transform ); + + /** HAPI : Return all group names for a given Geo. **/ + static bool HapiGetGroupNames( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, HAPI_PartId PartId, + HAPI_GroupType GroupType, TArray< FString > & GroupNames, const bool& isPackedPrim); + + /** HAPI : Retrieve group membership. **/ + static bool HapiGetGroupMembership( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, HAPI_PartId PartId, + HAPI_GroupType GroupType, const FString & GroupName, TArray< int32 > & GroupMembership ); + + /** HAPI : Get group count by type. **/ + static int32 HapiGetGroupCountByType( HAPI_GroupType GroupType, HAPI_GeoInfo & GeoInfo ); + + /** HAPI : Get element count by group type. **/ + static int32 HapiGetElementCountByGroupType( HAPI_GroupType GroupType, HAPI_PartInfo & PartInfo ); + + /** HAPI : Check if object geo part has group membership. **/ + static bool HapiCheckGroupMembership( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + HAPI_GroupType GroupType, const FString & GroupName ); + static bool HapiCheckGroupMembership( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, HAPI_GroupType GroupType, const FString & GroupName ); + + /** HAPI : Check if given attribute exists. **/ + static bool HapiCheckAttributeExists( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, const char * Name, HAPI_AttributeOwner Owner ); + static bool HapiCheckAttributeExists( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + const char * Name, HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID ); + static bool HapiCheckAttributeExists( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, const char * Name ); + + /** HAPI: Returns all the attributes of a given type for a given owner **/ + static int32 HapiGetAttributeOfType( + const HAPI_NodeId& AssetId, const HAPI_NodeId& ObjectId, + const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId, + const HAPI_AttributeOwner& AttributeOwner, + const HAPI_AttributeTypeInfo& AttributeType, + TArray< HAPI_AttributeInfo >& MatchingAttributesInfo, + TArray< FString >& MatchingAttributesName ); + + /** HAPI : Get attribute data as float. **/ + static bool HapiGetAttributeDataAsFloat( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, const char * Name, HAPI_AttributeInfo & ResultAttributeInfo, TArray< float > & Data, + int32 TupleSize = 0, HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID ); + + static bool HapiGetAttributeDataAsFloat( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, const char * Name, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< float > & Data, int32 TupleSize = 0, HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID ); + + /** HAPI : Get attribute data as integer. **/ + static bool HapiGetAttributeDataAsInteger( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, const char* Name, HAPI_AttributeInfo & ResultAttributeInfo, TArray< int32 > & Data, + int32 TupleSize = 0, HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID ); + + static bool HapiGetAttributeDataAsInteger( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, const char * Name, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< int32 > & Data, int32 TupleSize = 0, HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID ); + + /** HAPI : Get attribute data as string. **/ + static bool HapiGetAttributeDataAsString( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, const char * Name, HAPI_AttributeInfo & ResultAttributeInfo, TArray< FString > & Data, + int32 TupleSize = 0, HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID ); + + static bool HapiGetAttributeDataAsString( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, const char * Name, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< FString > & Data, int32 TupleSize = 0, HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID ); + + /** HAPI : Get parameter data as float. **/ + static bool HapiGetParameterDataAsFloat( + HAPI_NodeId NodeId, const std::string ParmName, float DefaultValue, float & Value ); + + /** HAPI : Get parameter data as integer. **/ + static bool HapiGetParameterDataAsInteger( + HAPI_NodeId NodeId, const std::string ParmName, int32 DefaultValue, int32 & Value ); + + /** HAPI : Get parameter data as string. **/ + static bool HapiGetParameterDataAsString( + HAPI_NodeId NodeId, const std::string ParmName, + const FString & DefaultValue, FString & Value); + + /** HAPI: Get a parameter's unit. **/ + static bool HapiGetParameterUnit( const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, FString& OutUnitString ); + + /** HAPI: Get a parameter's tag, return false if the tag doesnt exist**/ + static bool HapiGetParameterTag(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag, FString& TagValue); + + /** HAPI: Get a parameter's noswap tag value. **/ + static bool HapiGetParameterNoSwapTag( const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, bool& NoSwapValue ); + + /** HAPI : Retrieve names of all parameters. **/ + static void HapiRetrieveParameterNames( const TArray< HAPI_ParmInfo > & ParmInfos, TArray< FString > & Names ); + static void HapiRetrieveParameterNames( const TArray< HAPI_ParmInfo > & ParmInfos, TArray< std::string > & Names ); + + /** HAPI : Look for a parameter by name or tag and returns its index. Returns -1 if not found. **/ + static HAPI_ParmId HapiFindParameterByNameOrTag( const HAPI_NodeId& NodeId, const std::string ParmName ); + static HAPI_ParmId HapiFindParameterByNameOrTag( const HAPI_NodeId& NodeId, const std::string ParmName, HAPI_ParmInfo& FoundParmInfo ); + + /** HAPI : Return a give node's parent ID, -1 if none **/ + static HAPI_NodeId HapiGetParentNodeId( const HAPI_NodeId& NodeId ); +#if WITH_EDITOR + + /** Helper routine to check invalid lightmap faces. **/ + static bool ContainsInvalidLightmapFaces( const FRawMesh & RawMesh, int32 LightmapSourceIdx ); + +#endif // WITH_EDITOR + + /** HAPI : Retrieve instance transforms for a specified geo object. **/ + static bool HapiGetInstanceTransforms( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, TArray< FTransform > & Transforms ); + + static bool HapiGetInstanceTransforms( + const FHoudiniGeoPartObject & HoudiniGeoPartObject, + TArray< FTransform > & Transforms ); + + /** HAPI : Given vertex list, retrieve new vertex list for a specified group. **/ + /** Return number of processed valid index vertices for this split. **/ + static int32 HapiGetVertexListForGroup( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, + HAPI_PartId PartId, const FString & GroupName, const TArray< int32 > & FullVertexList, + TArray< int32 > & NewVertexList, TArray< int32 > & AllVertexList, TArray< int32 > & AllFaceList, + TArray< int32 > & AllCollisionFaceIndices, const bool& isPackedPrim ); + + /** HAPI : Retrieves the mesh sockets list for the current part **/ + static int32 AddMeshSocketToList( + HAPI_NodeId AssetId, HAPI_NodeId ObjectId, + HAPI_NodeId GeoId, HAPI_PartId PartId, + TArray< FTransform >& AllSockets, + TArray< FString >& AllSocketsName, + TArray< FString >& AllSocketsActors, + TArray< FString >& AllSocketsTags, + const bool& isPackedPrim ); + + /** Add the mesh sockets in the list to the specified StaticMesh **/ + static bool AddMeshSocketsToStaticMesh( + UStaticMesh* StaticMesh, + FHoudiniGeoPartObject& HoudiniGeoPartObject, + TArray< FTransform >& AllSockets, + TArray< FString >& AllSocketsNames, + TArray< FString >& AllSocketsActors, + TArray< FString >& AllSocketsTags ); + + /** Add the actor stored in the socket tag to the socket for the given static mesh component **/ + static bool AddActorsToMeshSocket( UStaticMeshSocket* Socket, class UStaticMeshComponent* StaticMeshComponent ); + + /** Add the mesh aggregate collision geo to the specified StaticMesh **/ + static bool AddAggregateCollisionGeometryToStaticMesh( + UStaticMesh* StaticMesh, + FHoudiniGeoPartObject& HoudiniGeoPartObject, + FKAggregateGeom& AggregateCollisionGeo ); + + /** Add convex hull to the mesh's aggregate collision geometry **/ + static bool AddConvexCollisionToAggregate( + const TArray& Positions, const TArray& SplitGroupVertexList, + const bool& MultiHullDecomp, FKAggregateGeom& AggregateCollisionGeo ); + + /** Add convex hull to the mesh's aggregate collision geometry **/ + static bool AddSimpleCollision( + const FString& SplitGroupName, UStaticMesh* StaticMesh, + FHoudiniGeoPartObject& HoudiniGeoPartObject, FKAggregateGeom& AggregateCollisionGeo, + bool& bSimpleCollisionAddedToAggregate ); + + /** Updates all Uproperty attributes found on the given object **/ + static void UpdateUPropertyAttributesOnObject( + UObject* MeshComponent, const FHoudiniGeoPartObject& HoudiniGeoPartObject ); + + /** Return a list with all the UProperty attributes found **/ + static int32 GetUPropertyAttributeList( + const FHoudiniGeoPartObject& GeoPartObject, + TArray< UGenericAttribute >& AllUProps ); + + /** Return a list of all the generic attributes for a given attribute owner **/ + static int32 GetGenericAttributeList( + const FHoudiniGeoPartObject& GeoPartObject, + const FString& GenericAttributePrefix, + TArray< UGenericAttribute >& AllUProps, + const HAPI_AttributeOwner& AttributeOwner, + int32 PrimitiveIndex = -1); + + /** Tries to find a Uproperty by name/label on an object + FoundPropertyObject will be the object containing the uprop which may or not be ParentObject **/ + static bool FindUPropertyAttributesOnObject( + UObject* ParentObject, const UGenericAttribute& UPropertiesToFind, + FProperty*& FoundProperty, UObject*& FoundPropertyObject, void*& StructContainer ); + + /** Modifies the value of a UProperty **/ + static bool ModifyUPropertyValueOnObject( + UObject* MeshComponent, UGenericAttribute UPropertiesToFind, + FProperty* FoundProperty, void * StructContainer ); + + /** Tries to update values for all the UProperty attributes to apply on the object. **/ + /*static void ApplyUPropertyAttributesOnObject( + UObject* MeshComponent, const TArray< UGenericAttribute >& UPropertiesToModify );*/ + + //static bool TryToFindInStructProperty( UObject* Object, FString UPropertyNameToFind, FStructProperty* ArrayProperty, FProperty*& FoundProperty, void*& StructContainer ); + + //static bool TryToFindInArrayProperty( UObject* Object, FString UPropertyNameToFind, FArrayProperty* ArrayProperty, FProperty*& FoundProperty, void*& StructContainer ); + + /** Helper function to extract a raw name from a given Fstring. Caller is responsible for clean up. **/ + static char * ExtractRawName(const FString & Name); + + /** Helper function for retrieving attribute infos and texture cooordinates of both "regular" uv attributes (uv1..uv7) and 16.5 uvs (texture type attributes) **/ + static void GetAllUVAttributesInfoAndTexCoords( + const HAPI_NodeId& AssetId, const HAPI_NodeId& ObjectId, + const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId, + TArray< HAPI_AttributeInfo >& AttribInfoUVs, + TArray< TArray< float > >& TextureCoordinates ); + + /** Helper function that will create groups or attribute for the given tag array **/ + static bool CreateGroupOrAttributeFromTags( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, + const TArray& Tags, const bool& bCreateAttributes = false ); + + // Helper function that extracts the unreal_tag_XX primitive attributes + static bool GetUnrealTagAttributes( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, TArray& OutTags); + + protected: + +#if PLATFORM_WINDOWS + /** Attempt to locate libHAPI on Windows in the registry. Return handle if located and return location. **/ + static void* LocateLibHAPIInRegistry( + const FString & HoudiniInstallationType, FString & StoredLibHAPILocation, bool LookIn32bitRegistry); +#endif + public: + + /** Helper function to extract colors and store them in a given RawMesh. Returns number of wedges. **/ + static int32 TransferRegularPointAttributesToVertices( + const TArray< int32 > & VertexList, const HAPI_AttributeInfo & AttribInfo, TArray< float > & Data ); + + /** Helper function to extract colors and store them in a given RawMesh. Returns number of wedges. **/ + static int32 TransferRegularPointAttributesToVertices( + const TArray< int32 > & VertexList, const HAPI_AttributeInfo & AttribInfo, + const TArray< float > & Data, TArray< float >& VertexData ); + +#if WITH_EDITOR + + /** Helper routine to check if Raw Mesh contains degenerate triangles. **/ + static bool ContainsDegenerateTriangles( const FRawMesh & RawMesh ); + + /** Helper routine to count number of degenerate triangles. **/ + static int32 CountDegenerateTriangles( const FRawMesh & RawMesh ); + + /** Create helper array of material names, we use it for marshalling. **/ + static void CreateFaceMaterialArray( + const TArray< UMaterialInterface * >& Materials, + const TArray< int32 > & FaceMaterialIndices, + TArray< char * > & OutStaticMeshFaceMaterials ); + + /** Delete helper array of material names. **/ + static void DeleteFaceMaterialArray( TArray< char * > & OutStaticMeshFaceMaterials ); + +#endif // WITH_EDITOR + + /** Return a specified HAPI status string. **/ + static const FString GetStatusString( HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity ); + + /** Extract all unique material ids for all geo object parts. **/ + static bool ExtractUniqueMaterialIds( + const HAPI_AssetInfo & AssetInfo, TSet< HAPI_NodeId > & MaterialIds, + TSet< HAPI_NodeId > & InstancerMaterialIds, + TMap< FHoudiniGeoPartObject, HAPI_NodeId > & InstancerMaterialMap ); + + /** Pick vertex color from texture mip level. **/ + static FColor PickVertexColorFromTextureMip( + const uint8 * MipBytes, FVector2D & UVCoord, int32 MipWidth, int32 MipHeight ); + + protected: + static FString ComputeVersionString(bool ExtraDigit); +#if WITH_EDITOR + + /** Reset streams used by the given RawMesh. **/ + static void ResetRawMesh( FRawMesh & RawMesh ); + +#endif // WITH_EDITOR + + public: + + /** How many GUID symbols are used for package component name generation. **/ + static const int32 PackageGUIDComponentNameLength; + + /** How many GUID symbols are used for package item name generation. **/ + static const int32 PackageGUIDItemNameLength; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp new file mode 100644 index 00000000..29792006 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp @@ -0,0 +1,2190 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniGeoPartObject.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngine.h" +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniEngineString.h" + +uint32 +GetTypeHash( const FHoudiniGeoPartObject & HoudiniGeoPartObject ) +{ + return HoudiniGeoPartObject.GetTypeHash(); +} + +FArchive & +operator<<( FArchive & Ar, FHoudiniGeoPartObject & HoudiniGeoPartObject ) +{ + HoudiniGeoPartObject.Serialize( Ar ); + return Ar; +} + +bool +FHoudiniGeoPartObjectSortPredicate::operator()( const FHoudiniGeoPartObject & A, const FHoudiniGeoPartObject & B ) const +{ + if ( !A.IsValid() || !B.IsValid() ) + return false; + + if ( A.ObjectId == B.ObjectId ) + { + if ( A.GeoId == B.GeoId ) + { + if ( A.PartId == B.PartId ) + return A.SplitId < B.SplitId; + else + return A.PartId < B.PartId; + } + else + { + return A.GeoId < B.GeoId; + } + } + + return A.ObjectId < B.ObjectId; +} + +FHoudiniGeoPartObject::FHoudiniGeoPartObject() + : TransformMatrix( FMatrix::Identity ) + , ObjectName( TEXT("Empty" ) ) + , PartName( TEXT( "Empty" ) ) + , SplitName( TEXT( "" ) ) + , InstancerMaterialName( TEXT( "" ) ) + , InstancerAttributeMaterialName( TEXT( "" ) ) + , AssetId( -1 ) + , ObjectId( -1 ) + , GeoId( -1 ) + , PartId( -1 ) + , SplitId( 0 ) + , bIsVisible( true ) + , bIsInstancer( false ) + , bIsCurve( false ) + , bIsEditable( false ) + , bHasGeoChanged( false ) + , bIsCollidable( false ) + , bIsRenderCollidable( false ) + , bIsLoaded( false ) + , bPlaceHolderFlags( 0 ) + , bIsTransacting( false ) + , bHasCustomName( false ) + , bIsBox( false ) + , bIsSphere( false ) + , bInstancerMaterialAvailable( false ) + , bIsVolume( false ) + , bInstancerAttributeMaterialAvailable( false ) + , bIsPackedPrimitiveInstancer( false ) + , bIsUCXCollisionGeo( false ) + , bIsSimpleCollisionGeo( false ) + , bHasCollisionBeenAdded( false ) + , bHasSocketBeenAdded( false ) + , UnusedFlagsSpace( 0u ) + , HoudiniGeoPartObjectVersion( VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE ) +{} + +FHoudiniGeoPartObject::FHoudiniGeoPartObject( + HAPI_NodeId InAssetId, HAPI_NodeId InObjectId, HAPI_NodeId InGeoId, HAPI_PartId InPartId ) + : TransformMatrix( FMatrix::Identity ) + , ObjectName( TEXT( "Empty" ) ) + , PartName( TEXT( "Empty" ) ) + , SplitName( TEXT( "" ) ) + , InstancerMaterialName( TEXT( "" ) ) + , AssetId( InAssetId ) + , ObjectId( InObjectId ) + , GeoId( InGeoId ) + , PartId( InPartId ) + , SplitId( 0 ) + , bIsVisible( true ) + , bIsInstancer( false ) + , bIsCurve( false ) + , bIsEditable( false ) + , bHasGeoChanged( false ) + , bIsCollidable( false ) + , bIsRenderCollidable( false ) + , bIsLoaded( false ) + , bPlaceHolderFlags( 0 ) + , bIsTransacting( false ) + , bHasCustomName( false ) + , bIsBox( false ) + , bIsSphere( false ) + , bInstancerMaterialAvailable( false ) + , bIsVolume( false ) + , bInstancerAttributeMaterialAvailable( false ) + , bIsPackedPrimitiveInstancer( false ) + , bIsUCXCollisionGeo( false ) + , bIsSimpleCollisionGeo( false ) + , bHasCollisionBeenAdded( false ) + , bHasSocketBeenAdded( false ) + , UnusedFlagsSpace( 0u ) + , HoudiniGeoPartObjectVersion( VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE ) +{} + +FHoudiniGeoPartObject::FHoudiniGeoPartObject( + const FTransform & InTransform, HAPI_NodeId InAssetId, + const HAPI_ObjectInfo & ObjectInfo, const HAPI_GeoInfo & GeoInfo, const HAPI_PartInfo & PartInfo ) + : TransformMatrix( InTransform ) + , ObjectName( TEXT( "Empty" ) ) + , PartName( TEXT( "Empty" ) ) + , SplitName( TEXT( "" ) ) + , InstancerMaterialName( TEXT( "" ) ) + , AssetId( InAssetId ) + , ObjectId( ObjectInfo.nodeId ) + , GeoId( GeoInfo.nodeId ) + , PartId( PartInfo.id ) + , SplitId( 0 ) + , bIsVisible( ObjectInfo.isVisible ) + , bIsInstancer( ObjectInfo.isInstancer ) + , bIsCurve( PartInfo.type == HAPI_PARTTYPE_CURVE ) + , bIsEditable( GeoInfo.isEditable ) + , bHasGeoChanged( GeoInfo.hasGeoChanged ) + , bIsCollidable( false ) + , bIsRenderCollidable( false ) + , bIsLoaded( false ) + , bPlaceHolderFlags( 0 ) + , bIsTransacting( false ) + , bHasCustomName( false ) + , bIsBox( PartInfo.type == HAPI_PARTTYPE_BOX ) + , bIsSphere( PartInfo.type == HAPI_PARTTYPE_SPHERE ) + , bInstancerMaterialAvailable( false ) + , bIsVolume( PartInfo.type == HAPI_PARTTYPE_VOLUME ) + , bInstancerAttributeMaterialAvailable( false ) + , bIsPackedPrimitiveInstancer( PartInfo.type == HAPI_PARTTYPE_INSTANCER ) + , bIsUCXCollisionGeo( false ) + , bIsSimpleCollisionGeo( false ) + , bHasCollisionBeenAdded( false ) + , bHasSocketBeenAdded( false ) + , UnusedFlagsSpace( 0u ) + , HoudiniGeoPartObjectVersion( VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE ) +{} + +FHoudiniGeoPartObject::FHoudiniGeoPartObject( + const FTransform & InTransform, const FString & InObjectName, + const FString & InPartName, HAPI_NodeId InAssetId, + HAPI_NodeId InObjectId, HAPI_NodeId InGeoId, + HAPI_PartId InPartId ) + : TransformMatrix( InTransform ) + , ObjectName( InObjectName ) + , PartName( InPartName ) + , SplitName( TEXT( "" ) ) + , InstancerMaterialName( TEXT( "" ) ) + , AssetId( InAssetId ) + , ObjectId( InObjectId ) + , GeoId( InGeoId ) + , PartId( InPartId ) + , SplitId( 0 ) + , bIsVisible( true ) + , bIsInstancer( false ) + , bIsCurve( false ) + , bIsEditable( false ) + , bHasGeoChanged( false ) + , bIsCollidable( false ) + , bIsRenderCollidable( false ) + , bIsLoaded( false ) + , bPlaceHolderFlags( 0 ) + , bIsTransacting( false ) + , bHasCustomName( false ) + , bIsBox( false ) + , bIsSphere( false ) + , bInstancerMaterialAvailable( false ) + , bIsVolume( false ) + , bInstancerAttributeMaterialAvailable( false ) + , bIsPackedPrimitiveInstancer( false ) + , bIsUCXCollisionGeo( false ) + , bIsSimpleCollisionGeo( false ) + , bHasCollisionBeenAdded( false ) + , bHasSocketBeenAdded( false ) + , UnusedFlagsSpace( 0u ) + , HoudiniGeoPartObjectVersion( VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE ) +{} + +FHoudiniGeoPartObject::FHoudiniGeoPartObject( const FHoudiniGeoPartObject & GeoPartObject, bool bCopyLoaded ) + : TransformMatrix( GeoPartObject.TransformMatrix ) + , ObjectName( GeoPartObject.ObjectName ) + , PartName( GeoPartObject.PartName ) + , SplitName( GeoPartObject.SplitName ) + , InstancerMaterialName( GeoPartObject.InstancerMaterialName ) + , AssetId( GeoPartObject.AssetId ) + , ObjectId( GeoPartObject.ObjectId ) + , GeoId( GeoPartObject.GeoId ) + , PartId( GeoPartObject.PartId ) + , SplitId( GeoPartObject.SplitId ) + , bIsVisible( GeoPartObject.bIsVisible ) + , bIsInstancer( GeoPartObject.bIsInstancer ) + , bIsCurve( GeoPartObject.bIsCurve ) + , bIsEditable( GeoPartObject.bIsEditable ) + , bHasGeoChanged( GeoPartObject.bHasGeoChanged ) + , bIsCollidable( GeoPartObject.bIsCollidable ) + , bIsRenderCollidable( GeoPartObject.bIsRenderCollidable ) + , bIsLoaded( GeoPartObject.bIsLoaded ) + , bPlaceHolderFlags( GeoPartObject.bPlaceHolderFlags ) + , bIsTransacting( GeoPartObject.bIsTransacting ) + , bHasCustomName( GeoPartObject.bHasCustomName ) + , bIsBox( GeoPartObject.bIsBox ) + , bIsSphere( GeoPartObject.bIsSphere ) + , bInstancerMaterialAvailable( GeoPartObject.bInstancerMaterialAvailable ) + , bIsVolume( GeoPartObject.bIsVolume ) + , bInstancerAttributeMaterialAvailable( GeoPartObject.bInstancerAttributeMaterialAvailable ) + , bIsPackedPrimitiveInstancer( GeoPartObject.bIsPackedPrimitiveInstancer ) + , bIsUCXCollisionGeo( GeoPartObject.bIsUCXCollisionGeo ) + , bIsSimpleCollisionGeo( GeoPartObject.bIsSimpleCollisionGeo ) + , bHasCollisionBeenAdded( GeoPartObject.bHasCollisionBeenAdded ) + , bHasSocketBeenAdded( GeoPartObject.bHasSocketBeenAdded ) + , UnusedFlagsSpace( 0u ) + , HoudiniGeoPartObjectVersion( VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE ) +{ + if ( bCopyLoaded ) + bIsLoaded = true; +} + +bool +FHoudiniGeoPartObject::IsVisible() const +{ + return bIsVisible; +} + +bool +FHoudiniGeoPartObject::IsInstancer() const +{ + return bIsInstancer; +} + +bool +FHoudiniGeoPartObject::IsCurve() const +{ + return bIsCurve; +} + +bool +FHoudiniGeoPartObject::IsBox() const +{ + return bIsBox; +} + +bool +FHoudiniGeoPartObject::IsSphere() const +{ + return bIsSphere; +} + +bool +FHoudiniGeoPartObject::IsVolume() const +{ + return bIsVolume; +} + +bool +FHoudiniGeoPartObject::IsEditable() const +{ + return bIsEditable; +} + +bool +FHoudiniGeoPartObject::HasGeoChanged() const +{ + return bHasGeoChanged; +} + +bool +FHoudiniGeoPartObject::IsCollidable() const +{ + return bIsCollidable; +} + +bool +FHoudiniGeoPartObject::IsRenderCollidable() const +{ + return bIsRenderCollidable; +} + +HAPI_NodeId +FHoudiniGeoPartObject::GetObjectId() const +{ + return ObjectId; +} + +HAPI_NodeId +FHoudiniGeoPartObject::GetGeoId() const +{ + return GeoId; +} + +HAPI_PartId +FHoudiniGeoPartObject::GetPartId() const +{ + return PartId; +} + +const FString& +FHoudiniGeoPartObject::GetNodePath() const +{ + // query on first-use + if ( NodePath.IsEmpty() ) + { + FString NodePathTemp; + if ( AssetId == GeoId ) + { + // This is a SOP asset, just return the asset name in this case + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + if ( FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ) == HAPI_RESULT_SUCCESS ) + { + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + if ( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo ) == HAPI_RESULT_SUCCESS ) + { + FHoudiniEngineString AssetPathHEString( AssetNodeInfo.nameSH ); + if ( AssetPathHEString.ToFString( NodePathTemp ) ) + { + NodePath = FString::Printf( TEXT( "%s_%d" ), *NodePathTemp, PartId ); + } + } + } + } + else + { + // This is an OBJ asset, return the path to this geo relative to the asset + if ( FHoudiniEngineUtils::HapiGetNodePath( GeoId, AssetId, NodePathTemp ) ) + { + NodePath = FString::Printf( TEXT( "%s_%d" ), *NodePathTemp, PartId ); + } + } + + if ( NodePath.IsEmpty() ) + { + NodePath = TEXT( "Empty" ); + } + } + return NodePath; +} + +bool +FHoudiniGeoPartObject::operator==( const FHoudiniGeoPartObject & GeoPartObject ) const +{ + // Object/Geo/Part IDs must match + if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) + return false; + + // If split ID and name match, we're equal... + if (SplitId == GeoPartObject.SplitId && SplitName == GeoPartObject.SplitName ) + return true; + + // ... if not we should compare our names + return CompareNames(GeoPartObject); +} + +uint32 +FHoudiniGeoPartObject::GetTypeHash() const +{ + int32 HashBuffer[ 4 ] = { ObjectId, GeoId, PartId, SplitId }; + int32 Hash = FCrc::MemCrc32( (void *) &HashBuffer[ 0 ], sizeof( HashBuffer ) ); + return FCrc::StrCrc32(*SplitName, Hash); +} + +void +FHoudiniGeoPartObject::Serialize( FArchive & Ar ) +{ + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + HoudiniGeoPartObjectVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniGeoPartObjectVersion; + + Ar << TransformMatrix; + + Ar << ObjectName; + Ar << PartName; + Ar << SplitName; + + // Serialize instancer material. + if ( HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME ) + Ar << InstancerMaterialName; + + // Serialize instancer attribute material. + if ( HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME ) + Ar << InstancerAttributeMaterialName; + + Ar << AssetId; + Ar << ObjectId; + Ar << GeoId; + + // Node id values can change between saving and loading a level + if ( Ar.IsLoading() && !Ar.IsTransacting() ) + { + AssetId = -1; + // Although Object and Geo ids are invalid, we don't -1 them so that our parts can + // still be hashed into a map on the component. This will be dropped after the next cook + // anyway. + } + + Ar << PartId; + Ar << SplitId; + + Ar << HoudiniGeoPartObjectFlagsPacked; + + if ( HoudiniGeoPartObjectVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE ) + { + // Prior to this version the unused flags space was not zero-initialized, so + // zero them out now to prevent misinterpreting any 1s + HoudiniGeoPartObjectFlagsPacked &= 0x3FFFF; + } + + if ( HoudiniGeoPartObjectVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH ) + { + if ( Ar.IsSaving() && !Ar.IsTransacting() ) + { + // If we are saving to level - make sure node path is present + (void) GetNodePath(); + Ar << NodePath; + } + else + { + Ar << NodePath; + } + } + + if ( Ar.IsLoading() ) + bIsLoaded = true; + + if ( Ar.IsTransacting() ) + bIsTransacting = true; +} + +bool +FHoudiniGeoPartObject::IsValid() const +{ + return ( ObjectId >= 0 && GeoId >= 0 && PartId >= 0 ); +} + +bool +FHoudiniGeoPartObject::CompareNames( const FHoudiniGeoPartObject & HoudiniGeoPartObject ) const +{ + // Object and part names must match + if (ObjectName != HoudiniGeoPartObject.ObjectName || PartName != HoudiniGeoPartObject.PartName) + return false; + + // Split names must match... + if (SplitName == HoudiniGeoPartObject.SplitName) + return true; + + //... or we can tolerate them to be different if they both start with LOD + // and just the lod number is different + if (SplitName.StartsWith("LOD") && HoudiniGeoPartObject.SplitName.StartsWith("LOD")) + return true; + + return false; +} + +bool +FHoudiniGeoPartObject::HasParameters() const +{ + return HasParameters(AssetId); +} + +bool +FHoudiniGeoPartObject::HasParameters( HAPI_NodeId InAssetId ) const +{ + HAPI_NodeId NodeId = HapiGeoGetNodeId( InAssetId ); + if ( NodeId == -1 ) + return false; + + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + FHoudiniApi::GetNodeInfo( FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo ); + + return NodeInfo.parmCount > 0; +} + +bool FHoudiniGeoPartObject::IsPackedPrimitiveInstancer() const +{ + return bIsPackedPrimitiveInstancer; +} + +bool FHoudiniGeoPartObject::IsAttributeInstancer() const +{ + return HapiCheckAttributeExistance( HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT ); +} + +bool FHoudiniGeoPartObject::IsAttributeOverrideInstancer() const +{ + // Check if this is an attribute override instancer (on detail or point). + return HapiCheckAttributeExistance( HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL) + | HapiCheckAttributeExistance( HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT ); +} + +bool +FHoudiniGeoPartObject::HasCustomName() const +{ + return bHasCustomName; +} + +void +FHoudiniGeoPartObject::SetCustomName( const FString & CustomName ) +{ + PartName = CustomName; + bHasCustomName = true; +} + +bool +FHoudiniGeoPartObject::UpdateCustomName() +{ + TArray< FString > GeneratedMeshNames; + HAPI_AttributeInfo AttribGeneratedMeshName; + FHoudiniApi::AttributeInfo_Init(&AttribGeneratedMeshName); + //FMemory::Memzero< HAPI_AttributeInfo >(AttribGeneratedMeshName); + + bHasCustomName = false; + if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString( + AssetId, ObjectId, GeoId, PartId, HAPI_UNREAL_ATTRIB_GENERATED_MESH_NAME, AttribGeneratedMeshName, GeneratedMeshNames ) ) + { + if ( GeneratedMeshNames.Num() > 0 ) + SetCustomName( GeneratedMeshNames[0] ); + } + + return HasCustomName(); +} + +bool +FHoudiniGeoPartObject::HapiCheckAttributeExistance( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeOwner AttributeOwner ) const +{ + std::string AttributeNameRaw = ""; + FHoudiniEngineUtils::ConvertUnrealString( AttributeName, AttributeNameRaw ); + + return HapiCheckAttributeExistance( OtherAssetId, AttributeNameRaw, AttributeOwner ); +} + +bool +FHoudiniGeoPartObject::HapiCheckAttributeExistance( + const FString& AttributeName, HAPI_AttributeOwner AttributeOwner ) const +{ + return HapiCheckAttributeExistance( AssetId, AttributeName, AttributeOwner ); +} + +bool +FHoudiniGeoPartObject::HapiCheckAttributeExistance( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeOwner AttributeOwner ) const +{ + return HapiCheckAttributeExistance( AttributeName.c_str(), AttributeOwner ); +} + +bool +FHoudiniGeoPartObject::HapiCheckAttributeExistance( + const std::string & AttributeName, HAPI_AttributeOwner AttributeOwner ) const +{ + return HapiCheckAttributeExistance( AssetId, AttributeName, AttributeOwner ); +} + +bool +FHoudiniGeoPartObject::HapiCheckAttributeExistance( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeOwner AttributeOwner ) const +{ + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memset< HAPI_AttributeInfo >( AttributeInfo, 0 ); + + if ( FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, AttributeName, + AttributeOwner, &AttributeInfo ) == HAPI_RESULT_SUCCESS ) + { + return AttributeInfo.exists; + } + + return false; +} + +bool +FHoudiniGeoPartObject::HapiCheckAttributeExistance( + const char * AttributeName, HAPI_AttributeOwner AttributeOwner ) const +{ + return HapiCheckAttributeExistance( AssetId, AttributeName, AttributeOwner ); +} + +bool +FHoudiniGeoPartObject::HapiGetInstanceTransforms( HAPI_NodeId OtherAssetId, TArray< FTransform > & AllTransforms ) const +{ + AllTransforms.Empty(); + int32 PointCount = HapiPartGetPointCount( OtherAssetId ); + + if ( PointCount > 0 ) + { + TArray< HAPI_Transform > InstanceTransforms; + InstanceTransforms.SetNumUninitialized( PointCount ); + for (int32 Idx = 0; Idx < InstanceTransforms.Num(); Idx++) + FHoudiniApi::Transform_Init(&(InstanceTransforms[Idx])); + + if ( FHoudiniApi::GetInstanceTransformsOnPart( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, HAPI_SRT, &InstanceTransforms[ 0 ], + 0, PointCount) == HAPI_RESULT_SUCCESS ) + { + for ( int32 PointIdx = 0; PointIdx < PointCount; ++PointIdx ) + { + FTransform TempTransformMatrix; + + const HAPI_Transform & HapiInstanceTransform = InstanceTransforms[ PointIdx ]; + FHoudiniEngineUtils::TranslateHapiTransform( HapiInstanceTransform, TempTransformMatrix ); + + AllTransforms.Add( TempTransformMatrix ); + } + } + else + { + return false; + } + } + + return true; +} + +bool +FHoudiniGeoPartObject::HapiGetInstanceTransforms( TArray< FTransform > & AllTransforms ) const +{ + return HapiGetInstanceTransforms( AssetId, AllTransforms ); +} + +HAPI_NodeId +FHoudiniGeoPartObject::HapiObjectGetToInstanceId( HAPI_NodeId OtherAssetId ) const +{ + HAPI_NodeId ObjectToInstance = -1; + HAPI_ObjectInfo ObjectInfo; + + if ( HapiObjectGetInfo( OtherAssetId, ObjectInfo ) ) + ObjectToInstance = ObjectInfo.objectToInstanceId; + + return ObjectToInstance; +} + +HAPI_NodeId +FHoudiniGeoPartObject::HapiObjectGetToInstanceId() const +{ + return HapiObjectGetToInstanceId( AssetId ); +} + +bool +FHoudiniGeoPartObject::HapiObjectGetInfo( HAPI_ObjectInfo & ObjectInfo ) const +{ + return HapiObjectGetInfo( AssetId, ObjectInfo ); +} + +bool +FHoudiniGeoPartObject::HapiObjectGetInfo( HAPI_NodeId OtherAssetId, HAPI_ObjectInfo & ObjectInfo ) const +{ + //FMemory::Memset< HAPI_ObjectInfo >( ObjectInfo, 0 ); + FHoudiniApi::ObjectInfo_Init(&ObjectInfo); + + HAPI_Result Result = FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), ObjectId, &ObjectInfo ); + if ( Result != HAPI_RESULT_SUCCESS ) + return false; + + return true; +} + +FHoudiniEngineString +FHoudiniGeoPartObject::HapiObjectGetName( HAPI_NodeId OtherAssetId ) const +{ + HAPI_StringHandle StringHandle = -1; + HAPI_ObjectInfo ObjectInfo; + if ( HapiObjectGetInfo( OtherAssetId, ObjectInfo ) ) + StringHandle = ObjectInfo.nameSH; + + return FHoudiniEngineString( StringHandle ); +} + +FHoudiniEngineString +FHoudiniGeoPartObject::HapiObjectGetName() const +{ + return HapiObjectGetName( AssetId ); +} + +FHoudiniEngineString +FHoudiniGeoPartObject::HapiObjectGetInstancePath( HAPI_NodeId OtherAssetId ) const +{ + HAPI_StringHandle StringHandle = -1; + HAPI_ObjectInfo ObjectInfo; + if ( HapiObjectGetInfo( OtherAssetId, ObjectInfo ) ) + StringHandle = ObjectInfo.objectInstancePathSH; + + return FHoudiniEngineString( StringHandle ); +} + +FHoudiniEngineString +FHoudiniGeoPartObject::HapiObjectGetInstancePath() const +{ + return HapiObjectGetInstancePath( AssetId ); +} + +bool +FHoudiniGeoPartObject::HapiObjectIsVisible( HAPI_NodeId OtherAssetId ) const +{ + bool bIsObjectVisible = false; + HAPI_ObjectInfo ObjectInfo; + if ( HapiObjectGetInfo( OtherAssetId, ObjectInfo ) ) + bIsObjectVisible = ObjectInfo.isVisible; + + return bIsObjectVisible; +} + +bool +FHoudiniGeoPartObject::HapiObjectIsVisible() const +{ + return HapiObjectIsVisible( AssetId ); +} + +bool +FHoudiniGeoPartObject::HapiObjectIsInstancer( HAPI_NodeId OtherAssetId ) const +{ + bool bIsObjectInstancer = false; + HAPI_ObjectInfo ObjectInfo; + + if ( HapiObjectGetInfo( OtherAssetId, ObjectInfo ) ) + bIsObjectInstancer = ObjectInfo.isInstancer; + + return bIsObjectInstancer; +} + +bool +FHoudiniGeoPartObject::HapiObjectIsInstancer() const +{ + return HapiObjectIsInstancer( AssetId ); +} + +bool +FHoudiniGeoPartObject::HapiObjectHasTransformChanged( HAPI_NodeId OtherAssetId ) const +{ + bool bObjectTransformHasChanged = false; + HAPI_ObjectInfo ObjectInfo; + + if ( HapiObjectGetInfo( OtherAssetId, ObjectInfo ) ) + bObjectTransformHasChanged = ObjectInfo.hasTransformChanged; + + return bObjectTransformHasChanged; +} + +bool +FHoudiniGeoPartObject::HapiObjectHasTransformChanged() const +{ + return HapiObjectHasTransformChanged( AssetId ); +} + +bool +FHoudiniGeoPartObject::HapiObjectHaveGeosChanged( HAPI_NodeId OtherAssetId ) const +{ + bool bGeosChanged = false; + HAPI_ObjectInfo ObjectInfo; + + if ( HapiObjectGetInfo( OtherAssetId, ObjectInfo ) ) + bGeosChanged = ObjectInfo.haveGeosChanged; + + return bGeosChanged; +} + +bool +FHoudiniGeoPartObject::HapiObjectHaveGeosChanged() const +{ + return HapiObjectHaveGeosChanged( AssetId ); +} + +int32 +FHoudiniGeoPartObject::HapiObjectGetGeoCount( HAPI_NodeId OtherAssetId ) const +{ + int32 GeoCount = 0; + HAPI_ObjectInfo ObjectInfo; + + if ( HapiObjectGetInfo( OtherAssetId, ObjectInfo ) ) + GeoCount = ObjectInfo.geoCount; + + return GeoCount; +} + + +int32 +FHoudiniGeoPartObject::HapiObjectGetGeoCount() const +{ + return HapiObjectGetGeoCount( AssetId ); +} + +HAPI_NodeId +FHoudiniGeoPartObject::HapiObjectGetNodeId() const +{ + HAPI_NodeId NodeId = -1; + HAPI_ObjectInfo ObjectInfo; + + if (HapiObjectGetInfo(ObjectInfo)) + NodeId = ObjectInfo.nodeId; + + return NodeId; +} + +HAPI_NodeId +FHoudiniGeoPartObject::HapiObjectGetNodeId( HAPI_NodeId OtherAssetId ) const +{ + HAPI_NodeId NodeId = -1; + HAPI_ObjectInfo ObjectInfo; + + if ( HapiObjectGetInfo( OtherAssetId, ObjectInfo ) ) + NodeId = ObjectInfo.nodeId; + + return NodeId; +} + +bool +FHoudiniGeoPartObject::HapiGeoGetInfo( HAPI_GeoInfo & GeoInfo ) const +{ + return HapiGeoGetInfo( AssetId, GeoInfo ); +} + +bool +FHoudiniGeoPartObject::HapiGeoGetInfo( HAPI_NodeId OtherAssetId, HAPI_GeoInfo & GeoInfo ) const +{ + FMemory::Memset< HAPI_GeoInfo >( GeoInfo, 0 ); + + if ( FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, &GeoInfo ) == HAPI_RESULT_SUCCESS ) + { + return true; + } + + return false; +} + +HAPI_GeoType +FHoudiniGeoPartObject::HapiGeoGetType( HAPI_NodeId OtherAssetId ) const +{ + HAPI_GeoType GeoType = HAPI_GEOTYPE_INVALID; + HAPI_GeoInfo GeoInfo; + + if ( HapiGeoGetInfo( OtherAssetId, GeoInfo ) ) + GeoType = GeoInfo.type; + + return GeoType; +} + +HAPI_GeoType +FHoudiniGeoPartObject::HapiGeoGetType() const +{ + return HapiGeoGetType( AssetId ); +} + +FHoudiniEngineString +FHoudiniGeoPartObject::HapiGeoGetName( HAPI_NodeId OtherAssetId ) const +{ + HAPI_StringHandle StringHandle = -1; + HAPI_GeoInfo GeoInfo; + + if ( HapiGeoGetInfo( OtherAssetId, GeoInfo ) ) + StringHandle = GeoInfo.nameSH; + + return FHoudiniEngineString( StringHandle ); +} + +FHoudiniEngineString +FHoudiniGeoPartObject::HapiGeoGetName() const +{ + return HapiGeoGetName( AssetId ); +} + +HAPI_NodeId +FHoudiniGeoPartObject::HapiGeoGetNodeId() const +{ + HAPI_NodeId NodeId = -1; + HAPI_GeoInfo GeoInfo; + + if ( HapiGeoGetInfo( GeoInfo ) ) + NodeId = GeoInfo.nodeId; + + return NodeId; +} + +HAPI_NodeId +FHoudiniGeoPartObject::HapiGeoGetNodeId( HAPI_NodeId OtherAssetId ) const +{ + HAPI_NodeId NodeId = -1; + HAPI_GeoInfo GeoInfo; + + if ( HapiGeoGetInfo( OtherAssetId, GeoInfo ) ) + NodeId = GeoInfo.nodeId; + + return NodeId; +} + +bool +FHoudiniGeoPartObject::HapiGeoIsEditable( HAPI_NodeId OtherAssetId ) const +{ + bool bLocalIsEditable = false; + HAPI_GeoInfo GeoInfo; + + if ( HapiGeoGetInfo( OtherAssetId, GeoInfo ) ) + bLocalIsEditable = GeoInfo.isEditable; + + return bLocalIsEditable; +} + +bool +FHoudiniGeoPartObject::HapiGeoIsEditable() const +{ + return HapiGeoIsEditable( AssetId ); +} + +bool +FHoudiniGeoPartObject::HapiGeoIsTemplated( HAPI_NodeId OtherAssetId ) const +{ + bool bIsTemplated = false; + HAPI_GeoInfo GeoInfo; + + if ( HapiGeoGetInfo( OtherAssetId, GeoInfo ) ) + bIsTemplated = GeoInfo.isTemplated; + + return bIsTemplated; +} + +bool +FHoudiniGeoPartObject::HapiGeoIsTemplated() const +{ + return HapiGeoIsTemplated( AssetId ); +} + +bool +FHoudiniGeoPartObject::HapiGeoIsDisplayGeo( HAPI_NodeId OtherAssetId ) const +{ + bool bIsDisplayGeo = false; + HAPI_GeoInfo GeoInfo; + + if ( HapiGeoGetInfo( OtherAssetId, GeoInfo ) ) + bIsDisplayGeo = GeoInfo.isDisplayGeo; + + return bIsDisplayGeo; +} + +bool +FHoudiniGeoPartObject::HapiGeoIsDisplayGeo() const +{ + return HapiGeoIsDisplayGeo( AssetId ); +} + +bool +FHoudiniGeoPartObject::HapiGeoHasChanged( HAPI_NodeId OtherAssetId ) const +{ + bool bGeoChanged = false; + HAPI_GeoInfo GeoInfo; + + if ( HapiGeoGetInfo( OtherAssetId, GeoInfo ) ) + bGeoChanged = GeoInfo.hasGeoChanged; + + return bGeoChanged; +} + +bool +FHoudiniGeoPartObject::HapiGeoHasChanged() const +{ + return HapiGeoHasChanged( AssetId ); +} + +bool +FHoudiniGeoPartObject::HapiGeoHasMaterialChanged( HAPI_NodeId OtherAssetId ) const +{ + bool bHasMaterialChanged = false; + HAPI_GeoInfo GeoInfo; + + if ( HapiGeoGetInfo( OtherAssetId, GeoInfo ) ) + bHasMaterialChanged = GeoInfo.hasMaterialChanged; + + return bHasMaterialChanged; +} + +bool +FHoudiniGeoPartObject::HapiGeoHasMaterialChanged() const +{ + return HapiGeoHasMaterialChanged( AssetId ); +} + +int32 +FHoudiniGeoPartObject::HapiGeoGetPointGroupCount( HAPI_NodeId OtherAssetId ) const +{ + int32 PointGroupCount = 0; + HAPI_GeoInfo GeoInfo; + + if ( HapiGeoGetInfo( OtherAssetId, GeoInfo ) ) + PointGroupCount = GeoInfo.pointGroupCount; + + return PointGroupCount; +} + +int32 +FHoudiniGeoPartObject::HapiGeoGetPointGroupCount() const +{ + return HapiGeoGetPointGroupCount( AssetId ); +} + +int32 +FHoudiniGeoPartObject::HapiGeoGetPrimitiveGroupCount( HAPI_NodeId OtherAssetId ) const +{ + int32 PrimitiveGroupCount = 0; + HAPI_GeoInfo GeoInfo; + + if ( HapiGeoGetInfo( OtherAssetId, GeoInfo ) ) + PrimitiveGroupCount = GeoInfo.primitiveGroupCount; + + return PrimitiveGroupCount; +} + +int32 +FHoudiniGeoPartObject::HapiGeoGetPrimitiveGroupCount() const +{ + return HapiGeoGetPrimitiveGroupCount( AssetId ); +} + +int32 +FHoudiniGeoPartObject::HapiGeoGetPartCount( HAPI_NodeId OtherAssetId ) const +{ + int32 PartCount = 0; + HAPI_GeoInfo GeoInfo; + + if ( HapiGeoGetInfo( OtherAssetId, GeoInfo ) ) + PartCount = GeoInfo.partCount; + + return PartCount; +} + +int32 +FHoudiniGeoPartObject::HapiGeoGetPartCount() const +{ + return HapiGeoGetPartCount( AssetId ); +} + +bool +FHoudiniGeoPartObject::HapiPartGetInfo( HAPI_PartInfo & PartInfo ) const +{ + return HapiPartGetInfo( AssetId, PartInfo ); +} + +bool +FHoudiniGeoPartObject::HapiPartGetInfo( HAPI_NodeId OtherAssetId, HAPI_PartInfo & PartInfo ) const +{ + FMemory::Memset< HAPI_PartInfo >( PartInfo, 0 ); + + if ( FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, &PartInfo ) == HAPI_RESULT_SUCCESS ) + { + return true; + } + + return false; +} + +FHoudiniEngineString +FHoudiniGeoPartObject::HapiPartGetName( HAPI_NodeId OtherAssetId ) const +{ + HAPI_StringHandle StringHandle = -1; + HAPI_PartInfo PartInfo; + + if ( HapiPartGetInfo( OtherAssetId, PartInfo ) ) + StringHandle = PartInfo.nameSH; + + return FHoudiniEngineString( StringHandle ); +} + +FHoudiniEngineString +FHoudiniGeoPartObject::HapiPartGetName() const +{ + return HapiPartGetName( AssetId ); +} + +HAPI_PartType +FHoudiniGeoPartObject::HapiPartGetType( HAPI_NodeId OtherAssetId ) const +{ + HAPI_PartType PartType = HAPI_PARTTYPE_INVALID; + HAPI_PartInfo PartInfo; + + if ( HapiPartGetInfo( OtherAssetId, PartInfo ) ) + PartType = PartInfo.type; + + return PartType; +} + +HAPI_PartType +FHoudiniGeoPartObject::HapiPartGetType() const +{ + return HapiPartGetType( AssetId ); +} + +int32 +FHoudiniGeoPartObject::HapiPartGetFaceCount( HAPI_NodeId OtherAssetId ) const +{ + int32 FaceCount = 0; + HAPI_PartInfo PartInfo; + + if ( HapiPartGetInfo( OtherAssetId, PartInfo ) ) + FaceCount = PartInfo.faceCount; + + return FaceCount; +} + +int32 +FHoudiniGeoPartObject::HapiPartGetFaceCount() const +{ + return HapiPartGetFaceCount( AssetId ); +} + +int32 +FHoudiniGeoPartObject::HapiPartGetVertexCount( HAPI_NodeId OtherAssetId ) const +{ + int32 VertexCount = 0; + HAPI_PartInfo PartInfo; + + if ( HapiPartGetInfo( OtherAssetId, PartInfo ) ) + VertexCount = PartInfo.vertexCount; + + return VertexCount; +} + +int32 +FHoudiniGeoPartObject::HapiPartGetVertexCount() const +{ + return HapiPartGetVertexCount( AssetId ); +} + +int32 +FHoudiniGeoPartObject::HapiPartGetPointCount( HAPI_NodeId OtherAssetId ) const +{ + int32 PointCount = 0; + HAPI_PartInfo PartInfo; + + if ( HapiPartGetInfo( OtherAssetId, PartInfo ) ) + PointCount = PartInfo.pointCount; + + return PointCount; +} + +int32 +FHoudiniGeoPartObject::HapiPartGetPointCount() const +{ + return HapiPartGetPointCount( AssetId ); +} + +bool +FHoudiniGeoPartObject::HapiPartIsInstanced( HAPI_NodeId OtherAssetId ) const +{ + bool bPartIsInstanced = false; + HAPI_PartInfo PartInfo; + + if ( HapiPartGetInfo( OtherAssetId, PartInfo ) ) + bPartIsInstanced = PartInfo.isInstanced; + + return bPartIsInstanced; +} + +bool +FHoudiniGeoPartObject::HapiPartIsInstanced() const +{ + return HapiPartIsInstanced( AssetId ); +} + +int32 +FHoudiniGeoPartObject::HapiPartGetInstancedPartCount( HAPI_NodeId OtherAssetId ) const +{ + int32 InstancedPartCount = 0; + HAPI_PartInfo PartInfo; + + if ( HapiPartGetInfo( OtherAssetId, PartInfo ) ) + InstancedPartCount = PartInfo.instancedPartCount; + + return InstancedPartCount; +} + +int32 +FHoudiniGeoPartObject::HapiPartGetInstancedPartCount() const +{ + return HapiPartGetInstancedPartCount( AssetId ); +} + +int32 +FHoudiniGeoPartObject::HapiPartGetInstanceCount( HAPI_NodeId OtherAssetId ) const +{ + int32 InstanceCount = 0; + HAPI_PartInfo PartInfo; + + if ( HapiPartGetInfo( OtherAssetId, PartInfo ) ) + InstanceCount = PartInfo.instanceCount; + + return InstanceCount; +} + +int32 +FHoudiniGeoPartObject::HapiPartGetInstanceCount() const +{ + return HapiPartGetInstanceCount( AssetId ); +} + +int32 +FHoudiniGeoPartObject::HapiPartGetPointAttributeCount( HAPI_NodeId OtherAssetId ) const +{ + int32 PointAttributeCount = 0; + HAPI_PartInfo PartInfo; + + if ( HapiPartGetInfo( OtherAssetId, PartInfo ) ) + PointAttributeCount = PartInfo.attributeCounts[ HAPI_ATTROWNER_POINT ]; + + return PointAttributeCount; +} + +int32 +FHoudiniGeoPartObject::HapiPartGetPointAttributeCount() const +{ + return HapiPartGetPointAttributeCount( AssetId ); +} + +int32 +FHoudiniGeoPartObject::HapiPartGetVertexAttributeCount( HAPI_NodeId OtherAssetId ) const +{ + int32 VertexAttributeCount = 0; + HAPI_PartInfo PartInfo; + + if ( HapiPartGetInfo( OtherAssetId, PartInfo ) ) + VertexAttributeCount = PartInfo.attributeCounts[ HAPI_ATTROWNER_VERTEX ]; + + return VertexAttributeCount; +} + +int32 +FHoudiniGeoPartObject::HapiPartGetVertexAttributeCount() const +{ + return HapiPartGetVertexAttributeCount( AssetId ); +} + +int32 +FHoudiniGeoPartObject::HapiPartGetPrimitiveAttributeCount( HAPI_NodeId OtherAssetId ) const +{ + int32 PrimitiveAttributeCount = 0; + HAPI_PartInfo PartInfo; + + if ( HapiPartGetInfo( OtherAssetId, PartInfo ) ) + PrimitiveAttributeCount = PartInfo.attributeCounts[ HAPI_ATTROWNER_PRIM ]; + + return PrimitiveAttributeCount; +} + +int32 +FHoudiniGeoPartObject::HapiPartGetPrimitiveAttributeCount() const +{ + return HapiPartGetPrimitiveAttributeCount( AssetId ); +} + +int32 +FHoudiniGeoPartObject::HapiPartGetDetailAttributeCount( HAPI_NodeId OtherAssetId ) const +{ + int32 DetailAttributeCount = 0; + HAPI_PartInfo PartInfo; + + if ( HapiPartGetInfo( OtherAssetId, PartInfo ) ) + DetailAttributeCount = PartInfo.attributeCounts[ HAPI_ATTROWNER_DETAIL ]; + + return DetailAttributeCount; +} + +int32 +FHoudiniGeoPartObject::HapiPartGetDetailAttributeCount() const +{ + return HapiPartGetDetailAttributeCount( AssetId ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeInfo( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & AttributeInfo ) const +{ + FMemory::Memset< HAPI_AttributeInfo >( AttributeInfo, 0 ); + + if ( FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, AttributeName, AttributeOwner, + &AttributeInfo ) == HAPI_RESULT_SUCCESS ) + { + return true; + } + + return false; +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeInfo( + const char * AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & AttributeInfo ) const +{ + return HapiGetAttributeInfo( AssetId, AttributeName, AttributeOwner, AttributeInfo ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeInfo( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & AttributeInfo) const +{ + return HapiGetAttributeInfo( OtherAssetId, AttributeName.c_str(), AttributeOwner, AttributeInfo ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeInfo( + const std::string & AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & AttributeInfo) const +{ + return HapiGetAttributeInfo( AssetId, AttributeName, AttributeOwner, AttributeInfo ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeInfo( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & AttributeInfo ) const +{ + std::string AttributeNameRaw = ""; + FHoudiniEngineUtils::ConvertUnrealString( AttributeName, AttributeNameRaw ); + + return HapiGetAttributeInfo( OtherAssetId, AttributeNameRaw, AttributeOwner, AttributeInfo ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeInfo( + const FString & AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & AttributeInfo ) const +{ + return HapiGetAttributeInfo( AssetId, AttributeName, AttributeOwner, AttributeInfo ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeInfo( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeInfo & AttributeInfo ) const +{ + for ( int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx ) + { + if ( !HapiGetAttributeInfo( + OtherAssetId, AttributeName, (HAPI_AttributeOwner) AttrIdx, AttributeInfo ) ) + { + AttributeInfo.exists = false; + return false; + } + + if ( AttributeInfo.exists ) + break; + } + + return true; +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeInfo( const char * AttributeName, HAPI_AttributeInfo & AttributeInfo ) const +{ + return HapiGetAttributeInfo( AssetId, AttributeName, AttributeInfo ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeInfo( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeInfo & AttributeInfo ) const +{ + return HapiGetAttributeInfo( OtherAssetId, AttributeName.c_str(), AttributeInfo ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeInfo( const std::string & AttributeName, HAPI_AttributeInfo & AttributeInfo ) const +{ + return HapiGetAttributeInfo( AssetId, AttributeName, AttributeInfo ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeInfo( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeInfo & AttributeInfo) const +{ + std::string AttributeNameRaw = ""; + FHoudiniEngineUtils::ConvertUnrealString( AttributeName, AttributeNameRaw ); + + return HapiGetAttributeInfo( OtherAssetId, AttributeNameRaw, AttributeInfo ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeInfo( const FString & AttributeName, HAPI_AttributeInfo & AttributeInfo ) const +{ + return HapiGetAttributeInfo( AssetId, AttributeName, AttributeInfo ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsFloat( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, + int32 TupleSize ) const +{ + AttributeData.SetNumUninitialized( 0 ); + + if ( !HapiGetAttributeInfo( OtherAssetId, AttributeName, AttributeOwner, ResultAttributeInfo ) ) + { + ResultAttributeInfo.exists = false; + return false; + } + + if ( !ResultAttributeInfo.exists ) + return false; + + if ( TupleSize > 0 ) + ResultAttributeInfo.tupleSize = TupleSize; + + AttributeData.SetNumUninitialized( ResultAttributeInfo.count * ResultAttributeInfo.tupleSize ); + + if ( FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, AttributeName, + &ResultAttributeInfo, -1, &AttributeData[ 0 ], 0, + ResultAttributeInfo.count ) == HAPI_RESULT_SUCCESS ) + { + return true; + } + + ResultAttributeInfo.exists = false; + return false; +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsFloat( + const char * AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< float > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsFloat( + AssetId, AttributeName, AttributeOwner, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsFloat( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, TArray< float > & AttributeData, + int32 TupleSize ) const +{ + return HapiGetAttributeDataAsFloat( + OtherAssetId, AttributeName.c_str(), AttributeOwner, ResultAttributeInfo, + AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsFloat( + const std::string & AttributeName, + HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, + int32 TupleSize ) const +{ + return HapiGetAttributeDataAsFloat( + AssetId, AttributeName, AttributeOwner, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsFloat( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, + int32 TupleSize ) const +{ + std::string AttributeNameRaw = ""; + FHoudiniEngineUtils::ConvertUnrealString( AttributeName, AttributeNameRaw ); + + return HapiGetAttributeDataAsFloat( + OtherAssetId, AttributeNameRaw, AttributeOwner, ResultAttributeInfo, + AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsFloat( + const FString & AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< float > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsFloat( + AssetId, AttributeName, AttributeOwner, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsFloat( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, int32 TupleSize ) const +{ + for ( int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx ) + { + if ( !HapiGetAttributeDataAsFloat( + OtherAssetId, AttributeName, (HAPI_AttributeOwner) AttrIdx, + ResultAttributeInfo, AttributeData, TupleSize ) ) + { + ResultAttributeInfo.exists = false; + return false; + } + + if ( ResultAttributeInfo.exists ) + break; + } + + return true; +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsFloat( + const char * AttributeName, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsFloat( AssetId, AttributeName, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsFloat( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsFloat( + OtherAssetId, AttributeName.c_str(), ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsFloat( + const std::string & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsFloat( AssetId, AttributeName, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsFloat( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, int32 TupleSize ) const +{ + std::string AttributeNameRaw = ""; + FHoudiniEngineUtils::ConvertUnrealString( AttributeName, AttributeNameRaw ); + + return HapiGetAttributeDataAsFloat( + OtherAssetId, AttributeNameRaw, ResultAttributeInfo, + AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsFloat( + const FString & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsFloat( AssetId, AttributeName, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsInt( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, + int32 TupleSize ) const +{ + AttributeData.SetNumUninitialized( 0 ); + + if ( !HapiGetAttributeInfo( OtherAssetId, AttributeName, AttributeOwner, ResultAttributeInfo ) ) + { + ResultAttributeInfo.exists = false; + return false; + } + + if ( !ResultAttributeInfo.exists ) + return false; + + if ( TupleSize > 0 ) + ResultAttributeInfo.tupleSize = TupleSize; + + AttributeData.SetNumUninitialized( ResultAttributeInfo.count * ResultAttributeInfo.tupleSize ); + + if ( FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, AttributeName, + &ResultAttributeInfo, -1, &AttributeData[ 0 ], 0, + ResultAttributeInfo.count ) == HAPI_RESULT_SUCCESS ) + { + return true; + } + + ResultAttributeInfo.exists = false; + return false; +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsInt( + const char * AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsInt( + AssetId, AttributeName, AttributeOwner, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsInt( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, + int32 TupleSize ) const +{ + return HapiGetAttributeDataAsInt( + OtherAssetId, AttributeName.c_str(), AttributeOwner, ResultAttributeInfo, + AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsInt( + const std::string & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, + int32 TupleSize ) const +{ + return HapiGetAttributeDataAsInt( + AssetId, AttributeName, AttributeOwner, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsInt( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, + int32 TupleSize ) const +{ + std::string AttributeNameRaw = ""; + FHoudiniEngineUtils::ConvertUnrealString( AttributeName, AttributeNameRaw ); + + return HapiGetAttributeDataAsInt( + OtherAssetId, AttributeNameRaw, AttributeOwner, ResultAttributeInfo, + AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsInt( + const FString & AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< int32 > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsInt( + AssetId, AttributeName, AttributeOwner, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsInt( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize ) const +{ + for ( int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx ) + { + if ( !HapiGetAttributeDataAsInt( + OtherAssetId, AttributeName, (HAPI_AttributeOwner) AttrIdx, + ResultAttributeInfo, AttributeData, TupleSize ) ) + { + ResultAttributeInfo.exists = false; + return false; + } + + if ( ResultAttributeInfo.exists ) + break; + } + + return true; +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsInt( + const char * AttributeName, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsInt( AssetId, AttributeName, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsInt( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsInt( + OtherAssetId, AttributeName.c_str(), ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsInt( + const std::string & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsInt( AssetId, AttributeName, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsInt( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< int32 > & AttributeData, int32 TupleSize ) const +{ + std::string AttributeNameRaw = ""; + FHoudiniEngineUtils::ConvertUnrealString( AttributeName, AttributeNameRaw ); + + return HapiGetAttributeDataAsInt( + OtherAssetId, AttributeNameRaw, ResultAttributeInfo, + AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsInt( + const FString & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsInt( AssetId, AttributeName, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsString( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, + int32 TupleSize ) const +{ + AttributeData.SetNumUninitialized( 0 ); + + if ( !HapiGetAttributeInfo( OtherAssetId, AttributeName, AttributeOwner, ResultAttributeInfo ) ) + { + ResultAttributeInfo.exists = false; + return false; + } + + if ( !ResultAttributeInfo.exists ) + return false; + + if ( TupleSize > 0 ) + ResultAttributeInfo.tupleSize = TupleSize; + + TArray< HAPI_StringHandle > StringHandles; + StringHandles.Init( -1, ResultAttributeInfo.count * ResultAttributeInfo.tupleSize ); + if ( FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, AttributeName, &ResultAttributeInfo, + &StringHandles[ 0 ], 0, ResultAttributeInfo.count ) == HAPI_RESULT_SUCCESS ) + { + // Use a map to minimize the number of HAPI calls for performance! + TMap StringHandleToStringMap; + for ( int32 Idx = 0; Idx < StringHandles.Num(); ++Idx ) + { + FString* FoundString = StringHandleToStringMap.Find( StringHandles[ Idx ] ); + if ( FoundString ) + { + AttributeData.Add( *FoundString ); + } + else + { + FString HapiString = TEXT(""); + FHoudiniEngineString HoudiniEngineString( StringHandles[ Idx ] ); + HoudiniEngineString.ToFString( HapiString ); + StringHandleToStringMap.Add( StringHandles[ Idx ], HapiString ); + AttributeData.Add( *HapiString ); + } + } + + return true; + } + + ResultAttributeInfo.exists = false; + return false; +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsString( + const char * AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsString( + AssetId, AttributeName, AttributeOwner, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsString( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, + int32 TupleSize ) const +{ + return HapiGetAttributeDataAsString( + OtherAssetId, AttributeName.c_str(), AttributeOwner, ResultAttributeInfo, + AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsString( + const std::string & AttributeName, + HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, + int32 TupleSize ) const +{ + return HapiGetAttributeDataAsString( + AssetId, AttributeName, AttributeOwner, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsString( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, + int32 TupleSize ) const +{ + std::string AttributeNameRaw = ""; + FHoudiniEngineUtils::ConvertUnrealString( AttributeName, AttributeNameRaw ); + + return HapiGetAttributeDataAsString( + OtherAssetId, AttributeNameRaw, AttributeOwner, ResultAttributeInfo, + AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsString( + const FString & AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< FString > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsString( + AssetId, AttributeName, AttributeOwner, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsString( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< FString > & AttributeData, int32 TupleSize ) const +{ + for ( int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx ) + { + if ( !HapiGetAttributeDataAsString( + OtherAssetId, AttributeName, (HAPI_AttributeOwner) AttrIdx, + ResultAttributeInfo, AttributeData, TupleSize ) ) + { + ResultAttributeInfo.exists = false; + return false; + } + + if ( ResultAttributeInfo.exists ) + break; + } + + return true; +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsString( + const char * AttributeName, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsString( AssetId, AttributeName, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsString( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< FString > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsString( + OtherAssetId, AttributeName.c_str(), ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsString( + const std::string & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsString( AssetId, AttributeName, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsString( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize ) const +{ + std::string AttributeNameRaw = ""; + FHoudiniEngineUtils::ConvertUnrealString( AttributeName, AttributeNameRaw ); + + return HapiGetAttributeDataAsString( + OtherAssetId, AttributeNameRaw, ResultAttributeInfo, + AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeDataAsString( + const FString & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize ) const +{ + return HapiGetAttributeDataAsString( AssetId, AttributeName, ResultAttributeInfo, AttributeData, TupleSize ); +} + +bool +FHoudiniGeoPartObject::HapiObjectGetUniqueInstancerMaterialId( HAPI_NodeId & MaterialId ) const +{ + return HapiObjectGetUniqueInstancerMaterialId( AssetId, MaterialId ); +} + +bool +FHoudiniGeoPartObject::HapiObjectGetUniqueInstancerMaterialId( + HAPI_NodeId OtherAssetId, HAPI_NodeId& MaterialId ) const +{ + MaterialId = -1; + + if ( HapiObjectIsInstancer( OtherAssetId ) ) + { + HAPI_Bool bSingleFaceMaterial = false; + if ( FHoudiniApi::GetMaterialNodeIdsOnFaces( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, &bSingleFaceMaterial, + &MaterialId, 0, 1 ) == HAPI_RESULT_SUCCESS ) + { + return true; + } + } + + return false; +} + +bool +FHoudiniGeoPartObject::HapiPartGetUniqueMaterialIds( TSet< HAPI_NodeId > & MaterialIds ) const +{ + return HapiPartGetUniqueMaterialIds( AssetId, MaterialIds ); +} + +bool +FHoudiniGeoPartObject::HapiPartGetUniqueMaterialIds( + HAPI_NodeId OtherAssetId, + TSet< HAPI_NodeId > & MaterialIds ) const +{ + MaterialIds.Empty(); + + int32 FaceCount = HapiPartGetFaceCount( OtherAssetId ); + if ( FaceCount > 0 ) + { + TArray< HAPI_NodeId > FaceMaterialIds; + FaceMaterialIds.SetNumUninitialized( FaceCount ); + + HAPI_Bool bSingleFaceMaterial = false; + if ( FHoudiniApi::GetMaterialNodeIdsOnFaces( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, &bSingleFaceMaterial, + &FaceMaterialIds[0], 0, FaceCount ) == HAPI_RESULT_SUCCESS ) + { + MaterialIds.Append( FaceMaterialIds ); + return true; + } + } + + return false; +} + +bool +FHoudiniGeoPartObject::HapiGetAllAttributeNames( HAPI_NodeId OtherAssetId, TArray< FString > & AttributeNames ) const +{ + AttributeNames.Empty(); + + for ( int32 AttrIdx = HAPI_ATTROWNER_VERTEX; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx ) + { + TArray< FString > AttributeValues; + if ( HapiGetAttributeNames( OtherAssetId, (HAPI_AttributeOwner) AttrIdx, AttributeValues ) ) + { + if ( AttributeValues.Num() > 0 ) + AttributeNames.Append(AttributeValues); + } + } + + return true; +} + +bool +FHoudiniGeoPartObject::HapiGetAllAttributeNames( TArray< FString > & AttributeNames ) const +{ + return HapiGetAllAttributeNames( AssetId, AttributeNames ); +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeNames( + HAPI_NodeId OtherAssetId, HAPI_AttributeOwner AttributeOwner, + TArray< FString > & AttributeNames ) const +{ + AttributeNames.Empty(); + int32 AttributeCount = 0; + + switch ( AttributeOwner) + { + case HAPI_ATTROWNER_POINT: + { + AttributeCount = HapiPartGetPointAttributeCount( OtherAssetId ); + break; + } + + case HAPI_ATTROWNER_VERTEX: + { + AttributeCount = HapiPartGetVertexAttributeCount( OtherAssetId ); + break; + } + + case HAPI_ATTROWNER_PRIM: + { + AttributeCount = HapiPartGetPrimitiveAttributeCount( OtherAssetId ); + break; + } + + case HAPI_ATTROWNER_DETAIL: + { + AttributeCount = HapiPartGetDetailAttributeCount( OtherAssetId ); + break; + } + + default: + { + return false; + } + } + + if ( AttributeCount > 0 ) + { + TArray< HAPI_StringHandle > AttributeNameHandles; + AttributeNameHandles.SetNumUninitialized( AttributeCount ); + + if ( FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, + AttributeOwner, &AttributeNameHandles[ 0 ], + AttributeCount ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + for ( int32 Idx = 0, Num = AttributeNameHandles.Num(); Idx < Num; ++Idx ) + { + FString HapiString = TEXT( "" ); + FHoudiniEngineString HoudiniEngineString( AttributeNameHandles[ Idx ] ); + if ( HoudiniEngineString.ToFString( HapiString ) ) + AttributeNames.Add( HapiString ); + } + } + + return true; +} + +bool +FHoudiniGeoPartObject::HapiGetAttributeNames( + HAPI_AttributeOwner AttributeOwner, + TArray< FString > & AttributeNames ) const +{ + return HapiGetAttributeNames( AssetId, AttributeOwner, AttributeNames ); +} + +bool +FHoudiniGeoPartObject::HapiGetPointAttributeNames( HAPI_NodeId OtherAssetId, TArray< FString > & AttributeNames ) const +{ + return HapiGetAttributeNames( OtherAssetId, HAPI_ATTROWNER_POINT, AttributeNames ); +} + +bool +FHoudiniGeoPartObject::HapiGetPointAttributeNames( TArray< FString > & AttributeNames ) const +{ + return HapiGetPointAttributeNames( AssetId, AttributeNames ); +} + +bool +FHoudiniGeoPartObject::HapiGetVertexAttributeNames( HAPI_NodeId OtherAssetId, TArray< FString > & AttributeNames ) const +{ + return HapiGetAttributeNames( OtherAssetId, HAPI_ATTROWNER_VERTEX, AttributeNames ); +} + +bool +FHoudiniGeoPartObject::HapiGetVertexAttributeNames( TArray< FString > & AttributeNames ) const +{ + return HapiGetVertexAttributeNames( AssetId, AttributeNames ); +} + +bool +FHoudiniGeoPartObject::HapiGetPrimitiveAttributeNames( + HAPI_NodeId OtherAssetId, + TArray< FString > & AttributeNames ) const +{ + return HapiGetAttributeNames( OtherAssetId, HAPI_ATTROWNER_PRIM, AttributeNames ); +} + +bool +FHoudiniGeoPartObject::HapiGetPrimitiveAttributeNames( TArray< FString > & AttributeNames ) const +{ + return HapiGetPrimitiveAttributeNames( AssetId, AttributeNames ); +} + +bool +FHoudiniGeoPartObject::HapiGetDetailAttributeNames( HAPI_NodeId OtherAssetId, TArray< FString > & AttributeNames ) const +{ + return HapiGetAttributeNames( OtherAssetId, HAPI_ATTROWNER_DETAIL, AttributeNames ); +} + +bool +FHoudiniGeoPartObject::HapiGetDetailAttributeNames( TArray< FString > & AttributeNames ) const +{ + return HapiGetDetailAttributeNames( AssetId, AttributeNames ); +} + + +bool +FHoudiniGeoPartObject::HapiGetVertices( HAPI_NodeId OtherAssetId, TArray< int32 > & Vertices ) const +{ + Vertices.Empty(); + + int32 VertexCount = HapiPartGetVertexCount( OtherAssetId ); + if ( !VertexCount ) + return false; + + Vertices.SetNumUninitialized( VertexCount ); + + if ( FHoudiniApi::GetVertexList( + FHoudiniEngine::Get().GetSession(), GeoId, + PartId, &Vertices[ 0 ], 0, VertexCount ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + return true; +} + +bool +FHoudiniGeoPartObject::HapiGetVertices( TArray< int32 > & Vertices ) const +{ + return HapiGetVertices( AssetId, Vertices ); +} diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp new file mode 100644 index 00000000..4bb7de0b --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp @@ -0,0 +1,246 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniHandleComponent.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniAssetComponent.h" + +HAPI_RSTOrder +UHoudiniHandleComponent::GetHapiRSTOrder( const TSharedPtr< FString > & StrPtr ) +{ + if ( StrPtr.Get() ) + { + FString & Str = *StrPtr; + + if ( Str == "trs" ) return HAPI_TRS; + else if ( Str == "tsr" ) return HAPI_TSR; + else if ( Str == "rts" ) return HAPI_RTS; + else if ( Str == "rst" ) return HAPI_RST; + else if ( Str == "str" ) return HAPI_STR; + else if ( Str == "srt" ) return HAPI_SRT; + } + + return HAPI_SRT; +} + +HAPI_XYZOrder +UHoudiniHandleComponent::GetHapiXYZOrder( const TSharedPtr< FString > & StrPtr ) +{ + if ( StrPtr.Get() ) + { + FString & Str = *StrPtr; + + if ( Str == "xyz" ) return HAPI_XYZ; + else if ( Str == "xzy" ) return HAPI_XZY; + else if ( Str == "yxz" ) return HAPI_YXZ; + else if ( Str == "yzx" ) return HAPI_YZX; + else if ( Str == "zxy" ) return HAPI_ZXY; + else if ( Str == "zyx" ) return HAPI_ZYX; + } + + return HAPI_XYZ; +} + +UHoudiniHandleComponent::UHoudiniHandleComponent( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{} + +UHoudiniHandleComponent::~UHoudiniHandleComponent() +{} + +bool +UHoudiniHandleComponent::Construct( + HAPI_NodeId AssetId, + int32 HandleIdx, + const FString & HandleName, + const HAPI_HandleInfo & HandleInfo, + const TMap< HAPI_ParmId, UHoudiniAssetParameter * > & Parameters, EHoudiniHandleType InHandleType ) +{ + HandleType = InHandleType; + TArray< HAPI_HandleBindingInfo > BindingInfos; + BindingInfos.SetNumZeroed( HandleInfo.bindingsCount ); + + if ( FHoudiniApi::GetHandleBindingInfo( + FHoudiniEngine::Get().GetSession(), + AssetId, HandleIdx, &BindingInfos[ 0 ], 0, + HandleInfo.bindingsCount ) != HAPI_RESULT_SUCCESS ) + { + return false; + } + + HAPI_TransformEuler HapiEulerXform; + FMemory::Memzero< HAPI_TransformEuler >( HapiEulerXform ); + HapiEulerXform.position[ 0 ] = HapiEulerXform.position[ 1 ] = HapiEulerXform.position[ 2 ] = 0.0f; + HapiEulerXform.rotationEuler[ 0 ] = HapiEulerXform.rotationEuler[ 1 ] = HapiEulerXform.rotationEuler[ 2 ] = 0.0f; + HapiEulerXform.scale[ 0 ] = HapiEulerXform.scale[ 1 ] = HapiEulerXform.scale[ 2 ] = 1.0f; + + TSharedPtr< FString > RSTOrderStrPtr, XYZOrderStrPtr; + + for ( const auto& BindingInfo : BindingInfos ) + { + FString HandleParmName = TEXT( "" ); + FHoudiniEngineString HoudiniEngineString( BindingInfo.handleParmNameSH ); + HoudiniEngineString.ToFString( HandleParmName ); + + const HAPI_NodeId AssetParmId = BindingInfo.assetParmId; + + (void)( XformParms[ EXformParameter::TX ].Bind( HapiEulerXform.position[ 0 ], "tx", 0, HandleParmName, AssetParmId, Parameters ) + || XformParms[ EXformParameter::TY ].Bind( HapiEulerXform.position[ 1 ], "ty", 1, HandleParmName, AssetParmId, Parameters ) + || XformParms[ EXformParameter::TZ ].Bind( HapiEulerXform.position[ 2 ], "tz", 2, HandleParmName, AssetParmId, Parameters ) + + || XformParms[ EXformParameter::RX ].Bind( HapiEulerXform.rotationEuler[ 0 ], "rx", 0, HandleParmName, AssetParmId, Parameters ) + || XformParms[ EXformParameter::RY ].Bind( HapiEulerXform.rotationEuler[ 1 ], "ry", 1, HandleParmName, AssetParmId, Parameters ) + || XformParms[ EXformParameter::RZ ].Bind( HapiEulerXform.rotationEuler[ 2 ], "rz", 2, HandleParmName, AssetParmId, Parameters ) + + || XformParms[ EXformParameter::SX ].Bind( HapiEulerXform.scale[ 0 ], "sx", 0, HandleParmName, AssetParmId, Parameters ) + || XformParms[ EXformParameter::SY ].Bind( HapiEulerXform.scale[ 1 ], "sy", 1, HandleParmName, AssetParmId, Parameters ) + || XformParms[ EXformParameter::SZ ].Bind( HapiEulerXform.scale[ 2 ], "sz", 2, HandleParmName, AssetParmId, Parameters ) + + || RSTParm.Bind( RSTOrderStrPtr, "trs_order", 0, HandleParmName, AssetParmId, Parameters ) + || RotOrderParm.Bind( XYZOrderStrPtr, "xyz_order", 0, HandleParmName, AssetParmId, Parameters ) + ); + } + + HapiEulerXform.rstOrder = GetHapiRSTOrder( RSTOrderStrPtr ); + HapiEulerXform.rotationOrder = GetHapiXYZOrder( XYZOrderStrPtr ); + constexpr float MaxFloat = TNumericLimits::Max(); + constexpr float MinFloat = TNumericLimits::Min(); + HapiEulerXform.scale[ 0 ] = FMath::Clamp( HapiEulerXform.scale[ 0 ], MinFloat, MaxFloat ); + HapiEulerXform.scale[ 1 ] = FMath::Clamp( HapiEulerXform.scale[ 1 ], MinFloat, MaxFloat ); + HapiEulerXform.scale[ 2 ] = FMath::Clamp( HapiEulerXform.scale[ 2 ], MinFloat, MaxFloat ); + + FTransform UnrealXform; + FHoudiniEngineUtils::TranslateHapiTransform( HapiEulerXform, UnrealXform ); + + SetRelativeTransform( UnrealXform ); + return true; +} + +void +UHoudiniHandleComponent::ResolveDuplicatedParameters( const TMap< HAPI_ParmId, UHoudiniAssetParameter * > & NewParameters ) +{ + for ( size_t i = 0; i < EXformParameter::COUNT; ++i ) + XformParms[ i ].ResolveDuplicated( NewParameters ); + + RSTParm.ResolveDuplicated( NewParameters ); + RotOrderParm.ResolveDuplicated( NewParameters ); +} + +void +UHoudiniHandleComponent::UpdateTransformParameters() +{ + HAPI_Transform HapiXform; + FMemory::Memzero< HAPI_Transform >( HapiXform ); + FHoudiniEngineUtils::TranslateUnrealTransform( GetRelativeTransform(), HapiXform ); + + const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); + + float HapiMatrix[ 16 ]; + FHoudiniApi::ConvertTransformQuatToMatrix( Session, &HapiXform, HapiMatrix ); + + HAPI_TransformEuler HapiEulerXform; + FMemory::Memzero< HAPI_TransformEuler >( HapiEulerXform ); + FHoudiniApi::ConvertMatrixToEuler( + Session, + HapiMatrix, + GetHapiRSTOrder( RSTParm.Get( TSharedPtr< FString >() ) ), + GetHapiXYZOrder( RotOrderParm.Get( TSharedPtr< FString >() ) ), + &HapiEulerXform + ); + + XformParms[ EXformParameter::TX ] = HapiEulerXform.position[ 0 ]; + XformParms[ EXformParameter::TY ] = HapiEulerXform.position[ 1 ]; + XformParms[ EXformParameter::TZ ] = HapiEulerXform.position[ 2 ]; + + XformParms[ EXformParameter::RX ] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[ 0 ]); + XformParms[ EXformParameter::RY ] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[ 1 ]); + XformParms[ EXformParameter::RZ ] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[ 2 ]); + + constexpr float MaxFloat = TNumericLimits::Max(); + constexpr float MinFloat = TNumericLimits::Min(); + HapiEulerXform.scale[ 0 ] = FMath::Clamp( HapiEulerXform.scale[ 0 ], MinFloat, MaxFloat ); + HapiEulerXform.scale[ 1 ] = FMath::Clamp( HapiEulerXform.scale[ 1 ], MinFloat, MaxFloat ); + HapiEulerXform.scale[ 2 ] = FMath::Clamp( HapiEulerXform.scale[ 2 ], MinFloat, MaxFloat ); + + XformParms[ EXformParameter::SX ] = HapiEulerXform.scale[ 0 ]; + XformParms[ EXformParameter::SY ] = HapiEulerXform.scale[ 1 ]; + XformParms[ EXformParameter::SZ ] = HapiEulerXform.scale[ 2 ]; +} + +void +UHoudiniHandleComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniHandleComponent* This = Cast< UHoudiniHandleComponent >( InThis ); + + if ( This && !This->IsPendingKill() ) + { + for ( size_t i = 0; i < EXformParameter::COUNT; ++i ) + This->XformParms[ i ].AddReferencedObject( Collector, InThis ); + + This->RSTParm.AddReferencedObject( Collector, InThis ); + This->RotOrderParm.AddReferencedObject( Collector, InThis ); + } +} + +void +UHoudiniHandleComponent::Serialize( FArchive & Ar ) +{ + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + for ( int32 i = 0; i < EXformParameter::COUNT; ++i ) + Ar << XformParms[ i ]; + + Ar << RSTParm; + Ar << RotOrderParm; +} + +#if WITH_EDITOR + +void +UHoudiniHandleComponent::PostEditUndo() +{ + Super::PostEditUndo(); + + UHoudiniAssetComponent * AttachComponent = Cast< UHoudiniAssetComponent >( GetAttachParent() ); + if ( AttachComponent ) + { + //UploadControlPoints(); + AttachComponent->StartTaskAssetCooking( true ); + } +} + +#endif // WITH_EDITOR diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h new file mode 100644 index 00000000..071e4792 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h @@ -0,0 +1,212 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#pragma once + +#include "HoudiniGeoPartObject.h" +#include "HoudiniAssetParameterChoice.h" +#include "HoudiniAssetParameterFloat.h" + +#include "Components/SceneComponent.h" + +#include "HoudiniHandleComponent.generated.h" + + +UENUM() +enum class EHoudiniHandleType : uint8 +{ + Xform, + Bounder, + Unsupported +}; + +UCLASS( config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniHandleComponent : public USceneComponent +{ + public: + + friend class UHoudiniAssetComponent; + +#if WITH_EDITOR + + friend class FHoudiniHandleComponentVisualizer; + +#endif // WITH_EDITOR + + GENERATED_UCLASS_BODY() + + virtual ~UHoudiniHandleComponent(); + + /** UObject methods. **/ + public: + + virtual void Serialize( FArchive & Ar ) override; + +#if WITH_EDITOR + + virtual void PostEditUndo() override; + +#endif // WITH_EDITOR + + public: + bool Construct( + HAPI_NodeId AssetId, int32 HandleIdx, const FString & HandleName, + const HAPI_HandleInfo &, const TMap< HAPI_ParmId, UHoudiniAssetParameter * > &, EHoudiniHandleType InHandleType); + + void ResolveDuplicatedParameters( const TMap< HAPI_ParmId, UHoudiniAssetParameter * > & ); + + // Update HAPI transform handle parameters from the current ComponentToWorld Unreal transform + void UpdateTransformParameters(); + + static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); + + private: + static HAPI_RSTOrder GetHapiRSTOrder( const TSharedPtr< FString > & ); + static HAPI_XYZOrder GetHapiXYZOrder( const TSharedPtr< FString > & ); + + template < class ASSET_PARM > + class THandleParameter + { + public: + THandleParameter() + : AssetParameter( nullptr ) + , TupleIdx( 0 ) + {} + + void AddReferencedObject( FReferenceCollector & Collector, const UObject * ReferencingObject ) + { + if ( AssetParameter ) + Collector.AddReferencedObject( AssetParameter, ReferencingObject ); + } + + friend FArchive & operator<<( FArchive & Ar, THandleParameter & InThis ) + { + Ar << InThis.AssetParameter; + Ar << InThis.TupleIdx; + + return Ar; + } + + template < typename VALUE > + bool Bind( + VALUE & OutValue, + const char * CmpName, + int32 InTupleIdx, + const FString & HandleParmName, + HAPI_ParmId AssetParamId, + const TMap< HAPI_ParmId, UHoudiniAssetParameter * > & Parameters ) + { + if (HandleParmName != CmpName) + { + return false; + } + + UHoudiniAssetParameter * const * FoundAbstractParm = Parameters.Find(AssetParamId); + if (!FoundAbstractParm) + { + return false; + } + + AssetParameter = Cast< ASSET_PARM >( *FoundAbstractParm ); + if ( AssetParameter ) + { + // It is possible that the handle param is bound to a single tuple param. + // Ignore the preset tuple index if that's the case or we'll crash. + if ((*FoundAbstractParm)->GetTupleSize() <= InTupleIdx) + InTupleIdx = 0; + + auto Optional = AssetParameter->GetValue( InTupleIdx ); + if ( Optional.IsSet() ) + { + TupleIdx = InTupleIdx; + OutValue = static_cast< VALUE >( Optional.GetValue() ); + return true; + } + } + + return false; + } + + void ResolveDuplicated( const TMap< HAPI_ParmId, UHoudiniAssetParameter * > & NewParameters ) + { + if ( AssetParameter ) + { + if ( UHoudiniAssetParameter * const * FoundNewParameter = NewParameters.Find( AssetParameter->GetParmId() ) ) + AssetParameter = Cast< ASSET_PARM >( *FoundNewParameter ); + else + AssetParameter = nullptr; + } + } + + template < typename VALUE > + VALUE Get( VALUE DefaulValue ) const + { + if ( AssetParameter ) + { + auto Optional = AssetParameter->GetValue( TupleIdx ); + if ( Optional.IsSet() ) + return static_cast< VALUE >( Optional.GetValue() ); + } + + return DefaulValue; + } + + template < typename VALUE > + THandleParameter & operator=( VALUE Value ) + { + if ( AssetParameter ) + AssetParameter->SetValue( Value, TupleIdx ); + + return *this; + } + + ASSET_PARM * AssetParameter; + int32 TupleIdx; + }; + + struct EXformParameter + { + enum Type + { + TX, TY, TZ, + RX, RY, RZ, + SX, SY, SZ, + COUNT + }; + }; + + typedef THandleParameter< UHoudiniAssetParameterFloat > FXformParameter; + FXformParameter XformParms[ EXformParameter::COUNT ]; + + THandleParameter< UHoudiniAssetParameterChoice > RSTParm; + THandleParameter< UHoudiniAssetParameterChoice > RotOrderParm; + UPROPERTY() + EHoudiniHandleType HandleType; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp new file mode 100644 index 00000000..61eb3c64 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp @@ -0,0 +1,230 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniInstancedActorComponent.h" + +#include "HoudiniApi.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#if WITH_EDITOR +#include "LevelEditorViewport.h" +#endif +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniInstancedActorComponent::UHoudiniInstancedActorComponent( const FObjectInitializer& ObjectInitializer ) +: Super( ObjectInitializer ) +, InstancedAsset( nullptr ) +{ +} + + +void UHoudiniInstancedActorComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) +{ + ClearInstances(); + Super::OnComponentDestroyed( bDestroyingHierarchy ); +} + +void +UHoudiniInstancedActorComponent::Serialize( FArchive & Ar ) +{ + Super::Serialize( Ar ); + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + Ar << InstancedAsset; + Ar << Instances; +} + +void +UHoudiniInstancedActorComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniInstancedActorComponent * ThisHIAC = Cast< UHoudiniInstancedActorComponent >(InThis); + if ( ThisHIAC && !ThisHIAC->IsPendingKill() ) + { + if ( ThisHIAC->InstancedAsset && !ThisHIAC->InstancedAsset->IsPendingKill() ) + Collector.AddReferencedObject( ThisHIAC->InstancedAsset, ThisHIAC ); + + Collector.AddReferencedObjects(ThisHIAC->Instances, ThisHIAC ); + } +} + +void +UHoudiniInstancedActorComponent::SetInstances( const TArray& InstanceTransforms ) +{ +#if WITH_EDITOR + if ( Instances.Num() || InstanceTransforms.Num() ) + { + const FScopedTransaction Transaction( LOCTEXT( "UpdateInstances", "Update Instances" ) ); + GetOwner()->Modify(); + ClearInstances(); + + if( InstancedAsset && !InstancedAsset->IsPendingKill() ) + { + for( const FTransform& InstanceTransform : InstanceTransforms ) + { + AddInstance( InstanceTransform ); + } + } + else + { + HOUDINI_LOG_ERROR( TEXT( "%s: Null InstancedAsset for instanced actor override" ), *GetOwner()->GetName() ); + } + } +#endif +} + +int32 +UHoudiniInstancedActorComponent::AddInstance( const FTransform& InstanceTransform ) +{ + AActor * NewActor = SpawnInstancedActor(InstanceTransform); + if ( NewActor && !NewActor->IsPendingKill() ) + { + NewActor->AttachToComponent( this, FAttachmentTransformRules::KeepRelativeTransform ); + NewActor->SetActorRelativeTransform( InstanceTransform ); + return Instances.Add( NewActor ); + } + return -1; +} + +AActor* +UHoudiniInstancedActorComponent::SpawnInstancedActor( const FTransform& InstancedTransform ) const +{ +#if WITH_EDITOR + if (InstancedAsset && !InstancedAsset->IsPendingKill()) + { + GEditor->ClickLocation = InstancedTransform.GetTranslation(); + GEditor->ClickPlane = FPlane(GEditor->ClickLocation, FVector::UpVector); + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject(GetOwner()->GetLevel(), InstancedAsset, false, RF_Transactional, nullptr); + + if (NewActors.Num() > 0) + { + if ( NewActors[0] && !NewActors[0]->IsPendingKill() ) + return NewActors[0]; + } + } +#endif + return nullptr; +} + +void +UHoudiniInstancedActorComponent::ClearInstances() +{ + for ( AActor* Instance : Instances ) + { + if ( Instance && !Instance->IsPendingKill() ) + Instance->Destroy(); + } + Instances.Empty(); +} + + +void +UHoudiniInstancedActorComponent::OnComponentCreated() +{ + Super::OnComponentCreated(); + + // If our instances are parented to another actor we should duplicate them + bool bNeedDuplicate = false; + for (auto CurrentInstance : Instances) + { + if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) + continue; + + if ( CurrentInstance->GetAttachParentActor() != GetOwner() ) + bNeedDuplicate = true; + } + + if ( !bNeedDuplicate ) + return; + + // We need to duplicate our instances + TArray< AActor* > SourceInstances = Instances; + Instances.Empty(); + for ( AActor* CurrentInstance : SourceInstances ) + { + if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) + continue; + + FTransform InstanceTransform; + if ( CurrentInstance->GetRootComponent() ) + InstanceTransform = CurrentInstance->GetRootComponent()->GetRelativeTransform(); + + AddInstance( InstanceTransform ); + } +} + +void UHoudiniInstancedActorComponent::UpdateInstancerComponentInstances( + USceneComponent * Component, + const TArray< FTransform > & ProcessedTransforms, const TArray & InstancedColors ) +{ + UInstancedStaticMeshComponent* ISMC = Cast( Component ); + UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(Component); + UHoudiniInstancedActorComponent* IAC = Cast( Component ); + UHoudiniMeshSplitInstancerComponent* MSIC = Cast( Component ); + + if(!ISMC && !IAC && !MSIC) + return; + + if( ISMC && !ISMC->IsPendingKill() ) + { + ISMC->ClearInstances(); + if( HISMC ) + { + // HISM need a special treatment as calling AddInstance multiple times on them can cause crashes: + // see UE4 bug UE-68582 + // Calling UHierarchicalInstancedStaticMeshComponent::AddInstance multiple times causes + // multiple BuildTrees to be created and run asynchronously at the same time. + bool bAautoRebuildState = HISMC->bAutoRebuildTreeOnInstanceChanges; + HISMC->bAutoRebuildTreeOnInstanceChanges = false; + + for( int32 InstanceIdx = 0; InstanceIdx < ProcessedTransforms.Num(); ++InstanceIdx ) + { + HISMC->AddInstance(ProcessedTransforms[InstanceIdx]); + } + + HISMC->bAutoRebuildTreeOnInstanceChanges = bAautoRebuildState; + HISMC->BuildTreeIfOutdated(true, true); + } + else + { + for( int32 InstanceIdx = 0; InstanceIdx < ProcessedTransforms.Num(); ++InstanceIdx ) + { + ISMC->AddInstance(ProcessedTransforms[InstanceIdx]); + } + } + } + else if( IAC && !IAC->IsPendingKill() ) + { + IAC->SetInstances(ProcessedTransforms); + } + else if( MSIC && !MSIC->IsPendingKill() ) + { + MSIC->SetInstances(ProcessedTransforms, InstancedColors ); + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h new file mode 100644 index 00000000..6829c82d --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h @@ -0,0 +1,74 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Chris Grebeldinger +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#pragma once + +#include "Components/SceneComponent.h" +#include "HoudiniInstancedActorComponent.generated.h" + + +UCLASS( config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniInstancedActorComponent : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + virtual void OnComponentCreated() override; + virtual void OnComponentDestroyed( bool bDestroyingHierarchy ) override; + virtual void Serialize( FArchive & Ar ) override; + + static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); + + /** Set the instances. Transforms are given in local space of this component. */ + void SetInstances( const TArray& InstanceTransforms ); + + /** Add an instance to this component. Transform is given in local space of this component. */ + int32 AddInstance( const FTransform& InstanceTransform ); + + /** Destroy all extant instances */ + void ClearInstances(); + + /** Spawn a single instance */ + AActor* SpawnInstancedActor( const FTransform& InstancedTransform ) const; + + /** Update instances of a given instancer component. (could be ISMC, IAC or MSIC) **/ + static void UpdateInstancerComponentInstances( + USceneComponent * Component, + const TArray< FTransform > & ProcessedTransforms, + const TArray & InstancedColors ); + + UPROPERTY( SkipSerialization, VisibleAnywhere, Category = Instances ) + UObject* InstancedAsset; + + UPROPERTY( SkipSerialization, VisibleInstanceOnly, Category = Instances ) + TArray< AActor* > Instances; + +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniLandscapeUtils.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniLandscapeUtils.cpp new file mode 100644 index 00000000..f7f4fa6e --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniLandscapeUtils.cpp @@ -0,0 +1,4335 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniLandscapeUtils.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineString.h" +#include "HoudiniCookHandler.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" + +#include "LandscapeInfo.h" +#include "LandscapeComponent.h" +#include "LandscapeEdit.h" +#include "LandscapeLayerInfoObject.h" +#include "LandscapeStreamingProxy.h" +#include "LightMap.h" +#include "Engine/MapBuildDataRegistry.h" + +#if WITH_EDITOR + #include "FileHelpers.h" + #include "EngineUtils.h" + #include "LandscapeEditorModule.h" + #include "LandscapeFileFormatInterface.h" +#endif + +void +FHoudiniLandscapeUtils::GetHeightfieldsInArray( + const TArray< FHoudiniGeoPartObject >& InArray, + TArray< const FHoudiniGeoPartObject* >& FoundHeightfields ) +{ + FoundHeightfields.Empty(); + + // First, we need to extract proper height data from FoundVolumes + for ( TArray< FHoudiniGeoPartObject >::TConstIterator Iter( InArray ); Iter; ++Iter ) + { + const FHoudiniGeoPartObject & HoudiniGeoPartObject = *Iter; + if ( !HoudiniGeoPartObject.IsVolume() ) + continue; + + // Retrieve node id from geo part. + //HAPI_NodeId NodeId = HoudiniGeoPartObject.HapiGeoGetNodeId( AssetId ); + HAPI_NodeId NodeId = HoudiniGeoPartObject.HapiGeoGetNodeId(); + if ( NodeId == -1 ) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, HoudiniGeoPartObject.PartId, + &CurrentVolumeInfo ) ) + continue; + + // We're only interested in heightfields + FString CurrentVolumeName; + FHoudiniEngineString( CurrentVolumeInfo.nameSH ).ToFString( CurrentVolumeName ); + if ( !CurrentVolumeName.Contains( "height" ) ) + continue; + + // We're only handling single values for now + if ( CurrentVolumeInfo.tupleSize != 1 ) + continue; + + // Terrains always have a ZSize of 1. + if ( CurrentVolumeInfo.zLength != 1 ) + continue; + + // Values should be float + if ( CurrentVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT ) + continue; + + FoundHeightfields.Add( &HoudiniGeoPartObject ); + } +} + +void +FHoudiniLandscapeUtils::GetHeightfieldsLayersInArray( + const TArray< FHoudiniGeoPartObject >& InArray, + const FHoudiniGeoPartObject& Heightfield, + TArray< const FHoudiniGeoPartObject* >& FoundLayers ) +{ + FoundLayers.Empty(); + + // We need the parent heightfield's node ID and tile attribute + HAPI_NodeId HeightFieldNodeId = Heightfield.HapiGeoGetNodeId(); + + // We need the tile attribute if the height has it + bool bParentHeightfieldHasTile = false; + int32 HeightFieldTile = -1; + { + HAPI_AttributeInfo AttribInfoTile; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); + TArray< int32 > TileValues; + + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + Heightfield, "tile", + AttribInfoTile, TileValues ); + + if ( AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0 ) + { + HeightFieldTile = TileValues[ 0 ]; + bParentHeightfieldHasTile = true; + } + } + + // Look for all the layers/masks corresponding to the current heightfield + for ( TArray< FHoudiniGeoPartObject >::TConstIterator IterLayers( InArray ); IterLayers; ++IterLayers ) + { + const FHoudiniGeoPartObject & HoudiniGeoPartObject = *IterLayers; + if ( !HoudiniGeoPartObject.IsVolume() ) + continue; + + // Retrieve node id from geo part. + HAPI_NodeId NodeId = HoudiniGeoPartObject.HapiGeoGetNodeId(); + if ( NodeId == -1 ) + continue; + + // It needs to be from the same node as the parent heightfield... + if ( NodeId != HeightFieldNodeId ) + continue; + + // If the parent had a tile info, retrieve the tile attribute for the current layer + if ( bParentHeightfieldHasTile ) + { + int32 CurrentTile = -1; + + HAPI_AttributeInfo AttribInfoTile; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); + TArray TileValues; + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject, "tile", + AttribInfoTile, TileValues ); + + if ( AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0 ) + { + CurrentTile = TileValues[ 0 ]; + } + + // Does this layer come from the same tile as the height? + if ( ( CurrentTile != HeightFieldTile ) || ( CurrentTile == -1 ) ) + continue; + } + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, HoudiniGeoPartObject.PartId, + &CurrentVolumeInfo ) ) + continue; + + // We're interesting in anything but height data + FString CurrentVolumeName; + FHoudiniEngineString( CurrentVolumeInfo.nameSH ).ToFString( CurrentVolumeName ); + if ( CurrentVolumeName.Contains( "height" ) ) + continue; + + // We're only handling single values for now + if ( CurrentVolumeInfo.tupleSize != 1 ) + continue; + + // Terrains always have a ZSize of 1. + if ( CurrentVolumeInfo.zLength != 1 ) + continue; + + // Values should be float + if ( CurrentVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT ) + continue; + + FoundLayers.Add( &HoudiniGeoPartObject ); + } +} + +void +FHoudiniLandscapeUtils::CalcHeightfieldsArrayGlobalZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums ) +{ + GlobalMinimums.Empty(); + GlobalMaximums.Empty(); + + for (TArray< FHoudiniGeoPartObject >::TConstIterator CurrentHeightfield(InHeightfieldArray); CurrentHeightfield; ++CurrentHeightfield) + { + // Get the current Heightfield GeoPartObject + if (!CurrentHeightfield) + continue; + + if (!CurrentHeightfield->IsVolume()) + continue; + + // Retrieve node id from geo part. + HAPI_NodeId NodeId = CurrentHeightfield->HapiGeoGetNodeId(); + if (NodeId == -1) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield->PartId, + &CurrentVolumeInfo)) + continue; + + // Unreal's Z values are Y in Houdini + float ymin, ymax; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield->PartId, + nullptr, &ymin, nullptr, + nullptr, &ymax, nullptr, + nullptr, nullptr, nullptr)) + continue; + + // Retrieve the volume name. + FString VolumeName; + FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); + HoudiniEngineStringPartName.ToFString(VolumeName); + + // Read the global min value for this volume + if ( !GlobalMinimums.Contains(VolumeName) ) + { + GlobalMinimums.Add(VolumeName, ymin); + } + else + { + // Update the min if necessary + if ( ymin < GlobalMinimums[VolumeName] ) + GlobalMinimums[VolumeName] = ymin; + } + + // Read the global max value for this volume + float fCurrentVolumeGlobalMax = -MAX_FLT; + if (!GlobalMaximums.Contains(VolumeName)) + { + GlobalMaximums.Add(VolumeName, ymax); + } + else + { + // Update the max if necessary + if (ymax > GlobalMaximums[VolumeName]) + GlobalMaximums[VolumeName] = ymax; + } + } +} + +void +FHoudiniLandscapeUtils::CalcHeightfieldsArrayGlobalZMinZMax( + const TArray< const FHoudiniGeoPartObject* > & InHeightfieldArray, + float& fGlobalMin, float& fGlobalMax ) +{ + // Initialize the global values + fGlobalMin = MAX_FLT; + fGlobalMax = -MAX_FLT; + + for ( TArray< const FHoudiniGeoPartObject* >::TConstIterator IterHeighfields( InHeightfieldArray ); IterHeighfields; ++IterHeighfields ) + { + // Get the current Heightfield GeoPartObject + const FHoudiniGeoPartObject* CurrentHeightfield = *IterHeighfields; + if ( !CurrentHeightfield ) + continue; + + if ( !CurrentHeightfield->IsVolume() ) + continue; + + // Retrieve node id from geo part. + HAPI_NodeId NodeId = CurrentHeightfield->HapiGeoGetNodeId(); + if ( NodeId == -1 ) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield->PartId, + &CurrentVolumeInfo ) ) + continue; + + // Unreal's Z values are Y in Houdini + float ymin, ymax; + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds( FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield->PartId, + nullptr, &ymin, nullptr, + nullptr, &ymax, nullptr, + nullptr, nullptr, nullptr ) ) + continue; + + if ( ymin < fGlobalMin ) + fGlobalMin = ymin; + + if ( ymax > fGlobalMax ) + fGlobalMax = ymax; + } + + // Set Min/Max to zero if we couldn't set the values properly + if ( fGlobalMin > fGlobalMax ) + { + fGlobalMin = 0.0f; + fGlobalMax = 0.0f; + } +} + +bool FHoudiniLandscapeUtils::GetHeightfieldData( + const FHoudiniGeoPartObject& Heightfield, + TArray& FloatValues, + HAPI_VolumeInfo& VolumeInfo, + float& FloatMin, float& FloatMax ) +{ + FloatValues.Empty(); + FloatMin = 0.0f; + FloatMax = 0.0f; + + if ( !Heightfield.IsVolume() ) + return false; + + // Retrieve node id from geo part. + HAPI_NodeId NodeId = Heightfield.HapiGeoGetNodeId(); + if ( NodeId == -1 ) + return false; + + // Retrieve the VolumeInfo + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, Heightfield.PartId, + &VolumeInfo ), false ); + + // We're only handling single values for now + if ( VolumeInfo.tupleSize != 1 ) + return false; + + // Terrains always have a ZSize of 1. + if ( VolumeInfo.zLength != 1 ) + return false; + + // Values must be float + if ( VolumeInfo.storage != HAPI_STORAGETYPE_FLOAT ) + return false; + + if ( ( VolumeInfo.xLength < 2 ) || ( VolumeInfo.yLength < 2 ) ) + return false; + + int32 SizeInPoints = VolumeInfo.xLength * VolumeInfo.yLength; + int32 TotalSize = SizeInPoints * VolumeInfo.tupleSize; + + //-------------------------------------------------------------------------------------------------- + // 1. Reading and converting the Height values from HAPI + //-------------------------------------------------------------------------------------------------- + FloatValues.SetNumUninitialized( TotalSize ); + + // Get all the heightfield data + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetHeightFieldData( + FHoudiniEngine::Get().GetSession(), + NodeId, Heightfield.PartId, + FloatValues.GetData(), + 0, SizeInPoints ), false ); + + // We will need the min and max value for the conversion to uint16 + FloatMin = FloatValues[0]; + FloatMax = FloatMin; + for ( int32 n = 0; n < FloatValues.Num(); n++ ) + { + if ( FloatValues[ n ] > FloatMax ) + FloatMax = FloatValues[ n ]; + else if ( FloatValues[ n ] < FloatMin ) + FloatMin = FloatValues[ n ]; + } + + return true; +} + +bool +FHoudiniLandscapeUtils::ConvertHeightfieldDataToLandscapeData( + const TArray< float >& HeightfieldFloatValues, + const HAPI_VolumeInfo& HeightfieldVolumeInfo, + const int32& FinalXSize, const int32& FinalYSize, + float FloatMin, float FloatMax, + TArray< uint16 >& IntHeightData, + FTransform& LandscapeTransform, + const bool& NoResize ) +{ + IntHeightData.Empty(); + LandscapeTransform.SetIdentity(); + // HF sizes needs an X/Y swap + int32 HoudiniXSize = HeightfieldVolumeInfo.yLength; + int32 HoudiniYSize = HeightfieldVolumeInfo.xLength; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ( ( HoudiniXSize < 2 ) || ( HoudiniYSize < 2 ) ) + return false; + + // Test for potential special cases... + // Just print a warning for now + if ( HeightfieldVolumeInfo.minX != 0 ) + HOUDINI_LOG_WARNING( TEXT( "Converting Landscape: heightfield's min X is not zero." ) ); + + if ( HeightfieldVolumeInfo.minY != 0 ) + HOUDINI_LOG_WARNING( TEXT( "Converting Landscape: heightfield's min Y is not zero." ) ); + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to uint16 using doubles to get the maximum precision during the conversion + //-------------------------------------------------------------------------------------------------- + + // Extract the HF's current transform + FTransform CurrentVolumeTransform; + { + HAPI_Transform HapiTransform = HeightfieldVolumeInfo.transform; + FQuat ObjectRotation( + HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[1], + HapiTransform.rotationQuaternion[2], -HapiTransform.rotationQuaternion[3]); + Swap(ObjectRotation.Y, ObjectRotation.Z); + + FVector ObjectTranslation(HapiTransform.position[0], HapiTransform.position[1], HapiTransform.position[2]); + ObjectTranslation *= 100.0f; + Swap(ObjectTranslation[2], ObjectTranslation[1]); + + FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[1], HapiTransform.scale[2]); + + CurrentVolumeTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); + } + + // The ZRange in Houdini (in m) + double MeterZRange = (double) ( FloatMax - FloatMin ); + + // The corresponding unreal digit range (as unreal uses uint16, max is 65535) + // We may want to not use the full range in order to be able to sculpt the landscape past the min/max values after. + const double dUINT16_MAX = (double)UINT16_MAX; + double DigitZRange = 49152.0; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if ( HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseFullResolution ) + DigitZRange = dUINT16_MAX - 1.0; + + // If we are not using the full range, we need to center the digit values so the terrain can be edited up and down + double DigitCenterOffset = FMath::FloorToDouble( ( dUINT16_MAX - DigitZRange ) / 2.0 ); + + // The factor used to convert from Houdini's ZRange to the desired digit range + double ZSpacing = ( MeterZRange != 0.0 ) ? ( DigitZRange / MeterZRange ) : 0.0; + + // Changes these values if the user wants to loose a lot of precision + // just to keep the same transform as the landscape input + bool bUseDefaultUE4Scaling = false; + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) + bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; + + if ( bUseDefaultUE4Scaling ) + { + //Check that our values are compatible with UE4's default scale values + if (FloatMin < -256.0f || FloatMin > 256.0f || FloatMax < -256.0f || FloatMax > 256.0f) + { + // Warn the user that the landscape conversion will have issues + // invite him to change that setting + HOUDINI_LOG_WARNING( + TEXT("The heightfield's min and max height values are too large for being used with the \"Use Default UE4 scaling\" option.\n \ + The generated Heightfield will likely be incorrectly converted to landscape unless you disable that option in the project settings and recook the asset.")); + } + + DigitZRange = dUINT16_MAX - 1.0; + DigitCenterOffset = 0; + + // Default unreal landscape scaling is -256m:256m at Scale = 100 + // We need to apply the scale back to + FloatMin = -256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f; + FloatMax = 256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f; + MeterZRange = (double)(FloatMax - FloatMin); + + ZSpacing = ((double)DigitZRange) / MeterZRange; + } + + // Converting the data from Houdini to Unreal + // For correct orientation in unreal, the point matrix has to be transposed. + IntHeightData.SetNumUninitialized( SizeInPoints ); + + int32 nUnreal = 0; + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y + int32 nHoudini = nY + nX * HoudiniYSize; + + // Get the double values in [0 - ZRange] + double DoubleValue = (double)HeightfieldFloatValues[nHoudini] - (double)FloatMin; + + // Then convert it to [0 - DesiredRange] and center it + DoubleValue = DoubleValue * ZSpacing + DigitCenterOffset; + IntHeightData[nUnreal++] = FMath::RoundToInt(DoubleValue); + } + } + + //-------------------------------------------------------------------------------------------------- + // 2. Resample / Pad the int data so that if fits unreal size requirements + //-------------------------------------------------------------------------------------------------- + + // UE has specific size requirements for landscape, + // so we might need to pad/resample the heightfield data + FVector LandscapeResizeFactor = FVector::OneVector; + FVector LandscapePositionOffsetInPixels = FVector::ZeroVector; + if (!NoResize) + { + // Try to resize the data + if ( !FHoudiniLandscapeUtils::ResizeHeightDataForLandscape( + IntHeightData, + HoudiniXSize, HoudiniYSize, FinalXSize, FinalYSize, + LandscapeResizeFactor, LandscapePositionOffsetInPixels)) + return false; + } + + //-------------------------------------------------------------------------------------------------- + // 3. Calculating the proper transform for the landscape to be sized and positionned properly + //-------------------------------------------------------------------------------------------------- + + // Scale: + // Calculating the equivalent scale to match Houdini's Terrain Size in Unreal + FVector LandscapeScale; + + // Unreal has a X/Y resolution of 1m per point while Houdini is dependant on the heighfield's grid spacing + LandscapeScale.X = CurrentVolumeTransform.GetScale3D().X * 2.0f; + LandscapeScale.Y = CurrentVolumeTransform.GetScale3D().Y * 2.0f; + + // Calculating the Z Scale so that the Z values in Unreal are the same as in Houdini + // Unreal has a default Z range is 512m for a scale of a 100% + LandscapeScale.Z = (float)( (double)( dUINT16_MAX / DigitZRange ) * MeterZRange / 512.0 ); + if ( bUseDefaultUE4Scaling ) + LandscapeScale.Z = CurrentVolumeTransform.GetScale3D().Z * 2.0f; + LandscapeScale *= 100.f; + + // If the data was resized and not expanded, we need to modify the landscape's scale + LandscapeScale *= LandscapeResizeFactor; + + // Don't allow a zero scale, as this results in divide by 0 operations in FMatrix::InverseFast in the landscape component. + if (FMath::IsNearlyZero(LandscapeScale.Z)) + LandscapeScale.Z = 1.0f; + + // We'll use the position from Houdini, but we will need to offset the Z Position to center the + // values properly as the data has been offset by the conversion to uint16 + FVector LandscapePosition = CurrentVolumeTransform.GetLocation(); + //LandscapePosition.Z = 0.0f; + + // We need to calculate the position offset so that Houdini and Unreal have the same Zero position + // In Unreal, zero has a height value of 32768. + // These values are then divided by 128 internally, and then multiplied by the Landscape's Z scale + // ( DIGIT - 32768 ) / 128 * ZScale = ZOffset + + // We need the Digit (Unreal) value of Houdini's zero for the scale calculation + // ( float and int32 are used for this because 0 might be out of the landscape Z range! + // when using the full range, this would cause an overflow for a uint16!! ) + float HoudiniZeroValueInDigit = (float)FMath::RoundToInt((0.0 - (double)FloatMin) * ZSpacing + DigitCenterOffset); + float ZOffset = -( HoudiniZeroValueInDigit - 32768.0f ) / 128.0f * LandscapeScale.Z; + + LandscapePosition.Z += ZOffset; + + // If we have padded the data when resizing the landscape, we need to offset the position because of + // the added values on the topLeft Corner of the Landscape + if ( LandscapePositionOffsetInPixels != FVector::ZeroVector ) + { + FVector LandscapeOffset = LandscapePositionOffsetInPixels * LandscapeScale; + LandscapeOffset.Z = 0.0f; + + LandscapePosition += LandscapeOffset; + } + + // Landscape rotation + //FRotator LandscapeRotation( 0.0, -90.0, 0.0 ); + //Landscape->SetActorRelativeRotation( LandscapeRotation ); + + // We can now set the Landscape position + LandscapeTransform.SetLocation( LandscapePosition ); + LandscapeTransform.SetScale3D( LandscapeScale ); + + return true; +} + +bool FHoudiniLandscapeUtils::ConvertHeightfieldLayerToLandscapeLayer( + const TArray& FloatLayerData, + const int32& HoudiniXSize, const int32& HoudiniYSize, + const float& LayerMin, const float& LayerMax, + const int32& LandscapeXSize, const int32& LandscapeYSize, + TArray& LayerData, const bool& NoResize ) +{ + // Convert the float data to uint8 + LayerData.SetNumUninitialized( HoudiniXSize * HoudiniYSize ); + + // Calculating the factor used to convert from Houdini's ZRange to [0 255] + double LayerZRange = ( LayerMax - LayerMin ); + double LayerZSpacing = ( LayerZRange != 0.0 ) ? ( 255.0 / (double)( LayerZRange ) ) : 0.0; + + int32 nUnrealIndex = 0; + for ( int32 nY = 0; nY < HoudiniYSize; nY++ ) + { + for ( int32 nX = 0; nX < HoudiniXSize; nX++ ) + { + // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y + int32 nHoudini = nY + nX * HoudiniYSize; + + // Get the double values in [0 - ZRange] + double DoubleValue = (double)FMath::Clamp(FloatLayerData[ nHoudini ], LayerMin, LayerMax) - (double)LayerMin; + + // Then convert it to [0 - 255] + DoubleValue *= LayerZSpacing; + + LayerData[ nUnrealIndex++ ] = FMath::RoundToInt( DoubleValue ); + } + } + + // Finally, resize the data to fit with the new landscape size if needed + if ( NoResize ) + return true; + + return FHoudiniLandscapeUtils::ResizeLayerDataForLandscape( + LayerData, HoudiniXSize, HoudiniYSize, + LandscapeXSize, LandscapeYSize ); +} + +bool +FHoudiniLandscapeUtils::GetNonWeightBlendedLayerNames( const FHoudiniGeoPartObject& HeightfieldGeoPartObject, TArray& NonWeightBlendedLayerNames ) +{ + // See if we can find the NonWeightBlendedLayer prim attribute on the heightfield + HAPI_NodeId HeightfieldNodeId = HeightfieldGeoPartObject.HapiGeoGetNodeId(); + + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + if ( !HeightfieldGeoPartObject.HapiPartGetInfo( PartInfo ) ) + return false; + + HAPI_PartId PartId = HeightfieldGeoPartObject.GetPartId(); + + // Get All attribute names for that part + int32 nAttribCount = PartInfo.attributeCounts[ HAPI_ATTROWNER_PRIM ]; + + TArray AttribNameSHArray; + AttribNameSHArray.SetNum( nAttribCount ); + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + HeightfieldNodeId, PartInfo.id, HAPI_ATTROWNER_PRIM, + AttribNameSHArray.GetData(), nAttribCount ) ) + return false; + + // Looking for all the attributes that starts with unreal_landscape_layer_nonweightblended + for ( int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx ) + { + FString HapiString = TEXT(""); + FHoudiniEngineString HoudiniEngineString( AttribNameSHArray[ Idx ] ); + HoudiniEngineString.ToFString( HapiString ); + + if ( !HapiString.StartsWith( "unreal_landscape_layer_nonweightblended", ESearchCase::IgnoreCase ) ) + continue; + + // Get the Attribute Info + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + HeightfieldNodeId, PartId, TCHAR_TO_UTF8( *HapiString ), + HAPI_ATTROWNER_PRIM, &AttribInfo ), false ); + + if ( AttribInfo.storage != HAPI_STORAGETYPE_STRING ) + break; + + // Initialize a string handle array + TArray< HAPI_StringHandle > HapiSHArray; + HapiSHArray.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize ); + + // Get the string handle(s) + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + HeightfieldNodeId, PartId, TCHAR_TO_UTF8( *HapiString ), &AttribInfo, + HapiSHArray.GetData(), 0, AttribInfo.count ), false ); + + // Convert them to FString + for ( int32 IdxSH = 0; IdxSH < HapiSHArray.Num(); IdxSH++ ) + { + FString CurrentString; + FHoudiniEngineString HEngineString( HapiSHArray[ IdxSH ] ); + HEngineString.ToFString( CurrentString ); + + TArray Tokens; + CurrentString.ParseIntoArray( Tokens, TEXT(" "), true ); + + for( int32 n = 0; n < Tokens.Num(); n++ ) + NonWeightBlendedLayerNames.Add( Tokens[ n ] ); + } + + // We found the attribute, exit + break; + } + + return true; +} + +//------------------------------------------------------------------------------------------------------------------- +// From LandscapeEditorUtils.h +// +// Helpers function for FHoudiniEngineUtils::ResizeHeightDataForLandscape +//------------------------------------------------------------------------------------------------------------------- +template +void ExpandData( T* OutData, const T* InData, + int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, + int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY ) +{ + const int32 OldWidth = OldMaxX - OldMinX + 1; + const int32 OldHeight = OldMaxY - OldMinY + 1; + const int32 NewWidth = NewMaxX - NewMinX + 1; + const int32 NewHeight = NewMaxY - NewMinY + 1; + const int32 OffsetX = NewMinX - OldMinX; + const int32 OffsetY = NewMinY - OldMinY; + + for ( int32 Y = 0; Y < NewHeight; ++Y ) + { + const int32 OldY = FMath::Clamp( Y + OffsetY, 0, OldHeight - 1 ); + + // Pad anything to the left + const T PadLeft = InData[ OldY * OldWidth + 0 ]; + for ( int32 X = 0; X < -OffsetX; ++X ) + { + OutData[ Y * NewWidth + X ] = PadLeft; + } + + // Copy one row of the old data + { + const int32 X = FMath::Max( 0, -OffsetX ); + const int32 OldX = FMath::Clamp( X + OffsetX, 0, OldWidth - 1 ); + FMemory::Memcpy( &OutData[ Y * NewWidth + X ], &InData[ OldY * OldWidth + OldX ], FMath::Min( OldWidth, NewWidth ) * sizeof( T ) ); + } + + const T PadRight = InData[ OldY * OldWidth + OldWidth - 1 ]; + for ( int32 X = -OffsetX + OldWidth; X < NewWidth; ++X ) + { + OutData[ Y * NewWidth + X ] = PadRight; + } + } +} + +template +TArray ExpandData(const TArray& Data, + int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, + int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY, + int32* PadOffsetX = nullptr, int32* PadOffsetY = nullptr ) +{ + const int32 NewWidth = NewMaxX - NewMinX + 1; + const int32 NewHeight = NewMaxY - NewMinY + 1; + + TArray Result; + Result.Empty( NewWidth * NewHeight ); + Result.AddUninitialized( NewWidth * NewHeight ); + + ExpandData( Result.GetData(), Data.GetData(), + OldMinX, OldMinY, OldMaxX, OldMaxY, + NewMinX, NewMinY, NewMaxX, NewMaxY ); + + // Return the padding so we can offset the terrain position after + if ( PadOffsetX ) + *PadOffsetX = NewMinX; + + if ( PadOffsetY ) + *PadOffsetY = NewMinY; + + return Result; +} + +template +TArray ResampleData( const TArray& Data, int32 OldWidth, int32 OldHeight, int32 NewWidth, int32 NewHeight ) +{ + TArray Result; + Result.Empty( NewWidth * NewHeight ); + Result.AddUninitialized( NewWidth * NewHeight ); + + const float XScale = (float)( OldWidth - 1 ) / ( NewWidth - 1 ); + const float YScale = (float)( OldHeight - 1 ) / ( NewHeight - 1 ); + for ( int32 Y = 0; Y < NewHeight; ++Y ) + { + for ( int32 X = 0; X < NewWidth; ++X ) + { + const float OldY = Y * YScale; + const float OldX = X * XScale; + const int32 X0 = FMath::FloorToInt( OldX ); + const int32 X1 = FMath::Min( FMath::FloorToInt( OldX ) + 1, OldWidth - 1 ); + const int32 Y0 = FMath::FloorToInt( OldY ); + const int32 Y1 = FMath::Min( FMath::FloorToInt( OldY ) + 1, OldHeight - 1 ); + const T& Original00 = Data[ Y0 * OldWidth + X0 ]; + const T& Original10 = Data[ Y0 * OldWidth + X1 ]; + const T& Original01 = Data[ Y1 * OldWidth + X0 ]; + const T& Original11 = Data[ Y1 * OldWidth + X1 ]; + Result[ Y * NewWidth + X ] = FMath::BiLerp( Original00, Original10, Original01, Original11, FMath::Fractional( OldX ), FMath::Fractional( OldY ) ); + } + } + + return Result; +} + +//------------------------------------------------------------------------------------------------------------------- +bool +FHoudiniLandscapeUtils::CalcLandscapeSizeFromHeightfieldSize( + const int32& SizeX, const int32& SizeY, + int32& NewSizeX, int32& NewSizeY, + int32& NumberOfSectionsPerComponent, + int32& NumberOfQuadsPerSection ) +{ + if ( (SizeX < 2) || (SizeY < 2) ) + return false; + + NumberOfSectionsPerComponent = 1; + NumberOfQuadsPerSection = 1; + NewSizeX = -1; + NewSizeY = -1; + + // Unreal's default sizes + int32 SectionSizes[] = { 7, 15, 31, 63, 127, 255 }; + int32 NumSections[] = { 1, 2 }; + + // Component count used to calculate the final size of the landscape + int32 ComponentsCountX = 1; + int32 ComponentsCountY = 1; + + // Lambda for clamping the number of component in X/Y + auto ClampLandscapeSize = [&]() + { + // Max size is either whole components below 8192 verts, or 32 components + ComponentsCountX = FMath::Clamp(ComponentsCountX, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumberOfSectionsPerComponent * NumberOfQuadsPerSection)))); + ComponentsCountY = FMath::Clamp(ComponentsCountY, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumberOfSectionsPerComponent * NumberOfQuadsPerSection)))); + }; + + // Try to find a section size and number of sections that exactly matches the dimensions of the heightfield + bool bFoundMatch = false; + for (int32 SectionSizesIdx = UE_ARRAY_COUNT(SectionSizes) - 1; SectionSizesIdx >= 0; SectionSizesIdx--) + { + for (int32 NumSectionsIdx = UE_ARRAY_COUNT(NumSections) - 1; NumSectionsIdx >= 0; NumSectionsIdx--) + { + int32 ss = SectionSizes[SectionSizesIdx]; + int32 ns = NumSections[NumSectionsIdx]; + + if (((SizeX - 1) % (ss * ns)) == 0 && ((SizeX - 1) / (ss * ns)) <= 32 && + ((SizeY - 1) % (ss * ns)) == 0 && ((SizeY - 1) / (ss * ns)) <= 32) + { + bFoundMatch = true; + NumberOfQuadsPerSection = ss; + NumberOfSectionsPerComponent = ns; + ComponentsCountX = (SizeX - 1) / (ss * ns); + ComponentsCountY = (SizeY - 1) / (ss * ns); + ClampLandscapeSize(); + break; + } + } + if (bFoundMatch) + { + break; + } + } + + if (!bFoundMatch) + { + // if there was no exact match, try increasing the section size until we encompass the whole heightmap + const int32 CurrentSectionSize = NumberOfQuadsPerSection; + const int32 CurrentNumSections = NumberOfSectionsPerComponent; + for (int32 SectionSizesIdx = 0; SectionSizesIdx < UE_ARRAY_COUNT(SectionSizes); SectionSizesIdx++) + { + if (SectionSizes[SectionSizesIdx] < CurrentSectionSize) + { + continue; + } + + const int32 ComponentsX = FMath::DivideAndRoundUp((SizeX - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); + const int32 ComponentsY = FMath::DivideAndRoundUp((SizeY - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); + if (ComponentsX <= 32 && ComponentsY <= 32) + { + bFoundMatch = true; + NumberOfQuadsPerSection = SectionSizes[SectionSizesIdx]; + ComponentsCountX = ComponentsX; + ComponentsCountY = ComponentsY; + ClampLandscapeSize(); + break; + } + } + } + + if (!bFoundMatch) + { + // if the heightmap is very large, fall back to using the largest values we support + const int32 MaxSectionSize = SectionSizes[UE_ARRAY_COUNT(SectionSizes) - 1]; + const int32 MaxNumSubSections = NumSections[UE_ARRAY_COUNT(NumSections) - 1]; + const int32 ComponentsX = FMath::DivideAndRoundUp((SizeX - 1), MaxSectionSize * MaxNumSubSections); + const int32 ComponentsY = FMath::DivideAndRoundUp((SizeY - 1), MaxSectionSize * MaxNumSubSections); + + bFoundMatch = true; + NumberOfQuadsPerSection = MaxSectionSize; + NumberOfSectionsPerComponent = MaxNumSubSections; + ComponentsCountX = ComponentsX; + ComponentsCountY = ComponentsY; + ClampLandscapeSize(); + } + + if (!bFoundMatch) + { + // Using default size just to not crash.. + NewSizeX = 512; + NewSizeY = 512; + NumberOfSectionsPerComponent = 1; + NumberOfQuadsPerSection = 511; + ComponentsCountX = 1; + ComponentsCountY = 1; + } + else + { + // Calculating the desired size + int32 QuadsPerComponent = NumberOfSectionsPerComponent * NumberOfQuadsPerSection; + + NewSizeX = ComponentsCountX * QuadsPerComponent + 1; + NewSizeY = ComponentsCountY * QuadsPerComponent + 1; + } + + return bFoundMatch; +} + +//------------------------------------------------------------------------------------------------------------------- +bool +FHoudiniLandscapeUtils::ResizeHeightDataForLandscape( + TArray& HeightData, + const int32& SizeX, const int32& SizeY, + const int32& NewSizeX, const int32& NewSizeY, + FVector& LandscapeResizeFactor, + FVector& LandscapePositionOffset ) +{ + LandscapeResizeFactor = FVector::OneVector; + LandscapePositionOffset = FVector::ZeroVector; + + if ( HeightData.Num() <= 4 ) + return false; + + if ( ( SizeX < 2 ) || ( SizeY < 2 ) ) + return false; + + // No need to resize anything + if ( SizeX == NewSizeX && SizeY == NewSizeY ) + return true; + + // Do we need to resize/expand the data to the new size? + bool bForceResample = false; + bool bResample = bForceResample ? true : ( ( NewSizeX <= SizeX ) && ( NewSizeY <= SizeY ) ); + + TArray NewData; + if ( !bResample ) + { + // Expanding the data by padding + NewData.SetNumUninitialized( NewSizeX * NewSizeY ); + + const int32 OffsetX = (int32)( NewSizeX - SizeX ) / 2; + const int32 OffsetY = (int32)( NewSizeY - SizeY ) / 2; + + // Store the offset in pixel due to the padding + int32 PadOffsetX = 0; + int32 PadOffsetY = 0; + + // Expanding the Data + NewData = ExpandData( + HeightData, 0, 0, SizeX - 1, SizeY - 1, + -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1, + &PadOffsetX, &PadOffsetY ); + + // We will need to offset the landscape position due to the value added by the padding + LandscapePositionOffset.X = (float)PadOffsetX; + LandscapePositionOffset.Y = (float)PadOffsetY; + + // Notify the user that the data was padded + HOUDINI_LOG_WARNING( + TEXT("Landscape data was padded from ( %d x %d ) to ( %d x %d )."), + SizeX, SizeY, NewSizeX, NewSizeY ); + } + else + { + // Resampling the data + NewData.SetNumUninitialized( NewSizeX * NewSizeY ); + NewData = ResampleData( HeightData, SizeX, SizeY, NewSizeX, NewSizeY ); + + // The landscape has been resized, we'll need to take that into account when sizing it + LandscapeResizeFactor.X = (float)SizeX / (float)NewSizeX; + LandscapeResizeFactor.Y = (float)SizeY / (float)NewSizeY; + LandscapeResizeFactor.Z = 1.0f; + + // Notify the user if the heightfield data was resized + HOUDINI_LOG_WARNING( + TEXT("Landscape data was resized from ( %d x %d ) to ( %d x %d )."), + SizeX, SizeY, NewSizeX, NewSizeY ); + } + + // Replaces Old data with the new one + HeightData = NewData; + + return true; +} + +bool +FHoudiniLandscapeUtils::ResizeLayerDataForLandscape( + TArray< uint8 >& LayerData, + const int32& SizeX, const int32& SizeY, + const int32& NewSizeX, const int32& NewSizeY ) +{ + if ( ( NewSizeX == SizeX ) && ( NewSizeY == SizeY ) ) + return true; + + bool bForceResample = false; + bool bResample = bForceResample ? true : ( ( NewSizeX <= SizeX ) && ( NewSizeY <= SizeY ) ); + + TArray NewData; + if (!bResample) + { + NewData.SetNumUninitialized( NewSizeX * NewSizeY ); + + const int32 OffsetX = (int32)( NewSizeX - SizeX ) / 2; + const int32 OffsetY = (int32)( NewSizeY - SizeY ) / 2; + + // Expanding the Data + NewData = ExpandData( + LayerData, + 0, 0, SizeX - 1, SizeY - 1, + -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1 ); + } + else + { + // Resampling the data + NewData.SetNumUninitialized( NewSizeX * NewSizeY ); + NewData = ResampleData( LayerData, SizeX, SizeY, NewSizeX, NewSizeY ); + } + + LayerData = NewData; + + return true; +} + +#if WITH_EDITOR +bool +FHoudiniLandscapeUtils::CreateHeightfieldFromLandscape( + ALandscapeProxy* LandscapeProxy, HAPI_NodeId& CreatedHeightfieldNodeId ) +{ + if ( !LandscapeProxy ) + return false; + + // Export the whole landscape and its layer as a single heightfield + + //-------------------------------------------------------------------------------------------------- + // 1. Extracting the height data + //-------------------------------------------------------------------------------------------------- + TArray HeightData; + int32 XSize, YSize; + FVector Min, Max; + if ( !GetLandscapeData( LandscapeProxy, HeightData, XSize, YSize, Min, Max ) ) + return false; + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the height uint16 data to float + //-------------------------------------------------------------------------------------------------- + TArray HeightfieldFloatValues; + HAPI_VolumeInfo HeightfieldVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); + FTransform LandscapeTransform = LandscapeProxy->ActorToWorld(); + FVector CenterOffset = FVector::ZeroVector; + if ( !ConvertLandscapeDataToHeightfieldData( + HeightData, XSize, YSize, Min, Max, LandscapeTransform, + HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset ) ) + return false; + + //-------------------------------------------------------------------------------------------------- + // 3. Create the Heightfield Input Node + //-------------------------------------------------------------------------------------------------- + HAPI_NodeId HeightFieldId = -1; + HAPI_NodeId HeightId = -1; + HAPI_NodeId MaskId = -1; + HAPI_NodeId MergeId = -1; + if ( !CreateHeightfieldInputNode( -1, LandscapeProxy->GetName(), XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId ) ) + return false; + + //-------------------------------------------------------------------------------------------------- + // 4. Set the HeightfieldData in Houdini + //-------------------------------------------------------------------------------------------------- + // Set the Height volume's data + HAPI_PartId PartId = 0; + if ( !SetHeighfieldData( HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height") ) ) + return false; + + // Add the materials used + UMaterialInterface* LandscapeMat = LandscapeProxy->GetLandscapeMaterial(); + UMaterialInterface* LandscapeHoleMat = LandscapeProxy->GetLandscapeHoleMaterial(); + AddLandscapeMaterialAttributesToVolume( HeightId, PartId, LandscapeMat, LandscapeHoleMat ); + + // Add the landscape's actor tags as prim attributes if we have any + FHoudiniEngineUtils::CreateGroupOrAttributeFromTags(HeightId, PartId, LandscapeProxy->Tags, true); + + // Commit the height volume + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), HeightId ), false ); + + //-------------------------------------------------------------------------------------------------- + // 5. Extract and convert all the layers + //-------------------------------------------------------------------------------------------------- + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if ( !LandscapeInfo ) + return false; + + bool MaskInitialized = false; + int32 MergeInputIndex = 2; + int32 NumLayers = LandscapeInfo->Layers.Num(); + for ( int32 n = 0; n < NumLayers; n++ ) + { + // 1. Extract the uint8 values from the layer + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if ( !GetLandscapeLayerData( LandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName ) ) + continue; + + // 2. Convert unreal uint8 values to floats + // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float + HAPI_VolumeInfo CurrentLayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); + TArray < float > CurrentLayerFloatData; + if ( !ConvertLandscapeLayerDataToHeightfieldData( + CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, + CurrentLayerFloatData, CurrentLayerVolumeInfo ) ) + continue; + + // We reuse the height layer's transform + CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform; + + // 3. See if we need to create an input volume, or can reuse the HF's default mask volume + bool IsMask = false; + if ( LayerName.Equals( TEXT("mask"), ESearchCase::IgnoreCase ) ) + IsMask = true; + + HAPI_NodeId LayerVolumeNodeId = -1; + if ( !IsMask ) + { + // Current layer is not mask, so we need to create a new input volume + std::string LayerNameStr; + FHoudiniEngineUtils::ConvertUnrealString(LayerName, LayerNameStr); + + FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f ); + } + else + { + // Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node + LayerVolumeNodeId = MaskId; + } + + // Check if we have a valid id for the input volume. + if ( !FHoudiniEngineUtils::IsHoudiniNodeValid( LayerVolumeNodeId ) ) + continue; + + // 4. Set the layer/mask heighfield data in Houdini + HAPI_PartId CurrentPartId = 0; + if ( !SetHeighfieldData( LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName ) ) + continue; + + // Also add the material attributes to the layer volumes + AddLandscapeMaterialAttributesToVolume(LayerVolumeNodeId, PartId, LandscapeMat, LandscapeHoleMat); + + // Add the landscape's actor tags as prim attributes if we have any + FHoudiniEngineUtils::CreateGroupOrAttributeFromTags(LayerVolumeNodeId, PartId, LandscapeProxy->Tags, true); + + // Commit the volume's geo + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId ), false); + + if ( !IsMask ) + { + // We had to create a new volume for this layer, so we need to connect it to the HF's merge node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, MergeInputIndex, LayerVolumeNodeId, 0 ), false); + + MergeInputIndex++; + } + else + { + MaskInitialized = true; + } + } + + // We need to have a mask layer as it is required for proper heightfield functionalities + // Setting the volume info on the mask is needed for the HF to have proper transform in H! + // If we didn't create a mask volume before, send a default one now + if (!MaskInitialized) + { + MaskInitialized = InitDefaultHeightfieldMask( HeightfieldVolumeInfo, MaskId ); + + // Add the materials used + AddLandscapeMaterialAttributesToVolume( MaskId, PartId, LandscapeMat, LandscapeHoleMat ); + + // Add the landscape's actor tags as prim attributes if we have any + FHoudiniEngineUtils::CreateGroupOrAttributeFromTags(MaskId, PartId, LandscapeProxy->Tags, true); + + // Commit the mask volume's geo + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), MaskId ), false ); + } + + HAPI_TransformEuler HAPIObjectTransform; + FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform); + //FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform ); + LandscapeTransform.SetScale3D(FVector::OneVector); + FHoudiniEngineUtils::TranslateUnrealTransform( LandscapeTransform, HAPIObjectTransform ); + HAPIObjectTransform.position[1] = 0.0f; + + HAPI_NodeId ParentObjNodeId = FHoudiniEngineUtils::HapiGetParentNodeId( HeightFieldId ); + FHoudiniApi::SetObjectTransform(FHoudiniEngine::Get().GetSession(), ParentObjNodeId, &HAPIObjectTransform); + + // Since HF are centered but landscape aren't, we need to set the HF's center parameter + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 0, CenterOffset.X); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 1, 0.0); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 2, CenterOffset.Y); + + // Finally, cook the Heightfield node + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), HeightFieldId, nullptr), false ); + + CreatedHeightfieldNodeId = HeightFieldId; + + return true; +} + +bool +FHoudiniLandscapeUtils::CreateHeightfieldFromLandscapeComponentArray( + ALandscapeProxy* LandscapeProxy, + TSet< ULandscapeComponent * >& LandscapeComponentArray, + HAPI_NodeId& CreatedHeightfieldNodeId ) +{ + if ( LandscapeComponentArray.Num() <= 0 ) + return false; + + //-------------------------------------------------------------------------------------------------- + // Each selected component will be exported as tiled volumes in a single heightfield + //-------------------------------------------------------------------------------------------------- + FTransform LandscapeTransform = LandscapeProxy->GetTransform(); + + // + HAPI_NodeId HeightfieldNodeId = -1; + HAPI_NodeId HeightfieldeMergeId = -1; + + int32 MergeInputIndex = 0; + bool bAllComponentCreated = true; + for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) + { + ULandscapeComponent * CurrentComponent = LandscapeProxy->LandscapeComponents[ ComponentIdx ]; + if ( !CurrentComponent ) + continue; + + if ( !LandscapeComponentArray.Contains( CurrentComponent ) ) + continue; + + if ( !CreateHeightfieldFromLandscapeComponent( CurrentComponent, ComponentIdx, HeightfieldNodeId, HeightfieldeMergeId, MergeInputIndex ) ) + bAllComponentCreated = false; + } + + // Check that we have a valid id for the input Heightfield. + if ( FHoudiniEngineUtils::IsHoudiniNodeValid( HeightfieldNodeId ) ) + CreatedHeightfieldNodeId = HeightfieldNodeId; + + // Set the HF's parent OBJ's tranform to the Landscape's transform + HAPI_TransformEuler HAPIObjectTransform; + FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform); + //FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform ); + LandscapeTransform.SetScale3D( FVector::OneVector ); + FHoudiniEngineUtils::TranslateUnrealTransform( LandscapeTransform, HAPIObjectTransform ); + HAPIObjectTransform.position[ 1 ] = 0.0f; + + HAPI_NodeId ParentObjNodeId = FHoudiniEngineUtils::HapiGetParentNodeId( HeightfieldNodeId ); + FHoudiniApi::SetObjectTransform( FHoudiniEngine::Get().GetSession(), ParentObjNodeId, &HAPIObjectTransform ); + + return bAllComponentCreated; +} + + +bool +FHoudiniLandscapeUtils::CreateHeightfieldFromLandscapeComponent( + ULandscapeComponent * LandscapeComponent, + const int32& ComponentIndex, + HAPI_NodeId& HeightFieldId, + HAPI_NodeId& MergeId, + int32& MergeInputIndex ) +{ + if ( !LandscapeComponent ) + return false; + + //-------------------------------------------------------------------------------------------------- + // 1. Extract the height data + //-------------------------------------------------------------------------------------------------- + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + LandscapeComponent->GetComponentExtent( MinX, MinY, MaxX, MaxY ); + + ULandscapeInfo* LandscapeInfo = LandscapeComponent->GetLandscapeInfo(); + if ( !LandscapeInfo ) + return false; + + TArray HeightData; + int32 XSize, YSize; + if ( !GetLandscapeData( LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize ) ) + return false; + + FVector Origin = LandscapeComponent->Bounds.Origin; + FVector Extents = LandscapeComponent->Bounds.BoxExtent; + FVector Min = Origin - Extents; + FVector Max = Origin + Extents; + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the landscape's height uint16 data to float + //-------------------------------------------------------------------------------------------------- + TArray HeightfieldFloatValues; + HAPI_VolumeInfo HeightfieldVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); + FTransform LandscapeComponentTransform = LandscapeComponent->GetComponentTransform(); + + FVector CenterOffset = FVector::ZeroVector; + if ( !ConvertLandscapeDataToHeightfieldData( + HeightData, XSize, YSize, Min, Max, LandscapeComponentTransform, + HeightfieldFloatValues, HeightfieldVolumeInfo, + CenterOffset ) ) + return false; + + // We need to modify the Volume's position to the Component's position relative to the Landscape's position + FVector RelativePosition = LandscapeComponent->GetRelativeTransform().GetLocation(); + HeightfieldVolumeInfo.transform.position[1] = RelativePosition.X; + HeightfieldVolumeInfo.transform.position[0] = RelativePosition.Y; + HeightfieldVolumeInfo.transform.position[2] = 0.0f; + + //-------------------------------------------------------------------------------------------------- + // 3. Create the Heightfield Input Node + //-------------------------------------------------------------------------------------------------- + HAPI_NodeId HeightId = -1; + HAPI_NodeId MaskId = -1; + bool CreatedHeightfieldNode = false; + if ( HeightFieldId < 0 || MergeId < 0 ) + { + // We haven't created the HF input node yet, do it now + if (!CreateHeightfieldInputNode(-1, TEXT("LandscapeComponents"), XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) + return false; + + MergeInputIndex = 2; + CreatedHeightfieldNode = true; + } + else + { + // Heightfield node was previously created, create additionnal height and a mask volumes for it + FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, &HeightId, "height", XSize, YSize, 1.0f ); + + FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, &MaskId, "mask", XSize, YSize, 1.0f ); + + // Connect the two newly created volumes to the HF's merge node + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, MergeInputIndex++, HeightId, 0 ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, MergeInputIndex++, MaskId, 0 ), false ); + } + + + //-------------------------------------------------------------------------------------------------- + // 4. Set the HeightfieldData in Houdini + //-------------------------------------------------------------------------------------------------- + // Set the Height volume's data + HAPI_PartId PartId = 0; + if (!SetHeighfieldData( HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height") ) ) + return false; + + // Add the materials used + UMaterialInterface* LandscapeMat = LandscapeComponent->GetLandscapeMaterial(); + UMaterialInterface* LandscapeHoleMat = LandscapeComponent->GetLandscapeHoleMaterial(); + AddLandscapeMaterialAttributesToVolume(HeightId, PartId, LandscapeMat, LandscapeHoleMat); + + // Add the tile attribute + AddLandscapeTileAttribute(HeightId, PartId, ComponentIndex); + + // Add the landscape component extent attribute + AddLandscapeComponentExtentAttributes( HeightId, PartId, MinX, MaxX, MinY, MaxY ); + + // Add the component's tag as prim attributes if we have any + FHoudiniEngineUtils::CreateGroupOrAttributeFromTags(HeightId, PartId, LandscapeComponent->ComponentTags, true); + + // Commit the height volume + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), HeightId), false); + + //-------------------------------------------------------------------------------------------------- + // 4. Extract and convert all the layers to HF masks + //-------------------------------------------------------------------------------------------------- + bool MaskInitialized = false; + int32 NumLayers = LandscapeInfo->Layers.Num(); + for ( int32 n = 0; n < NumLayers; n++ ) + { + // 1. Extract the uint8 values from the layer + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if ( !GetLandscapeLayerData(LandscapeInfo, n, MinX, MinY, MaxX, MaxY, CurrentLayerIntData, LayerUsageDebugColor, LayerName) ) + continue; + + // 2. Convert unreal uint8 to float + // If the layer came from Houdini, additional info might have been stored in the DebugColor + HAPI_VolumeInfo CurrentLayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); + TArray < float > CurrentLayerFloatData; + if ( !ConvertLandscapeLayerDataToHeightfieldData( + CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, + CurrentLayerFloatData, CurrentLayerVolumeInfo ) ) + continue; + + // We reuse the transform used for the height volume + CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform; + + // 3. See if we need to create an input volume, or if we can reuse the HF's default mask volume + bool IsMask = false; + if ( LayerName.Equals( TEXT("mask"), ESearchCase::IgnoreCase ) ) + IsMask = true; + + HAPI_NodeId LayerVolumeNodeId = -1; + if ( !IsMask ) + { + // Current layer is not mask, so we need to create a new input volume + std::string LayerNameStr; + FHoudiniEngineUtils::ConvertUnrealString( LayerName, LayerNameStr ); + + FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f ); + } + else + { + // Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node + LayerVolumeNodeId = MaskId; + } + + // Check if we have a valid id for the input volume. + if ( !FHoudiniEngineUtils::IsHoudiniNodeValid( LayerVolumeNodeId ) ) + continue; + + // 4. Set the layer/mask heighfield data in Houdini + HAPI_PartId CurrentPartId = 0; + if ( !SetHeighfieldData( LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName ) ) + continue; + + // Add the materials used + AddLandscapeMaterialAttributesToVolume( LayerVolumeNodeId, PartId, LandscapeMat, LandscapeHoleMat ); + + // Add the tile attribute + AddLandscapeTileAttribute( LayerVolumeNodeId, PartId, ComponentIndex ); + + // Add the landscape component extent attribute + AddLandscapeComponentExtentAttributes( LayerVolumeNodeId, PartId, MinX, MaxX, MinY, MaxY ); + + // Add the component's tag as prim attributes if we have any + FHoudiniEngineUtils::CreateGroupOrAttributeFromTags(LayerVolumeNodeId, PartId, LandscapeComponent->ComponentTags, true); + + // Commit the volume's geo + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId ), false); + + if ( !IsMask ) + { + // We had to create a new volume for this layer, so we need to connect it to the HF's merge node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, MergeInputIndex, LayerVolumeNodeId, 0 ), false); + + MergeInputIndex++; + } + else + { + MaskInitialized = true; + } + } + + // We need to have a mask layer as it is required for proper heightfield functionalities + // Setting the volume info on the mask is needed for the HF to have proper transform in H! + // If we didn't create a mask volume before, send a default one now + if (!MaskInitialized) + { + MaskInitialized = InitDefaultHeightfieldMask( HeightfieldVolumeInfo, MaskId ); + + // Add the materials used + AddLandscapeMaterialAttributesToVolume( MaskId, PartId, LandscapeMat, LandscapeHoleMat ); + + // Add the tile attribute + AddLandscapeTileAttribute( MaskId, PartId, ComponentIndex ); + + // Add the landscape component extent attribute + AddLandscapeComponentExtentAttributes( MaskId, PartId, MinX, MaxX, MinY, MaxY ); + + // Add the component's tag as prim attributes if we have any + FHoudiniEngineUtils::CreateGroupOrAttributeFromTags(MaskId, PartId, LandscapeComponent->ComponentTags, true); + + // Commit the mask volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), MaskId), false); + } + + if ( CreatedHeightfieldNode ) + { + // Since HF are centered but landscape arent, we need to set the HF's center parameter + // Do it only once after creating the Heightfield node + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 0, CenterOffset.X); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 1, 0.0); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 2, CenterOffset.Y); + } + + // Finally, cook the Heightfield node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), HeightFieldId, nullptr), false); + + return true; +} + +bool +FHoudiniLandscapeUtils::GetLandscapeData( + ALandscape* Landscape, + TArray& HeightData, + int32& XSize, int32& YSize, + FVector& Min, FVector& Max ) +{ + if ( !Landscape ) + return false; + + ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); + if ( !LandscapeInfo ) + return false; + + // Get the landscape extents to get its size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + + if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + return false; + + if ( !GetLandscapeData( LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize ) ) + return false; + + // Get the landscape Min/Max values + // Do not use Landscape->GetActorBounds() here as instanced geo + // (due to grass layers for example) can cause it to return incorrect bounds! + FVector Origin, Extent; + GetLandscapeActorBounds(Landscape, Origin, Extent); + + // Get the landscape Min/Max values + Min = Origin - Extent; + Max = Origin + Extent; + + return true; +} + +bool +FHoudiniLandscapeUtils::GetLandscapeData( + ALandscapeProxy* LandscapeProxy, + TArray& HeightData, + int32& XSize, int32& YSize, + FVector& Min, FVector& Max) +{ + if (!LandscapeProxy) + return false; + + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Get the landscape extents to get its size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + + // To handle streaming proxies correctly, get the extents via all the components, + // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. + for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) + { + Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); + } + + if (!GetLandscapeData(LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize)) + return false; + + // Get the landscape Min/Max values + // Do not use Landscape->GetActorBounds() here as instanced geo + // (due to grass layers for example) can cause it to return incorrect bounds! + FVector Origin, Extent; + GetLandscapeProxyBounds(LandscapeProxy, Origin, Extent); + + // Get the landscape Min/Max values + Min = Origin - Extent; + Max = Origin + Extent; + + return true; +} + + +void +FHoudiniLandscapeUtils::GetLandscapeActorBounds( + ALandscape* Landscape, FVector& Origin, FVector& Extents ) +{ + // Iterate only on the landscape components + FBox Bounds(ForceInit); + for (const UActorComponent* ActorComponent : Landscape->GetComponents()) + { + const ULandscapeComponent* LandscapeComp = Cast(ActorComponent); + if ( LandscapeComp && LandscapeComp->IsRegistered() ) + Bounds += LandscapeComp->Bounds.GetBox(); + } + + // Convert the bounds to origin/offset vectors + Bounds.GetCenterAndExtents(Origin, Extents); +} + +void +FHoudiniLandscapeUtils::GetLandscapeProxyBounds( + ALandscapeProxy* LandscapeProxy, FVector& Origin, FVector& Extents) +{ + // Iterate only on the landscape components + FBox Bounds(ForceInit); + for (const UActorComponent* ActorComponent : LandscapeProxy->GetComponents()) + { + const ULandscapeComponent* LandscapeComp = Cast(ActorComponent); + if (LandscapeComp && LandscapeComp->IsRegistered()) + Bounds += LandscapeComp->Bounds.GetBox(); + } + + // Convert the bounds to origin/offset vectors + Bounds.GetCenterAndExtents(Origin, Extents); +} + +bool +FHoudiniLandscapeUtils::GetLandscapeData( + ULandscapeInfo* LandscapeInfo, + const int32& MinX, const int32& MinY, + const int32& MaxX, const int32& MaxY, + TArray& HeightData, + int32& XSize, int32& YSize ) +{ + if ( !LandscapeInfo ) + return false; + + // Get the X/Y size in points + XSize = ( MaxX - MinX + 1 ); + YSize = ( MaxY - MinY + 1 ); + + if ( ( XSize < 2 ) || ( YSize < 2 ) ) + return false; + + // Extracting the uint16 values from the landscape + FLandscapeEditDataInterface LandscapeEdit( LandscapeInfo ); + HeightData.AddZeroed( XSize * YSize ); + LandscapeEdit.GetHeightDataFast( MinX, MinY, MaxX, MaxY, HeightData.GetData(), 0 ); + + return true; +} + +bool +FHoudiniLandscapeUtils::GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, const int32& LayerIndex, + TArray& LayerData, FLinearColor& LayerUsageDebugColor, + FString& LayerName ) +{ + if ( !LandscapeInfo ) + return false; + + // Get the landscape X/Y Size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + if ( !LandscapeInfo->GetLandscapeExtent( MinX, MinY, MaxX, MaxY ) ) + return false; + + if ( !GetLandscapeLayerData( + LandscapeInfo, LayerIndex, + MinX, MinY, MaxX, MaxY, + LayerData, LayerUsageDebugColor, LayerName ) ) + return false; + + return true; +} + +bool +FHoudiniLandscapeUtils::GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + const int32& MinX, const int32& MinY, + const int32& MaxX, const int32& MaxY, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName ) +{ + if ( !LandscapeInfo ) + return false; + + if ( !LandscapeInfo->Layers.IsValidIndex( LayerIndex ) ) + return false; + + FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[ LayerIndex ]; + ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; + if (!LayerInfo) + return false; + + // Calc the X/Y size in points + int32 XSize = (MaxX - MinX + 1); + int32 YSize = (MaxY - MinY + 1); + if ( (XSize < 2) || (YSize < 2) ) + return false; + + // extracting the uint8 values from the layer + FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); + LayerData.AddZeroed(XSize * YSize); + LandscapeEdit.GetWeightDataFast(LayerInfo, MinX, MinY, MaxX, MaxY, LayerData.GetData(), 0); + + LayerUsageDebugColor = LayerInfo->LayerUsageDebugColor; + + LayerName = LayersSetting.GetLayerName().ToString(); + + return true; +} +#endif + +bool +FHoudiniLandscapeUtils::ConvertLandscapeDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + FVector Min, FVector Max, + const FTransform& LandscapeTransform, + TArray& HeightfieldFloatValues, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + FVector& CenterOffset) +{ + HeightfieldFloatValues.Empty(); + + int32 HoudiniXSize = YSize; + int32 HoudiniYSize = XSize; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ( (HoudiniXSize < 2) || (HoudiniYSize < 2) ) + return false; + + if ( IntHeightData.Num() != SizeInPoints ) + return false; + + // Use default unreal scaling for marshalling landscapes + // A lot of precision will be lost in order to keep the same transform as the landscape input + bool bUseDefaultUE4Scaling = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) + bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to float + //-------------------------------------------------------------------------------------------------- + + + // Convert the min/max values from cm to meters + Min /= 100.0; + Max /= 100.0; + + // Unreal's landscape uses 16bits precision and range from -256m to 256m with the default scale of 100.0 + // To convert the uint16 values to float "metric" values, offset the int by 32768 to center it, + // then scale it + + // Spacing used to convert from uint16 to meters + double ZSpacing = 512.0 / ((double)UINT16_MAX); + ZSpacing *= ( (double)LandscapeTransform.GetScale3D().Z / 100.0 ); + + // Center value in meters (Landscape ranges from [-255:257] meters at default scale + double ZCenterOffset = 32767; + double ZPositionOffset = LandscapeTransform.GetLocation().Z / 100.0f; + // Convert the Int data to Float + HeightfieldFloatValues.SetNumUninitialized( SizeInPoints ); + + for ( int32 nY = 0; nY < HoudiniYSize; nY++ ) + { + for ( int32 nX = 0; nX < HoudiniXSize; nX++ ) + { + // We need to invert X/Y when reading the value from Unreal + int32 nHoudini = nX + nY * HoudiniXSize; + int32 nUnreal = nY + nX * XSize; + + // Convert the int values to meter + // Unreal's digit value have a zero value of 32768 + double DoubleValue = ((double)IntHeightData[nUnreal] - ZCenterOffset) * ZSpacing + ZPositionOffset; + HeightfieldFloatValues[nHoudini] = (float)DoubleValue; + } + } + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the Unreal Transform to a HAPI_transform + //-------------------------------------------------------------------------------------------------- + HAPI_Transform HapiTransform; + FHoudiniApi::Transform_Init(&HapiTransform); + //FMemory::Memzero< HAPI_Transform >( HapiTransform ); + { + FQuat Rotation = LandscapeTransform.GetRotation(); + if ( Rotation != FQuat::Identity ) + { + //Swap(ObjectRotation.Y, ObjectRotation.Z); + HapiTransform.rotationQuaternion[0] = Rotation.X; + HapiTransform.rotationQuaternion[1] = Rotation.Z; + HapiTransform.rotationQuaternion[2] = Rotation.Y; + HapiTransform.rotationQuaternion[3] = -Rotation.W; + } + else + { + HapiTransform.rotationQuaternion[0] = 0; + HapiTransform.rotationQuaternion[1] = 0; + HapiTransform.rotationQuaternion[2] = 0; + HapiTransform.rotationQuaternion[3] = 1; + } + + // Heightfield are centered, landscapes are not + CenterOffset = (Max - Min) * 0.5f; + + // Unreal XYZ becomes Houdini YXZ (since heightfields are also rotated due the ZX transform) + //FVector Position = LandscapeTransform.GetLocation() / 100.0f; + HapiTransform.position[ 1 ] = 0.0f;//Position.X + CenterOffset.X; + HapiTransform.position[ 0 ] = 0.0f;//Position.Y + CenterOffset.Y; + HapiTransform.position[ 2 ] = 0.0f; + + FVector Scale = LandscapeTransform.GetScale3D() / 100.0f; + HapiTransform.scale[ 0 ] = Scale.X * 0.5f * HoudiniXSize; + HapiTransform.scale[ 1 ] = Scale.Y * 0.5f * HoudiniYSize; + HapiTransform.scale[ 2 ] = 0.5f; + if ( bUseDefaultUE4Scaling ) + HapiTransform.scale[2] *= Scale.Z; + + HapiTransform.shear[ 0 ] = 0.0f; + HapiTransform.shear[ 1 ] = 0.0f; + HapiTransform.shear[ 2 ] = 0.0f; + } + + //-------------------------------------------------------------------------------------------------- + // 3. Fill the volume info + //-------------------------------------------------------------------------------------------------- + HeightfieldVolumeInfo.xLength = HoudiniXSize; + HeightfieldVolumeInfo.yLength = HoudiniYSize; + HeightfieldVolumeInfo.zLength = 1; + + HeightfieldVolumeInfo.minX = 0; + HeightfieldVolumeInfo.minY = 0; + HeightfieldVolumeInfo.minZ = 0; + + HeightfieldVolumeInfo.transform = HapiTransform; + + HeightfieldVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; + HeightfieldVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; + HeightfieldVolumeInfo.tupleSize = 1; + HeightfieldVolumeInfo.tileSize = 1; + + HeightfieldVolumeInfo.hasTaper = false; + HeightfieldVolumeInfo.xTaper = 0.0; + HeightfieldVolumeInfo.yTaper = 0.0; + + return true; +} + +// Converts Unreal uint16 values to Houdini Float +bool +FHoudiniLandscapeUtils::ConvertLandscapeLayerDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + const FLinearColor& LayerUsageDebugColor, + TArray& LayerFloatValues, + HAPI_VolumeInfo& LayerVolumeInfo) +{ + LayerFloatValues.Empty(); + + int32 HoudiniXSize = YSize; + int32 HoudiniYSize = XSize; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ( ( HoudiniXSize < 2 ) || ( HoudiniYSize < 2 ) ) + return false; + + if ( IntHeightData.Num() != SizeInPoints ) + return false; + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to float + //-------------------------------------------------------------------------------------------------- + + // We need the ZMin / ZMax unt8 values + uint8 IntMin = IntHeightData[ 0 ]; + uint8 IntMax = IntMin; + + for ( int n = 0; n < IntHeightData.Num(); n++ ) + { + if ( IntHeightData[ n ] < IntMin ) + IntMin = IntHeightData[ n ]; + if ( IntHeightData[ n ] > IntMax ) + IntMax = IntHeightData[ n ]; + } + + // The range in Digits + double DigitRange = (double)IntMax - (double)IntMin; + + // By default, the values will be converted to [0, 1] + float LayerMin = 0.0f; + float LayerMax = 1.0f; + float LayerSpacing = 1.0f / DigitRange; + + // If this layer came from Houdini, its alpha value should be PI + // So we can extract the additionnal infos stored its debug usage color + if ( LayerUsageDebugColor.A == PI ) + { + LayerMin = LayerUsageDebugColor.R; + LayerMax = LayerUsageDebugColor.G; + LayerSpacing = LayerUsageDebugColor.B; + } + + LayerSpacing = ( LayerMax - LayerMin ) / DigitRange; + + // Convert the Int data to Float + LayerFloatValues.SetNumUninitialized( SizeInPoints ); + + for ( int32 nY = 0; nY < HoudiniYSize; nY++ ) + { + for ( int32 nX = 0; nX < HoudiniXSize; nX++ ) + { + // We need to invert X/Y when reading the value from Unreal + int32 nHoudini = nX + nY * HoudiniXSize; + int32 nUnreal = nY + nX * XSize; + + // Convert the int values to meter + // Unreal's digit value have a zero value of 32768 + double DoubleValue = ( (double)IntHeightData[ nUnreal ] - (double)IntMin ) * LayerSpacing + LayerMin; + LayerFloatValues[ nHoudini ] = (float)DoubleValue; + } + } + + // Verifying the converted ZMin / ZMax + float FloatMin = LayerFloatValues[ 0 ]; + float FloatMax = FloatMin; + for ( int32 n = 0; n < LayerFloatValues.Num(); n++ ) + { + if ( LayerFloatValues[ n ] < FloatMin ) + FloatMin = LayerFloatValues[ n ]; + if ( LayerFloatValues[ n ] > FloatMax ) + FloatMax = LayerFloatValues[ n ]; + } + + //-------------------------------------------------------------------------------------------------- + // 2. Fill the volume info + //-------------------------------------------------------------------------------------------------- + LayerVolumeInfo.xLength = HoudiniXSize; + LayerVolumeInfo.yLength = HoudiniYSize; + LayerVolumeInfo.zLength = 1; + + LayerVolumeInfo.minX = 0; + LayerVolumeInfo.minY = 0; + LayerVolumeInfo.minZ = 0; + + LayerVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; + LayerVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; + LayerVolumeInfo.tupleSize = 1; + LayerVolumeInfo.tileSize = 1; + + LayerVolumeInfo.hasTaper = false; + LayerVolumeInfo.xTaper = 0.0; + LayerVolumeInfo.yTaper = 0.0; + + // The layer transform will have to be copied from the main heightfield's transform + return true; +} + +bool +FHoudiniLandscapeUtils::SetHeighfieldData( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + TArray& FloatValues, + const HAPI_VolumeInfo& VolumeInfo, + const FString& HeightfieldName ) +{ + // Cook the node to get proper infos on it + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, nullptr ), false ); + + // Read the geo/part/volume info from the volume node + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + //FMemory::Memset< HAPI_GeoInfo >(GeoInfo, 0); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + VolumeNodeId, &GeoInfo), false); + + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + //FMemory::Memset< HAPI_PartInfo >(PartInfo, 0); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), + GeoInfo.nodeId, PartId, &PartInfo), false); + + // Update the volume infos + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + VolumeNodeId, PartInfo.id, &VolumeInfo), false ); + + // Volume name + std::string NameStr; + FHoudiniEngineUtils::ConvertUnrealString( HeightfieldName, NameStr ); + + // Set the Heighfield data on the volume + float * HeightData = FloatValues.GetData(); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetHeightFieldData( + FHoudiniEngine::Get().GetSession(), + GeoInfo.nodeId, PartInfo.id, NameStr.c_str(), HeightData, 0, FloatValues.Num() ), false ); + + return true; +} + +bool +FHoudiniLandscapeUtils::CreateHeightfieldInputNode( + const HAPI_NodeId& ParentNodeId, const FString& NodeName, const int32& XSize, const int32& YSize, + HAPI_NodeId& HeightfieldNodeId, HAPI_NodeId& HeightNodeId, HAPI_NodeId& MaskNodeId, HAPI_NodeId& MergeNodeId ) +{ + // Make sure the Heightfield node doesnt already exists + if (HeightfieldNodeId != -1) + return false; + + // Convert the node's name + std::string NameStr; + FHoudiniEngineUtils::ConvertUnrealString(NodeName, NameStr); + + // Create the heigthfield node via HAPI + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateHeightfieldInputNode( + FHoudiniEngine::Get().GetSession(), + ParentNodeId, NameStr.c_str(), XSize, YSize, 1.0f, + &HeightfieldNodeId, &HeightNodeId, &MaskNodeId, &MergeNodeId ), false); + + // Cook it + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), HeightfieldNodeId, nullptr), false); + + return true; +} + +bool +FHoudiniLandscapeUtils::DestroyLandscapeAssetNode( HAPI_NodeId& ConnectedAssetId, TArray& CreatedInputAssetIds ) +{ + HAPI_AssetInfo NodeAssetInfo; + FHoudiniApi::AssetInfo_Init(&NodeAssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &NodeAssetInfo ), false ); + + FHoudiniEngineString AssetOpName( NodeAssetInfo.fullOpNameSH ); + FString OpName; + if ( !AssetOpName.ToFString( OpName ) ) + return false; + + if ( !OpName.Contains(TEXT("xform"))) + { + // Not a transform node, so not a Heightfield + // We just need to destroy the landscape asset node + return FHoudiniEngineUtils::DestroyHoudiniAsset( ConnectedAssetId ); + } + + // The landscape was marshalled as a heightfield, so we need to destroy and disconnect + // the volvis nodes, all the merge node's input (each merge input is a volume for one + // of the layer/mask of the landscape ) + + // Query the volvis node id + // The volvis node is the fist input of the xform node + HAPI_NodeId VolvisNodeId = -1; + FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, &VolvisNodeId ); + + // First, destroy the merge node and its inputs + // The merge node is in the first input of the volvis node + HAPI_NodeId MergeNodeId = -1; + FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + VolvisNodeId, 0, &MergeNodeId ); + + if ( MergeNodeId != -1 ) + { + // Get the merge node info + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), MergeNodeId, &NodeInfo ), false ); + + for ( int32 n = 0; n < NodeInfo.inputCount; n++ ) + { + // Get the Input node ID from the host ID + HAPI_NodeId InputNodeId = -1; + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeNodeId, n, &InputNodeId ) ) + break; + + if ( InputNodeId == -1 ) + break; + + // Disconnect and Destroy that input + FHoudiniEngineUtils::HapiDisconnectAsset( MergeNodeId, n ); + FHoudiniEngineUtils::DestroyHoudiniAsset( InputNodeId ); + } + } + + // Second step, destroy all the volumes GEO assets + for ( HAPI_NodeId AssetNodeId : CreatedInputAssetIds ) + { + FHoudiniEngineUtils::DestroyHoudiniAsset( AssetNodeId ); + } + CreatedInputAssetIds.Empty(); + + // Finally disconnect and destroy the xform, volvis and merge nodes, then destroy them + FHoudiniEngineUtils::HapiDisconnectAsset( ConnectedAssetId, 0 ); + FHoudiniEngineUtils::HapiDisconnectAsset( VolvisNodeId, 0 ); + FHoudiniEngineUtils::DestroyHoudiniAsset( MergeNodeId ); + FHoudiniEngineUtils::DestroyHoudiniAsset( VolvisNodeId ); + + return FHoudiniEngineUtils::DestroyHoudiniAsset( ConnectedAssetId ); +} + +FColor +FHoudiniLandscapeUtils::PickVertexColorFromTextureMip( + const uint8 * MipBytes, FVector2D & UVCoord, int32 MipWidth, int32 MipHeight ) +{ + check( MipBytes ); + + FColor ResultColor( 0, 0, 0, 255 ); + + if ( UVCoord.X >= 0.0f && UVCoord.X < 1.0f && UVCoord.Y >= 0.0f && UVCoord.Y < 1.0f ) + { + const int32 X = MipWidth * UVCoord.X; + const int32 Y = MipHeight * UVCoord.Y; + + const int32 Index = ( ( Y * MipWidth ) + X ) * 4; + + ResultColor.B = MipBytes[ Index + 0 ]; + ResultColor.G = MipBytes[ Index + 1 ]; + ResultColor.R = MipBytes[ Index + 2 ]; + ResultColor.A = MipBytes[ Index + 3 ]; + } + + return ResultColor; +} + +#if WITH_EDITOR +bool +FHoudiniLandscapeUtils::ExtractLandscapeData( + ALandscapeProxy * LandscapeProxy, TSet& SelectedComponents, + const bool& bExportLighting, const bool& bExportTileUVs, const bool& bExportNormalizedUVs, + TArray& LandscapePositionArray, + TArray& LandscapeNormalArray, + TArray& LandscapeUVArray, + TArray& LandscapeComponentVertexIndicesArray, + TArray& LandscapeComponentNameArray, + TArray& LandscapeLightmapValues ) +{ + if ( !LandscapeProxy ) + return false; + + if ( SelectedComponents.Num() < 1 ) + return false; + + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION; + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + + if ( HoudiniRuntimeSettings ) + { + GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + } + + // Calc all the needed sizes + int32 ComponentSizeQuads = ( ( LandscapeProxy->ComponentSizeQuads + 1 ) >> LandscapeProxy->ExportLOD ) - 1; + float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; + + int32 NumComponents = SelectedComponents.Num(); + bool bExportOnlySelected = NumComponents != LandscapeProxy->LandscapeComponents.Num(); + + int32 VertexCountPerComponent = FMath::Square( ComponentSizeQuads + 1 ); + int32 VertexCount = NumComponents * VertexCountPerComponent; + if ( !VertexCount ) + return false; + + // Initialize the data arrays + LandscapePositionArray.SetNumUninitialized( VertexCount ); + LandscapeNormalArray.SetNumUninitialized( VertexCount ); + LandscapeUVArray.SetNumUninitialized( VertexCount ); + LandscapeComponentNameArray.SetNumUninitialized( VertexCount ); + LandscapeComponentVertexIndicesArray.SetNumUninitialized( VertexCount ); + if ( bExportLighting ) + LandscapeLightmapValues.SetNumUninitialized( VertexCount ); + + //----------------------------------------------------------------------------------------------------------------- + // EXTRACT THE LANDSCAPE DATA + //----------------------------------------------------------------------------------------------------------------- + FIntPoint IntPointMax = FIntPoint::ZeroValue; + + int32 AllPositionsIdx = 0; + for ( int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++ ) + { + ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ ComponentIdx ]; + if ( bExportOnlySelected && !SelectedComponents.Contains( LandscapeComponent ) ) + continue; + + TArray64< uint8 > LightmapMipData; + int32 LightmapMipSizeX = 0; + int32 LightmapMipSizeY = 0; + + // See if we need to export lighting information. + if ( bExportLighting ) + { + const FMeshMapBuildData* MapBuildData = LandscapeComponent->GetMeshMapBuildData(); + FLightMap2D* LightMap2D = MapBuildData && MapBuildData->LightMap ? MapBuildData->LightMap->GetLightMap2D() : nullptr; + if ( LightMap2D && LightMap2D->IsValid( 0 ) ) + { + UTexture2D * TextureLightmap = LightMap2D->GetTexture( 0 ); + if ( TextureLightmap ) + { + if ( TextureLightmap->Source.GetMipData(LightmapMipData, 0, 0, 0, nullptr)) + { + LightmapMipSizeX = TextureLightmap->Source.GetSizeX(); + LightmapMipSizeY = TextureLightmap->Source.GetSizeY(); + } + else + { + LightmapMipData.Empty(); + } + } + } + } + + // Construct landscape component data interface to access raw data. + FLandscapeComponentDataInterface CDI( LandscapeComponent, LandscapeProxy->ExportLOD ); + + // Get name of this landscape component. + char * LandscapeComponentNameStr = FHoudiniEngineUtils::ExtractRawName( LandscapeComponent->GetName() ); + + for ( int32 VertexIdx = 0; VertexIdx < VertexCountPerComponent; VertexIdx++ ) + { + int32 VertX = 0; + int32 VertY = 0; + CDI.VertexIndexToXY( VertexIdx, VertX, VertY ); + + // Get position. + FVector PositionVector = CDI.GetWorldVertex( VertX, VertY ); + + // Get normal / tangent / binormal. + FVector Normal = FVector::ZeroVector; + FVector TangentX = FVector::ZeroVector; + FVector TangentY = FVector::ZeroVector; + CDI.GetLocalTangentVectors( VertX, VertY, TangentX, TangentY, Normal ); + + // Export UVs. + FVector TextureUV = FVector::ZeroVector; + if ( bExportTileUVs ) + { + // We want to export uvs per tile. + TextureUV = FVector( VertX, VertY, 0.0f ); + + // If we need to normalize UV space. + if ( bExportNormalizedUVs ) + TextureUV /= ComponentSizeQuads; + } + else + { + // We want to export global uvs (default). + FIntPoint IntPoint = LandscapeComponent->GetSectionBase(); + TextureUV = FVector( VertX * ScaleFactor + IntPoint.X, VertY * ScaleFactor + IntPoint.Y, 0.0f ); + + // Keep track of max offset. + IntPointMax = IntPointMax.ComponentMax( IntPoint ); + } + + if ( bExportLighting ) + { + FLinearColor VertexLightmapColor( 0.0f, 0.0f, 0.0f, 1.0f ); + if ( LightmapMipData.Num() > 0 ) + { + FVector2D UVCoord( VertX, VertY ); + UVCoord /= ( ComponentSizeQuads + 1 ); + + FColor LightmapColorRaw = PickVertexColorFromTextureMip( + LightmapMipData.GetData(), UVCoord, LightmapMipSizeX, LightmapMipSizeY ); + + VertexLightmapColor = LightmapColorRaw.ReinterpretAsLinear(); + } + + LandscapeLightmapValues[ AllPositionsIdx ] = VertexLightmapColor; + } + + // Retrieve component transform. + const FTransform & ComponentTransform = LandscapeComponent->GetComponentTransform(); + + // Retrieve component scale. + const FVector & ScaleVector = ComponentTransform.GetScale3D(); + + // Perform normalization. + Normal /= ScaleVector; + Normal.Normalize(); + + TangentX /= ScaleVector; + TangentX.Normalize(); + + TangentY /= ScaleVector; + TangentY.Normalize(); + + // Perform position scaling. + FVector PositionTransformed = PositionVector / GeneratedGeometryScaleFactor; + if ( ImportAxis == HRSAI_Unreal ) + { + LandscapePositionArray[ AllPositionsIdx ].X = PositionTransformed.X; + LandscapePositionArray[ AllPositionsIdx ].Y = PositionTransformed.Z; + LandscapePositionArray[ AllPositionsIdx ].Z = PositionTransformed.Y; + + Swap( Normal.Y, Normal.Z ); + } + else if ( ImportAxis == HRSAI_Houdini ) + { + LandscapePositionArray[ AllPositionsIdx ].X = PositionTransformed.X; + LandscapePositionArray[ AllPositionsIdx ].Y = PositionTransformed.Y; + LandscapePositionArray[ AllPositionsIdx ].Z = PositionTransformed.Z; + } + else + { + // Not a valid enum value. + check( 0 ); + } + + // Store landscape component name for this point. + LandscapeComponentNameArray[ AllPositionsIdx ] = LandscapeComponentNameStr; + + // Store vertex index (x,y) for this point. + LandscapeComponentVertexIndicesArray[ AllPositionsIdx ].X = VertX; + LandscapeComponentVertexIndicesArray[ AllPositionsIdx ].Y = VertY; + + // Store point normal. + LandscapeNormalArray[ AllPositionsIdx ] = Normal; + + // Store uv. + LandscapeUVArray[ AllPositionsIdx ] = TextureUV; + + AllPositionsIdx++; + } + } + + // If we need to normalize UV space and we are doing global UVs. + if ( !bExportTileUVs && bExportNormalizedUVs ) + { + IntPointMax += FIntPoint( ComponentSizeQuads, ComponentSizeQuads ); + IntPointMax = IntPointMax.ComponentMax( FIntPoint( 1, 1 ) ); + + for ( int32 UVIdx = 0; UVIdx < VertexCount; ++UVIdx ) + { + FVector & PositionUV = LandscapeUVArray[ UVIdx ]; + PositionUV.X /= IntPointMax.X; + PositionUV.Y /= IntPointMax.Y; + } + } + + return true; +} +#endif + +bool FHoudiniLandscapeUtils::AddLandscapePositionAttribute( const HAPI_NodeId& NodeId, const TArray< FVector >& LandscapePositionArray ) +{ + int32 VertexCount = LandscapePositionArray.Num(); + if ( VertexCount < 3 ) + return false; + + // Create point attribute info containing positions. + HAPI_AttributeInfo AttributeInfoPointPosition; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointPosition); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointPosition ); + AttributeInfoPointPosition.count = VertexCount; + AttributeInfoPointPosition.tupleSize = 3; + AttributeInfoPointPosition.exists = true; + AttributeInfoPointPosition.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointPosition.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointPosition.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition, + (const float *)LandscapePositionArray.GetData(), + 0, AttributeInfoPointPosition.count ), false ); + + return true; +} + +bool FHoudiniLandscapeUtils::AddLandscapeNormalAttribute( const HAPI_NodeId& NodeId, const TArray& LandscapeNormalArray ) +{ + int32 VertexCount = LandscapeNormalArray.Num(); + if ( VertexCount < 3 ) + return false; + + HAPI_AttributeInfo AttributeInfoPointNormal; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointNormal); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointNormal ); + AttributeInfoPointNormal.count = VertexCount; + AttributeInfoPointNormal.tupleSize = 3; + AttributeInfoPointNormal.exists = true; + AttributeInfoPointNormal.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointNormal.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointNormal.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal, + (const float *)LandscapeNormalArray.GetData(), 0, AttributeInfoPointNormal.count ), false ); + + return true; +} + +bool FHoudiniLandscapeUtils::AddLandscapeUVAttribute( const HAPI_NodeId& NodeId, const TArray& LandscapeUVArray ) +{ + int32 VertexCount = LandscapeUVArray.Num(); + if ( VertexCount < 3 ) + return false; + + HAPI_AttributeInfo AttributeInfoPointUV; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointUV); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointUV ); + AttributeInfoPointUV.count = VertexCount; + AttributeInfoPointUV.tupleSize = 3; + AttributeInfoPointUV.exists = true; + AttributeInfoPointUV.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointUV.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointUV.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV, + (const float *)LandscapeUVArray.GetData(), 0, AttributeInfoPointUV.count ), false ); + + return true; +} + +bool FHoudiniLandscapeUtils::AddLandscapeComponentVertexIndicesAttribute( const HAPI_NodeId& NodeId, const TArray& LandscapeComponentVertexIndicesArray ) +{ + int32 VertexCount = LandscapeComponentVertexIndicesArray.Num(); + if ( VertexCount < 3 ) + return false; + + HAPI_AttributeInfo AttributeInfoPointLandscapeComponentVertexIndices; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentVertexIndices); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentVertexIndices ); + AttributeInfoPointLandscapeComponentVertexIndices.count = VertexCount; + AttributeInfoPointLandscapeComponentVertexIndices.tupleSize = 2; + AttributeInfoPointLandscapeComponentVertexIndices.exists = true; + AttributeInfoPointLandscapeComponentVertexIndices.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLandscapeComponentVertexIndices.storage = HAPI_STORAGETYPE_INT; + AttributeInfoPointLandscapeComponentVertexIndices.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, + &AttributeInfoPointLandscapeComponentVertexIndices ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, + &AttributeInfoPointLandscapeComponentVertexIndices, + (const int * )LandscapeComponentVertexIndicesArray.GetData(), 0, + AttributeInfoPointLandscapeComponentVertexIndices.count ), false ); + + return true; +} + +bool FHoudiniLandscapeUtils::AddLandscapeComponentNameAttribute( const HAPI_NodeId& NodeId, const TArray& LandscapeComponentNameArray ) +{ + int32 VertexCount = LandscapeComponentNameArray.Num(); + if ( VertexCount < 3 ) + return false; + + // Create point attribute containing landscape component name. + HAPI_AttributeInfo AttributeInfoPointLandscapeComponentNames; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentNames); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentNames ); + AttributeInfoPointLandscapeComponentNames.count = VertexCount; + AttributeInfoPointLandscapeComponentNames.tupleSize = 1; + AttributeInfoPointLandscapeComponentNames.exists = true; + AttributeInfoPointLandscapeComponentNames.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLandscapeComponentNames.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPointLandscapeComponentNames.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, + &AttributeInfoPointLandscapeComponentNames), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, + &AttributeInfoPointLandscapeComponentNames, + (const char **)LandscapeComponentNameArray.GetData(), + 0, AttributeInfoPointLandscapeComponentNames.count ), false ); + + return true; +} + +bool FHoudiniLandscapeUtils::AddLandscapeLightmapColorAttribute( const HAPI_NodeId& NodeId, const TArray& LandscapeLightmapValues ) +{ + int32 VertexCount = LandscapeLightmapValues.Num(); + + HAPI_AttributeInfo AttributeInfoPointLightmapColor; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLightmapColor); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLightmapColor ); + AttributeInfoPointLightmapColor.count = VertexCount; + AttributeInfoPointLightmapColor.tupleSize = 4; + AttributeInfoPointLightmapColor.exists = true; + AttributeInfoPointLightmapColor.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLightmapColor.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointLightmapColor.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor, + (const float *)LandscapeLightmapValues.GetData(), 0, + AttributeInfoPointLightmapColor.count ), false ); + + return true; +} + +bool FHoudiniLandscapeUtils::AddLandscapeMeshIndicesAndMaterialsAttribute( + const HAPI_NodeId& NodeId, const bool& bExportMaterials, + const int32& ComponentSizeQuads, const int32& QuadCount, + ALandscapeProxy * LandscapeProxy, + const TSet< ULandscapeComponent * >& SelectedComponents ) +{ + if ( !LandscapeProxy ) + return false; + + // Compute number of necessary indices. + int32 IndexCount = QuadCount * 4; + if ( IndexCount < 0 ) + return false; + + int32 VertexCountPerComponent = FMath::Square( ComponentSizeQuads + 1 ); + + // Get runtime settings. + EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if ( HoudiniRuntimeSettings ) + ImportAxis = HoudiniRuntimeSettings->ImportAxis; + + // Array holding indices data. + TArray< int32 > LandscapeIndices; + LandscapeIndices.SetNumUninitialized( IndexCount ); + + // Allocate space for face names. + // The LandscapeMaterial and HoleMaterial per point + TArray< const char * > FaceMaterials; + TArray< const char * > FaceHoleMaterials; + FaceMaterials.SetNumUninitialized( QuadCount ); + FaceHoleMaterials.SetNumUninitialized( QuadCount ); + + int32 VertIdx = 0; + int32 QuadIdx = 0; + + char * MaterialRawStr = nullptr; + char * MaterialHoleRawStr = nullptr; + + const int32 QuadComponentCount = ComponentSizeQuads + 1; + for ( int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++ ) + { + ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ ComponentIdx ]; + if ( !SelectedComponents.Contains( LandscapeComponent ) ) + continue; + + if ( bExportMaterials ) + { + // If component has an override material, we need to get the raw name (if exporting materials). + if ( LandscapeComponent->OverrideMaterial ) + { + MaterialRawStr = FHoudiniEngineUtils::ExtractRawName(LandscapeComponent->OverrideMaterial->GetName()); + } + + // If component has an override hole material, we need to get the raw name (if exporting materials). + if ( LandscapeComponent->OverrideHoleMaterial ) + { + MaterialHoleRawStr = FHoudiniEngineUtils::ExtractRawName(LandscapeComponent->OverrideHoleMaterial->GetName()); + } + } + + int32 BaseVertIndex = ComponentIdx * VertexCountPerComponent; + for ( int32 YIdx = 0; YIdx < ComponentSizeQuads; YIdx++ ) + { + for ( int32 XIdx = 0; XIdx < ComponentSizeQuads; XIdx++ ) + { + if ( ImportAxis == HRSAI_Unreal ) + { + LandscapeIndices[ VertIdx + 0 ] = BaseVertIndex + ( XIdx + 0 ) + ( YIdx + 0 ) * QuadComponentCount; + LandscapeIndices[ VertIdx + 1 ] = BaseVertIndex + ( XIdx + 1 ) + ( YIdx + 0 ) * QuadComponentCount; + LandscapeIndices[ VertIdx + 2 ] = BaseVertIndex + ( XIdx + 1 ) + ( YIdx + 1 ) * QuadComponentCount; + LandscapeIndices[ VertIdx + 3 ] = BaseVertIndex + ( XIdx + 0 ) + ( YIdx + 1 ) * QuadComponentCount; + } + else if (ImportAxis == HRSAI_Houdini) + { + LandscapeIndices[ VertIdx + 0 ] = BaseVertIndex + ( XIdx + 0 ) + ( YIdx + 0 ) * QuadComponentCount; + LandscapeIndices[ VertIdx + 1 ] = BaseVertIndex + ( XIdx + 0 ) + ( YIdx + 1 ) * QuadComponentCount; + LandscapeIndices[ VertIdx + 2 ] = BaseVertIndex + ( XIdx + 1 ) + ( YIdx + 1 ) * QuadComponentCount; + LandscapeIndices[ VertIdx + 3 ] = BaseVertIndex + ( XIdx + 1 ) + ( YIdx + 0 ) * QuadComponentCount; + } + else + { + // Not a valid enum value. + check( 0 ); + } + + // Store override materials (if exporting materials). + if ( bExportMaterials ) + { + FaceMaterials[ QuadIdx ] = MaterialRawStr; + FaceHoleMaterials[ QuadIdx ] = MaterialHoleRawStr; + } + + VertIdx += 4; + QuadIdx++; + } + } + } + + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, LandscapeIndices.GetData(), 0, LandscapeIndices.Num() ), false ); + + // We need to generate array of face counts. + TArray< int32 > LandscapeFaces; + LandscapeFaces.Init( 4, QuadCount ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, LandscapeFaces.GetData(), 0, LandscapeFaces.Num() ), false ); + + if ( bExportMaterials ) + { + if ( !FaceMaterials.Contains( nullptr ) ) + { + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL; + if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeMaterial.IsEmpty() ) + { + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeMaterial, + MarshallingAttributeMaterialName ); + } + + // Marshall in override primitive material names. + HAPI_AttributeInfo AttributeInfoPrimitiveMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterial ); + AttributeInfoPrimitiveMaterial.count = FaceMaterials.Num(); + AttributeInfoPrimitiveMaterial.tupleSize = 1; + AttributeInfoPrimitiveMaterial.exists = true; + AttributeInfoPrimitiveMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoPrimitiveMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPrimitiveMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoPrimitiveMaterial ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoPrimitiveMaterial, + (const char **)FaceMaterials.GetData(), 0, AttributeInfoPrimitiveMaterial.count ), false ); + } + + if ( !FaceHoleMaterials.Contains( nullptr ) ) + { + // Get name of attribute used for marshalling hole materials. + std::string MarshallingAttributeMaterialHoleName = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE; + if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeMaterialHole.IsEmpty() ) + { + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeMaterialHole, + MarshallingAttributeMaterialHoleName ); + } + + // Marshall in override primitive material hole names. + HAPI_AttributeInfo AttributeInfoPrimitiveMaterialHole; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterialHole); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterialHole ); + AttributeInfoPrimitiveMaterialHole.count = FaceHoleMaterials.Num(); + AttributeInfoPrimitiveMaterialHole.tupleSize = 1; + AttributeInfoPrimitiveMaterialHole.exists = true; + AttributeInfoPrimitiveMaterialHole.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoPrimitiveMaterialHole.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPrimitiveMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MarshallingAttributeMaterialHoleName.c_str(), + &AttributeInfoPrimitiveMaterialHole ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MarshallingAttributeMaterialHoleName.c_str(), + &AttributeInfoPrimitiveMaterialHole, (const char **) FaceHoleMaterials.GetData(), 0, + AttributeInfoPrimitiveMaterialHole.count ), false ); + } + } + + return true; +} + +bool FHoudiniLandscapeUtils::AddLandscapeTileAttribute( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const int32& TileIdx ) +{ + HAPI_AttributeInfo AttributeInfoTileIndex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoTileIndex); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoTileIndex ); + AttributeInfoTileIndex.count = 1; + AttributeInfoTileIndex.tupleSize = 1; + AttributeInfoTileIndex.exists = true; + AttributeInfoTileIndex.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoTileIndex.storage = HAPI_STORAGETYPE_INT; + AttributeInfoTileIndex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + PartId, "tile", &AttributeInfoTileIndex ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, "tile", &AttributeInfoTileIndex, + (const int *)&TileIdx, 0, AttributeInfoTileIndex.count ), false ); + + return true; +} + +// Add the landscape component extent attribute +bool FHoudiniLandscapeUtils::AddLandscapeComponentExtentAttributes( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, + const int32& MinX, const int32& MaxX, + const int32& MinY, const int32& MaxY ) +{ + // Create an AttributeInfo + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfo); + AttributeInfo.count = 1; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_INT; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + // Add the landscape_component_min_X primitive attribute + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, "landscape_component_min_X", &AttributeInfo ), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, "landscape_component_min_X", &AttributeInfo, + (const int *)&MinX, 0, AttributeInfo.count), false); + + // Add the landscape_component_max_X primitive attribute + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, "landscape_component_max_X", &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, "landscape_component_max_X", &AttributeInfo, + (const int *)&MaxX, 0, AttributeInfo.count), false); + + // Add the landscape_component_min_Y primitive attribute + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, "landscape_component_min_Y", &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, "landscape_component_min_Y", &AttributeInfo, + (const int *)&MinY, 0, AttributeInfo.count), false); + + // Add the landscape_component_max_Y primitive attribute + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, "landscape_component_max_Y", &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, "landscape_component_max_Y", &AttributeInfo, + (const int *)&MaxY, 0, AttributeInfo.count), false); + + return true; +} + +// Read the landscape component extent attribute from a heightfield +bool FHoudiniLandscapeUtils::GetLandscapeComponentExtentAttributes( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, + int32& MinX, int32& MaxX, + int32& MinY, int32& MaxY ) +{ + // If we dont have minX, we likely dont have the others too + if ( !FHoudiniEngineUtils::HapiCheckAttributeExists( + HoudiniGeoPartObject, "landscape_component_min_X", HAPI_ATTROWNER_PRIM) ) + return false; + + // Create an AttributeInfo + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfo); + + // Get MinX + TArray IntData; + if ( !FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject, "landscape_component_min_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM ) ) + return false; + + if ( IntData.Num() > 0 ) + MinX = IntData[0]; + + // Get MaxX + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject, "landscape_component_max_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MaxX = IntData[0]; + + // Get MinY + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject, "landscape_component_min_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MinY = IntData[0]; + + // Get MaxX + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject, "landscape_component_max_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MaxY = IntData[0]; + + return true; +} + +#if WITH_EDITOR +bool +FHoudiniLandscapeUtils::CreateAllLandscapes( + FHoudiniCookParams& HoudiniCookParams, + const TArray< FHoudiniGeoPartObject > & FoundVolumes, + TMap< FHoudiniGeoPartObject, TWeakObjectPtr >& Landscapes, + TMap< FHoudiniGeoPartObject, TWeakObjectPtr >& NewLandscapes, + TArray& InputLandscapeToUpdate, + float ForcedZMin , float ForcedZMax ) +{ + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if ( HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesForceMinMaxValues ) + { + ForcedZMin = HoudiniRuntimeSettings->MarshallingLandscapesForcedMinValue; + ForcedZMax = HoudiniRuntimeSettings->MarshallingLandscapesForcedMaxValue; + } + + // First, we need to extract proper height data from FoundVolumes + TArray< const FHoudiniGeoPartObject* > FoundHeightfields; + FHoudiniLandscapeUtils::GetHeightfieldsInArray( FoundVolumes, FoundHeightfields ); + + // If we have multiple heightfields, we want to convert them using the same Z range + // Either that range has been specified/forced by the user, or we'll have to calculate it from all the height volumes. + float fGlobalMin = ForcedZMin, fGlobalMax = ForcedZMax; + if ( FoundHeightfields.Num() > 1 && ( fGlobalMin == 0.0f && fGlobalMax == 0.0f ) ) + FHoudiniLandscapeUtils::CalcHeightfieldsArrayGlobalZMinZMax( FoundHeightfields, fGlobalMin, fGlobalMax ); + + // We want to be doing a similar things for the mask/layer volumes + TMap GlobalMinimums; + TMap GlobalMaximums; + if ( FoundVolumes.Num() > 1 ) + FHoudiniLandscapeUtils::CalcHeightfieldsArrayGlobalZMinZMax( FoundVolumes, GlobalMinimums, GlobalMaximums ); + + // Try to create a Landscape for each HeightData found + NewLandscapes.Empty(); + for (TArray< const FHoudiniGeoPartObject* >::TConstIterator IterHeighfields(FoundHeightfields); IterHeighfields; ++IterHeighfields) + { + // Get the current Heightfield GeoPartObject + const FHoudiniGeoPartObject* CurrentHeightfield = *IterHeighfields; + if (!CurrentHeightfield) + continue; + + // Look for the unreal_landscape_streaming_proxy attribute on the height volume + bool bCreateLandscapeStreamingProxy = false; + TArray IntData; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfo); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + CurrentHeightfield->AssetId, CurrentHeightfield->ObjectId, + CurrentHeightfield->GeoId, CurrentHeightfield->PartId, + "unreal_landscape_streaming_proxy", AttributeInfo, IntData, 1)) + { + if (IntData.Num() > 0 && IntData[0] != 0) + bCreateLandscapeStreamingProxy = true; + } + + // Try to find the landscape previously created from that HGPO + ALandscapeProxy * FoundLandscape = nullptr; + if (TWeakObjectPtr* FoundLandscapePtr = Landscapes.Find(*CurrentHeightfield)) + { + FoundLandscape = FoundLandscapePtr->Get(); + if (!FoundLandscape || !FoundLandscape->IsValidLowLevel()) + FoundLandscape = nullptr; + } + + bool bLandscapeNeedsToBeUpdated = true; + if (!CurrentHeightfield->bHasGeoChanged) + { + // The Geo has not changed, do we need to recreate the landscape? + if (FoundLandscape) + { + // Check that all layers/mask have not changed too + TArray< const FHoudiniGeoPartObject* > FoundLayers; + FHoudiniLandscapeUtils::GetHeightfieldsLayersInArray(FoundVolumes, *CurrentHeightfield, FoundLayers); + + bool bLayersHaveChanged = false; + for (int32 n = 0; n < FoundLayers.Num(); n++) + { + if (FoundLayers[n] && FoundLayers[n]->bHasGeoChanged) + { + bLayersHaveChanged = true; + break; + } + } + + if (!bLayersHaveChanged) + { + // Height and layers/masks have not changed, there is no need to reimport the landscape + bLandscapeNeedsToBeUpdated = false; + } + + // Force update/recreation if the actor is not of the desired type + if ( (!FoundLandscape->IsA(ALandscapeStreamingProxy::StaticClass()) && bCreateLandscapeStreamingProxy) + || (FoundLandscape->IsA(ALandscapeStreamingProxy::StaticClass()) && !bCreateLandscapeStreamingProxy) ) + bLandscapeNeedsToBeUpdated = true; + } + } + + // Height and mask volumes have not changed + // No need to update the Landscape + if (!bLandscapeNeedsToBeUpdated) + { + // We can add the landscape to the map and remove it from the old one to avoid its destruction + NewLandscapes.Add(*CurrentHeightfield, FoundLandscape); + Landscapes.Remove(*CurrentHeightfield); + continue; + } + + HAPI_NodeId HeightFieldNodeId = CurrentHeightfield->HapiGeoGetNodeId(); + + // We need to see if the current heightfield has an unreal_material or unreal_hole_material assigned to it + UMaterialInterface* LandscapeMaterial = nullptr; + UMaterialInterface* LandscapeHoleMaterial = nullptr; + FHoudiniLandscapeUtils::GetHeightFieldLandscapeMaterials(*CurrentHeightfield, LandscapeMaterial, LandscapeHoleMaterial); + + // Extract the Float Data from the Heightfield + TArray< float > FloatValues; + HAPI_VolumeInfo VolumeInfo; + float FloatMin, FloatMax; + if (!FHoudiniLandscapeUtils::GetHeightfieldData(*CurrentHeightfield, FloatValues, VolumeInfo, FloatMin, FloatMax)) + continue; + + // Do we need to convert the heightfields using the same global Min/Max + if (fGlobalMin != fGlobalMax) + { + FloatMin = fGlobalMin; + FloatMax = fGlobalMax; + } + + // See if we need to create a new Landscape Actor for this heightfield: + // Either we haven't created one yet, or it's size has changed + int32 HoudiniXSize = VolumeInfo.yLength; + int32 HoudiniYSize = VolumeInfo.xLength; + int32 UnrealXSize = -1; + int32 UnrealYSize = -1; + int32 NumSectionPerLandscapeComponent = -1; + int32 NumQuadsPerLandscapeSection = -1; + if (!FHoudiniLandscapeUtils::CalcLandscapeSizeFromHeightfieldSize( + HoudiniXSize, HoudiniYSize, + UnrealXSize, UnrealYSize, + NumSectionPerLandscapeComponent, + NumQuadsPerLandscapeSection)) + continue; + + // See if the Heightfield has component extent attributes + // This would mean that we'd need to update parts of the landscape only + bool UpdateLandscapeComponent = false; + int32 MinX, MaxX, MinY, MaxY; + bool HasComponentExtent = GetLandscapeComponentExtentAttributes( + *CurrentHeightfield, MinX, MaxX, MinY, MaxY); + + // Try to see if we have an input landscape that matches the size of the current HGPO + bool UpdatingInputLandscape = false; + for (int nIdx = 0; nIdx < InputLandscapeToUpdate.Num(); nIdx++) + { + ALandscapeProxy* CurrentInputLandscape = InputLandscapeToUpdate[nIdx]; + if (!CurrentInputLandscape) + continue; + + ULandscapeInfo* CurrentInfo = CurrentInputLandscape->GetLandscapeInfo(); + if (!CurrentInfo) + continue; + + int32 InputMinX = 0; + int32 InputMinY = 0; + int32 InputMaxX = 0; + int32 InputMaxY = 0; + CurrentInfo->GetLandscapeExtent(InputMinX, InputMinY, InputMaxX, InputMaxY); + + // If the full size matches, we'll update that input landscape + bool SizeMatch = false; + if ((InputMaxX - InputMinX + 1) == UnrealXSize && (InputMaxY - InputMinY + 1) == UnrealYSize) + SizeMatch = true; + + /* + // If not, see if we could update that landscape's components instead of the full landscape + if ( !SizeMatch && HasComponentExtent ) + { + if ( (MinX >= InputMinX) && (MinX <= InputMaxX) + && (MinY >= InputMinY) && (MinY <= InputMaxY) + && (MaxX >= InputMinX) && (MaxX <= InputMaxX) + && (MaxY >= InputMinY) && (MaxY <= InputMaxY) ) + { + // Try to update this landscape component data + SizeMatch = true; + UpdateLandscapeComponent = true; + } + } + */ + + // HF and landscape don't match, try another one + if ( !SizeMatch ) + continue; + + // Replace FoundLandscape by that input landscape + FoundLandscape = CurrentInputLandscape; + + // If we're not updating part of that landscape via components, + // Remove that landscape from the input array so we dont try to update it twice + if ( !UpdateLandscapeComponent ) + InputLandscapeToUpdate.RemoveAt(nIdx); + + UpdatingInputLandscape = true; + } + + // See if we need to create a new Landscape Actor for this heightfield + // Either we haven't created one yet, or it's size has changed + bool bLandscapeNeedsRecreate = true; + if ( UpdatingInputLandscape ) + { + // No need to create a new landscape as we're modifying the input one + bLandscapeNeedsRecreate = false; + } + else if ( FoundLandscape && !FoundLandscape->IsPendingKill() ) + { + // See if we can reuse the previous landscape + ULandscapeInfo* PreviousInfo = FoundLandscape->GetLandscapeInfo(); + if ( PreviousInfo ) + { + int32 PrevMinX = 0; + int32 PrevMinY = 0; + int32 PrevMaxX = 0; + int32 PrevMaxY = 0; + PreviousInfo->GetLandscapeExtent(PrevMinX, PrevMinY, PrevMaxX, PrevMaxY); + + bool SizeMatch = false; + if ( (PrevMaxX - PrevMinX + 1) == UnrealXSize + && (PrevMaxY - PrevMinY + 1) == UnrealYSize ) + SizeMatch = true; + + /* + // If not, see if we could update that landscape's component + if (!SizeMatch && HasComponentExtent) + { + if ((MinX >= PrevMinX) && (MinX <= PrevMaxX) + && (MinY >= PrevMinY) && (MinY <= PrevMaxY) + && (MaxX >= PrevMinX) && (MaxX <= PrevMaxX) + && (MaxY >= PrevMinY) && (MaxY <= PrevMaxY)) + { + // Try to update this landscape component data + SizeMatch = true; + UpdateLandscapeComponent = true; + } + } + */ + if ( SizeMatch ) + { + // We can reuse the existing actor + bLandscapeNeedsRecreate = false; + } + } + } + + if (!bLandscapeNeedsRecreate) + { + // Force update/recreation if the actor is not of the desired type + if ((!FoundLandscape->IsA(ALandscapeStreamingProxy::StaticClass()) && bCreateLandscapeStreamingProxy) + || (FoundLandscape->IsA(ALandscapeStreamingProxy::StaticClass()) && !bCreateLandscapeStreamingProxy)) + bLandscapeNeedsRecreate = true; + } + + if ( bLandscapeNeedsRecreate ) + { + // We need to create a new Landscape Actor + // Either we didnt create one before, or we cannot reuse the old one (size has changed) + + // Convert the height data from Houdini's heightfield to Unreal's Landscape + TArray< uint16 > IntHeightData; + FTransform LandscapeTransform; + if (!FHoudiniLandscapeUtils::ConvertHeightfieldDataToLandscapeData( + FloatValues, VolumeInfo, + UnrealXSize, UnrealYSize, + FloatMin, FloatMax, + IntHeightData, LandscapeTransform)) + continue; + + // Look for all the layers/masks corresponding to the current heightfield + TArray< const FHoudiniGeoPartObject* > FoundLayers; + FHoudiniLandscapeUtils::GetHeightfieldsLayersInArray(FoundVolumes, *CurrentHeightfield, FoundLayers); + + // Extract and convert the Landscape layers + TArray< FLandscapeImportLayerInfo > ImportLayerInfos; + if (!FHoudiniLandscapeUtils::CreateLandscapeLayers(HoudiniCookParams, FoundLayers, *CurrentHeightfield, + UnrealXSize, UnrealYSize, GlobalMinimums, GlobalMaximums, ImportLayerInfos)) + continue; + + // Create the actual Landscape + ALandscapeProxy * CurrentLandscape = CreateLandscape( + IntHeightData, ImportLayerInfos, + LandscapeTransform, UnrealXSize, UnrealYSize, + NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, + LandscapeMaterial, LandscapeHoleMaterial, bCreateLandscapeStreamingProxy ); + + if (!CurrentLandscape) + continue; + + // Update the visibility mask / layer if we have any + for (auto CurrLayerInfo : ImportLayerInfos) + { + if (CurrLayerInfo.LayerInfo && CurrLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + { + CurrentLandscape->VisibilityLayer = CurrLayerInfo.LayerInfo; + CurrentLandscape->VisibilityLayer->bNoWeightBlend = true; + CurrentLandscape->VisibilityLayer->AddToRoot(); + } + } + + // Add the new landscape to the map + NewLandscapes.Add(*CurrentHeightfield, CurrentLandscape); + } + else + { + // We're reusing an existing landscape Actor + // Only update the height / layer data that has changed + + // Update the materials if they have changed + if ( FoundLandscape->GetLandscapeMaterial() != LandscapeMaterial ) + FoundLandscape->LandscapeMaterial = LandscapeMaterial; + + if ( FoundLandscape->GetLandscapeHoleMaterial() != LandscapeHoleMaterial ) + FoundLandscape->LandscapeHoleMaterial = LandscapeHoleMaterial; + + // Update the previous landscape's height data + // Decide if we need to create a new Landscape or if we can reuse the previous one + ULandscapeInfo* PreviousInfo = FoundLandscape->GetLandscapeInfo(); + if ( !PreviousInfo ) + continue; + + FLandscapeEditDataInterface LandscapeEdit(PreviousInfo); + + // Update the height data only if it's marked as changed + if ( CurrentHeightfield->bHasGeoChanged ) + { + // Convert the height data from Houdini's heightfield to Unreal's Landscape + TArray< uint16 > IntHeightData; + FTransform LandscapeTransform; + if ( !FHoudiniLandscapeUtils::ConvertHeightfieldDataToLandscapeData( + FloatValues, VolumeInfo, + UnrealXSize, UnrealYSize, + FloatMin, FloatMax, + IntHeightData, LandscapeTransform, + UpdateLandscapeComponent ) ) + continue; + + if ( !UpdateLandscapeComponent ) + LandscapeEdit.SetHeightData(0, 0, UnrealXSize - 1, UnrealYSize - 1, IntHeightData.GetData(), 0, true); + else + LandscapeEdit.SetHeightData(MinX, MinY, MaxX, MaxY, IntHeightData.GetData(), 0, true); + + // Set the landscape Transform + if ( !UpdateLandscapeComponent ) + FoundLandscape->SetActorRelativeTransform(LandscapeTransform); + } + + // Look for all the layers/masks corresponding to the current heightfield + TArray< const FHoudiniGeoPartObject* > FoundLayers; + FHoudiniLandscapeUtils::GetHeightfieldsLayersInArray(FoundVolumes, *CurrentHeightfield, FoundLayers); + + // Get the names of all the non weight blended layers + TArray< FString > NonWeightBlendedLayerNames; + FHoudiniLandscapeUtils::GetNonWeightBlendedLayerNames(*CurrentHeightfield, NonWeightBlendedLayerNames); + + for ( auto LayerGeoPartObject : FoundLayers ) + { + if (!LayerGeoPartObject) + continue; + + if (!LayerGeoPartObject->IsValid()) + continue; + + if (LayerGeoPartObject->AssetId == -1) + continue; + + if ( !LayerGeoPartObject->bHasGeoChanged ) + continue; + + // Extract the layer's float data from the HF + TArray< float > FloatLayerData; + HAPI_VolumeInfo LayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&LayerVolumeInfo); + + float LayerMin = 0; + float LayerMax = 0; + if (!FHoudiniLandscapeUtils::GetHeightfieldData(*LayerGeoPartObject, FloatLayerData, LayerVolumeInfo, LayerMin, LayerMax)) + continue; + + // No need to create flat layers as Unreal will remove them afterwards.. + if (LayerMin == LayerMax) + continue; + + // Get the layer's name + FString LayerString; + FHoudiniEngineString(LayerVolumeInfo.nameSH).ToFString(LayerString); + + // Check if that landscape layer has been marked as unit (range in [0-1] + if ( IsUnitLandscapeLayer( *LayerGeoPartObject ) ) + { + LayerMin = 0.0f; + LayerMax = 1.0f; + } + else + { + // Do we want to convert the layer's value using the global Min/Max + if (GlobalMaximums.Contains(LayerString)) + LayerMax = GlobalMaximums[LayerString]; + + if (GlobalMinimums.Contains(LayerString)) + LayerMin = GlobalMinimums[LayerString]; + } + + // Find the ImportLayerInfo and LayerInfo objects + ObjectTools::SanitizeObjectName(LayerString); + FName LayerName(*LayerString); + FLandscapeImportLayerInfo currentLayerInfo(LayerName); + + UPackage * Package = nullptr; + currentLayerInfo.LayerInfo = FHoudiniLandscapeUtils::CreateLandscapeLayerInfoObject(HoudiniCookParams, LayerString.GetCharArray().GetData(), Package, LayerGeoPartObject->GetPartId()); + if (!currentLayerInfo.LayerInfo || !Package) + continue; + + // Convert the float data to uint8 + if ( !FHoudiniLandscapeUtils::ConvertHeightfieldLayerToLandscapeLayer( + FloatLayerData, LayerVolumeInfo.yLength, LayerVolumeInfo.xLength, + LayerMin, LayerMax, + UnrealXSize, UnrealYSize, + currentLayerInfo.LayerData, + UpdateLandscapeComponent ) ) + continue; + + // Store the data used to convert the Houdini float values to int in the DebugColor + // This is the only way we'll be able to reconvert those values back to their houdini equivalent afterwards... + // R = Min, G = Max, B = Spacing, A = ? + currentLayerInfo.LayerInfo->LayerUsageDebugColor.R = LayerMin; + currentLayerInfo.LayerInfo->LayerUsageDebugColor.G = LayerMax; + currentLayerInfo.LayerInfo->LayerUsageDebugColor.B = (LayerMax - LayerMin) / 255.0f; + currentLayerInfo.LayerInfo->LayerUsageDebugColor.A = PI; + + // Updated non weight blended layers (visibility is by default non weight blended) + if (NonWeightBlendedLayerNames.Contains(LayerString) || LayerString.Equals(TEXT("visibility"), ESearchCase::IgnoreCase)) + currentLayerInfo.LayerInfo->bNoWeightBlend = true; + else + currentLayerInfo.LayerInfo->bNoWeightBlend = false; + + // Update the layer on the heightfield + if ( !UpdateLandscapeComponent ) + LandscapeEdit.SetAlphaData( currentLayerInfo.LayerInfo, 0, 0, UnrealXSize - 1, UnrealYSize - 1, currentLayerInfo.LayerData.GetData(), 0 ); + else + LandscapeEdit.SetAlphaData( currentLayerInfo.LayerInfo, MinX, MinY, MaxX, MaxY, currentLayerInfo.LayerData.GetData(), 0 ); + + if ( currentLayerInfo.LayerInfo && currentLayerInfo.LayerName.ToString().Equals( TEXT("Visibility"), ESearchCase::IgnoreCase ) ) + { + FoundLandscape->VisibilityLayer = currentLayerInfo.LayerInfo; + FoundLandscape->VisibilityLayer->bNoWeightBlend = true; + FoundLandscape->VisibilityLayer->AddToRoot(); + } + } + + // Update the materials if they have changed + if (FoundLandscape->GetLandscapeMaterial() != LandscapeMaterial) + FoundLandscape->LandscapeMaterial = LandscapeMaterial; + + if (FoundLandscape->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) + FoundLandscape->LandscapeHoleMaterial = LandscapeHoleMaterial; + + //FoundLandscape->RecreateCollisionComponents(); + + //PreviousInfo->UpdateLayerInfoMap(); + //PreviousInfo->RecreateCollisionComponents(); + //PreviousInfo->UpdateAllAddCollisions(); + + // We can add the landscape to the new map + NewLandscapes.Add(*CurrentHeightfield, FoundLandscape); + } + } + + for (auto Iter : NewLandscapes) + { + FHoudiniGeoPartObject HGPO = Iter.Key; + + TWeakObjectPtr Landscape = Iter.Value; + if (!Landscape.IsValid()) + continue; + + // Update the landscape's collisions + Landscape->RecreateCollisionComponents(); + + // Handle the HF's tags + // See if we have unreal_tag_ attribute + TArray Tags; + if (!FHoudiniEngineUtils::GetUnrealTagAttributes(HGPO, Tags)) + continue; + + Landscape->Tags = Tags; + } + + return true; +} + +ALandscapeProxy * +FHoudiniLandscapeUtils::CreateLandscape( + const TArray< uint16 >& IntHeightData, + const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, + const FTransform& LandscapeTransform, + const int32& XSize, const int32& YSize, + const int32& NumSectionPerLandscapeComponent, const int32& NumQuadsPerLandscapeSection, + UMaterialInterface* LandscapeMaterial, UMaterialInterface* LandscapeHoleMaterial, + const bool& CreateLandscapeStreamingProxy ) +{ + if ( ( XSize < 2 ) || ( YSize < 2 ) ) + return nullptr; + + if ( IntHeightData.Num() != ( XSize * YSize ) ) + return nullptr; + + if ( !GEditor ) + return nullptr; + + // Get the world we'll spawn the landscape in + UWorld* MyWorld = nullptr; + { + // We want to create the landscape in the landscape editor mode's world + FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext(); + MyWorld = EditorWorldContext.World(); + } + + if ( !MyWorld ) + return nullptr; + + // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos + ALandscapeProxy* LandscapeProxy = nullptr; + if ( CreateLandscapeStreamingProxy ) + LandscapeProxy = MyWorld->SpawnActor(); + else + LandscapeProxy = MyWorld->SpawnActor(); + + if (!LandscapeProxy) + return nullptr; + + // Create a new GUID + FGuid currentGUID = FGuid::NewGuid(); + LandscapeProxy->SetLandscapeGuid( currentGUID ); + + // Set the landscape Transform + LandscapeProxy->SetActorTransform( LandscapeTransform ); + + // Autosaving the layers prevents them for being deleted with the Asset + // Save the packages created for the LayerInfos + //if ( CreatedLayerInfoPackage.Num() > 0 ) + // FEditorFileUtils::PromptForCheckoutAndSave( CreatedLayerInfoPackage, true, false ); + + // Import the landscape data + + // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue + LandscapeProxy->bCastStaticShadow = false; + + if ( LandscapeMaterial ) + LandscapeProxy->LandscapeMaterial = LandscapeMaterial; + + if ( LandscapeHoleMaterial ) + LandscapeProxy->LandscapeHoleMaterial = LandscapeHoleMaterial; + + // Setting the layer type here. + ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; + + TMap> HeightmapDataPerLayers; + TMap> MaterialLayerDataPerLayer; + HeightmapDataPerLayers.Add(FGuid(), IntHeightData); + MaterialLayerDataPerLayer.Add(FGuid(), ImportLayerInfos); + + // Import the data + LandscapeProxy->Import( + currentGUID, + 0, 0, XSize - 1, YSize - 1, + NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, + HeightmapDataPerLayers, NULL, + MaterialLayerDataPerLayer, ImportLayerType ); + + // Copied straight from UE source code to avoid crash after importing the landscape: + // automatically calculate a lighting LOD that won't crash lightmass (hopefully) + // < 2048x2048 -> LOD0, >=2048x2048 -> LOD1, >= 4096x4096 -> LOD2, >= 8192x8192 -> LOD3 + LandscapeProxy->StaticLightingLOD = FMath::DivideAndRoundUp( FMath::CeilLogTwo( ( XSize * YSize ) / ( 2048 * 2048 ) + 1 ), ( uint32 )2 ); + + // Register all the landscape components + LandscapeProxy->RegisterAllComponents(); + + /*ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); + if (!Landscape) + return nullptr;*/ + + return LandscapeProxy; +} + +void FHoudiniLandscapeUtils::GetHeightFieldLandscapeMaterials( + const FHoudiniGeoPartObject& Heightfield, + UMaterialInterface*& LandscapeMaterial, + UMaterialInterface*& LandscapeHoleMaterial ) +{ + LandscapeMaterial = nullptr; + LandscapeHoleMaterial = nullptr; + + if ( !Heightfield.IsVolume() ) + return; + + std::string MarshallingAttributeNameMaterial = HAPI_UNREAL_ATTRIB_MATERIAL; + std::string MarshallingAttributeNameMaterialInstance = HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE; + std::string MarshallingAttributeNameMaterialHole = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE; + std::string MarshallingAttributeNameMaterialHoleInstance = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE; + + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if ( HoudiniRuntimeSettings ) + { + if ( !HoudiniRuntimeSettings->MarshallingAttributeMaterial.IsEmpty() ) + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeMaterial, + MarshallingAttributeNameMaterial); + + if ( !HoudiniRuntimeSettings->MarshallingAttributeMaterialHole.IsEmpty() ) + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeMaterialHole, + MarshallingAttributeNameMaterialHole ); + } + + TArray< FString > Materials; + HAPI_AttributeInfo AttribMaterials; + FHoudiniApi::AttributeInfo_Init(&AttribMaterials); + //FMemory::Memset< HAPI_AttributeInfo >( AttribMaterials, 0 ); + + // First, look for landscape material + { + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + Heightfield, MarshallingAttributeNameMaterial.c_str(), + AttribMaterials, Materials ); + + // If the material attribute was not found, check the material instance attribute. + if ( !AttribMaterials.exists ) + { + Materials.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + Heightfield, MarshallingAttributeNameMaterialInstance.c_str(), + AttribMaterials, Materials); + } + + if ( AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL ) + { + HOUDINI_LOG_WARNING( TEXT( "Landscape: unreal_material must be a primitive or detail attribute, ignoring attribute." ) ); + AttribMaterials.exists = false; + Materials.Empty(); + } + + if ( AttribMaterials.exists && Materials.Num() > 0 ) + { + // Load the material + LandscapeMaterial = Cast< UMaterialInterface >( StaticLoadObject( + UMaterialInterface::StaticClass(), + nullptr, *( Materials[ 0 ] ), nullptr, LOAD_NoWarn, nullptr ) ); + } + } + + Materials.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribMaterials); + //FMemory::Memset< HAPI_AttributeInfo >( AttribMaterials, 0 ); + + // Then, for the hole_material + { + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + Heightfield, MarshallingAttributeNameMaterialHole.c_str(), + AttribMaterials, Materials ); + + // If the material attribute was not found, check the material instance attribute. + if (!AttribMaterials.exists) + { + Materials.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + Heightfield, MarshallingAttributeNameMaterialHoleInstance.c_str(), + AttribMaterials, Materials); + } + + if ( AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL ) + { + HOUDINI_LOG_WARNING( TEXT( "Landscape: unreal_material must be a primitive or detail attribute, ignoring attribute." ) ); + AttribMaterials.exists = false; + Materials.Empty(); + } + + if ( AttribMaterials.exists && Materials.Num() > 0 ) + { + // Load the material + LandscapeHoleMaterial = Cast< UMaterialInterface >( StaticLoadObject( + UMaterialInterface::StaticClass(), + nullptr, *( Materials[ 0 ] ), nullptr, LOAD_NoWarn, nullptr ) ); + } + } +} + +bool FHoudiniLandscapeUtils::CreateLandscapeLayers( + FHoudiniCookParams& HoudiniCookParams, + const TArray< const FHoudiniGeoPartObject* >& FoundLayers, + const FHoudiniGeoPartObject& Heightfield, + const int32& LandscapeXSize, const int32& LandscapeYSize, + const TMap& GlobalMinimums, + const TMap& GlobalMaximums, + TArray& ImportLayerInfos ) +{ + // Verifying HoudiniCookParams validity + if ( !HoudiniCookParams.HoudiniAsset || !HoudiniCookParams.CookedTemporaryLandscapeLayers ) + return false; + + ImportLayerInfos.Empty(); + + // Get the names of all the non weight blended layers + TArray< FString > NonWeightBlendedLayerNames; + FHoudiniLandscapeUtils::GetNonWeightBlendedLayerNames( Heightfield, NonWeightBlendedLayerNames ); + + TArray CreatedLandscapeLayerPackage; + + // Try to create all the layers + ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; + for ( TArray::TConstIterator IterLayers( FoundLayers ); IterLayers; ++IterLayers ) + { + const FHoudiniGeoPartObject * LayerGeoPartObject = *IterLayers; + if ( !LayerGeoPartObject ) + continue; + + if ( !LayerGeoPartObject->IsValid() ) + continue; + + if ( LayerGeoPartObject->AssetId == -1 ) + continue; + + TArray< float > FloatLayerData; + HAPI_VolumeInfo LayerVolumeInfo; + float LayerMin = 0; + float LayerMax = 0; + if ( !FHoudiniLandscapeUtils::GetHeightfieldData( *LayerGeoPartObject, FloatLayerData, LayerVolumeInfo, LayerMin, LayerMax ) ) + continue; + + // No need to create flat layers as Unreal will remove them afterwards.. + if ( LayerMin == LayerMax ) + continue; + + // Get the layer's name + FString LayerString; + FHoudiniEngineString(LayerVolumeInfo.nameSH).ToFString(LayerString); + + // Check if that landscape layer has been marked as unit (range in [0-1] + if ( IsUnitLandscapeLayer( *LayerGeoPartObject ) ) + { + LayerMin = 0.0f; + LayerMax = 1.0f; + } + else + { + // We want to convert the layer using the global Min/Max + if ( GlobalMaximums.Contains( LayerString ) ) + LayerMax = GlobalMaximums[ LayerString ]; + + if ( GlobalMinimums.Contains( LayerString ) ) + LayerMin = GlobalMinimums[ LayerString ]; + } + + // Creating the ImportLayerInfo and LayerInfo objects + ObjectTools::SanitizeObjectName( LayerString ); + FName LayerName( *LayerString ); + FLandscapeImportLayerInfo currentLayerInfo( LayerName ); + + UPackage * Package = nullptr; + currentLayerInfo.LayerInfo = FHoudiniLandscapeUtils::CreateLandscapeLayerInfoObject( HoudiniCookParams, LayerString.GetCharArray().GetData(), Package, LayerGeoPartObject->PartId); + if ( !currentLayerInfo.LayerInfo || !Package ) + continue; + + // Convert the float data to uint8 + // HF masks need their X/Y sizes swapped + if ( !FHoudiniLandscapeUtils::ConvertHeightfieldLayerToLandscapeLayer( + FloatLayerData, LayerVolumeInfo.yLength, LayerVolumeInfo.xLength, + LayerMin, LayerMax, + LandscapeXSize, LandscapeYSize, + currentLayerInfo.LayerData ) ) + continue; + + // We will store the data used to convert from Houdini values to int in the DebugColor + // This is the only way we'll be able to reconvert those values back to their houdini equivalent afterwards... + // R = Min, G = Max, B = Spacing, A = ? + currentLayerInfo.LayerInfo->LayerUsageDebugColor.R = LayerMin; + currentLayerInfo.LayerInfo->LayerUsageDebugColor.G = LayerMax; + currentLayerInfo.LayerInfo->LayerUsageDebugColor.B = ( LayerMax - LayerMin) / 255.0f; + currentLayerInfo.LayerInfo->LayerUsageDebugColor.A = PI; + + HoudiniCookParams.CookedTemporaryLandscapeLayers->Add( Package, Heightfield ); + + // Visibility are by default non weight blended + if ( NonWeightBlendedLayerNames.Contains( LayerString ) + || LayerString.Equals(TEXT("visibility"), ESearchCase::IgnoreCase) ) + currentLayerInfo.LayerInfo->bNoWeightBlend = true; + else + currentLayerInfo.LayerInfo->bNoWeightBlend = false; + + // Mark the package dirty... + Package->MarkPackageDirty(); + + CreatedLandscapeLayerPackage.Add( Package ); + + ImportLayerInfos.Add( currentLayerInfo ); + } + + // Autosaving the layers prevents them for being deleted with the Asset + // Save the packages created for the LayerInfos + FEditorFileUtils::PromptForCheckoutAndSave( CreatedLandscapeLayerPackage, true, false ); + + return true; +} + +ULandscapeLayerInfoObject * +FHoudiniLandscapeUtils::CreateLandscapeLayerInfoObject( FHoudiniCookParams& HoudiniCookParams, const TCHAR* LayerName, UPackage*& Package , HAPI_PartId PartId) +{ + // Verifying HoudiniCookParams validity + if ( !HoudiniCookParams.HoudiniAsset || HoudiniCookParams.HoudiniAsset->IsPendingKill() ) + return nullptr; + + FString ComponentGUIDString = HoudiniCookParams.PackageGUID.ToString().Left( FHoudiniEngineUtils::PackageGUIDComponentNameLength ); + + FString LayerNameString = FString::Printf( TEXT( "%s_%d" ), LayerName, (int32)PartId ); + LayerNameString = UPackageTools::SanitizePackageName( LayerNameString ); + + // Create the LandscapeInfoObjectName from the Asset name and the mask name + FName LayerObjectName = FName( * (HoudiniCookParams.HoudiniAsset->GetName() + ComponentGUIDString + TEXT( "_LayerInfoObject_" ) + LayerNameString ) ); + + // Save the package in the temp folder + FString Path = HoudiniCookParams.TempCookFolder.ToString() + TEXT( "/" ); + FString PackageName = Path + LayerObjectName.ToString(); + PackageName = UPackageTools::SanitizePackageName( PackageName ); + + // See if package exists, if it does, reuse it + bool bCreatedPackage = false; + Package = FindPackage( nullptr, *PackageName ); + if ( !Package || Package->IsPendingKill() ) + { + // We need to create a new package + Package = CreatePackage( nullptr, *PackageName ); + bCreatedPackage = true; + } + + if ( !Package || Package->IsPendingKill() ) + return nullptr; + + if ( !Package->IsFullyLoaded() ) + Package->FullyLoad(); + + ULandscapeLayerInfoObject* LayerInfo = nullptr; + if ( !bCreatedPackage ) + { + // See if we can load the layer info instead of creating a new one + LayerInfo = (ULandscapeLayerInfoObject*)StaticFindObjectFast(ULandscapeLayerInfoObject::StaticClass(), Package, LayerObjectName); + } + + if ( !LayerInfo || LayerInfo->IsPendingKill() ) + { + // Create a new LandscapeLayerInfoObject in the package + LayerInfo = NewObject(Package, LayerObjectName, RF_Public | RF_Standalone /*| RF_Transactional*/); + + // Notify the asset registry + FAssetRegistryModule::AssetCreated(LayerInfo); + } + + if ( LayerInfo && !LayerInfo->IsPendingKill() ) + { + LayerInfo->LayerName = LayerName; + + // Trigger update of the Layer Info + LayerInfo->PreEditChange(nullptr); + LayerInfo->PostEditChange(); + LayerInfo->MarkPackageDirty(); + + // Mark the package dirty... + Package->MarkPackageDirty(); + } + + return LayerInfo; +} + +bool FHoudiniLandscapeUtils::AddLandscapeMaterialAttributesToVolume( + const HAPI_NodeId& VolumeNodeId, const HAPI_PartId& PartId, + UMaterialInterface* LandscapeMaterial, UMaterialInterface* LandscapeHoleMaterial ) +{ + if ( !FHoudiniEngineUtils::IsValidNodeId(VolumeNodeId) ) + return false; + + // LANDSCAPE MATERIAL + if ( LandscapeMaterial && !LandscapeMaterial->IsPendingKill() ) + { + // Extract the path name from the material interface + FString LandscapeMaterialString = LandscapeMaterial->GetPathName(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL; + + // Marshall in material names. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); + AttributeInfoMaterial.count = 1; + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); + + if ( HAPI_RESULT_SUCCESS == Result ) + { + // Convert the FString to cont char * + std::string LandscapeMatCStr = TCHAR_TO_ANSI(*LandscapeMaterialString); + const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); + TArray LandscapeMatArr; + LandscapeMatArr.Add( LandscapeMatCStrRaw ); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, + LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); + } + + if( Result != HAPI_RESULT_SUCCESS ) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_material attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + } + + // HOLE MATERIAL + if ( LandscapeHoleMaterial && !LandscapeHoleMaterial->IsPendingKill() ) + { + // Extract the path name from the material interface + FString LandscapeMaterialString = LandscapeHoleMaterial->GetPathName(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE; + + // Marshall in material names. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); + AttributeInfoMaterial.count = 1; + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); + + if ( Result == HAPI_RESULT_SUCCESS ) + { + // Convert the FString to cont char * + std::string LandscapeMatCStr = TCHAR_TO_ANSI(*LandscapeMaterialString); + const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); + TArray LandscapeMatArr; + LandscapeMatArr.Add(LandscapeMatCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, + LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); + } + + if ( Result != HAPI_RESULT_SUCCESS ) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_material attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + } + + return true; +} + +bool FHoudiniLandscapeUtils::AddLandscapeGlobalMaterialAttribute( const HAPI_NodeId& NodeId, ALandscapeProxy * LandscapeProxy ) +{ + if ( !LandscapeProxy ) + return false; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL; + if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeMaterial.IsEmpty() ) + { + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeMaterial, + MarshallingAttributeMaterialName ); + } + + // If there's a global landscape material, we marshall it as detail. + UMaterialInterface * MaterialInterface = LandscapeProxy->GetLandscapeMaterial(); + const char * MaterialNameStr = ""; + if ( MaterialInterface ) + { + FString FullMaterialName = MaterialInterface->GetPathName(); + MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); + } + + HAPI_AttributeInfo AttributeInfoDetailMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterial ); + AttributeInfoDetailMaterial.count = 1; + AttributeInfoDetailMaterial.tupleSize = 1; + AttributeInfoDetailMaterial.exists = true; + AttributeInfoDetailMaterial.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoDetailMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoDetailMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoDetailMaterial ), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoDetailMaterial, + (const char**)&MaterialNameStr, 0, AttributeInfoDetailMaterial.count ), false ); + + // Get name of attribute used for marshalling hole materials. + std::string MarshallingAttributeMaterialHoleName = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE; + if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeMaterialHole.IsEmpty() ) + { + FHoudiniEngineUtils::ConvertUnrealString( + HoudiniRuntimeSettings->MarshallingAttributeMaterialHole, + MarshallingAttributeMaterialHoleName ); + } + + // If there's a global landscape hole material, we marshall it as detail. + UMaterialInterface * HoleMaterialInterface = LandscapeProxy->GetLandscapeHoleMaterial(); + const char * HoleMaterialNameStr = ""; + if ( HoleMaterialInterface ) + { + FString FullMaterialName = HoleMaterialInterface->GetPathName(); + MaterialNameStr = TCHAR_TO_UTF8( *FullMaterialName ); + } + + HAPI_AttributeInfo AttributeInfoDetailMaterialHole; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterialHole); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterialHole ); + AttributeInfoDetailMaterialHole.count = 1; + AttributeInfoDetailMaterialHole.tupleSize = 1; + AttributeInfoDetailMaterialHole.exists = true; + AttributeInfoDetailMaterialHole.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoDetailMaterialHole.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoDetailMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MarshallingAttributeMaterialHoleName.c_str(), + &AttributeInfoDetailMaterialHole), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MarshallingAttributeMaterialHoleName.c_str(), + &AttributeInfoDetailMaterialHole, (const char **)&HoleMaterialNameStr, 0, + AttributeInfoDetailMaterialHole.count ), false ); + + return true; +} + +bool +FHoudiniLandscapeUtils::UpdateOldLandscapeReference(ALandscapeProxy* OldLandscape, ALandscapeProxy* NewLandscape) +{ + if ( !OldLandscape || !NewLandscape ) + return false; + + bool bReturn = false; + + // Iterates through all the Houdini Assets in the scene + UWorld* editorWorld = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr; + if ( !editorWorld ) + return false; + + for ( TActorIterator ActorItr( editorWorld ); ActorItr; ++ActorItr ) + { + AHoudiniAssetActor* Actor = *ActorItr; + UHoudiniAssetComponent * HoudiniAssetComponent = Actor->GetHoudiniAssetComponent(); + if ( !HoudiniAssetComponent || !HoudiniAssetComponent->IsValidLowLevel() ) + { + HOUDINI_LOG_ERROR(TEXT("Failed to export a Houdini Asset in the scene!")); + continue; + } + + if ( HoudiniAssetComponent->IsTemplate() ) + continue; + + if ( HoudiniAssetComponent->IsPendingKillOrUnreachable() ) + continue; + + if ( !HoudiniAssetComponent->GetOuter() ) + continue; + + bReturn = HoudiniAssetComponent->ReplaceLandscapeInInputs( OldLandscape, NewLandscape ); + } + + return bReturn; +} + +bool +FHoudiniLandscapeUtils::InitDefaultHeightfieldMask( + const HAPI_VolumeInfo& HeightVolumeInfo, + const HAPI_NodeId& MaskVolumeNodeId ) +{ + // We need to have a mask layer as it is required for proper heightfield functionalities + + // Creating an array filled with 0.0 + TArray< float > MaskFloatData; + MaskFloatData.Init( 0.0f, HeightVolumeInfo.xLength * HeightVolumeInfo.yLength ); + + // Creating the volume infos + HAPI_VolumeInfo MaskVolumeInfo = HeightVolumeInfo; + + // Set the heighfield data in Houdini + FString MaskName = TEXT("mask"); + HAPI_PartId PartId = 0; + if ( !SetHeighfieldData( MaskVolumeNodeId, PartId, MaskFloatData, MaskVolumeInfo, MaskName ) ) + return false; + + return true; +} + + +bool +FHoudiniLandscapeUtils::BackupLandscapeToFile(const FString& BaseName, ALandscapeProxy* Landscape) +{ + // We need to cache the input landscape to a file + if ( !Landscape ) + return false; + + ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Save Height data to file + //FString HeightSave = TEXT("/Game/HoudiniEngine/Temp/HeightCache.png"); + FString HeightSave = BaseName + TEXT("_height.png"); + LandscapeInfo->ExportHeightmap(HeightSave); + Landscape->ReimportHeightmapFilePath = HeightSave; + + // Save each layer to a file + for ( int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++ ) + { + FName CurrentLayerName = LandscapeInfo->Layers[LayerIndex].GetLayerName(); + //ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->GetLayerInfoByName(CurrentLayerName, Landscape); + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; + if ( !CurrentLayerInfo || CurrentLayerInfo->IsPendingKill() ) + continue; + + FString LayerSave = BaseName + CurrentLayerName.ToString() + TEXT(".png"); + LandscapeInfo->ExportLayer(CurrentLayerInfo, LayerSave); + + // Update the file reimport path on the input landscape for this layer + LandscapeInfo->GetLayerEditorSettings(CurrentLayerInfo).ReimportLayerFilePath = LayerSave; + } + + return true; +} + +bool +FHoudiniLandscapeUtils::RestoreLandscapeFromFile( ALandscapeProxy* LandscapeProxy ) +{ + if ( !LandscapeProxy ) + return false; + + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Restore Height data from the backup file + FString ReimportFile = LandscapeProxy->ReimportHeightmapFilePath; + if ( !ImportLandscapeData(LandscapeInfo, ReimportFile, TEXT("height") ) ) + HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); + + + // Restore each layer from the backup file + TArray< ULandscapeLayerInfoObject* > SourceLayers; + for ( int LayerIndex = 0; LayerIndex < LandscapeProxy->EditorLayerSettings.Num(); LayerIndex++ ) + { + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeProxy->EditorLayerSettings[LayerIndex].LayerInfoObj; + if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) + continue; + + FString CurrentLayerName = CurrentLayerInfo->LayerName.ToString(); + ReimportFile = LandscapeProxy->EditorLayerSettings[LayerIndex].ReimportLayerFilePath; + + if (!ImportLandscapeData(LandscapeInfo, ReimportFile, CurrentLayerName, CurrentLayerInfo)) + HOUDINI_LOG_ERROR( TEXT("Could not restore the landscape actor's source height data.") ); + + SourceLayers.Add( CurrentLayerInfo ); + } + + // Iterate on the landscape info's layer to remove any layer that could have been added by Houdini + for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) + { + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; + if ( SourceLayers.Contains( CurrentLayerInfo ) ) + continue; + + // Delete the added layer + FName LayerName = LandscapeInfo->Layers[LayerIndex].LayerName; + LandscapeInfo->DeleteLayer(CurrentLayerInfo, LayerName); + } + + return true; +} + +bool +FHoudiniLandscapeUtils::ImportLandscapeData( + ULandscapeInfo* LandscapeInfo, const FString& Filename, const FString& LayerName, ULandscapeLayerInfoObject* LayerInfoObject ) +{ + // + // Code copied/edited from FEdModeLandscape::ImportData as we cannot access that function + // + if ( !LandscapeInfo ) + return false; + + bool IsHeight = LayerName.Equals(TEXT("height"), ESearchCase::IgnoreCase); + + int32 MinX, MinY, MaxX, MaxY; + if (LandscapeInfo && LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + { + const FLandscapeFileResolution LandscapeResolution = { (uint32)(1 + MaxX - MinX), (uint32)(1 + MaxY - MinY) }; + + ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked("LandscapeEditor"); + + if ( IsHeight ) + { + const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); + + if (!HeightmapFormat) + { + HOUDINI_LOG_ERROR( TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName ); + return false; + } + + FLandscapeFileResolution ImportResolution = { 0, 0 }; + + const FLandscapeHeightmapInfo HeightmapInfo = HeightmapFormat->Validate(*Filename); + + // display error message if there is one, and abort the import + if (HeightmapInfo.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString()) ); + return false; + } + + // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape + if (HeightmapInfo.PossibleResolutions.Num() > 1) + { + if (!HeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The heightmap file does not match the Landscape extent and its exact resolution could not be determined")); + return false; + } + else + { + ImportResolution = LandscapeResolution; + } + } + + // display warning message if there is one and allow user to cancel + if (HeightmapInfo.ResultCode == ELandscapeImportResult::Warning) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); + + // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape + // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is + if (HeightmapInfo.PossibleResolutions.Num() == 1) + { + ImportResolution = HeightmapInfo.PossibleResolutions[0]; + if (ImportResolution != LandscapeResolution) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); + } + + FLandscapeHeightmapImportData ImportData = HeightmapFormat->Import(*Filename, ImportResolution); + if (ImportData.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); + return false; + } + + TArray Data; + if (ImportResolution != LandscapeResolution) + { + // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked + // so that reimports behave the same as the initial import :) + + const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; + const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; + + Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint16)); + + ExpandData(Data.GetData(), ImportData.Data.GetData(), + 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, + -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); + } + else + { + Data = MoveTemp(ImportData.Data); + } + + //FScopedTransaction Transaction(TEXT("Undo_ImportHeightmap", "Importing Landscape Heightmap")); + + FHeightmapAccessor HeightmapAccessor(LandscapeInfo); + HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData()); + } + else + { + // We're importing a Landscape layer + if ( !LayerInfoObject || LayerInfoObject->IsPendingKill() ) + return false; + + const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); + + if (!WeightmapFormat) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); + return false; + } + + FLandscapeFileResolution ImportResolution = { 0, 0 }; + + const FLandscapeWeightmapInfo WeightmapInfo = WeightmapFormat->Validate(*Filename, FName(*LayerName)); + + // display error message if there is one, and abort the import + if (WeightmapInfo.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); + return false; + } + + // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape + if (WeightmapInfo.PossibleResolutions.Num() > 1) + { + if (!WeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The weightmap file does not match the Landscape extent and its exact resolution could not be determined")); + return false; + } + else + { + ImportResolution = LandscapeResolution; + } + } + + // display warning message if there is one and allow user to cancel + if (WeightmapInfo.ResultCode == ELandscapeImportResult::Warning) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); + + // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape + // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is + if (WeightmapInfo.PossibleResolutions.Num() == 1) + { + ImportResolution = WeightmapInfo.PossibleResolutions[0]; + if (ImportResolution != LandscapeResolution) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); + } + + FLandscapeWeightmapImportData ImportData = WeightmapFormat->Import(*Filename, FName(*LayerName), ImportResolution); + + if (ImportData.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); + return false; + } + + TArray Data; + if (ImportResolution != LandscapeResolution) + { + // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked + // so that reimports behave the same as the initial import :) + const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; + const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; + + Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint8)); + + ExpandData(Data.GetData(), ImportData.Data.GetData(), + 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, + -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); + } + else + { + Data = MoveTemp(ImportData.Data); + } + + //FScopedTransaction Transaction(LOCTEXT("Undo_ImportWeightmap", "Importing Landscape Layer")); + + FAlphamapAccessor AlphamapAccessor(LandscapeInfo, LayerInfoObject); + AlphamapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData(), ELandscapeLayerPaintingRestriction::None); + } + } + + return true; +} + +#endif + +bool +FHoudiniLandscapeUtils::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& LayerGeoPartObject) +{ + // Check the attribute exists on primitive or detail + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; + if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject, "unreal_unit_landscape_layer", HAPI_ATTROWNER_PRIM)) + Owner = HAPI_ATTROWNER_PRIM; + else if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject, "unreal_unit_landscape_layer", HAPI_ATTROWNER_DETAIL)) + Owner = HAPI_ATTROWNER_DETAIL; + else + return false; + + // Check the value + HAPI_AttributeInfo AttribInfoUnitLayer; + FHoudiniApi::AttributeInfo_Init(&AttribInfoUnitLayer); + TArray< int32 > AttribValues; + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + LayerGeoPartObject, "unreal_unit_landscape_layer", AttribInfoUnitLayer, AttribValues, 1, Owner); + + if (AttribValues.Num() > 0 && AttribValues[0] == 1 ) + return true; + + return false; +} \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniLandscapeUtils.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniLandscapeUtils.h new file mode 100644 index 00000000..b0e578a6 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniLandscapeUtils.h @@ -0,0 +1,372 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Damien Pernuit +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#pragma once + +#include "HoudiniGeoPartObject.h" +#include "Landscape.h" + +struct FHoudiniCookParams; + +struct HOUDINIENGINERUNTIME_API FHoudiniLandscapeUtils +{ + public: + + //-------------------------------------------------------------------------------------------------- + // Houdini to Unreal + //-------------------------------------------------------------------------------------------------- +#if WITH_EDITOR + // Creates all the landscapes/layers from the volume array + static bool CreateAllLandscapes( + FHoudiniCookParams& HoudiniCookParams, + const TArray< FHoudiniGeoPartObject > & FoundVolumes, + TMap< FHoudiniGeoPartObject, TWeakObjectPtr >& Landscapes, + TMap< FHoudiniGeoPartObject, TWeakObjectPtr >& NewLandscapes, + TArray& InputLandscapeToUpdate, + float ForcedZMin = 0.0f, float ForcedZMax = 0.0f ); + + // Creates a single landscape object from the converted data + static ALandscapeProxy * CreateLandscape( + const TArray< uint16 >& IntHeightData, + const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, + const FTransform& LandscapeTransform, + const int32& XSize, const int32& YSize, + const int32& NumSectionPerLandscapeComponent, + const int32& NumQuadsPerLandscapeSection, + UMaterialInterface* LandscapeMaterial, + UMaterialInterface* LandscapeHoleMaterial, + const bool& CreateLandscapeStreamingProxy ); + + // Returns the materials assigned to the heightfield + static void GetHeightFieldLandscapeMaterials( + const FHoudiniGeoPartObject& Heightfield, + UMaterialInterface*& LandscapeMaterial, + UMaterialInterface*& LandscapeHoleMaterial ); + + // Creates the package needed to store landscape layer infos + static ULandscapeLayerInfoObject* CreateLandscapeLayerInfoObject( + FHoudiniCookParams& HoudiniCookParams, const TCHAR* LayerName, UPackage*& Package, HAPI_PartId PartId ); + + // Creates all the landscape layers for a given heightfield + static bool CreateLandscapeLayers( + FHoudiniCookParams& HoudiniCookParams, + const TArray< const FHoudiniGeoPartObject* >& FoundLayers, + const FHoudiniGeoPartObject& Heightfield, + const int32& LandscapeXSize, const int32& LandscapeYSize, + const TMap& GlobalMinimums, + const TMap& GlobalMaximums, + TArray& ImportLayerInfos ); + + /** Updates a reference to a generated landscape by the newly created one **/ + static bool UpdateOldLandscapeReference( + ALandscapeProxy* OldLandscape, ALandscapeProxy* NewLandscape ); +#endif + // Returns Heightfield contained in the GeoPartObject array + static void GetHeightfieldsInArray( + const TArray< FHoudiniGeoPartObject >& InArray, + TArray< const FHoudiniGeoPartObject* >& OutHeightfields ); + + // Returns the layers corresponding to a height field contained in the GeoPartObject array + static void GetHeightfieldsLayersInArray( + const TArray< FHoudiniGeoPartObject >& InArray, + const FHoudiniGeoPartObject& Heightfield, + TArray< const FHoudiniGeoPartObject* >& FoundLayers ); + + // Returns the global ZMin/ZMax value of all heightfields contained in the array + static void CalcHeightfieldsArrayGlobalZMinZMax( + const TArray< const FHoudiniGeoPartObject* >& InHeightfieldArray, + float& fGlobalMin, float& fGlobalMax ); + + // Returns the global ZMin/ZMax value per volume for all the heightfields contained in the array + static void CalcHeightfieldsArrayGlobalZMinZMax( + const TArray< FHoudiniGeoPartObject >& InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums); + + // Extract the float values of a given heightfield + static bool GetHeightfieldData( + const FHoudiniGeoPartObject& Heightfield, + TArray< float >& FloatValues, + HAPI_VolumeInfo& VolumeInfo, + float& FloatMin, float& FloatMax ); + + // Converts the Houdini Float height values to Unreal uint16 + static bool ConvertHeightfieldDataToLandscapeData( + const TArray< float >& HeightfieldFloatValues, + const HAPI_VolumeInfo& HeightfieldVolumeInfo, + const int32& FinalXSize, const int32& FinalYSize, + float FloatMin, float FloatMax, + TArray< uint16 >& IntHeightData, + FTransform& LandscapeTransform, + const bool& NoResize = false); + + // Converts the Houdini float layer values to Unreal uint8 + static bool ConvertHeightfieldLayerToLandscapeLayer( + const TArray< float >& FloatLayerData, + const int32& LayerXSize, const int32& LayerYSize, + const float& LayerMin, const float& LayerMax, + const int32& LandscapeXSize, const int32& LandscapeYSize, + TArray< uint8 >& LayerData, const bool& NoResize = false ); + + + // Calculates the closest "unreal friendly" size given a heighfield volume's size + static bool CalcLandscapeSizeFromHeightfieldSize( + const int32& SizeX, const int32& SizeY, + int32& NewSizeX, + int32& NewSizeY, + int32& NumberOfSectionsPerComponent, + int32& NumberOfQuadsPerSection ); + + // Resizes the HeightData so that it fits to UE4's size requirements. + static bool ResizeHeightDataForLandscape( + TArray& HeightData, + const int32& SizeX, const int32& SizeY, + const int32& NewSizeX, const int32& NewSizeY, + FVector& LandscapeResizeFactor, + FVector& LandscapePositionOffset ); + + // Resizes LayerData so that it fits the Landscape size + static bool ResizeLayerDataForLandscape( + TArray< uint8 >& LayerData, + const int32& SizeX, const int32& SizeY, + const int32& NewSizeX, const int32& NewSizeY ); + + // Checks if a layer's value should be converted in the [0 1] range + static bool IsUnitLandscapeLayer( + const FHoudiniGeoPartObject& LayerGeoPartObject ); + + //-------------------------------------------------------------------------------------------------- + // Unreal to Houdini - HEIGHTFIELDS + //-------------------------------------------------------------------------------------------------- + +#if WITH_EDITOR + // Creates a heightfield from a Landscape + static bool CreateHeightfieldFromLandscape( + ALandscapeProxy* LandscapeProxy, HAPI_NodeId& CreatedHeightfieldNodeId ); + + // Creates multiple heightfield from an array of Landscape Components + static bool CreateHeightfieldFromLandscapeComponentArray( + ALandscapeProxy* LandscapeProxy, + TSet< ULandscapeComponent * >& LandscapeComponentArray, + HAPI_NodeId& CreatedHeightfieldNodeId ); + + // Creates a Heightfield from a Landscape Component + static bool CreateHeightfieldFromLandscapeComponent( + ULandscapeComponent * LandscapeComponent, + const int32& ComponentIndex, + HAPI_NodeId& HeightFieldId, + HAPI_NodeId& MergeId, + int32& MergeInputIndex ); + + // Initialise the Heightfield Mask with default values + static bool InitDefaultHeightfieldMask( + const HAPI_VolumeInfo& HeightVolumeInfo, + const HAPI_NodeId& MaskVolumeNodeId ); + + // Extracts the uint16 values of a given landscape + static bool GetLandscapeData( + ALandscape* Landscape, + TArray& HeightData, + int32& XSize, int32& YSize, + FVector& Min, FVector& Max ); + + static bool GetLandscapeData( + ALandscapeProxy* LandscapeProxy, + TArray& HeightData, + int32& XSize, int32& YSize, + FVector& Min, FVector& Max ); + + static bool GetLandscapeData( + ULandscapeInfo* LandscapeInfo, + const int32& MinX, const int32& MinY, + const int32& MaxX, const int32& MaxY, + TArray& HeightData, + int32& XSize, int32& YSize ); + + // Extracts the uint8 values of a given landscape + static bool GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, const int32& LayerIndex, + TArray& LayerData, FLinearColor& LayerUsageDebugColor, + FString& LayerName ); + + static bool GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + const int32& MinX, const int32& MinY, + const int32& MaxX, const int32& MaxY, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName ); +#endif + // Converts Unreal uint16 values to Houdini Float + static bool ConvertLandscapeDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + FVector Min, FVector Max, + const FTransform& LandscapeTransform, + TArray& HeightfieldFloatValues, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + FVector& CenterOffset); + + // Converts Unreal uint8 values to Houdini Float + static bool ConvertLandscapeLayerDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + const FLinearColor& LayerUsageDebugColor, + TArray& LayerFloatValues, + HAPI_VolumeInfo& LayerVolumeInfo); + + // Set the volume float value for a heightfield + static bool SetHeighfieldData( + const HAPI_NodeId& AssetId, + const HAPI_PartId& PartId, + TArray< float >& FloatValues, + const HAPI_VolumeInfo& VolumeInfo, + const FString& HeightfieldName ); + + // Creates an unlocked heightfield input node + static bool CreateHeightfieldInputNode( + const HAPI_NodeId& ParentNodeId, const FString& NodeName, + const int32& XSize, const int32& YSize, + HAPI_NodeId& HeightfieldNodeId, HAPI_NodeId& HeightNodeId, + HAPI_NodeId& MaskNodeId, HAPI_NodeId& MergeNodeId ); + + // Landscape nodes clean up + static bool DestroyLandscapeAssetNode( + HAPI_NodeId& ConnectedAssetId, TArray& CreatedInputAssetIds ); + + // Returns an array containing the names of the non weightblended layers + static bool GetNonWeightBlendedLayerNames( + const FHoudiniGeoPartObject& HeightfieldGeoPartObject, TArray& NonWeightBlendedLayerNames ); + + static void GetLandscapeActorBounds( + ALandscape* Landscape, FVector& Origin, FVector& Extents ); + + static void GetLandscapeProxyBounds( + ALandscapeProxy* LandscapeProxy, FVector& Origin, FVector& Extents ); + + static bool AddLandscapeMaterialAttributesToVolume( + const HAPI_NodeId& VolumeNodeId, const HAPI_PartId& PartId, + UMaterialInterface* LandscapeMaterial, UMaterialInterface* LandscapeHoleMaterial ); + + //-------------------------------------------------------------------------------------------------- + // Input Landscape caching + //-------------------------------------------------------------------------------------------------- + static bool BackupLandscapeToFile( + const FString& BaseName, ALandscapeProxy* Landscape ); + + static bool RestoreLandscapeFromFile( + ALandscapeProxy* LandscapeProxy ); + + static bool ImportLandscapeData( + ULandscapeInfo* LandscapeInfo, const FString& Filename, const FString& LayerName, ULandscapeLayerInfoObject* LayerInfoObject = nullptr); + + //-------------------------------------------------------------------------------------------------- + // Unreal to Houdini - MESH / POINTS + //-------------------------------------------------------------------------------------------------- +#if WITH_EDITOR + // Extract data from the landscape + static bool ExtractLandscapeData( + ALandscapeProxy * LandscapeProxy, TSet< ULandscapeComponent * >& SelectedComponents, + const bool& bExportLighting, const bool& bExportTileUVs, const bool& bExportNormalizedUVs, + TArray& LandscapePositionArray, + TArray& LandscapeNormalArray, + TArray& LandscapeUVArray, + TArray& LandscapeComponentVertexIndicesArray, + TArray& LandscapeComponentNameArray, + TArray& LandscapeLightmapValues ); +#endif + + // Add the Position attribute extracted from a landscape + static bool AddLandscapePositionAttribute( + const HAPI_NodeId& NodeId, const TArray< FVector >& LandscapePositionArray ); + + // Add the Normal attribute extracted from a landscape + static bool AddLandscapeNormalAttribute( + const HAPI_NodeId& NodeId, const TArray< FVector >& LandscapeNormalArray ); + + // Add the UV attribute extracted from a landscape + static bool AddLandscapeUVAttribute( + const HAPI_NodeId& NodeId, const TArray< FVector >& LandscapeUVArray ); + + // Add the Component Vertex Index attribute extracted from a landscape + static bool AddLandscapeComponentVertexIndicesAttribute( + const HAPI_NodeId& NodeId, const TArray& LandscapeComponentVertexIndicesArray ); + + // Add the Component Name attribute extracted from a landscape + static bool AddLandscapeComponentNameAttribute( + const HAPI_NodeId& NodeId, const TArray& LandscapeComponentNameArray ); + + // Add the lightmap color attribute extracted from a landscape + static bool AddLandscapeLightmapColorAttribute( + const HAPI_NodeId& NodeId, const TArray& LandscapeLightmapValues ); + + // Creates and add the vertex indices and face materials attribute from a landscape + static bool AddLandscapeMeshIndicesAndMaterialsAttribute( + const HAPI_NodeId& NodeId, const bool& bExportMaterials, + const int32& ComponentSizeQuads, const int32& QuadCount, + ALandscapeProxy * LandscapeProxy, + const TSet< ULandscapeComponent * >& SelectedComponents ); + + // Add the tile index primitive attribute + static bool AddLandscapeTileAttribute( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const int32& TileIdx ); + + // Add the landscape component extent attribute + static bool AddLandscapeComponentExtentAttributes( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, + const int32& MinX, const int32& MaxX, + const int32& MinY, const int32& MaxY ); + + // Add the landscape component extent attribute + static bool GetLandscapeComponentExtentAttributes( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, + int32& MinX, int32& MaxX, + int32& MinY, int32& MaxY ); + +#if WITH_EDITOR + // Add the global (detail) material and hole material attribute from a landscape + static bool AddLandscapeGlobalMaterialAttribute( + const HAPI_NodeId& NodeId, ALandscapeProxy * LandscapeProxy ); +#endif + + // Helper functions to extract color from a texture + static FColor PickVertexColorFromTextureMip( + const uint8 * MipBytes, FVector2D & UVCoord, int32 MipWidth, int32 MipHeight); + + /* + // Duplicate a given Landscape. This will create a new package for it. This will also create necessary + // materials, textures, landscape layers and their corresponding packages. + static ALandscape * DuplicateLandscapeAndCreatePackage( + const ALandscape * Landscape, UHoudiniAssetComponent * Component, + const FHoudiniGeoPartObject & HoudiniGeoPartObject, EBakeMode BakeMode ); + */ +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp new file mode 100644 index 00000000..0b64f10b --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp @@ -0,0 +1,198 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniMeshSplitInstancerComponent.h" + +#include "HoudiniApi.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "Components/StaticMeshComponent.h" +#if WITH_EDITOR +#include "LevelEditorViewport.h" +#include "MeshPaintHelpers.h" +#endif + +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniMeshSplitInstancerComponent::UHoudiniMeshSplitInstancerComponent( const FObjectInitializer& ObjectInitializer ) +: Super( ObjectInitializer ) +, InstancedMesh( nullptr ) +{ +} + +void +UHoudiniMeshSplitInstancerComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) +{ + ClearInstances(0); + Super::OnComponentDestroyed( bDestroyingHierarchy ); +} + +void +UHoudiniMeshSplitInstancerComponent::Serialize( FArchive & Ar ) +{ + Super::Serialize( Ar ); + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + Ar << InstancedMesh; + Ar << OverrideMaterial; + Ar << Instances; +} + +void +UHoudiniMeshSplitInstancerComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniMeshSplitInstancerComponent * ThisMSIC = Cast< UHoudiniMeshSplitInstancerComponent >(InThis); + if ( ThisMSIC && !ThisMSIC->IsPendingKill() ) + { + Collector.AddReferencedObject(ThisMSIC->InstancedMesh, ThisMSIC); + Collector.AddReferencedObject(ThisMSIC->OverrideMaterial, ThisMSIC); + Collector.AddReferencedObjects(ThisMSIC->Instances, ThisMSIC); + } +} + +void +UHoudiniMeshSplitInstancerComponent::SetInstances( + const TArray& InstanceTransforms, + const TArray & InstancedColors) +{ +#if WITH_EDITOR + if ( Instances.Num() || InstanceTransforms.Num() ) + { + if (!GetOwner() || GetOwner()->IsPendingKill()) + return; + + const FScopedTransaction Transaction( LOCTEXT( "UpdateInstances", "Update Instances" ) ); + GetOwner()->Modify(); + + // Destroy previous instances while keeping some of the one that we'll be able to reuse + ClearInstances(InstanceTransforms.Num()); + + if( !InstancedMesh || InstancedMesh->IsPendingKill() ) + { + HOUDINI_LOG_ERROR(TEXT("%s: Null InstancedMesh for split instanced mesh override"), *GetOwner()->GetName()); + return; + } + + TArray InstanceColorOverride; + InstanceColorOverride.SetNumUninitialized(InstancedColors.Num()); + for( int32 ix = 0; ix < InstancedColors.Num(); ++ix ) + { + InstanceColorOverride[ix] = InstancedColors[ix].GetClamped().ToFColor(false); + } + + // Only create new SMC for newly added instances + for (int32 iAdd = Instances.Num(); iAdd < InstanceTransforms.Num(); ++iAdd) + { + const FTransform& InstanceTransform = InstanceTransforms[iAdd]; + + UStaticMeshComponent* SMC = NewObject< UStaticMeshComponent >( + GetOwner(), UStaticMeshComponent::StaticClass(), + NAME_None, RF_Transactional); + + SMC->SetRelativeTransform(InstanceTransform); + + Instances.Add(SMC); + } + + ensure(InstanceTransforms.Num() == Instances.Num()); + if (InstanceTransforms.Num() == Instances.Num()) + { + for (int32 iIns = 0; iIns < Instances.Num(); ++iIns) + { + UStaticMeshComponent* SMC = Instances[iIns]; + const FTransform& InstanceTransform = InstanceTransforms[iIns]; + + if (!SMC || SMC->IsPendingKill()) + continue; + + SMC->SetRelativeTransform(InstanceTransform); + + // Attach created static mesh component to this thing + SMC->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + + SMC->SetStaticMesh(InstancedMesh); + SMC->SetVisibility(IsVisible()); + SMC->SetMobility(Mobility); + if (OverrideMaterial && !OverrideMaterial->IsPendingKill()) + { + int32 MeshMaterialCount = InstancedMesh->StaticMaterials.Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + SMC->SetMaterial(Idx, OverrideMaterial); + } + + // If we have override colors, apply them + int32 InstIndex = Instances.Num(); + if (InstanceColorOverride.IsValidIndex(InstIndex)) + { + MeshPaintHelpers::FillStaticMeshVertexColors(SMC, -1, InstanceColorOverride[InstIndex], FColor::White); + //FIXME: How to get rid of the warning about fixup vertex colors on load? + //SMC->FixupOverrideColorsIfNecessary(); + } + + SMC->RegisterComponent(); + + // Adding to the array has been done above + // Instances.Add(SMC); + + // Properties not being propagated to newly created UStaticMeshComponents + if (UHoudiniAssetComponent * pHoudiniAsset = Cast(GetAttachParent())) + { + pHoudiniAsset->CopyComponentPropertiesTo(SMC); + } + } + } + } +#endif +} + +void +UHoudiniMeshSplitInstancerComponent::ClearInstances(int32 NumToKeep) +{ + if (NumToKeep <= 0) + { + for (auto&& Instance : Instances) + { + if (Instance) + { + Instance->ConditionalBeginDestroy(); + } + } + Instances.Empty(); + } + else if (NumToKeep > 0 && NumToKeep < Instances.Num()) + { + for (int32 i = NumToKeep; i < Instances.Num(); ++i) + { + UStaticMeshComponent * Instance = Instances[i]; + if (Instance) + { + Instance->ConditionalBeginDestroy(); + } + } + Instances.SetNum(NumToKeep); + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h new file mode 100644 index 00000000..496f46f7 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h @@ -0,0 +1,67 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "Components/SceneComponent.h" +#include "HoudiniMeshSplitInstancerComponent.generated.h" + +/** +* UHoudiniMeshSplitInstancerComponent is used to manage a single static mesh being +* 'instanced' multiple times by multiple UStaticMeshComponents. This is as opposed to the +* UInstancedStaticMeshComponent wherein a signle mesh is instanced multiple times by one component. +*/ +UCLASS( config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniMeshSplitInstancerComponent : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + virtual void OnComponentDestroyed( bool bDestroyingHierarchy ) override; + virtual void Serialize( FArchive & Ar ) override; + + static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); + + void SetStaticMesh(class UStaticMesh* StaticMesh) { InstancedMesh = StaticMesh; } + class UStaticMesh* GetStaticMesh() const { return InstancedMesh; } + + void SetOverrideMaterial(class UMaterialInterface* MI) { OverrideMaterial = MI; } + + // Set the instances. Transforms are given in local space of this component. + void SetInstances( const TArray& InstanceTransforms, const TArray & InstancedColors ); + + // Destroy existing instances, keeping agiven number of them to be reused + void ClearInstances(int32 NumToKeep); + + const TArray< class UStaticMeshComponent* >& GetInstances() const { return Instances; } + +private: + UPROPERTY( SkipSerialization, VisibleInstanceOnly, Category = Instances ) + TArray< class UStaticMeshComponent* > Instances; + + UPROPERTY( SkipSerialization, VisibleInstanceOnly, Category=Instances) + class UMaterialInterface* OverrideMaterial; + + UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances ) + class UStaticMesh* InstancedMesh; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniParamUtils.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniParamUtils.cpp new file mode 100644 index 00000000..3785d3ee --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniParamUtils.cpp @@ -0,0 +1,443 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniParamUtils.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniAssetInput.h" +#include "HoudiniAssetInstanceInput.h" +#include "HoudiniAssetParameter.h" +#include "HoudiniAssetParameterButton.h" +#include "HoudiniAssetParameterChoice.h" +#include "HoudiniAssetParameterColor.h" +#include "HoudiniAssetParameterFile.h" +#include "HoudiniAssetParameterFloat.h" +#include "HoudiniAssetParameterFolder.h" +#include "HoudiniAssetParameterFolderList.h" +#include "HoudiniAssetParameterInt.h" +#include "HoudiniAssetParameterLabel.h" +#include "HoudiniAssetParameterMultiparm.h" +#include "HoudiniAssetParameterRamp.h" +#include "HoudiniAssetParameterSeparator.h" +#include "HoudiniAssetParameterString.h" +#include "HoudiniAssetParameterToggle.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineString.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniRuntimeSettings.h" + + +bool +FHoudiniParamUtils::Build( HAPI_NodeId AssetId, class UObject* PrimaryObject, + TMap< HAPI_ParmId, class UHoudiniAssetParameter * >& CurrentParameters, + TMap< HAPI_ParmId, class UHoudiniAssetParameter * >& NewParameters ) +{ + if( !FHoudiniEngineUtils::IsValidNodeId( AssetId ) ) + { + // There's no Houdini asset, we can return. + return false; + } + + bool bTreatRampParametersAsMultiparms = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if( HoudiniRuntimeSettings ) + bTreatRampParametersAsMultiparms = HoudiniRuntimeSettings->bTreatRampParametersAsMultiparms; + + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ), false ); + + HAPI_NodeInfo NodeInfo; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo ), false ); + + if( NodeInfo.parmCount > 0 ) + { + // Retrieve parameters. + TArray< HAPI_ParmInfo > ParmInfos; + ParmInfos.SetNumUninitialized( NodeInfo.parmCount ); + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[ 0 ], 0, + NodeInfo.parmCount ), false ); + + // Create name lookup cache + TMap CurrentParametersByName; + CurrentParametersByName.Reserve( CurrentParameters.Num() ); + for( auto& ParmPair : CurrentParameters ) + { + if ( ParmPair.Value && !ParmPair.Value->IsPendingKill() ) + CurrentParametersByName.Add( ParmPair.Value->GetParameterName(), ParmPair.Value ); + } + + // Create properties for parameters. + for( int32 ParamIdx = 0; ParamIdx < NodeInfo.parmCount; ++ParamIdx ) + { + // Retrieve param info at this index. + const HAPI_ParmInfo & ParmInfo = ParmInfos[ ParamIdx ]; + + // If the parameter is corrupt, skip it + if( ParmInfo.id < 0 || ParmInfo.childIndex < 0 ) + { + HOUDINI_LOG_WARNING( TEXT( "Corrupt parameter %d detected, skipping. Note: Plug-in does not support nested Multiparm parameters" ), ParamIdx ); + continue; + } + + // If parameter is invisible, skip it. + if( ParmInfo.invisible ) + continue; + + // Check if any parent folder of this parameter is invisible + bool SkipParm = false; + HAPI_ParmId ParentId = ParmInfo.parentId; + while( ParentId > 0 && !SkipParm ) + { + if( const HAPI_ParmInfo* ParentInfoPtr = ParmInfos.FindByPredicate( [=]( const HAPI_ParmInfo& Info ) { + return Info.id == ParentId; + } ) ) + { + if( ParentInfoPtr->invisible && ParentInfoPtr->type == HAPI_PARMTYPE_FOLDER ) + SkipParm = true; + ParentId = ParentInfoPtr->parentId; + } + else + { + HOUDINI_LOG_ERROR( TEXT( "Could not find parent of parameter %d" ), ParmInfo.id ); + SkipParm = true; + } + } + + if( SkipParm ) + continue; + + UHoudiniAssetParameter * HoudiniAssetParameter = nullptr; + + // See if this parameter has already been created. + // We can't use HAPI_ParmId because that is not unique to parameter instances, so instead + // we find the existing parameter by name + FString NewParmName; + FHoudiniEngineString( ParmInfo.nameSH ).ToFString( NewParmName ); + UHoudiniAssetParameter ** FoundHoudiniAssetParameter = CurrentParametersByName.Find( NewParmName ); + + // If parameter exists, we can reuse it. + if( FoundHoudiniAssetParameter && *FoundHoudiniAssetParameter && !(*FoundHoudiniAssetParameter)->IsPendingKill() ) + { + // sanity check that type and tuple size hasn't changed + if( (*FoundHoudiniAssetParameter)->GetTupleSize() == ParmInfo.size ) + { + UClass* FoundClass = (*FoundHoudiniAssetParameter)->GetClass(); + bool FailedTypeCheck = true; + switch( ParmInfo.type ) + { + case HAPI_PARMTYPE_STRING: + if( !ParmInfo.choiceCount ) + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniAssetParameterString >(); + else + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniAssetParameterChoice >(); + break; + case HAPI_PARMTYPE_INT: + if( !ParmInfo.choiceCount ) + FailedTypeCheck &= !FoundClass->IsChildOf(); + else + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + case HAPI_PARMTYPE_FLOAT: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + case HAPI_PARMTYPE_TOGGLE: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + case HAPI_PARMTYPE_COLOR: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + case HAPI_PARMTYPE_LABEL: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + case HAPI_PARMTYPE_BUTTON: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + case HAPI_PARMTYPE_SEPARATOR: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + case HAPI_PARMTYPE_FOLDERLIST: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + case HAPI_PARMTYPE_FOLDER: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + case HAPI_PARMTYPE_MULTIPARMLIST: + if( !bTreatRampParametersAsMultiparms && ( HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || + HAPI_RAMPTYPE_COLOR == ParmInfo.rampType ) ) + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + else + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + case HAPI_PARMTYPE_PATH_FILE: + case HAPI_PARMTYPE_PATH_FILE_DIR: + case HAPI_PARMTYPE_PATH_FILE_GEO: + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + case HAPI_PARMTYPE_NODE: + if( ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || + ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || + ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ ) + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + else + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + break; + } + + if( !FailedTypeCheck ) + { + HoudiniAssetParameter = *FoundHoudiniAssetParameter; + + // Transfer param object from current map to new map + CurrentParameters.Remove( HoudiniAssetParameter->GetParmId() ); + CurrentParametersByName.Remove( NewParmName ); + + // Reinitialize parameter and add it to map. + HoudiniAssetParameter->CreateParameter( PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + NewParameters.Add( ParmInfo.id, HoudiniAssetParameter ); + continue; + } + } + } + + switch( ParmInfo.type ) + { + case HAPI_PARMTYPE_STRING: + { + if( !ParmInfo.choiceCount ) + HoudiniAssetParameter = UHoudiniAssetParameterString::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + else + HoudiniAssetParameter = UHoudiniAssetParameterChoice::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + + break; + } + + case HAPI_PARMTYPE_INT: + { + if( !ParmInfo.choiceCount ) + HoudiniAssetParameter = UHoudiniAssetParameterInt::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + else + HoudiniAssetParameter = UHoudiniAssetParameterChoice::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + + break; + } + + case HAPI_PARMTYPE_FLOAT: + { + HoudiniAssetParameter = UHoudiniAssetParameterFloat::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + break; + } + + case HAPI_PARMTYPE_TOGGLE: + { + HoudiniAssetParameter = UHoudiniAssetParameterToggle::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + break; + } + + case HAPI_PARMTYPE_COLOR: + { + HoudiniAssetParameter = UHoudiniAssetParameterColor::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + break; + } + + case HAPI_PARMTYPE_LABEL: + { + HoudiniAssetParameter = UHoudiniAssetParameterLabel::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + break; + } + + case HAPI_PARMTYPE_BUTTON: + { + HoudiniAssetParameter = UHoudiniAssetParameterButton::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + break; + } + + case HAPI_PARMTYPE_SEPARATOR: + { + HoudiniAssetParameter = UHoudiniAssetParameterSeparator::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + break; + } + + case HAPI_PARMTYPE_FOLDERLIST: + { + HoudiniAssetParameter = UHoudiniAssetParameterFolderList::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + break; + } + + case HAPI_PARMTYPE_FOLDER: + { + HoudiniAssetParameter = UHoudiniAssetParameterFolder::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + break; + } + + case HAPI_PARMTYPE_MULTIPARMLIST: + { + if( !bTreatRampParametersAsMultiparms && ( HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || + HAPI_RAMPTYPE_COLOR == ParmInfo.rampType ) ) + { + HoudiniAssetParameter = UHoudiniAssetParameterRamp::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + } + else + { + HoudiniAssetParameter = UHoudiniAssetParameterMultiparm::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + } + + break; + } + + case HAPI_PARMTYPE_PATH_FILE: + case HAPI_PARMTYPE_PATH_FILE_DIR: + case HAPI_PARMTYPE_PATH_FILE_GEO: + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + { + HoudiniAssetParameter = UHoudiniAssetParameterFile::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + break; + } + + case HAPI_PARMTYPE_NODE: + { + if( ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || + ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || + ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ ) + { + HoudiniAssetParameter = UHoudiniAssetInput::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + } + else + { + HoudiniAssetParameter = UHoudiniAssetParameterString::Create( + PrimaryObject, nullptr, AssetInfo.nodeId, ParmInfo ); + } + break; + } + + default: + { + // Just ignore unsupported types for now. + HOUDINI_LOG_WARNING( TEXT( "Parameter Type (%d) is unsupported" ), static_cast( ParmInfo.type ) ); + continue; + } + } + + if ( HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill() ) + { + // Add this parameter to the map. + NewParameters.Add( ParmInfo.id, HoudiniAssetParameter ); + } + } + + // We a pass over the new params to patch parent links. + for( int32 ParamIdx = 0; ParamIdx < NodeInfo.parmCount; ++ParamIdx ) + { + // Retrieve param info at this index. + const HAPI_ParmInfo & ParmInfo = ParmInfos[ ParamIdx ]; + + // Locate corresponding parameter. + UHoudiniAssetParameter * const * FoundHoudiniAssetParameter = NewParameters.Find( ParmInfo.id ); + if (!FoundHoudiniAssetParameter) + continue; + + UHoudiniAssetParameter * HoudiniAssetParameter = *FoundHoudiniAssetParameter; + if ( !HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill() ) + continue; + + // Get parent parm id. + UHoudiniAssetParameter * HoudiniAssetParentParameter = nullptr; + HAPI_ParmId ParentParmId = HoudiniAssetParameter->GetParmParentId(); + if( ParentParmId != -1 ) + { + // Locate corresponding parent parameter. + UHoudiniAssetParameter * const * FoundHoudiniAssetParentParameter = NewParameters.Find( ParentParmId ); + if( FoundHoudiniAssetParentParameter ) + HoudiniAssetParentParameter = *FoundHoudiniAssetParentParameter; + } + + // Set parent for this parameter. + HoudiniAssetParameter->SetParentParameter( HoudiniAssetParentParameter ); + + if( ParmInfo.type == HAPI_PARMTYPE_FOLDERLIST ) + { + // For folder lists we need to add children manually. + HoudiniAssetParameter->ResetChildParameters(); + + for( int32 ChildIdx = 0; ChildIdx < ParmInfo.size; ++ChildIdx ) + { + // Children folder parm infos come after folder list parm info. + const HAPI_ParmInfo & ChildParmInfo = ParmInfos[ ParamIdx + ChildIdx + 1 ]; + + UHoudiniAssetParameter * const * FoundHoudiniAssetParameterChild = + NewParameters.Find( ChildParmInfo.id ); + + if( FoundHoudiniAssetParameterChild ) + { + UHoudiniAssetParameter * HoudiniAssetParameterChild = *FoundHoudiniAssetParameterChild; + if ( HoudiniAssetParameterChild && !HoudiniAssetParameterChild->IsPendingKill() ) + HoudiniAssetParameterChild->SetParentParameter( HoudiniAssetParameter ); + } + } + } + } + + // Another pass to notify parameters that all children parameters have been assigned + for( auto& NewParamPair : NewParameters ) + { + if (NewParamPair.Value && !NewParamPair.Value->IsPendingKill() ) + continue; + + if( NewParamPair.Value->HasChildParameters() ) + NewParamPair.Value->NotifyChildParametersCreated(); + } + } + return true; +} + + diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniParamUtils.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniParamUtils.h new file mode 100644 index 00000000..817b27d7 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniParamUtils.h @@ -0,0 +1,44 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HAPI_Common.h" +#include "Containers/Map.h" + +struct HOUDINIENGINERUNTIME_API FHoudiniParamUtils +{ + + /** Update parameters from the asset, re-uses parameters passed into CurrentParameters. + @AssetId: Id of the digital asset + @PrimaryObject: Object to use for transactions and as Outer for new top-level parameters + @CurrentParameters: pre: current & post: invalid parameters + @NewParameters: new params added to this + + On Return: CurrentParameters are the old parameters that are no longer valid, + NewParameters are new and re-used parameters. + */ + static bool Build( HAPI_NodeId AssetId, class UObject* PrimaryObject, + TMap< HAPI_ParmId, class UHoudiniAssetParameter * >& CurrentParameters, + TMap< HAPI_ParmId, class UHoudiniAssetParameter * >& NewParameters ); +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp new file mode 100644 index 00000000..497a6342 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp @@ -0,0 +1,42 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Chris Grebeldinger +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniPluginSerializationVersion.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "Misc/Guid.h" + +const FGuid FHoudiniCustomSerializationVersion::GUID( 0x1AB9CECC, 0x6913, 0x4875, 0x203d51fb ); + +// Register the custom version with core +FCustomVersionRegistration GRegisterHoudiniCustomVersion( FHoudiniCustomSerializationVersion::GUID, VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION, TEXT( "HoudiniUE4PluginVer" ) ); diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h new file mode 100644 index 00000000..d4a01564 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h @@ -0,0 +1,94 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Chris Grebeldinger +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#pragma once + +struct FGuid; + +// Deprecated per-class versions used to load old files +// +// Serialization of parameter name map. +#define VER_HOUDINI_ENGINE_COMPONENT_PARAMETER_NAME_MAP 2 +// Serialization of instancer material, if it is available. +#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME 1 +// Serialization of attribute instancer material, if it is available. +#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME 2 +// Landscape serialization in asset inputs. +#define VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT 1 +// Asset instance member. +#define VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER 2 +// World Outliner inputs. +#define VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT 3 + +enum EHoudiniPluginSerializationVersion +{ + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE = 5, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE = 6, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM = 7, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT = 8, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH = 9, + VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM = 10, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT = 11, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_CUSTOM_LINKER = 12, // added custom linker version to archives + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ACTOR_INSTANCING = 13, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_LANDSCAPES = 14, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT = 15, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BAKENAME_OVERRIDE = 16, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES = 17, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS = 18, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY = 19, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS = 20, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP = 21, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS = 22, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP = 23, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT = 24, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY = 25, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX = 26, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM = 27, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME = 28, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX = 29, // Version 29 is a fix for a serialization issue with UE4.19 / H17.0/16.5, 29 is actually version 26 minus the version 24 changes... + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_POST_419_SERIALIZATION_FIX = 30, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF = 31, + + // ------------------------------------------------------ + // - this needs to be the last line (see note below) + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE, + VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION = VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE - 1 +}; + +struct FHoudiniCustomSerializationVersion +{ + // The GUID for this custom version number + const static FGuid GUID; + +private: + FHoudiniCustomSerializationVersion() {} +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp new file mode 100644 index 00000000..8e426bad --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp @@ -0,0 +1,504 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include "HoudiniRuntimeSettings.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineUtils.h" + +#include "Internationalization/Internationalization.h" +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & ObjectInitializer ) +: Super( ObjectInitializer ) +{ + /** Session options. **/ + SessionType = HRSST_NamedPipe; + ServerHost = HAPI_UNREAL_SESSION_SERVER_HOST; + ServerPort = HAPI_UNREAL_SESSION_SERVER_PORT; + ServerPipeName = HAPI_UNREAL_SESSION_SERVER_PIPENAME; + bStartAutomaticServer = HAPI_UNREAL_SESSION_SERVER_AUTOSTART; + AutomaticServerTimeout = HAPI_UNREAL_SESSION_SERVER_TIMEOUT; + +#if PLATFORM_LINUX + // Since 4.17, Linux has library conflict, so we need to create an out-of-process session by default + // We default to a named pipe, but TCP Sockets should work fine too. + SessionType = HRSST_Socket; + bStartAutomaticServer = true; +#endif + + /** Instantiation options. **/ + bShowMultiAssetDialog = true; + + /** Cooking options. **/ + bPauseCookingOnStart = false; + bEnableCooking = true; + bUploadTransformsToHoudiniEngine = true; + bTransformChangeTriggersCooks = false; + bDisplaySlateCookingNotifications = true; + bCookCurvesOnMouseRelease = false; + + TemporaryCookFolder = LOCTEXT("Temp", "/Game/HoudiniEngine/Temp"); + + /** Parameter options. **/ + bTreatRampParametersAsMultiparms = false; + + /** Collision generation. **/ + CollisionGroupNamePrefix = TEXT( HAPI_UNREAL_GROUP_GEOMETRY_COLLISION ); + RenderedCollisionGroupNamePrefix = TEXT(HAPI_UNREAL_GROUP_GEOMETRY_RENDERED_COLLISION ); + UCXCollisionGroupNamePrefix = TEXT( HAPI_UNREAL_GROUP_GEOMETRY_COLLISION_UCX ); + UCXRenderedCollisionGroupNamePrefix = TEXT( HAPI_UNREAL_GROUP_GEOMETRY_RENDERED_COLLISION_UCX ); + SimpleCollisionGroupNamePrefix = TEXT( HAPI_UNREAL_GROUP_GEOMETRY_SIMPLE_COLLISION ); + SimpleRenderedCollisionGroupNamePrefix = TEXT( HAPI_UNREAL_GROUP_GEOMETRY_SIMPLE_RENDERED_COLLISION ); + + /** Geometry marshalling. **/ + MarshallingAttributeMaterial = TEXT( HAPI_UNREAL_ATTRIB_MATERIAL ); + MarshallingAttributeMaterialHole = TEXT( HAPI_UNREAL_ATTRIB_MATERIAL_HOLE ); + MarshallingAttributeInstanceOverride = TEXT( HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE ); + MarshallingAttributeFaceSmoothingMask = TEXT( HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK ); + MarshallingAttributeLightmapResolution = TEXT( HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION ); + MarshallingAttributeGeneratedMeshName = TEXT( HAPI_UNREAL_ATTRIB_GENERATED_MESH_NAME ); + MarshallingAttributeInputMeshName = TEXT( HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME ); + MarshallingAttributeInputSourceFile = TEXT( HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE ); + MarshallingSplineResolution = HAPI_UNREAL_PARAM_SPLINE_RESOLUTION_DEFAULT; + MarshallingLandscapesUseDefaultUnrealScaling = false; + MarshallingLandscapesUseFullResolution = true; + MarshallingLandscapesForceMinMaxValues = false; + MarshallingLandscapesForcedMinValue = -2000.0f; + MarshallingLandscapesForcedMaxValue = 4553.0f; + + /** Geometry scaling. **/ + GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION; + TransformScaleFactor = HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + ImportAxis = HRSAI_Unreal; + + /** Generated StaticMesh settings. **/ + bDoubleSidedGeometry = false; + PhysMaterial = nullptr; + DefaultBodyInstance.SetCollisionProfileName("BlockAll"); + CollisionTraceFlag = CTF_UseDefault; + LightMapResolution = 32; + LpvBiasMultiplier = 1.0f; + LightMapCoordinateIndex = 1; + bUseMaximumStreamingTexelRatio = false; + StreamingDistanceMultiplier = 1.0f; + GeneratedDistanceFieldResolutionScale = 0.0f; + + /** Static Mesh build settings. **/ + bUseFullPrecisionUVs = false; + SrcLightmapIndex = 0; + DstLightmapIndex = 1; + MinLightmapResolution = 64; + bRemoveDegenerates = true; + GenerateLightmapUVsFlag = HRSRF_OnlyIfMissing; + RecomputeNormalsFlag = HRSRF_OnlyIfMissing; + RecomputeTangentsFlag = HRSRF_OnlyIfMissing; + bUseMikkTSpace = true; + bBuildAdjacencyBuffer = false; + + /** Custom Houdini location. **/ + bUseCustomHoudiniLocation = false; + CustomHoudiniLocation.Path = TEXT( "" ); + + // Placement Mode Tools + bHidePlacementModeHoudiniTools = false; + + /** Arguments for HAPI_Initialize */ + CookingThreadStackSize = -1; +} + +UHoudiniRuntimeSettings::~UHoudiniRuntimeSettings() +{} + +FProperty * +UHoudiniRuntimeSettings::LocateProperty( const FString & PropertyName ) const +{ + for ( TFieldIterator< FProperty > PropIt( GetClass() ); PropIt; ++PropIt ) + { + FProperty * Property = *PropIt; + + if ( Property->GetNameCPP() == PropertyName ) + return Property; + } + + return nullptr; +} + +void +UHoudiniRuntimeSettings::SetPropertyReadOnly( const FString & PropertyName, bool bReadOnly ) +{ + FProperty * Property = LocateProperty( PropertyName ); + if ( Property ) + { + if ( bReadOnly ) + Property->SetPropertyFlags( CPF_EditConst ); + else + Property->ClearPropertyFlags( CPF_EditConst ); + } +} + +void +UHoudiniRuntimeSettings::PostInitProperties() +{ + Super::PostInitProperties(); + + // Set Collision generation options as read only + { + if ( FProperty* Property = LocateProperty( TEXT( "CollisionGroupNamePrefix" ) ) ) + Property->SetPropertyFlags( CPF_EditConst ); + + if ( FProperty* Property = LocateProperty( TEXT( "RenderedCollisionGroupNamePrefix" ) ) ) + Property->SetPropertyFlags( CPF_EditConst ); + + if ( FProperty* Property = LocateProperty(TEXT("UCXCollisionGroupNamePrefix" ) ) ) + Property->SetPropertyFlags(CPF_EditConst); + + if ( FProperty* Property = LocateProperty(TEXT("UCXRenderedCollisionGroupNamePrefix" ) ) ) + Property->SetPropertyFlags(CPF_EditConst); + + if ( FProperty* Property = LocateProperty(TEXT("SimpleCollisionGroupNamePrefix" ) ) ) + Property->SetPropertyFlags(CPF_EditConst); + + if ( FProperty* Property = LocateProperty(TEXT("SimpleRenderedCollisionGroupNamePrefix" ) ) ) + Property->SetPropertyFlags(CPF_EditConst); + } + + // Set marshalling attributes options as read only + { + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterial"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterialHole"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInstanceOverride"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeFaceSmoothingMask"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeLightmapResolution"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeGeneratedMeshName"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInputMeshName"))) + Property->SetPropertyFlags(CPF_EditConst); + } + +/* + // Set Cook Folder as read-only + { + if ( FProperty* Property = LocateProperty( TEXT( "TemporaryCookFolder" ) ) ) + Property->SetPropertyFlags( CPF_EditConst ); + } +*/ + + // Set Landscape forced min/max as read only when not overriden + if ( !MarshallingLandscapesForceMinMaxValues ) + { + if ( FProperty* Property = LocateProperty( TEXT( "MarshallingLandscapesForcedMinValue" ) ) ) + Property->SetPropertyFlags( CPF_EditConst ); + if ( FProperty* Property = LocateProperty( TEXT( "MarshallingLandscapesForcedMaxValue" ) ) ) + Property->SetPropertyFlags( CPF_EditConst ); + } + + // Disable UI elements depending on current session type. +#if WITH_EDITOR + + UpdateSessionUi(); + +#endif // WITH_EDITOR + + SetPropertyReadOnly( TEXT( "CustomHoudiniLocation" ), !bUseCustomHoudiniLocation ); +} + +#if WITH_EDITOR + +void +UHoudiniRuntimeSettings::PostEditChangeProperty( struct FPropertyChangedEvent & PropertyChangedEvent ) +{ + Super::PostEditChangeProperty( PropertyChangedEvent ); + + FProperty * Property = PropertyChangedEvent.MemberProperty; + FProperty * LookupProperty = nullptr; + + if ( !Property ) + return; + + if ( Property->GetName() == TEXT( "GeneratedGeometryScaleFactor" ) ) + GeneratedGeometryScaleFactor = FMath::Clamp( GeneratedGeometryScaleFactor, KINDA_SMALL_NUMBER, 10000.0f ); + else if ( Property->GetName() == TEXT( "TransformScaleFactor" ) ) + GeneratedGeometryScaleFactor = FMath::Clamp( GeneratedGeometryScaleFactor, KINDA_SMALL_NUMBER, 10000.0f ); + else if ( Property->GetName() == TEXT( "SessionType" ) ) + UpdateSessionUi(); + else if ( Property->GetName() == TEXT( "bUseCustomHoudiniLocation" ) ) + SetPropertyReadOnly( TEXT( "CustomHoudiniLocation" ), !bUseCustomHoudiniLocation ); + else if ( Property->GetName() == TEXT( "CustomHoudiniLocation" ) ) + { + FString LibHAPIName = FHoudiniEngineUtils::HoudiniGetLibHAPIName(); + FString & CustomHoudiniLocationPath = CustomHoudiniLocation.Path; + FString LibHAPICustomPath = FString::Printf( TEXT( "%s/%s" ), *CustomHoudiniLocationPath, *LibHAPIName ); + + // If path does not point to libHAPI location, we need to let user know. + if ( !FPaths::FileExists( LibHAPICustomPath ) ) + { + FString MessageString = FString::Printf( + TEXT( "%s was not found in %s" ), *LibHAPIName, *CustomHoudiniLocationPath ); + + FPlatformMisc::MessageBoxExt( + EAppMsgType::Ok, *MessageString, + TEXT( "Invalid Custom Location Specified, resetting." ) ); + + CustomHoudiniLocationPath = TEXT( "" ); + } + } + else if (Property->GetName() == TEXT("MarshallingSplineResolution")) + MarshallingSplineResolution = FMath::Clamp(MarshallingSplineResolution, 0.0f, 10000.0f); + + if ( Property->GetName() == TEXT( "MarshallingLandscapesForceMinMaxValues" ) ) + { + // Set Landscape forced min/max as read only when not overriden + if ( !MarshallingLandscapesForceMinMaxValues ) + { + if ( FProperty* MinProperty = LocateProperty( TEXT( "MarshallingLandscapesForcedMinValue") ) ) + MinProperty->SetPropertyFlags( CPF_EditConst ); + if ( FProperty* MaxProperty = LocateProperty( TEXT( "MarshallingLandscapesForcedMaxValue" ) ) ) + MaxProperty->SetPropertyFlags( CPF_EditConst ); + } + else + { + if ( FProperty* MinProperty = LocateProperty( TEXT( "MarshallingLandscapesForcedMinValue") ) ) + MinProperty->ClearPropertyFlags( CPF_EditConst ); + if ( FProperty* MaxProperty = LocateProperty( TEXT( "MarshallingLandscapesForcedMaxValue" ) ) ) + MaxProperty->ClearPropertyFlags( CPF_EditConst ); + } + } + + /* + if ( Property->GetName() == TEXT( "bEnableCooking" ) ) + { + // Cooking is disabled, we need to disable transform change triggers cooks option is as well. + if ( bEnableCooking ) + { + LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + } + else + { + LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + } + } + else if ( Property->GetName() == TEXT( "bUploadTransformsToHoudiniEngine" ) ) + { + // If upload of transformations is disabled, then there's no sense in cooking asset on transformation change. + if ( bUploadTransformsToHoudiniEngine ) + { + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + } + else + { + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + } + } + */ +} + +void +UHoudiniRuntimeSettings::SetMeshBuildSettings( FMeshBuildSettings & MeshBuildSettings, FRawMesh & RawMesh ) const +{ + MeshBuildSettings.bRemoveDegenerates = bRemoveDegenerates; + MeshBuildSettings.bUseMikkTSpace = bUseMikkTSpace; + MeshBuildSettings.bBuildAdjacencyBuffer = bBuildAdjacencyBuffer; + MeshBuildSettings.MinLightmapResolution = MinLightmapResolution; + MeshBuildSettings.bUseFullPrecisionUVs = bUseFullPrecisionUVs; + MeshBuildSettings.SrcLightmapIndex = SrcLightmapIndex; + MeshBuildSettings.DstLightmapIndex = DstLightmapIndex; + + // Recomputing normals. + switch ( RecomputeNormalsFlag ) + { + case HRSRF_Always: + { + MeshBuildSettings.bRecomputeNormals = true; + break; + } + + case HRSRF_OnlyIfMissing: + { + MeshBuildSettings.bRecomputeNormals = ( 0 == RawMesh.WedgeTangentZ.Num() ); + break; + } + + case HRSRF_Nothing: + default: + { + MeshBuildSettings.bRecomputeNormals = false; + break; + } + } + + // Recomputing tangents. + switch ( RecomputeTangentsFlag ) + { + case HRSRF_Always: + { + MeshBuildSettings.bRecomputeTangents = true; + break; + } + + case HRSRF_OnlyIfMissing: + { + MeshBuildSettings.bRecomputeTangents = ( 0 == RawMesh.WedgeTangentX.Num() || 0 == RawMesh.WedgeTangentY.Num() ); + break; + } + + case HRSRF_Nothing: + default: + { + MeshBuildSettings.bRecomputeTangents = false; + break; + } + } + + // Lightmap UV generation. + bool bHasLightmapUVSet = FHoudiniEngineUtils::CountUVSets( RawMesh ) > 1; + + switch ( GenerateLightmapUVsFlag ) + { + case HRSRF_Always: + { + MeshBuildSettings.bGenerateLightmapUVs = true; + break; + } + + case HRSRF_OnlyIfMissing: + { + MeshBuildSettings.bGenerateLightmapUVs = !bHasLightmapUVSet; + break; + } + + case HRSRF_Nothing: + default: + { + MeshBuildSettings.bGenerateLightmapUVs = false; + break; + } + } +} + +void +UHoudiniRuntimeSettings::UpdateSessionUi() +{ + SetPropertyReadOnly( TEXT( "ServerHost" ), true ); + SetPropertyReadOnly( TEXT( "ServerPort" ), true ); + SetPropertyReadOnly( TEXT( "ServerPipeName" ), true ); + SetPropertyReadOnly( TEXT( "bStartAutomaticServer" ), true ); + SetPropertyReadOnly( TEXT( "AutomaticServerTimeout" ), true ); + + bool bServerType = false; + + switch ( SessionType ) + { + case HRSST_Socket: + { + SetPropertyReadOnly( TEXT( "ServerHost" ), false); + SetPropertyReadOnly( TEXT( "ServerPort" ), false); + bServerType = true; + break; + } + + case HRSST_NamedPipe: + { + SetPropertyReadOnly( TEXT( "ServerPipeName" ), false); + bServerType = true; + break; + } + + default: + break; + } + + if ( bServerType ) + { + SetPropertyReadOnly( TEXT( "bStartAutomaticServer" ), false ); + SetPropertyReadOnly( TEXT( "AutomaticServerTimeout" ), false ); + } +} + +#endif // WITH_EDITOR + +bool +UHoudiniRuntimeSettings::GetSettingsValue( const FString & PropertyName, std::string & PropertyValue ) +{ + FString PropertyString = TEXT( "" ); + if ( UHoudiniRuntimeSettings::GetSettingsValue( PropertyName, PropertyString ) ) + { + FHoudiniEngineUtils::ConvertUnrealString( PropertyString, PropertyValue ); + return true; + } + + return false; +} + +bool +UHoudiniRuntimeSettings::GetSettingsValue( const FString & PropertyName, FString & PropertyValue ) +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if ( HoudiniRuntimeSettings ) + { + FStrProperty * Property = CastField< FStrProperty >( HoudiniRuntimeSettings->LocateProperty( PropertyName ) ); + if ( Property ) + { + const void * ValueRaw = Property->ContainerPtrToValuePtr< void >( HoudiniRuntimeSettings ); + FString RetrievedPropertyValue = Property->GetPropertyValue( ValueRaw ); + + if ( !RetrievedPropertyValue.IsEmpty() ) + { + PropertyValue = RetrievedPropertyValue; + return true; + } + } + } + + return false; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h new file mode 100644 index 00000000..f8ca644b --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h @@ -0,0 +1,527 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once +#include +#include "Engine/EngineTypes.h" +#include "UObject/SoftObjectPtr.h" +#include "PhysicsEngine/BodySetup.h" +#include "HoudiniRuntimeSettings.generated.h" + +struct FRawMesh; +class UAssetUserData; +class UPhysicalMaterial; +struct FMeshBuildSettings; +struct FPropertyChangedEvent; +class UFoliageType_InstancedStaticMesh; + +UENUM() +enum EHoudiniRuntimeSettingsSessionType +{ + // In process session. + HRSST_InProcess UMETA( Hidden ), + + // TCP socket connection to Houdini Engine server. + HRSST_Socket UMETA( DisplayName = "TCP socket" ), + + // Connection to Houdini Engine server via pipe connection. + HRSST_NamedPipe UMETA( DisplayName = "Named pipe or domain socket" ), + + HRSST_MAX, +}; + +UENUM() +enum EHoudiniRuntimeSettingsRecomputeFlag +{ + // Recompute always. + HRSRF_Always UMETA( DisplayName = "Always" ), + + // Recompute only if missing. + HRSRF_OnlyIfMissing UMETA( DisplayName = "Only if missing" ), + + // Do not recompute. + HRSRF_Nothing UMETA( DisplayName = "Never" ), + + HRSRF_MAX, +}; + +UENUM() +enum EHoudiniRuntimeSettingsAxisImport +{ + // Use Unreal coordinate system. + HRSAI_Unreal UMETA( DisplayName = "Unreal" ), + + // Use Houdini coordinate system. + HRSAI_Houdini UMETA( DisplayName = "Houdini" ), + + HRSAI_MAX, +}; + +UENUM() +enum class EHoudiniToolType : uint8 +{ + // For tools that generates geometry, and do not need input + HTOOLTYPE_GENERATOR UMETA( DisplayName = "Generator" ), + + // For tools that have a single input, the selection will be merged in that single input + HTOOLTYPE_OPERATOR_SINGLE UMETA( DisplayName = "Operator (single)" ), + + // For Tools that have multiple input, a single selected asset will be applied to each input + HTOOLTYPE_OPERATOR_MULTI UMETA( DisplayName = "Operator (multiple)" ), + + // For tools that needs to be applied each time for each single selected + HTOOLTYPE_OPERATOR_BATCH UMETA( DisplayName = "Batch Operator" ) +}; + +UENUM() +enum class EHoudiniToolSelectionType : uint8 +{ + // For tools that can be applied both to Content Browser and World selection + HTOOL_SELECTION_ALL UMETA( DisplayName = "Content Browser AND World" ), + + // For tools that can be applied only to World selection + HTOOL_SELECTION_WORLD_ONLY UMETA( DisplayName = "World selection only" ), + + // For tools that can be applied only to Content Browser selection + HTOOL_SELECTION_CB_ONLY UMETA( DisplayName = "Content browser selection only" ) +}; + +USTRUCT(BlueprintType) +struct FHoudiniToolDirectory +{ + GENERATED_USTRUCT_BODY() + + /** Name of the tool directory */ + UPROPERTY(GlobalConfig, Category = Tool, EditAnywhere) + FString Name; + + /** Path of the tool directory */ + UPROPERTY(GlobalConfig, Category = Tool, EditAnywhere) + FDirectoryPath Path; + + /** Unique generated ID used to store the imported uasset for the tools */ + UPROPERTY(GlobalConfig, Category = Tool, VisibleDefaultsOnly) + FString ContentDirID; + + FORCEINLINE bool operator==(const FHoudiniToolDirectory& Other)const + { + return Name == Other.Name && Path.Path == Other.Path.Path; + } + + FORCEINLINE bool operator!=(const FHoudiniToolDirectory& Other)const + { + return !(*this == Other); + } +}; + +UCLASS( config = Engine, defaultconfig ) +class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject +{ + GENERATED_UCLASS_BODY() + + public: + + /** Destructor. **/ + virtual ~UHoudiniRuntimeSettings(); + + /** UObject methods. **/ + public: + + virtual void PostInitProperties() override; + +#if WITH_EDITOR + + virtual void PostEditChangeProperty( FPropertyChangedEvent & PropertyChangedEvent ) override; + +#endif // WITH_EDITOR + + protected: + + /** Locate property of this class by name. **/ + FProperty * LocateProperty( const FString & PropertyName ) const; + + /** Make specified property read only. **/ + void SetPropertyReadOnly( const FString & PropertyName, bool bReadOnly = true ); + +#if WITH_EDITOR + + /** Update session ui elements. **/ + void UpdateSessionUi(); + +#endif // WITH_EDITOR + + public: + +#if WITH_EDITOR + + /** Fill static mesh build settings structure based on assigned settings. **/ + void SetMeshBuildSettings( FMeshBuildSettings & MeshBuildSettings, FRawMesh & RawMesh ) const; + +#endif // WITH_EDITOR + + public: + + /** Retrieve a string settings value. **/ + static bool GetSettingsValue( const FString & PropertyName, std::string & PropertyValue ); + static bool GetSettingsValue( const FString & PropertyName, FString & PropertyValue ); + + /** Session options. **/ + public: + + /** Session Type: Change requires editor restart */ + UPROPERTY( GlobalConfig, EditAnywhere, Category = Session ) + TEnumAsByte< enum EHoudiniRuntimeSettingsSessionType > SessionType; + + UPROPERTY( GlobalConfig, EditAnywhere, Category = Session ) + FString ServerHost; + + UPROPERTY( GlobalConfig, EditAnywhere, Category = Session ) + int32 ServerPort; + + UPROPERTY( GlobalConfig, EditAnywhere, Category = Session ) + FString ServerPipeName; + + /** Whether to automatically start a HARS process */ + UPROPERTY( GlobalConfig, EditAnywhere, Category = Session ) + bool bStartAutomaticServer; + + UPROPERTY( GlobalConfig, EditAnywhere, Category = Session ) + float AutomaticServerTimeout; + + /** Instantiation options. **/ + public: + + // Whether to ask user to select an asset when instantiating an HDA with multiple assets inside. If disabled, will always instantiate first asset. + UPROPERTY( GlobalConfig, EditAnywhere, Category = Instantiating ) + bool bShowMultiAssetDialog; + + /** Cooking options. **/ + public: + + // Whether houdini engine cooking is paused or not upon initializing the plugin + UPROPERTY( GlobalConfig, EditAnywhere, Category = Cooking ) + bool bPauseCookingOnStart; + + // Enables cooking on parameter or input change for new Houdini Assets. + UPROPERTY( GlobalConfig, EditAnywhere, Category = Cooking ) + bool bEnableCooking; + + // Enables uploading of transformation changes back to Houdini Engine for new Houdini Assets. + UPROPERTY( GlobalConfig, EditAnywhere, Category = Cooking ) + bool bUploadTransformsToHoudiniEngine; + + // Enables cooking upon transformation changes for new Houdini Assets. + UPROPERTY( GlobalConfig, EditAnywhere, Category = Cooking ) + bool bTransformChangeTriggersCooks; + + // Whether to display instantiation and cooking Slate notifications. + UPROPERTY( GlobalConfig, EditAnywhere, Category = Cooking ) + bool bDisplaySlateCookingNotifications; + + // Curves will only cook on mouse release. + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + bool bCookCurvesOnMouseRelease; + + // Content folder storing all the temporary cook data + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + FText TemporaryCookFolder; + + /** Parameter options. **/ + public: + + // Will force treatment of ramp parameters as multiparms. + UPROPERTY( GlobalConfig, EditAnywhere, Category = Parameters ) + bool bTreatRampParametersAsMultiparms; + + /** Collision generation. **/ + public: + + // Group name prefix used for collision geometry generation. + UPROPERTY( GlobalConfig, EditAnywhere, Category = CollisionGeneration ) + FString CollisionGroupNamePrefix; + + // Group name prefix used for rendered collision geometry generation. + UPROPERTY( GlobalConfig, EditAnywhere, Category = CollisionGeneration ) + FString RenderedCollisionGroupNamePrefix; + + // Group name prefix used for UCX collision geometry generation. + UPROPERTY( GlobalConfig, EditAnywhere, Category = CollisionGeneration) + FString UCXCollisionGroupNamePrefix; + + // Group name prefix used for rendered UBX collision geometry generation. + UPROPERTY( GlobalConfig, EditAnywhere, Category = CollisionGeneration) + FString UCXRenderedCollisionGroupNamePrefix; + + // Group name prefix used for simple collision geometry generation. + // The type can be added after this: _box, _sphere, _capsule, _kdop10X, _kdop10Y, _kdop10Z, _kdop18, _kdop26 ... + UPROPERTY( GlobalConfig, EditAnywhere, Category = CollisionGeneration) + FString SimpleCollisionGroupNamePrefix; + + // Group name prefix used for rendered UBX collision geometry generation. + // The type can be added after this: _box, _sphere, _capsule, _kdop10X, _kdop10Y, _kdop10Z, _kdop18, _kdop26 ... + UPROPERTY( GlobalConfig, EditAnywhere, Category = CollisionGeneration) + FString SimpleRenderedCollisionGroupNamePrefix; + + /** Geometry marshalling. **/ + public: + + // Name of attribute used for marshalling Unreal materials. + UPROPERTY( GlobalConfig, EditAnywhere, Category = GeometryMarshalling ) + FString MarshallingAttributeMaterial; + + // Name of attribute used for marshalling Unreal hole materials. + UPROPERTY( GlobalConfig, EditAnywhere, Category = GeometryMarshalling ) + FString MarshallingAttributeMaterialHole; + + // Name of attribute used for marshalling Unreal instances. + UPROPERTY( GlobalConfig, EditAnywhere, Category = GeometryMarshalling ) + FString MarshallingAttributeInstanceOverride; + + // Name of attribute used for marshalling Unreal face smoothing masks. + UPROPERTY( GlobalConfig, EditAnywhere, Category = GeometryMarshalling ) + FString MarshallingAttributeFaceSmoothingMask; + + // Name of attribute used for marshalling light map resolution. + UPROPERTY( GlobalConfig, EditAnywhere, Category = GeometryMarshalling ) + FString MarshallingAttributeLightmapResolution; + + // Name of attribute used to set generated mesh name. + UPROPERTY( GlobalConfig, EditAnywhere, Category = GeometryMarshalling ) + FString MarshallingAttributeGeneratedMeshName; + + // Name of attribute set to the path of mesh asset inputs when marshalled to Houdini. + UPROPERTY( GlobalConfig, EditAnywhere, Category = GeometryMarshalling ) + FString MarshallingAttributeInputMeshName; + + // Name of attribute set to the asset's source file path for inputs when marshalled to Houdini. + UPROPERTY( GlobalConfig, EditAnywhere, Category = GeometryMarshalling ) + FString MarshallingAttributeInputSourceFile; + + // Default resolution used when marshalling the Unreal Splines to HoudiniEngine (step in cm between CVs) + UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + float MarshallingSplineResolution; + + // If true, generated Landscapes will be marshalled using default unreal scaling. + // Generated landscape will loose a lot of precision on the Z axis but will use the same transforms + // as Unreal's default landscape + UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + bool MarshallingLandscapesUseDefaultUnrealScaling; + + // If true, generated Landscapes will be using full precision for their ZAxis, + // allowing for more precision but preventing them from being sculpted higher/lower than their min/max. + UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + bool MarshallingLandscapesUseFullResolution; + + // If true, the min/max values used to convert heightfields to landscape will be forced values + // This is usefull when importing multiple landscapes from different HDAs + UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + bool MarshallingLandscapesForceMinMaxValues; + // The minimum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled + UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + float MarshallingLandscapesForcedMinValue; + // The maximum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled + UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + float MarshallingLandscapesForcedMaxValue; + + /** Geometry scaling. **/ + public: + + // Scale factor of generated Houdini geometry. + UPROPERTY( GlobalConfig, EditAnywhere, Category = GeometryScalingAndImport ) + float GeneratedGeometryScaleFactor; + + // Scale factor of Houdini transformations. + UPROPERTY( GlobalConfig, EditAnywhere, Category = GeometryScalingAndImport ) + float TransformScaleFactor; + + // Which coordinate system to use. + UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryScalingAndImport ) + TEnumAsByte< enum EHoudiniRuntimeSettingsAxisImport > ImportAxis; + + /** Generated StaticMesh settings. **/ + public: + + // If true, the physics triangle mesh will use double sided faces for new Houdini Assets when doing scene queries. + UPROPERTY( + GlobalConfig, EditAnywhere, Category = GeneratedStaticMeshSettings, + Meta = ( DisplayName = "Double Sided Geometry" ) ) + uint32 bDoubleSidedGeometry : 1; + + // Physical material to use for simple collision of new Houdini Assets. Encodes information about density, friction etc. + UPROPERTY( + EditAnywhere, Category = GeneratedStaticMeshSettings, + Meta = ( DisplayName = "Simple Collision Physical Material" ) ) + UPhysicalMaterial * PhysMaterial; + + //* Default properties of the body instance + UPROPERTY(EditAnywhere, Category = GeneratedStaticMeshSettings, meta = ( FullyExpand = "true" )) + struct FBodyInstance DefaultBodyInstance; + + //* Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate for new Houdini Assets. + UPROPERTY( + GlobalConfig, VisibleDefaultsOnly, Category = GeneratedStaticMeshSettings, + Meta = ( DisplayName = "Collision Complexity" ) ) + TEnumAsByte< enum ECollisionTraceFlag > CollisionTraceFlag; + + // Resolution of lightmap for baked lighting. + UPROPERTY( + GlobalConfig, EditAnywhere, Category = GeneratedStaticMeshSettings, + Meta = ( DisplayName = "Light Map Resolution", FixedIncrement = "4.0" ) ) + int32 LightMapResolution; + + // Bias multiplier for Light Propagation Volume lighting for new Houdini Assets. + UPROPERTY( + GlobalConfig, EditAnywhere, BlueprintReadOnly, Category = GeneratedStaticMeshSettings, + Meta = ( DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0" ) ) + float LpvBiasMultiplier; + + /** Default Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ + UPROPERTY( + GlobalConfig, EditAnywhere, Category = GeneratedStaticMeshSettings, + Meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) + float GeneratedDistanceFieldResolutionScale; + + // Custom walkable slope setting for bodies of new Houdini Assets. + UPROPERTY( + GlobalConfig, EditAnywhere, AdvancedDisplay, Category = GeneratedStaticMeshSettings, + Meta = ( DisplayName = "Walkable Slope Override" ) ) + FWalkableSlopeOverride WalkableSlopeOverride; + + // The UV coordinate index of lightmap + UPROPERTY( + GlobalConfig, EditAnywhere, AdvancedDisplay, Category = GeneratedStaticMeshSettings, + Meta = ( DisplayName = "Light map coordinate index" ) ) + int32 LightMapCoordinateIndex; + + // True if mesh should use a less-conservative method of mip LOD texture factor computation for new Houdini Assets. + UPROPERTY( + GlobalConfig, EditAnywhere, AdvancedDisplay, Category = GeneratedStaticMeshSettings, + Meta = ( DisplayName = "Use Maximum Streaming Texel Ratio" ) ) + uint32 bUseMaximumStreamingTexelRatio:1; + + // Allows artists to adjust the distance where textures using UV 0 are streamed in/out for new Houdini Assets. + UPROPERTY( + GlobalConfig, EditAnywhere, AdvancedDisplay, Category = GeneratedStaticMeshSettings, + Meta = ( DisplayName = "Streaming Distance Multiplier" ) ) + float StreamingDistanceMultiplier; + + // Default settings when using new Houdini Asset mesh for instanced foliage. + UPROPERTY( + EditAnywhere, AdvancedDisplay, Instanced, Category = GeneratedStaticMeshSettings, + Meta = ( DisplayName = "Foliage Default Settings" ) ) + UFoliageType_InstancedStaticMesh * FoliageDefaultSettings; + + // Array of user data stored with the new Houdini Asset. + UPROPERTY( + EditAnywhere, AdvancedDisplay, Instanced, Category = GeneratedStaticMeshSettings, + Meta = ( DisplayName = "Asset User Data" ) ) + TArray< UAssetUserData * > AssetUserData; + + /** Static Mesh build settings. **/ + public: + + // If true, UVs will be stored at full floating point precision. + UPROPERTY( GlobalConfig, EditAnywhere, Category = StaticMeshBuildSettings ) + bool bUseFullPrecisionUVs; + + // Source UV set for generated lightmap. + UPROPERTY( GlobalConfig, EditAnywhere, Category = StaticMeshBuildSettings, Meta = ( DisplayName = "Source Lightmap Index" ) ) + int32 SrcLightmapIndex; + + // Destination UV set for generated lightmap. + UPROPERTY( GlobalConfig, EditAnywhere, Category = StaticMeshBuildSettings, Meta = ( DisplayName = "Destination Lightmap Index" ) ) + int32 DstLightmapIndex; + + // Target lightmap resolution to for generated lightmap. Determines the padding between UV shells in a packed lightmap. + UPROPERTY( GlobalConfig, EditAnywhere, Category = StaticMeshBuildSettings ) + int32 MinLightmapResolution; + + // If true, degenerate triangles will be removed. + UPROPERTY( GlobalConfig, EditAnywhere, Category = StaticMeshBuildSettings ) + bool bRemoveDegenerates; + + // Lightmap UV generation + UPROPERTY( GlobalConfig, EditAnywhere, Category = StaticMeshBuildSettings, Meta = ( DisplayName = "Generate Lightmap UVs" ) ) + TEnumAsByte< enum EHoudiniRuntimeSettingsRecomputeFlag > GenerateLightmapUVsFlag; + + // Normals generation + UPROPERTY( GlobalConfig, EditAnywhere, Category = StaticMeshBuildSettings, Meta = ( DisplayName="Recompute Normals" ) ) + TEnumAsByte< enum EHoudiniRuntimeSettingsRecomputeFlag > RecomputeNormalsFlag; + + // Tangents generation + UPROPERTY( GlobalConfig, EditAnywhere, Category = StaticMeshBuildSettings, Meta = ( DisplayName="Recompute Tangents" ) ) + TEnumAsByte< enum EHoudiniRuntimeSettingsRecomputeFlag > RecomputeTangentsFlag; + + // If true, recomputed tangents and normals will be calculated using MikkT Space. This method does require properly laid out UVs though otherwise you'll get a degenerate tangent warning + UPROPERTY( GlobalConfig, EditAnywhere, Category = StaticMeshBuildSettings, Meta = ( DisplayName="Generate Using MikkT Space" ) ) + bool bUseMikkTSpace; + + // Required for PNT tessellation but can be slow. Recommend disabling for larger meshes. + UPROPERTY( GlobalConfig, EditAnywhere, Category = StaticMeshBuildSettings ) + bool bBuildAdjacencyBuffer; + + /** Custom Houdini location. **/ + public: + + // Whether to use custom Houdini location. + UPROPERTY( + GlobalConfig, EditAnywhere, Category = HoudiniLocation, + Meta = ( DisplayName = "Use custom Houdini location (requires restart)" ) ) + bool bUseCustomHoudiniLocation; + + // Custom Houdini location (where HAPI library is located). + UPROPERTY( + GlobalConfig, EditAnywhere, Category = HoudiniLocation, + Meta = ( DisplayName = "Custom Houdini location" ) ) + FDirectoryPath CustomHoudiniLocation; + + /** Custom Houdini Tools **/ + public: + /** Don't add Houdini Tools to Placement Editor Mode */ + UPROPERTY( GlobalConfig, EditAnywhere, Category = CustomHoudiniTools ) + bool bHidePlacementModeHoudiniTools; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = CustomHoudiniTools) + TArray CustomHoudiniToolsLocation; + + /** Arguments for HAPI_Initialize */ + public: + // Evaluation thread stack size in bytes. -1 for default + UPROPERTY( GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization ) + int32 CookingThreadStackSize; + // List of paths to Houdini-compatible .env files (; separated on Windows, : otherwise) + UPROPERTY( GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization ) + FString HoudiniEnvironmentFiles; + // Path to find other OTL/HDA files + UPROPERTY( GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization ) + FString OtlSearchPath; + // Sets HOUDINI_DSO_PATH + UPROPERTY( GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization ) + FString DsoSearchPath; + // Sets HOUDINI_IMAGE_DSO_PATH + UPROPERTY( GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization ) + FString ImageDsoSearchPath; + // Sets HOUDINI_AUDIO_DSO_PATH + UPROPERTY( GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization ) + FString AudioDsoSearchPath; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp new file mode 100644 index 00000000..30d3fe4f --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp @@ -0,0 +1,433 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#include "HoudiniSplineComponent.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetInput.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngine.h" +#include "HoudiniPluginSerializationVersion.h" + +UHoudiniSplineComponent::UHoudiniSplineComponent( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) + , HoudiniAssetInput( nullptr ) + , CurveType( EHoudiniSplineComponentType::Polygon ) + , CurveMethod( EHoudiniSplineComponentMethod::Breakpoints ) + , bClosedCurve( false ) +{ + // By default we will create two points. + FTransform transf = FTransform::Identity; + CurvePoints.Add( transf ); + transf.SetTranslation( FVector(200.0f, 0.0f, 0.0f) ); + CurvePoints.Add( transf ); + + // Change the default mobility to static to avoid modifying the parent HAC's mobility to Moveable + Mobility = EComponentMobility::Static; +} + +UHoudiniSplineComponent::~UHoudiniSplineComponent() +{} + +void +UHoudiniSplineComponent::Serialize( FArchive & Ar ) +{ + Super::Serialize( Ar ); + + Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID ); + + int32 Version = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << Version; + + Ar << HoudiniGeoPartObject; + + if (Version < VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM) + { + // Before, curve points where stored as Vectors, not Transforms + TArray OldCurvePoints; + Ar << OldCurvePoints; + + CurvePoints.SetNumUninitialized(OldCurvePoints.Num()); + + FTransform trans = FTransform::Identity; + for (int32 n = 0; n < CurvePoints.Num(); n++) + { + trans.SetLocation(OldCurvePoints[n]); + CurvePoints[n] = trans; + } + } + else + { + Ar << CurvePoints; + } + + Ar << CurveDisplayPoints; + + SerializeEnumeration< EHoudiniSplineComponentType::Enum >( Ar, CurveType ); + SerializeEnumeration< EHoudiniSplineComponentMethod::Enum >( Ar, CurveMethod ); + Ar << bClosedCurve; +} + +#if WITH_EDITOR + +void +UHoudiniSplineComponent::PostEditUndo() +{ + Super::PostEditUndo(); + + UpdateHoudiniComponents(); +} + +#endif // WITH_EDITOR + +bool +UHoudiniSplineComponent::Construct( + const FHoudiniGeoPartObject & InHoudiniGeoPartObject, + const TArray< FTransform > & InCurvePoints, + const TArray< FVector > & InCurveDisplayPoints, + EHoudiniSplineComponentType::Enum InCurveType, + EHoudiniSplineComponentMethod::Enum InCurveMethod, + bool bInClosedCurve ) +{ + ResetCurvePoints(); + AddPoints( InCurvePoints ); + + return Construct( + InHoudiniGeoPartObject, + InCurveDisplayPoints, + InCurveType, + InCurveMethod, + bInClosedCurve ); +} + + +bool +UHoudiniSplineComponent::Construct( + const FHoudiniGeoPartObject & InHoudiniGeoPartObject, + const TArray< FVector > & InCurveDisplayPoints, + EHoudiniSplineComponentType::Enum InCurveType, + EHoudiniSplineComponentMethod::Enum InCurveMethod, + bool bInClosedCurve) +{ + HoudiniGeoPartObject = InHoudiniGeoPartObject; + + ResetCurveDisplayPoints(); + AddDisplayPoints(InCurveDisplayPoints); + + CurveType = InCurveType; + CurveMethod = InCurveMethod; + bClosedCurve = bInClosedCurve; + + return true; +} + + +void +UHoudiniSplineComponent::SetHoudiniGeoPartObject( const FHoudiniGeoPartObject& InHoudiniGeoPartObject ) +{ + HoudiniGeoPartObject = InHoudiniGeoPartObject; +} + + +bool +UHoudiniSplineComponent::CopyFrom( UHoudiniSplineComponent* InSplineComponent ) +{ + if (!InSplineComponent || InSplineComponent->IsPendingKill() ) + return false; + + HoudiniGeoPartObject = FHoudiniGeoPartObject(InSplineComponent->HoudiniGeoPartObject); + + ResetCurvePoints(); + AddPoints(InSplineComponent->GetCurvePoints()); + + ResetCurveDisplayPoints(); + AddDisplayPoints(InSplineComponent->CurveDisplayPoints); + + CurveType = InSplineComponent->GetCurveType(); + CurveMethod = InSplineComponent->GetCurveMethod(); + bClosedCurve = InSplineComponent->IsClosedCurve(); + + return true; +} + + +EHoudiniSplineComponentType::Enum +UHoudiniSplineComponent::GetCurveType() const +{ + return CurveType; +} + +EHoudiniSplineComponentMethod::Enum +UHoudiniSplineComponent::GetCurveMethod() const +{ + return CurveMethod; +} + +int32 +UHoudiniSplineComponent::GetCurvePointCount() const +{ + return CurvePoints.Num(); +} + +bool +UHoudiniSplineComponent::IsClosedCurve() const +{ + return bClosedCurve; +} + +void +UHoudiniSplineComponent::ResetCurvePoints() +{ + CurvePoints.Empty(); +} + +void +UHoudiniSplineComponent::ResetCurveDisplayPoints() +{ + CurveDisplayPoints.Empty(); +} + +void +UHoudiniSplineComponent::AddPoint( const FTransform & Point ) +{ + CurvePoints.Add( Point ); +} + +void +UHoudiniSplineComponent::AddPoints( const TArray< FTransform > & Points ) +{ + CurvePoints.Append( Points ); +} + +void +UHoudiniSplineComponent::AddDisplayPoints( const TArray< FVector > & Points ) +{ + CurveDisplayPoints.Append( Points ); +} + +bool +UHoudiniSplineComponent::IsValidCurve() const +{ + if ( CurvePoints.Num() < 2 ) + return false; + + return true; +} + +void +UHoudiniSplineComponent::UpdatePoint( int32 PointIndex, const FTransform & Point ) +{ + check( PointIndex >= 0 && PointIndex < CurvePoints.Num() ); + CurvePoints[ PointIndex ] = Point; +} + +void +UHoudiniSplineComponent::UploadControlPoints() +{ + HAPI_NodeId HostAssetId = -1; + HAPI_NodeId NodeId = -1; + if (HoudiniGeoPartObject.IsValid()) + { + if ( IsInputCurve() ) + { + HostAssetId = HoudiniAssetInput->GetConnectedAssetId(); + NodeId = HoudiniGeoPartObject.HapiGeoGetNodeId(); + } + else + { + // Grab component we are attached to. + UHoudiniAssetComponent * AttachedComponent = Cast< UHoudiniAssetComponent >( GetAttachParent() ); + if ( AttachedComponent && !AttachedComponent->IsPendingKill() ) + HostAssetId = AttachedComponent->GetAssetId(); + + NodeId = HoudiniGeoPartObject.HapiGeoGetNodeId( HostAssetId ); + } + } + + if ( ( NodeId < 0 ) || ( HostAssetId < 0 ) ) + return; + + // Extract positions rotations and scales and upload them to the curve node + TArray Positions; + GetCurvePositions(Positions); + + if ( IsInputCurve() ) + { + // Only input curves support rotation and scale + TArray Rotations; + GetCurveRotations(Rotations); + + TArray Scales; + GetCurveScales(Scales); + + FHoudiniEngineUtils::HapiCreateCurveInputNodeForData( + HostAssetId, + NodeId, + &Positions, + &Rotations, + &Scales, + nullptr); + + // We need to cook the spline node. + FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), NodeId, nullptr); + } + else + { + FString PositionString = TEXT(""); + FHoudiniEngineUtils::CreatePositionsString(Positions, PositionString); + + // Get param id. + HAPI_ParmId ParmId = -1; + if (FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), NodeId, + HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId) != HAPI_RESULT_SUCCESS) + { + return; + } + + // Update the new point positions + std::string ConvertedString = TCHAR_TO_UTF8(*PositionString); + if (FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NodeId, + ConvertedString.c_str(), ParmId, 0) != HAPI_RESULT_SUCCESS) + { + return; + } + } +} + +void +UHoudiniSplineComponent::UpdateHoudiniComponents() +{ + if ( IsInputCurve() ) + { + if ( HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill() ) + HoudiniAssetInput->OnInputCurveChanged(); + } + else + { + // If not an input curve, we need first to upload the new CVs + UploadControlPoints(); + +#if WITH_EDITOR + UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >( GetAttachParent() ); + if ( HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill() ) + HoudiniAssetComponent->NotifyHoudiniSplineChanged( this ); +#endif + } + +#if WITH_EDITOR + if ( GEditor ) + GEditor->RedrawLevelEditingViewports( true ); +#endif +} + +void +UHoudiniSplineComponent::RemovePoint( int32 PointIndex ) +{ + check( PointIndex >= 0 && PointIndex < CurvePoints.Num() ); + CurvePoints.RemoveAt( PointIndex ); +} + +void +UHoudiniSplineComponent::AddPoint( int32 PointIndex, const FTransform & Point ) +{ + check( PointIndex >= 0 && PointIndex < CurvePoints.Num() ); + CurvePoints.Insert( Point, PointIndex ); +} + +bool +UHoudiniSplineComponent::IsInputCurve() const +{ + return ( HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill() ); +} + +void +UHoudiniSplineComponent::SetHoudiniAssetInput( UHoudiniAssetInput * InHoudiniAssetInput ) +{ + if ( !InHoudiniAssetInput || InHoudiniAssetInput->IsPendingKill() ) + HoudiniAssetInput = nullptr; + else + HoudiniAssetInput = InHoudiniAssetInput; +} + + +const TArray< FTransform > & +UHoudiniSplineComponent::GetCurvePoints() const +{ + return CurvePoints; +} + + +void +UHoudiniSplineComponent::GetCurvePositions(TArray& Positions) const +{ + Positions.SetNumUninitialized(CurvePoints.Num()); + for (int32 n = 0; n < CurvePoints.Num(); n++) + { + Positions[n] = CurvePoints[n].GetLocation(); + } +} + + +void +UHoudiniSplineComponent::GetCurveRotations(TArray& Rotations) const +{ + Rotations.SetNumUninitialized(CurvePoints.Num()); + for (int32 n = 0; n < CurvePoints.Num(); n++) + { + Rotations[n] = CurvePoints[n].GetRotation(); + } +} + +void +UHoudiniSplineComponent::GetCurveScales(TArray& Scales) const +{ + Scales.SetNumUninitialized(CurvePoints.Num()); + for (int32 n = 0; n < CurvePoints.Num(); n++) + { + Scales[n] = CurvePoints[n].GetScale3D(); + } +} + +bool +UHoudiniSplineComponent::IsActive() const +{ + if ( HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill() ) + { + if ( !HoudiniAssetInput->IsCurveAssetConnected() ) + return false; + } + + return true; +} \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h new file mode 100644 index 00000000..7d945907 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h @@ -0,0 +1,200 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Produced by: +* Mykola Konyk +* Side Effects Software Inc +* 123 Front Street West, Suite 1401 +* Toronto, Ontario +* Canada M5J 2M2 +* 416-504-9876 +* +*/ + +#pragma once + +#include "HoudiniGeoPartObject.h" +#include "Components/SceneComponent.h" +#include "HoudiniSplineComponent.generated.h" + + +namespace EHoudiniSplineComponentType +{ + enum Enum + { + Polygon, + Nurbs, + Bezier + }; +} + +namespace EHoudiniSplineComponentMethod +{ + enum Enum + { + CVs, + Breakpoints, + Freehand + }; +} + +UCLASS( config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent +{ + public: + friend class UHoudiniAssetComponent; + +#if WITH_EDITOR + + friend class FHoudiniSplineComponentVisualizer; + +#endif // WITH_EDITOR + + GENERATED_UCLASS_BODY() + + virtual ~UHoudiniSplineComponent(); + + /** UObject methods. **/ + public: + + virtual void Serialize(FArchive& Ar) override; + +#if WITH_EDITOR + + virtual void PostEditUndo() override; + +#endif // WITH_EDITOR + + public: + + /** Construct spline from given information. Resets any existing state. **/ + bool Construct( + const FHoudiniGeoPartObject & InHoudiniGeoPartObject, + const TArray< FTransform > & InCurvePoints, + const TArray< FVector > & InCurveDisplayPoints, + EHoudiniSplineComponentType::Enum InCurveType, + EHoudiniSplineComponentMethod::Enum InCurveMethod, + bool bInClosedCurve = false ); + + + /** Construct spline from given information. Resets any existing state. **/ + bool Construct( + const FHoudiniGeoPartObject & InHoudiniGeoPartObject, + const TArray< FVector > & InCurveDisplayPoints, + EHoudiniSplineComponentType::Enum InCurveType, + EHoudiniSplineComponentMethod::Enum InCurveMethod, + bool bInClosedCurve = false); + + void SetHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHoudiniGeoPartObject); + + + /** Copies data from an another curve. Resets any existing state **/ + bool CopyFrom( UHoudiniSplineComponent* InSplineComponent ); + + /** Return the type of this curve. **/ + EHoudiniSplineComponentType::Enum GetCurveType() const; + + /** Return method used by this curve. **/ + EHoudiniSplineComponentMethod::Enum GetCurveMethod() const; + + /** Return true if this curve is closed. **/ + bool IsClosedCurve() const; + + /** Return number of curve points. **/ + int32 GetCurvePointCount() const; + + /** Resets all points of this curve. **/ + void ResetCurvePoints(); + + /** Reset display points of this curve. **/ + void ResetCurveDisplayPoints(); + + /** Add a point to this curve. **/ + void AddPoint( const FTransform & Point ); + + /** Add points to this curve. **/ + void AddPoints( const TArray< FTransform > & Points ); + + /** Add display points to this curve. **/ + void AddDisplayPoints( const TArray< FVector > & Points ); + + /** Return true if this spline is a valid spline. **/ + bool IsValidCurve() const; + + /** Update point at given index with new information. **/ + void UpdatePoint( int32 PointIndex, const FTransform & Point ); + + /** Upload changed control points to HAPI. **/ + void UploadControlPoints(); + + /** Remove point at a given index. **/ + void RemovePoint( int32 PointIndex ); + + /** Add a point to this curve at given point index. **/ + void AddPoint( int32 PointIndex, const FTransform & Point ); + + /** Return true if this is an input curve. **/ + bool IsInputCurve() const; + + /** Returns true if this Spline component is Active **/ + bool IsActive() const; + + /** Assign input parameter to this spline, if it is an input curve. **/ + void SetHoudiniAssetInput( class UHoudiniAssetInput * InHoudiniAssetInput ); + + /** Return curve points. **/ + const TArray< FTransform > & GetCurvePoints() const; + + /** Extract Positions from the Transform Array **/ + void GetCurvePositions(TArray& Positions) const; + + /** Extract Rotations from the Transform Array **/ + void GetCurveRotations(TArray& Roatations) const; + + /** Extract Scales from the Transform Array **/ + void GetCurveScales(TArray& Scales) const; + + /** Updates self and notify parent component **/ + void UpdateHoudiniComponents(); + + protected: + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject HoudiniGeoPartObject; + + /** List of points composing this curve. **/ + TArray< FTransform > CurvePoints; + + /** List of refined points used for drawing. **/ + TArray< FVector > CurveDisplayPoints; + + /** Corresponding asset input parameter if this is an input curve. **/ + class UHoudiniAssetInput * HoudiniAssetInput; + + /** Type of this curve. **/ + EHoudiniSplineComponentType::Enum CurveType; + + /** Method used for this curve. **/ + EHoudiniSplineComponentMethod::Enum CurveMethod; + + /** Whether this spline is closed. **/ + bool bClosedCurve; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/Tests/HoudiniEngineRuntimeTest.cpp b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/Tests/HoudiniEngineRuntimeTest.cpp new file mode 100644 index 00000000..7b91a6f1 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/Tests/HoudiniEngineRuntimeTest.cpp @@ -0,0 +1,763 @@ +#include "HoudiniEngineRuntimeTest.h" + +#include "HoudiniApi.h" +#if WITH_EDITOR +#include "CoreMinimal.h" +#include "Editor.h" +#include "Misc/AutomationTest.h" +#include "FileCacheUtilities.h" +#include "StaticMeshResources.h" +#include "LevelEditorViewport.h" +#include "AssetRegistryModule.h" +#include "PropertyEditorModule.h" +#include "Tests/AutomationCommon.h" +#include "IDetailsView.h" + +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniParamUtils.h" +#include "HoudiniCookHandler.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetParameterInt.h" + + +DEFINE_LOG_CATEGORY_STATIC( LogHoudiniTests, Log, All ); + +static constexpr int32 kTestFlags = EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter; + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( FHoudiniEngineRuntimeMeshMarshalTest, "Houdini.Runtime.MeshMarshalTest", kTestFlags ) +IMPLEMENT_SIMPLE_AUTOMATION_TEST( FHoudiniEngineRuntimeUploadStaticMeshTest, "Houdini.Runtime.UploadStaticMesh", kTestFlags ) +IMPLEMENT_SIMPLE_AUTOMATION_TEST( FHoudiniEngineRuntimeActorTest, "Houdini.Runtime.ActorTest", kTestFlags ) +IMPLEMENT_SIMPLE_AUTOMATION_TEST( FHoudiniEngineRuntimeParamTest, "Houdini.Runtime.ParamTest", kTestFlags ) +IMPLEMENT_SIMPLE_AUTOMATION_TEST( FHoudiniEngineRuntimeBatchTest, "Houdini.Runtime.BatchTest", kTestFlags ) + +static float TestTickDelay = 1.0f; + +struct FTestCookHandler : public FHoudiniCookParams, public IHoudiniCookHandler +{ + /** Transient cache of last baked parts */ + TMap > BakedStaticMeshPackagesForParts_; + /** Transient cache of last baked materials and textures */ + TMap > BakedMaterialPackagesForIds_; + /** Cache of the temp cook content packages created by the asset for its materials/textures **/ + TMap > CookedTemporaryStaticMeshPackages_; + /** Cache of the temp cook content packages created by the asset for its materials/textures **/ + TMap > CookedTemporaryPackages_; + + FTestCookHandler( class UHoudiniAsset* InHoudiniAsset ) + : FHoudiniCookParams( InHoudiniAsset ) + { + BakedStaticMeshPackagesForParts = &BakedStaticMeshPackagesForParts_; + BakedMaterialPackagesForIds = &BakedMaterialPackagesForIds_; + CookedTemporaryStaticMeshPackages = &CookedTemporaryStaticMeshPackages_; + CookedTemporaryPackages = &CookedTemporaryPackages_; + } + + virtual ~FTestCookHandler() + { + } + + virtual FString GetBakingBaseName( const struct FHoudiniGeoPartObject& GeoPartObject ) const override + { + if( GeoPartObject.HasCustomName() ) + { + return GeoPartObject.PartName; + } + + return FString::Printf( TEXT( "test_%d_%d_%d_%d" ), + GeoPartObject.ObjectId, GeoPartObject.GeoId, GeoPartObject.PartId, GeoPartObject.SplitId ); + } + + + virtual void SetStaticMeshGenerationParameters( class UStaticMesh* StaticMesh ) const override + { + + } + + + virtual class UMaterialInterface * GetAssignmentMaterial( const FString& MaterialName ) override + { + return nullptr; + } + + + virtual void ClearAssignmentMaterials() override + { + + } + + + virtual void AddAssignmentMaterial( const FString& MaterialName, class UMaterialInterface* MaterialInterface ) override + { + + } + + + virtual class UMaterialInterface * GetReplacementMaterial( const struct FHoudiniGeoPartObject& GeoPartObject, const FString& MaterialName ) override + { + return nullptr; + } +}; + +struct FHVert +{ + int32 PointNum; + float Nx, Ny, Nz; + float uv0, uv1, uv2; +}; +struct FSortVectors +{ + bool operator()( const FVector& A, const FVector& B ) const + { + if( A.X == B.X ) + if( A.Y == B.Y ) + if( A.Z == B.Z ) + return false; + else + return A.Z < B.Z; + else + return A.Y < B.Y; + else + return A.X < B.X; + } +}; + +#if 0 +struct FParamBlock +{ + +}; + +typedef TMap< FHoudiniGeoPartObject, UStaticMesh * > PartMeshMap; + +struct IHoudiniPluginAPI +{ + virtual void InstatiateLiveAsset( const char* AssetPath, TFunction OnComplete ) + { + FFunctionGraphTask::CreateAndDispatchWhenReady( [=]() { + OnComplete( -1, FParamBlock() ); + } + , TStatId(), nullptr, ENamedThreads::GameThread ); + } + + virtual void CookLiveAsset( + HAPI_NodeId AssetId, + const FParamBlock& ParamBlock, + PartMeshMap& CurrentParts, + TFunction OnComplete ) + { + FFunctionGraphTask::CreateAndDispatchWhenReady( [=]() { + PartMeshMap NewParts; + OnComplete( true, NewParts ); + } + , TStatId(), nullptr, ENamedThreads::GameThread ); + } +}; +#endif + +UWorld* HelperGetWorld() +{ + return GWorld; +} + +UObject* FindAssetUObject( FName AssetUObjectPath ) +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( "AssetRegistry" ); + TArray AssetData; + AssetRegistryModule.Get().GetAssetsByPackageName( AssetUObjectPath, AssetData ); + if( AssetData.Num() > 0 ) + { + return AssetData[ 0 ].GetAsset(); + } + return nullptr; +} + +template +T* HelperInstantiateAssetActor( + FAutomationTestBase* Test, + FName AssetPath ) +{ + if( UHoudiniAsset* TestAsset = Cast( FindAssetUObject( AssetPath ) ) ) + { + GEditor->ClickLocation = FVector::ZeroVector; + GEditor->ClickPlane = FPlane( GEditor->ClickLocation, FVector::UpVector ); + + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( HelperGetWorld()->GetLevel( 0 ), TestAsset, true, RF_Transactional, nullptr ); + Test->TestTrue( TEXT( "Placed Actor" ), NewActors.Num() > 0 ); + if( NewActors.Num() > 0 ) + { + return Cast( NewActors[ 0 ] ); + } + } + return nullptr; +} + +void HelperInstantiateAsset( + FAutomationTestBase* Test, + FName AssetPath, + TFunction OnFinishedInstantiate ) +{ + UHoudiniAsset* TestAsset = Cast( FindAssetUObject(AssetPath) ); + HAPI_AssetLibraryId AssetLibraryId = -1; + TArray< HAPI_StringHandle > AssetNames; + HAPI_StringHandle AssetHapiName = -1; + if( FHoudiniEngineUtils::GetAssetNames( TestAsset, AssetLibraryId, AssetNames ) ) + { + AssetHapiName = AssetNames[ 0 ]; + } + + auto InstGUID = FGuid::NewGuid(); + FHoudiniEngineTask Task( EHoudiniEngineTaskType::AssetInstantiation, InstGUID ); + Task.Asset = TestAsset; + Task.ActorName = TEXT( "TestActor" ); + Task.bLoadedComponent = false; + Task.AssetLibraryId = AssetLibraryId; + Task.AssetHapiName = AssetHapiName; + FHoudiniEngine::Get().AddTask( Task ); + + Test->AddCommand( new FDelayedFunctionLatentCommand( [=]() { + // check back on status on Instantiate + FHoudiniEngineTaskInfo InstantiateTaskInfo = {}; + InstantiateTaskInfo.AssetId = -1; + if( FHoudiniEngine::Get().RetrieveTaskInfo( InstGUID, InstantiateTaskInfo ) ) + { + if( InstantiateTaskInfo.TaskState != EHoudiniEngineTaskState::FinishedInstantiation ) + { + Test->AddError( FString::Printf( TEXT( "AssetInstantiation failed" ) ) ); + } + UE_LOG( LogHoudiniTests, Log, TEXT( "InstantiateTask.StatusText: %s" ), *InstantiateTaskInfo.StatusText.ToString() ); + + FHoudiniEngine::Get().RemoveTaskInfo( InstGUID ); + + OnFinishedInstantiate( InstantiateTaskInfo, TestAsset ); + } + }, 1.f )); +} + +void HelperDeleteAsset( FAutomationTestBase* Test, HAPI_NodeId AssetId ) +{ + // Now destroy asset + auto DelGUID = FGuid::NewGuid(); + FHoudiniEngineTask DeleteTask( EHoudiniEngineTaskType::AssetDeletion, DelGUID ); + DeleteTask.AssetId = AssetId; + FHoudiniEngine::Get().AddTask( DeleteTask ); + + Test->AddCommand( new FDelayedFunctionLatentCommand( [=] { + FHoudiniEngineTaskInfo DeleteTaskInfo; + if( FHoudiniEngine::Get().RetrieveTaskInfo( DelGUID, DeleteTaskInfo ) ) + { + // we don't have a task state to check since it's fire and forget + if( DeleteTaskInfo.Result != HAPI_RESULT_SUCCESS ) + { + Test->AddError( FString::Printf( TEXT( "DeleteTask.Result: %d" ), (int32)DeleteTaskInfo.Result ) ); + } + UE_LOG( LogHoudiniTests, Log, TEXT( "DeleteTask.StatusText: %s" ), *DeleteTaskInfo.StatusText.ToString() ); + + FHoudiniEngine::Get().RemoveTaskInfo( DelGUID ); + } + + }, 1.f ) ); +} + + +void HelperCookAsset( + FAutomationTestBase* Test, + class UHoudiniAsset* InHoudiniAsset, + HAPI_NodeId AssetId, + TFunction )> OnFinishedCook ) +{ + auto CookGUID = FGuid::NewGuid(); + FHoudiniEngineTask CookTask( EHoudiniEngineTaskType::AssetCooking, CookGUID ); + CookTask.AssetId = AssetId; + + FHoudiniEngine::Get().AddTask( CookTask ); + + Test->AddCommand( new FDelayedFunctionLatentCommand( [=] { + FHoudiniEngineTaskInfo CookTaskInfo; + if( FHoudiniEngine::Get().RetrieveTaskInfo( CookGUID, CookTaskInfo ) ) + { + bool CookSuccess = false; + TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesOut; + if( CookTaskInfo.TaskState != EHoudiniEngineTaskState::FinishedCooking ) + { + Test->AddError( FString::Printf( TEXT( "CookTaskInfo.Result: %d, TaskState: %d" ), (int32)CookTaskInfo.Result, (int32)CookTaskInfo.TaskState ) ); + } + else + { + // Marshal the static mesh data + float GeoScale = 1.f; + if( const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >() ) + { + GeoScale = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + } + TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesIn; + FTestCookHandler CookHandler ( InHoudiniAsset ); + CookHandler.HoudiniCookManager = &CookHandler; + CookHandler.StaticMeshBakeMode = EBakeMode::CookToTemp; + FTransform AssetTransform; + CookSuccess = FHoudiniEngineUtils::CreateStaticMeshesFromHoudiniAsset( + AssetId, + CookHandler, + false, false, StaticMeshesIn, StaticMeshesOut, AssetTransform ); + } + OnFinishedCook( CookSuccess, StaticMeshesOut ); + } + }, 2.f )); +} + +void HelperTestMeshEqual( FAutomationTestBase* Test, UStaticMesh* MeshA, UStaticMesh* MeshB ) +{ + int32 InputNumVerts = 0; + int32 InputNumTris = 0; + + Test->TestTrue( TEXT( "MeshA Valid" ), MeshA->RenderData->LODResources.Num() > 0 ); + + if( MeshA->RenderData->LODResources.Num() > 0 ) + { + FPositionVertexBuffer& VB = MeshA->RenderData->LODResources[ 0 ].VertexBuffers.PositionVertexBuffer; + InputNumVerts = VB.GetNumVertices(); + InputNumTris = MeshA->RenderData->LODResources[ 0 ].GetNumTriangles(); + } + + Test->TestTrue( TEXT( "MeshB Valid" ), MeshB->RenderData->LODResources.Num() > 0 ); + if( MeshB->RenderData->LODResources.Num() > 0 ) + { + Test->TestEqual( TEXT( "Num Triangles" ), InputNumTris, MeshB->RenderData->LODResources[ 0 ].GetNumTriangles() ); + FPositionVertexBuffer& VB = MeshB->RenderData->LODResources[ 0 ].VertexBuffers.PositionVertexBuffer; + Test->TestEqual( TEXT( "Num Verts" ), VB.GetNumVertices(), InputNumVerts ); + } +} + +bool FHoudiniEngineRuntimeParamTest::RunTest( const FString& Paramters ) +{ + HelperInstantiateAsset( this, TEXT( "/HoudiniEngine/Test/TestParams" ), + [=]( FHoudiniEngineTaskInfo InstantiateTaskInfo, UHoudiniAsset* HoudiniAsset ) + { + HAPI_NodeId AssetId = InstantiateTaskInfo.AssetId; + if( AssetId < 0 ) + return; + + UTestHoudiniParameterBuilder * Builder = NewObject( HelperGetWorld(), TEXT("ParmBuilder"), RF_Standalone ); + bool Ok = FHoudiniParamUtils::Build( AssetId, Builder, Builder->CurrentParameters, Builder->NewParameters ); + TestTrue( TEXT( "Build success" ), Ok ); + if ( Ok ) + { + // Look at old params + TestEqual( TEXT( "No Old Parms" ), Builder->CurrentParameters.Num(), 0 ); + TestEqual( TEXT( "New Parms" ), Builder->NewParameters.Num(), 1 ); + + // Look at new params + for( auto& ParamPair : Builder->NewParameters ) + { + UHoudiniAssetParameter* Param = ParamPair.Value; + UE_LOG( LogHoudiniTests, Log, TEXT( "New Parm: %s" ), *Param->GetParameterName() ); + if( UHoudiniAssetParameterInt* ParamInt = Cast( Param ) ) + { + // Change a parameter + + int32 Val = ParamInt->GetParameterValue( 0, -1 ); + TestEqual( TEXT("GetParamterValue"), Val, 1 ); + ParamInt->SetValue( 2, 0, false, false ); + ParamInt->UploadParameterValue(); + /* + ADD_LATENT_AUTOMATION_COMMAND( FTakeActiveEditorScreenshotCommand( TEXT( "FHoudiniEngineRuntimeParamTest_0.png" ) )); + //Wait so the screenshots have a chance to save + ADD_LATENT_AUTOMATION_COMMAND( FWaitLatentCommand( 0.1f ) ); + */ + break; + } + AddError( TEXT( "Didn't find Int Param" ) ); + } + + // Requery and verify param + Builder->CurrentParameters = Builder->NewParameters; + Builder->NewParameters.Empty(); + Ok = FHoudiniParamUtils::Build( AssetId, Builder, Builder->CurrentParameters, Builder->NewParameters ); + TestTrue( TEXT( "Build success2" ), Ok ); + if( Ok ) + { + TestEqual( TEXT( "No Old Parms2" ), Builder->CurrentParameters.Num(), 0 ); + TestEqual( TEXT( "New Parms1" ), Builder->NewParameters.Num(), 1 ); + for( auto& ParamPair : Builder->NewParameters ) + { + UHoudiniAssetParameter* Param = ParamPair.Value; + UE_LOG( LogHoudiniTests, Log, TEXT( "New Parm: %s" ), *Param->GetParameterName() ); + if( UHoudiniAssetParameterInt* ParamInt = Cast( Param ) ) + { + int32 Val = ParamInt->GetParameterValue( 0, 0 ); + TestEqual( TEXT( "GetParamterValue2" ), Val, 2 ); + break; + } + AddError( TEXT("Didn't find Int Param2") ); + } + } + } + + Builder->ConditionalBeginDestroy(); + + HelperCookAsset( this, HoudiniAsset, AssetId, + [=]( bool CookOk, TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesOut ) + { + if( CookOk ) + { + TestEqual( TEXT( "Num Mesh" ), StaticMeshesOut.Num(), 1 ); + } + + HelperDeleteAsset( this, AssetId ); + } ); + + + } ); + + return true; +} + +bool FHoudiniEngineRuntimeBatchTest::RunTest( const FString& Paramters ) +{ + HelperInstantiateAsset( this, TEXT( "/HoudiniEngine/Test/TestPolyReduce" ), + [=]( FHoudiniEngineTaskInfo InstantiateTaskInfo, UHoudiniAsset* HoudiniAsset ) + { + HAPI_NodeId AssetId = InstantiateTaskInfo.AssetId; + if( AssetId < 0 ) + return; + + UTestHoudiniParameterBuilder * Builder = NewObject( HelperGetWorld(), TEXT( "ParmBuilder" ), RF_Standalone ); + bool Ok = FHoudiniParamUtils::Build( AssetId, Builder, Builder->CurrentParameters, Builder->NewParameters ); + TestTrue( TEXT( "Build success" ), Ok ); + if( Ok ) + { + // Build the inputs + UHoudiniAssetInput* GeoInputParm = UHoudiniAssetInput::Create( Builder, 0, AssetId ); + TestTrue( TEXT( "Found Input" ), GeoInputParm != nullptr ); + + int32 InputNumTris = 0; + UStaticMesh * GeoInput = Cast( StaticLoadObject( + UObject::StaticClass(), nullptr, TEXT( "StaticMesh'/Engine/BasicShapes/Cube.Cube'" ), nullptr, LOAD_None, nullptr ) ); + + TestTrue( TEXT( "Load Input Mesh" ), GeoInput != nullptr ); + if( ! GeoInput ) + return; + + if( GeoInput->RenderData->LODResources.Num() > 0 ) + { + InputNumTris = GeoInput->RenderData->LODResources[ 0 ].GetNumTriangles(); + } + TestTrue( TEXT( "Find Geo Input" ), GeoInputParm != nullptr ); + + for( int32 Ix = 0; Ix < 3; ++Ix ) + { + GEditor->ClickLocation = FVector(0, Ix * 200, 0); + GEditor->ClickPlane = FPlane( GEditor->ClickLocation, FVector::UpVector ); + + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( HelperGetWorld()->GetLevel( 0 ), GeoInput, true, RF_Transactional, nullptr ); + TestTrue( TEXT( "Placed Actor" ), NewActors.Num() > 0 ); + + if( GeoInputParm && NewActors.Num() > 0 ) + { + AActor* SMActor = NewActors.Pop(); + GeoInputParm->ClearInputs(); + GeoInputParm->ForceSetInputObject( SMActor, 0, true ); // triggers upload of data + + auto CookGUID = FGuid::NewGuid(); + FHoudiniEngineTask CookTask( EHoudiniEngineTaskType::AssetCooking, CookGUID ); + CookTask.AssetId = AssetId; + + FHoudiniEngine::Get().AddTask( CookTask ); + + while( true ) + { + FHoudiniEngineTaskInfo CookTaskInfo; + if( FHoudiniEngine::Get().RetrieveTaskInfo( CookGUID, CookTaskInfo ) ) + { + bool CookSuccess = false; + TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesOut; + if( CookTaskInfo.TaskState == EHoudiniEngineTaskState::Processing ) + { + FPlatformProcess::Sleep( 1 ); + continue; + } + else if( CookTaskInfo.TaskState == EHoudiniEngineTaskState::FinishedCookingWithErrors ) + { + AddError( FString::Printf( TEXT( "CookTaskInfo.Result: %d, TaskState: %d" ), (int32)CookTaskInfo.Result, (int32)CookTaskInfo.TaskState ) ); + break; + } + else + { + // Marshal the static mesh data + float GeoScale = 1.f; + if( const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >() ) + { + GeoScale = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + } + TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesIn; + FTestCookHandler CookHandler ( HoudiniAsset ); + CookHandler.HoudiniCookManager = &CookHandler; + CookHandler.StaticMeshBakeMode = EBakeMode::CookToTemp; + FTransform AssetTransform; + CookSuccess = FHoudiniEngineUtils::CreateStaticMeshesFromHoudiniAsset( + AssetId, + CookHandler, + false, false, StaticMeshesIn, StaticMeshesOut, AssetTransform ); + + if( CookSuccess && StaticMeshesOut.Num() > 0) + { + for( auto SMPair : StaticMeshesOut ) + { + int32 OutputNumTris = SMPair.Value->RenderData->LODResources[ 0 ].GetNumTriangles(); + TestTrue( TEXT( "reduce worked" ), OutputNumTris < InputNumTris ); + } + } + else + { + AddError( TEXT( "Failed to get static mesh output" ) ); + } + } + break; + } + else + { + AddError( TEXT( "Failed to get task state" ) ); + break; + } + } + } + } + Builder->ConditionalBeginDestroy(); + HelperDeleteAsset( this, AssetId ); + } + } ); + + return true; +} + +bool FHoudiniEngineRuntimeActorTest::RunTest( const FString& Paramters ) +{ + AHoudiniAssetActor* Actor = HelperInstantiateAssetActor( this, TEXT( "/HoudiniEngine/Test/InputEcho" ) ); + AddCommand( new FDelayedFunctionLatentCommand( [=] { + if( Actor && Actor->GetHoudiniAssetComponent() ) + { + UHoudiniAssetComponent* Comp = Actor->GetHoudiniAssetComponent(); + TestEqual( TEXT( "Done Cooking" ), Comp->IsInstantiatingOrCooking(), false ); + + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >( "PropertyEditor" ); + + // Locate the details panel. + FName DetailsPanelName = "LevelEditorSelectionDetails"; + TSharedPtr< IDetailsView > DetailsView = PropertyModule.FindDetailView( DetailsPanelName ); + + if( DetailsView.IsValid() ) + { + /* + auto DetailsW = StaticCastSharedRef( DetailsView->AsShared() ); + if( FAutomationTestFramework::Get().IsScreenshotAllowed() ) + { + const FString TestName = TEXT( "HoudiniEngineRuntimeActorTest_0.png" ); + TArray OutImageData; + FIntVector OutImageSize; + if( FSlateApplication::Get().TakeScreenshot( DetailsW, OutImageData, OutImageSize ) ) + { + FAutomationScreenshotData Data; + Data.Width = OutImageSize.X; + Data.Height = OutImageSize.Y; + Data.Path = TestName; + FAutomationTestFramework::Get().OnScreenshotCaptured().ExecuteIfBound( OutImageData, Data ); + } + } + */ + } + + } + }, 1.5f )); + return true; +} + +bool FHoudiniEngineRuntimeUploadStaticMeshTest::RunTest( const FString& Parameters ) +{ + HelperInstantiateAsset( this, TEXT( "/HoudiniEngine/Test/InputEcho"), + [=]( FHoudiniEngineTaskInfo InstantiateTaskInfo, UHoudiniAsset* HoudiniAsset ) + { + HAPI_NodeId AssetId = InstantiateTaskInfo.AssetId; + + if( AssetId < 0 ) + { + return; + } + + TArray InputObjects; + TArray< FTransform > InputTransforms; + + HAPI_NodeId ConnectedAssetId; + TArray< HAPI_NodeId > GeometryInputAssetIds; + + UStaticMesh * GeoInput = Cast( StaticLoadObject( + UObject::StaticClass(), nullptr, TEXT( "StaticMesh'/Engine/BasicShapes/Cube.Cube'" ), nullptr, LOAD_None, nullptr )); + + TestTrue( TEXT("Load Input Mesh"), GeoInput != nullptr ); + if( ! GeoInput ) + return; + + InputObjects.Add( GeoInput ); + InputTransforms.Add( FTransform::Identity ); + + if( ! FHoudiniEngineUtils::HapiCreateInputNodeForObjects( AssetId, InputObjects, InputTransforms, ConnectedAssetId, GeometryInputAssetIds, false ) ) + { + AddError( FString::Printf( TEXT( "HapiCreateInputNodeForData failed" ))); + } + + // Now connect the input + int32 InputIndex = 0; + HAPI_Result Result = FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), AssetId, InputIndex, + ConnectedAssetId, 0 ); + + TestEqual( TEXT("ConnectNodeInput"), HAPI_RESULT_SUCCESS, Result ); + + HelperCookAsset( this, HoudiniAsset, AssetId, + [=]( bool Ok, TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesOut ) + { + + if( Ok ) + { + TestEqual( TEXT( "Num Mesh" ), StaticMeshesOut.Num(), 1 ); + } + + for( auto GeoPartSM : StaticMeshesOut ) + { + FHoudiniGeoPartObject& Part = GeoPartSM.Key; + if( UStaticMesh* NewSM = GeoPartSM.Value ) + { + HelperTestMeshEqual( this, GeoInput, NewSM ); + } + break; + } + + HelperDeleteAsset( this, AssetId ); + } ); + + } ); + return true; +} + +bool FHoudiniEngineRuntimeMeshMarshalTest::RunTest( const FString& Parameters ) +{ + HelperInstantiateAsset( this, TEXT( "/HoudiniEngine/Test/TestBox" ), + [=]( FHoudiniEngineTaskInfo InstantiateTaskInfo, UHoudiniAsset* HoudiniAsset ) + { + HAPI_NodeId AssetId = InstantiateTaskInfo.AssetId; + + if( AssetId < 0 ) + { + return; + } + + HelperCookAsset( this, HoudiniAsset, AssetId, + [=]( bool Ok, TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesOut ) + { + + if( !Ok ) + { + return; + } + +#define ExpectedNumVerts 24 +#define ExpectedNumPoints 8 +#define ExpectedNumTris 12 + + FHVert ExpectedVerts[ ExpectedNumVerts ] = { + {1, -0.0, -0.0, -1.0, 0.333333, 0.666667, 0.0}, + {5, -0.0, 0.0, -1.0, 0.333333, 0.982283, 0.0}, + {4, -0.0, -0.0, -1.0, 0.64895, 0.982283, 0.0}, + {0, 0.0, -0.0, -1.0, 0.64895, 0.666667, 0.0}, + {2, 1.0, -0.0, -0.0, 0.0, 0.666667, 0.0}, + {6, 1.0, -0.0, -0.0, 0.0, 0.982283, 0.0}, + {5, 1.0, 0.0, 0.0, 0.315616, 0.982283, 0.0}, + {1, 1.0, -0.0, -0.0, 0.315616, 0.666667, 0.0}, + {3, -0.0, -0.0, 1.0, 0.0, 0.333333, 0.0}, + {7, -0.0, -0.0, 1.0, 0.0, 0.64895, 0.0}, + {6, -0.0, -0.0, 1.0, 0.315616, 0.64895, 0.0}, + {2, 0.0, 0.0, 1.0, 0.315616, 0.333333, 0.0}, + {0, -1.0, 0.0, -0.0, 0.333333, 0.333333, 0.0}, + {4, -1.0, -0.0, -0.0, 0.333333, 0.64895, 0.0}, + {7, -1.0, -0.0, 0.0, 0.64895, 0.64895, 0.0}, + {3, -1.0, -0.0, -0.0, 0.64895, 0.333333, 0.0}, + {2, 0.0, -1.0, -0.0, 0.64895, 0.315616, 0.0}, + {1, -0.0, -1.0, -0.0, 0.64895, 0.0, 0.0}, + {0, -0.0, -1.0, 0.0, 0.333333, 0.0, 0.0}, + {3, -0.0, -1.0, -0.0, 0.333333, 0.315616, 0.0}, + {5, -0.0, 1.0, -0.0, 0.315616, 0.315616, 0.0}, + {6, -0.0, 1.0, -0.0, 0.315616, 0.0, 0.0}, + {7, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0}, + {4, -0.0, 1.0, -0.0, 0.0, 0.315616, 0.0}, + }; + + FVector ExpectedPoints[ ExpectedNumPoints ] = { + {-0.5, -0.5, -0.5}, + {0.5, -0.5, -0.5}, + {0.5, -0.5, 0.5}, + {-0.5, -0.5, 0.5}, + {-0.5, 0.5, -0.5}, + {0.5, 0.5, -0.5}, + {0.5, 0.5, 0.5}, + {-0.5, 0.5, 0.5} + }; + + // Scale our expected data into unreal space + float GeoScale = 1.f; + if( const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >() ) + { + GeoScale = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; + } + for( int32 Ix = 0; Ix < ExpectedNumPoints; ++Ix ) + { + ExpectedPoints[ Ix ] *= GeoScale; + } + + + TestEqual( TEXT( "Num Mesh" ), StaticMeshesOut.Num(), 1 ); + for( auto GeoPartSM : StaticMeshesOut ) + { + FHoudiniGeoPartObject& Part = GeoPartSM.Key; + if( UStaticMesh* NewSM = GeoPartSM.Value ) + { + if( NewSM->RenderData->LODResources.Num() > 0 ) + { + TestEqual( TEXT( "Num Triangles" ), ExpectedNumTris, NewSM->RenderData->LODResources[ 0 ].GetNumTriangles() ); + FPositionVertexBuffer& VB = NewSM->RenderData->LODResources[ 0 ].VertexBuffers.PositionVertexBuffer; + const int32 VertexCount = VB.GetNumVertices(); + if( VertexCount != ExpectedNumVerts ) + { + TestEqual( TEXT( "Num Verts" ), VertexCount, ExpectedNumVerts ); + break; + } + TArray GeneratedPoints, ExpectedVertPositions; + GeneratedPoints.SetNumUninitialized( VertexCount ); + ExpectedVertPositions.SetNumUninitialized( VertexCount ); + for( int32 Index = 0; Index < VertexCount; Index++ ) + { + GeneratedPoints[ Index ] = VB.VertexPosition( Index ); + ExpectedVertPositions[ Index ] = ExpectedPoints[ ExpectedVerts[ Index ].PointNum ]; + } + + GeneratedPoints.Sort( FSortVectors() ); + ExpectedVertPositions.Sort( FSortVectors() ); + + for( int32 Index = 0; Index < VertexCount; Index++ ) + { + TestEqual( TEXT( "Points match" ), GeneratedPoints[ Index ], ExpectedVertPositions[ Index ] ); + } + } + } + break; + } + + HelperDeleteAsset( this, AssetId ); + } ); + }); + return true; +} + +#endif // WITH_EDITOR \ No newline at end of file diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/Tests/HoudiniEngineRuntimeTest.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/Tests/HoudiniEngineRuntimeTest.h new file mode 100644 index 00000000..fc09b70b --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/Tests/HoudiniEngineRuntimeTest.h @@ -0,0 +1,18 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "UObject/Class.h" + +#include "HAPI.h" +#include "HoudiniEngineRuntimeTest.generated.h" + +UCLASS() +class UTestHoudiniParameterBuilder : public UObject +{ + GENERATED_BODY() +public: + TMap< HAPI_ParmId, class UHoudiniAssetParameter * > NewParameters; + TMap< HAPI_ParmId, class UHoudiniAssetParameter * > CurrentParameters; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI.h new file mode 100644 index 00000000..dbeff8b8 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI.h @@ -0,0 +1,7849 @@ +/* + * PROPRIETARY INFORMATION. This software is proprietary to + * Side Effects Software Inc., and is not to be reproduced, + * transmitted, or disclosed in any way without written permission. + * + * COMMENTS: + * For parsing help, there is a variable naming convention we maintain: + * strings: char * and does not end in "buffer" + * binary: char * and is either exactly "buffer" or ends + * with "_buffer" + * single values: don't end with "_array" or "_buffer" + * arrays: * and is either "array" or ends + * with "_array". Use "_fixed_array" to skip resize using + * tupleSize for the thrift generator. + * array length: is either "length", "count", or ends with + * "_length" or "_count". Use "_fixed_array" to skip resize + * using tupleSize for the thrift generator. + */ + +#ifndef __HAPI_h__ +#define __HAPI_h__ + +#include "HAPI_API.h" +#include "HAPI_Common.h" +#include "HAPI_Helpers.h" + +// SESSION ------------------------------------------------------------------ + +/// @brief Creates a new in-process session. There can only be +/// one such session per host process. +/// +/// @param[out] session +/// A ::HAPI_Session struct to receive the session id, +/// in this case always 0. +/// +HAPI_DECL HAPI_CreateInProcessSession( HAPI_Session * session ); + +/// @brief Starts a Thrift RPC server process on the local host serving +/// clients on a TCP socket and waits for it to start serving. +/// It is safe to create an RPC session on local host using the +/// specified port after this call succeeds. +/// +/// @param[in] options +/// Options to configure the server being started. +/// +/// @param[in] port +/// The TCP socket to create on the server. +/// +/// @param[out] process_id +/// The process id of the server, if started successfully. +/// +HAPI_DECL HAPI_StartThriftSocketServer( + const HAPI_ThriftServerOptions * options, + int port, + HAPI_ProcessId * process_id ); + +/// @brief Creates a Thrift RPC session using a TCP socket as transport. +/// +/// @param[out] session +/// A ::HAPI_Session struct to receive the unique session id +/// of the new session. +/// +/// @param[in] host_name +/// The name of the server host. +/// +/// @param[in] port +/// The server port to connect to. +/// +HAPI_DECL HAPI_CreateThriftSocketSession( HAPI_Session * session, + const char * host_name, + int port ); + +/// @brief Starts a Thrift RPC server process on the local host serving +/// clients on a Windows named pipe or a Unix domain socket and +/// waits for it to start serving. It is safe to create an RPC +/// session using the specified pipe or socket after this call +/// succeeds. +/// +/// @param[in] options +/// Options to configure the server being started. +/// +/// @param[in] pipe_name +/// The name of the pipe or socket. +/// +/// @param[out] process_id +/// The process id of the server, if started successfully. +/// +HAPI_DECL HAPI_StartThriftNamedPipeServer( + const HAPI_ThriftServerOptions * options, + const char * pipe_name, + HAPI_ProcessId * process_id ); + +/// @brief Creates a Thrift RPC session using a Windows named pipe +/// or a Unix domain socket as transport. +/// +/// @param[out] session +/// A ::HAPI_Session struct to receive the unique session id +/// of the new session. +/// +/// @param[in] pipe_name +/// The name of the pipe or socket. +/// +HAPI_DECL HAPI_CreateThriftNamedPipeSession( HAPI_Session * session, + const char * pipe_name ); + +/// @brief Binds a new implementation DLL to one of the custom session +/// slots. +/// +/// @param[in] session_type +/// Which custom implementation slot to bind the +/// DLL to. Must be one of ::HAPI_SESSION_CUSTOM1, +/// ::HAPI_SESSION_CUSTOM2, or ::HAPI_SESSION_CUSTOM3. +/// +/// @param[in] dll_path +/// The path to the custom implementation DLL. +/// +HAPI_DECL HAPI_BindCustomImplementation( HAPI_SessionType session_type, + const char * dll_path ); + +/// @brief Creates a new session using a custom implementation. +/// Note that the implementation DLL must already have +/// been bound to the session via calling +/// ::HAPI_BindCustomImplementation(). +/// +/// @param[in] session_type +/// session_type indicates which custom session +/// slot to create the session on. +/// +/// @param[in,out] session_info +/// Any data required by the custom implementation to +/// create its session. +/// +/// @param[out] session +/// A ::HAPI_Session struct to receive the session id, +/// The sessionType parameter of the struct should +/// also match the session_type parameter passed in. +/// +HAPI_DECL HAPI_CreateCustomSession( HAPI_SessionType session_type, + void * session_info, + HAPI_Session * session ); + +/// @brief Checks whether the session identified by ::HAPI_Session::id is +/// a valid session opened in the implementation identified by +/// ::HAPI_Session::type. +/// +/// @param[in] session +/// The ::HAPI_Session to check. +/// +/// @return ::HAPI_RESULT_SUCCESS if the session is valid. +/// Otherwise, the session is invalid and passing it to +/// other HAPI calls may result in undefined behavior. +/// +HAPI_DECL HAPI_IsSessionValid( const HAPI_Session * session ); + +/// @brief Closes a session. If the session has been established using +/// RPC, then the RPC connection is closed. +/// +/// @param[in] session +/// The HAPI_Session to close. After this call, this +/// session is invalid and passing it to HAPI calls other +/// than ::HAPI_IsSessionValid() may result in undefined +/// behavior. +/// +HAPI_DECL HAPI_CloseSession( const HAPI_Session * session ); + +// INITIALIZATION / CLEANUP ------------------------------------------------- + +/// @brief Check whether the runtime has been initialized yet using +/// ::HAPI_Initialize(). Function will return ::HAPI_RESULT_SUCCESS +/// if the runtime has been initialized and ::HAPI_RESULT_NOT_INITIALIZED +/// otherwise. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +HAPI_DECL HAPI_IsInitialized( const HAPI_Session * session ); + +/// @brief Create the asset manager, set up environment variables, and +/// initialize the main Houdini scene. No license checking is +/// during this step. Only when you try to load an asset library +/// (OTL) do we actually check for licenses. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] cook_options +/// Global cook options used by subsequent default cooks. +/// This can be overwritten by individual cooks but if +/// you choose to instantiate assets with cook_on_load +/// set to true then these cook options will be used. +/// +/// @param[in] use_cooking_thread +/// Use a separate thread for cooking of assets. This +/// allows for asynchronous cooking and larger stack size. +/// +/// +/// @param[in] cooking_thread_stack_size +/// Set the stack size of the cooking thread. Use -1 to +/// set the stack size to the Houdini default. This +/// value is in bytes. +/// +/// +/// @param[in] houdini_environment_files +/// A list of paths, separated by a ";" on Windows and a ":" +/// on Linux and Mac, to .env files that follow the same +/// syntax as the houdini.env file in Houdini's user prefs +/// folder. These will be applied after the default +/// houdini.env file and will overwrite the process' +/// environment variable values. You an use this to enforce +/// a stricter environment when running engine. +/// For more info, see: +/// http://www.sidefx.com/docs/houdini/basics/config_env +/// +/// +/// @param[in] otl_search_path +/// The directory where OTLs are searched for. You can +/// pass NULL here which will only use the default +/// Houdini OTL search paths. You can also pass in +/// multiple paths separated by a ";" on Windows and a ":" +/// on Linux and Mac. If something other than NULL is +/// passed the default Houdini search paths will be +/// appended to the end of the path string. +/// +/// +/// @param[in] dso_search_path +/// The directory where generic DSOs (custom plugins) are +/// searched for. You can pass NULL here which will +/// only use the default Houdini DSO search paths. You +/// can also pass in multiple paths separated by a ";" +/// on Windows and a ":" on Linux and Mac. If something +/// other than NULL is passed the default Houdini search +/// paths will be appended to the end of the path string. +/// +/// +/// @param[in] image_dso_search_path +/// The directory where image DSOs (custom plugins) are +/// searched for. You can pass NULL here which will +/// only use the default Houdini DSO search paths. You +/// can also pass in multiple paths separated by a ";" +/// on Windows and a ":" on Linux and Mac. If something +/// other than NULL is passed the default Houdini search +/// paths will be appended to the end of the path string. +/// +/// +/// @param[in] audio_dso_search_path +/// The directory where audio DSOs (custom plugins) are +/// searched for. You can pass NULL here which will +/// only use the default Houdini DSO search paths. You +/// can also pass in multiple paths separated by a ";" +/// on Windows and a ":" on Linux and Mac. If something +/// other than NULL is passed the default Houdini search +/// paths will be appended to the end of the path string. +/// +/// +/// [HAPI_Initialize] +HAPI_DECL HAPI_Initialize( const HAPI_Session * session, + const HAPI_CookOptions * cook_options, + HAPI_Bool use_cooking_thread, + int cooking_thread_stack_size, + const char * houdini_environment_files, + const char * otl_search_path, + const char * dso_search_path, + const char * image_dso_search_path, + const char * audio_dso_search_path ); +/// [HAPI_Initialize] + +/// @brief Clean up memory. This will unload all assets and you will +/// need to call ::HAPI_Initialize() again to be able to use any +/// HAPI methods again. +/// +/// @note This does NOT release any licenses. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +HAPI_DECL HAPI_Cleanup( const HAPI_Session * session ); + +// DIAGNOSTICS -------------------------------------------------------------- + +/// @brief Gives back a certain environment integers like version number. +/// Note that you do not need a session for this. These constants +/// are hard-coded in all HAPI implementations, including HARC and +/// HAPIL. This should be the first API you call to determine if +/// any future API calls will mismatch implementation. +/// +/// @param[in] int_type +/// One of ::HAPI_EnvIntType. +/// +/// @param[out] value +/// Int value. +/// +HAPI_DECL HAPI_GetEnvInt( HAPI_EnvIntType int_type, int * value ); + +/// @brief Gives back a certain session-specific environment integers +/// like current license type being used. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] int_type +/// One of ::HAPI_SessionEnvIntType. +/// +/// @param[out] value +/// Int value. +/// +HAPI_DECL HAPI_GetSessionEnvInt( const HAPI_Session * session, + HAPI_SessionEnvIntType int_type, + int * value ); + +/// @brief Get environment variable from the server process as an integer. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] variable_name +/// Name of the environmnet variable. +/// +/// @param[out] value +/// The int pointer to return the value in. +/// +HAPI_DECL HAPI_GetServerEnvInt( const HAPI_Session * session, + const char * variable_name, + int * value ); + +/// @brief Get environment variable from the server process as a string. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] variable_name +/// Name of the environmnet variable. +/// +/// @param[out] value +/// The HAPI_StringHandle pointer to return the value in. +/// +HAPI_DECL HAPI_GetServerEnvString( const HAPI_Session * session, + const char * variable_name, + HAPI_StringHandle * value ); + +/// @brief Provides the number of environment variables that are in +/// the server environment's process +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] env_count +/// A pointer to an int to return the value in +HAPI_DECL HAPI_GetServerEnvVarCount( const HAPI_Session * session, + int * env_count ); + +/// @brief Provides a list of all of the environment variables +/// in the server's process +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] values_array +/// An ::HAPI_StringHandle array at least the size of length +/// +/// @param[in] start +/// First index of range. Must be at least @c 0 and at most +/// @c env_count returned by ::HAPI_GetServerEnvVarCount() +/// +/// +/// +/// +/// @param[in] length +/// Given @c env_count returned by ::HAPI_GetServerEnvVarCount(), +/// length should be at least @c 0 and at most env_count - start. +/// +HAPI_DECL HAPI_GetServerEnvVarList( const HAPI_Session * session, + HAPI_StringHandle * values_array, + int start, + int length ); + +/// @brief Set environment variable for the server process as an integer. +/// +/// Note that this may affect other sessions on the same server +/// process. The session parameter is mainly there to identify the +/// server process, not the specific session. +/// +/// For in-process sessions, this will affect the current process's +/// environment. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] variable_name +/// Name of the environment variable. +/// +/// @param[in] value +/// The integer value. +/// +HAPI_DECL HAPI_SetServerEnvInt( const HAPI_Session * session, + const char * variable_name, + int value ); + +/// @brief Set environment variable for the server process as a string. +/// +/// Note that this may affect other sessions on the same server +/// process. The session parameter is mainly there to identify the +/// server process, not the specific session. +/// +/// For in-process sessions, this will affect the current process's +/// environment. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] variable_name +/// Name of the environmnet variable. +/// +/// @param[in] value +/// The string value. +/// +HAPI_DECL HAPI_SetServerEnvString( const HAPI_Session * session, + const char * variable_name, + const char * value ); + +/// @brief Gives back the status code for a specific status type. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] status_type +/// One of ::HAPI_StatusType. +/// +/// @param[out] status +/// Actual status code for the status type given. That is, +/// if you pass in ::HAPI_STATUS_CALL_RESULT as +/// status_type, you'll get back a ::HAPI_Result for this +/// argument. If you pass in ::HAPI_STATUS_COOK_STATE +/// as status_type, you'll get back a ::HAPI_State enum +/// for this argument. +/// +HAPI_DECL HAPI_GetStatus( const HAPI_Session * session, + HAPI_StatusType status_type, + int * status ); + +/// @brief Return length of string buffer storing status string message. +/// +/// If called with ::HAPI_STATUS_COOK_RESULT this will actually +/// parse the node networks for the previously cooked asset(s) +/// and aggregate all node errors, warnings, and messages +/// (depending on the @c verbosity level set). Usually this is done +/// just for the last cooked single asset but if you load a whole +/// Houdini scene using ::HAPI_LoadHIPFile() then you'll have +/// multiple "previously cooked assets". +/// +/// You MUST call ::HAPI_GetStatusStringBufLength() before calling +/// ::HAPI_GetStatusString() because ::HAPI_GetStatusString() will +/// not return the real status string and instead return a +/// cached version of the string that was created inside +/// ::HAPI_GetStatusStringBufLength(). The reason for this is that +/// the length of the real status string may change between +/// the call to ::HAPI_GetStatusStringBufLength() and the call to +/// ::HAPI_GetStatusString(). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] status_type +/// One of ::HAPI_StatusType. +/// +/// @param[in] verbosity +/// Preferred verbosity level. +/// +/// @param[out] buffer_length +/// Length of buffer char array ready to be filled. +/// +HAPI_DECL HAPI_GetStatusStringBufLength( const HAPI_Session * session, + HAPI_StatusType status_type, + HAPI_StatusVerbosity verbosity, + int * buffer_length ); + +/// @brief Return status string message. +/// +/// You MUST call ::HAPI_GetStatusStringBufLength() before calling +/// ::HAPI_GetStatusString() because ::HAPI_GetStatusString() will +/// not return the real status string and instead return a +/// cached version of the string that was created inside +/// ::HAPI_GetStatusStringBufLength(). The reason for this is that +/// the length of the real status string may change between +/// the call to ::HAPI_GetStatusStringBufLength() and the call to +/// ::HAPI_GetStatusString(). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] status_type +/// One of ::HAPI_StatusType. +/// +/// @param[out] string_value +/// Buffer char array ready to be filled. +/// +/// @param[in] length +/// Length of the string buffer (must match size of +/// @p string_value - so including NULL terminator). +/// +/// +HAPI_DECL HAPI_GetStatusString( const HAPI_Session * session, + HAPI_StatusType status_type, + char * string_value, + int length ); + +/// @brief Compose the cook result string (errors and warnings) of a +/// specific node. +/// +/// This will actually parse the node network inside the given +/// node and return ALL errors/warnings/messages of all child nodes, +/// combined into a single string. If you'd like a more narrowed +/// search, call this function on one of the child nodes. +/// +/// You MUST call ::HAPI_ComposeNodeCookResult() before calling +/// ::HAPI_GetComposedNodeCookResult() because +/// ::HAPI_GetComposedNodeCookResult() will +/// not return the real result string and instead return a +/// cached version of the string that was created inside +/// ::HAPI_ComposeNodeCookResult(). The reason for this is that +/// the length of the real status string may change between +/// the call to ::HAPI_ComposeNodeCookResult() and the call to +/// ::HAPI_GetComposedNodeCookResult(). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] verbosity +/// Preferred verbosity level. +/// +/// @param[out] buffer_length +/// Length of buffer char array ready to be filled. +/// +HAPI_DECL HAPI_ComposeNodeCookResult( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_StatusVerbosity verbosity, + int * buffer_length ); + +/// @brief Return cook result string message on a single node. +/// +/// You MUST call ::HAPI_ComposeNodeCookResult() before calling +/// ::HAPI_GetComposedNodeCookResult() because +/// ::HAPI_GetComposedNodeCookResult() will +/// not return the real result string and instead return a +/// cached version of the string that was created inside +/// ::HAPI_ComposeNodeCookResult(). The reason for this is that +/// the length of the real status string may change between +/// the call to ::HAPI_ComposeNodeCookResult() and the call to +/// ::HAPI_GetComposedNodeCookResult(). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] string_value +/// Buffer char array ready to be filled. +/// +/// @param[in] length +/// Length of the string buffer (must match size of +/// @p string_value - so including NULL terminator). +/// +/// +HAPI_DECL HAPI_GetComposedNodeCookResult( const HAPI_Session * session, + char * string_value, + int length ); + +/// @brief Recursively check for specific errors by error code on a node. +/// +/// Note that checking for errors can be expensive because it checks +/// ALL child nodes within a node and then tries to do a string match +/// for the errors being looked for. This is why such error checking +/// is part of a standalone function and not done during the cooking +/// step. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] errors_to_look_for +/// The HAPI_ErrorCode error codes (as a bitfield) to look for. +/// +/// @param[out] errors_found +/// Returned HAPI_ErrorCode bitfield indicating which of the +/// looked for errors have been found. +/// +HAPI_DECL HAPI_CheckForSpecificErrors( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_ErrorCodeBits errors_to_look_for, + HAPI_ErrorCodeBits * errors_found ); + +/// @brief Clears the connection error. Should be used before starting +/// or creating Thrift server. +/// +/// Only available when using Thrift connections. +/// +HAPI_DECL HAPI_ClearConnectionError( ); + +/// @brief Return the length of string buffer storing connection error +/// message. +/// +/// Only available when using Thrift connections. +/// +/// @param[out] buffer_length +/// Length of buffer char array ready to be filled. +/// +HAPI_DECL HAPI_GetConnectionErrorLength( int * buffer_length ); + +/// @brief Return the connection error message. +/// +/// You MUST call ::HAPI_GetConnectionErrorLength() before calling +/// this to get the correct string length. +/// +/// Only available when using Thrift connections. +/// +/// @param[out] string_value +/// Buffer char array ready to be filled. +/// +/// @param[in] length +/// Length of the string buffer (must match size of +/// string_value - so including NULL terminator). +/// Use ::HAPI_GetConnectionErrorLength to get this length. +/// +/// @param[in] clear +/// If true, will clear the error when HAPI_RESULT_SUCCESS +/// is returned. +/// +HAPI_DECL HAPI_GetConnectionError( char * string_value, + int length, + HAPI_Bool clear ); + +/// @brief Get total number of nodes that need to cook in the current +/// session. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] count +/// Total cook count. +/// +HAPI_DECL HAPI_GetCookingTotalCount( const HAPI_Session * session, + int * count ); + +/// @brief Get current number of nodes that have already cooked in the +/// current session. Note that this is a very crude approximation +/// of the cooking progress - it may never make it to 100% or it +/// might spend another hour at 100%. Use ::HAPI_GetStatusString +/// to get a better idea of progress if this number gets stuck. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] count +/// Current cook count. +/// +HAPI_DECL HAPI_GetCookingCurrentCount( const HAPI_Session * session, + int * count ); + +// UTILITY ------------------------------------------------------------------ + +/// @brief Converts the transform described by a ::HAPI_TransformEuler +/// struct into a different transform and rotation order. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] transform_in +/// The transform to be converted. +/// +/// @param[in] rst_order +/// The desired transform order of the output. +/// +/// @param[in] rot_order +/// The desired rotation order of the output. +/// +/// @param[out] transform_out +/// The converted transform. +/// +HAPI_DECL HAPI_ConvertTransform( const HAPI_Session * session, + const HAPI_TransformEuler * transform_in, + HAPI_RSTOrder rst_order, + HAPI_XYZOrder rot_order, + HAPI_TransformEuler * transform_out ); + +/// @brief Converts a 4x4 matrix into its TRS form. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] matrix +/// A 4x4 matrix expressed in a 16 element float array. +/// +/// @param[in] rst_order +/// The desired transform order of the output. +/// +/// @param[out] transform_out +/// Used for the output. +/// +HAPI_DECL HAPI_ConvertMatrixToQuat( const HAPI_Session * session, + const float * matrix, + HAPI_RSTOrder rst_order, + HAPI_Transform * transform_out ); + +/// @brief Converts a 4x4 matrix into its TRS form. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] matrix +/// A 4x4 matrix expressed in a 16 element float array. +/// +/// @param[in] rst_order +/// The desired transform order of the output. +/// +/// @param[in] rot_order +/// The desired rotation order of the output. +/// +/// @param[out] transform_out +/// Used for the output. +/// +HAPI_DECL HAPI_ConvertMatrixToEuler( const HAPI_Session * session, + const float * matrix, + HAPI_RSTOrder rst_order, + HAPI_XYZOrder rot_order, + HAPI_TransformEuler * transform_out ); + +/// @brief Converts ::HAPI_Transform into a 4x4 transform matrix. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] transform +/// The ::HAPI_Transform you wish to convert. +/// +/// @param[out] matrix +/// A 16 element float array that will contain the result. +/// +HAPI_DECL HAPI_ConvertTransformQuatToMatrix( const HAPI_Session * session, + const HAPI_Transform * transform, + float * matrix ); + +/// @brief Converts ::HAPI_TransformEuler into a 4x4 transform matrix. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] transform +/// The ::HAPI_TransformEuler you wish to convert. +/// +/// @param[out] matrix +/// A 16 element float array that will contain the result. +/// +HAPI_DECL HAPI_ConvertTransformEulerToMatrix( + const HAPI_Session * session, + const HAPI_TransformEuler * transform, + float * matrix ); + +/// @brief Acquires or releases the Python interpreter lock. This is +/// needed if HAPI is called from Python and HAPI is in threaded +/// mode (see ::HAPI_Initialize()). +/// +/// The problem arises when async functions like +/// ::HAPI_CreateNode() may start a cooking thread that +/// may try to run Python code. That is, we would now have +/// Python running on two different threads - something not +/// allowed by Python by default. +/// +/// We need to tell Python to explicitly "pause" the Python state +/// on the client thread while we run Python in our cooking thread. +/// +/// You must call this function first with locked == true before +/// any async HAPI call. Then, after the async call finished, +/// detected via calls to ::HAPI_GetStatus(), call this method +/// again to release the lock with locked == false. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] locked +/// True will acquire the interpreter lock to use it for +/// the HAPI cooking thread. False will release the lock +/// back to the client thread. +/// +HAPI_DECL HAPI_PythonThreadInterpreterLock( const HAPI_Session * session, + HAPI_Bool locked ); + +// STRINGS ------------------------------------------------------------------ + +/// @brief Gives back the string length of the string with the +/// given handle. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] string_handle +/// Handle of the string to query. +/// +/// @param[out] buffer_length +/// Buffer length of the queried string (including NULL +/// terminator). +/// +HAPI_DECL HAPI_GetStringBufLength( const HAPI_Session * session, + HAPI_StringHandle string_handle, + int * buffer_length ); + +/// @brief Gives back the string value of the string with the +/// given handle. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] string_handle +/// Handle of the string to query. +/// +/// @param[out] string_value +/// Actual string value (character array). +/// +/// @param[in] length +/// Length of the string buffer (must match size of +/// @p string_value - so including NULL terminator). +/// +HAPI_DECL HAPI_GetString( const HAPI_Session * session, + HAPI_StringHandle string_handle, + char * string_value, + int length ); + +/// @brief Adds the given string to the string table and returns +/// the handle. It is the responsibility of the caller to +/// manage access to the string. The intended use for custom strings +/// is to allow structs that reference strings to be passed in to HAPI +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] string_value +/// Actual string value (character array). +/// +/// @param[out] handle_value +/// Handle of the string that was added +/// +HAPI_DECL HAPI_SetCustomString( const HAPI_Session * session, + const char * string_value, + int * handle_value ); + +/// @brief Removes the specified string from the server +/// and invalidates the handle +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] string_handle +/// Handle of the string that was added +/// +HAPI_DECL HAPI_RemoveCustomString( const HAPI_Session * session, + const int string_handle ); + +/// @brief Gives back the length of the buffer needed to hold +/// all the values null-separated for the given string +/// handles. Used with HAPI_GetStringBatch. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] string_handle_array +/// Array of string handles to be read. +/// +/// @param[in] string_handle_count +/// Length of @p string_handle_array +/// +/// @param[out] string_buffer_size +/// Buffer length required for subsequent call to +/// HAPI_GetStringBatch to hold all the given +/// string values null-terminated +/// +HAPI_DECL HAPI_GetStringBatchSize( const HAPI_Session * session, + const int * string_handle_array, + int string_handle_count, + int * string_buffer_size ); + +/// @brief Gives back the values of the given string handles. +/// The given char array is filled with null-separated +/// values, and the final value is null-terminated. +/// Used with HAPI_GetStringBatchSize. Using this function +/// instead of repeated calls to HAPI_GetString can be more +/// more efficient for a large number of strings. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] char_buffer +/// Array of characters to hold string values. +/// +/// @param[in] char_array_length +/// Length of @p char_array. Must be large enough to hold +/// all the string values including null separators. +/// +/// +/// +HAPI_DECL HAPI_GetStringBatch( const HAPI_Session * session, + char * char_buffer, + int char_array_length ); + + +// TIME --------------------------------------------------------------------- + +/// @brief Gets the global time of the scene. All API calls deal with +/// this time to cook. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] time +/// Time as a float in seconds. +/// +HAPI_DECL HAPI_GetTime( const HAPI_Session * session, float * time ); + +/// @brief Sets the global time of the scene. All API calls will deal +/// with this time to cook. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] time +/// Time as a float in seconds. +/// +HAPI_DECL HAPI_SetTime( const HAPI_Session * session, float time ); + +/// @brief Returns whether the Houdini session will use the current time in +/// Houdini when cooking and retrieving data. By default this is +/// disabled and the Houdini session uses time 0 (i.e. frame 1). +/// In SessionSync, it is enabled by default, but can be overridden. +/// Note that this function will ALWAYS return +/// ::HAPI_RESULT_SUCCESS. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] enabled +/// Whether use Houdini time is enabled or not. +/// +HAPI_DECL HAPI_GetUseHoudiniTime( const HAPI_Session * session, + HAPI_Bool * enabled ); + +/// @brief Sets whether the Houdini session should use the current time in +/// Houdini when cooking and retrieving data. By default this is +/// disabled and the Houdini session uses time 0 (i.e. frame 1). +/// In SessionSync, it is enabled by default, but can be overridden. +/// Note that this function will ALWAYS return +/// ::HAPI_RESULT_SUCCESS. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] enabled +/// Set to true to use Houdini time. +/// +HAPI_DECL HAPI_SetUseHoudiniTime( const HAPI_Session * session, + HAPI_Bool enabled ); + +/// @brief Gets the current global timeline options. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] timeline_options +/// The global timeline options struct. +/// +HAPI_DECL HAPI_GetTimelineOptions( const HAPI_Session * session, + HAPI_TimelineOptions * timeline_options ); + +/// @brief Sets the global timeline options. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] timeline_options +/// The global timeline options struct. +/// +HAPI_DECL HAPI_SetTimelineOptions( + const HAPI_Session * session, + const HAPI_TimelineOptions * timeline_options ); + +// ASSETS ------------------------------------------------------------------- + +/// @brief Loads a Houdini asset library (OTL) from a .otl file. +/// It does NOT create anything inside the Houdini scene. +/// +/// @note This is when we actually check for valid licenses. +/// +/// The next step is to call ::HAPI_GetAvailableAssetCount() +/// to get the number of assets contained in the library using the +/// returned library_id. Then call ::HAPI_GetAvailableAssets() +/// to get the list of available assets by name. Use the asset +/// names with ::HAPI_CreateNode() to actually create +/// one of these nodes in the Houdini scene and get back +/// an asset_id. +/// +/// @note The HIP file saved using ::HAPI_SaveHIPFile() will only +/// have an absolute path reference to the loaded OTL meaning +/// that if the OTL is moved or renamed the HIP file won't +/// load properly. It also means that if you change the OTL +/// using the saved HIP scene the same OTL file will change +/// as the one used with Houdini Engine. +/// See @ref HAPI_Fundamentals_SavingHIPFile. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] file_path +/// Absolute path to the .otl file. +/// +/// @param[in] allow_overwrite +/// With this true, if the library file being loaded +/// contains asset definitions that have already been +/// loaded they will overwrite the existing definitions. +/// Otherwise, a library containing asset definitions that +/// already exist will fail to load, returning a +/// ::HAPI_Result of +/// ::HAPI_RESULT_ASSET_DEF_ALREADY_LOADED. +/// +/// @param[out] library_id +/// Newly loaded otl id to be used with +/// ::HAPI_GetAvailableAssetCount() and +/// ::HAPI_GetAvailableAssets(). +/// +HAPI_DECL HAPI_LoadAssetLibraryFromFile( const HAPI_Session * session, + const char * file_path, + HAPI_Bool allow_overwrite, + HAPI_AssetLibraryId * library_id ); + +/// @brief Loads a Houdini asset library (OTL) from memory. +/// It does NOT create anything inside the Houdini scene. +/// +/// @note This is when we actually check for valid licenses. +/// +/// Please note that the performance benefit of loading a library +/// from memory are negligible at best. Due to limitations of +/// Houdini's library manager, there is still some disk access +/// and file writes because every asset library needs to be +/// saved to a real file. Use this function only as a convenience +/// if you already have the library file in memory and don't wish +/// to have to create your own temporary library file and then +/// call ::HAPI_LoadAssetLibraryFromFile(). +/// +/// The next step is to call ::HAPI_GetAvailableAssetCount() +/// to get the number of assets contained in the library using the +/// returned library_id. Then call ::HAPI_GetAvailableAssets() +/// to get the list of available assets by name. Use the asset +/// names with ::HAPI_CreateNode() to actually create +/// one of these nodes in the Houdini scene and get back +/// an asset_id. +/// +/// @note The saved HIP file using ::HAPI_SaveHIPFile() will +/// @a contain the OTL loaded as part of its @b Embedded OTLs. +/// This means that you can safely move or rename the original +/// OTL file and the HIP will continue to work but if you make +/// changes to the OTL while using the saved HIP the changes +/// won't be saved to the original OTL. +/// See @ref HAPI_Fundamentals_SavingHIPFile. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] library_buffer +/// The memory buffer containing the asset definitions +/// in the same format as a standard Houdini .otl file. +/// +/// @param[in] library_buffer_length +/// The size of the OTL memory buffer. +/// +/// @param[in] allow_overwrite +/// With this true, if the library file being loaded +/// contains asset definitions that have already been +/// loaded they will overwrite the existing definitions. +/// Otherwise, a library containing asset definitions that +/// already exist will fail to load, returning a +/// ::HAPI_Result of +/// ::HAPI_RESULT_ASSET_DEF_ALREADY_LOADED. +/// +/// @param[out] library_id +/// Newly loaded otl id to be used with +/// ::HAPI_GetAvailableAssetCount() and +/// ::HAPI_GetAvailableAssets(). +/// +HAPI_DECL HAPI_LoadAssetLibraryFromMemory( const HAPI_Session * session, + const char * library_buffer, + int library_buffer_length, + HAPI_Bool allow_overwrite, + HAPI_AssetLibraryId * library_id ); + +/// @brief Get the number of assets contained in an asset library. +/// You should call ::HAPI_LoadAssetLibraryFromFile() prior to +/// get a library_id. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] library_id +/// Returned by ::HAPI_LoadAssetLibraryFromFile(). +/// +/// +/// @param[out] asset_count +/// The number of assets contained in this asset library. +/// +HAPI_DECL HAPI_GetAvailableAssetCount( const HAPI_Session * session, + HAPI_AssetLibraryId library_id, + int * asset_count ); + +/// @brief Get the names of the assets contained in an asset library. +/// +/// The asset names will contain additional information about +/// the type of asset, namespace, and version, along with the +/// actual asset name. For example, if you have an Object type +/// asset, in the "hapi" namespace, of version 2.0, named +/// "foo", the asset name returned here will be: +/// hapi::Object/foo::2.0 +/// +/// However, you should not need to worry about this detail. Just +/// pass this string directly to ::HAPI_CreateNode() to +/// create the node. You can then get the pretty name +/// using ::HAPI_GetAssetInfo(). +/// +/// You should call ::HAPI_LoadAssetLibraryFromFile() prior to +/// get a library_id. Then, you should call +/// ::HAPI_GetAvailableAssetCount() to get the number of assets to +/// know how large of a string handles array you need to allocate. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] library_id +/// Returned by ::HAPI_LoadAssetLibraryFromFile(). +/// +/// +/// @param[out] asset_names_array +/// Array of string handles (integers) that should be +/// at least the size of asset_count. +/// +/// @param[in] asset_count +/// Should be the same or less than the value returned by +/// ::HAPI_GetAvailableAssetCount(). +/// +/// +/// +HAPI_DECL HAPI_GetAvailableAssets( const HAPI_Session * session, + HAPI_AssetLibraryId library_id, + HAPI_StringHandle * asset_names_array, + int asset_count ); + +/// @brief Fill an asset_info struct from a node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[out] asset_info +/// Returned ::HAPI_AssetInfo struct. +/// +HAPI_DECL HAPI_GetAssetInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_AssetInfo * asset_info ); + +/// @brief Get the number of asset parameters contained in an asset +/// library, as well as the number of parameter int, float, +/// string, and choice values. +/// +/// This does not create the asset in the session. +/// Use this for faster querying of asset parameters compared to +/// creating the asset node and querying the node's parameters. +/// +/// This does require ::HAPI_LoadAssetLibraryFromFile() to be +/// called prior, in order to load the asset library and +/// acquire library_id. Then ::HAPI_GetAvailableAssetCount and +/// ::HAPI_GetAvailableAssets should be called to get the +/// asset_name. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] library_id +/// Returned by ::HAPI_LoadAssetLibraryFromFile(). +/// +/// +/// @param[in] asset_name +/// Name of the asset to get the parm counts for. +/// +/// @param[out] parm_count +/// The number of parameters in the asset library. +/// +/// @param[out] int_value_count +/// The number of int values for parameters in the asset +/// library. +/// +/// @param[out] float_value_count +/// The number of float values for parameters in the asset +/// library. +/// +/// @param[out] string_value_count +/// The number of string values for parameters in the asset +/// library. +/// +/// @param[out] choice_value_count +/// The number of choice values for parameters in the asset +/// library. +/// +HAPI_DECL HAPI_GetAssetDefinitionParmCounts( const HAPI_Session * session, + HAPI_AssetLibraryId library_id, + const char * asset_name, + int * parm_count, + int * int_value_count, + int * float_value_count, + int * string_value_count, + int * choice_value_count ); + +/// @brief Fill an array of ::HAPI_ParmInfo structs with parameter +/// information for the specified asset in the specified asset +/// library. +/// +/// This does not create the asset in the session. +/// Use this for faster querying of asset parameters compared to +/// creating the asset node and querying the node's parameters. +/// +/// This does require ::HAPI_LoadAssetLibraryFromFile() to be +/// called prior, in order to load the asset library and +/// acquire library_id. ::HAPI_GetAssetDefinitionParmCounts should +/// be called prior to acquire the count for the size of +/// parm_infos_array. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] library_id +/// Returned by ::HAPI_LoadAssetLibraryFromFile(). +/// +/// +/// @param[in] asset_name +/// Name of the asset to get the parm counts for. +/// +/// @param[out] parm_infos_array +/// Array of ::HAPI_ParmInfo at least the size of +/// length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most parm_count - 1 acquired from +/// ::HAPI_GetAssetInfo. +/// +/// +/// +/// +/// @param[in] length +/// Must be at least 1 and at most parm_count - start acquired +/// from ::HAPI_GetAssetInfo +/// +/// +/// +/// +HAPI_DECL HAPI_GetAssetDefinitionParmInfos( const HAPI_Session * session, + HAPI_AssetLibraryId library_id, + const char * asset_name, + HAPI_ParmInfo * parm_infos_array, + int start, + int length ); + +/// @brief Fill arrays of parameter int values, float values, string values, +/// and choice values for parameters in the specified asset in the +/// specified asset library. +/// +/// This does not create the asset in the session. +/// Use this for faster querying of asset parameters compared to +/// creating the asset node and querying the node's parameters. +/// Note that only default values are retrieved. +/// +/// This does require ::HAPI_LoadAssetLibraryFromFile() to be +/// called prior, in order to load the asset library and +/// acquire library_id. ::HAPI_GetAssetDefinitionParmCounts should +/// be called prior to acquire the counts for the sizes of +/// the values arrays. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] library_id +/// Returned by ::HAPI_LoadAssetLibraryFromFile(). +/// +/// +/// @param[in] asset_name +/// Name of the asset to get the parm counts for. +/// +/// @param[out] int_values_array +/// Array of ints at least the size of int_length. +/// +/// @param[in] int_start +/// First index of range for int_values_array. Must be at +/// least 0 and at most int_value_count - 1 acquired from +/// ::HAPI_GetAssetDefinitionParmCounts. +/// +/// +/// +/// +/// @param[in] int_length +/// Must be at least 0 and at most int_value_count - int_start +/// acquired from ::HAPI_GetAssetDefinitionParmCounts. +/// +/// +/// +/// +/// @param[out] float_values_array +/// Array of floats at least the size of float_length. +/// +/// @param[in] float_start +/// First index of range for float_values_array. Must be at +/// least 0 and at most float_value_count - 1 acquired from +/// ::HAPI_GetAssetDefinitionParmCounts. +/// +/// +/// +/// +/// @param[in] float_length +/// Must be at least 0 and at most float_value_count - +/// float_start acquired from +/// ::HAPI_GetAssetDefinitionParmCounts. +/// +/// +/// +/// +/// @param[in] string_evaluate +/// Whether or not to evaluate the string expressions. +/// For example, the string "$F" would evaluate to the +/// current frame number. So, passing in evaluate = false +/// would give you back the string "$F" and passing +/// in evaluate = true would give you back "1" (assuming +/// the current frame is 1). +/// +/// +/// @param[out] string_values_array +/// Array of HAPI_StringHandle at least the size of +/// string_length. +/// +/// @param[in] string_start +/// First index of range for string_values_array. Must be at +/// least 0 and at most string_value_count - 1 acquired from +/// ::HAPI_GetAssetDefinitionParmCounts. +/// +/// +/// +/// +/// @param[in] string_length +/// Must be at least 0 and at most string_value_count - +/// string_start acquired from +/// ::HAPI_GetAssetDefinitionParmCounts. +/// +/// +/// +/// +/// @param[out] choice_values_array +/// Array of ::HAPI_ParmChoiceInfo at least the size of +/// choice_length. +/// +/// @param[in] choice_start +/// First index of range for choice_values_array. Must be at +/// least 0 and at most choice_value_count - 1 acquired from +/// ::HAPI_GetAssetDefinitionParmCounts. +/// +/// +/// +/// +/// @param[in] choice_length +/// Must be at least 0 and at most choice_value_count - +/// choice_start acquired from +/// ::HAPI_GetAssetDefinitionParmCounts. +/// +/// +/// +/// +HAPI_DECL HAPI_GetAssetDefinitionParmValues( + const HAPI_Session * session, + HAPI_AssetLibraryId library_id, + const char * asset_name, + int * int_values_array, + int int_start, + int int_length, + float * float_values_array, + int float_start, + int float_length, + HAPI_Bool string_evaluate, + HAPI_StringHandle * string_values_array, + int string_start, + int string_length, + HAPI_ParmChoiceInfo * choice_values_array, + int choice_start, + int choice_length ); + +/// @brief Interrupt a cook or load operation. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +HAPI_DECL HAPI_Interrupt( const HAPI_Session * session ); + +// HIP FILES ---------------------------------------------------------------- + +/// @brief Loads a .hip file into the main Houdini scene. +/// +/// @note In threaded mode, this is an _async call_! +/// +/// @note This method will merge the HIP file into the scene. This means +/// that any registered `hou.hipFile` event callbacks will be triggered +/// with the `hou.hipFileEventType.BeforeMerge` and +/// `hou.hipFileEventType.AfterMerge` events. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] file_name +/// Absolute path to the .hip file to load. +/// +/// @param[in] cook_on_load +/// Set to true if you wish the nodes to cook as soon +/// as they are created. Otherwise, you will have to +/// call ::HAPI_CookNode() explicitly for each after you +/// call this function. +/// +/// +HAPI_DECL HAPI_LoadHIPFile( const HAPI_Session * session, + const char * file_name, + HAPI_Bool cook_on_load ); + +/// @brief Loads a .hip file into the main Houdini scene. +/// +/// @note In threaded mode, this is an _async call_! +/// +/// @note This method will merge the HIP file into the scene. This means +/// that any registered `hou.hipFile` event callbacks will be triggered +/// with the `hou.hipFileEventType.BeforeMerge` and +/// `hou.hipFileEventType.AfterMerge` events. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// @param[in] file_name +/// Absolute path to the .hip file to load. +/// +/// @param[in] cook_on_load +/// Set to true if you wish the nodes to cook as soon +/// as they are created. Otherwise, you will have to +/// call ::HAPI_CookNode() explicitly for each after you +/// call this function. +/// +/// @param[out] file_id +/// This parameter will be set to the HAPI_HIPFileId of the +/// loaded HIP file. This can be used to lookup nodes that were +/// created as a result of loading this HIP file. +/// +HAPI_DECL HAPI_MergeHIPFile(const HAPI_Session * session, + const char * file_name, + HAPI_Bool cook_on_load, + HAPI_HIPFileId * file_id); + +/// @brief Saves a .hip file of the current Houdini scene. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] file_path +/// Absolute path to the .hip file to save to. +/// +/// @param[in] lock_nodes +/// Specify whether to lock all SOP nodes before saving +/// the scene file. This way, when you load the scene +/// file you can see exactly the state of each SOP at +/// the time it was saved instead of relying on the +/// re-cook to accurately reproduce the state. It does, +/// however, take a lot more space and time locking all +/// nodes like this. +/// +/// +HAPI_DECL HAPI_SaveHIPFile( const HAPI_Session * session, + const char * file_path, + HAPI_Bool lock_nodes ); + +/// @brief Gets the number of nodes that were created as a result of loading a +/// .hip file +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// @param[in] id +/// The HIP file id. +/// +/// @param[out] count +/// Pointer to an int where the HIP file node count will be +/// stored. +HAPI_DECL HAPI_GetHIPFileNodeCount(const HAPI_Session *session, + HAPI_HIPFileId id, + int * count); + +/// @brief Fills an array of ::HAPI_NodeIds of nodes that were created as a +/// result of loading the HIP file specified by the ::HAPI_HIPFileId +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// @param[in] id +/// The HIP file id. +/// +/// @param[out] node_ids +/// Array of ::HAPI_NodeId at least the size of length. +/// +/// @param[in] length +/// The number of ::HAPI_NodeId to be stored. This should be at +/// least 0 and at most the count provided by +/// HAPI_GetHIPFileNodeCount +HAPI_DECL HAPI_GetHIPFileNodeIds(const HAPI_Session *session, + HAPI_HIPFileId id, + HAPI_NodeId * node_ids, + int length); + +// NODES -------------------------------------------------------------------- + +/// @brief Determine if your instance of the node actually still exists +/// inside the Houdini scene. This is what can be used to +/// determine when the Houdini scene needs to be re-populated +/// using the host application's instances of the nodes. +/// Note that this function will ALWAYS return +/// ::HAPI_RESULT_SUCCESS. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] unique_node_id +/// The unique node id from +/// ::HAPI_NodeInfo::uniqueHoudiniNodeId. +/// +/// +/// @param[out] answer +/// Answer to the question. +/// +HAPI_DECL HAPI_IsNodeValid( const HAPI_Session * session, + HAPI_NodeId node_id, + int unique_node_id, + HAPI_Bool * answer ); + +/// @brief Fill an ::HAPI_NodeInfo struct. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[out] node_info +/// Return value - contains things like asset id. +/// +HAPI_DECL HAPI_GetNodeInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_NodeInfo * node_info ); + +/// @brief Get the node absolute path in the Houdini node network or a +/// relative path any other node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] relative_to_node_id +/// Set this to -1 to get the absolute path of the node_id. +/// Otherwise, the path will be relative to this node id. +/// +/// @param[out] path +/// The returned path string, valid until the next call to +/// this function. +/// +HAPI_DECL HAPI_GetNodePath( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_NodeId relative_to_node_id, + HAPI_StringHandle * path ); + +/// @brief Get the root node of a particular network type (ie. OBJ). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_type +/// The node network type. +/// +/// @param[out] node_id +/// The node id of the root node network. +/// +HAPI_DECL HAPI_GetManagerNodeId( const HAPI_Session * session, + HAPI_NodeType node_type, + HAPI_NodeId * node_id ); + +/// @brief Compose a list of child nodes based on given filters. +/// +/// This function will only compose the list of child nodes. It will +/// not return this list. After your call to this function, call +/// HAPI_GetComposedChildNodeList() to get the list of child node ids. +/// +/// Note: When looking for all Display SOP nodes using this function, +/// and using recursive mode, the recursion will stop as soon as a +/// display SOP is found within each OBJ geometry network. It is +/// almost never useful to get a list of ALL display SOP nodes +/// recursively as they would all containt the same geometry. Even so, +/// this special case only comes up if the display SOP itself is a +/// subnet. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] parent_node_id +/// The node id of the parent node. +/// +/// @param[in] node_type_filter +/// The node type by which to filter the children. +/// +/// @param[in] node_flags_filter +/// The node flags by which to filter the children. +/// +/// @param[in] recursive +/// Whether or not to compose the list recursively. +/// +/// @param[out] count +/// The number of child nodes composed. Use this as the +/// argument to ::HAPI_GetComposedChildNodeList(). +/// +HAPI_DECL HAPI_ComposeChildNodeList( const HAPI_Session * session, + HAPI_NodeId parent_node_id, + HAPI_NodeTypeBits node_type_filter, + HAPI_NodeFlagsBits node_flags_filter, + HAPI_Bool recursive, + int * count ); + +/// @brief Get the composed list of child node ids from the previous call +/// to HAPI_ComposeChildNodeList(). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] parent_node_id +/// The node id of the parent node. +/// +/// @param[out] child_node_ids_array +/// The array of ::HAPI_NodeId for the child nodes. +/// +/// @param[in] count +/// The number of children in the composed list. MUST match +/// the count returned by HAPI_ComposeChildNodeList(). +/// +/// +/// +/// +HAPI_DECL HAPI_GetComposedChildNodeList( const HAPI_Session * session, + HAPI_NodeId parent_node_id, + HAPI_NodeId * child_node_ids_array, + int count ); + +/// @brief Create a node inside a node network. Nodes created this way +/// will have their ::HAPI_NodeInfo::createdPostAssetLoad set +/// to true. +/// +/// @note In threaded mode, this is an _async call_! +/// +/// @note This is also when we actually check for valid licenses. +/// +/// This API will invoke the cooking thread if threading is +/// enabled. This means it will return immediately with a call +/// result of ::HAPI_RESULT_SUCCESS, even if fed garbage. Use +/// the status and cooking count APIs under DIAGNOSTICS to get +/// a sense of the progress. All other API calls will block +/// until the creation (and, optionally, the first cook) +/// of the node has finished. +/// +/// Also note that the cook result won't be of type +/// ::HAPI_STATUS_CALL_RESULT like all calls (including this one). +/// Whenever the threading cook is done it will fill the +/// @a cook result which is queried using +/// ::HAPI_STATUS_COOK_RESULT. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] parent_node_id +/// The parent node network's node id or -1 if the parent +/// network is the manager (top-level) node. In that case, +/// the manager must be identified by the table name in the +/// operator_name. +/// +/// +/// +/// @param[in] operator_name +/// The name of the node operator type. +/// +/// If you passed parent_node_id == -1, then the operator_name +/// has to include the table name (ie. Object/ or Sop/). +/// This is the common case for when creating asset nodes +/// from a loaded asset library. In that case, just pass +/// whatever ::HAPI_GetAvailableAssets() returns. +/// +/// If you have a parent_node_id then you should +/// include only the namespace, name, and version. +/// +/// For example, lets say you have an Object type asset, in +/// the "hapi" namespace, of version 2.0, named "foo". If +/// you pass parent_node_id == -1, then set the operator_name +/// as "Object/hapi::foo::2.0". Otherwise, if you have a valid +/// parent_node_id, then just pass operator_name as +/// "hapi::foo::2.0". +/// +/// @param[in] node_label +/// (Optional) The label of the newly created node. +/// +/// +/// @param[in] cook_on_creation +/// Set whether the node should cook once created or not. +/// +/// +/// @param[out] new_node_id +/// The returned node id of the just-created node. +/// +HAPI_DECL HAPI_CreateNode( const HAPI_Session * session, + HAPI_NodeId parent_node_id, + const char * operator_name, + const char * node_label, + HAPI_Bool cook_on_creation, + HAPI_NodeId * new_node_id ); + +/// @brief Creates a simple geometry SOP node that can accept geometry input. +/// This will create a dummy OBJ node with a Null SOP inside that +/// you can set the geometry of using the geometry SET APIs. +/// You can then connect this node to any other node as a geometry +/// input. +/// +/// Note that when saving the Houdini scene using +/// ::HAPI_SaveHIPFile() the nodes created with this +/// method will be green and will start with the name "input". +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] node_id +/// Newly created node's id. Use ::HAPI_GetNodeInfo() +/// to get more information about the node. +/// +/// @param[in] name +/// Give this input node a name for easy debugging. +/// The node's parent OBJ node and the Null SOP node will both +/// get this given name with "input_" prepended. +/// You can also pass NULL in which case the name will +/// be "input#" where # is some number. +/// +/// +HAPI_DECL HAPI_CreateInputNode( const HAPI_Session * session, + HAPI_NodeId * node_id, + const char * name ); + +/// @brief Creates the required node hierarchy needed for Heightfield inputs. +/// +/// Note that when saving the Houdini scene using +/// ::HAPI_SaveHIPFile() the nodes created with this +/// method will be green and will start with the name "input". +/// +/// Note also that this uses center sampling. Use +/// ::HAPI_CreateHeightfieldInput to specify corner sampling. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] parent_node_id +/// The parent node network's node id or -1 if the parent +/// network is the manager (top-level) node. In that case, +/// the manager must be identified by the table name in the +/// operator_name. +/// +/// +/// +/// @param[in] name +/// Give this input node a name for easy debugging. +/// The node's parent OBJ node and the Null SOP node will both +/// get this given name with "input_" prepended. +/// You can also pass NULL in which case the name will +/// be "input#" where # is some number. +/// +/// +/// @param[in] xsize +/// size of the heightfield in X +/// +/// @param[in] ysize +/// size of the heightfield in y +/// +/// @param[in] voxelsize +/// Size of the voxel +/// +/// @param[out] heightfield_node_id +/// Newly created node id for the Heightfield node. +/// Use ::HAPI_GetNodeInfo() to get more information about +/// the node. +/// +/// @param[out] height_node_id +/// Newly created node id for the height volume. +/// Use ::HAPI_GetNodeInfo() to get more information about the node. +/// +/// @param[out] mask_node_id +/// Newly created node id for the mask volume. +/// Use ::HAPI_GetNodeInfo() to get more information about the +/// node. +/// +/// @param[out] merge_node_id +/// Newly created merge node id. +/// The merge node can be used to connect additional input masks. +/// Use ::HAPI_GetNodeInfo() to get more information about the node. +/// +HAPI_DECL_DEPRECATED( 3.3.5, 18.0.162 ) +HAPI_CreateHeightfieldInputNode( const HAPI_Session * session, + HAPI_NodeId parent_node_id, + const char * name, + int xsize, + int ysize, + float voxelsize, + HAPI_NodeId * heightfield_node_id, + HAPI_NodeId * height_node_id, + HAPI_NodeId * mask_node_id, + HAPI_NodeId * merge_node_id ); + +/// @brief Creates the required node hierarchy needed for heightfield inputs. +/// +/// Note that when saving the Houdini scene using +/// ::HAPI_SaveHIPFile() the nodes created with this +/// method will be green and will start with the name "input". +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] parent_node_id +/// The parent node network's node id or -1 if the parent +/// network is the manager (top-level) node. In that case, +/// the manager must be identified by the table name in the +/// operator_name. +/// +/// +/// +/// @param[in] name +/// Give this input node a name for easy debugging. +/// The node's parent OBJ node and the Null SOP node will both +/// get this given name with "input_" prepended. +/// You can also pass NULL in which case the name will +/// be "input#" where # is some number. +/// +/// +/// @param[in] xsize +/// size of the heightfield in X +/// +/// @param[in] ysize +/// size of the heightfield in y +/// +/// @param[in] voxelsize +/// Size of the voxel +/// +/// @param[in] sampling +/// Type of sampling which should be either center or corner. +/// +/// @param[out] heightfield_node_id +/// Newly created node id for the heightfield node. +/// Use ::HAPI_GetNodeInfo() to get more information about +/// the node. +/// +/// @param[out] height_node_id +/// Newly created node id for the height volume. +/// Use ::HAPI_GetNodeInfo() to get more information about the node. +/// +/// @param[out] mask_node_id +/// Newly created node id for the mask volume. +/// Use ::HAPI_GetNodeInfo() to get more information about the +/// node. +/// +/// @param[out] merge_node_id +/// Newly created merge node id. +/// The merge node can be used to connect additional input masks. +/// Use ::HAPI_GetNodeInfo() to get more information about the node. +/// +HAPI_DECL HAPI_CreateHeightFieldInput( const HAPI_Session * session, + HAPI_NodeId parent_node_id, + const char * name, + int xsize, + int ysize, + float voxelsize, + HAPI_HeightFieldSampling sampling, + HAPI_NodeId * heightfield_node_id, + HAPI_NodeId * height_node_id, + HAPI_NodeId * mask_node_id, + HAPI_NodeId * merge_node_id ); + +/// @brief Creates a volume input node that can be used with Heightfields +/// +/// Note that when saving the Houdini scene using +/// ::HAPI_SaveHIPFile() the nodes created with this +/// method will be green and will start with the name "input". +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] parent_node_id +/// The parent node network's node id or -1 if the parent +/// network is the manager (top-level) node. In that case, +/// the manager must be identified by the table name in the +/// operator_name. +/// +/// +/// +/// @param[out] new_node_id +/// Newly created node id for the volume. +/// Use ::HAPI_GetNodeInfo() to get more information about the +/// node. +/// +/// @param[in] name +/// The name of the volume to create. +/// You can also pass NULL in which case the name will +/// be "input#" where # is some number. +/// +/// +/// @param[in] xsize +/// size of the heightfield in X +/// +/// @param[in] ysize +/// size of the heightfield in y +/// +/// @param[in] voxelsize +/// Size of the voxel +/// +HAPI_DECL HAPI_CreateHeightfieldInputVolumeNode( const HAPI_Session * session, + HAPI_NodeId parent_node_id, + HAPI_NodeId * new_node_id, + const char * name, + int xsize, + int ysize, + float voxelsize ); + +/// @brief Initiate a cook on this node. Note that this may trigger +/// cooks on other nodes if they are connected. +/// +/// @note In threaded mode, this is an _async call_! +/// +/// This API will invoke the cooking thread if threading is +/// enabled. This means it will return immediately. Use +/// the status and cooking count APIs under DIAGNOSTICS to get +/// a sense of the progress. All other API calls will block +/// until the cook operation has finished. +/// +/// Also note that the cook result won't be of type +/// ::HAPI_STATUS_CALL_RESULT like all calls (including this one). +/// Whenever the threading cook is done it will fill the +/// @a cook result which is queried using +/// ::HAPI_STATUS_COOK_RESULT. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] cook_options +/// The cook options. Pass in NULL to use the global +/// cook options that you specified when calling +/// ::HAPI_Initialize(). +/// +HAPI_DECL HAPI_CookNode( const HAPI_Session * session, + HAPI_NodeId node_id, + const HAPI_CookOptions * cook_options ); + +/// @brief Delete a node from a node network. Only nodes with their +/// ::HAPI_NodeInfo::createdPostAssetLoad set to true can be +/// deleted this way. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node to delete. +/// +HAPI_DECL HAPI_DeleteNode( const HAPI_Session * session, + HAPI_NodeId node_id ); + +/// @brief Rename a node that you created. Only nodes with their +/// ::HAPI_NodeInfo::createdPostAssetLoad set to true can be +/// renamed this way. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node to rename. +/// +/// @param[in] new_name +/// The new node name. +/// +HAPI_DECL HAPI_RenameNode( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * new_name ); + +/// @brief Connect two nodes together. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node whom's input to connect to. +/// +/// @param[in] input_index +/// The input index. Should be between 0 and the +/// to_node's ::HAPI_NodeInfo::inputCount - 1. +/// +/// +/// @param[in] node_id_to_connect +/// The node to connect to node_id's input. +/// +/// @param[in] output_index +/// The output index. Should be between 0 and the +/// to_node's ::HAPI_NodeInfo::outputCount - 1. +/// +/// +HAPI_DECL HAPI_ConnectNodeInput( const HAPI_Session * session, + HAPI_NodeId node_id, + int input_index, + HAPI_NodeId node_id_to_connect, + int output_index ); + +/// @brief Disconnect a node input. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node whom's input to disconnect. +/// +/// @param[in] input_index +/// The input index. Should be between 0 and the +/// to_node's ::HAPI_NodeInfo::inputCount - 1. +/// +/// +HAPI_DECL HAPI_DisconnectNodeInput( const HAPI_Session * session, + HAPI_NodeId node_id, + int input_index ); + +/// @brief Query which node is connected to another node's input. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_to_query +/// The node to query. +/// +/// @param[in] input_index +/// The input index. Should be between 0 and the +/// to_node's ::HAPI_NodeInfo::inputCount - 1. +/// +/// +/// @param[out] connected_node_id +/// The node id of the connected node to this input. If +/// nothing is connected then -1 will be returned. +/// +HAPI_DECL HAPI_QueryNodeInput( const HAPI_Session * session, + HAPI_NodeId node_to_query, + int input_index, + HAPI_NodeId * connected_node_id ); + +/// @brief Get the name of an node's input. This function will return +/// a string handle for the name which will be valid (persist) +/// until the next call to this function. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] input_idx +/// The input index. Should be between 0 and the +/// node_to_query's ::HAPI_NodeInfo::inputCount - 1. +/// +/// +/// @param[out] name +/// Input name string handle return value - valid until +/// the next call to this function. +/// +HAPI_DECL HAPI_GetNodeInputName( const HAPI_Session * session, + HAPI_NodeId node_id, + int input_idx, + HAPI_StringHandle * name ); + +/// @brief Disconnect all of the node's output connections at the output index. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node whom's outputs to disconnect. +/// +/// @param[in] output_index +/// The output index. Should be between 0 and the +/// to_node's ::HAPI_NodeInfo::outputCount. +/// +/// +HAPI_DECL HAPI_DisconnectNodeOutputsAt( const HAPI_Session * session, + HAPI_NodeId node_id, + int output_index ); + +/// @brief Get the number of nodes currently connected to the given node at +/// the output index. +/// +/// Use the @c count returned by this function to get the +/// ::HAPI_NodeIds of connected nodes using +/// ::HAPI_QueryNodeOutputConnectedNodes(). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] output_idx +/// The output index. Should be between 0 and the +/// to_node's ::HAPI_NodeInfo::outputCount - 1. +/// +/// +/// +/// @param[in] into_subnets +/// Whether to search by diving into subnets. +/// +/// +/// @param[in] through_dots +/// Whether to search through dots. +/// +/// +/// @param[out] connected_count +/// The number of nodes currently connected to this node at +/// given output index. Use this count with a call to +/// ::HAPI_QueryNodeOutputConnectedNodes() to get list of +/// connected nodes. +/// +HAPI_DECL HAPI_QueryNodeOutputConnectedCount( const HAPI_Session * session, + HAPI_NodeId node_id, + int output_idx, + HAPI_Bool into_subnets, + HAPI_Bool through_dots, + int * connected_count ); + +/// @brief Get the ids of nodes currently connected to the given node +/// at the output index. +/// +/// Use the @c connected_count returned by +/// ::HAPI_QueryNodeOutputConnectedCount(). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] output_idx +/// The output index. Should be between 0 and the +/// to_node's ::HAPI_NodeInfo::outputCount - 1. +/// +/// +/// +/// @param[in] into_subnets +/// Whether to search by diving into subnets. +/// +/// +/// @param[in] through_dots +/// Whether to search through dots. +/// +/// +/// @param[out] connected_node_ids_array +/// Array of ::HAPI_NodeId at least the size of @c length. +/// +/// @param[in] start +/// At least @c 0 and at most @c connected_count returned by +/// ::HAPI_QueryNodeOutputConnectedCount(). +/// +/// +/// +/// +/// @param[in] length +/// Given @c connected_count returned by +/// ::HAPI_QueryNodeOutputConnectedCount(), @c length should +/// be at least @c 1 and at most connected_count - start. +/// +/// +/// +/// +HAPI_DECL HAPI_QueryNodeOutputConnectedNodes( const HAPI_Session * session, + HAPI_NodeId node_id, + int output_idx, + HAPI_Bool into_subnets, + HAPI_Bool through_dots, + HAPI_NodeId * connected_node_ids_array, + int start, int length ); + +/// @brief Get the name of an node's output. This function will return +/// a string handle for the name which will be valid (persist) +/// until the next call to this function. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] output_idx +/// The output index. Should be between 0 and the +/// to_node's ::HAPI_NodeInfo::outputCount - 1. +/// +/// +/// +/// @param[out] name +/// Output name string handle return value - valid until +/// the next call to this function. +/// +HAPI_DECL HAPI_GetNodeOutputName( const HAPI_Session * session, + HAPI_NodeId node_id, + int output_idx, + HAPI_StringHandle * name ); + +/// @brief Gets the node id of an output node in a SOP network. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id of a SOP node with at least one output node. The +/// total number of node outputs can be found from the node's +/// ::HAPI_NodeInfo::outputCount +/// +/// @param[in] output +/// The output index. Should be between 0 and the node's +/// ::HAPI_NodeInfo::outputCount - 1. +/// +/// +/// +/// @param[out] output_node_id +/// Pointer to a HAPI_NodeId where the node id of the output +/// node will be stored. +HAPI_DECL HAPI_GetOutputNodeId( const HAPI_Session * session, + HAPI_NodeId node_id, + int output, + HAPI_NodeId * output_node_id ); + +// PARAMETERS --------------------------------------------------------------- + +/// @brief Fill an array of ::HAPI_ParmInfo structs with parameter +/// information from the asset instance node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[out] parm_infos_array +/// Array of ::HAPI_ParmInfo at least the size of +/// length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_NodeInfo::parmCount - 1. +/// +/// +/// +/// +/// @param[in] length +/// Must be at least 1 and at most +/// ::HAPI_NodeInfo::parmCount - start. +/// +/// +/// +/// +HAPI_DECL HAPI_GetParameters( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_ParmInfo * parm_infos_array, + int start, int length ); + +/// @brief Get the parm info of a parameter by parm id. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_id +/// The parm id. +/// +/// @param[out] parm_info +/// The returned parm info. +/// +HAPI_DECL HAPI_GetParmInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_ParmId parm_id, + HAPI_ParmInfo * parm_info ); + +/// @brief All parameter APIs require a ::HAPI_ParmId but if you know the +/// parameter you wish to operate on by name than you can use +/// this function to get its ::HAPI_ParmId. If the parameter with +/// the given name is not found the parameter id returned +/// will be -1. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_name +/// The parm name. +/// +/// @param[out] parm_id +/// The return value. The parameter's ::HAPI_ParmId. If +/// the parameter with the given name is not found the +/// parameter id returned will be -1. +/// +HAPI_DECL HAPI_GetParmIdFromName( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * parm_name, + HAPI_ParmId * parm_id ); + +/// @brief Get the parm info of a parameter by name. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_name +/// The parm name. +/// +/// @param[out] parm_info +/// The returned parm info. +/// +HAPI_DECL HAPI_GetParmInfoFromName( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * parm_name, + HAPI_ParmInfo * parm_info ); + +/// @brief Get the tag name on a parameter given an index. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_id +/// The parm id. +/// +/// @param[in] tag_index +/// The tag index, which should be between 0 and +/// ::HAPI_ParmInfo::tagCount - 1. +/// @note These indices are invalidated whenever tags are added +/// to parameters. Do not store these or expect them to be the +/// same if the scene is modified. +/// +/// +/// +/// @param[out] tag_name +/// The returned tag name. This string handle will be valid +/// until another call to ::HAPI_GetParmTagName(). +/// +HAPI_DECL HAPI_GetParmTagName( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_ParmId parm_id, + int tag_index, + HAPI_StringHandle * tag_name ); + +/// @brief Get the tag value on a parameter given the tag name. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_id +/// The parm id. +/// +/// @param[in] tag_name +/// The tag name, either known or returned by +/// ::HAPI_GetParmTagName(). +/// +/// @param[out] tag_value +/// The returned tag value. This string handle will be valid +/// until another call to ::HAPI_GetParmTagValue(). +/// +HAPI_DECL HAPI_GetParmTagValue( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_ParmId parm_id, + const char * tag_name, + HAPI_StringHandle * tag_value ); + +/// @brief See if a parameter has a specific tag. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_id +/// The parm id. +/// +/// @param[in] tag_name +/// The tag name to look for. +/// +/// @param[out] has_tag +/// True if the tag exists on the parameter, false otherwise. +/// +HAPI_DECL HAPI_ParmHasTag( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_ParmId parm_id, + const char * tag_name, + HAPI_Bool * has_tag ); + +/// @brief See if a parameter has an expression +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_name +/// The parm name. +/// +/// @param[in] index +/// The parm index. +/// +/// @param[out] has_expression +/// True if an expression exists on the parameter, false otherwise. +/// +HAPI_DECL HAPI_ParmHasExpression( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * parm_name, + int index, + HAPI_Bool * has_expression ); + +/// @brief Get the first parm with a specific, ideally unique, tag on it. +/// This is particularly useful for getting the ogl parameters on a +/// material node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] tag_name +/// The tag name to look for. +/// +/// @param[out] parm_id +/// The returned parm id. This will be -1 if no parm was found +/// with this tag. +/// +HAPI_DECL HAPI_GetParmWithTag( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * tag_name, + HAPI_ParmId * parm_id ); + +/// @brief Get single integer or float parm expression by name +/// or Null string if no expression is present +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_name +/// The parm name. +/// +/// @param[in] index +/// Index within the parameter's values tuple. +/// +/// @param[out] value +/// The returned string value. +/// +HAPI_DECL HAPI_GetParmExpression( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * parm_name, + int index, + HAPI_StringHandle * value ); + +/// @brief Revert single parm by name to default +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_name +/// The parm name. +/// +/// @param[in] index +/// Index within the parameter's values tuple. +/// +HAPI_DECL HAPI_RevertParmToDefault( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * parm_name, + int index ); + +/// @brief Revert all instances of the parm by name to defaults +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_name +/// The parm name. +/// +HAPI_DECL HAPI_RevertParmToDefaults( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * parm_name ); + +/// @brief Set (push) an expression string. We can only set a single value at +/// a time because we want to avoid fixed size string buffers. +/// +/// @note Regardless of the value, when calling this function +/// on a parameter, if that parameter has a callback function +/// attached to it, that callback function will be called. For +/// example, if the parameter is a button the button will be +/// pressed. +/// +/// @note In threaded mode, this is an _async call_! +/// +/// This API will invoke the cooking thread if threading is +/// enabled. This means it will return immediately. Use +/// the status and cooking count APIs under DIAGNOSTICS to get +/// a sense of the progress. All other API calls will block +/// until the cook operation has finished. +/// +/// Also note that the cook result won't be of type +/// ::HAPI_STATUS_CALL_RESULT like all calls (including this one). +/// Whenever the threading cook is done it will fill the +/// @a cook result which is queried using +/// ::HAPI_STATUS_COOK_RESULT. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] value +/// The expression string. +/// +/// @param[in] parm_id +/// Parameter id of the parameter being updated. +/// +/// @param[in] index +/// Index within the parameter's values tuple. +/// +HAPI_DECL HAPI_SetParmExpression( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * value, + HAPI_ParmId parm_id, int index ); + +/// @brief Remove the expression string, leaving the value of the +/// parm at the current value of the expression +/// +/// @note Regardless of the value, when calling this function +/// on a parameter, if that parameter has a callback function +/// attached to it, that callback function will be called. For +/// example, if the parameter is a button the button will be +/// pressed. +/// +/// @note In threaded mode, this is an _async call_! +/// +/// This API will invoke the cooking thread if threading is +/// enabled. This means it will return immediately. Use +/// the status and cooking count APIs under DIAGNOSTICS to get +/// a sense of the progress. All other API calls will block +/// until the cook operation has finished. +/// +/// Also note that the cook result won't be of type +/// ::HAPI_STATUS_CALL_RESULT like all calls (including this one). +/// Whenever the threading cook is done it will fill the +/// @a cook result which is queried using +/// ::HAPI_STATUS_COOK_RESULT. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_id +/// Parameter id of the parameter being updated. +/// +/// @param[in] index +/// Index within the parameter's values tuple. +/// +HAPI_DECL HAPI_RemoveParmExpression( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_ParmId parm_id, int index ); + +/// @brief Get single parm int value by name. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_name +/// The parm name. +/// +/// @param[in] index +/// Index within the parameter's values tuple. +/// +/// @param[out] value +/// The returned int value. +/// +HAPI_DECL HAPI_GetParmIntValue( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * parm_name, + int index, + int * value ); + +/// @brief Fill an array of parameter int values. This is more efficient +/// than calling ::HAPI_GetParmIntValue() individually for each +/// parameter value. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[out] values_array +/// Array of ints at least the size of length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_NodeInfo::parmIntValueCount - 1. +/// +/// +/// +/// +/// @param[in] length +/// Must be at least 1 and at most +/// ::HAPI_NodeInfo::parmIntValueCount - start. +/// +/// +/// +/// +HAPI_DECL HAPI_GetParmIntValues( const HAPI_Session * session, + HAPI_NodeId node_id, + int * values_array, + int start, int length ); + +/// @brief Get single parm float value by name. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_name +/// The parm name. +/// +/// @param[in] index +/// Index within the parameter's values tuple. +/// +/// @param[out] value +/// The returned float value. +/// +HAPI_DECL HAPI_GetParmFloatValue( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * parm_name, + int index, + float * value ); + +/// @brief Fill an array of parameter float values. This is more efficient +/// than calling ::HAPI_GetParmFloatValue() individually for each +/// parameter value. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[out] values_array +/// Array of floats at least the size of length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_NodeInfo::parmFloatValueCount - 1. +/// +/// +/// +/// +/// @param[in] length +/// Must be at least 1 and at most +/// ::HAPI_NodeInfo::parmFloatValueCount - start. +/// +/// +/// +/// +HAPI_DECL HAPI_GetParmFloatValues( const HAPI_Session * session, + HAPI_NodeId node_id, + float * values_array, + int start, int length ); + +/// @brief Get single parm string value by name. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_name +/// The name of the parameter. +/// +/// @param[in] index +/// Index within the parameter's values tuple. +/// +/// @param[in] evaluate +/// Whether or not to evaluate the string expression. +/// For example, the string "$F" would evaluate to the +/// current frame number. So, passing in evaluate = false +/// would give you back the string "$F" and passing +/// in evaluate = true would give you back "1" (assuming +/// the current frame is 1). +/// +/// +/// @param[out] value +/// The returned string value. +/// +HAPI_DECL HAPI_GetParmStringValue( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * parm_name, + int index, + HAPI_Bool evaluate, + HAPI_StringHandle * value ); + +/// @brief Fill an array of parameter string handles. These handles must +/// be used in conjunction with ::HAPI_GetString() to get the +/// actual string values. This is more efficient than calling +/// ::HAPI_GetParmStringValue() individually for each +/// parameter value. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] evaluate +/// Whether or not to evaluate the string expression. +/// For example, the string "$F" would evaluate to the +/// current frame number. So, passing in evaluate = false +/// would give you back the string "$F" and passing +/// in evaluate = true would give you back "1" (assuming +/// the current frame is 1). +/// +/// @param[out] values_array +/// Array of integers at least the size of length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_NodeInfo::parmStringValueCount - 1. +/// +/// +/// +/// +/// @param[in] length +/// Must be at least 1 and at most +/// ::HAPI_NodeInfo::parmStringValueCount - start. +/// +/// +/// +/// +HAPI_DECL HAPI_GetParmStringValues( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_Bool evaluate, + HAPI_StringHandle * values_array, + int start, int length ); + +/// @brief Get a single node id parm value of an Op Path parameter. This is +/// how you see which node is connected as an input for the current +/// node (via parameter). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_name +/// The name of the parameter. +/// +/// @param[out] value +/// The node id of the node being pointed to by the parm. +/// If there is no node found, -1 will be returned. +/// +HAPI_DECL HAPI_GetParmNodeValue( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * parm_name, + HAPI_NodeId * value ); + +/// @brief Extract a file specified by path on a parameter. This will copy +/// the file to the destination directory from wherever it might be, +/// inlcuding inside the asset definition or online. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_name +/// The name of the parameter. +/// +/// @param[in] destination_directory +/// The destination directory to copy the file to. +/// +/// @param[in] destination_file_name +/// The destination file name. +/// +HAPI_DECL HAPI_GetParmFile( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * parm_name, + const char * destination_directory, + const char * destination_file_name ); + +/// @brief Fill an array of ::HAPI_ParmChoiceInfo structs with parameter +/// choice list information from the asset instance node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[out] parm_choices_array +/// Array of ::HAPI_ParmChoiceInfo exactly the size of +/// length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_NodeInfo::parmChoiceCount - 1. +/// +/// +/// +/// +/// @param[in] length +/// Must be at least 1 and at most +/// ::HAPI_NodeInfo::parmChoiceCount - start. +/// +/// +/// +/// +HAPI_DECL HAPI_GetParmChoiceLists( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_ParmChoiceInfo * parm_choices_array, + int start, int length ); + +/// @brief Set single parm int value by name. +/// +/// @note Regardless of the value, when calling this function +/// on a parameter, if that parameter has a callback function +/// attached to it, that callback function will be called. For +/// example, if the parameter is a button the button will be +/// pressed. +/// +/// @note In threaded mode, this is an _async call_! +/// +/// This API will invoke the cooking thread if threading is +/// enabled. This means it will return immediately. Use +/// the status and cooking count APIs under DIAGNOSTICS to get +/// a sense of the progress. All other API calls will block +/// until the cook operation has finished. +/// +/// Also note that the cook result won't be of type +/// ::HAPI_STATUS_CALL_RESULT like all calls (including this one). +/// Whenever the threading cook is done it will fill the +/// @a cook result which is queried using +/// ::HAPI_STATUS_COOK_RESULT. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_name +/// The parm name. +/// +/// @param[in] index +/// Index within the parameter's values tuple. +/// +/// @param[in] value +/// The int value. +/// +HAPI_DECL HAPI_SetParmIntValue( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * parm_name, + int index, + int value ); + +/// @brief Set (push) an array of parameter int values. +/// +/// @note Regardless of the values, when calling this function +/// on a set of parameters, if any parameter has a callback +/// function attached to it, that callback function will be called. +/// For example, if the parameter is a button the button will be +/// pressed. +/// +/// @note In threaded mode, this is an _async call_! +/// +/// This API will invoke the cooking thread if threading is +/// enabled. This means it will return immediately. Use +/// the status and cooking count APIs under DIAGNOSTICS to get +/// a sense of the progress. All other API calls will block +/// until the cook operation has finished. +/// +/// Also note that the cook result won't be of type +/// ::HAPI_STATUS_CALL_RESULT like all calls (including this one). +/// Whenever the threading cook is done it will fill the +/// @a cook result which is queried using +/// ::HAPI_STATUS_COOK_RESULT. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] values_array +/// Array of integers at least the size of length. +/// +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_NodeInfo::parmIntValueCount - 1. +/// +/// +/// +/// +/// @param[in] length +/// Must be at least 1 and at most +/// ::HAPI_NodeInfo::parmIntValueCount - start. +/// +/// +/// +/// +HAPI_DECL HAPI_SetParmIntValues( const HAPI_Session * session, + HAPI_NodeId node_id, + const int * values_array, + int start, int length ); + +/// @brief Set single parm float value by name. +/// +/// @note Regardless of the value, when calling this function +/// on a parameter, if that parameter has a callback function +/// attached to it, that callback function will be called. For +/// example, if the parameter is a button the button will be +/// pressed. +/// +/// @note In threaded mode, this is an _async call_! +/// +/// This API will invoke the cooking thread if threading is +/// enabled. This means it will return immediately. Use +/// the status and cooking count APIs under DIAGNOSTICS to get +/// a sense of the progress. All other API calls will block +/// until the cook operation has finished. +/// +/// Also note that the cook result won't be of type +/// ::HAPI_STATUS_CALL_RESULT like all calls (including this one). +/// Whenever the threading cook is done it will fill the +/// @a cook result which is queried using +/// ::HAPI_STATUS_COOK_RESULT. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_name +/// The parm name. +/// +/// @param[in] index +/// Index within the parameter's values tuple. +/// +/// @param[in] value +/// The float value. +/// +HAPI_DECL HAPI_SetParmFloatValue( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * parm_name, + int index, + float value ); + +/// @brief Set (push) an array of parameter float values. +/// +/// @note Regardless of the values, when calling this function +/// on a set of parameters, if any parameter has a callback +/// function attached to it, that callback function will be called. +/// For example, if the parameter is a button the button will be +/// pressed. +/// +/// @note In threaded mode, this is an _async call_! +/// +/// This API will invoke the cooking thread if threading is +/// enabled. This means it will return immediately. Use +/// the status and cooking count APIs under DIAGNOSTICS to get +/// a sense of the progress. All other API calls will block +/// until the cook operation has finished. +/// +/// Also note that the cook result won't be of type +/// ::HAPI_STATUS_CALL_RESULT like all calls (including this one). +/// Whenever the threading cook is done it will fill the +/// @a cook result which is queried using +/// ::HAPI_STATUS_COOK_RESULT. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] values_array +/// Array of floats at least the size of length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_NodeInfo::parmFloatValueCount - 1. +/// +/// @param[in] length +/// Must be at least 1 and at most +/// ::HAPI_NodeInfo::parmFloatValueCount - start. +/// +HAPI_DECL HAPI_SetParmFloatValues( const HAPI_Session * session, + HAPI_NodeId node_id, + const float * values_array, + int start, int length ); + +/// @brief Set (push) a string value. We can only set a single value at +/// a time because we want to avoid fixed size string buffers. +/// +/// @note Regardless of the value, when calling this function +/// on a parameter, if that parameter has a callback function +/// attached to it, that callback function will be called. For +/// example, if the parameter is a button the button will be +/// pressed. +/// +/// @note In threaded mode, this is an _async call_! +/// +/// This API will invoke the cooking thread if threading is +/// enabled. This means it will return immediately. Use +/// the status and cooking count APIs under DIAGNOSTICS to get +/// a sense of the progress. All other API calls will block +/// until the cook operation has finished. +/// +/// Also note that the cook result won't be of type +/// ::HAPI_STATUS_CALL_RESULT like all calls (including this one). +/// Whenever the threading cook is done it will fill the +/// @a cook result which is queried using +/// ::HAPI_STATUS_COOK_RESULT. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] value +/// The string value. +/// +/// @param[in] parm_id +/// Parameter id of the parameter being updated. +/// +/// @param[in] index +/// Index within the parameter's values tuple. +/// +HAPI_DECL HAPI_SetParmStringValue( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * value, + HAPI_ParmId parm_id, int index ); + +/// @brief Set a node id parm value of an Op Path parameter. For example, +/// This is how you connect the geometry output of an asset to the +/// geometry input of another asset - whether the input is a parameter +/// or a node input (the top of the node). Node inputs get converted +/// top parameters in HAPI. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_name +/// The name of the parameter. +/// +/// @param[in] value +/// The node id of the node being connected. Pass -1 to +/// disconnect. +/// +HAPI_DECL HAPI_SetParmNodeValue( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * parm_name, + HAPI_NodeId value ); + +/// @brief Insert an instance of a multiparm before instance_position. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_id +/// A parm id given by a ::HAPI_ParmInfo struct that +/// has type ::HAPI_PARMTYPE_MULTIPARMLIST. +/// +/// @param[in] instance_position +/// The new instance will be inserted at this position +/// index. Do note the multiparms can start at position +/// 1 or 0. Use ::HAPI_ParmInfo::instanceStartOffset to +/// distinguish. +/// +HAPI_DECL HAPI_InsertMultiparmInstance( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_ParmId parm_id, + int instance_position ); + +/// @brief Remove the instance of a multiparm given by instance_position. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] parm_id +/// A parm id given by a ::HAPI_ParmInfo struct that +/// has type ::HAPI_PARMTYPE_MULTIPARMLIST. +/// +/// @param[in] instance_position +/// The instance at instance_position will removed. +/// +HAPI_DECL HAPI_RemoveMultiparmInstance( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_ParmId parm_id, + int instance_position ); + +// HANDLES ------------------------------------------------------------------ + +/// @brief Fill an array of ::HAPI_HandleInfo structs with information +/// about every exposed user manipulation handle on the node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[out] handle_infos_array +/// Array of ::HAPI_HandleInfo at least the size of length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AssetInfo::handleCount - 1. +/// +/// +/// @param[in] length +/// Must be at least 1 and at most +/// ::HAPI_AssetInfo::handleCount - start. +/// +/// +HAPI_DECL HAPI_GetHandleInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_HandleInfo * handle_infos_array, + int start, int length ); + +/// @brief Fill an array of ::HAPI_HandleBindingInfo structs with information +/// about the binding of a particular handle on the given node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] handle_index +/// The index of the handle, from 0 to handleCount - 1 +/// from the call to ::HAPI_GetAssetInfo(). +/// +/// @param[out] handle_binding_infos_array +/// Array of ::HAPI_HandleBindingInfo at least the size +/// of length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_HandleInfo::bindingsCount - 1. +/// +/// +/// @param[in] length +/// Must be at least 1 and at most +/// ::HAPI_HandleInfo::bindingsCount - start. +/// +/// +HAPI_DECL HAPI_GetHandleBindingInfo( + const HAPI_Session * session, + HAPI_NodeId node_id, + int handle_index, + HAPI_HandleBindingInfo * handle_binding_infos_array, + int start, int length ); + +// PRESETS ------------------------------------------------------------------ + +/// @brief Generate a preset blob of the current state of all the +/// parameter values, cache it, and return its size in bytes. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The exposed node id. +/// +/// @param[in] preset_type +/// The preset type. +/// +/// @param[in] preset_name +/// Optional. This is only used if the @p preset_type is +/// ::HAPI_PRESETTYPE_IDX. If NULL is given, the preset +/// name will be the same as the name of the node with +/// the given @p node_id. +/// +/// @param[out] buffer_length +/// Size of the buffer. +/// +HAPI_DECL HAPI_GetPresetBufLength( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PresetType preset_type, + const char * preset_name, + int * buffer_length ); + +/// @brief Generates a preset for the given asset. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The exposed node id. +/// +/// @param[out] buffer +/// Buffer to hold the preset data. +/// +/// @param[in] buffer_length +/// Size of the buffer. Should be the same as the length +/// returned by ::HAPI_GetPresetBufLength(). +/// +HAPI_DECL HAPI_GetPreset( const HAPI_Session * session, + HAPI_NodeId node_id, + char * buffer, + int buffer_length ); + +/// @brief Sets a particular asset to a given preset. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The exposed node id. +/// +/// @param[in] preset_type +/// The preset type. +/// +/// @param[in] preset_name +/// Optional. This is only used if the @p preset_type is +/// ::HAPI_PRESETTYPE_IDX. If NULL is give, the first +/// preset in the IDX file will be chosen. +/// +/// +/// @param[in] buffer +/// Buffer to hold the preset data. +/// +/// @param[in] buffer_length +/// Size of the buffer. +/// +HAPI_DECL HAPI_SetPreset( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PresetType preset_type, + const char * preset_name, + const char * buffer, + int buffer_length ); + +// OBJECTS ------------------------------------------------------------------ + +/// @brief Get the object info on an OBJ node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[out] object_info +/// The output ::HAPI_ObjectInfo. +/// +HAPI_DECL HAPI_GetObjectInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_ObjectInfo * object_info ); + +/// @brief Get the tranform of an OBJ node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The object node id. +/// +/// @param[in] relative_to_node_id +/// The object node id for the object to which the returned +/// transform will be relative to. Pass -1 or the node_id +/// to just get the object's local transform. +/// +/// @param[in] rst_order +/// The order of application of translation, rotation and +/// scale. +/// +/// @param[out] transform +/// The output ::HAPI_Transform transform. +/// +HAPI_DECL HAPI_GetObjectTransform( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_NodeId relative_to_node_id, + HAPI_RSTOrder rst_order, + HAPI_Transform * transform ); + +/// @brief Compose a list of child object nodes given a parent node id. +/// +/// Use the @c object_count returned by this function to get the +/// ::HAPI_ObjectInfo structs for each child object using +/// ::HAPI_GetComposedObjectList(). +/// +/// Note, if not using the @c categories arg, this is equivalent to: +/// @code +/// HAPI_ComposeChildNodeList( +/// session, parent_node_id, +/// HAPI_NODETYPE_OBJ, +/// HAPI_NODEFLAGS_OBJ_GEOMETRY, +/// true, &object_count ); +/// @endcode +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] parent_node_id +/// The parent node id. +/// +/// @param[in] categories +/// (Optional) Lets you filter object nodes by their render +/// categories. This is a standard OBJ parameter, usually +/// under the Render > Shading tab. If an OBJ node does not +/// have this parameter, one can always add it as a spare. +/// +/// The value of this string argument should be NULL if not +/// used or a space-separated list of category names. +/// Multiple category names will be treated as an AND op. +/// +/// +/// @param[out] object_count +/// The number of object nodes currently under the parent. +/// Use this count with a call to +/// ::HAPI_GetComposedObjectList() to get the object infos. +/// +HAPI_DECL HAPI_ComposeObjectList( const HAPI_Session * session, + HAPI_NodeId parent_node_id, + const char * categories, + int * object_count ); + +/// @brief Fill an array of ::HAPI_ObjectInfo structs. +/// +/// This is best used with ::HAPI_ComposeObjectList() with. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] parent_node_id +/// The parent node id. +/// +/// @param[out] object_infos_array +/// Array of ::HAPI_ObjectInfo at least the size of +/// @c length. +/// +/// @param[in] start +/// At least @c 0 and at most @c object_count returned by +/// ::HAPI_ComposeObjectList(). +/// +/// +/// @param[in] length +/// Given @c object_count returned by +/// ::HAPI_ComposeObjectList(), @c length should be at least +/// @c 0 and at most object_count - start. +/// +/// +HAPI_DECL HAPI_GetComposedObjectList( const HAPI_Session * session, + HAPI_NodeId parent_node_id, + HAPI_ObjectInfo * object_infos_array, + int start, int length ); + +/// @brief Fill an array of ::HAPI_Transform structs. +/// +/// This is best used with ::HAPI_ComposeObjectList() with. +/// +/// Note that these transforms will be relative to the +/// @c parent_node_id originally given to ::HAPI_ComposeObjectList() +/// and expected to be the same with this call. If @c parent_node_id +/// is not an OBJ node, the transforms will be given as they are on +/// the object node itself. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] parent_node_id +/// The parent node id. The object transforms will be +/// relative to this node unless this node is not an OBJ. +/// +/// @param[in] rst_order +/// The order of application of translation, rotation and +/// scale. +/// +/// @param[out] transform_array +/// Array of ::HAPI_Transform at least the size of +/// length. +/// +/// @param[in] start +/// At least @c 0 and at most @c object_count returned by +/// ::HAPI_ComposeObjectList(). +/// +/// +/// @param[in] length +/// Given @c object_count returned by +/// ::HAPI_ComposeObjectList(), @c length should be at least +/// @c 0 and at most object_count - start. +/// +/// +HAPI_DECL HAPI_GetComposedObjectTransforms( const HAPI_Session * session, + HAPI_NodeId parent_node_id, + HAPI_RSTOrder rst_order, + HAPI_Transform * transform_array, + int start, int length ); + +/// @brief Get the node ids for the objects being instanced by an +/// Instance OBJ node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] object_node_id +/// The object node id. +/// +/// @param[out] instanced_node_id_array +/// Array of ::HAPI_NodeId at least the size of length. +/// +/// @param[in] start +/// At least @c 0 and at most @c object_count returned by +/// ::HAPI_ComposeObjectList(). +/// +/// +/// @param[in] length +/// Given @c object_count returned by +/// ::HAPI_ComposeObjectList(), @c length should be at least +/// @c 0 and at most object_count - start. +/// +/// +HAPI_DECL HAPI_GetInstancedObjectIds( const HAPI_Session * session, + HAPI_NodeId object_node_id, + HAPI_NodeId * instanced_node_id_array, + int start, int length ); + +/// @brief Fill an array of ::HAPI_Transform structs with the transforms +/// of each instance of this instancer object. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] object_node_id +/// The object node id. +/// +/// @param[in] rst_order +/// The order of application of translation, rotation and +/// scale. +/// +/// @param[out] transforms_array +/// Array of ::HAPI_Transform at least the size of length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_PartInfo::pointCount - 1. This is the 0th +/// part of the display geo of the instancer object node. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_PartInfo::pointCount - @p start. This is the 0th +/// part of the display geo of the instancer object node. +/// +/// +HAPI_DECL_DEPRECATED( 3.2.42, 18.0.150 ) +HAPI_GetInstanceTransforms( const HAPI_Session * session, + HAPI_NodeId object_node_id, + HAPI_RSTOrder rst_order, + HAPI_Transform * transforms_array, + int start, int length ); + +/// @brief Fill an array of ::HAPI_Transform structs with the transforms +/// of each instance of this instancer object for a given part. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The object node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] rst_order +/// The order of application of translation, rotation and +/// scale. +/// +/// @param[out] transforms_array +/// Array of ::HAPI_Transform at least the size of length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_PartInfo::pointCount - 1. This is the 0th +/// part of the display geo of the instancer object node. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_PartInfo::pointCount - @p start. This is the 0th +/// part of the display geo of the instancer object node. +/// +/// +HAPI_DECL HAPI_GetInstanceTransformsOnPart( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_RSTOrder rst_order, + HAPI_Transform * transforms_array, + int start, int length ); + +/// @brief Set the transform of an individual object. Note that the object +/// nodes have to either be editable or have their transform +/// parameters exposed at the asset level. This won't work otherwise. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The object node id. +/// +/// @param[in] trans +/// A ::HAPI_TransformEuler that stores the transform. +/// +HAPI_DECL HAPI_SetObjectTransform( const HAPI_Session * session, + HAPI_NodeId node_id, + const HAPI_TransformEuler * trans ); + +// GEOMETRY GETTERS --------------------------------------------------------- + +/// @brief Get the display geo (SOP) node inside an Object node. If there +/// there are multiple display SOP nodes, only the first one is +/// returned. If the node is a display SOP itself, even if a network, +/// it will return its own geo info. If the node is a SOP but not +/// a network and not the display SOP, this function will fail. +/// +/// The above implies that you can safely call this function on both +/// OBJ and SOP asset nodes and get the same (equivalent) geometry +/// display node back. SOP asset nodes will simply return themselves. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] object_node_id +/// The object node id. +/// +/// @param[out] geo_info +/// ::HAPI_GeoInfo return value. +/// +HAPI_DECL HAPI_GetDisplayGeoInfo( const HAPI_Session * session, + HAPI_NodeId object_node_id, + HAPI_GeoInfo * geo_info ); + +/// @brief Get the geometry info struct (::HAPI_GeoInfo) on a SOP node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[out] geo_info +/// ::HAPI_GeoInfo return value. +/// +HAPI_DECL HAPI_GetGeoInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_GeoInfo * geo_info ); + +/// @brief Get a particular part info struct. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[out] part_info +/// ::HAPI_PartInfo return value. +/// +HAPI_DECL HAPI_GetPartInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_PartInfo * part_info ); + +/// @brief Get the array of faces where the nth integer in the array is +/// the number of vertices the nth face has. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[out] face_counts_array +/// An integer array at least the size of length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_PartInfo::faceCount - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_PartInfo::faceCount - @p start. +/// +/// +HAPI_DECL HAPI_GetFaceCounts( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + int * face_counts_array, + int start, int length ); + +/// @brief Get array containing the vertex-point associations where the +/// ith element in the array is the point index the ith vertex +/// associates with. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[out] vertex_list_array +/// An integer array at least the size of length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_PartInfo::vertexCount - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_PartInfo::vertexCount - @p start. +/// +/// +HAPI_DECL HAPI_GetVertexList( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + int * vertex_list_array, + int start, int length ); + +/// @brief Get the attribute info struct for the attribute specified by name. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] owner +/// Attribute owner. +/// +/// @param[out] attr_info +/// ::HAPI_AttributeInfo to be filled. Check +/// ::HAPI_AttributeInfo::exists to see if this attribute +/// exists. +/// +HAPI_DECL HAPI_GetAttributeInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeOwner owner, + HAPI_AttributeInfo * attr_info ); + +/// @brief Get list of attribute names by attribute owner. Note that the +/// name string handles are only valid until the next time this +/// function is called. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] owner +/// The ::HAPI_AttributeOwner enum value specifying the +/// owner of the attribute. +/// +/// @param[out] attribute_names_array +/// Array of ints (string handles) to house the +/// attribute names. Should be exactly the size of the +/// appropriate attribute owner type count +/// in ::HAPI_PartInfo. +/// +/// @param[in] count +/// Sanity check count. Must be equal to the appropriate +/// attribute owner type count in ::HAPI_PartInfo. +/// +HAPI_DECL HAPI_GetAttributeNames( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_AttributeOwner owner, + HAPI_StringHandle * attribute_names_array, + int count ); + +/// @brief Get attribute integer data. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] stride +/// Specifies how many items to skip over for each element. +/// With a stride of -1, the stride will be set to +/// @c attr_info->tuple_size. Otherwise, the stride will be +/// set to the maximum of @c attr_info->tuple_size and +/// @c stride. +/// +/// @param[out] data_array +/// An integer array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeIntData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + int stride, + int * data_array, + int start, int length ); + +/// @brief Get array attribute integer data. +/// Each entry in an array attribute can have varying array lengths. +/// Therefore the array values are returned as a flat array, with +/// another sizes array containing the lengths of each array entry. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[out] data_fixed_array +/// An integer array at least the size of +/// ::HAPI_AttributeInfo::totalArrayElements. +/// +/// @param[in] data_fixed_length +/// Must be ::HAPI_AttributeInfo::totalArrayElements. +/// +/// +/// @param[out] sizes_fixed_array +/// An integer array at least the size of +/// sizes_fixed_length to hold the size of each entry. +/// +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] sizes_fixed_length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeIntArrayData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + int * data_fixed_array, + int data_fixed_length, + int * sizes_fixed_array, + int start, int sizes_fixed_length ); + +/// @brief Get attribute 64-bit integer data. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] stride +/// Specifies how many items to skip over for each element. +/// With a stride of -1, the stride will be set to +/// @c attr_info->tuple_size. Otherwise, the stride will be +/// set to the maximum of @c attr_info->tuple_size and +/// @c stride. +/// +/// @param[out] data_array +/// An 64-bit integer array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeInt64Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + int stride, + HAPI_Int64 * data_array, + int start, int length ); + +/// @brief Get array attribute 64-bit integer data. +/// Each entry in an array attribute can have varying array lengths. +/// Therefore the array values are returned as a flat array, with +/// another sizes array containing the lengths of each array entry. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[out] data_fixed_array +/// An 64-bit integer array at least the size of +/// ::HAPI_AttributeInfo::totalArrayElements. +/// +/// @param[in] data_fixed_length +/// Must be ::HAPI_AttributeInfo::totalArrayElements. +/// +/// +/// @param[out] sizes_fixed_array +/// An integer array at least the size of +/// sizes_fixed_length to hold the size of each entry. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] sizes_fixed_length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeInt64ArrayData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + HAPI_Int64 * data_fixed_array, + int data_fixed_length, + int * sizes_fixed_array, + int start, int sizes_fixed_length ); + +/// @brief Get attribute float data. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] stride +/// Specifies how many items to skip over for each element. +/// With a stride of -1, the stride will be set to +/// @c attr_info->tuple_size. Otherwise, the stride will be +/// set to the maximum of @c attr_info->tuple_size and +/// @c stride. +/// +/// @param[out] data_array +/// An float array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeFloatData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + int stride, + float * data_array, + int start, int length ); + +/// @brief Get array attribute float data. +/// Each entry in an array attribute can have varying array lengths. +/// Therefore the array values are returned as a flat array, with +/// another sizes array containing the lengths of each array entry. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[out] data_fixed_array +/// An float array at least the size of +/// ::HAPI_AttributeInfo::totalArrayElements. +/// +/// @param[in] data_fixed_length +/// Must be ::HAPI_AttributeInfo::totalArrayElements. +/// +/// +/// @param[out] sizes_fixed_array +/// An integer array at least the size of +/// sizes_fixed_length to hold the size of each entry. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] sizes_fixed_length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeFloatArrayData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + float * data_fixed_array, + int data_fixed_length, + int * sizes_fixed_array, + int start, int sizes_fixed_length ); + +/// @brief Get 64-bit attribute float data. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] stride +/// Specifies how many items to skip over for each element. +/// With a stride of -1, the stride will be set to +/// @c attr_info->tuple_size. Otherwise, the stride will be +/// set to the maximum of @c attr_info->tuple_size and +/// @c stride. +/// +/// @param[out] data_array +/// An 64-bit float array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeFloat64Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + int stride, + double * data_array, + int start, int length ); + +/// @brief Get array attribute 64-bit float data. +/// Each entry in an array attribute can have varying array lengths. +/// Therefore the array values are returned as a flat array, with +/// another sizes array containing the lengths of each array entry. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for the. +/// totalArrayElements. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[out] data_fixed_array +/// An 64-bit float array at least the size of +/// ::HAPI_AttributeInfo::totalArrayElements. +/// +/// @param[in] data_fixed_length +/// Must be ::HAPI_AttributeInfo::totalArrayElements. +/// +/// +/// @param[out] sizes_fixed_array +/// An integer array at least the size of +/// sizes_fixed_length to hold the size of each entry. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] sizes_fixed_length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeFloat64ArrayData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + double * data_fixed_array, + int data_fixed_length, + int * sizes_fixed_array, + int start, int sizes_fixed_length ); + +/// @brief Get attribute string data. Note that the string handles +/// returned are only valid until the next time this function +/// is called. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[out] data_array +/// An ::HAPI_StringHandle array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeStringData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + HAPI_StringHandle * data_array, + int start, int length ); + +/// @brief Get array attribute string data. +/// Each entry in an array attribute can have varying array lengths. +/// Therefore the array values are returned as a flat array, with +/// another sizes array containing the lengths of each array entry. +/// Note that the string handles returned are only valid until +/// the next time this function is called. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for the. +/// totalArrayElements. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[out] data_fixed_array +/// An ::HAPI_StringHandle array at least the size of +/// ::HAPI_AttributeInfo::totalArrayElements. +/// +/// @param[in] data_fixed_length +/// Must be ::HAPI_AttributeInfo::totalArrayElements. +/// +/// +/// @param[out] sizes_fixed_array +/// An integer array at least the size of +/// sizes_fixed_length to hold the size of each entry. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] sizes_fixed_length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeStringArrayData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + HAPI_StringHandle * data_fixed_array, + int data_fixed_length, + int * sizes_fixed_array, + int start, int sizes_fixed_length ); + +/// @brief Get group names for an entire geo. Please note that this +/// function is NOT per-part, but it is per-geo. The companion +/// function ::HAPI_GetGroupMembership() IS per-part. Also keep +/// in mind that the name string handles are only +/// valid until the next time this function is called. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] group_type +/// The group type. +/// +/// @param[out] group_names_array +/// The array of names to be filled. Should be the size +/// given by ::HAPI_GeoInfo_GetGroupCountByType() with +/// @p group_type and the ::HAPI_GeoInfo of @p geo_id. +/// @note These string handles are only valid until the +/// next call to ::HAPI_GetGroupNames(). +/// +/// @param[in] group_count +/// Sanity check. Should be less than or equal to the size +/// of @p group_names. +/// +HAPI_DECL HAPI_GetGroupNames( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_GroupType group_type, + HAPI_StringHandle * group_names_array, + int group_count ); + +/// @brief Get group membership. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] group_type +/// The group type. +/// +/// @param[in] group_name +/// The group name. +/// +/// @param[out] membership_array_all_equal +/// (optional) Quick way to determine if all items are in +/// the given group or all items our not in the group. +/// You can just pass NULL here if not interested. +/// +/// @param[out] membership_array +/// Array of ints that represent the membership of this +/// group. Should be the size given by +/// ::HAPI_PartInfo_GetElementCountByGroupType() with +/// @p group_type and the ::HAPI_PartInfo of @p part_id. +/// +/// @param[in] start +/// Start offset into the membership array. Must be +/// less than ::HAPI_PartInfo_GetElementCountByGroupType(). +/// +/// +/// @param[in] length +/// Should be less than or equal to the size +/// of @p membership_array. +/// +/// +HAPI_DECL HAPI_GetGroupMembership( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_GroupType group_type, + const char * group_name, + HAPI_Bool * membership_array_all_equal, + int * membership_array, + int start, int length ); + +/// @brief Get group counts for a specific packed instanced part. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. (should be a packed primitive) +/// +/// @param[out] pointGroupCount +/// Number of point groups on the packed instance part. +/// Will be set to -1 if the part is not a valid packed part. +/// +/// @param[out] primitiveGroupCount +/// Number of primitive groups on the instanced part. +/// Will be set to -1 if the part is not a valid instancer +/// +HAPI_DECL HAPI_GetGroupCountOnPackedInstancePart( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + int * pointGroupCount, + int * primitiveGroupCount ); + +/// @brief Get the group names for a packed instance part +/// This functions allows you to get the group name for a specific +/// packed primitive part. +/// Keep in mind that the name string handles are only +/// valid until the next time this function is called. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. (should be a packed primitive) +/// +/// @param[in] group_type +/// The group type. +/// +/// @param[out] group_names_array +/// The array of names to be filled. Should be the size +/// given by ::HAPI_GetGroupCountOnInstancedPart() with +/// @p group_type and the ::HAPI_PartInfo of @p part_id. +/// @note These string handles are only valid until the +/// next call to ::HAPI_GetGroupNamesOnPackedInstancePart(). +/// +/// @param[in] group_count +/// Sanity check. Should be less than or equal to the size +/// of @p group_names. +/// +HAPI_DECL HAPI_GetGroupNamesOnPackedInstancePart( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_GroupType group_type, + HAPI_StringHandle * group_names_array, + int group_count ); + +/// @brief Get group membership for a packed instance part +/// This functions allows you to get the group membership for a specific +/// packed primitive part. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. (should be a packed primitive) +/// +/// @param[in] group_type +/// The group type. +/// +/// @param[in] group_name +/// The group name. +/// +/// @param[out] membership_array_all_equal +/// (optional) Quick way to determine if all items are in +/// the given group or all items our not in the group. +/// You can just pass NULL here if not interested. +/// +/// @param[out] membership_array +/// Array of ints that represent the membership of this +/// group. Should be the size given by +/// ::HAPI_PartInfo_GetElementCountByGroupType() with +/// @p group_type and the ::HAPI_PartInfo of @p part_id. +/// +/// @param[in] start +/// Start offset into the membership array. Must be +/// less than ::HAPI_PartInfo_GetElementCountByGroupType(). +/// +/// +/// @param[in] length +/// Should be less than or equal to the size +/// of @p membership_array. +/// +/// +HAPI_DECL HAPI_GetGroupMembershipOnPackedInstancePart( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_GroupType group_type, + const char * group_name, + HAPI_Bool * membership_array_all_equal, + int * membership_array, + int start, int length ); + +/// @brief Get the part ids that this instancer part is instancing. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The instancer part id. +/// +/// @param[out] instanced_parts_array +/// Array of ::HAPI_PartId's to instance. +/// +/// @param[in] start +/// Should be less than @p part_id's +/// ::HAPI_PartInfo::instancedPartCount but more than or +/// equal to 0. +/// +/// +/// @param[in] length +/// Should be less than @p part_id's +/// ::HAPI_PartInfo::instancedPartCount - @p start. +/// +/// +HAPI_DECL HAPI_GetInstancedPartIds( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_PartId * instanced_parts_array, + int start, int length ); + +/// @brief Get the instancer part's list of transforms on which to +/// instance the instanced parts you got from +/// ::HAPI_GetInstancedPartIds(). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The instancer part id. +/// +/// @param[in] rst_order +/// The order of application of translation, rotation and +/// scale. +/// +/// @param[out] transforms_array +/// Array of ::HAPI_PartId's to instance. +/// +/// @param[in] start +/// Should be less than @p part_id's +/// ::HAPI_PartInfo::instanceCount but more than or +/// equal to 0. +/// +/// +/// @param[in] length +/// Should be less than @p part_id's +/// ::HAPI_PartInfo::instanceCount - @p start. +/// +/// +HAPI_DECL HAPI_GetInstancerPartTransforms( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_RSTOrder rst_order, + HAPI_Transform * transforms_array, + int start, int length ); + +// GEOMETRY SETTERS --------------------------------------------------------- + +/// @brief Set the main part info struct (::HAPI_PartInfo). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// +/// @param[in] part_info +/// ::HAPI_PartInfo value that describes the input +/// geometry. +/// +HAPI_DECL HAPI_SetPartInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const HAPI_PartInfo * part_info ); + +/// @brief Set the array of faces where the nth integer in the array is +/// the number of vertices the nth face has. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// +/// @param[in] face_counts_array +/// An integer array at least the size of @p length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_PartInfo::faceCount - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_PartInfo::faceCount - @p start. +/// +/// +HAPI_DECL HAPI_SetFaceCounts( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const int * face_counts_array, + int start, int length ); + +/// @brief Set array containing the vertex-point associations where the +/// ith element in the array is the point index the ith vertex +/// associates with. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] vertex_list_array +/// An integer array at least the size of length. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_PartInfo::vertexCount - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_PartInfo::vertexCount - @p start. +/// +/// +HAPI_DECL HAPI_SetVertexList( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const int * vertex_list_array, + int start, int length ); + +/// @brief Add an attribute. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo stores attribute properties. +/// +HAPI_DECL HAPI_AddAttribute( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + const HAPI_AttributeInfo * attr_info ); +/// @brief Delete an attribute from an input geo +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. + /// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo stores attribute properties. +/// +HAPI_DECL HAPI_DeleteAttribute( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + const HAPI_AttributeInfo * attr_info ); + +/// @brief Set attribute integer data. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] data_array +/// An integer array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// +/// +HAPI_DECL HAPI_SetAttributeIntData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + const HAPI_AttributeInfo * attr_info, + const int * data_array, + int start, int length ); + +/// @brief Set 64-bit attribute integer data. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] data_array +/// An 64-bit integer array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// +/// +HAPI_DECL HAPI_SetAttributeInt64Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + const HAPI_AttributeInfo * attr_info, + const HAPI_Int64 * data_array, + int start, int length ); + +/// @brief Set attribute float data. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] data_array +/// An float array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// +/// +HAPI_DECL HAPI_SetAttributeFloatData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + const HAPI_AttributeInfo * attr_info, + const float * data_array, + int start, int length ); + +/// @brief Set 64-bit attribute float data. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] data_array +/// An 64-bit float array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// +/// +HAPI_DECL HAPI_SetAttributeFloat64Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + const HAPI_AttributeInfo * attr_info, + const double * data_array, + int start, int length ); + +/// @brief Set attribute string data. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] data_array +/// An ::HAPI_StringHandle array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// +/// +HAPI_DECL HAPI_SetAttributeStringData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + const HAPI_AttributeInfo * attr_info, + const char ** data_array, + int start, int length ); + +/// @brief Add a group to the input geo with the given type and name. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] group_type +/// The group type. +/// +/// @param[in] group_name +/// Name of new group to be added. +/// +HAPI_DECL HAPI_AddGroup( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_GroupType group_type, + const char * group_name ); + +/// @brief Remove a group from the input geo with the given type and name. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] group_type +/// The group type. +/// +/// @param[in] group_name +/// Name of the group to be removed +/// +HAPI_DECL HAPI_DeleteGroup( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_GroupType group_type, + const char * group_name ); + +/// @brief Set group membership. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// +/// @param[in] group_type +/// The group type. +/// +/// @param[in] group_name +/// The group name. +/// +/// @param[in] membership_array +/// Array of ints that represent the membership of this +/// group. Should be the size given by +/// ::HAPI_PartInfo_GetElementCountByGroupType() with +/// @p group_type and the ::HAPI_PartInfo of @p part_id. +/// +/// @param[in] start +/// Start offset into the membership array. Must be +/// less than ::HAPI_PartInfo_GetElementCountByGroupType(). +/// +/// +/// @param[in] length +/// Should be less than or equal to the size +/// of @p membership_array. +/// +/// +HAPI_DECL HAPI_SetGroupMembership( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_GroupType group_type, + const char * group_name, + const int * membership_array, + int start, int length ); + +/// @brief Commit the current input geometry to the cook engine. Nodes +/// that use this geometry node will re-cook using the input +/// geometry given through the geometry setter API calls. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +HAPI_DECL HAPI_CommitGeo( const HAPI_Session * session, + HAPI_NodeId node_id ); + +/// @brief Remove all changes that have been committed to this +/// geometry. If this is an intermediate result node (Edit SOP), all +/// deltas will be removed. If it's any other type of node, the node +/// will be unlocked if it is locked. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +HAPI_DECL HAPI_RevertGeo( const HAPI_Session * session, + HAPI_NodeId node_id ); + +// MATERIALS ---------------------------------------------------------------- + +/// @brief Get material ids by face/primitive. The material ids returned +/// will be valid as long as the asset is alive. You should query +/// this list after every cook to see if the material assignments +/// have changed. You should also query each material individually +/// using ::HAPI_GetMaterialInfo() to see if it is dirty and needs +/// to be re-imported. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] geometry_node_id +/// The geometry node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[out] are_all_the_same +/// (optional) If true, all faces on this part have the +/// same material assignment. You can pass NULL here. +/// +/// @param[out] material_ids_array +/// An array of ::HAPI_NodeId at least the size of +/// @p length and at most the size of +/// ::HAPI_PartInfo::faceCount. +/// +/// @param[in] start +/// The starting index into the list of faces from which +/// you wish to get the material ids from. Note that +/// this should be less than ::HAPI_PartInfo::faceCount. +/// +/// +/// @param[in] length +/// The number of material ids you wish to get. Note that +/// this should be at most: +/// ::HAPI_PartInfo::faceCount - @p start. +/// +/// +HAPI_DECL HAPI_GetMaterialNodeIdsOnFaces( const HAPI_Session * session, + HAPI_NodeId geometry_node_id, + HAPI_PartId part_id, + HAPI_Bool * are_all_the_same, + HAPI_NodeId * material_ids_array, + int start, int length ); + +/// @brief Get the material info. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] material_node_id +/// The material node id. +/// +/// @param[out] material_info +/// The returned material info. +/// +HAPI_DECL HAPI_GetMaterialInfo( const HAPI_Session * session, + HAPI_NodeId material_node_id, + HAPI_MaterialInfo * material_info ); + +/// @brief Render a single texture from a COP to an image for +/// later extraction. +/// +/// Note that you must call this first for any of the other material +/// APIs to work. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] cop_node_id +/// The COP node id. +/// +HAPI_DECL HAPI_RenderCOPToImage( const HAPI_Session * session, + HAPI_NodeId cop_node_id ); + +/// @brief Render only a single texture to an image for later extraction. +/// An example use of this method might be to render the diffuse, +/// normal, and bump texture maps of a material to individual +/// texture files for use within the client application. +/// +/// Note that you must call this first for any of the other material +/// APIs to work. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] material_node_id +/// The material node id. +/// +/// @param[in] parm_id +/// This is the index in the parameter list of the +/// material_id's node of the parameter containing the +/// texture map file path. +/// +HAPI_DECL HAPI_RenderTextureToImage( const HAPI_Session * session, + HAPI_NodeId material_node_id, + HAPI_ParmId parm_id ); + +/// @brief Get information about the image that was just rendered, like +/// resolution and default file format. This information will be +/// used when extracting planes to an image. +/// +/// Note that you must call ::HAPI_RenderTextureToImage() first for +/// this method call to make sense. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] material_node_id +/// The material node id. +/// +/// @param[out] image_info +/// The struct containing the image information. +/// +HAPI_DECL HAPI_GetImageInfo( const HAPI_Session * session, + HAPI_NodeId material_node_id, + HAPI_ImageInfo * image_info ); + +/// @brief Set image information like resolution and file format. +/// This information will be used when extracting planes to +/// an image. +/// +/// Note that you must call ::HAPI_RenderTextureToImage() first for +/// this method call to make sense. +/// +/// You should also first call ::HAPI_GetImageInfo() to get the +/// current Image Info and change only the properties +/// you don't like. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] material_node_id +/// The material node id. +/// +/// @param[in] image_info +/// The struct containing the new image information. +/// +HAPI_DECL HAPI_SetImageInfo( const HAPI_Session * session, + HAPI_NodeId material_node_id, + const HAPI_ImageInfo * image_info ); + +/// @brief Get the number of image planes for the just rendered image. +/// +/// Note that you must call ::HAPI_RenderTextureToImage() first for +/// this method call to make sense. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] material_node_id +/// The material node id. +/// +/// @param[out] image_plane_count +/// The number of image planes. +/// +HAPI_DECL HAPI_GetImagePlaneCount( const HAPI_Session * session, + HAPI_NodeId material_node_id, + int * image_plane_count ); + +/// @brief Get the names of the image planes of the just rendered image. +/// +/// Note that you must call ::HAPI_RenderTextureToImage() first for +/// this method call to make sense. +/// +/// You should also call ::HAPI_GetImagePlaneCount() first to get +/// the total number of image planes so you know how large the +/// image_planes string handle array should be. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] material_node_id +/// The material node id. +/// +/// @param[out] image_planes_array +/// The image plane names. +/// +/// @param[in] image_plane_count +/// The number of image planes to get names for. This +/// must be less than or equal to the count returned +/// by ::HAPI_GetImagePlaneCount(). +/// +/// +HAPI_DECL HAPI_GetImagePlanes( const HAPI_Session * session, + HAPI_NodeId material_node_id, + HAPI_StringHandle * image_planes_array, + int image_plane_count ); + +/// @brief Extract a rendered image to a file. +/// +/// Note that you must call ::HAPI_RenderTextureToImage() first for +/// this method call to make sense. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] material_node_id +/// The material node id. +/// +/// @param[in] image_file_format_name +/// The image file format name you wish the image to be +/// extracted as. You can leave this parameter NULL to +/// get the image in the original format if it comes from +/// another texture file or in the default HAPI format, +/// which is ::HAPI_DEFAULT_IMAGE_FORMAT_NAME, if the image +/// is generated. +/// +/// You can get some of the very common standard image +/// file format names from HAPI_Common.h under the +/// "Defines" section. +/// +/// You can also get a list of all supported file formats +/// (and the exact names this parameter expects) +/// by using ::HAPI_GetSupportedImageFileFormats(). This +/// list will include custom file formats you created via +/// custom DSOs (see HDK docs about IMG_Format). You will +/// get back a list of ::HAPI_ImageFileFormat. This +/// parameter expects the ::HAPI_ImageFileFormat::nameSH +/// of a given image file format. +/// +/// @param[in] image_planes +/// The image planes you wish to extract into the file. +/// Multiple image planes should be separated by spaces. +/// +/// @param[in] destination_folder_path +/// The folder where the image file should be created. +/// +/// @param[in] destination_file_name +/// Optional parameter to overwrite the name of the +/// extracted texture file. This should NOT include +/// the extension as the file type will be decided +/// by the ::HAPI_ImageInfo you can set using +/// ::HAPI_SetImageInfo(). You still have to use +/// destination_file_path to get the final file path. +/// +/// Pass in NULL to have the file name be automatically +/// generated from the name of the material SHOP node, +/// the name of the texture map parameter if the +/// image was rendered from a texture, and the image +/// plane names specified. +/// +/// @param[out] destination_file_path +/// The full path string handle, including the +/// destination_folder_path and the texture file name, +/// to the extracted file. Note that this string handle +/// will only be valid until the next call to +/// this function. +/// +HAPI_DECL HAPI_ExtractImageToFile( const HAPI_Session * session, + HAPI_NodeId material_node_id, + const char * image_file_format_name, + const char * image_planes, + const char * destination_folder_path, + const char * destination_file_name, + int * destination_file_path ); +/// @brief Get the file name that this image would be saved to +/// +/// Check to see what file path HAPI_ExtractImageToFile would have +/// saved to given the same parms. Perhaps you might wish to see +/// if it already exists before extracting. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] material_node_id +/// The material node id. +/// +/// @param[in] image_file_format_name +/// The image file format name you wish the image to be +/// extracted as. See HAPI_ExtractImageToFile for more information. +/// +/// @param[in] image_planes +/// The image planes you wish to extract into the file. +/// Multiple image planes should be separated by spaces. +/// +/// @param[in] destination_folder_path +/// The folder where the image file sould be created. +/// +/// @param[in] destination_file_name +/// Optional parameter to overwrite the name of the +/// extracted texture file. See HAPI_ExtractImageToFile for more information. +/// +/// @param[in] texture_parm_id +/// The index in the parameter list of the material node. +/// of the parameter containing the texture map file path +/// +/// @param[out] destination_file_path +/// The full path string handle, including the +/// destination_folder_path and the texture file name, +/// to the extracted file. Note that this string handle +/// will only be valid until the next call to +/// this function. +/// +HAPI_DECL HAPI_GetImageFilePath( const HAPI_Session * session, + HAPI_NodeId material_node_id, + const char * image_file_format_name, + const char * image_planes, + const char * destination_folder_path, + const char * destination_file_name, + HAPI_ParmId texture_parm_id, + int * destination_file_path ); + +/// @brief Extract a rendered image to memory. +/// +/// Note that you must call ::HAPI_RenderTextureToImage() first for +/// this method call to make sense. +/// +/// Also note that this function will do all the work of +/// extracting and compositing the image into a memory buffer +/// but will not return to you that buffer, only its size. Use +/// the returned size to allocated a sufficiently large buffer +/// and call ::HAPI_GetImageMemoryBuffer() to fill your buffer +/// with the just extracted image. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] material_node_id +/// The material node id. +/// +/// @param[in] image_file_format_name +/// The image file format name you wish the image to be +/// extracted as. You can leave this parameter NULL to +/// get the image in the original format if it comes from +/// another texture file or in the default HAPI format, +/// which is ::HAPI_DEFAULT_IMAGE_FORMAT_NAME, if the image +/// is generated. +/// +/// You can get some of the very common standard image +/// file format names from HAPI_Common.h under the +/// "Defines" section. +/// +/// You can also get a list of all supported file formats +/// (and the exact names this parameter expects) +/// by using ::HAPI_GetSupportedImageFileFormats(). This +/// list will include custom file formats you created via +/// custom DSOs (see HDK docs about IMG_Format). You will +/// get back a list of ::HAPI_ImageFileFormat. This +/// parameter expects the ::HAPI_ImageFileFormat::nameSH +/// of a given image file format. +/// +/// @param[in] image_planes +/// The image planes you wish to extract into the file. +/// Multiple image planes should be separated by spaces. +/// +/// @param[out] buffer_size +/// The extraction will be done to an internal buffer +/// who's size you get via this parameter. Use the +/// returned buffer_size when calling +/// ::HAPI_GetImageMemoryBuffer() to get the image +/// buffer you just extracted. +/// +HAPI_DECL HAPI_ExtractImageToMemory( const HAPI_Session * session, + HAPI_NodeId material_node_id, + const char * image_file_format_name, + const char * image_planes, + int * buffer_size ); + +/// @brief Fill your allocated buffer with the just extracted +/// image buffer. +/// +/// Note that you must call ::HAPI_RenderTextureToImage() first for +/// this method call to make sense. +/// +/// Also note that you must call ::HAPI_ExtractImageToMemory() +/// first in order to perform the extraction and get the +/// extracted image buffer size that you need to know how much +/// memory to allocated to fit your extracted image. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] material_node_id +/// The material node id. +/// +/// @param[out] buffer +/// The buffer passed in here will be filled with the +/// image buffer created during the call to +/// ::HAPI_ExtractImageToMemory(). +/// +/// @param[in] length +/// Sanity check. This size should be the same as the +/// size allocated for the buffer passed in and should +/// be at least as large as the buffer_size returned by +/// the call to ::HAPI_ExtractImageToMemory(). +/// +/// +HAPI_DECL HAPI_GetImageMemoryBuffer( const HAPI_Session * session, + HAPI_NodeId material_node_id, + char * buffer, int length ); + +/// @brief Get the number of supported texture file formats. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] file_format_count +/// The number of supported texture file formats. +/// +HAPI_DECL HAPI_GetSupportedImageFileFormatCount( const HAPI_Session * session, + int * file_format_count ); + +/// @brief Get a list of support image file formats - their names, +/// descriptions and a list of recognized extensions. +/// +/// Note that you MUST call +/// ::HAPI_GetSupportedImageFileFormatCount() +/// before calling this function for the first time. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] formats_array +/// The list of ::HAPI_ImageFileFormat structs to +/// be filled. +/// +/// @param[in] file_format_count +/// The number of supported texture file formats. This +/// should be at least as large as the count returned +/// by ::HAPI_GetSupportedImageFileFormatCount(). +/// +/// +HAPI_DECL HAPI_GetSupportedImageFileFormats( + const HAPI_Session * session, + HAPI_ImageFileFormat * formats_array, + int file_format_count ); + +// SIMULATION/ANIMATION ----------------------------------------------------- + +/// @brief Set an animation curve on a parameter of an exposed node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The exposed node id. +/// +/// @param[in] parm_id +/// The id of an exposed parameter within the node. +/// @param[in] parm_index +/// The index of the parameter, if it is for example +/// a 3 tuple +/// +/// @param[in] curve_keyframes_array +/// An array of ::HAPI_Keyframe structs that describes +/// the keys on this curve. +/// +/// @param[in] keyframe_count +/// The number of keys on the curve. +/// +HAPI_DECL HAPI_SetAnimCurve( const HAPI_Session * session, + HAPI_NodeId node_id, HAPI_ParmId parm_id, + int parm_index, + const HAPI_Keyframe * curve_keyframes_array, + int keyframe_count ); + +/// @brief A specialized convenience function to set the T,R,S values +/// on an exposed node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The exposed node id. +/// +/// @param[in] trans_comp +/// A value of ::HAPI_TransformComponent that +/// identifies the particular component of the +/// transform to attach the curve to, for example +/// ::HAPI_TRANSFORM_TX. +/// +/// @param[in] curve_keyframes_array +/// An array of ::HAPI_Keyframe structs that describes +/// the keys on this curve. +/// +/// @param[in] keyframe_count +/// The number of keys on the curve. +/// +HAPI_DECL HAPI_SetTransformAnimCurve( + const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_TransformComponent trans_comp, + const HAPI_Keyframe * curve_keyframes_array, + int keyframe_count ); + +/// @brief Resets the simulation cache of the asset. This is very useful +/// for assets that use dynamics, to be called after some +/// setup has changed for the asset - for example, asset inputs +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The asset node id. +/// +HAPI_DECL HAPI_ResetSimulation( const HAPI_Session * session, + HAPI_NodeId node_id ); + +// VOLUMES ------------------------------------------------------------------ + +/// @brief Retrieve any meta-data about the volume primitive, including +/// its transform, location, scale, taper, resolution. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[out] volume_info +/// The meta-data associated with the volume on the +/// part specified by the previous parameters. +/// +HAPI_DECL HAPI_GetVolumeInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_VolumeInfo * volume_info ); + +/// @brief Iterate through a volume based on 8x8x8 sections of the volume +/// Start iterating through the value of the volume at part_id. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[out] tile +/// The tile info referring to the first tile in the +/// volume at part_id. +/// +HAPI_DECL HAPI_GetFirstVolumeTile( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_VolumeTileInfo * tile ); + +/// @brief Iterate through a volume based on 8x8x8 sections of the volume +/// Continue iterating through the value of the volume at part_id. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[out] tile +/// The tile info referring to the next tile in the +/// set of tiles associated with the volume at this part. +/// +HAPI_DECL HAPI_GetNextVolumeTile( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_VolumeTileInfo * tile ); + +/// @brief Retrieve floating point values of the voxel at a specific +/// index. Note that you must call ::HAPI_GetVolumeInfo() prior +/// to this call. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] x_index +/// The x index/coordinate of the voxel. +/// +/// @param[in] y_index +/// The y index/coordinate of the voxel. +/// +/// @param[in] z_index +/// The z index/coordinate of the voxel. +/// +/// @param[out] values_array +/// The values of the voxel. +/// +/// @param[in] value_count +/// Should be equal to the volume's +/// ::HAPI_VolumeInfo::tupleSize. +/// +/// +HAPI_DECL HAPI_GetVolumeVoxelFloatData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + int x_index, + int y_index, + int z_index, + float * values_array, + int value_count ); + +/// @brief Retrieve floating point values of the voxels pointed to +/// by a tile. Note that a tile may extend beyond the limits +/// of the volume so not all values in the given buffer will +/// be written to. Voxels outside the volume will be initialized +/// to the given fill value. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] fill_value +/// Value that will be used to fill the @p values_array. +/// This is useful so that you can see what values +/// have actually been written to. +/// +/// @param[in] tile +/// The tile to retrieve. +/// +/// @param[out] values_array +/// The values of the tile. +/// +/// @param[in] length +/// The length should be ( 8 ^ 3 ) * tupleSize. +/// +HAPI_DECL HAPI_GetVolumeTileFloatData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + float fill_value, + const HAPI_VolumeTileInfo * tile, + float * values_array, + int length ); + +/// @brief Retrieve integer point values of the voxel at a specific +/// index. Note that you must call ::HAPI_GetVolumeInfo() prior +/// to this call. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] x_index +/// The x index/coordinate of the voxel. +/// +/// @param[in] y_index +/// The y index/coordinate of the voxel. +/// +/// @param[in] z_index +/// The z index/coordinate of the voxel. +/// +/// @param[out] values_array +/// The values of the voxel. +/// +/// @param[in] value_count +/// Should be equal to the volume's +/// ::HAPI_VolumeInfo::tupleSize. +/// +/// +HAPI_DECL HAPI_GetVolumeVoxelIntData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + int x_index, + int y_index, + int z_index, + int * values_array, + int value_count ); + +/// @brief Retrieve integer point values of the voxels pointed to +/// by a tile. Note that a tile may extend beyond the limits +/// of the volume so not all values in the given buffer will +/// be written to. Voxels outside the volume will be initialized +/// to the given fill value. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] fill_value +/// Value that will be used to fill the @p values_array. +/// This is useful so that you can see what values +/// have actually been written to. +/// +/// @param[in] tile +/// The tile to retrieve. +/// +/// @param[out] values_array +/// The values of the tile. +/// +/// @param[in] length +/// The length should be ( 8 ^ 3 ) * tupleSize. +/// +HAPI_DECL HAPI_GetVolumeTileIntData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + int fill_value, + const HAPI_VolumeTileInfo * tile, + int * values_array, + int length ); + +/// @brief Get the height field data for a terrain volume as a flattened +/// 2D array of float heights. Should call ::HAPI_GetVolumeInfo() +/// first to make sure the volume info is initialized. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[out] values_array +/// Heightfield flattened array. Should be at least the size of +/// @p start + @p length. +/// +/// @param[in] start +/// The start at least 0 and at most +/// ( ::HAPI_VolumeInfo::xLength * ::HAPI_VolumeInfo::yLength ) +/// - @p length. +/// +/// @param[in] length +/// The length should be at least 1 or at most +/// ( ::HAPI_VolumeInfo::xLength * ::HAPI_VolumeInfo::yLength ) +/// - @p start. +/// +HAPI_DECL HAPI_GetHeightFieldData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + float * values_array, + int start, int length ); + +/// @brief Set the volume info of a geo on a geo input. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] volume_info +/// All volume information that can be specified per +/// volume. This includes the position, orientation, scale, +/// data format, tuple size, and taper. The tile size is +/// always 8x8x8. +/// +HAPI_DECL HAPI_SetVolumeInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const HAPI_VolumeInfo * volume_info ); + +/// @brief Set the values of a float tile: this is an 8x8x8 subsection of +/// the volume. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] tile +/// The tile that the volume will be input into. +/// +/// @param[in] values_array +/// The values of the individual voxel tiles in the +/// volume. The length of this array should +/// be ( 8 ^ 3 ) * tupleSize. +/// +/// @param[in] length +/// The length should be ( 8 ^ 3 ) * tupleSize. +/// +HAPI_DECL HAPI_SetVolumeTileFloatData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const HAPI_VolumeTileInfo * tile, + const float * values_array, + int length ); + +/// @brief Set the values of an int tile: this is an 8x8x8 subsection of +/// the volume. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] tile +/// The tile that the volume will be input into. +/// +/// @param[in] values_array +/// The values of the individual voxel tiles in the +/// volume. The length of this array should +/// be ( 8 ^ 3 ) * tupleSize. +/// +/// @param[in] length +/// The length should be ( 8 ^ 3 ) * tupleSize. +/// +HAPI_DECL HAPI_SetVolumeTileIntData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const HAPI_VolumeTileInfo * tile, + const int * values_array, + int length ); + +/// @brief Set the values of a float voxel in the volume. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] x_index +/// The x index/coordinate of the voxel. +/// +/// @param[in] y_index +/// The y index/coordinate of the voxel. +/// +/// @param[in] z_index +/// The z index/coordinate of the voxel. +/// +/// @param[in] values_array +/// The values of the voxel. +/// +/// @param[in] value_count +/// Should be equal to the volume's +/// ::HAPI_VolumeInfo::tupleSize. +/// +/// +HAPI_DECL HAPI_SetVolumeVoxelFloatData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + int x_index, + int y_index, + int z_index, + const float * values_array, + int value_count ); + +/// @brief Set the values of a integer voxel in the volume. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] x_index +/// The x index/coordinate of the voxel. +/// +/// @param[in] y_index +/// The y index/coordinate of the voxel. +/// +/// @param[in] z_index +/// The z index/coordinate of the voxel. +/// +/// @param[in] values_array +/// The values of the voxel. +/// +/// @param[in] value_count +/// Should be equal to the volume's +/// ::HAPI_VolumeInfo::tupleSize. +/// +/// +HAPI_DECL HAPI_SetVolumeVoxelIntData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + int x_index, + int y_index, + int z_index, + const int * values_array, + int value_count ); + +/// @brief Get the bounding values of a volume. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[out] x_min +/// The minimum x value of the volume's bounding box. +/// Can be null if you do not want this value. +/// +/// @param[out] y_min +/// The minimum y value of the volume's bounding box. +/// Can be null if you do not want this value. +/// +/// @param[out] z_min +/// The minimum z value of the volume's bounding box. +/// Can be null if you do not want this value. +/// +/// @param[out] x_max +/// The maximum x value of the volume's bounding box. +/// Can be null if you do not want this value. +/// +/// @param[out] y_max +/// The maximum y value of the volume's bounding box. +/// Can be null if you do not want this value. +/// +/// @param[out] z_max +/// The maximum z value of the volume's bounding box. +/// Can be null if you do not want this value. +/// +/// @param[out] x_center +/// The x value of the volume's bounding box center. +/// Can be null if you do not want this value. +/// +/// @param[out] y_center +/// The y value of the volume's bounding box center. +/// Can be null if you do not want this value. +/// +/// @param[out] z_center +/// The z value of the volume's bounding box center. +/// Can be null if you do not want this value. +/// +HAPI_DECL HAPI_GetVolumeBounds( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + float * x_min, float * y_min, float * z_min, + float * x_max, float * y_max, float * z_max, + float * x_center, float * y_center, float * z_center ); + +/// @brief Set the height field data for a terrain volume with the values from +/// a flattened 2D array of float. +/// ::HAPI_SetVolumeInfo() should be called first to make sure that the +/// volume and its info are initialized. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] values_array +/// Heightfield flattened array. Should be at least the size of +/// @p start + @p length. +/// +/// @param[in] start +/// The start at least 0 and at most +/// ( ::HAPI_VolumeInfo::xLength * ::HAPI_VolumeInfo::yLength ) +/// - @p length. +/// +/// @param[in] length +/// The length should be at least 1 or at most +/// ( ::HAPI_VolumeInfo::xLength * ::HAPI_VolumeInfo::yLength ) +/// - @p start. +/// +/// @param[in] name +/// The name of the volume used for the heightfield. +/// If set to "height" the values will be used for height information, +/// if not, the data will used as a mask. +/// +HAPI_DECL HAPI_SetHeightFieldData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + const float * values_array, + int start, int length ); + +/// @brief Retrieve the visualization meta-data of the volume. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[out] visual_info +/// The meta-data associated with the visualization +/// settings of the part specified by the previous +/// parameters. +/// +HAPI_DECL HAPI_GetVolumeVisualInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_VolumeVisualInfo * visual_info ); + +// CURVES ------------------------------------------------------------------- + +/// @brief Retrieve any meta-data about the curves, including the +/// curve's type, order, and periodicity. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[out] info +/// The curve info represents the meta-data about +/// the curves, including the type, order, +/// and periodicity. +/// +HAPI_DECL HAPI_GetCurveInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + HAPI_CurveInfo * info ); + +/// @brief Retrieve the number of vertices for each curve in the part. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[out] counts_array +/// The number of cvs each curve contains +/// +/// @param[in] start +/// The index of the first curve. +/// +/// +/// @param[in] length +/// The number of curves' counts to retrieve. +/// +HAPI_DECL HAPI_GetCurveCounts( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + int * counts_array, + int start, int length ); + +/// @brief Retrieve the orders for each curve in the part if the +/// curve has varying order. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[out] orders_array +/// The order of each curve will be returned in this +/// array. +/// +/// @param[in] start +/// The index of the first curve. +/// +/// +/// @param[in] length +/// The number of curves' orders to retrieve. +/// +HAPI_DECL HAPI_GetCurveOrders( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + int * orders_array, + int start, int length ); + +/// @brief Retrieve the knots of the curves in this part. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[out] knots_array +/// The knots of each curve will be returned in this +/// array. +/// +/// @param[in] start +/// The index of the first curve. +/// +/// +/// @param[in] length +/// The number of curves' knots to retrieve. The +/// length of all the knots on a single curve is +/// the order of that curve plus the number of +/// vertices (see ::HAPI_GetCurveOrders(), +/// and ::HAPI_GetCurveCounts()). +/// +HAPI_DECL HAPI_GetCurveKnots( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + float * knots_array, + int start, int length ); + +/// @brief Set meta-data for the curve mesh, including the +/// curve type, order, and periodicity. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// Currently unused. Input asset geos are assumed +/// to have only one part. +/// +/// @param[in] info +/// The curve info represents the meta-data about +/// the curves, including the type, order, +/// and periodicity. +/// +HAPI_DECL HAPI_SetCurveInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const HAPI_CurveInfo * info ); + +/// @brief Set the number of vertices for each curve in the part. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// Currently unused. Input asset geos are assumed +/// to have only one part. +/// +/// @param[in] counts_array +/// The number of cvs each curve contains. +/// +/// @param[in] start +/// The index of the first curve. +/// +/// +/// @param[in] length +/// The number of curves' counts to set. +/// +/// +HAPI_DECL HAPI_SetCurveCounts( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const int * counts_array, + int start, int length ); + +/// @brief Set the orders for each curve in the part if the +/// curve has varying order. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// Currently unused. Input asset geos are assumed +/// to have only one part. +/// +/// @param[in] orders_array +/// The orders of each curve. +/// +/// @param[in] start +/// The index of the first curve. +/// +/// +/// @param[in] length +/// The number of curves' orders to retrieve. +/// +/// +HAPI_DECL HAPI_SetCurveOrders( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const int * orders_array, + int start, int length ); + +/// @brief Set the knots of the curves in this part. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// Currently unused. Input asset geos are assumed +/// to have only one part. +/// +/// @param[in] knots_array +/// The knots of each curve. +/// +/// @param[in] start +/// The index of the first curve. +/// +/// +/// @param[in] length +/// The number of curves' knots to set. The +/// length of all the knots on a single curve is +/// the order of that curve plus the number of +/// vertices (see ::HAPI_SetCurveOrders(), +/// and ::HAPI_SetCurveCounts()). +/// +HAPI_DECL HAPI_SetCurveKnots( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const float * knots_array, + int start, int length ); + +// BASIC PRIMITIVES --------------------------------------------------------- + +/// @brief Get the box info on a geo part (if the part is a box). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] geo_node_id +/// The geo node id. +/// +/// @param[in] part_id +/// The part id of the +/// +/// @param[out] box_info +/// The returned box info. +/// +HAPI_DECL HAPI_GetBoxInfo( const HAPI_Session * session, + HAPI_NodeId geo_node_id, + HAPI_PartId part_id, + HAPI_BoxInfo * box_info ); + +/// @brief Get the sphere info on a geo part (if the part is a sphere). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] geo_node_id +/// The geo node id. +/// +/// @param[in] part_id +/// The part id of the +/// +/// @param[out] sphere_info +/// The returned sphere info. +/// +HAPI_DECL HAPI_GetSphereInfo( const HAPI_Session * session, + HAPI_NodeId geo_node_id, + HAPI_PartId part_id, + HAPI_SphereInfo * sphere_info ); + +// CACHING ------------------------------------------------------------------ + +/// @brief Get the number of currently active caches. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] active_cache_count +/// The number of currently active caches. +/// +HAPI_DECL HAPI_GetActiveCacheCount( const HAPI_Session * session, + int * active_cache_count ); + +/// @brief Get the names of the currently active caches. +/// +/// Requires a valid active cache count which you get from: +/// ::HAPI_GetActiveCacheCount(). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] cache_names_array +/// String array with the returned cache names. Must be +/// at least the size of @a active_cache_count. +/// +/// @param[in] active_cache_count +/// The count returned by ::HAPI_GetActiveCacheCount(). +/// +/// +HAPI_DECL HAPI_GetActiveCacheNames( const HAPI_Session * session, + HAPI_StringHandle * cache_names_array, + int active_cache_count ); + +/// @brief Lets you inspect specific properties of the different memory +/// caches in the current Houdini context. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] cache_name +/// Cache name from ::HAPI_GetActiveCacheNames(). +/// +/// @param[in] cache_property +/// The specific property of the cache to get the value for. +/// +/// @param[out] property_value +/// Returned property value. +/// +HAPI_DECL HAPI_GetCacheProperty( const HAPI_Session * session, + const char * cache_name, + HAPI_CacheProperty cache_property, + int * property_value ); + +/// @brief Lets you modify specific properties of the different memory +/// caches in the current Houdini context. This includes clearing +/// caches, reducing their memory use, or changing how memory limits +/// are respected by a cache. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] cache_name +/// Cache name from ::HAPI_GetActiveCacheNames(). +/// +/// @param[in] cache_property +/// The specific property of the cache to modify. +/// +/// @param[in] property_value +/// The new property value. +/// +HAPI_DECL HAPI_SetCacheProperty( const HAPI_Session * session, + const char * cache_name, + HAPI_CacheProperty cache_property, + int property_value ); + +/// @brief Saves a geometry to file. The type of file to save is +/// to be determined by the extension ie. .bgeo, .obj +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] file_name +/// The name of the file to be saved. The extension +/// of the file determines its type. +/// +HAPI_DECL HAPI_SaveGeoToFile( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * file_name ); + +/// @brief Loads a geometry file and put its contents onto a SOP +/// node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] file_name +/// The name of the file to be loaded +/// +HAPI_DECL HAPI_LoadGeoFromFile( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * file_name ); + +/// @brief Saves the node and all its contents to file. +/// The saved file can be loaded by calling ::HAPI_LoadNodeFromFile. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] file_name +/// The name of the file to be saved. The extension +/// of the file determines its type. +/// +HAPI_DECL HAPI_SaveNodeToFile( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * file_name ); + +/// @brief Loads and creates a previously saved node and all +/// its contents from given file. +/// The saved file must have been created by calling +/// ::HAPI_SaveNodeToFile. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] file_name +/// The name of the file to be loaded +/// +/// @param[in] parent_node_id +/// The parent node id of the Geometry object. +/// +/// @param[in] node_label +/// The name of the new Geometry object. +/// +/// @param[in] cook_on_load +/// Set to true if you wish the nodes to cook as soon +/// as they are created. Otherwise, you will have to +/// call ::HAPI_CookNode() explicitly for each after you +/// call this function. +/// +/// @param[out] new_node_id +/// The newly created node id. +/// +HAPI_DECL HAPI_LoadNodeFromFile( const HAPI_Session * session, + const char * file_name, + HAPI_NodeId parent_node_id, + const char * node_label, + HAPI_Bool cook_on_load, + HAPI_NodeId * new_node_id ); + +/// @brief Cache the current state of the geo to memory, given the +/// format, and return the size. Use this size with your call +/// to ::HAPI_SaveGeoToMemory() to copy the cached geo to your +/// buffer. It is guaranteed that the size will not change between +/// your call to ::HAPI_GetGeoSize() and ::HAPI_SaveGeoToMemory(). +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] format +/// The file format, ie. ".obj", ".bgeo.sc" etc. +/// +/// @param[out] size +/// The size of the buffer required to hold the output. +/// +HAPI_DECL HAPI_GetGeoSize( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * format, + int * size ); + +/// @brief Saves the cached geometry to your buffer in memory, +/// whose format and required size is identified by the call to +/// ::HAPI_GetGeoSize(). The call to ::HAPI_GetGeoSize() is +/// required as ::HAPI_GetGeoSize() does the actual saving work. +/// +/// Also note that this call to ::HAPI_SaveGeoToMemory will delete +/// the internal geo buffer that was cached in the previous call +/// to ::HAPI_GetGeoSize(). This means that you will need to call +/// ::HAPI_GetGeoSize() again before you can call this function. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[out] buffer +/// The buffer we will write into. +/// +/// @param[in] length +/// The size of the buffer passed in. +/// +/// +HAPI_DECL HAPI_SaveGeoToMemory( const HAPI_Session * session, + HAPI_NodeId node_id, + char * buffer, + int length ); + +/// @brief Loads a geometry from memory and put its +/// contents onto a SOP node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] format +/// The file format, ie. "obj", "bgeo" etc. +/// +/// @param[in] buffer +/// The buffer we will read the geometry from. +/// +/// @param[in] length +/// The size of the buffer passed in. +/// +/// +HAPI_DECL HAPI_LoadGeoFromMemory( const HAPI_Session * session, + HAPI_NodeId node_id, + const char * format, + const char * buffer, + int length ); + +/// @brief Set the specified node's display flag. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] onOff +/// Display flag. +/// +HAPI_DECL HAPI_SetNodeDisplay( const HAPI_Session * session, + HAPI_NodeId node_id, + int onOff ); + +// SESSIONSYNC -------------------------------------------------------------- + +/// @brief Get the specified node's total cook count, including +/// its children, if specified. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] node_type_filter +/// The node type by which to filter the children. +/// +/// @param[in] node_flags_filter +/// The node flags by which to filter the children. +/// +/// @param[in] recursive +/// Whether or not to include the specified node's +/// children cook count in the tally. +/// +/// @param[out] count +/// The number of cooks in total for this session. +/// +HAPI_DECL HAPI_GetTotalCookCount( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_NodeTypeBits node_type_filter, + HAPI_NodeFlagsBits node_flags_filter, + HAPI_Bool recursive, + int * count ); + +/// @brief Enable or disable SessionSync mode. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] enable +/// Enable or disable SessionSync mode. +/// +HAPI_DECL HAPI_SetSessionSync( const HAPI_Session * session, + HAPI_Bool enable ); + +/// @brief Get the ::HAPI_Viewport info for synchronizing viewport in +/// SessionSync. When SessionSync is running this will +/// return Houdini's current viewport information. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// @param[out] viewport +/// The output ::HAPI_Viewport. +/// +HAPI_DECL HAPI_GetViewport( const HAPI_Session * session, + HAPI_Viewport * viewport ); + +/// @brief Set the ::HAPI_Viewport info for synchronizing viewport in +/// SessionSync. When SessionSync is running, this can be +/// used to set the viewport information which Houdini +/// will then synchronizse with for its viewport. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// @param[in] viewport +/// A ::HAPI_Viewport that stores the viewport. +/// +HAPI_DECL HAPI_SetViewport( const HAPI_Session * session, + const HAPI_Viewport * viewport ); + +/// @brief Get the ::HAPI_SessionSyncInfo for synchronizing SessionSync +/// state between Houdini and Houdini Engine integrations. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// @param[out] session_sync_info +/// The output ::HAPI_SessionSyncInfo. +/// +HAPI_DECL HAPI_GetSessionSyncInfo( + const HAPI_Session * session, + HAPI_SessionSyncInfo * session_sync_info ); + +/// @brief Set the ::HAPI_SessionSyncInfo for synchronizing SessionSync +/// state between Houdini and Houdini Engine integrations. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// @param[in] session_sync_info +/// A ::HAPI_SessionSyncInfo that stores the state. +/// +HAPI_DECL HAPI_SetSessionSyncInfo( + const HAPI_Session * session, + const HAPI_SessionSyncInfo * session_sync_info ); + +// PDG ---------------------------------------------------------------------- + +/// @brief Return an array of PDG graph context names and ids, the first +/// count names will be returned. These ids can be used +/// with ::HAPI_GetPDGEvents and ::HAPI_GetPDGState. The values +/// of the names can be retrieved with ::HAPI_GetString. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] num_contexts +/// Total number of PDG graph contexts found. +/// +/// @param[out] context_names_array +/// Array of int (string handles) to house the +/// context names. These handles are valid until the next +/// call to this function. +/// +/// @param[out] context_id_array +/// Array of graph context ids. +/// +/// @param[in] count +/// Length of @p context_names_array and @p context_id_array +/// +HAPI_DECL HAPI_GetPDGGraphContexts( const HAPI_Session * session, + int * num_contexts, + HAPI_StringHandle * context_names_array, + HAPI_PDG_GraphContextId * context_id_array, + int count ); + +// @brief Get the PDG graph context for the specified TOP node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] top_node_id +/// The id of the TOP node to query its graph context. +/// +/// @param[out] context_id +/// The PDG graph context id. +/// +HAPI_DECL HAPI_GetPDGGraphContextId( const HAPI_Session * session, + HAPI_NodeId top_node_id, + HAPI_PDG_GraphContextId * context_id ); + +// @brief Starts a PDG cooking operation. This can be asynchronous. +/// Progress can be checked with ::HAPI_GetPDGState() and +/// ::HAPI_GetPDGState(). Events generated during this cook can be +/// collected with ::HAPI_GetPDGEvents(). Any uncollected events will be +/// discarded at the start of the cook. +/// +/// If there are any $HIPFILE file dependencies on nodes involved in the cook +/// a hip file will be automatically saved to $HOUDINI_TEMP_DIR directory so +/// that it can be copied to the working directory by the scheduler. This means +/// $HIP will be equal to $HOUDINI_TEMP_DIR. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// @param[in] cook_node_id +/// The node id of a TOP node for the cook operation. +/// +/// @param[in] generate_only +/// 1 means only static graph generation will done. 0 means +/// a full graph cook. Generation is always blocking. +/// +/// @param[in] blocking +/// 0 means return immediately and cooking will be done +/// asynchronously. 1 means return when cooking completes. +/// +HAPI_DECL HAPI_CookPDG( const HAPI_Session * session, + HAPI_NodeId cook_node_id, + int generate_only, + int blocking ); + +// @brief Returns PDG events that have been collected. Calling this function +/// will remove those events from the queue. Events collection is restarted +/// by calls to ::HAPI_CookPDG(). +/// +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] graph_context_id +/// The id of the graph context +/// +/// @param[out] event_array +/// buffer of ::HAPI_PDG_EventInfo of size at least length. +/// +/// @param[in] length +/// The size of the buffer passed in. +/// +/// @param[out] event_count +/// Number of events removed from queue and copied to buffer. +/// +/// @param[out] remaining_events +/// Number of queued events remaining after this operation. +/// +HAPI_DECL HAPI_GetPDGEvents( const HAPI_Session * session, + HAPI_PDG_GraphContextId graph_context_id, + HAPI_PDG_EventInfo * event_array, + int length, + int * event_count, + int * remaining_events ); + +// @brief Gets the state of a PDG graph +/// +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] graph_context_id +/// The graph context id +/// +/// @param[out] pdg_state +/// One of ::HAPI_PDG_State. +/// +HAPI_DECL HAPI_GetPDGState( const HAPI_Session * session, + HAPI_PDG_GraphContextId graph_context_id, + int * pdg_state ); + + +// @brief Creates a new pending workitem for the given node. The workitem +/// will not be submitted to the graph until it is committed with +/// ::HAPI_CommitWorkitems(). The node is expected to be a generator type. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[out] workitem_id +/// The id of the pending workitem. +/// +/// @param[in] name +/// The null-terminated name of the workitem. The name will +/// be automatically suffixed to make it unique. +/// +/// @param[in] index +/// The index of the workitem. The semantics of the index +/// are user defined. +/// +HAPI_DECL HAPI_CreateWorkitem( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PDG_WorkitemId * workitem_id, + const char * name, + int index ); + +// @brief Retrieves the info of a given workitem by id. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] graph_context_id +/// The graph context that the workitem is in. +/// +/// @param[in] workitem_id +/// The id of the workitem. +/// +/// @param[out] workitem_info +/// The returned ::HAPI_PDG_WorkitemInfo for the workitem. Note +/// that the enclosed string handle is only valid until the next +/// call to this function. +/// +HAPI_DECL HAPI_GetWorkitemInfo( const HAPI_Session * session, + HAPI_PDG_GraphContextId graph_context_id, + HAPI_PDG_WorkitemId workitem_id, + HAPI_PDG_WorkitemInfo * workitem_info ); + +// @brief Adds integer data to a pending PDG workitem data member for the given node. +/// +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] workitem_id +/// The id of the pending workitem returned by ::HAPI_CreateWorkitem() +/// +/// @param[in] data_name +/// null-terminated name of the data member +/// +/// @param[in] values_array +/// array of integer values +/// +/// @param[in] length +/// number of values to copy from values_array to the parameter +/// +HAPI_DECL HAPI_SetWorkitemIntData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PDG_WorkitemId workitem_id, + const char * data_name, + const int * values_array, + int length ); + +// @brief Adds float data to a pending PDG workitem data member for the given node. +/// +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] workitem_id +/// The id of the pending workitem returned by ::HAPI_CreateWorkitem() +/// +/// @param[in] data_name +/// null-terminated name of the workitem data member +/// +/// @param[in] values_array +/// array of float values +/// +/// @param[in] length +/// number of values to copy from values_array to the parameter +/// +HAPI_DECL HAPI_SetWorkitemFloatData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PDG_WorkitemId workitem_id, + const char * data_name, + const float * values_array, + int length ); + +// @brief Adds integer data to a pending PDG workitem data member for the given node. +/// +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] workitem_id +/// The id of the created workitem returned by HAPI_CreateWorkitem() +/// +/// @param[in] data_name +/// null-terminated name of the data member +/// +/// @param[in] data_index +/// index of the string data member +/// +/// @param[in] value +/// null-terminated string to copy to the workitem data member +/// +HAPI_DECL HAPI_SetWorkitemStringData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PDG_WorkitemId workitem_id, + const char * data_name, + int data_index, + const char * value ); + +// @brief Commits any pending workitems. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id for which the pending workitems have been +/// created but not yet injected. +/// +HAPI_DECL HAPI_CommitWorkitems( const HAPI_Session * session, + HAPI_NodeId node_id ); + +// @brief Gets the number of workitems that are available on the given node. +/// Should be used with ::HAPI_GetWorkitems. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[out] num +/// The number of workitems. +/// +HAPI_DECL HAPI_GetNumWorkitems( const HAPI_Session * session, + HAPI_NodeId node_id, + int * num ); + +// @brief Gets the list of work item ids for the given node +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[out] workitem_ids_array +/// buffer for resulting array of ::HAPI_PDG_WorkitemId +/// +/// @param[in] length +/// The length of the @p workitem_ids buffer +/// +HAPI_DECL HAPI_GetWorkitems( const HAPI_Session * session, + HAPI_NodeId node_id, + int * workitem_ids_array, + int length ); + +// @brief Gets the length of the workitem data member. +/// It is the length of the array of data. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] workitem_id +/// The id of the workitem +/// +/// @param[in] data_name +/// null-terminated name of the data member +/// +/// @param[out] length +/// The length of the data member array +/// +HAPI_DECL HAPI_GetWorkitemDataLength( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PDG_WorkitemId workitem_id, + const char * data_name, + int * length ); + +// @brief Gets int data from a work item member. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] workitem_id +/// The id of the workitem +/// +/// @param[in] data_name +/// null-terminated name of the data member +/// +/// @param[out] data_array +/// buffer of at least size length to copy the data into. The required +/// length should be determined by ::HAPI_GetWorkitemDataLength(). +/// +/// @param[in] length +/// The length of @p data_array +/// +HAPI_DECL HAPI_GetWorkitemIntData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PDG_WorkitemId workitem_id, + const char * data_name, + int * data_array, + int length ); + +// @brief Gets float data from a work item member. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] workitem_id +/// The id of the workitem +/// +/// @param[in] data_name +/// null-terminated name of the data member +/// +/// @param[out] data_array +/// buffer of at least size length to copy the data into. The required +/// length should be determined by ::HAPI_GetWorkitemDataLength(). +/// +/// @param[in] length +/// The length of the @p data_array +/// +HAPI_DECL HAPI_GetWorkitemFloatData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PDG_WorkitemId workitem_id, + const char * data_name, + float * data_array, + int length ); + +// @brief Gets string ids from a work item member. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] workitem_id +/// The id of the workitem +/// +/// @param[in] data_name +/// null-terminated name of the data member +/// +/// @param[out] data_array +/// buffer of at least size length to copy the data into. The required +/// length should be determined by ::HAPI_GetWorkitemDataLength(). +/// The data is an array of ::HAPI_StringHandle which can be used with +/// ::HAPI_GetString(). The string handles are valid until the +/// next call to this function. +/// +/// @param[in] length +/// The length of @p data_array +/// +HAPI_DECL HAPI_GetWorkitemStringData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PDG_WorkitemId workitem_id, + const char * data_name, + HAPI_StringHandle * data_array, + int length ); + +/// @brief Gets the info for workitem results. +/// The number of workitem results is found on the ::HAPI_PDG_WorkitemInfo +/// returned by HAPI_GetWorkitemInfo. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] workitem_id +/// The id of the workitem +/// +/// @param[out] resultinfo_array +/// Buffer to fill with info structs. String handles are valid +/// until the next call of this function. +/// +/// @param[in] resultinfo_count +/// The length of @p resultinfo_array +/// +HAPI_DECL HAPI_GetWorkitemResultInfo( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PDG_WorkitemId workitem_id, + HAPI_PDG_WorkitemResultInfo * resultinfo_array, + int resultinfo_count ); + +// @brief Dirties the given node. Cancels the cook if necessary and then +/// deletes all workitems on the node. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] clean_results +/// Remove the results generated by the node. +/// +/// +HAPI_DECL HAPI_DirtyPDGNode( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_Bool clean_results ); + +// @brief Pause the PDG cooking operation. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] graph_context_id +/// The id of the graph context +/// +HAPI_DECL HAPI_PausePDGCook( const HAPI_Session * session, + HAPI_PDG_GraphContextId graph_context_id ); + +// @brief Cancel the PDG cooking operation. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] graph_context_id +/// The id of the graph context +/// +HAPI_DECL HAPI_CancelPDGCook( const HAPI_Session * session, + HAPI_PDG_GraphContextId graph_context_id ); + +#endif // __HAPI_h__ diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI_API.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI_API.h new file mode 100644 index 00000000..413f18b6 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI_API.h @@ -0,0 +1,128 @@ +/* + * PROPRIETARY INFORMATION. This software is proprietary to + * Side Effects Software Inc., and is not to be reproduced, + * transmitted, or disclosed in any way without written permission. + * + * COMMENTS: + */ + +#ifndef __HAPI_API_h__ +#define __HAPI_API_h__ + +// Helper macros. +#if defined( __GNUC__ ) + #define HAPI_IS_GCC_GE( MAJOR, MINOR ) \ + ( __GNUC__ > MAJOR || (__GNUC__ == MAJOR && __GNUC_MINOR__ >= MINOR) ) +#else + #define HAPI_IS_GCC_GE( MAJOR, MINOR ) 0 +#endif // defined( __GNUC__ ) +#define HAPI_TO_STRING_( a ) # a +#define HAPI_TO_STRING( a ) HAPI_TO_STRING_( a ) + +// Mark function as deprecated and may be removed in the future. +// @note This qualifier can only appear in function declarations. +#ifdef HAPI_SUPPRESS_DEPRECATIONS + #define HAPI_DEPRECATED( hapi_ver, houdini_ver ) + #define HAPI_DEPRECATED_REPLACE( hapi_ver, houdini_ver, replacement ) +#elif defined( GCC3 ) + #if HAPI_IS_GCC_GE( 4, 6 ) || defined( __clang__ ) + #define HAPI_DEPRECATED( hapi_ver, houdini_ver ) \ + __attribute__( ( deprecated( \ + "Deprecated since version HAPI " HAPI_TO_STRING( hapi_ver ) \ + ", Houdini " HAPI_TO_STRING( houdini_ver ) ) ) ) + #define HAPI_DEPRECATED_REPLACE( hapi_ver, houdini_ver, replacement ) \ + __attribute__( ( deprecated( \ + "Deprecated since version HAPI " HAPI_TO_STRING( hapi_ver ) \ + ", Houdini " HAPI_TO_STRING( houdini_ver ) ". Use " \ + HAPI_TO_STRING( replacement ) " instead." ) ) ) + #else + #define HAPI_DEPRECATED( hapi_ver, houdini_ver ) \ + __attribute__( ( deprecated ) ) + #define HAPI_DEPRECATED_REPLACE( hapi_ver, houdini_ver, replacement ) \ + __attribute__( ( deprecated ) ) + #endif +#elif defined( _MSC_VER ) + #define HAPI_DEPRECATED( hapi_ver, houdini_ver ) \ + __declspec( deprecated( \ + "Deprecated since version HAPI " HAPI_TO_STRING( hapi_ver ) \ + ", Houdini " HAPI_TO_STRING( houdini_ver ) ) ) + #define HAPI_DEPRECATED_REPLACE( hapi_ver, houdini_ver, replacement ) \ + __declspec( deprecated( \ + "Deprecated since version HAPI " HAPI_TO_STRING( hapi_ver ) \ + ", Houdini " HAPI_TO_STRING( houdini_ver ) ". Use " \ + HAPI_TO_STRING( replacement ) " instead." ) ) +#else + #define HAPI_DEPRECATED( hapi_ver, houdini_ver ) + #define HAPI_DEPRECATED_REPLACE( hapi_ver, houdini_ver, replacement ) +#endif + +#if defined( WIN32 ) && !defined( MAKING_STATIC ) + #define HAPI_VISIBILITY_EXPORT __declspec( dllexport ) + #define HAPI_VISIBILITY_IMPORT __declspec( dllimport ) + #define HAPI_VISIBILITY_EXPORT_TINST HAPI_VISIBILITY_EXPORT + #define HAPI_VISIBILITY_IMPORT_TINST HAPI_VISIBILITY_IMPORT +#elif defined(__GNUC__) && HAPI_IS_GCC_GE( 4, 2 ) && !defined( MAKING_STATIC ) + #define HAPI_VISIBILITY_EXPORT __attribute__( ( visibility( "default" ) ) ) + #define HAPI_VISIBILITY_IMPORT __attribute__( ( visibility( "default" ) ) ) + #define HAPI_VISIBILITY_EXPORT_TINST + #define HAPI_VISIBILITY_IMPORT_TINST +#else + #define HAPI_VISIBILITY_EXPORT + #define HAPI_VISIBILITY_IMPORT + #define HAPI_VISIBILITY_EXPORT_TINST + #define HAPI_VISIBILITY_IMPORT_TINST +#endif + +#ifdef HAPI_EXPORTS + #define HAPI_API HAPI_VISIBILITY_EXPORT +#else + #define HAPI_API HAPI_VISIBILITY_IMPORT +#endif // HAPI_EXPORTS + +#if defined( WIN32 ) + #define HAPI_CALL __cdecl +#else + #define HAPI_CALL +#endif // defined( WIN32 ) + +#ifdef __cplusplus + #define HAPI_EXTERN_C extern "C" +#else + #define HAPI_EXTERN_C +#endif // __cplusplus + +#define HAPI_RETURN HAPI_Result HAPI_CALL + +#define HAPI_DECL_RETURN( r ) HAPI_EXTERN_C HAPI_API r HAPI_CALL +#define HAPI_DECL_DEPRECATED( hapi_ver, houdini_ver ) \ + HAPI_EXTERN_C \ + HAPI_DEPRECATED( hapi_ver, houdini_ver ) \ + HAPI_API \ + HAPI_RETURN +#define HAPI_DECL_DEPRECATED_REPLACE( hapi_ver, houdini_ver, replacement ) \ + HAPI_EXTERN_C \ + HAPI_DEPRECATED_REPLACE( hapi_ver, houdini_ver, replacement ) \ + HAPI_API \ + HAPI_RETURN + +#define HAPI_DECL HAPI_EXTERN_C HAPI_API HAPI_RETURN +#define HAPI_DEF HAPI_EXTERN_C HAPI_RETURN + +// Static asserts +#ifdef __cplusplus +#define HAPI_STATIC_ASSERT( cond, msg ) \ + template< bool b > \ + struct HAPI_StaticAssert_##msg {}; \ + template<> \ + struct HAPI_StaticAssert_##msg< true > \ + { \ + typedef int static_assert_##msg; \ + }; \ + typedef HAPI_StaticAssert_##msg< ( cond ) >::static_assert_##msg _sa +#else + #define HAPI_STATIC_ASSERT( cond, msg ) HAPI_STATIC_ASSERT_I( cond, msg ) + #define HAPI_STATIC_ASSERT_I( cond, msg ) \ + typedef char static_assert_##msg[ 2 * ( cond ) - 1 ] +#endif // __cplusplus + +#endif // __HAPI_API_h__ diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI_Common.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI_Common.h new file mode 100644 index 00000000..8e78e4dd --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI_Common.h @@ -0,0 +1,1808 @@ +/* + * PROPRIETARY INFORMATION. This software is proprietary to + * Side Effects Software Inc., and is not to be reproduced, + * transmitted, or disclosed in any way without written permission. + * + */ + +#ifndef __HAPI_COMMON_h__ +#define __HAPI_COMMON_h__ + +#include "HAPI_API.h" + +///////////////////////////////////////////////////////////////////////////// +// Defines + +#define HAPI_POSITION_VECTOR_SIZE 3 +#define HAPI_SCALE_VECTOR_SIZE 3 +#define HAPI_SHEAR_VECTOR_SIZE 3 +#define HAPI_NORMAL_VECTOR_SIZE 3 +#define HAPI_QUATERNION_VECTOR_SIZE 4 +#define HAPI_EULER_VECTOR_SIZE 3 +#define HAPI_UV_VECTOR_SIZE 2 +#define HAPI_COLOR_VECTOR_SIZE 4 +#define HAPI_CV_VECTOR_SIZE 4 + +#define HAPI_PRIM_MIN_VERTEX_COUNT 1 +#define HAPI_PRIM_MAX_VERTEX_COUNT 16 + +#define HAPI_INVALID_PARM_ID -1 + +/// Common Default Attributes' Names +/// @{ +#define HAPI_ATTRIB_POSITION "P" +#define HAPI_ATTRIB_UV "uv" +#define HAPI_ATTRIB_UV2 "uv2" +#define HAPI_ATTRIB_NORMAL "N" +#define HAPI_ATTRIB_TANGENT "tangentu" +#define HAPI_ATTRIB_TANGENT2 "tangentv" +#define HAPI_ATTRIB_COLOR "Cd" +#define HAPI_ATTRIB_NAME "name" +#define HAPI_ATTRIB_INSTANCE "instance" +/// @} + +/// This is the name of the primitive group created from all the primitives +/// that are not in any user-defined group. This way, when you put all the +/// groups together you cover the entire mesh. This is important for some +/// clients where the mesh has to be defined in terms of submeshes that cover +/// the entire original mesh. +#define HAPI_UNGROUPED_GROUP_NAME "__ungrouped_group" + +/// Common image file format names (to use with the material extract APIs). +/// Note that you may still want to check if they are supported via +/// HAPI_GetSupportedImageFileFormats() since all formats are loaded +/// dynamically by Houdini on-demand so just because these formats are defined +/// here doesn't mean they are supported in your instance. +/// @{ +#define HAPI_RAW_FORMAT_NAME "HAPI_RAW" // HAPI-only Raw Format +#define HAPI_PNG_FORMAT_NAME "PNG" +#define HAPI_JPEG_FORMAT_NAME "JPEG" +#define HAPI_BMP_FORMAT_NAME "Bitmap" +#define HAPI_TIFF_FORMAT_NAME "TIFF" +#define HAPI_TGA_FORMAT_NAME "Targa" +/// @} + +/// Default image file format's name - used when the image generated and has +/// no "original" file format and the user does not specify a format to +/// convert to. +#define HAPI_DEFAULT_IMAGE_FORMAT_NAME HAPI_PNG_FORMAT_NAME + +/// Name of subnet OBJ node containing the global nodes. +#define HAPI_GLOBAL_NODES_NODE_NAME "GlobalNodes" + +/// Environment variables. +#define HAPI_ENV_HIP "HIP" +#define HAPI_ENV_JOB "JOB" +#define HAPI_ENV_CLIENT_NAME "HAPI_CLIENT_NAME" + +/// [HAPI_CACHE] +/// Common cache names. You can see these same cache names in the +/// Cache Manager window in Houdini (Windows > Cache Manager). +#define HAPI_CACHE_COP_COOK "COP Cook Cache" +#define HAPI_CACHE_COP_FLIPBOOK "COP Flipbook Cache" +#define HAPI_CACHE_IMAGE "Image Cache" +#define HAPI_CACHE_OBJ "Object Transform Cache" +#define HAPI_CACHE_GL_TEXTURE "OpenGL Texture Cache" +#define HAPI_CACHE_GL_VERTEX "OpenGL Vertex Cache" +#define HAPI_CACHE_SOP "SOP Cache" +#define HAPI_CACHE_VEX "VEX File Cache" +/// [HAPI_CACHE] + +// Make sure our enums and structs are usable without those keywords, as-is, +// in C. +#ifdef __cplusplus + #define HAPI_C_ENUM_TYPEDEF( enum_name ) + #define HAPI_C_STRUCT_TYPEDEF( struct_name ) +#else + #define HAPI_C_ENUM_TYPEDEF( enum_name ) \ + typedef enum enum_name enum_name; + #define HAPI_C_STRUCT_TYPEDEF( struct_name ) \ + typedef struct struct_name struct_name; +#endif // __cplusplus + +///////////////////////////////////////////////////////////////////////////// +// Typedefs + +// C has no bool. +#ifdef __cplusplus + typedef bool HAPI_Bool; +#else + typedef char HAPI_Bool; +#endif // __cplusplus + +// 64-bit Integers +typedef long long HAPI_Int64; +HAPI_STATIC_ASSERT( sizeof( HAPI_Int64 ) == 8, unsupported_size_of_long ); + +// The process id has to be uint on Windows and int on any other platform. +#if ( defined _WIN32 || defined WIN32 ) + typedef unsigned int HAPI_ProcessId; +#else + typedef int HAPI_ProcessId; +#endif + +/// Has to be 64-bit. +typedef HAPI_Int64 HAPI_SessionId; + +/// Use this with HAPI_GetString() to get the value. +/// See @ref HAPI_Fundamentals_Strings. +typedef int HAPI_StringHandle; + +typedef int HAPI_AssetLibraryId; + +/// See @ref HAPI_Nodes_Basics. +typedef int HAPI_NodeId; + +/// Either get this from the ::HAPI_ParmInfo or ::HAPI_GetParmIdFromName(). +/// See @ref HAPI_Parameters. +typedef int HAPI_ParmId; + +/// Use this with ::HAPI_GetPartInfo(). +/// See @ref HAPI_Parts. +typedef int HAPI_PartId; + +/// Use this with PDG functions +typedef int HAPI_PDG_WorkitemId; + +/// Use this with PDG functions +typedef int HAPI_PDG_GraphContextId; + +/// When we load a HIP file, we associate a HIP file ID with the created nodes +/// so that they can be looked up later +typedef int HAPI_HIPFileId; + +///////////////////////////////////////////////////////////////////////////// +// Enums + +enum HAPI_License +{ + HAPI_LICENSE_NONE, + HAPI_LICENSE_HOUDINI_ENGINE, + HAPI_LICENSE_HOUDINI, + HAPI_LICENSE_HOUDINI_FX, + HAPI_LICENSE_HOUDINI_ENGINE_INDIE, + HAPI_LICENSE_HOUDINI_INDIE, + HAPI_LICENSE_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_License ) + +enum HAPI_StatusType +{ + HAPI_STATUS_CALL_RESULT, + HAPI_STATUS_COOK_RESULT, + HAPI_STATUS_COOK_STATE, + HAPI_STATUS_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_StatusType ) + +enum HAPI_StatusVerbosity +{ + HAPI_STATUSVERBOSITY_0, + HAPI_STATUSVERBOSITY_1, + HAPI_STATUSVERBOSITY_2, + + HAPI_STATUSVERBOSITY_ALL = HAPI_STATUSVERBOSITY_2, + ///< Equivalent to ::HAPI_STATUSVERBOSITY_2. + + // Used for Results. + HAPI_STATUSVERBOSITY_ERRORS = HAPI_STATUSVERBOSITY_0, + ///< Equivalent to ::HAPI_STATUSVERBOSITY_0. + HAPI_STATUSVERBOSITY_WARNINGS = HAPI_STATUSVERBOSITY_1, + ///< Equivalent to ::HAPI_STATUSVERBOSITY_1. + HAPI_STATUSVERBOSITY_MESSAGES = HAPI_STATUSVERBOSITY_2, + ///< Equivalent to ::HAPI_STATUSVERBOSITY_2. +}; +HAPI_C_ENUM_TYPEDEF( HAPI_StatusVerbosity ) + +enum HAPI_Result +{ + HAPI_RESULT_SUCCESS = 0, + HAPI_RESULT_FAILURE = 1, + HAPI_RESULT_ALREADY_INITIALIZED = 2, + HAPI_RESULT_NOT_INITIALIZED = 3, + HAPI_RESULT_CANT_LOADFILE = 4, + HAPI_RESULT_PARM_SET_FAILED = 5, + HAPI_RESULT_INVALID_ARGUMENT = 6, + HAPI_RESULT_CANT_LOAD_GEO = 7, + HAPI_RESULT_CANT_GENERATE_PRESET = 8, + HAPI_RESULT_CANT_LOAD_PRESET = 9, + HAPI_RESULT_ASSET_DEF_ALREADY_LOADED = 10, + + HAPI_RESULT_NO_LICENSE_FOUND = 110, + HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND = 120, + HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE = 130, + HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE = 140, + HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE = 150, + HAPI_RESULT_DISALLOWED_HENGINEINDIE_W_3PARTY_PLUGIN = 160, + + HAPI_RESULT_ASSET_INVALID = 200, + HAPI_RESULT_NODE_INVALID = 210, + + HAPI_RESULT_USER_INTERRUPTED = 300, + + HAPI_RESULT_INVALID_SESSION = 400 +}; +HAPI_C_ENUM_TYPEDEF( HAPI_Result ) + +enum HAPI_ErrorCode +{ + HAPI_ERRORCODE_ASSET_DEF_NOT_FOUND = 1 << 0, + HAPI_ERRORCODE_PYTHON_NODE_ERROR = 1 << 1 +}; +HAPI_C_ENUM_TYPEDEF( HAPI_ErrorCode ) +typedef int HAPI_ErrorCodeBits; + +enum HAPI_SessionType +{ + HAPI_SESSION_INPROCESS, + HAPI_SESSION_THRIFT, + HAPI_SESSION_CUSTOM1, + HAPI_SESSION_CUSTOM2, + HAPI_SESSION_CUSTOM3, + HAPI_SESSION_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_SessionType ) + +enum HAPI_State +{ + HAPI_STATE_READY, ///< Everything cook successfully without errors. + HAPI_STATE_READY_WITH_FATAL_ERRORS, ///< You should abort the cook. + HAPI_STATE_READY_WITH_COOK_ERRORS, ///< Only some objects failed. + HAPI_STATE_STARTING_COOK, + HAPI_STATE_COOKING, + HAPI_STATE_STARTING_LOAD, + HAPI_STATE_LOADING, + HAPI_STATE_MAX, + + HAPI_STATE_MAX_READY_STATE = HAPI_STATE_READY_WITH_COOK_ERRORS +}; +HAPI_C_ENUM_TYPEDEF( HAPI_State ) + +enum HAPI_PackedPrimInstancingMode +{ + HAPI_PACKEDPRIM_INSTANCING_MODE_INVALID = -1, + HAPI_PACKEDPRIM_INSTANCING_MODE_DISABLED, + HAPI_PACKEDPRIM_INSTANCING_MODE_HIERARCHY, + HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT, + HAPI_PACKEDPRIM_INSTANCING_MODE_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_PackedPrimInstancingMode ) + +enum HAPI_Permissions +{ + HAPI_PERMISSIONS_NON_APPLICABLE, + HAPI_PERMISSIONS_READ_WRITE, + HAPI_PERMISSIONS_READ_ONLY, + HAPI_PERMISSIONS_WRITE_ONLY, + HAPI_PERMISSIONS_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_Permissions ) + +enum HAPI_RampType +{ + HAPI_RAMPTYPE_INVALID = -1, + HAPI_RAMPTYPE_FLOAT, + HAPI_RAMPTYPE_COLOR, + HAPI_RAMPTYPE_MAX, +}; +HAPI_C_ENUM_TYPEDEF( HAPI_RampType ) + +/// As you can see, some of these high level types share the same underlying +/// raw data type. For instance, both string and file parameter types can be +/// represented with strings, yet semantically they are different. We will +/// group high level parameter types that share an underlying raw data type +/// together, so you can always check the raw data type of a parameter based +/// on its high level data type by checking a range of values. +enum HAPI_ParmType +{ + HAPI_PARMTYPE_INT = 0, + HAPI_PARMTYPE_MULTIPARMLIST, + HAPI_PARMTYPE_TOGGLE, + HAPI_PARMTYPE_BUTTON, + + HAPI_PARMTYPE_FLOAT, + HAPI_PARMTYPE_COLOR, + + HAPI_PARMTYPE_STRING, + HAPI_PARMTYPE_PATH_FILE, + HAPI_PARMTYPE_PATH_FILE_GEO, + HAPI_PARMTYPE_PATH_FILE_IMAGE, + + HAPI_PARMTYPE_NODE, + + HAPI_PARMTYPE_FOLDERLIST, + HAPI_PARMTYPE_FOLDERLIST_RADIO, + + HAPI_PARMTYPE_FOLDER, + HAPI_PARMTYPE_LABEL, + HAPI_PARMTYPE_SEPARATOR, + HAPI_PARMTYPE_PATH_FILE_DIR, + + + // Helpers + + HAPI_PARMTYPE_MAX, ///< Total number of supported parameter types. + + HAPI_PARMTYPE_INT_START = HAPI_PARMTYPE_INT, + HAPI_PARMTYPE_INT_END = HAPI_PARMTYPE_BUTTON, + + HAPI_PARMTYPE_FLOAT_START = HAPI_PARMTYPE_FLOAT, + HAPI_PARMTYPE_FLOAT_END = HAPI_PARMTYPE_COLOR, + + HAPI_PARMTYPE_STRING_START = HAPI_PARMTYPE_STRING, + HAPI_PARMTYPE_STRING_END = HAPI_PARMTYPE_NODE, + + HAPI_PARMTYPE_PATH_START = HAPI_PARMTYPE_PATH_FILE, + HAPI_PARMTYPE_PATH_END = HAPI_PARMTYPE_PATH_FILE_IMAGE, + + HAPI_PARMTYPE_NODE_START = HAPI_PARMTYPE_NODE, + HAPI_PARMTYPE_NODE_END = HAPI_PARMTYPE_NODE, + + HAPI_PARMTYPE_CONTAINER_START = HAPI_PARMTYPE_FOLDERLIST, + HAPI_PARMTYPE_CONTAINER_END = HAPI_PARMTYPE_FOLDERLIST_RADIO, + + HAPI_PARMTYPE_NONVALUE_START = HAPI_PARMTYPE_FOLDER, + HAPI_PARMTYPE_NONVALUE_END = HAPI_PARMTYPE_SEPARATOR +}; +HAPI_C_ENUM_TYPEDEF( HAPI_ParmType ) + +/// Corresponds to the types as shown in the Houdini Type Properties +/// window and in DialogScript files. Available on HAPI_ParmInfo +/// See: Parameter types +/// +enum HAPI_PrmScriptType +{ + HAPI_PRM_SCRIPT_TYPE_INT = 0, ///< "int", "integer" + HAPI_PRM_SCRIPT_TYPE_FLOAT, + HAPI_PRM_SCRIPT_TYPE_ANGLE, + HAPI_PRM_SCRIPT_TYPE_STRING, + HAPI_PRM_SCRIPT_TYPE_FILE, + HAPI_PRM_SCRIPT_TYPE_DIRECTORY, + HAPI_PRM_SCRIPT_TYPE_IMAGE, + HAPI_PRM_SCRIPT_TYPE_GEOMETRY, + HAPI_PRM_SCRIPT_TYPE_TOGGLE, ///< "toggle", "embed" + HAPI_PRM_SCRIPT_TYPE_BUTTON, + HAPI_PRM_SCRIPT_TYPE_VECTOR2, + HAPI_PRM_SCRIPT_TYPE_VECTOR3, ///< "vector", "vector3" + HAPI_PRM_SCRIPT_TYPE_VECTOR4, + HAPI_PRM_SCRIPT_TYPE_INTVECTOR2, + HAPI_PRM_SCRIPT_TYPE_INTVECTOR3, ///< "intvector", "intvector3" + HAPI_PRM_SCRIPT_TYPE_INTVECTOR4, + HAPI_PRM_SCRIPT_TYPE_UV, + HAPI_PRM_SCRIPT_TYPE_UVW, + HAPI_PRM_SCRIPT_TYPE_DIR, ///< "dir", "direction" + HAPI_PRM_SCRIPT_TYPE_COLOR, ///< "color", "rgb" + HAPI_PRM_SCRIPT_TYPE_COLOR4, ///< "color4", "rgba" + HAPI_PRM_SCRIPT_TYPE_OPPATH, + HAPI_PRM_SCRIPT_TYPE_OPLIST, + HAPI_PRM_SCRIPT_TYPE_OBJECT, + HAPI_PRM_SCRIPT_TYPE_OBJECTLIST, + HAPI_PRM_SCRIPT_TYPE_RENDER, + HAPI_PRM_SCRIPT_TYPE_SEPARATOR, + HAPI_PRM_SCRIPT_TYPE_GEOMETRY_DATA, + HAPI_PRM_SCRIPT_TYPE_KEY_VALUE_DICT, + HAPI_PRM_SCRIPT_TYPE_LABEL, + HAPI_PRM_SCRIPT_TYPE_RGBAMASK, + HAPI_PRM_SCRIPT_TYPE_ORDINAL, + HAPI_PRM_SCRIPT_TYPE_RAMP_FLT, + HAPI_PRM_SCRIPT_TYPE_RAMP_RGB, + HAPI_PRM_SCRIPT_TYPE_FLOAT_LOG, + HAPI_PRM_SCRIPT_TYPE_INT_LOG, + HAPI_PRM_SCRIPT_TYPE_DATA, + HAPI_PRM_SCRIPT_TYPE_FLOAT_MINMAX, + HAPI_PRM_SCRIPT_TYPE_INT_MINMAX, + HAPI_PRM_SCRIPT_TYPE_INT_STARTEND, + HAPI_PRM_SCRIPT_TYPE_BUTTONSTRIP, + HAPI_PRM_SCRIPT_TYPE_ICONSTRIP, + + // The following apply to HAPI_PARMTYPE_FOLDER type parms + HAPI_PRM_SCRIPT_TYPE_GROUPRADIO = 1000, ///< Radio buttons Folder + HAPI_PRM_SCRIPT_TYPE_GROUPCOLLAPSIBLE, ///< Collapsible Folder + HAPI_PRM_SCRIPT_TYPE_GROUPSIMPLE, ///< Simple Folder + HAPI_PRM_SCRIPT_TYPE_GROUP ///< Tabs Folder +}; +HAPI_C_ENUM_TYPEDEF( HAPI_PrmScriptType ) + +enum HAPI_ChoiceListType +{ + HAPI_CHOICELISTTYPE_NONE, ///< Parameter is not a menu. + HAPI_CHOICELISTTYPE_NORMAL, ///< Menu Only, Single Selection + HAPI_CHOICELISTTYPE_MINI, ///< Mini Menu Only, Single Selection + HAPI_CHOICELISTTYPE_REPLACE, ///< Field + Single Selection Menu + HAPI_CHOICELISTTYPE_TOGGLE ///< Field + Multiple Selection Menu +}; +HAPI_C_ENUM_TYPEDEF( HAPI_ChoiceListType ) + +enum HAPI_PresetType +{ + HAPI_PRESETTYPE_INVALID = -1, + HAPI_PRESETTYPE_BINARY = 0, ///< Just the presets binary blob. + HAPI_PRESETTYPE_IDX, ///< Presets blob within an .idx file format. + HAPI_PRESETTYPE_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_PresetType ) + +enum HAPI_NodeType +{ + HAPI_NODETYPE_ANY = -1, + HAPI_NODETYPE_NONE = 0, + HAPI_NODETYPE_OBJ = 1 << 0, + HAPI_NODETYPE_SOP = 1 << 1, + HAPI_NODETYPE_CHOP = 1 << 2, + HAPI_NODETYPE_ROP = 1 << 3, + HAPI_NODETYPE_SHOP = 1 << 4, + HAPI_NODETYPE_COP = 1 << 5, + HAPI_NODETYPE_VOP = 1 << 6, + HAPI_NODETYPE_DOP = 1 << 7, + HAPI_NODETYPE_TOP = 1 << 8 +}; +HAPI_C_ENUM_TYPEDEF( HAPI_NodeType ) +typedef int HAPI_NodeTypeBits; + +/// Flags used to filter compositions of node lists. Flags marked +/// 'Recursive Flag' will exclude children whos parent does not +/// satisfy the flag, even if the children themselves satisfy the flag. +enum HAPI_NodeFlags +{ + HAPI_NODEFLAGS_ANY = -1, + HAPI_NODEFLAGS_NONE = 0, + HAPI_NODEFLAGS_DISPLAY = 1 << 0, ///< Recursive Flag + HAPI_NODEFLAGS_RENDER = 1 << 1, ///< Recursive Flag + HAPI_NODEFLAGS_TEMPLATED = 1 << 2, + HAPI_NODEFLAGS_LOCKED = 1 << 3, + HAPI_NODEFLAGS_EDITABLE = 1 << 4, + HAPI_NODEFLAGS_BYPASS = 1 << 5, + HAPI_NODEFLAGS_NETWORK = 1 << 6, + + /// OBJ Node Specific Flags + HAPI_NODEFLAGS_OBJ_GEOMETRY = 1 << 7, + HAPI_NODEFLAGS_OBJ_CAMERA = 1 << 8, + HAPI_NODEFLAGS_OBJ_LIGHT = 1 << 9, + HAPI_NODEFLAGS_OBJ_SUBNET = 1 << 10, + + /// SOP Node Specific Flags + HAPI_NODEFLAGS_SOP_CURVE = 1 << 11, ///< Looks for "curve". + HAPI_NODEFLAGS_SOP_GUIDE = 1 << 12, ///< Looks for Guide Geometry + + /// TOP Node Specific Flags + HAPI_NODEFLAGS_TOP_NONSCHEDULER = 1 << 13 /// All TOP nodes except schedulers + +}; +HAPI_C_ENUM_TYPEDEF( HAPI_NodeFlags ) +typedef int HAPI_NodeFlagsBits; + +enum HAPI_GroupType +{ + HAPI_GROUPTYPE_INVALID = -1, + HAPI_GROUPTYPE_POINT, + HAPI_GROUPTYPE_PRIM, + HAPI_GROUPTYPE_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_GroupType ) + +enum HAPI_AttributeOwner +{ + HAPI_ATTROWNER_INVALID = -1, + HAPI_ATTROWNER_VERTEX, + HAPI_ATTROWNER_POINT, + HAPI_ATTROWNER_PRIM, + HAPI_ATTROWNER_DETAIL, + HAPI_ATTROWNER_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_AttributeOwner ) + +enum HAPI_CurveType +{ + HAPI_CURVETYPE_INVALID = -1, + HAPI_CURVETYPE_LINEAR, + HAPI_CURVETYPE_NURBS, + HAPI_CURVETYPE_BEZIER, + HAPI_CURVETYPE_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_CurveType ) + +enum HAPI_VolumeType +{ + HAPI_VOLUMETYPE_INVALID = -1, + HAPI_VOLUMETYPE_HOUDINI, + HAPI_VOLUMETYPE_VDB, + HAPI_VOLUMETYPE_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_VolumeType ) + +enum HAPI_VolumeVisualType +{ + HAPI_VOLUMEVISTYPE_INVALID = -1, + HAPI_VOLUMEVISTYPE_SMOKE, + HAPI_VOLUMEVISTYPE_RAINBOW, + HAPI_VOLUMEVISTYPE_ISO, + HAPI_VOLUMEVISTYPE_INVISIBLE, + HAPI_VOLUMEVISTYPE_HEIGHTFIELD, + HAPI_VOLUMEVISTYPE_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_VolumeVisualType ) + +enum HAPI_StorageType +{ + HAPI_STORAGETYPE_INVALID = -1, + HAPI_STORAGETYPE_INT, + HAPI_STORAGETYPE_INT64, + HAPI_STORAGETYPE_FLOAT, + HAPI_STORAGETYPE_FLOAT64, + HAPI_STORAGETYPE_STRING, + HAPI_STORAGETYPE_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_StorageType ) + +enum HAPI_AttributeTypeInfo +{ + HAPI_ATTRIBUTE_TYPE_INVALID = -1, + HAPI_ATTRIBUTE_TYPE_NONE, // Implicit type based on data + HAPI_ATTRIBUTE_TYPE_POINT, // Position + HAPI_ATTRIBUTE_TYPE_HPOINT, // Homogeneous position + HAPI_ATTRIBUTE_TYPE_VECTOR, // Direction vector + HAPI_ATTRIBUTE_TYPE_NORMAL, // Normal + HAPI_ATTRIBUTE_TYPE_COLOR, // Color + HAPI_ATTRIBUTE_TYPE_QUATERNION, // Quaternion + HAPI_ATTRIBUTE_TYPE_MATRIX3, // 3x3 Matrix + HAPI_ATTRIBUTE_TYPE_MATRIX, // Matrix + HAPI_ATTRIBUTE_TYPE_ST, // Parametric interval + HAPI_ATTRIBUTE_TYPE_HIDDEN, // "Private" (hidden) + HAPI_ATTRIBUTE_TYPE_BOX2, // 2x2 Bounding box + HAPI_ATTRIBUTE_TYPE_BOX, // 3x3 Bounding box + HAPI_ATTRIBUTE_TYPE_TEXTURE, // Texture coordinate + HAPI_ATTRIBUTE_TYPE_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_AttributeTypeInfo ) + +enum HAPI_GeoType +{ + HAPI_GEOTYPE_INVALID = -1, + + /// Most geos will be of this type which essentially means a geo + /// not of the other types. + HAPI_GEOTYPE_DEFAULT, + + /// An exposed edit node. + /// See @ref HAPI_IntermediateAssetsResults. + HAPI_GEOTYPE_INTERMEDIATE, + + /// An input geo that can accept geometry from the host. + /// See @ref HAPI_AssetInputs_MarshallingGeometryIntoHoudini. + HAPI_GEOTYPE_INPUT, + + /// A curve. + /// See @ref HAPI_Curves. + HAPI_GEOTYPE_CURVE, + + HAPI_GEOTYPE_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_GeoType ) + +enum HAPI_PartType +{ + HAPI_PARTTYPE_INVALID = -1, + HAPI_PARTTYPE_MESH, + HAPI_PARTTYPE_CURVE, + HAPI_PARTTYPE_VOLUME, + HAPI_PARTTYPE_INSTANCER, + HAPI_PARTTYPE_BOX, + HAPI_PARTTYPE_SPHERE, + HAPI_PARTTYPE_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_PartType ) + +enum HAPI_InputType +{ + HAPI_INPUT_INVALID = -1, + HAPI_INPUT_TRANSFORM, + HAPI_INPUT_GEOMETRY, + HAPI_INPUT_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_InputType ) + +enum HAPI_CurveOrders +{ + HAPI_CURVE_ORDER_VARYING = 0, + HAPI_CURVE_ORDER_INVALID = 1, + HAPI_CURVE_ORDER_LINEAR = 2, + HAPI_CURVE_ORDER_QUADRATIC = 3, + HAPI_CURVE_ORDER_CUBIC = 4, +}; +HAPI_C_ENUM_TYPEDEF( HAPI_CurveOrders ) + +enum HAPI_TransformComponent +{ + HAPI_TRANSFORM_TX = 0, + HAPI_TRANSFORM_TY, + HAPI_TRANSFORM_TZ, + HAPI_TRANSFORM_RX, + HAPI_TRANSFORM_RY, + HAPI_TRANSFORM_RZ, + HAPI_TRANSFORM_QX, + HAPI_TRANSFORM_QY, + HAPI_TRANSFORM_QZ, + HAPI_TRANSFORM_QW, + HAPI_TRANSFORM_SX, + HAPI_TRANSFORM_SY, + HAPI_TRANSFORM_SZ +}; +HAPI_C_ENUM_TYPEDEF( HAPI_TransformComponent ) + +enum HAPI_RSTOrder +{ + HAPI_TRS = 0, + HAPI_TSR, + HAPI_RTS, + HAPI_RST, + HAPI_STR, + HAPI_SRT, + + HAPI_RSTORDER_DEFAULT = HAPI_SRT +}; +HAPI_C_ENUM_TYPEDEF( HAPI_RSTOrder ) + +enum HAPI_XYZOrder +{ + HAPI_XYZ = 0, + HAPI_XZY, + HAPI_YXZ, + HAPI_YZX, + HAPI_ZXY, + HAPI_ZYX, + + HAPI_XYZORDER_DEFAULT = HAPI_XYZ +}; +HAPI_C_ENUM_TYPEDEF( HAPI_XYZOrder ) + +enum HAPI_ImageDataFormat +{ + HAPI_IMAGE_DATA_UNKNOWN = -1, + HAPI_IMAGE_DATA_INT8, + HAPI_IMAGE_DATA_INT16, + HAPI_IMAGE_DATA_INT32, + HAPI_IMAGE_DATA_FLOAT16, + HAPI_IMAGE_DATA_FLOAT32, + HAPI_IMAGE_DATA_MAX, + + HAPI_IMAGE_DATA_DEFAULT = HAPI_IMAGE_DATA_INT8 +}; +HAPI_C_ENUM_TYPEDEF( HAPI_ImageDataFormat ) + +enum HAPI_ImagePacking +{ + HAPI_IMAGE_PACKING_UNKNOWN = -1, + HAPI_IMAGE_PACKING_SINGLE, ///< Single Channel + HAPI_IMAGE_PACKING_DUAL, ///< Dual Channel + HAPI_IMAGE_PACKING_RGB, ///< RGB + HAPI_IMAGE_PACKING_BGR, ///< RGB Reversed + HAPI_IMAGE_PACKING_RGBA, ///< RGBA + HAPI_IMAGE_PACKING_ABGR, ///< RGBA Reversed + HAPI_IMAGE_PACKING_MAX, + + HAPI_IMAGE_PACKING_DEFAULT3 = HAPI_IMAGE_PACKING_RGB, + HAPI_IMAGE_PACKING_DEFAULT4 = HAPI_IMAGE_PACKING_RGBA +}; +HAPI_C_ENUM_TYPEDEF( HAPI_ImagePacking ) + +/// This enum is to be used with ::HAPI_GetEnvInt() to retrieve basic +/// information about the HAPI implementation currently being linked +/// against. Note that as of HAPI version 2.0, these enum values are +/// guaranteed never to change so you can reliably get this information from +/// any post-2.0 version of HAPI. The same goes for the actual +/// ::HAPI_GetEnvInt() API call. +enum HAPI_EnvIntType +{ + HAPI_ENVINT_INVALID = -1, + + /// The three components of the Houdini version that HAPI is + /// expecting to link against. + /// @{ + HAPI_ENVINT_VERSION_HOUDINI_MAJOR = 100, + HAPI_ENVINT_VERSION_HOUDINI_MINOR = 110, + HAPI_ENVINT_VERSION_HOUDINI_BUILD = 120, + HAPI_ENVINT_VERSION_HOUDINI_PATCH = 130, + /// @} + + /// The two components of the Houdini Engine (marketed) version. + /// @{ + HAPI_ENVINT_VERSION_HOUDINI_ENGINE_MAJOR = 200, + HAPI_ENVINT_VERSION_HOUDINI_ENGINE_MINOR = 210, + /// @} + + /// This is a monotonously increasing API version number that can be used + /// to lock against a certain API for compatibility purposes. Basically, + /// when this number changes code compiled against the HAPI.h methods + /// might no longer compile. Semantic changes to the methods will also + /// cause this version to increase. This number will be reset to 0 + /// every time the Houdini Engine version is bumped. + HAPI_ENVINT_VERSION_HOUDINI_ENGINE_API = 220, + + HAPI_ENVINT_MAX, +}; +HAPI_C_ENUM_TYPEDEF( HAPI_EnvIntType ) + +/// This enum is to be used with ::HAPI_GetSessionEnvInt() to retrieve basic +/// session-specific information. +enum HAPI_SessionEnvIntType +{ + HAPI_SESSIONENVINT_INVALID = -1, + + /// License Type. See ::HAPI_License. + HAPI_SESSIONENVINT_LICENSE = 100, + + HAPI_SESSIONENVINT_MAX +}; +HAPI_C_ENUM_TYPEDEF( HAPI_SessionEnvIntType ) + +/// [HAPI_CacheProperty] +enum HAPI_CacheProperty +{ + /// Current memory usage in MB. Setting this to 0 invokes + /// a cache clear. + HAPI_CACHEPROP_CURRENT, + + HAPI_CACHEPROP_HAS_MIN, ///< True if it actually has a minimum size. + HAPI_CACHEPROP_MIN, ///< Min cache memory limit in MB. + HAPI_CACHEPROP_HAS_MAX, ///< True if it actually has a maximum size. + HAPI_CACHEPROP_MAX, ///< Max cache memory limit in MB. + + /// How aggressive to cull memory. This only works for: + /// - ::HAPI_CACHE_COP_COOK where: + /// 0 -> Never reduce inactive cache. + /// 1 -> Always reduce inactive cache. + /// - ::HAPI_CACHE_OBJ where: + /// 0 -> Never enforce the max memory limit. + /// 1 -> Always enforce the max memory limit. + /// - ::HAPI_CACHE_SOP where: + /// 0 -> When to Unload = Never + /// When to Limit Max Memory = Never + /// 1-2 -> When to Unload = Based on Flag + /// When to Limit Max Memory = Never + /// 3-4 -> When to Unload = Based on Flag + /// When to Limit Max Memory = Always + /// 5 -> When to Unload = Always + /// When to Limit Max Memory = Always + HAPI_CACHEPROP_CULL_LEVEL, +}; + +HAPI_C_ENUM_TYPEDEF( HAPI_CacheProperty ) + +/// Type of sampling for heightfield +enum HAPI_HeightFieldSampling +{ + HAPI_HEIGHTFIELD_SAMPLING_CENTER, + HAPI_HEIGHTFIELD_SAMPLING_CORNER +}; +HAPI_C_ENUM_TYPEDEF( HAPI_HeightFieldSampling ) + +/// Used with PDG functions +enum HAPI_PDG_State +{ + HAPI_PDG_STATE_READY, + HAPI_PDG_STATE_COOKING, + HAPI_PDG_STATE_MAX, + + HAPI_PDG_STATE_MAX_READY_STATE = HAPI_PDG_STATE_READY +}; +HAPI_C_ENUM_TYPEDEF( HAPI_PDG_State ) + +/// Used with PDG functions +enum HAPI_PDG_EventType +{ + HAPI_PDG_EVENT_NULL, + + HAPI_PDG_EVENT_WORKITEM_ADD, + HAPI_PDG_EVENT_WORKITEM_REMOVE, + HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE, + + HAPI_PDG_EVENT_WORKITEM_ADD_DEP, + HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP, + + HAPI_PDG_EVENT_WORKITEM_ADD_PARENT, + HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT, + + HAPI_PDG_EVENT_NODE_CLEAR, + + HAPI_PDG_EVENT_COOK_ERROR, + HAPI_PDG_EVENT_COOK_WARNING, + + HAPI_PDG_EVENT_COOK_COMPLETE, + + HAPI_PDG_EVENT_DIRTY_START, + HAPI_PDG_EVENT_DIRTY_STOP, + + HAPI_PDG_EVENT_DIRTY_ALL, + + HAPI_PDG_EVENT_UI_SELECT, + + HAPI_PDG_EVENT_NODE_CREATE, + HAPI_PDG_EVENT_NODE_REMOVE, + HAPI_PDG_EVENT_NODE_RENAME, + HAPI_PDG_EVENT_NODE_CONNECT, + HAPI_PDG_EVENT_NODE_DISCONNECT, + + HAPI_PDG_EVENT_WORKITEM_SET_INT, + HAPI_PDG_EVENT_WORKITEM_SET_FLOAT, + HAPI_PDG_EVENT_WORKITEM_SET_STRING, + HAPI_PDG_EVENT_WORKITEM_SET_FILE, + HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT, + HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY, + HAPI_PDG_EVENT_WORKITEM_MERGE, + HAPI_PDG_EVENT_WORKITEM_RESULT, + + HAPI_PDG_EVENT_WORKITEM_PRIORITY, + + HAPI_PDG_EVENT_COOK_START, + + HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR, + HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR, + + HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE, + + HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED, + + HAPI_PDG_EVENT_ALL, + HAPI_PDG_EVENT_LOG, + + HAPI_PDG_EVENT_SCHEDULER_ADDED, + HAPI_PDG_EVENT_SCHEDULER_REMOVED, + HAPI_PDG_EVENT_SET_SCHEDULER, + + HAPI_PDG_EVENT_SERVICE_MANAGER_ALL, + + HAPI_PDG_CONTEXT_EVENTS +}; +HAPI_C_ENUM_TYPEDEF( HAPI_PDG_EventType ) + +/// Used with PDG functions +enum HAPI_PDG_WorkitemState +{ + HAPI_PDG_WORKITEM_UNDEFINED, + HAPI_PDG_WORKITEM_UNCOOKED, + HAPI_PDG_WORKITEM_WAITING, + HAPI_PDG_WORKITEM_SCHEDULED, + HAPI_PDG_WORKITEM_COOKING, + HAPI_PDG_WORKITEM_COOKED_SUCCESS, + HAPI_PDG_WORKITEM_COOKED_CACHE, + HAPI_PDG_WORKITEM_COOKED_FAIL, + HAPI_PDG_WORKITEM_COOKED_CANCEL, + HAPI_PDG_WORKITEM_DIRTY +}; +HAPI_C_ENUM_TYPEDEF( HAPI_PDG_WorkitemState ) + +///////////////////////////////////////////////////////////////////////////// +// Main API Structs + +// GENERICS ----------------------------------------------------------------- + +struct HAPI_API HAPI_Transform +{ + float position[ HAPI_POSITION_VECTOR_SIZE ]; + float rotationQuaternion[ HAPI_QUATERNION_VECTOR_SIZE ]; + float scale[ HAPI_SCALE_VECTOR_SIZE ]; + float shear[ HAPI_SHEAR_VECTOR_SIZE ]; + + HAPI_RSTOrder rstOrder; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_Transform ) + +struct HAPI_API HAPI_TransformEuler +{ + float position[ HAPI_POSITION_VECTOR_SIZE ]; + float rotationEuler[ HAPI_EULER_VECTOR_SIZE ]; + float scale[ HAPI_SCALE_VECTOR_SIZE ]; + float shear[ HAPI_SHEAR_VECTOR_SIZE ]; + + HAPI_XYZOrder rotationOrder; + HAPI_RSTOrder rstOrder; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_TransformEuler ) + +// SESSIONS ----------------------------------------------------------------- + +struct HAPI_API HAPI_Session +{ + /// The type of session determines the which implementation will be + /// used to communicate with the Houdini Engine library. + HAPI_SessionType type; + + /// Some session types support multiple simultaneous sessions. This means + /// that each session needs to have a unique identifier. + HAPI_SessionId id; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_Session ) + +/// Options to configure a Thrift server being started from HARC. +struct HAPI_API HAPI_ThriftServerOptions +{ + /// Close the server automatically when all clients disconnect from it. + HAPI_Bool autoClose; + + /// Timeout in milliseconds for waiting on the server to + /// signal that it's ready to serve. If the server fails + /// to signal within this time interval, the start server call fails + /// and the server process is terminated. + float timeoutMs; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_ThriftServerOptions ) + +// TIME --------------------------------------------------------------------- + +struct HAPI_API HAPI_TimelineOptions +{ + float fps; + + float startTime; + float endTime; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_TimelineOptions ) + +// ASSETS ------------------------------------------------------------------- + +struct HAPI_API HAPI_AssetInfo +{ + /// Use the node id to get the asset's parameters. + /// See @ref HAPI_Nodes_Basics. + HAPI_NodeId nodeId; + + /// The objectNodeId differs from the regular nodeId in that for + /// geometry based assets (SOPs) it will be the node id of the dummy + /// object (OBJ) node instead of the asset node. For object based assets + /// the objectNodeId will equal the nodeId. The reason the distinction + /// exists is because transforms are always stored on the object node + /// but the asset parameters may not be on the asset node if the asset + /// is a geometry asset so we need both. + HAPI_NodeId objectNodeId; + + /// It's possible to instantiate an asset without cooking it. + /// See @ref HAPI_Assets_Cooking. + HAPI_Bool hasEverCooked; + + HAPI_StringHandle nameSH; ///< Instance name (the label + a number). + HAPI_StringHandle labelSH; ///< This is what any end user should be shown. + HAPI_StringHandle filePathSH; ///< Path to the .otl library file. + HAPI_StringHandle versionSH; ///< User-defined asset version. + HAPI_StringHandle fullOpNameSH; ///< Full asset name and namespace. + HAPI_StringHandle helpTextSH; ///< Asset help marked-up text. + HAPI_StringHandle helpURLSH; ///< Asset help URL. + + int objectCount; ///< See @ref HAPI_Objects. + int handleCount; ///< See @ref HAPI_Handles. + + /// Transform inputs exposed by the asset. For OBJ assets this is the + /// number of transform inputs on the OBJ node. For SOP assets, this is + /// the singular transform input on the dummy wrapper OBJ node. + /// See @ref HAPI_AssetInputs. + int transformInputCount; + + /// Geometry inputs exposed by the asset. For SOP assets this is + /// the number of geometry inputs on the SOP node itself. OBJ assets + /// will always have zero geometry inputs. + /// See @ref HAPI_AssetInputs. + int geoInputCount; + + /// Geometry outputs exposed by the asset. For SOP assets this is + /// the number of geometry outputs on the SOP node itself. OBJ assets + /// will always have zero geometry outputs. + /// See @ref HAPI_AssetInputs. + int geoOutputCount; + + /// For incremental updates. Indicates whether any of the assets's + /// objects have changed. Refreshed only during an asset cook. + HAPI_Bool haveObjectsChanged; + + /// For incremental updates. Indicates whether any of the asset's + /// materials have changed. Refreshed only during an asset cook. + HAPI_Bool haveMaterialsChanged; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_AssetInfo ) + +struct HAPI_API HAPI_CookOptions +{ + /// Normally, geos are split into parts in two different ways. First it + /// is split by group and within each group it is split by primitive type. + /// + /// For example, if you have a geo with group1 covering half of the mesh + /// and volume1 and group2 covering the other half of the mesh, all of + /// curve1, and volume2 you will end up with 5 parts. First two parts + /// will be for the half-mesh of group1 and volume1, and the last three + /// will cover group2. + /// + /// This toggle lets you disable the splitting by group and just have + /// the geo be split by primitive type alone. By default, this is true + /// and therefore geos will be split by group and primitive type. If + /// set to false, geos will only be split by primitive type. + HAPI_Bool splitGeosByGroup; + + /// This toggle lets you enable the splitting by unique values + /// of a specified attribute. By default, this is false and + /// the geo be split as described above. + /// as described above. If this is set to true, and splitGeosByGroup + /// set to false, mesh geos will be split on attribute values + /// The attribute name to split on must be created with HAPI_SetCustomString + /// and then the splitAttrSH handle set on the struct. + HAPI_Bool splitGeosByAttribute; + HAPI_StringHandle splitAttrSH; + + /// For meshes only, this is enforced by convexing the mesh. Use -1 + /// to avoid convexing at all and get some performance boost. + int maxVerticesPerPrimitive; + + /// For curves only. + /// If this is set to true, then all curves will be refined to a linear + /// curve and you can no longer access the original CVs. You can control + /// the refinement detail via ::HAPI_CookOptions::curveRefineLOD. + /// If it's false, the curve type (NURBS, Bezier etc) will be left as is. + HAPI_Bool refineCurveToLinear; + + /// Controls the number of divisions per unit distance when refining + /// a curve to linear. The default in Houdini is 8.0. + float curveRefineLOD; + + /// If this option is turned on, then we will recursively clear the + /// errors and warnings (and messages) of all nodes before performing + /// the cook. + HAPI_Bool clearErrorsAndWarnings; + + /// Decide whether to recursively cook all templated geos or not. + HAPI_Bool cookTemplatedGeos; + + /// Decide whether to split points by vertex attributes. This takes + /// all vertex attributes and tries to copy them to their respective + /// points. If two vertices have any difference in their attribute values, + /// the corresponding point is split into two points. This is repeated + /// until all the vertex attributes have been copied to the points. + /// + /// With this option enabled, you can reduce the total number of vertices + /// on a game engine side as sharing of attributes (like UVs) is optimized. + /// To make full use of this feature, you have to think of Houdini points + /// as game engine vertices (sharable). With this option OFF (or before + /// this feature existed) you had to map Houdini vertices to game engine + /// vertices, to make sure all attribute values are accounted for. + HAPI_Bool splitPointsByVertexAttributes; + + /// Choose how you want the cook to handle packed primitives. + /// The default is: ::HAPI_PACKEDPRIM_INSTANCING_MODE_DISABLED + HAPI_PackedPrimInstancingMode packedPrimInstancingMode; + + /// Choose which special part types should be handled. Unhandled special + /// part types will just be refined to ::HAPI_PARTTYPE_MESH. + HAPI_Bool handleBoxPartTypes; + HAPI_Bool handleSpherePartTypes; + + /// If enabled, sets the ::HAPI_PartInfo::hasChanged member during the + /// cook. If disabled, the member will always be true. Checking for + /// part changes can be expensive, so there is a potential performance + /// gain when disabled. + HAPI_Bool checkPartChanges; + + /// For internal use only. :) + int extraFlags; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_CookOptions ) + +// NODES -------------------------------------------------------------------- + +struct HAPI_API HAPI_NodeInfo +{ + HAPI_NodeId id; + HAPI_NodeId parentId; + HAPI_StringHandle nameSH; + HAPI_NodeType type; + + /// Always true unless the asset's definition has changed due to loading + /// a duplicate asset definition and from another OTL asset library + /// file OR deleting the OTL asset library file used by this node's asset. + HAPI_Bool isValid; + + /// Total number of cooks of this node. + int totalCookCount; + + /// Use this unique id to grab the OP_Node pointer for this node. + /// If you're linking against the C++ HDK, include the OP_Node.h header + /// and call OP_Node::lookupNode(). + int uniqueHoudiniNodeId; + + /// This is the internal node path in the Houdini scene graph. This path + /// is meant to be abstracted away for most client purposes but for + /// advanced uses it can come in handy. + HAPI_StringHandle internalNodePathSH; + + /// Total number of parameters this asset has exposed. Includes hidden + /// parameters. + /// See @ref HAPI_Parameters. + int parmCount; + + /// Number of values. A single parameter may have more than one value so + /// this number is more than or equal to ::HAPI_NodeInfo::parmCount. + /// @{ + int parmIntValueCount; + int parmFloatValueCount; + int parmStringValueCount; + /// @} + + /// The total number of choices among all the combo box parameters. + /// See @ref HAPI_Parameters_ChoiceLists. + int parmChoiceCount; + + /// The number of child nodes. This is 0 for all nodes that are not + /// node networks. + int childNodeCount; + + /// The number of inputs this specific node has. + int inputCount; + + /// The number of outputs this specific node has. + int outputCount; + + /// Nodes created via scripts or via ::HAPI_CreateNode() will be have + /// this set to true. Only such nodes can be deleted using + /// ::HAPI_DeleteNode(). + HAPI_Bool createdPostAssetLoad; + + /// Indicates if this node will change over time + HAPI_Bool isTimeDependent; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_NodeInfo ) + +// PARAMETERS --------------------------------------------------------------- + +/// @struct HAPI_ParmInfo +/// +/// Contains parameter information like name, label, type, and size. +/// +struct HAPI_API HAPI_ParmInfo +{ + /// The parent id points to the id of the parent parm + /// of this parm. The parent parm is something like a folder. + HAPI_ParmId id; + + /// Parameter id of the parent of this parameter. + HAPI_ParmId parentId; + + /// Child index within its immediate parent parameter. + int childIndex; + + /// The HAPI type of the parm + HAPI_ParmType type; + + /// The Houdini script-type of the parm + HAPI_PrmScriptType scriptType; + + /// Some parameter types require additional type information. + /// - File path parameters will indicate what file extensions they + /// expect in a space-separated list of wild-cards. This is set + /// in the Operator Type Properties using the File Pattern + /// parameter property. + /// For example, for filtering by PNG and JPG only: "*.png *.jpg" + HAPI_StringHandle typeInfoSH; + + /// For the majority of parameter types permission will not be applicable. + /// For file path parameters these permissions will indicate how the + /// asset plans to use the file: whether it will only read it, only write + /// to it, or both. This is set in the Operator Type Properties using + /// the Browse Mode parameter property. + HAPI_Permissions permissions; + + /// Number of tags on this paramter. + int tagCount; + + /// Tuple size. For scalar parameters this value is 1, but for vector + /// parameters this value can be greater. For example, a 3 vector would + /// have a size of 3. For folders and folder lists, this value is the + /// number of children they own. + int size; + + /// Any ::HAPI_ParmType can be a choice list. If this is set to + /// ::HAPI_CHOICELISTTYPE_NONE, than this parameter is NOT a choice list. + /// Otherwise, the parameter is a choice list of the indicated type. + /// See @ref HAPI_Parameters_ChoiceLists. + HAPI_ChoiceListType choiceListType; + + /// Any ::HAPI_ParmType can be a choice list. If the parameter is a + /// choice list, this tells you how many choices it currently has. + /// Note that some menu parameters can have a dynamic number of choices + /// so it is important that this count is re-checked after every cook. + /// See @ref HAPI_Parameters_ChoiceLists. + int choiceCount; + + /// Note that folders are not real parameters in Houdini so they do not + /// have names. The folder names given here are generated from the name + /// of the folderlist (or switcher) parameter which is a parameter. The + /// folderlist parameter simply defines how many of the "next" parameters + /// belong to the first folder, how many of the parameters after that + /// belong to the next folder, and so on. This means that folder names + /// can change just by reordering the folders around so don't rely on + /// them too much. The only guarantee here is that the folder names will + /// be unique among all other parameter names. + HAPI_StringHandle nameSH; + + /// The label string for the parameter + HAPI_StringHandle labelSH; + + /// If this parameter is a multiparm instance than the + /// ::HAPI_ParmInfo::templateNameSH will be the hash-templated parm name, + /// containing #'s for the parts of the name that use the instance number. + /// Compared to the ::HAPI_ParmInfo::nameSH, the ::HAPI_ParmInfo::nameSH + /// will be the ::HAPI_ParmInfo::templateNameSH but with the #'s + /// replaced by the instance number. For regular parms, the + /// ::HAPI_ParmInfo::templateNameSH is identical to the + /// ::HAPI_ParmInfo::nameSH. + HAPI_StringHandle templateNameSH; + + /// The help string for this parameter + HAPI_StringHandle helpSH; + + /// Whether min/max exists for the parameter values. + /// @{ + HAPI_Bool hasMin; + HAPI_Bool hasMax; + HAPI_Bool hasUIMin; + HAPI_Bool hasUIMax; + /// @} + + /// Parameter value range, shared between int and float parameters. + /// @{ + float min; + float max; + float UIMin; + float UIMax; + /// @} + + /// Whether this parm should be hidden from the user entirely. This is + /// mostly used to expose parameters as asset meta-data but not allow the + /// user to directly modify them. + HAPI_Bool invisible; + + /// Whether this parm should appear enabled or disabled. + HAPI_Bool disabled; + + /// If true, it means this parameter doesn't actually exist on the node + /// in Houdini but was added by Houdini Engine as a spare parameter. + /// This is just for your information. The behaviour of this parameter + /// is not any different than a non-spare parameter. + HAPI_Bool spare; + + HAPI_Bool joinNext; ///< Whether this parm should be on the same line as + ///< the next parm. + HAPI_Bool labelNone; ///< Whether the label should be displayed. + + /// The index to use to look into the values array in order to retrieve + /// the actual value(s) of this parameter. + /// @{ + int intValuesIndex; + int floatValuesIndex; + int stringValuesIndex; + int choiceIndex; + /// @} + + /// If this is a ::HAPI_PARMTYPE_NODE, this tells you what node types + /// this parameter accepts. + HAPI_NodeType inputNodeType; + + /// The node input parameter could have another subtype filter specified, + /// like "Object: Geometry Only". In this case, this value will specify + /// that extra filter. If the filter demands a node that HAPI does not + /// support, both this and ::HAPI_ParmInfo::inputNodeType will be set to + /// NONE as such a node is not settable through HAPI. + HAPI_NodeFlags inputNodeFlag; + + /// See @ref HAPI_Parameters_MultiParms. + /// @{ + HAPI_Bool isChildOfMultiParm; + + int instanceNum; ///< The index of the instance in the multiparm. + int instanceLength; ///< The number of parms in a multiparm instance. + int instanceCount; ///< The number of instances in a multiparm. + + /// First instance's ::HAPI_ParmInfo::instanceNum. Either 0 or 1. + int instanceStartOffset; + + HAPI_RampType rampType; + /// @} + + /// Provides the raw condition string which is used to evaluate the + /// the visibility of a parm + HAPI_StringHandle visibilityConditionSH; + + /// Provides the raw condition string which is used to evalute whether + /// a parm is enabled or disabled + HAPI_StringHandle disabledConditionSH; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_ParmInfo ) + +struct HAPI_API HAPI_ParmChoiceInfo +{ + HAPI_ParmId parentParmId; + HAPI_StringHandle labelSH; + + /// This evaluates to the value of the token associated with the label + /// applies to string menus only. + HAPI_StringHandle valueSH; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_ParmChoiceInfo ) + +// HANDLES ------------------------------------------------------------------ + +/// @struct HAPI_HandleInfo +/// +/// Contains handle information such as the type of handle +/// (translate, rotate, scale, softxform ...etc) and the number of +/// parameters the current handle is bound to. +/// +struct HAPI_API HAPI_HandleInfo +{ + HAPI_StringHandle nameSH; + HAPI_StringHandle typeNameSH; + + int bindingsCount; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_HandleInfo ) + +/// @struct HAPI_HandleBindingInfo +/// +/// Contains binding information that maps the handle parameter to +/// the asset parameter. The index is only used for int and float vector +/// and colour parms. +/// +struct HAPI_API HAPI_HandleBindingInfo +{ + HAPI_StringHandle handleParmNameSH; + HAPI_StringHandle assetParmNameSH; + + HAPI_ParmId assetParmId; + int assetParmIndex; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_HandleBindingInfo ) + +// OBJECTS ------------------------------------------------------------------ + +struct HAPI_API HAPI_ObjectInfo +{ + HAPI_StringHandle nameSH; + + /// (deprecated) + HAPI_StringHandle objectInstancePathSH; + + /// For incremental updates. Indicates whether the object's transform + /// has changed. Refreshed only during an asset cook. + HAPI_Bool hasTransformChanged; + + /// For incremental updates. Indicates whether any of the object's + /// geometry nodes have changed. Refreshed only during an asset cook. + HAPI_Bool haveGeosChanged; + + /// Whether the object is hidden and should not be shown. Some objects + /// should be hidden but still brought into the host environment, for + /// example those used only for instancing. + /// See @ref HAPI_Instancing. + HAPI_Bool isVisible; + + /// See @ref HAPI_Instancing. + HAPI_Bool isInstancer; + + /// Determine if this object is being instanced. Normally, this implies + /// that while this object may not be visible, it should still be + /// brought into the host application because it is needed by an instancer. + /// See @ref HAPI_Instancing. + HAPI_Bool isInstanced; + + /// (deprecated) The number of geometries under this object. For those familiar + /// with Houdini, this number will always include the one visible SOP and any + /// SOPs that were exposed as "editable" or "templated". + /// See @ref HAPI_Geos. + int geoCount; + + /// Use the node id to get the node's parameters. + /// Using the HDK, you can also get the raw node C++ pointer for this + /// object's internal node. + /// See @ref HAPI_Nodes_Basics. + HAPI_NodeId nodeId; + + /// If the object is an instancer, this variable gives the object id of + /// the object that should be instanced. + /// See @ref HAPI_Instancing. + HAPI_NodeId objectToInstanceId; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_ObjectInfo ) + +// GEOMETRY ----------------------------------------------------------------- + +struct HAPI_API HAPI_GeoInfo +{ + HAPI_GeoType type; + HAPI_StringHandle nameSH; + + /// Use the node id to get the node's parameters. + /// Using the HDK, you can also get the raw node C++ pointer for this + /// object's internal node. + HAPI_NodeId nodeId; + + /// Whether the SOP node has been exposed by dragging it into the + /// editable nodes section of the asset definition. + HAPI_Bool isEditable; + + /// Has the templated flag turned on which means "expose as read-only". + HAPI_Bool isTemplated; + + /// Final Result (Display SOP). + HAPI_Bool isDisplayGeo; + + /// For incremental updates. + HAPI_Bool hasGeoChanged; + + /// (deprecated) This variable is deprecated and should no longer be used. + /// Materials are now separate from parts. They are maintained at the + /// asset level so you only need to check if the material itself has + /// changed via ::HAPI_MaterialInfo::hasChanged instead of the material + /// on the part. + HAPI_Bool hasMaterialChanged; + + /// Groups. + /// @{ + int pointGroupCount; + int primitiveGroupCount; + /// @} + + /// Total number of parts this geometry contains. + /// See @ref HAPI_Parts. + int partCount; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_GeoInfo ) + +struct HAPI_API HAPI_PartInfo +{ + HAPI_PartId id; + HAPI_StringHandle nameSH; + HAPI_PartType type; + + int faceCount; + int vertexCount; + int pointCount; ///< Number of points. Note that this is NOT the number + ///< of "positions" as "points" may imply. If your + ///< geometry has 3 points then set this to 3 and not 3*3. + + int attributeCounts[ HAPI_ATTROWNER_MAX ]; + + /// If this is true, don't display this part. Load its data but then + /// instance it where the corresponding instancer part tells you to + /// instance it. + HAPI_Bool isInstanced; + + /// The number of parts that this instancer part is instancing. + /// For example, if we're instancing a curve and a box, they would come + /// across as two parts, hence this count would be two. + /// Call ::HAPI_GetInstancedPartIds() to get the list of ::HAPI_PartId's. + int instancedPartCount; + + /// The number of instances that this instancer part is instancing. + /// Using the same example as with ::HAPI_PartInfo::instancedPartCount, + /// if I'm instancing the merge of a curve and a box 5 times, this count + /// would be 5. To be clear, all instanced parts are instanced the same + /// number of times and with the same transform for each instance. + /// Call ::HAPI_GetInstancerPartTransforms() to get the transform of + /// each instance. + int instanceCount; + + /// If this is false, the underlying attribute data appear to match that of + /// the previous cook. In this case you may be able to re-used marshaled + /// data from the previous cook. + HAPI_Bool hasChanged; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_PartInfo ) + +/// See @ref HAPI_Attributes. +struct HAPI_API HAPI_AttributeInfo +{ + HAPI_Bool exists; + + HAPI_AttributeOwner owner; + HAPI_StorageType storage; + + /// When converting from the Houdini native GA geometry format to the + /// GT geometry format HAPI uses, some attributes might change owners. + /// For example, in Houdini GA curves can have points shared by + /// vertices but the GT format only supports curve vertices + /// (no points). This means that if you had point attributes on a curve + /// in Houdini, when it comes out of HAPI those point attributes will now + /// be vertex attributes. In this case, the ::HAPI_AttributeInfo::owner + /// will be set to ::HAPI_ATTROWNER_VERTEX but the + /// ::HAPI_AttributeInfo::originalOwner will be ::HAPI_ATTROWNER_POINT. + HAPI_AttributeOwner originalOwner; + + /// Number of attributes. This count will match the number of values + /// given the owner. For example, if the owner is ::HAPI_ATTROWNER_VERTEX + /// this count will be the same as the ::HAPI_PartInfo::vertexCount. + /// To be clear, this is not the number of values in the attribute, rather + /// it is the number of attributes. If your geometry has three 3D points + /// then this count will be 3 (not 3*3) while the + /// ::HAPI_AttributeInfo::tupleSize will be 3. + int count; + + /// Number of values per attribute. + /// Note that this is NOT the memory size of the attribute. It is the + /// number of values per attributes. Multiplying this by the + /// size of the ::HAPI_AttributeInfo::storage will give you the memory + /// size per attribute. + int tupleSize; + + /// Total number of elements for an array attribute. + /// An array attribute can be thought of as a 2 dimensional array where + /// the 2nd dimension can vary in size for each element in the 1st + /// dimension. Therefore this returns the total number of values in + /// the entire array. + /// This should be used to determine the total storage + /// size needed by multiplying with ::HAPI_AttributeInfo::storage. + /// Note that this will be 0 for a non-array attribute. + HAPI_Int64 totalArrayElements; + + /// Attribute type info + /// This is used to help identify the type of data stored in an attribute. + /// Using the type is recommended over using just an attribute's name to identify + /// its purpose. + HAPI_AttributeTypeInfo typeInfo; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_AttributeInfo ) + +// MATERIALS ---------------------------------------------------------------- + +struct HAPI_API HAPI_MaterialInfo +{ + /// This is the HAPI node id for the SHOP node this material is attached + /// to. Use it to get access to the parameters (which contain the + /// texture paths). + /// IMPORTANT: When the ::HAPI_MaterialInfo::hasChanged is true this + /// @p nodeId could have changed. Do not assume ::HAPI_MaterialInfo::nodeId + /// will never change for a specific material. + HAPI_NodeId nodeId; + + HAPI_Bool exists; + + HAPI_Bool hasChanged; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_MaterialInfo ) + +struct HAPI_API HAPI_ImageFileFormat +{ + HAPI_StringHandle nameSH; + HAPI_StringHandle descriptionSH; + HAPI_StringHandle defaultExtensionSH; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_ImageFileFormat ) + +struct HAPI_API HAPI_ImageInfo +{ + /// Unlike the other members of this struct changing imageFileFormatNameSH + /// and giving this struct back to HAPI_SetImageInfo() nothing will happen. + /// Use this member variable to derive which image file format will be used + /// by the HAPI_ExtractImageTo...() functions if called with + /// image_file_format_name set to NULL. This way, you can decide whether + /// to ask for a file format conversion (slower) or not (faster). + HAPI_StringHandle imageFileFormatNameSH; // Read-only + + int xRes; + int yRes; + + HAPI_ImageDataFormat dataFormat; + + HAPI_Bool interleaved; ///< ex: true = RGBRGBRGB, false = RRRGGGBBB + HAPI_ImagePacking packing; + + /// Adjust the gamma of the image. For anything less than + /// ::HAPI_IMAGE_DATA_INT16, you probably want to leave this as 2.2. + double gamma; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_ImageInfo ) + +// ANIMATION ---------------------------------------------------------------- + +struct HAPI_API HAPI_Keyframe +{ + float time; + float value; + float inTangent; + float outTangent; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_Keyframe ) + +// VOLUMES ------------------------------------------------------------------ + +/// @struct HAPI_VolumeInfo +/// +/// This represents a volume primitive but does not contain the actual voxel +/// values, which can be retrieved on a per-tile basis. +/// +/// See @ref HAPI_Volumes. +/// +struct HAPI_API HAPI_VolumeInfo +{ + HAPI_StringHandle nameSH; + + HAPI_VolumeType type; + + /// Each voxel is identified with an index. The indices will range + /// between: + /// [ ( minX, minY, minZ ), ( minX+xLength, minY+yLength, minZ+zLength ) ) + /// @{ + int xLength; + int yLength; + int zLength; + int minX; + int minY; + int minZ; + /// @} + + /// Number of values per voxel. + /// The tuple size field is 1 for scalars and 3 for vector data. + int tupleSize; + + /// Can be either ::HAPI_STORAGETYPE_INT or ::HAPI_STORAGETYPE_FLOAT. + HAPI_StorageType storage; + + /// The dimensions of each tile. + /// This can be 8 or 16, denoting an 8x8x8 or 16x16x16 tiles. + int tileSize; + + /// The transform of the volume with respect to the lengths. + /// The volume may be positioned anywhere in space. + HAPI_Transform transform; + + /// Denotes special situations where the volume tiles are not perfect + /// cubes, but are tapered instead. + HAPI_Bool hasTaper; + + /// If there is taper involved, denotes the amount of taper involved. + /// @{ + float xTaper; + float yTaper; + /// @} +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_VolumeInfo ) + +/// @struct HAPI_VolumeTileInfo +/// +/// A HAPI_VolumeTileInfo represents an cube subarray of the volume. +/// The size of each dimension is HAPI_VoluemInfo::tileSize +/// bbox [(minX, minY, minZ), (minX+tileSize, minY+tileSize, minZ+tileSize)) +/// +struct HAPI_API HAPI_VolumeTileInfo +{ + int minX; + int minY; + int minZ; + HAPI_Bool isValid; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_VolumeTileInfo ) + +/// @struct HAPI_VolumeVisualInfo +/// +/// Describes the visual settings of a volume. +/// +struct HAPI_API HAPI_VolumeVisualInfo +{ + HAPI_VolumeVisualType type; + float iso; + float density; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_VolumeVisualInfo ) + +// CURVES ------------------------------------------------------------------- + +/// @struct HAPI_CurveInfo +/// +/// This represents the meta-data associated with a curve mesh (a number +/// of curves of the same type). +struct HAPI_API HAPI_CurveInfo +{ + HAPI_CurveType curveType; + int curveCount; ///< The number of curves contained in this curve mesh. + int vertexCount; ///< The number of control vertices (CVs) for all curves. + int knotCount; ///< The number of knots for all curves. + + HAPI_Bool isPeriodic; + ///< Whether the curves in this curve mesh are periodic. + HAPI_Bool isRational; + ///< Whether the curves in this curve mesh are rational. + int order; ///< Order of 1 is invalid. 0 means there is a varying order. + + HAPI_Bool hasKnots; ///< Whether the curve has knots. +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_CurveInfo ) + +// BASIC PRIMITIVES --------------------------------------------------------- + +struct HAPI_API HAPI_BoxInfo +{ + float center[ HAPI_POSITION_VECTOR_SIZE ]; + float size[ HAPI_SCALE_VECTOR_SIZE ]; + float rotation[ HAPI_EULER_VECTOR_SIZE ]; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_BoxInfo ) + +struct HAPI_API HAPI_SphereInfo +{ + float center[ HAPI_POSITION_VECTOR_SIZE ]; + float radius; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_SphereInfo ) + +// PDG Structs -------------------------------------------------------------- + +struct HAPI_API HAPI_PDG_EventInfo +{ + HAPI_NodeId nodeId; /// id of related node + HAPI_PDG_WorkitemId workitemId; /// id of related workitem + HAPI_PDG_WorkitemId dependencyId; /// id of related workitem dependency + int currentState; /// (HAPI_PDG_WorkItemState) value of current state for state change + int lastState; /// (HAPI_PDG_WorkItemState) value of last state for state change + int eventType; /// (HAPI_PDG_EventType) event type + HAPI_StringHandle msgSH; /// String handle of the event message (> 0 if there is a message) +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_PDG_EventInfo ) + +struct HAPI_API HAPI_PDG_WorkitemInfo +{ + int index; /// index of the workitem + int numResults; /// number of results reported + HAPI_StringHandle nameSH; /// name of the workitem +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_PDG_WorkitemInfo ) + +struct HAPI_API HAPI_PDG_WorkitemResultInfo +{ + int resultSH; /// result string data + int resultTagSH; /// result tag + HAPI_Int64 resultHash; /// hash value of result +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_PDG_WorkitemResultInfo ) + +// SESSIONSYNC -------------------------------------------------------------- + +/// @struct HAPI_Viewport +/// +/// Contains the information for synchronizing viewport between Houdini +/// and other applications. When SessionSync is enabled, Houdini will +/// update this struct with its viewport state. It will also update +/// its own viewport if this struct has changed. +/// The data stored is in Houdini's right-handed Y-up coordinate system. +/// +struct HAPI_API HAPI_Viewport +{ + /// The world position of the viewport camera's pivot. + float position[ HAPI_POSITION_VECTOR_SIZE ]; + + /// The direction of the viewport camera stored as a quaternion. + float rotationQuaternion[ HAPI_QUATERNION_VECTOR_SIZE ]; + + /// The offset from the pivot to the viewport camera's + /// actual world position. + float offset; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_Viewport ) + +/// @struct HAPI_SessionSyncInfo +/// +/// Contains the information for synchronizing general SessionSync +/// state between Houdini and other applications. When SessionSync +/// is enabled, Houdini will update this struct with its state. +/// It will also update its internal state if this struct has +/// changed. +/// +struct HAPI_API HAPI_SessionSyncInfo +{ + /// Specifies whether Houdini's current time is used for Houdini Engine + /// cooks. This is automatically enabled in SessionSync where + /// Houdini's viewport forces cooks to use Houdini's current time. + /// This is disabled in non-SessionSync mode, but can be toggled to + /// override default behaviour. + HAPI_Bool cookUsingHoudiniTime; + + /// Specifies whether viewport synchronization is enabled. If enabled, + /// in SessionSync, Houdini will update its own viewport using + /// ::HAPI_Viewport. + HAPI_Bool syncViewport; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_SessionSyncInfo ) + +#endif // __HAPI_COMMON_h__ diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI_Helpers.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI_Helpers.h new file mode 100644 index 00000000..5fc72d58 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI_Helpers.h @@ -0,0 +1,210 @@ +/* + * PROPRIETARY INFORMATION. This software is proprietary to + * Side Effects Software Inc., and is not to be reproduced, + * transmitted, or disclosed in any way without written permission. + * + * COMMENTS: + */ + +#ifndef __HAPI_HELPERS_h__ +#define __HAPI_HELPERS_h__ + +#include "HAPI_API.h" +#include "HAPI_Common.h" + +// TIME --------------------------------------------------------------------- + +HAPI_DECL_RETURN( void ) + HAPI_TimelineOptions_Init( HAPI_TimelineOptions * in ); +HAPI_DECL_RETURN( HAPI_TimelineOptions ) + HAPI_TimelineOptions_Create(); + +// ASSETS ------------------------------------------------------------------- + +HAPI_DECL_RETURN( void ) + HAPI_AssetInfo_Init( HAPI_AssetInfo * in ); +HAPI_DECL_RETURN( HAPI_AssetInfo ) + HAPI_AssetInfo_Create(); + +HAPI_DECL_RETURN( void ) + HAPI_CookOptions_Init( HAPI_CookOptions * in ); +HAPI_DECL_RETURN( HAPI_CookOptions ) + HAPI_CookOptions_Create(); +HAPI_DECL_RETURN( HAPI_Bool ) + HAPI_CookOptions_AreEqual( + const HAPI_CookOptions * left, + const HAPI_CookOptions * right ); + +// NODES -------------------------------------------------------------------- + +HAPI_DECL_RETURN( void ) + HAPI_NodeInfo_Init( HAPI_NodeInfo * in ); +HAPI_DECL_RETURN( HAPI_NodeInfo ) + HAPI_NodeInfo_Create(); + +// PARAMETERS --------------------------------------------------------------- + +/// Clears the struct to default values. +HAPI_DECL_RETURN( void ) + HAPI_ParmInfo_Init( HAPI_ParmInfo * in ); + +/// Creates a struct with default values and returns it. +HAPI_DECL_RETURN( HAPI_ParmInfo ) + HAPI_ParmInfo_Create(); + +/// Convenience function that checks on the value of the ::HAPI_ParmInfo::type +/// field to tell you the underlying data type. +/// @{ +HAPI_DECL_RETURN( HAPI_Bool ) + HAPI_ParmInfo_IsInt( const HAPI_ParmInfo * in ); +HAPI_DECL_RETURN( HAPI_Bool ) + HAPI_ParmInfo_IsFloat( const HAPI_ParmInfo * in ); +HAPI_DECL_RETURN( HAPI_Bool ) + HAPI_ParmInfo_IsString( const HAPI_ParmInfo * in ); +HAPI_DECL_RETURN( HAPI_Bool ) + HAPI_ParmInfo_IsPath( const HAPI_ParmInfo * in ); +HAPI_DECL_RETURN( HAPI_Bool ) + HAPI_ParmInfo_IsNode( const HAPI_ParmInfo * in ); +/// @} + +/// Parameter has no underlying No data type. Examples of this are UI items +/// such as folder lists and separators. +HAPI_DECL_RETURN( HAPI_Bool ) + HAPI_ParmInfo_IsNonValue( const HAPI_ParmInfo * in ); + +/// Convenience function. If the parameter can be represented by this data +/// type, it returns ::HAPI_ParmInfo::size, and zero otherwise. +/// @{ +HAPI_DECL_RETURN( int ) + HAPI_ParmInfo_GetIntValueCount( const HAPI_ParmInfo * in ); +HAPI_DECL_RETURN( int ) + HAPI_ParmInfo_GetFloatValueCount( const HAPI_ParmInfo * in ); +HAPI_DECL_RETURN( int ) + HAPI_ParmInfo_GetStringValueCount( const HAPI_ParmInfo* in ); +/// @} + +HAPI_DECL_RETURN( void ) + HAPI_ParmChoiceInfo_Init( HAPI_ParmChoiceInfo * in ); +HAPI_DECL_RETURN( HAPI_ParmChoiceInfo ) + HAPI_ParmChoiceInfo_Create(); + +// HANDLES ------------------------------------------------------------------ + +HAPI_DECL_RETURN( void ) + HAPI_HandleInfo_Init( HAPI_HandleInfo * in ); +HAPI_DECL_RETURN( HAPI_HandleInfo ) + HAPI_HandleInfo_Create(); + +HAPI_DECL_RETURN( void ) + HAPI_HandleBindingInfo_Init( HAPI_HandleBindingInfo * in ); +HAPI_DECL_RETURN( HAPI_HandleBindingInfo ) + HAPI_HandleBindingInfo_Create(); + +// OBJECTS ------------------------------------------------------------------ + +HAPI_DECL_RETURN( void ) + HAPI_ObjectInfo_Init( HAPI_ObjectInfo * in ); +HAPI_DECL_RETURN( HAPI_ObjectInfo ) + HAPI_ObjectInfo_Create(); + +// GEOMETRY ----------------------------------------------------------------- + +HAPI_DECL_RETURN( void ) + HAPI_GeoInfo_Init( HAPI_GeoInfo * in ); +HAPI_DECL_RETURN( HAPI_GeoInfo ) + HAPI_GeoInfo_Create(); +HAPI_DECL_RETURN( int ) + HAPI_GeoInfo_GetGroupCountByType( + HAPI_GeoInfo * in, HAPI_GroupType type ); + +HAPI_DECL_RETURN( void ) + HAPI_PartInfo_Init( HAPI_PartInfo * in ); +HAPI_DECL_RETURN( HAPI_PartInfo ) + HAPI_PartInfo_Create(); +HAPI_DECL_RETURN( int ) + HAPI_PartInfo_GetElementCountByAttributeOwner( + HAPI_PartInfo * in, HAPI_AttributeOwner owner ); +HAPI_DECL_RETURN( int ) + HAPI_PartInfo_GetElementCountByGroupType( + HAPI_PartInfo * in, HAPI_GroupType type ); +HAPI_DECL_RETURN( int ) + HAPI_PartInfo_GetAttributeCountByOwner( + HAPI_PartInfo * in, HAPI_AttributeOwner owner ); + +HAPI_DECL_RETURN( void ) + HAPI_AttributeInfo_Init( HAPI_AttributeInfo * in ); +HAPI_DECL_RETURN( HAPI_AttributeInfo ) + HAPI_AttributeInfo_Create(); + +// MATERIALS ---------------------------------------------------------------- + +HAPI_DECL_RETURN( void ) + HAPI_MaterialInfo_Init( HAPI_MaterialInfo * in ); +HAPI_DECL_RETURN( HAPI_MaterialInfo ) + HAPI_MaterialInfo_Create(); + +HAPI_DECL_RETURN( void ) + HAPI_ImageFileFormat_Init( HAPI_ImageFileFormat *in ); +HAPI_DECL_RETURN( HAPI_ImageFileFormat ) + HAPI_ImageFileFormat_Create(); + +HAPI_DECL_RETURN( void ) + HAPI_ImageInfo_Init( HAPI_ImageInfo * in ); +HAPI_DECL_RETURN( HAPI_ImageInfo ) + HAPI_ImageInfo_Create(); + +// ANIMATION ---------------------------------------------------------------- + +HAPI_DECL_RETURN( void ) + HAPI_Keyframe_Init( HAPI_Keyframe * in ); +HAPI_DECL_RETURN( HAPI_Keyframe ) + HAPI_Keyframe_Create(); + +// VOLUMES ------------------------------------------------------------------ + +HAPI_DECL_RETURN( void ) + HAPI_VolumeInfo_Init( HAPI_VolumeInfo * in ); +HAPI_DECL_RETURN( HAPI_VolumeInfo ) + HAPI_VolumeInfo_Create(); + +HAPI_DECL_RETURN( void ) + HAPI_VolumeTileInfo_Init( HAPI_VolumeTileInfo * in ); +HAPI_DECL_RETURN( HAPI_VolumeTileInfo ) + HAPI_VolumeTileInfo_Create(); + +// CURVES ------------------------------------------------------------------- + +HAPI_DECL_RETURN( void ) + HAPI_CurveInfo_Init( HAPI_CurveInfo * in ); +HAPI_DECL_RETURN( HAPI_CurveInfo ) + HAPI_CurveInfo_Create(); + +// TRANSFORMS --------------------------------------------------------------- + +HAPI_DECL_RETURN( void ) + HAPI_Transform_Init( HAPI_Transform * in ); + +HAPI_DECL_RETURN( HAPI_Transform ) + HAPI_Transform_Create(); + +HAPI_DECL_RETURN( void ) + HAPI_TransformEuler_Init( HAPI_TransformEuler * in ); + +HAPI_DECL_RETURN( HAPI_TransformEuler ) + HAPI_TransformEuler_Create(); + +// SESSIONSYNC -------------------------------------------------------------- + +HAPI_DECL_RETURN (void ) + HAPI_Viewport_Init( HAPI_Viewport * in ); + +HAPI_DECL_RETURN( HAPI_Viewport ) + HAPI_Viewport_Create(); + +HAPI_DECL_RETURN (void ) + HAPI_SessionSyncInfo_Init( HAPI_SessionSyncInfo * in ); + +HAPI_DECL_RETURN( HAPI_SessionSyncInfo ) + HAPI_SessionSyncInfo_Create(); + +#endif // __HAPI_HELPERS_h__ diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI_Version.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI_Version.h new file mode 100644 index 00000000..57d292fd --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HAPI/HAPI_Version.h @@ -0,0 +1,45 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc.* +* Permission is hereby granted, free of charge, to any person obtaining a copy* of this software and associated documentation files (the "Software"), to deal* in the Software without restriction, including without limitation the rights* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell* copies of the Software, and to permit persons to whom the Software is* furnished to do so, subject to the following conditions:** The above copyright notice and this permission notice shall be included in all* copies or substantial portions of the Software.** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE* SOFTWARE. * + * Produced by: + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + * COMMENTS: + * Generated version information to be used when linking for + * sanity checks. + */ + +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +// WARNING! This file is GENERATED by UNREAL_Version.py script. +// DO NOT modify manually or commit to the repository! +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// + +#ifndef __HAPI_VERSION_h__ +#define __HAPI_VERSION_h__ + +// The three components of the Houdini version that HAPI is +// expecting to compile against. +#define HAPI_VERSION_HOUDINI_MAJOR 18 +#define HAPI_VERSION_HOUDINI_MINOR 5 +#define HAPI_VERSION_HOUDINI_BUILD 351 +#define HAPI_VERSION_HOUDINI_PATCH 0 + +// The two components of the Houdini Engine (marketed) version. +#define HAPI_VERSION_HOUDINI_ENGINE_MAJOR 3 +#define HAPI_VERSION_HOUDINI_ENGINE_MINOR 5 + +// This is a monotonously increasing API version number that can be used +// to lock against a certain API for compatibility purposes. Basically, +// when this number changes code compiled against the HAPI.h methods +// might no longer compile. Semantic changes to the methods will also +// cause this version to increase. This number will be reset to 0 +// every time the Houdini Engine version is bumped. +#define HAPI_VERSION_HOUDINI_ENGINE_API 0 + +#endif // __HAPI_VERSION_h__ diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniApi.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniApi.h new file mode 100644 index 00000000..997b97b8 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniApi.h @@ -0,0 +1,887 @@ +/* + * Copyright (c) <2019> Side Effects Software Inc. * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + * COMMENTS: + * This file is generated. Do not modify directly. + */ + +#pragma once +#include "HAPI.h" +#include "HAL/PlatformProcess.h" + + +struct HOUDINIENGINERUNTIME_API FHoudiniApi +{ +public: + + static void InitializeHAPI(void* LibraryHandle); + static void FinalizeHAPI(); + static bool IsHAPIInitialized(); + +public: + + typedef HAPI_Result (*AddAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*AddGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + typedef HAPI_AssetInfo (*AssetInfo_CreateFuncPtr)(); + typedef void (*AssetInfo_InitFuncPtr)(HAPI_AssetInfo * in); + typedef HAPI_AttributeInfo (*AttributeInfo_CreateFuncPtr)(); + typedef void (*AttributeInfo_InitFuncPtr)(HAPI_AttributeInfo * in); + typedef HAPI_Result (*BindCustomImplementationFuncPtr)(HAPI_SessionType session_type, const char * dll_path); + typedef HAPI_Result (*CancelPDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + typedef HAPI_Result (*CheckForSpecificErrorsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); + typedef HAPI_Result (*CleanupFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*CloseSessionFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*CommitGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*CommitWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*ComposeChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + typedef HAPI_Result (*ComposeNodeCookResultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); + typedef HAPI_Result (*ComposeObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); + typedef HAPI_Result (*ConnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); + typedef HAPI_Result (*ConvertMatrixToEulerFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + typedef HAPI_Result (*ConvertMatrixToQuatFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); + typedef HAPI_Result (*ConvertTransformFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + typedef HAPI_Result (*ConvertTransformEulerToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); + typedef HAPI_Result (*ConvertTransformQuatToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); + typedef HAPI_Result (*CookNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); + typedef HAPI_Bool (*CookOptions_AreEqualFuncPtr)(const HAPI_CookOptions * left, const HAPI_CookOptions * right); + typedef HAPI_CookOptions (*CookOptions_CreateFuncPtr)(); + typedef void (*CookOptions_InitFuncPtr)(HAPI_CookOptions * in); + typedef HAPI_Result (*CookPDGFuncPtr)(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); + typedef HAPI_Result (*CreateCustomSessionFuncPtr)(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); + typedef HAPI_Result (*CreateHeightfieldInputNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); + typedef HAPI_Result (*CreateHeightfieldInputVolumeNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); + typedef HAPI_Result (*CreateInProcessSessionFuncPtr)(HAPI_Session * session); + typedef HAPI_Result (*CreateInputNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); + typedef HAPI_Result (*CreateNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); + typedef HAPI_Result (*CreateThriftNamedPipeSessionFuncPtr)(HAPI_Session * session, const char * pipe_name); + typedef HAPI_Result (*CreateThriftSocketSessionFuncPtr)(HAPI_Session * session, const char * host_name, int port); + typedef HAPI_Result (*CreateWorkitemFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); + typedef HAPI_CurveInfo (*CurveInfo_CreateFuncPtr)(); + typedef void (*CurveInfo_InitFuncPtr)(HAPI_CurveInfo * in); + typedef HAPI_Result (*DeleteAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*DeleteGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + typedef HAPI_Result (*DeleteNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*DirtyPDGNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); + typedef HAPI_Result (*DisconnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); + typedef HAPI_Result (*DisconnectNodeOutputsAtFuncPtr)(const HAPI_Session *session, HAPI_NodeId node_id, int output_index); + typedef HAPI_Result (*ExtractImageToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); + typedef HAPI_Result (*ExtractImageToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); + typedef HAPI_GeoInfo (*GeoInfo_CreateFuncPtr)(); + typedef int (*GeoInfo_GetGroupCountByTypeFuncPtr)(HAPI_GeoInfo * in, HAPI_GroupType type); + typedef void (*GeoInfo_InitFuncPtr)(HAPI_GeoInfo * in); + typedef HAPI_Result (*GetActiveCacheCountFuncPtr)(const HAPI_Session * session, int * active_cache_count); + typedef HAPI_Result (*GetActiveCacheNamesFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); + typedef HAPI_Result (*GetAssetInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); + typedef HAPI_Result (*GetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*GetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); + typedef HAPI_Result (*GetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); + typedef HAPI_Result (*GetAvailableAssetCountFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); + typedef HAPI_Result (*GetAvailableAssetsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); + typedef HAPI_Result (*GetBoxInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); + typedef HAPI_Result (*GetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); + typedef HAPI_Result (*GetComposedChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); + typedef HAPI_Result (*GetComposedNodeCookResultFuncPtr)(const HAPI_Session * session, char * string_value, int length); + typedef HAPI_Result (*GetComposedObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); + typedef HAPI_Result (*GetComposedObjectTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); + typedef HAPI_Result (*GetCookingCurrentCountFuncPtr)(const HAPI_Session * session, int * count); + typedef HAPI_Result (*GetCookingTotalCountFuncPtr)(const HAPI_Session * session, int * count); + typedef HAPI_Result (*GetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); + typedef HAPI_Result (*GetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); + typedef HAPI_Result (*GetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); + typedef HAPI_Result (*GetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); + typedef HAPI_Result (*GetDisplayGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); + typedef HAPI_Result (*GetEnvIntFuncPtr)(HAPI_EnvIntType int_type, int * value); + typedef HAPI_Result (*GetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); + typedef HAPI_Result (*GetFirstVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + typedef HAPI_Result (*GetGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); + typedef HAPI_Result (*GetGeoSizeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); + typedef HAPI_Result (*GetGroupCountOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); + typedef HAPI_Result (*GetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + typedef HAPI_Result (*GetGroupMembershipOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + typedef HAPI_Result (*GetGroupNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + typedef HAPI_Result (*GetGroupNamesOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + typedef HAPI_Result (*GetHandleBindingInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); + typedef HAPI_Result (*GetHandleInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); + typedef HAPI_Result (*GetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); + typedef HAPI_Result (*GetImageFilePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); + typedef HAPI_Result (*GetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); + typedef HAPI_Result (*GetImageMemoryBufferFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); + typedef HAPI_Result (*GetImagePlaneCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); + typedef HAPI_Result (*GetImagePlanesFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); + typedef HAPI_Result (*GetInstanceTransformsOnPartFuncPtr)(const HAPI_Session *session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform *transforms_array, int start, int length); + typedef HAPI_Result (*GetInstancedObjectIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); + typedef HAPI_Result (*GetInstancedPartIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); + typedef HAPI_Result (*GetInstancerPartTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + typedef HAPI_Result (*GetManagerNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); + typedef HAPI_Result (*GetMaterialInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); + typedef HAPI_Result (*GetMaterialNodeIdsOnFacesFuncPtr)(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); + typedef HAPI_Result (*GetNextVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + typedef HAPI_Result (*GetNodeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); + typedef HAPI_Result (*GetNodeInputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); + typedef HAPI_Result (*GetNodeOutputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); + typedef HAPI_Result (*GetNodePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); + typedef HAPI_Result (*GetNumWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * num); + typedef HAPI_Result (*GetObjectInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); + typedef HAPI_Result (*GetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); + typedef HAPI_Result (*GetPDGEventsFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); + typedef HAPI_Result (*GetPDGGraphContextIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); + typedef HAPI_Result (*GetPDGGraphContextsFuncPtr)(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); + typedef HAPI_Result (*GetPDGStateFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); + typedef HAPI_Result (*GetParametersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); + typedef HAPI_Result (*GetParmChoiceListsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo *parm_choices_array, int start, int length); + typedef HAPI_Result (*GetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); + typedef HAPI_Result (*GetParmFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); + typedef HAPI_Result (*GetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); + typedef HAPI_Result (*GetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); + typedef HAPI_Result (*GetParmIdFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); + typedef HAPI_Result (*GetParmInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); + typedef HAPI_Result (*GetParmInfoFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); + typedef HAPI_Result (*GetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); + typedef HAPI_Result (*GetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); + typedef HAPI_Result (*GetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); + typedef HAPI_Result (*GetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); + typedef HAPI_Result (*GetParmStringValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); + typedef HAPI_Result (*GetParmTagNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); + typedef HAPI_Result (*GetParmTagValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); + typedef HAPI_Result (*GetParmWithTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); + typedef HAPI_Result (*GetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); + typedef HAPI_Result (*GetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); + typedef HAPI_Result (*GetPresetBufLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); + typedef HAPI_Result (*GetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int * value); + typedef HAPI_Result (*GetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); + typedef HAPI_Result (*GetServerEnvVarCountFuncPtr)(const HAPI_Session * session, int * env_count); + typedef HAPI_Result (*GetServerEnvVarListFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); + typedef HAPI_Result (*GetSessionEnvIntFuncPtr)(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); + typedef HAPI_Result (*GetSphereInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); + typedef HAPI_Result (*GetStatusFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, int * status); + typedef HAPI_Result (*GetStatusStringFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); + typedef HAPI_Result (*GetStatusStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); + typedef HAPI_Result (*GetStringFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); + typedef HAPI_Result (*GetStringBatchFuncPtr)(const HAPI_Session * session, char * char_array, int char_array_length); + typedef HAPI_Result (*GetStringBatchSizeFuncPtr)(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int* string_buffer_size); + typedef HAPI_Result (*GetStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); + typedef HAPI_Result (*GetSupportedImageFileFormatCountFuncPtr)(const HAPI_Session * session, int * file_format_count); + typedef HAPI_Result (*GetSupportedImageFileFormatsFuncPtr)(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); + typedef HAPI_Result (*GetTimeFuncPtr)(const HAPI_Session * session, float * time); + typedef HAPI_Result (*GetTimelineOptionsFuncPtr)(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); + typedef HAPI_Result (*GetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); + typedef HAPI_Result (*GetVolumeBoundsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); + typedef HAPI_Result (*GetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); + typedef HAPI_Result (*GetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); + typedef HAPI_Result (*GetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); + typedef HAPI_Result (*GetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); + typedef HAPI_Result (*GetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); + typedef HAPI_Result (*GetWorkitemDataLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); + typedef HAPI_Result (*GetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); + typedef HAPI_Result (*GetWorkitemInfoFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); + typedef HAPI_Result (*GetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char* data_name, int * data_array, int length); + typedef HAPI_Result (*GetWorkitemResultInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); + typedef HAPI_Result (*GetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); + typedef HAPI_Result (*GetWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); + typedef HAPI_HandleBindingInfo (*HandleBindingInfo_CreateFuncPtr)(); + typedef void (*HandleBindingInfo_InitFuncPtr)(HAPI_HandleBindingInfo * in); + typedef HAPI_HandleInfo (*HandleInfo_CreateFuncPtr)(); + typedef void (*HandleInfo_InitFuncPtr)(HAPI_HandleInfo * in); + typedef HAPI_ImageFileFormat (*ImageFileFormat_CreateFuncPtr)(); + typedef void (*ImageFileFormat_InitFuncPtr)(HAPI_ImageFileFormat *in); + typedef HAPI_ImageInfo (*ImageInfo_CreateFuncPtr)(); + typedef void (*ImageInfo_InitFuncPtr)(HAPI_ImageInfo * in); + typedef HAPI_Result (*InitializeFuncPtr)(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); + typedef HAPI_Result (*InsertMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + typedef HAPI_Result (*InterruptFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*IsInitializedFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*IsNodeValidFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); + typedef HAPI_Result (*IsSessionValidFuncPtr)(const HAPI_Session * session); + typedef HAPI_Keyframe (*Keyframe_CreateFuncPtr)(); + typedef void (*Keyframe_InitFuncPtr)(HAPI_Keyframe * in); + typedef HAPI_Result (*LoadAssetLibraryFromFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId* library_id); + typedef HAPI_Result (*LoadAssetLibraryFromMemoryFuncPtr)(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + typedef HAPI_Result (*LoadGeoFromFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + typedef HAPI_Result (*LoadGeoFromMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); + typedef HAPI_Result (*LoadHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); + typedef HAPI_MaterialInfo (*MaterialInfo_CreateFuncPtr)(); + typedef void (*MaterialInfo_InitFuncPtr)(HAPI_MaterialInfo * in); + typedef HAPI_NodeInfo (*NodeInfo_CreateFuncPtr)(); + typedef void (*NodeInfo_InitFuncPtr)(HAPI_NodeInfo * in); + typedef HAPI_ObjectInfo (*ObjectInfo_CreateFuncPtr)(); + typedef void (*ObjectInfo_InitFuncPtr)(HAPI_ObjectInfo * in); + typedef HAPI_ParmChoiceInfo (*ParmChoiceInfo_CreateFuncPtr)(); + typedef void (*ParmChoiceInfo_InitFuncPtr)(HAPI_ParmChoiceInfo * in); + typedef HAPI_Result (*ParmHasExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); + typedef HAPI_Result (*ParmHasTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); + typedef HAPI_ParmInfo (*ParmInfo_CreateFuncPtr)(); + typedef int (*ParmInfo_GetFloatValueCountFuncPtr)(const HAPI_ParmInfo * in); + typedef int (*ParmInfo_GetIntValueCountFuncPtr)(const HAPI_ParmInfo * in); + typedef int (*ParmInfo_GetStringValueCountFuncPtr)(const HAPI_ParmInfo* in); + typedef void (*ParmInfo_InitFuncPtr)(HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsFloatFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsIntFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsNodeFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsNonValueFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsPathFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsStringFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_PartInfo (*PartInfo_CreateFuncPtr)(); + typedef int (*PartInfo_GetAttributeCountByOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + typedef int (*PartInfo_GetElementCountByAttributeOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + typedef int (*PartInfo_GetElementCountByGroupTypeFuncPtr)(HAPI_PartInfo * in, HAPI_GroupType type); + typedef void (*PartInfo_InitFuncPtr)(HAPI_PartInfo * in); + typedef HAPI_Result (*PausePDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + typedef HAPI_Result (*PythonThreadInterpreterLockFuncPtr)(const HAPI_Session * session, HAPI_Bool locked); + typedef HAPI_Result (*QueryNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); + typedef HAPI_Result (*QueryNodeOutputConnectedCountFuncPtr)(const HAPI_Session *session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); + typedef HAPI_Result (*QueryNodeOutputConnectedNodesFuncPtr)(const HAPI_Session *session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); + typedef HAPI_Result (*RemoveCustomStringFuncPtr)(const HAPI_Session * session, const int string_handle); + typedef HAPI_Result (*RemoveMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + typedef HAPI_Result (*RemoveParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*RenameNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); + typedef HAPI_Result (*RenderCOPToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId cop_node_id); + typedef HAPI_Result (*RenderTextureToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); + typedef HAPI_Result (*ResetSimulationFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*RevertGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*RevertParmToDefaultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); + typedef HAPI_Result (*RevertParmToDefaultsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); + typedef HAPI_Result (*SaveGeoToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + typedef HAPI_Result (*SaveGeoToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); + typedef HAPI_Result (*SaveHIPFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); + typedef HAPI_Result (*SetAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + typedef HAPI_Result (*SetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo *attr_info, const char ** data_array, int start, int length); + typedef HAPI_Result (*SetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); + typedef HAPI_Result (*SetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); + typedef HAPI_Result (*SetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); + typedef HAPI_Result (*SetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); + typedef HAPI_Result (*SetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); + typedef HAPI_Result (*SetCustomStringFuncPtr)(const HAPI_Session * session, const char * string_value, int *handle_value); + typedef HAPI_Result (*SetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); + typedef HAPI_Result (*SetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); + typedef HAPI_Result (*SetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); + typedef HAPI_Result (*SetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); + typedef HAPI_Result (*SetNodeDisplayFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); + typedef HAPI_Result (*SetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); + typedef HAPI_Result (*SetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*SetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); + typedef HAPI_Result (*SetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); + typedef HAPI_Result (*SetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); + typedef HAPI_Result (*SetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); + typedef HAPI_Result (*SetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); + typedef HAPI_Result (*SetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*SetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); + typedef HAPI_Result (*SetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); + typedef HAPI_Result (*SetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int value); + typedef HAPI_Result (*SetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, const char * value); + typedef HAPI_Result (*SetTimeFuncPtr)(const HAPI_Session * session, float time); + typedef HAPI_Result (*SetTimelineOptionsFuncPtr)(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); + typedef HAPI_Result (*SetTransformAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + typedef HAPI_Result (*SetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); + typedef HAPI_Result (*SetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); + typedef HAPI_Result (*SetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); + typedef HAPI_Result (*SetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); + typedef HAPI_Result (*SetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); + typedef HAPI_Result (*SetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); + typedef HAPI_Result (*SetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); + typedef HAPI_Result (*SetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); + typedef HAPI_Result (*SetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); + typedef HAPI_Result (*StartThriftNamedPipeServerFuncPtr)(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); + typedef HAPI_Result (*StartThriftSocketServerFuncPtr)(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); + typedef HAPI_TimelineOptions (*TimelineOptions_CreateFuncPtr)(); + typedef void (*TimelineOptions_InitFuncPtr)(HAPI_TimelineOptions * in); + typedef HAPI_TransformEuler (*TransformEuler_CreateFuncPtr)(); + typedef void (*TransformEuler_InitFuncPtr)(HAPI_TransformEuler * in); + typedef HAPI_Transform (*Transform_CreateFuncPtr)(); + typedef void (*Transform_InitFuncPtr)(HAPI_Transform * in); + typedef HAPI_VolumeInfo (*VolumeInfo_CreateFuncPtr)(); + typedef void (*VolumeInfo_InitFuncPtr)(HAPI_VolumeInfo * in); + typedef HAPI_VolumeTileInfo (*VolumeTileInfo_CreateFuncPtr)(); + typedef void (*VolumeTileInfo_InitFuncPtr)(HAPI_VolumeTileInfo * in); + +public: + + static AddAttributeFuncPtr AddAttribute; + static AddGroupFuncPtr AddGroup; + static AssetInfo_CreateFuncPtr AssetInfo_Create; + static AssetInfo_InitFuncPtr AssetInfo_Init; + static AttributeInfo_CreateFuncPtr AttributeInfo_Create; + static AttributeInfo_InitFuncPtr AttributeInfo_Init; + static BindCustomImplementationFuncPtr BindCustomImplementation; + static CancelPDGCookFuncPtr CancelPDGCook; + static CheckForSpecificErrorsFuncPtr CheckForSpecificErrors; + static CleanupFuncPtr Cleanup; + static CloseSessionFuncPtr CloseSession; + static CommitGeoFuncPtr CommitGeo; + static CommitWorkitemsFuncPtr CommitWorkitems; + static ComposeChildNodeListFuncPtr ComposeChildNodeList; + static ComposeNodeCookResultFuncPtr ComposeNodeCookResult; + static ComposeObjectListFuncPtr ComposeObjectList; + static ConnectNodeInputFuncPtr ConnectNodeInput; + static ConvertMatrixToEulerFuncPtr ConvertMatrixToEuler; + static ConvertMatrixToQuatFuncPtr ConvertMatrixToQuat; + static ConvertTransformFuncPtr ConvertTransform; + static ConvertTransformEulerToMatrixFuncPtr ConvertTransformEulerToMatrix; + static ConvertTransformQuatToMatrixFuncPtr ConvertTransformQuatToMatrix; + static CookNodeFuncPtr CookNode; + static CookOptions_AreEqualFuncPtr CookOptions_AreEqual; + static CookOptions_CreateFuncPtr CookOptions_Create; + static CookOptions_InitFuncPtr CookOptions_Init; + static CookPDGFuncPtr CookPDG; + static CreateCustomSessionFuncPtr CreateCustomSession; + static CreateHeightfieldInputNodeFuncPtr CreateHeightfieldInputNode; + static CreateHeightfieldInputVolumeNodeFuncPtr CreateHeightfieldInputVolumeNode; + static CreateInProcessSessionFuncPtr CreateInProcessSession; + static CreateInputNodeFuncPtr CreateInputNode; + static CreateNodeFuncPtr CreateNode; + static CreateThriftNamedPipeSessionFuncPtr CreateThriftNamedPipeSession; + static CreateThriftSocketSessionFuncPtr CreateThriftSocketSession; + static CreateWorkitemFuncPtr CreateWorkitem; + static CurveInfo_CreateFuncPtr CurveInfo_Create; + static CurveInfo_InitFuncPtr CurveInfo_Init; + static DeleteAttributeFuncPtr DeleteAttribute; + static DeleteGroupFuncPtr DeleteGroup; + static DeleteNodeFuncPtr DeleteNode; + static DirtyPDGNodeFuncPtr DirtyPDGNode; + static DisconnectNodeInputFuncPtr DisconnectNodeInput; + static DisconnectNodeOutputsAtFuncPtr DisconnectNodeOutputsAt; + static ExtractImageToFileFuncPtr ExtractImageToFile; + static ExtractImageToMemoryFuncPtr ExtractImageToMemory; + static GeoInfo_CreateFuncPtr GeoInfo_Create; + static GeoInfo_GetGroupCountByTypeFuncPtr GeoInfo_GetGroupCountByType; + static GeoInfo_InitFuncPtr GeoInfo_Init; + static GetActiveCacheCountFuncPtr GetActiveCacheCount; + static GetActiveCacheNamesFuncPtr GetActiveCacheNames; + static GetAssetInfoFuncPtr GetAssetInfo; + static GetAttributeFloat64DataFuncPtr GetAttributeFloat64Data; + static GetAttributeFloatDataFuncPtr GetAttributeFloatData; + static GetAttributeInfoFuncPtr GetAttributeInfo; + static GetAttributeInt64DataFuncPtr GetAttributeInt64Data; + static GetAttributeIntDataFuncPtr GetAttributeIntData; + static GetAttributeNamesFuncPtr GetAttributeNames; + static GetAttributeStringDataFuncPtr GetAttributeStringData; + static GetAvailableAssetCountFuncPtr GetAvailableAssetCount; + static GetAvailableAssetsFuncPtr GetAvailableAssets; + static GetBoxInfoFuncPtr GetBoxInfo; + static GetCachePropertyFuncPtr GetCacheProperty; + static GetComposedChildNodeListFuncPtr GetComposedChildNodeList; + static GetComposedNodeCookResultFuncPtr GetComposedNodeCookResult; + static GetComposedObjectListFuncPtr GetComposedObjectList; + static GetComposedObjectTransformsFuncPtr GetComposedObjectTransforms; + static GetCookingCurrentCountFuncPtr GetCookingCurrentCount; + static GetCookingTotalCountFuncPtr GetCookingTotalCount; + static GetCurveCountsFuncPtr GetCurveCounts; + static GetCurveInfoFuncPtr GetCurveInfo; + static GetCurveKnotsFuncPtr GetCurveKnots; + static GetCurveOrdersFuncPtr GetCurveOrders; + static GetDisplayGeoInfoFuncPtr GetDisplayGeoInfo; + static GetEnvIntFuncPtr GetEnvInt; + static GetFaceCountsFuncPtr GetFaceCounts; + static GetFirstVolumeTileFuncPtr GetFirstVolumeTile; + static GetGeoInfoFuncPtr GetGeoInfo; + static GetGeoSizeFuncPtr GetGeoSize; + static GetGroupCountOnPackedInstancePartFuncPtr GetGroupCountOnPackedInstancePart; + static GetGroupMembershipFuncPtr GetGroupMembership; + static GetGroupMembershipOnPackedInstancePartFuncPtr GetGroupMembershipOnPackedInstancePart; + static GetGroupNamesFuncPtr GetGroupNames; + static GetGroupNamesOnPackedInstancePartFuncPtr GetGroupNamesOnPackedInstancePart; + static GetHandleBindingInfoFuncPtr GetHandleBindingInfo; + static GetHandleInfoFuncPtr GetHandleInfo; + static GetHeightFieldDataFuncPtr GetHeightFieldData; + static GetImageFilePathFuncPtr GetImageFilePath; + static GetImageInfoFuncPtr GetImageInfo; + static GetImageMemoryBufferFuncPtr GetImageMemoryBuffer; + static GetImagePlaneCountFuncPtr GetImagePlaneCount; + static GetImagePlanesFuncPtr GetImagePlanes; + static GetInstanceTransformsOnPartFuncPtr GetInstanceTransformsOnPart; + static GetInstancedObjectIdsFuncPtr GetInstancedObjectIds; + static GetInstancedPartIdsFuncPtr GetInstancedPartIds; + static GetInstancerPartTransformsFuncPtr GetInstancerPartTransforms; + static GetManagerNodeIdFuncPtr GetManagerNodeId; + static GetMaterialInfoFuncPtr GetMaterialInfo; + static GetMaterialNodeIdsOnFacesFuncPtr GetMaterialNodeIdsOnFaces; + static GetNextVolumeTileFuncPtr GetNextVolumeTile; + static GetNodeInfoFuncPtr GetNodeInfo; + static GetNodeInputNameFuncPtr GetNodeInputName; + static GetNodeOutputNameFuncPtr GetNodeOutputName; + static GetNodePathFuncPtr GetNodePath; + static GetNumWorkitemsFuncPtr GetNumWorkitems; + static GetObjectInfoFuncPtr GetObjectInfo; + static GetObjectTransformFuncPtr GetObjectTransform; + static GetPDGEventsFuncPtr GetPDGEvents; + static GetPDGGraphContextIdFuncPtr GetPDGGraphContextId; + static GetPDGGraphContextsFuncPtr GetPDGGraphContexts; + static GetPDGStateFuncPtr GetPDGState; + static GetParametersFuncPtr GetParameters; + static GetParmChoiceListsFuncPtr GetParmChoiceLists; + static GetParmExpressionFuncPtr GetParmExpression; + static GetParmFileFuncPtr GetParmFile; + static GetParmFloatValueFuncPtr GetParmFloatValue; + static GetParmFloatValuesFuncPtr GetParmFloatValues; + static GetParmIdFromNameFuncPtr GetParmIdFromName; + static GetParmInfoFuncPtr GetParmInfo; + static GetParmInfoFromNameFuncPtr GetParmInfoFromName; + static GetParmIntValueFuncPtr GetParmIntValue; + static GetParmIntValuesFuncPtr GetParmIntValues; + static GetParmNodeValueFuncPtr GetParmNodeValue; + static GetParmStringValueFuncPtr GetParmStringValue; + static GetParmStringValuesFuncPtr GetParmStringValues; + static GetParmTagNameFuncPtr GetParmTagName; + static GetParmTagValueFuncPtr GetParmTagValue; + static GetParmWithTagFuncPtr GetParmWithTag; + static GetPartInfoFuncPtr GetPartInfo; + static GetPresetFuncPtr GetPreset; + static GetPresetBufLengthFuncPtr GetPresetBufLength; + static GetServerEnvIntFuncPtr GetServerEnvInt; + static GetServerEnvStringFuncPtr GetServerEnvString; + static GetServerEnvVarCountFuncPtr GetServerEnvVarCount; + static GetServerEnvVarListFuncPtr GetServerEnvVarList; + static GetSessionEnvIntFuncPtr GetSessionEnvInt; + static GetSphereInfoFuncPtr GetSphereInfo; + static GetStatusFuncPtr GetStatus; + static GetStatusStringFuncPtr GetStatusString; + static GetStatusStringBufLengthFuncPtr GetStatusStringBufLength; + static GetStringFuncPtr GetString; + static GetStringBatchFuncPtr GetStringBatch; + static GetStringBatchSizeFuncPtr GetStringBatchSize; + static GetStringBufLengthFuncPtr GetStringBufLength; + static GetSupportedImageFileFormatCountFuncPtr GetSupportedImageFileFormatCount; + static GetSupportedImageFileFormatsFuncPtr GetSupportedImageFileFormats; + static GetTimeFuncPtr GetTime; + static GetTimelineOptionsFuncPtr GetTimelineOptions; + static GetVertexListFuncPtr GetVertexList; + static GetVolumeBoundsFuncPtr GetVolumeBounds; + static GetVolumeInfoFuncPtr GetVolumeInfo; + static GetVolumeTileFloatDataFuncPtr GetVolumeTileFloatData; + static GetVolumeTileIntDataFuncPtr GetVolumeTileIntData; + static GetVolumeVoxelFloatDataFuncPtr GetVolumeVoxelFloatData; + static GetVolumeVoxelIntDataFuncPtr GetVolumeVoxelIntData; + static GetWorkitemDataLengthFuncPtr GetWorkitemDataLength; + static GetWorkitemFloatDataFuncPtr GetWorkitemFloatData; + static GetWorkitemInfoFuncPtr GetWorkitemInfo; + static GetWorkitemIntDataFuncPtr GetWorkitemIntData; + static GetWorkitemResultInfoFuncPtr GetWorkitemResultInfo; + static GetWorkitemStringDataFuncPtr GetWorkitemStringData; + static GetWorkitemsFuncPtr GetWorkitems; + static HandleBindingInfo_CreateFuncPtr HandleBindingInfo_Create; + static HandleBindingInfo_InitFuncPtr HandleBindingInfo_Init; + static HandleInfo_CreateFuncPtr HandleInfo_Create; + static HandleInfo_InitFuncPtr HandleInfo_Init; + static ImageFileFormat_CreateFuncPtr ImageFileFormat_Create; + static ImageFileFormat_InitFuncPtr ImageFileFormat_Init; + static ImageInfo_CreateFuncPtr ImageInfo_Create; + static ImageInfo_InitFuncPtr ImageInfo_Init; + static InitializeFuncPtr Initialize; + static InsertMultiparmInstanceFuncPtr InsertMultiparmInstance; + static InterruptFuncPtr Interrupt; + static IsInitializedFuncPtr IsInitialized; + static IsNodeValidFuncPtr IsNodeValid; + static IsSessionValidFuncPtr IsSessionValid; + static Keyframe_CreateFuncPtr Keyframe_Create; + static Keyframe_InitFuncPtr Keyframe_Init; + static LoadAssetLibraryFromFileFuncPtr LoadAssetLibraryFromFile; + static LoadAssetLibraryFromMemoryFuncPtr LoadAssetLibraryFromMemory; + static LoadGeoFromFileFuncPtr LoadGeoFromFile; + static LoadGeoFromMemoryFuncPtr LoadGeoFromMemory; + static LoadHIPFileFuncPtr LoadHIPFile; + static MaterialInfo_CreateFuncPtr MaterialInfo_Create; + static MaterialInfo_InitFuncPtr MaterialInfo_Init; + static NodeInfo_CreateFuncPtr NodeInfo_Create; + static NodeInfo_InitFuncPtr NodeInfo_Init; + static ObjectInfo_CreateFuncPtr ObjectInfo_Create; + static ObjectInfo_InitFuncPtr ObjectInfo_Init; + static ParmChoiceInfo_CreateFuncPtr ParmChoiceInfo_Create; + static ParmChoiceInfo_InitFuncPtr ParmChoiceInfo_Init; + static ParmHasExpressionFuncPtr ParmHasExpression; + static ParmHasTagFuncPtr ParmHasTag; + static ParmInfo_CreateFuncPtr ParmInfo_Create; + static ParmInfo_GetFloatValueCountFuncPtr ParmInfo_GetFloatValueCount; + static ParmInfo_GetIntValueCountFuncPtr ParmInfo_GetIntValueCount; + static ParmInfo_GetStringValueCountFuncPtr ParmInfo_GetStringValueCount; + static ParmInfo_InitFuncPtr ParmInfo_Init; + static ParmInfo_IsFloatFuncPtr ParmInfo_IsFloat; + static ParmInfo_IsIntFuncPtr ParmInfo_IsInt; + static ParmInfo_IsNodeFuncPtr ParmInfo_IsNode; + static ParmInfo_IsNonValueFuncPtr ParmInfo_IsNonValue; + static ParmInfo_IsPathFuncPtr ParmInfo_IsPath; + static ParmInfo_IsStringFuncPtr ParmInfo_IsString; + static PartInfo_CreateFuncPtr PartInfo_Create; + static PartInfo_GetAttributeCountByOwnerFuncPtr PartInfo_GetAttributeCountByOwner; + static PartInfo_GetElementCountByAttributeOwnerFuncPtr PartInfo_GetElementCountByAttributeOwner; + static PartInfo_GetElementCountByGroupTypeFuncPtr PartInfo_GetElementCountByGroupType; + static PartInfo_InitFuncPtr PartInfo_Init; + static PausePDGCookFuncPtr PausePDGCook; + static PythonThreadInterpreterLockFuncPtr PythonThreadInterpreterLock; + static QueryNodeInputFuncPtr QueryNodeInput; + static QueryNodeOutputConnectedCountFuncPtr QueryNodeOutputConnectedCount; + static QueryNodeOutputConnectedNodesFuncPtr QueryNodeOutputConnectedNodes; + static RemoveCustomStringFuncPtr RemoveCustomString; + static RemoveMultiparmInstanceFuncPtr RemoveMultiparmInstance; + static RemoveParmExpressionFuncPtr RemoveParmExpression; + static RenameNodeFuncPtr RenameNode; + static RenderCOPToImageFuncPtr RenderCOPToImage; + static RenderTextureToImageFuncPtr RenderTextureToImage; + static ResetSimulationFuncPtr ResetSimulation; + static RevertGeoFuncPtr RevertGeo; + static RevertParmToDefaultFuncPtr RevertParmToDefault; + static RevertParmToDefaultsFuncPtr RevertParmToDefaults; + static SaveGeoToFileFuncPtr SaveGeoToFile; + static SaveGeoToMemoryFuncPtr SaveGeoToMemory; + static SaveHIPFileFuncPtr SaveHIPFile; + static SetAnimCurveFuncPtr SetAnimCurve; + static SetAttributeFloat64DataFuncPtr SetAttributeFloat64Data; + static SetAttributeFloatDataFuncPtr SetAttributeFloatData; + static SetAttributeInt64DataFuncPtr SetAttributeInt64Data; + static SetAttributeIntDataFuncPtr SetAttributeIntData; + static SetAttributeStringDataFuncPtr SetAttributeStringData; + static SetCachePropertyFuncPtr SetCacheProperty; + static SetCurveCountsFuncPtr SetCurveCounts; + static SetCurveInfoFuncPtr SetCurveInfo; + static SetCurveKnotsFuncPtr SetCurveKnots; + static SetCurveOrdersFuncPtr SetCurveOrders; + static SetCustomStringFuncPtr SetCustomString; + static SetFaceCountsFuncPtr SetFaceCounts; + static SetGroupMembershipFuncPtr SetGroupMembership; + static SetHeightFieldDataFuncPtr SetHeightFieldData; + static SetImageInfoFuncPtr SetImageInfo; + static SetNodeDisplayFuncPtr SetNodeDisplay; + static SetObjectTransformFuncPtr SetObjectTransform; + static SetParmExpressionFuncPtr SetParmExpression; + static SetParmFloatValueFuncPtr SetParmFloatValue; + static SetParmFloatValuesFuncPtr SetParmFloatValues; + static SetParmIntValueFuncPtr SetParmIntValue; + static SetParmIntValuesFuncPtr SetParmIntValues; + static SetParmNodeValueFuncPtr SetParmNodeValue; + static SetParmStringValueFuncPtr SetParmStringValue; + static SetPartInfoFuncPtr SetPartInfo; + static SetPresetFuncPtr SetPreset; + static SetServerEnvIntFuncPtr SetServerEnvInt; + static SetServerEnvStringFuncPtr SetServerEnvString; + static SetTimeFuncPtr SetTime; + static SetTimelineOptionsFuncPtr SetTimelineOptions; + static SetTransformAnimCurveFuncPtr SetTransformAnimCurve; + static SetVertexListFuncPtr SetVertexList; + static SetVolumeInfoFuncPtr SetVolumeInfo; + static SetVolumeTileFloatDataFuncPtr SetVolumeTileFloatData; + static SetVolumeTileIntDataFuncPtr SetVolumeTileIntData; + static SetVolumeVoxelFloatDataFuncPtr SetVolumeVoxelFloatData; + static SetVolumeVoxelIntDataFuncPtr SetVolumeVoxelIntData; + static SetWorkitemFloatDataFuncPtr SetWorkitemFloatData; + static SetWorkitemIntDataFuncPtr SetWorkitemIntData; + static SetWorkitemStringDataFuncPtr SetWorkitemStringData; + static StartThriftNamedPipeServerFuncPtr StartThriftNamedPipeServer; + static StartThriftSocketServerFuncPtr StartThriftSocketServer; + static TimelineOptions_CreateFuncPtr TimelineOptions_Create; + static TimelineOptions_InitFuncPtr TimelineOptions_Init; + static TransformEuler_CreateFuncPtr TransformEuler_Create; + static TransformEuler_InitFuncPtr TransformEuler_Init; + static Transform_CreateFuncPtr Transform_Create; + static Transform_InitFuncPtr Transform_Init; + static VolumeInfo_CreateFuncPtr VolumeInfo_Create; + static VolumeInfo_InitFuncPtr VolumeInfo_Init; + static VolumeTileInfo_CreateFuncPtr VolumeTileInfo_Create; + static VolumeTileInfo_InitFuncPtr VolumeTileInfo_Init; + +public: + + static HAPI_Result AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + static HAPI_Result AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + static HAPI_AssetInfo AssetInfo_CreateEmptyStub(); + static void AssetInfo_InitEmptyStub(HAPI_AssetInfo * in); + static HAPI_AttributeInfo AttributeInfo_CreateEmptyStub(); + static void AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in); + static HAPI_Result BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path); + static HAPI_Result CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + static HAPI_Result CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); + static HAPI_Result CleanupEmptyStub(const HAPI_Session * session); + static HAPI_Result CloseSessionEmptyStub(const HAPI_Session * session); + static HAPI_Result CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + static HAPI_Result ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); + static HAPI_Result ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); + static HAPI_Result ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); + static HAPI_Result ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + static HAPI_Result ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); + static HAPI_Result ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + static HAPI_Result ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); + static HAPI_Result ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); + static HAPI_Result CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); + static HAPI_Bool CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right); + static HAPI_CookOptions CookOptions_CreateEmptyStub(); + static void CookOptions_InitEmptyStub(HAPI_CookOptions * in); + static HAPI_Result CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); + static HAPI_Result CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); + static HAPI_Result CreateHeightfieldInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); + static HAPI_Result CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); + static HAPI_Result CreateInProcessSessionEmptyStub(HAPI_Session * session); + static HAPI_Result CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); + static HAPI_Result CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); + static HAPI_Result CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name); + static HAPI_Result CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port); + static HAPI_Result CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); + static HAPI_CurveInfo CurveInfo_CreateEmptyStub(); + static void CurveInfo_InitEmptyStub(HAPI_CurveInfo * in); + static HAPI_Result DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + static HAPI_Result DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + static HAPI_Result DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); + static HAPI_Result DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); + static HAPI_Result DisconnectNodeOutputsAtEmptyStub(const HAPI_Session *session, HAPI_NodeId node_id, int output_index); + static HAPI_Result ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); + static HAPI_Result ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); + static HAPI_GeoInfo GeoInfo_CreateEmptyStub(); + static int GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type); + static void GeoInfo_InitEmptyStub(HAPI_GeoInfo * in); + static HAPI_Result GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count); + static HAPI_Result GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); + static HAPI_Result GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); + static HAPI_Result GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); + static HAPI_Result GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); + static HAPI_Result GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); + static HAPI_Result GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); + static HAPI_Result GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); + static HAPI_Result GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); + static HAPI_Result GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); + static HAPI_Result GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); + static HAPI_Result GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); + static HAPI_Result GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); + static HAPI_Result GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); + static HAPI_Result GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); + static HAPI_Result GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length); + static HAPI_Result GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); + static HAPI_Result GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); + static HAPI_Result GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count); + static HAPI_Result GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count); + static HAPI_Result GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); + static HAPI_Result GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); + static HAPI_Result GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); + static HAPI_Result GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); + static HAPI_Result GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); + static HAPI_Result GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value); + static HAPI_Result GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); + static HAPI_Result GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + static HAPI_Result GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); + static HAPI_Result GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); + static HAPI_Result GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); + static HAPI_Result GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + static HAPI_Result GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + static HAPI_Result GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + static HAPI_Result GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + static HAPI_Result GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); + static HAPI_Result GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); + static HAPI_Result GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); + static HAPI_Result GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); + static HAPI_Result GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); + static HAPI_Result GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); + static HAPI_Result GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); + static HAPI_Result GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); + static HAPI_Result GetInstanceTransformsOnPartEmptyStub(const HAPI_Session *session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform *transforms_array, int start, int length); + static HAPI_Result GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); + static HAPI_Result GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); + static HAPI_Result GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + static HAPI_Result GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); + static HAPI_Result GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); + static HAPI_Result GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); + static HAPI_Result GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + static HAPI_Result GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); + static HAPI_Result GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); + static HAPI_Result GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); + static HAPI_Result GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); + static HAPI_Result GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num); + static HAPI_Result GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); + static HAPI_Result GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); + static HAPI_Result GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); + static HAPI_Result GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); + static HAPI_Result GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); + static HAPI_Result GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); + static HAPI_Result GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); + static HAPI_Result GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo *parm_choices_array, int start, int length); + static HAPI_Result GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); + static HAPI_Result GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); + static HAPI_Result GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); + static HAPI_Result GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); + static HAPI_Result GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); + static HAPI_Result GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); + static HAPI_Result GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); + static HAPI_Result GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); + static HAPI_Result GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); + static HAPI_Result GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); + static HAPI_Result GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); + static HAPI_Result GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); + static HAPI_Result GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); + static HAPI_Result GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); + static HAPI_Result GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); + static HAPI_Result GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); + static HAPI_Result GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); + static HAPI_Result GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); + static HAPI_Result GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value); + static HAPI_Result GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); + static HAPI_Result GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count); + static HAPI_Result GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); + static HAPI_Result GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); + static HAPI_Result GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); + static HAPI_Result GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status); + static HAPI_Result GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); + static HAPI_Result GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); + static HAPI_Result GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); + static HAPI_Result GetStringBatchEmptyStub(const HAPI_Session * session, char * char_array, int char_array_length); + static HAPI_Result GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int* string_buffer_size); + static HAPI_Result GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); + static HAPI_Result GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count); + static HAPI_Result GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); + static HAPI_Result GetTimeEmptyStub(const HAPI_Session * session, float * time); + static HAPI_Result GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); + static HAPI_Result GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); + static HAPI_Result GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); + static HAPI_Result GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); + static HAPI_Result GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); + static HAPI_Result GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); + static HAPI_Result GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); + static HAPI_Result GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); + static HAPI_Result GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); + static HAPI_Result GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); + static HAPI_Result GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); + static HAPI_Result GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char* data_name, int * data_array, int length); + static HAPI_Result GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); + static HAPI_Result GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); + static HAPI_Result GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); + static HAPI_HandleBindingInfo HandleBindingInfo_CreateEmptyStub(); + static void HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in); + static HAPI_HandleInfo HandleInfo_CreateEmptyStub(); + static void HandleInfo_InitEmptyStub(HAPI_HandleInfo * in); + static HAPI_ImageFileFormat ImageFileFormat_CreateEmptyStub(); + static void ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in); + static HAPI_ImageInfo ImageInfo_CreateEmptyStub(); + static void ImageInfo_InitEmptyStub(HAPI_ImageInfo * in); + static HAPI_Result InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); + static HAPI_Result InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + static HAPI_Result InterruptEmptyStub(const HAPI_Session * session); + static HAPI_Result IsInitializedEmptyStub(const HAPI_Session * session); + static HAPI_Result IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); + static HAPI_Result IsSessionValidEmptyStub(const HAPI_Session * session); + static HAPI_Keyframe Keyframe_CreateEmptyStub(); + static void Keyframe_InitEmptyStub(HAPI_Keyframe * in); + static HAPI_Result LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId* library_id); + static HAPI_Result LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + static HAPI_Result LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + static HAPI_Result LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); + static HAPI_Result LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); + static HAPI_MaterialInfo MaterialInfo_CreateEmptyStub(); + static void MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in); + static HAPI_NodeInfo NodeInfo_CreateEmptyStub(); + static void NodeInfo_InitEmptyStub(HAPI_NodeInfo * in); + static HAPI_ObjectInfo ObjectInfo_CreateEmptyStub(); + static void ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in); + static HAPI_ParmChoiceInfo ParmChoiceInfo_CreateEmptyStub(); + static void ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in); + static HAPI_Result ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); + static HAPI_Result ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); + static HAPI_ParmInfo ParmInfo_CreateEmptyStub(); + static int ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in); + static int ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in); + static int ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in); + static void ParmInfo_InitEmptyStub(HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in); + static HAPI_PartInfo PartInfo_CreateEmptyStub(); + static int PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + static int PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + static int PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type); + static void PartInfo_InitEmptyStub(HAPI_PartInfo * in); + static HAPI_Result PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + static HAPI_Result PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked); + static HAPI_Result QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); + static HAPI_Result QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session *session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); + static HAPI_Result QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session *session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); + static HAPI_Result RemoveCustomStringEmptyStub(const HAPI_Session * session, const int string_handle); + static HAPI_Result RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + static HAPI_Result RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); + static HAPI_Result RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); + static HAPI_Result RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id); + static HAPI_Result RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); + static HAPI_Result ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); + static HAPI_Result RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); + static HAPI_Result SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + static HAPI_Result SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); + static HAPI_Result SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); + static HAPI_Result SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + static HAPI_Result SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); + static HAPI_Result SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); + static HAPI_Result SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); + static HAPI_Result SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); + static HAPI_Result SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo *attr_info, const char ** data_array, int start, int length); + static HAPI_Result SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); + static HAPI_Result SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); + static HAPI_Result SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); + static HAPI_Result SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); + static HAPI_Result SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); + static HAPI_Result SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, int *handle_value); + static HAPI_Result SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); + static HAPI_Result SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); + static HAPI_Result SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); + static HAPI_Result SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); + static HAPI_Result SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); + static HAPI_Result SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); + static HAPI_Result SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + static HAPI_Result SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); + static HAPI_Result SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); + static HAPI_Result SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); + static HAPI_Result SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); + static HAPI_Result SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); + static HAPI_Result SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + static HAPI_Result SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); + static HAPI_Result SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); + static HAPI_Result SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value); + static HAPI_Result SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value); + static HAPI_Result SetTimeEmptyStub(const HAPI_Session * session, float time); + static HAPI_Result SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); + static HAPI_Result SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + static HAPI_Result SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); + static HAPI_Result SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); + static HAPI_Result SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); + static HAPI_Result SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); + static HAPI_Result SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); + static HAPI_Result SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); + static HAPI_Result SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); + static HAPI_Result SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); + static HAPI_Result SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); + static HAPI_Result StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); + static HAPI_Result StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); + static HAPI_TimelineOptions TimelineOptions_CreateEmptyStub(); + static void TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in); + static HAPI_TransformEuler TransformEuler_CreateEmptyStub(); + static void TransformEuler_InitEmptyStub(HAPI_TransformEuler * in); + static HAPI_Transform Transform_CreateEmptyStub(); + static void Transform_InitEmptyStub(HAPI_Transform * in); + static HAPI_VolumeInfo VolumeInfo_CreateEmptyStub(); + static void VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in); + static HAPI_VolumeTileInfo VolumeTileInfo_CreateEmptyStub(); + static void VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in); +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniAsset.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniAsset.h new file mode 100644 index 00000000..ab5de392 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniAsset.h @@ -0,0 +1,124 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once +#include "UObject/Object.h" +#include "HAPI.h" +#include "HoudiniAsset.generated.h" + + +class UThumbnailInfo; +class UAssetImportData; +class UHoudiniAssetComponent; + + +UCLASS( EditInlineNew, config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniAsset : public UObject +{ + GENERATED_UCLASS_BODY() + + /** UObject methods. **/ + public: + + virtual void FinishDestroy() override; + virtual void Serialize( FArchive & Ar ) override; + virtual void GetAssetRegistryTags( TArray< FAssetRegistryTag > & OutTags ) const override; + + public: + + /** Initialize this asset from given buffer / file. **/ + void CreateAsset( const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName ); + + /** Return buffer containing the raw Houdini OTL data. **/ + const uint8* GetAssetBytes() const; + + /** Return path of the corresponding OTL/HDA file. **/ + const FString& GetAssetFileName() const; + + /** Return the size in bytes of raw Houdini OTL data. **/ + uint32 GetAssetBytesCount() const; + + /** Returns true if this asset contains Houdini logo. **/ + bool IsPreviewHoudiniLogo() const; + + /** Return true if this asset is a limited commercial asset. **/ + bool IsAssetLimitedCommercial() const; + + /** Return true if this asset is a non commercial asset. **/ + bool IsAssetNonCommercial() const; + + /** Retrieves list of asset names contained within the HDA. **/ + bool GetAssetNames( HAPI_AssetLibraryId & AssetLibraryId, TArray< HAPI_StringHandle > & AssetNames ); + + public: + + /** Filename of the OTL. **/ + UPROPERTY() + FString AssetFileName; + + /** Information for thumbnail rendering. */ + UPROPERTY() + UThumbnailInfo * ThumbnailInfo; + +#if WITH_EDITORONLY_DATA + + /** Importing data and options used for this Houdini asset. */ + UPROPERTY( Category = ImportSettings, VisibleAnywhere, Instanced ) + UAssetImportData * AssetImportData; + +#endif + + protected: + + /** Current version of the asset. **/ + static const uint32 PersistenceFormatVersion; + + protected: + + /** Buffer containing raw Houdini OTL data. **/ + uint8 * AssetBytes; + + /** Field containing the size of raw Houdini OTL data in bytes. **/ + uint32 AssetBytesCount; + + /** Version of the asset file format. **/ + uint32 FileFormatVersion; + + /** Flags used by this asset. **/ + union + { + struct + { + /** Flag which is set to true when preview geometry contains Houdini logo. **/ + uint32 bPreviewHoudiniLogo : 1; + + /** Flag which indicates if this is a limited commercial license asset. **/ + uint32 bAssetLimitedCommercial : 1; + + /** Flag which indicates if this is a non-commercial license asset. **/ + uint32 bAssetNonCommercial : 1; + }; + + uint32 HoudiniAssetFlagsPacked; + }; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniCookHandler.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniCookHandler.h new file mode 100644 index 00000000..d050ad8c --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniCookHandler.h @@ -0,0 +1,98 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "CoreGlobals.h" +#include "Containers/UnrealString.h" +#include "Misc/Guid.h" + +/** Used to control behavior of package baking helper functions */ +enum class EBakeMode +{ + Intermediate, + CreateNewAssets, + ReplaceExisitingAssets, + CookToTemp +}; + +class HOUDINIENGINERUNTIME_API IHoudiniCookHandler +{ +public: + virtual FString GetBakingBaseName( const struct FHoudiniGeoPartObject& GeoPartObject ) const = 0; + virtual void SetStaticMeshGenerationParameters( class UStaticMesh* StaticMesh ) const = 0; + virtual class UMaterialInterface * GetAssignmentMaterial( const FString& MaterialName ) = 0; + virtual void ClearAssignmentMaterials() = 0; + virtual void AddAssignmentMaterial( const FString& MaterialName, class UMaterialInterface* MaterialInterface ) = 0; + virtual class UMaterialInterface * GetReplacementMaterial( const struct FHoudiniGeoPartObject& GeoPartObject, const FString& MaterialName) = 0; +}; + +struct HOUDINIENGINERUNTIME_API FHoudiniCookParams +{ + FHoudiniCookParams(class UHoudiniAsset* InHoudiniAsset); + FHoudiniCookParams(class UHoudiniAssetComponent* HoudiniAssetComponent); + + // Helper functions returning the default behavior expected when cooking mesh + static EBakeMode GetDefaultStaticMeshesCookMode() { return EBakeMode::Intermediate; }; + // Helper functions returning the default behavior expected when cooking materials or textures + static EBakeMode GetDefaultMaterialAndTextureCookMode() { return EBakeMode::CookToTemp; }; + + + // Members + + // The attached HoudiniAsset + class UHoudiniAsset* HoudiniAsset = nullptr; + + // Object that handles material and mesh caches as they are built + IHoudiniCookHandler* HoudiniCookManager = nullptr; + EBakeMode StaticMeshBakeMode = GetDefaultStaticMeshesCookMode(); + EBakeMode MaterialAndTextureBakeMode = GetDefaultMaterialAndTextureCookMode(); + + // GUID used to decorate package names in intermediate mode + FGuid PackageGUID; + + // Transient cache of last baked parts + TMap >* BakedStaticMeshPackagesForParts = nullptr; + // Transient cache of last baked materials and textures + TMap >* BakedMaterialPackagesForIds = nullptr; + + // Cache of the temp cook content packages created by the asset for its materials/textures + TMap >* CookedTemporaryStaticMeshPackages = nullptr; + // Cache of the temp cook content packages created by the asset for its materials/textures + TMap >* CookedTemporaryPackages = nullptr; + /** Cache of the temp cook content packages created by the asset for its Landscape layers **/ + /** As packages are unique their are used as the key (we can have multiple package for the same geopartobj **/ + TMap< TWeakObjectPtr, FHoudiniGeoPartObject >* CookedTemporaryLandscapeLayers = nullptr; + + // When cooking in temp mode - folder to create assets in + FText TempCookFolder; + // When cooking in bake mode - folder to create assets in + FText BakeFolder; + /** overrides for baking names per part */ + TMap< FHoudiniGeoPartObject, FString >* BakeNameOverrides = nullptr; + + // When cooking in intermediate mode - uobject to use as outer + class UObject* IntermediateOuter = nullptr; + + int32 GeneratedDistanceFieldResolutionScale = 0; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniEngineString.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniEngineString.h new file mode 100644 index 00000000..5c3197c4 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniEngineString.h @@ -0,0 +1,69 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +class FText; +class FString; +class FName; + +#include "CoreTypes.h" +#include + +class HOUDINIENGINERUNTIME_API FHoudiniEngineString +{ + public: + + FHoudiniEngineString(); + FHoudiniEngineString( int32 InStringId ); + FHoudiniEngineString( const FHoudiniEngineString & Other ); + + public: + + FHoudiniEngineString & operator=( const FHoudiniEngineString & Other ); + + public: + + bool operator==( const FHoudiniEngineString & Other ) const; + bool operator!=( const FHoudiniEngineString & Other ) const; + + public: + + bool ToStdString( std::string & String ) const; + bool ToFName( FName & Name ) const; + bool ToFString( FString & String ) const; + bool ToFText( FText & Text ) const; + + public: + + /** Return id of this string. **/ + int32 GetId() const; + + /** Return true if this string has a valid id. **/ + bool HasValidId() const; + + protected: + + /** Id of the underlying Houdini Engine string. **/ + int32 StringId; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniEngineTask.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniEngineTask.h new file mode 100644 index 00000000..8194f571 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniEngineTask.h @@ -0,0 +1,77 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniApi.h" +#include "CoreMinimal.h" +#include "Misc/Guid.h" +#include "UObject/WeakObjectPtr.h" + +namespace EHoudiniEngineTaskType +{ + enum Type + { + None, + + /** This type corresponds to Houdini asset instantiation (without cooking). **/ + AssetInstantiation, + + /** This type corresponds to Houdini asset cooking request. **/ + AssetCooking, + + /** This type is used for asynchronous asset deletion. **/ + AssetDeletion + }; +} + +struct HOUDINIENGINERUNTIME_API FHoudiniEngineTask +{ + /** Constructors. **/ + FHoudiniEngineTask(); + FHoudiniEngineTask( EHoudiniEngineTaskType::Type InTaskType, FGuid InHapiGUID ); + + /** GUID of this request. **/ + FGuid HapiGUID; + + /** Type of this task. **/ + EHoudiniEngineTaskType::Type TaskType; + + /** Houdini asset for instantiation. **/ + TWeakObjectPtr< class UHoudiniAsset > Asset; + + /** Name of the actor requesting this task. **/ + FString ActorName; + + /** Asset Id. **/ + HAPI_NodeId AssetId; + + /** Library Id. **/ + HAPI_AssetLibraryId AssetLibraryId; + + /** HAPI name of the asset. **/ + int32 AssetHapiName; + + /** Is set to true if component has been loaded. **/ + bool bLoadedComponent; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniEngineTaskInfo.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniEngineTaskInfo.h new file mode 100644 index 00000000..e26e5b30 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniEngineTaskInfo.h @@ -0,0 +1,70 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HoudiniEngineTask.h" + + +namespace EHoudiniEngineTaskState +{ + enum Type + { + None, + + Processing, + FinishedInstantiation, + FinishedInstantiationWithErrors, + FinishedCooking, + FinishedCookingWithErrors, + Aborted + }; +} + +struct HOUDINIENGINERUNTIME_API FHoudiniEngineTaskInfo +{ + /** Constructors. **/ + FHoudiniEngineTaskInfo(); + FHoudiniEngineTaskInfo( + HAPI_Result InResult, HAPI_NodeId InAssetId, + EHoudiniEngineTaskType::Type InTaskType, + EHoudiniEngineTaskState::Type InTaskState ); + + /** Current HAPI result. **/ + HAPI_Result Result; + + /** Current Asset Id. **/ + HAPI_NodeId AssetId; + + /** Type of task. **/ + EHoudiniEngineTaskType::Type TaskType; + + /** Current status. **/ + EHoudiniEngineTaskState::Type TaskState; + + /** String used for status / progress bar. **/ + FText StatusText; + + /** Is set to true if corresponding task was issued for loaded component. **/ + bool bLoadedComponent; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniGeoPartObject.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniGeoPartObject.h new file mode 100644 index 00000000..43c039ea --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/HoudiniGeoPartObject.h @@ -0,0 +1,658 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "HAPI_Common.h" +#include "CoreMinimal.h" +#include + + +class FArchive; +struct FTransform; +struct HAPI_GeoInfo; +struct HAPI_PartInfo; +struct HAPI_ObjectInfo; +class FHoudiniEngineString; + +struct HOUDINIENGINERUNTIME_API FHoudiniGeoPartObject +{ + public: + + /** Constructors. **/ + FHoudiniGeoPartObject(); + FHoudiniGeoPartObject( HAPI_NodeId InAssetId, HAPI_NodeId InObjectId, HAPI_NodeId InGeoId, HAPI_PartId InPartId ); + + FHoudiniGeoPartObject( + const FTransform & InTransform, HAPI_NodeId InAssetId, const HAPI_ObjectInfo & ObjectInfo, + const HAPI_GeoInfo & GeoInfo, const HAPI_PartInfo & PartInfo ); + + FHoudiniGeoPartObject( + const FTransform & InTransform, const FString & InObjectName, const FString & InPartName, + HAPI_NodeId InAssetId, HAPI_NodeId InObjectId, HAPI_NodeId InGeoId, HAPI_PartId InPartId ); + + FHoudiniGeoPartObject( const FHoudiniGeoPartObject & GeoPartObject, bool bCopyLoaded = false ); + + public: + + /** Return hash value for this object, used when using this object as a key inside hashing containers. **/ + uint32 GetTypeHash() const; + + /** Comparison operator, used by hashing containers. **/ + bool operator==( const FHoudiniGeoPartObject & HoudiniGeoPartObject ) const; + + /** Compare based on object and part name. **/ + bool CompareNames( const FHoudiniGeoPartObject & HoudiniGeoPartObject ) const; + + /** Serialization. **/ + void Serialize( FArchive & Ar ); + + /** Return true if this geo part object corresponds to a valid HAPI object. **/ + bool IsValid() const; + + /** Return true if this geo part object corresponds to an instancer. **/ + bool IsInstancer() const; + + /** Return true if this geo part object is visible. **/ + bool IsVisible() const; + + /** Return true if this geo part object is a curve. **/ + bool IsCurve() const; + + /** Return true if this geo part object is a box. **/ + bool IsBox() const; + + /** Return true if this geo part object is a sphere. **/ + bool IsSphere() const; + + /** Return true if this geo part object is a volume. **/ + bool IsVolume() const; + + /** Return true if this geo part object is editable. **/ + bool IsEditable() const; + + /** Return true if this geo part is used for collision. **/ + bool IsCollidable() const; + + /** Return true if this geo part is used for collision and is renderable. **/ + bool IsRenderCollidable() const; + + /** Return true if this geo part object corresponds to a packed primitive instancer **/ + bool IsPackedPrimitiveInstancer() const; + + /** Return true if this geo part object corresponds to an attribute instancer **/ + bool IsAttributeInstancer() const; + + /** Return true if this geo part object corresponds to an attribute override instancer **/ + bool IsAttributeOverrideInstancer() const; + + /** Return true if corresponding geometry has changed. **/ + bool HasGeoChanged() const; + + /** Return true if this object has a custom name. **/ + bool HasCustomName() const; + + /** Set custom name. **/ + void SetCustomName( const FString & CustomName ); + + /** Updates the HGPO's custom name, returns true if a custom name was found **/ + bool UpdateCustomName(); + + public: + + /** Return object id. **/ + HAPI_NodeId GetObjectId() const; + + /** Return geo id. **/ + HAPI_NodeId GetGeoId() const; + + /** Return part id. **/ + HAPI_PartId GetPartId() const; + + /** Return the unique path to this part's node */ + const FString& GetNodePath() const; + + /** HAPI: Other helpers. **/ + public: + + /** HAPI: Return true if given attribute exists on a given owner. **/ + bool HapiCheckAttributeExistance( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeOwner AttributeOwner ) const; + bool HapiCheckAttributeExistance( + const FString & AttributeName, HAPI_AttributeOwner AttributeOwner ) const; + bool HapiCheckAttributeExistance( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeOwner AttributeOwner ) const; + bool HapiCheckAttributeExistance( + const std::string & AttributeName, HAPI_AttributeOwner AttributeOwner ) const; + bool HapiCheckAttributeExistance( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeOwner AttributeOwner ) const; + bool HapiCheckAttributeExistance( + const char * AttributeName, HAPI_AttributeOwner AttributeOwner ) const; + + /** HAPI: Get instance transformations. **/ + bool HapiGetInstanceTransforms( HAPI_NodeId OtherAssetId, TArray< FTransform > & AllTransforms ) const; + bool HapiGetInstanceTransforms( TArray< FTransform > & AllTransforms ) const; + + /** HAPI: Get Attribute info on a specified owner. **/ + bool HapiGetAttributeInfo( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & AttributeInfo ) const; + bool HapiGetAttributeInfo( + const char * AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & AttributeInfo ) const; + bool HapiGetAttributeInfo( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & AttributeInfo ) const; + bool HapiGetAttributeInfo( + const std::string & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & AttributeInfo ) const; + bool HapiGetAttributeInfo( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & AttributeInfo ) const; + bool HapiGetAttributeInfo( + const FString & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & AttributeInfo ) const; + + /** HAPI: Get Attribute info on any owner. **/ + bool HapiGetAttributeInfo( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeInfo & AttributeInfo ) const; + bool HapiGetAttributeInfo( + const char * AttributeName, HAPI_AttributeInfo & AttributeInfo ) const; + bool HapiGetAttributeInfo( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeInfo & AttributeInfo ) const; + bool HapiGetAttributeInfo( + const std::string & AttributeName, HAPI_AttributeInfo & AttributeInfo ) const; + bool HapiGetAttributeInfo( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeInfo & AttributeInfo ) const; + bool HapiGetAttributeInfo( + const FString & AttributeName, HAPI_AttributeInfo & AttributeInfo ) const; + + /** HAPI: Get attribute float data on a specified owner. **/ + bool HapiGetAttributeDataAsFloat( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsFloat( + const char * AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsFloat( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, + int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsFloat( + const std::string & AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsFloat( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, + int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsFloat( + const FString & AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, int32 TupleSize = 0 ) const; + + /** HAPI: Get attribute float data on any owner. **/ + bool HapiGetAttributeDataAsFloat( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< float > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsFloat( + const char * AttributeName, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsFloat( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< float > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsFloat( + const std::string & AttributeName, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsFloat( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, TArray< float > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsFloat( + const FString & AttributeName, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< float > & AttributeData, int32 TupleSize = 0 ) const; + + /** HAPI: Get attribute int data on a specified owner. **/ + bool HapiGetAttributeDataAsInt( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsInt( + const char * AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsInt( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsInt( + const std::string & AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsInt( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsInt( + const FString & AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize = 0 ) const; + + /** HAPI: Get attribute int data on any owner. **/ + bool HapiGetAttributeDataAsInt( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsInt( + const char* AttributeName, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsInt( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsInt( + const std::string & AttributeName, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsInt( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsInt( + const FString & AttributeName, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< int32 > & AttributeData, int32 TupleSize = 0 ) const; + + /** HAPI: Get attribute string data on a specified owner. **/ + bool HapiGetAttributeDataAsString( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsString( + const char * AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsString( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsString( + const std::string & AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsString( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeOwner AttributeOwner, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsString( + const FString & AttributeName, HAPI_AttributeOwner AttributeOwner, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize = 0 ) const; + + /** HAPI: Get attribute string data on any owner. **/ + bool HapiGetAttributeDataAsString( + HAPI_NodeId OtherAssetId, const char * AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsString( + const char * AttributeName, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsString( + HAPI_NodeId OtherAssetId, const std::string & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsString( + const std::string & AttributeName, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsString( + HAPI_NodeId OtherAssetId, const FString & AttributeName, + HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize = 0 ) const; + bool HapiGetAttributeDataAsString( + const FString & AttributeName, HAPI_AttributeInfo & ResultAttributeInfo, + TArray< FString > & AttributeData, int32 TupleSize = 0 ) const; + + /** HAPI: Get names of all attributes on all owners. **/ + bool HapiGetAllAttributeNames( HAPI_NodeId OtherAssetId, TArray< FString > & AttributeNames ) const; + bool HapiGetAllAttributeNames( TArray< FString > & AttributeNames ) const; + + /** HAPI: Get names of all attributes on a given owner. **/ + bool HapiGetAttributeNames( + HAPI_NodeId OtherAssetId, HAPI_AttributeOwner AttributeOwner, + TArray< FString > & AttributeNames ) const; + bool HapiGetAttributeNames( HAPI_AttributeOwner AttributeOwner, TArray< FString > & AttributeNames ) const; + + /** HAPI: Get attribute names on point, vertex, detail or primitive. **/ + bool HapiGetPointAttributeNames( HAPI_NodeId OtherAssetId, TArray< FString > & AttributeNames) const; + bool HapiGetPointAttributeNames( TArray< FString > & AttributeNames ) const; + bool HapiGetVertexAttributeNames( HAPI_NodeId OtherAssetId, TArray< FString > & AttributeNames ) const; + bool HapiGetVertexAttributeNames( TArray< FString > & AttributeNames ) const; + bool HapiGetPrimitiveAttributeNames( HAPI_NodeId OtherAssetId, TArray< FString > & AttributeNames ) const; + bool HapiGetPrimitiveAttributeNames( TArray< FString > & AttributeNames ) const; + bool HapiGetDetailAttributeNames( HAPI_NodeId OtherAssetId, TArray< FString > & AttributeNames ) const; + bool HapiGetDetailAttributeNames( TArray< FString > & AttributeNames ) const; + + /** HAPI: Object related getters. **/ + public: + + /** HAPI: Retrieve corresponding object info structure. **/ + bool HapiObjectGetInfo( HAPI_ObjectInfo & ObjectInfo ) const; + bool HapiObjectGetInfo( HAPI_NodeId OtherAssetId, HAPI_ObjectInfo & ObjectInfo ) const; + + /** HAPI: If this is an instancer, return id of instanced object. Return -1 if no such object is found. **/ + HAPI_NodeId HapiObjectGetToInstanceId() const; + HAPI_NodeId HapiObjectGetToInstanceId( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return object name. **/ + FHoudiniEngineString HapiObjectGetName() const; + FHoudiniEngineString HapiObjectGetName( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return object instance path. **/ + FHoudiniEngineString HapiObjectGetInstancePath() const; + FHoudiniEngineString HapiObjectGetInstancePath( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return true if object is visible. **/ + bool HapiObjectIsVisible() const; + bool HapiObjectIsVisible( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return true if object is an instancer. **/ + bool HapiObjectIsInstancer() const; + bool HapiObjectIsInstancer( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return true if object transform has changed. **/ + bool HapiObjectHasTransformChanged() const; + bool HapiObjectHasTransformChanged( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return true if any of the underlying geos have changed. **/ + bool HapiObjectHaveGeosChanged() const; + bool HapiObjectHaveGeosChanged( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Get number of geos. **/ + int32 HapiObjectGetGeoCount() const; + int32 HapiObjectGetGeoCount( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Get associated node id. This corresponds to id of an obj node. **/ + HAPI_NodeId HapiObjectGetNodeId() const; + HAPI_NodeId HapiObjectGetNodeId( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return unique material id associated with an instancer. **/ + bool HapiObjectGetUniqueInstancerMaterialId( HAPI_NodeId & MaterialId ) const; + bool HapiObjectGetUniqueInstancerMaterialId( HAPI_NodeId OtherAssetId, HAPI_NodeId & MaterialId ) const; + + /** HAPI: Geo related getters. **/ + public: + + /** HAPI: Retrieve corresponding geo info structure. **/ + bool HapiGeoGetInfo( HAPI_GeoInfo & GeoInfo ) const; + bool HapiGeoGetInfo( HAPI_NodeId OtherAssetId, HAPI_GeoInfo & GeoInfo ) const; + + /** HAPI: Return geo type. **/ + HAPI_GeoType HapiGeoGetType() const; + HAPI_GeoType HapiGeoGetType( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return name of this geo. **/ + FHoudiniEngineString HapiGeoGetName() const; + FHoudiniEngineString HapiGeoGetName( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return geo node id. This corresponds to id of a sop node. **/ + HAPI_NodeId HapiGeoGetNodeId() const; + HAPI_NodeId HapiGeoGetNodeId( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return true if this geo is editable. **/ + bool HapiGeoIsEditable() const; + bool HapiGeoIsEditable( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return true if this geo is templated. **/ + bool HapiGeoIsTemplated() const; + bool HapiGeoIsTemplated( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return true if this is a display sop geo. **/ + bool HapiGeoIsDisplayGeo() const; + bool HapiGeoIsDisplayGeo( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return true if geo has changed. **/ + bool HapiGeoHasChanged() const; + bool HapiGeoHasChanged( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return true if material on this geo has changed. **/ + bool HapiGeoHasMaterialChanged() const; + bool HapiGeoHasMaterialChanged( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return number of point groups. **/ + int32 HapiGeoGetPointGroupCount() const; + int32 HapiGeoGetPointGroupCount( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return number of primitive groups. **/ + int32 HapiGeoGetPrimitiveGroupCount() const; + int32 HapiGeoGetPrimitiveGroupCount( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return number of parts within this geo. **/ + int32 HapiGeoGetPartCount() const; + int32 HapiGeoGetPartCount( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Part related getters. **/ + public: + + /** HAPI: Retrieve corresponding part info structure. **/ + bool HapiPartGetInfo( HAPI_PartInfo & PartInfo ) const; + bool HapiPartGetInfo( HAPI_NodeId OtherAssetId, HAPI_PartInfo & PartInfo ) const; + + /** HAPI: Get name of this part. **/ + FHoudiniEngineString HapiPartGetName() const; + FHoudiniEngineString HapiPartGetName( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return part type. **/ + HAPI_PartType HapiPartGetType() const; + HAPI_PartType HapiPartGetType( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return face count. **/ + int32 HapiPartGetFaceCount() const; + int32 HapiPartGetFaceCount( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return vertex count. **/ + int32 HapiPartGetVertexCount() const; + int32 HapiPartGetVertexCount( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return point count. **/ + int32 HapiPartGetPointCount() const; + int32 HapiPartGetPointCount( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return true if this part is used by an instancer. **/ + bool HapiPartIsInstanced() const; + bool HapiPartIsInstanced( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Number of parts this instancer part is instancing. **/ + int32 HapiPartGetInstancedPartCount() const; + int32 HapiPartGetInstancedPartCount( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Number of instances that this instancer part is instancing. **/ + int32 HapiPartGetInstanceCount() const; + int32 HapiPartGetInstanceCount( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Get number of point attributes. **/ + int32 HapiPartGetPointAttributeCount() const; + int32 HapiPartGetPointAttributeCount( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Get number of vertex attributes. **/ + int32 HapiPartGetVertexAttributeCount() const; + int32 HapiPartGetVertexAttributeCount( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Get number of primitive attributes. **/ + int32 HapiPartGetPrimitiveAttributeCount() const; + int32 HapiPartGetPrimitiveAttributeCount( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Get number of detail attributes. **/ + int32 HapiPartGetDetailAttributeCount() const; + int32 HapiPartGetDetailAttributeCount( HAPI_NodeId OtherAssetId ) const; + + /** HAPI: Return unique material ids associated with this part. **/ + bool HapiPartGetUniqueMaterialIds( TSet< HAPI_NodeId > & MaterialIds ) const; + bool HapiPartGetUniqueMaterialIds( HAPI_NodeId OtherAssetId, TSet< HAPI_NodeId > & MaterialIds ) const; + + public: + + /** Return list of vertices associated with this geo part object. **/ + bool HapiGetVertices( HAPI_NodeId OtherAssetId, TArray< int32 > & Vertices ) const; + bool HapiGetVertices( TArray< int32 > & Vertices ) const; + + public: + + /** Return true if this geo part has parameters. **/ + bool HasParameters() const; + bool HasParameters( HAPI_NodeId InAssetId ) const; + + public: + + /** Transform of this geo part object. **/ + FTransform TransformMatrix; + + /** Name of associated object. **/ + FString ObjectName; + + /** Name of associated part. **/ + FString PartName; + + /** Name of group which was used for splitting, empty if there's none. **/ + FString SplitName; + + /** Name of the instancer material, if available. **/ + FString InstancerMaterialName; + + /** Name of attribute material, if available. **/ + FString InstancerAttributeMaterialName; + + /** Id of corresponding HAPI Asset. **/ + HAPI_NodeId AssetId; + + /** Id of corresponding HAPI Object. **/ + HAPI_NodeId ObjectId; + + /** Id of corresponding HAPI Geo. **/ + HAPI_NodeId GeoId; + + /** Id of corresponding HAPI Part. **/ + HAPI_PartId PartId; + + /** Id of a split. In most cases this will be 0. **/ + int32 SplitId; + + /** Path to the corresponding node */ + mutable FString NodePath; + + /** Flags used by geo part object. **/ + union + { + struct + { + /* Is set to true when referenced object is visible. This is typically used by instancers. **/ + uint32 bIsVisible : 1; + + /** Is set to true when referenced object is an instancer. **/ + uint32 bIsInstancer : 1; + + /** Is set to true when referenced object is a curve. **/ + uint32 bIsCurve : 1; + + /** Is set to true when referenced object is editable. **/ + uint32 bIsEditable : 1; + + /** Is set to true when geometry has changed. **/ + uint32 bHasGeoChanged : 1; + + /** Is set to true when referenced object is collidable. **/ + uint32 bIsCollidable : 1; + + /** Is set to true when referenced object is collidable and is renderable. **/ + uint32 bIsRenderCollidable : 1; + + /** Is set to true when referenced object has just been loaded. **/ + uint32 bIsLoaded : 1; + + /** Unused flags. **/ + uint32 bPlaceHolderFlags : 3; + + /** Is set to true when referenced object has been loaded during transaction. **/ + uint32 bIsTransacting : 1; + + /** Is set to true when referenced object has a custom name. **/ + uint32 bHasCustomName : 1; + + /** Is set to true when referenced object is a box. **/ + uint32 bIsBox : 1; + + /** Is set to true when referenced object is a sphere. **/ + uint32 bIsSphere : 1; + + /** Is set to true when instancer material is available. **/ + uint32 bInstancerMaterialAvailable : 1; + + /** Is set to true when referenced object is a volume. **/ + uint32 bIsVolume : 1; + + /** Is set to true when instancer attribute material is available. **/ + uint32 bInstancerAttributeMaterialAvailable : 1; + + /** Is set when referenced object contains packed primitive instancing */ + uint32 bIsPackedPrimitiveInstancer : 1; + + /** Is set to true when referenced object is a UCX collision geo. **/ + uint32 bIsUCXCollisionGeo : 1; + + /** Is set to true when referenced object is a rendered UCX collision geo. **/ + uint32 bIsSimpleCollisionGeo : 1; + + /** Is set to true when new collision geo has been generated **/ + uint32 bHasCollisionBeenAdded : 1; + + /** Is set to true when new sockets have been added **/ + uint32 bHasSocketBeenAdded : 1; + + /** unused flag space is zero initialized */ + uint32 UnusedFlagsSpace : 14; + }; + + uint32 HoudiniGeoPartObjectFlagsPacked; + }; + + /** Temporary variable holding serialization version. **/ + uint32 HoudiniGeoPartObjectVersion; +}; + +/** Function used by hashing containers to create a unique hash for this type of object. **/ +HOUDINIENGINERUNTIME_API uint32 GetTypeHash( const FHoudiniGeoPartObject & HoudiniGeoPartObject ); + +/** Serialization function. **/ +HOUDINIENGINERUNTIME_API FArchive& operator<<( FArchive & Ar, FHoudiniGeoPartObject & HoudiniGeoPartObject ); + +/** Functor used to sort geo part objects. **/ +struct HOUDINIENGINERUNTIME_API FHoudiniGeoPartObjectSortPredicate +{ + bool operator()( const FHoudiniGeoPartObject & A, const FHoudiniGeoPartObject & B ) const; +}; diff --git a/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/IHoudiniEngine.h b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/IHoudiniEngine.h new file mode 100644 index 00000000..a16cb866 --- /dev/null +++ b/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Public/IHoudiniEngine.h @@ -0,0 +1,103 @@ +/* +* Copyright (c) <2017> Side Effects Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#pragma once + +#include "Modules/ModuleInterface.h" +#include "Brushes/SlateDynamicImageBrush.h" +#include "HAPI_Common.h" +#include "HoudiniGeoPartObject.h" + + +class UMaterial; +class UStaticMesh; +class ISlateStyle; +class UHoudiniAsset; +struct FHoudiniEngineNotificationInfo; +struct FHoudiniEngineTask; +struct FHoudiniEngineTaskInfo; +struct HAPI_Session; +struct FHoudiniCookParams; +class ALandscape; +class ALandscapeProxy; + +class IHoudiniEngine : public IModuleInterface +{ +public: + +#if WITH_EDITOR + + /** Return Houdini logo brush. **/ + virtual TSharedPtr GetHoudiniLogoBrush() const = 0; + +#endif + + /** Return static mesh reprensenting Houdini logo. **/ + virtual TWeakObjectPtr GetHoudiniLogoStaticMesh() const = 0; + + /** Return default material. **/ + virtual TWeakObjectPtr GetHoudiniDefaultMaterial() const = 0; + + /** Return Houdini digital asset used for bgeo file loading. **/ + virtual TWeakObjectPtr GetHoudiniBgeoAsset() const = 0; + + /** Return true if HAPI version mismatch is detected (between defined and running versions). **/ + virtual bool CheckHapiVersionMismatch() const = 0; + + /** Return current HAPI state. **/ + virtual HAPI_Result GetHapiState() const = 0; + + /** Set HAPI state. **/ + virtual void SetHapiState(HAPI_Result Result) = 0; + + /** Return location of libHAPI binary. **/ + virtual const FString& GetLibHAPILocation() const = 0; + + /** Register task for execution. **/ + virtual void AddTask(const FHoudiniEngineTask& Task) = 0; + + /** Register task info. **/ + virtual void AddTaskInfo(const FGuid HapIGUID, const FHoudiniEngineTaskInfo& TaskInfo) = 0; + + /** Remove task info. **/ + virtual void RemoveTaskInfo(const FGuid HapIGUID) = 0; + + /** Retrieve task info. **/ + virtual bool RetrieveTaskInfo(const FGuid HapIGUID, FHoudiniEngineTaskInfo& TaskInfo) = 0; + + /** Retrieve HAPI session. **/ + virtual const HAPI_Session* GetSession() const = 0; + + /** Construct static meshes for a given Houdini asset. **/ + virtual bool CookNode( + HAPI_NodeId AssetId, FHoudiniCookParams& HoudiniCookParams, + bool ForceRebuildStaticMesh, bool ForceRecookAll, + const TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesIn, + TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesOut, + TMap< FHoudiniGeoPartObject, TWeakObjectPtr >& LandscapesIn, + TMap< FHoudiniGeoPartObject, TWeakObjectPtr >& LandscapesOut, + TMap< FHoudiniGeoPartObject, USceneComponent * >& InstancersIn, + TMap< FHoudiniGeoPartObject, USceneComponent * >& InstancersOut, + USceneComponent* ParentComponent, + FTransform & ComponentTransform ) = 0; +};