/* * 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