FTransform USpringArmComponent::GetSocketTransform(FName InSocketName, ERelativeTransformSpace TransformSpace) const { FTransform RelativeTransform(RelativeSocketRotation, RelativeSocketLocation); switch(TransformSpace) { case RTS_World: { return RelativeTransform * ComponentToWorld; break; } case RTS_Actor: { if( const AActor* Actor = GetOwner() ) { FTransform SocketTransform = RelativeTransform * ComponentToWorld; return SocketTransform.GetRelativeTransform(Actor->GetTransform()); } break; } case RTS_Component: { return RelativeTransform; } } return RelativeTransform; }
void UAnimPreviewInstance::SetKeyImplementation(const FCompactPose& PreControllerInLocalSpace, const FCompactPose& PostControllerInLocalSpace) { #if WITH_EDITOR // evaluate the curve data first UAnimSequence* CurrentSequence = Cast<UAnimSequence>(CurrentAsset); UDebugSkelMeshComponent* Component = Cast<UDebugSkelMeshComponent> (GetSkelMeshComponent()); if(CurrentSequence && CurrentSkeleton && Component && Component->SkeletalMesh) { FScopedTransaction ScopedTransaction(LOCTEXT("SetKey", "Set Key")); CurrentSequence->Modify(true); Modify(); TArray<FName> BonesToModify; // need to get component transform first. Depending on when this gets called, the transform is not up-to-date. // first look at the bonecontrollers, and convert each bone controller to transform curve key // and add new curvebonecontrollers with additive data type // clear bone controller data for(auto& SingleBoneController : BoneControllers) { // find bone name, and just get transform of the bone in local space // and get the additive data // find if this already exists, then just add curve data only FName BoneName = SingleBoneController.BoneToModify.BoneName; // now convert data const FMeshPoseBoneIndex MeshBoneIndex(Component->GetBoneIndex(BoneName)); const FCompactPoseBoneIndex BoneIndex = RequiredBones.MakeCompactPoseIndex(MeshBoneIndex); FTransform LocalTransform = PostControllerInLocalSpace[BoneIndex]; // now we have LocalTransform and get additive data FTransform AdditiveTransform = LocalTransform.GetRelativeTransform(PreControllerInLocalSpace[BoneIndex]); AddKeyToSequence(CurrentSequence, CurrentTime, BoneName, AdditiveTransform); BonesToModify.Add(BoneName); } // see if the bone is selected right now and if that is added - if bone is selected, we should add identity key to it. if ( Component->BonesOfInterest.Num() > 0 ) { // if they're selected, we should add to the modifyBone list even if they're not modified, so that they can key that point. // first make sure those are added // if not added, make sure to set the key for them for (const auto& BoneIndex : Component->BonesOfInterest) { FName BoneName = Component->GetBoneName(BoneIndex); // if it's not on BonesToModify, add identity here. if (!BonesToModify.Contains(BoneName)) { AddKeyToSequence(CurrentSequence, CurrentTime, BoneName, FTransform::Identity); } } } ResetModifiedBone(false); OnSetKeyCompleteDelegate.ExecuteIfBound(); } #endif }
int32 UPaperGroupedSpriteComponent::AddInstanceWithMaterial(const FTransform& Transform, UPaperSprite* Sprite, UMaterialInterface* MaterialOverride, bool bWorldSpace, FLinearColor Color) { const int32 NewInstanceIndex = PerInstanceSpriteData.Num(); const FTransform LocalTransform(bWorldSpace ? Transform.GetRelativeTransform(ComponentToWorld) : Transform); FSpriteInstanceData& NewInstanceData = *new (PerInstanceSpriteData)FSpriteInstanceData(); SetupNewInstanceData(NewInstanceData, NewInstanceIndex, LocalTransform, Sprite, MaterialOverride, Color.ToFColor(/*bSRGB=*/ false)); MarkRenderStateDirty(); UNavigationSystem::UpdateNavOctree(this); return NewInstanceIndex; }
bool UPaperGroupedSpriteComponent::UpdateInstanceTransform(int32 InstanceIndex, const FTransform& NewInstanceTransform, bool bWorldSpace, bool bMarkRenderStateDirty) { if (!PerInstanceSpriteData.IsValidIndex(InstanceIndex)) { return false; } // Request navigation update UNavigationSystem::UpdateNavOctree(this); FSpriteInstanceData& InstanceData = PerInstanceSpriteData[InstanceIndex]; // Render data uses local transform of the instance FTransform LocalTransform = bWorldSpace ? NewInstanceTransform.GetRelativeTransform(ComponentToWorld) : NewInstanceTransform; InstanceData.Transform = LocalTransform.ToMatrixWithScale(); if (bPhysicsStateCreated) { // Physics uses world transform of the instance const FTransform WorldTransform = bWorldSpace ? NewInstanceTransform : (LocalTransform * ComponentToWorld); if (FBodyInstance* InstanceBodyInstance = InstanceBodies[InstanceIndex]) { // Update transform. InstanceBodyInstance->SetBodyTransform(WorldTransform, ETeleportType::None); InstanceBodyInstance->UpdateBodyScale(WorldTransform.GetScale3D()); } } // Request navigation update UNavigationSystem::UpdateNavOctree(this); if (bMarkRenderStateDirty) { MarkRenderStateDirty(); } return true; }
bool UnFbx::FFbxImporter::ImportAnimation(USkeleton* Skeleton, UAnimSequence * DestSeq, const FString& FileName, TArray<FbxNode*>& SortedLinks, TArray<FbxNode*>& NodeArray, FbxAnimStack* CurAnimStack, const int32 ResampleRate, const FbxTimeSpan AnimTimeSpan) { // @todo : the length might need to change w.r.t. sampling keys FbxTime SequenceLength = AnimTimeSpan.GetDuration(); float PreviousSequenceLength = DestSeq->SequenceLength; // if you have one pose(thus 0.f duration), it still contains animation, so we'll need to consider that as MINIMUM_ANIMATION_LENGTH time length DestSeq->SequenceLength = FGenericPlatformMath::Max<float>(SequenceLength.GetSecondDouble(), MINIMUM_ANIMATION_LENGTH); if(PreviousSequenceLength > MINIMUM_ANIMATION_LENGTH && DestSeq->RawCurveData.FloatCurves.Num() > 0) { // The sequence already existed when we began the import. We need to scale the key times for all curves to match the new // duration before importing over them. This is to catch any user-added curves float ScaleFactor = DestSeq->SequenceLength / PreviousSequenceLength; for(FFloatCurve& Curve : DestSeq->RawCurveData.FloatCurves) { Curve.FloatCurve.ScaleCurve(0.0f, ScaleFactor); } } if (ImportOptions->bDeleteExistingMorphTargetCurves) { for (int32 CurveIdx=0; CurveIdx<DestSeq->RawCurveData.FloatCurves.Num(); ++CurveIdx) { auto& Curve = DestSeq->RawCurveData.FloatCurves[CurveIdx]; if (Curve.GetCurveTypeFlag(ACF_DrivesMorphTarget)) { DestSeq->RawCurveData.FloatCurves.RemoveAt(CurveIdx, 1, false); --CurveIdx; } } DestSeq->RawCurveData.FloatCurves.Shrink(); } // // import blend shape curves // { GWarn->BeginSlowTask( LOCTEXT("BeginImportMorphTargetCurves", "Importing Morph Target Curves"), true); for ( int32 NodeIndex = 0; NodeIndex < NodeArray.Num(); NodeIndex++ ) { // consider blendshape animation curve FbxGeometry* Geometry = (FbxGeometry*)NodeArray[NodeIndex]->GetNodeAttribute(); if (Geometry) { int32 BlendShapeDeformerCount = Geometry->GetDeformerCount(FbxDeformer::eBlendShape); for(int32 BlendShapeIndex = 0; BlendShapeIndex<BlendShapeDeformerCount; ++BlendShapeIndex) { FbxBlendShape* BlendShape = (FbxBlendShape*)Geometry->GetDeformer(BlendShapeIndex, FbxDeformer::eBlendShape); const int32 BlendShapeChannelCount = BlendShape->GetBlendShapeChannelCount(); FString BlendShapeName = UTF8_TO_TCHAR(MakeName(BlendShape->GetName())); for(int32 ChannelIndex = 0; ChannelIndex<BlendShapeChannelCount; ++ChannelIndex) { FbxBlendShapeChannel* Channel = BlendShape->GetBlendShapeChannel(ChannelIndex); if(Channel) { FString ChannelName = UTF8_TO_TCHAR(MakeName(Channel->GetName())); // Maya adds the name of the blendshape and an underscore to the front of the channel name, so remove it if(ChannelName.StartsWith(BlendShapeName)) { ChannelName = ChannelName.Right(ChannelName.Len() - (BlendShapeName.Len()+1)); } FbxAnimCurve* Curve = Geometry->GetShapeChannel(BlendShapeIndex, ChannelIndex, (FbxAnimLayer*)CurAnimStack->GetMember(0)); if (Curve && Curve->KeyGetCount() > 0) { FFormatNamedArguments Args; Args.Add(TEXT("BlendShape"), FText::FromString(ChannelName)); const FText StatusUpate = FText::Format(LOCTEXT("ImportingMorphTargetCurvesDetail", "Importing Morph Target Curves [{BlendShape}]"), Args); GWarn->StatusUpdate(NodeIndex + 1, NodeArray.Num(), StatusUpate); // now see if we have one already exists. If so, just overwrite that. if not, add new one. ImportCurveToAnimSequence(DestSeq, *ChannelName, Curve, ACF_DrivesMorphTarget | ACF_TriggerEvent, AnimTimeSpan, 0.01f /** for some reason blend shape values are coming as 100 scaled **/); } } } } } } GWarn->EndSlowTask(); } // // importing custom attribute START // if (ImportOptions->bImportCustomAttribute) { GWarn->BeginSlowTask( LOCTEXT("BeginImportMorphTargetCurves", "Importing Custom Attirubte Curves"), true); const int32 TotalLinks = SortedLinks.Num(); int32 CurLinkIndex=0; for(auto Node: SortedLinks) { FbxProperty Property = Node->GetFirstProperty(); while (Property.IsValid()) { FbxAnimCurveNode* CurveNode = Property.GetCurveNode(); // do this if user defined and animated and leaf node if( CurveNode && Property.GetFlag(FbxPropertyAttr::eUserDefined) && CurveNode->IsAnimated() && IsSupportedCurveDataType(Property.GetPropertyDataType().GetType()) ) { FString CurveName = UTF8_TO_TCHAR(CurveNode->GetName()); UE_LOG(LogFbx, Log, TEXT("CurveName : %s"), *CurveName ); int32 TotalCount = CurveNode->GetChannelsCount(); for (int32 ChannelIndex=0; ChannelIndex<TotalCount; ++ChannelIndex) { FbxAnimCurve * AnimCurve = CurveNode->GetCurve(ChannelIndex); FString ChannelName = CurveNode->GetChannelName(ChannelIndex).Buffer(); if (AnimCurve) { FString FinalCurveName; if (TotalCount == 1) { FinalCurveName = CurveName; } else { FinalCurveName = CurveName + "_" + ChannelName; } FFormatNamedArguments Args; Args.Add(TEXT("CurveName"), FText::FromString(FinalCurveName)); const FText StatusUpate = FText::Format(LOCTEXT("ImportingCustomAttributeCurvesDetail", "Importing Custom Attribute [{CurveName}]"), Args); GWarn->StatusUpdate(CurLinkIndex + 1, TotalLinks, StatusUpate); ImportCurveToAnimSequence(DestSeq, FinalCurveName, AnimCurve, ACF_DefaultCurve, AnimTimeSpan); } } } Property = Node->GetNextProperty(Property); } CurLinkIndex++; } GWarn->EndSlowTask(); } // importing custom attribute END const bool bSourceDataExists = (DestSeq->SourceRawAnimationData.Num() > 0); TArray<AnimationTransformDebug::FAnimationTransformDebugData> TransformDebugData; int32 TotalNumKeys = 0; const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton(); // import animation { GWarn->BeginSlowTask( LOCTEXT("BeginImportAnimation", "Importing Animation"), true); TArray<struct FRawAnimSequenceTrack>& RawAnimationData = bSourceDataExists? DestSeq->SourceRawAnimationData : DestSeq->RawAnimationData; DestSeq->TrackToSkeletonMapTable.Empty(); DestSeq->AnimationTrackNames.Empty(); RawAnimationData.Empty(); TArray<FName> FbxRawBoneNames; FillAndVerifyBoneNames(Skeleton, SortedLinks, FbxRawBoneNames, FileName); UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance(); const bool bPreserveLocalTransform = FbxImporter->GetImportOptions()->bPreserveLocalTransform; // Build additional transform matrix UFbxAnimSequenceImportData* TemplateData = Cast<UFbxAnimSequenceImportData>(DestSeq->AssetImportData); FbxAMatrix FbxAddedMatrix; BuildFbxMatrixForImportTransform(FbxAddedMatrix, TemplateData); FMatrix AddedMatrix = Converter.ConvertMatrix(FbxAddedMatrix); const int32 NumSamplingKeys = FMath::FloorToInt(AnimTimeSpan.GetDuration().GetSecondDouble() * ResampleRate); const FbxTime TimeIncrement = (NumSamplingKeys > 1)? AnimTimeSpan.GetDuration() / (NumSamplingKeys - 1) : AnimTimeSpan.GetDuration(); for(int32 SourceTrackIdx = 0; SourceTrackIdx < FbxRawBoneNames.Num(); ++SourceTrackIdx) { int32 NumKeysForTrack = 0; // see if it's found in Skeleton FName BoneName = FbxRawBoneNames[SourceTrackIdx]; int32 BoneTreeIndex = RefSkeleton.FindBoneIndex(BoneName); // update status FFormatNamedArguments Args; Args.Add(TEXT("TrackName"), FText::FromName(BoneName)); Args.Add(TEXT("TotalKey"), FText::AsNumber(NumSamplingKeys)); Args.Add(TEXT("TrackIndex"), FText::AsNumber(SourceTrackIdx+1)); Args.Add(TEXT("TotalTracks"), FText::AsNumber(FbxRawBoneNames.Num())); const FText StatusUpate = FText::Format(LOCTEXT("ImportingAnimTrackDetail", "Importing Animation Track [{TrackName}] ({TrackIndex}/{TotalTracks}) - TotalKey {TotalKey}"), Args); GWarn->StatusForceUpdate(SourceTrackIdx + 1, FbxRawBoneNames.Num(), StatusUpate); if (BoneTreeIndex!=INDEX_NONE) { bool bSuccess = true; FRawAnimSequenceTrack RawTrack; RawTrack.PosKeys.Empty(); RawTrack.RotKeys.Empty(); RawTrack.ScaleKeys.Empty(); AnimationTransformDebug::FAnimationTransformDebugData NewDebugData; FbxNode* Link = SortedLinks[SourceTrackIdx]; FbxNode * LinkParent = Link->GetParent(); for(FbxTime CurTime = AnimTimeSpan.GetStart(); CurTime <= AnimTimeSpan.GetStop(); CurTime += TimeIncrement) { // save global trasnform FbxAMatrix GlobalMatrix = Link->EvaluateGlobalTransform(CurTime); // we'd like to verify this before going to Transform. // currently transform has tons of NaN check, so it will crash there FMatrix GlobalUEMatrix = Converter.ConvertMatrix(GlobalMatrix); if (GlobalUEMatrix.ContainsNaN()) { bSuccess = false; AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_InvalidTransform", "Track {0} contains invalid transform. Could not import the track."), FText::FromName(BoneName))), FFbxErrors::Animation_TransformError); break; } FTransform GlobalTransform = Converter.ConvertTransform(GlobalMatrix); if (GlobalTransform.ContainsNaN()) { bSuccess = false; AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_InvalidUnrealTransform", "Track {0} did not yeild valid transform. Please report this to animation team."), FText::FromName(BoneName))), FFbxErrors::Animation_TransformError); break; } // debug data, including import transformation FTransform AddedTransform(AddedMatrix); NewDebugData.SourceGlobalTransform.Add(GlobalTransform * AddedTransform); FTransform LocalTransform; if( !bPreserveLocalTransform && LinkParent) { // I can't rely on LocalMatrix. I need to recalculate quaternion/scale based on global transform if Parent exists FbxAMatrix ParentGlobalMatrix = Link->GetParent()->EvaluateGlobalTransform(CurTime); FTransform ParentGlobalTransform = Converter.ConvertTransform(ParentGlobalMatrix); LocalTransform = GlobalTransform.GetRelativeTransform(ParentGlobalTransform); NewDebugData.SourceParentGlobalTransform.Add(ParentGlobalTransform); } else { FbxAMatrix& LocalMatrix = Link->EvaluateLocalTransform(CurTime); FbxVector4 NewLocalT = LocalMatrix.GetT(); FbxVector4 NewLocalS = LocalMatrix.GetS(); FbxQuaternion NewLocalQ = LocalMatrix.GetQ(); LocalTransform.SetTranslation(Converter.ConvertPos(NewLocalT)); LocalTransform.SetScale3D(Converter.ConvertScale(NewLocalS)); LocalTransform.SetRotation(Converter.ConvertRotToQuat(NewLocalQ)); NewDebugData.SourceParentGlobalTransform.Add(FTransform::Identity); } if(TemplateData && BoneTreeIndex == 0) { // If we found template data earlier, apply the import transform matrix to // the root track. LocalTransform.SetFromMatrix(LocalTransform.ToMatrixWithScale() * AddedMatrix); } if (LocalTransform.ContainsNaN()) { bSuccess = false; AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_InvalidUnrealLocalTransform", "Track {0} did not yeild valid local transform. Please report this to animation team."), FText::FromName(BoneName))), FFbxErrors::Animation_TransformError); break; } RawTrack.ScaleKeys.Add(LocalTransform.GetScale3D()); RawTrack.PosKeys.Add(LocalTransform.GetTranslation()); RawTrack.RotKeys.Add(LocalTransform.GetRotation()); NewDebugData.RecalculatedLocalTransform.Add(LocalTransform); ++NumKeysForTrack; } if (bSuccess) { //add new track int32 NewTrackIdx = RawAnimationData.Add(RawTrack); DestSeq->AnimationTrackNames.Add(BoneName); NewDebugData.SetTrackData(NewTrackIdx, BoneTreeIndex, BoneName); // add mapping to skeleton bone track DestSeq->TrackToSkeletonMapTable.Add(FTrackToSkeletonMap(BoneTreeIndex)); TransformDebugData.Add(NewDebugData); } } TotalNumKeys = FMath::Max( TotalNumKeys, NumKeysForTrack ); } DestSeq->NumFrames = TotalNumKeys; GWarn->EndSlowTask(); } // compress animation { GWarn->BeginSlowTask( LOCTEXT("BeginCompressAnimation", "Compress Animation"), true); GWarn->StatusForceUpdate(1, 1, LOCTEXT("CompressAnimation", "Compressing Animation")); // if source data exists, you should bake it to Raw to apply if(bSourceDataExists) { DestSeq->BakeTrackCurvesToRawAnimation(); } else { // otherwise just compress DestSeq->PostProcessSequence(); } // run debug mode AnimationTransformDebug::OutputAnimationTransformDebugData(TransformDebugData, TotalNumKeys, RefSkeleton); GWarn->EndSlowTask(); } return true; }
bool UGripMotionControllerComponent::GripComponent( UPrimitiveComponent* ComponentToGrip, const FTransform &WorldOffset, bool bWorldOffsetIsRelative, FName OptionalSnapToSocketName, TEnumAsByte<EGripCollisionType> GripCollisionType, bool bAllowSetMobility, float GripStiffness, float GripDamping, bool bTurnOffLateUpdateWhenColliding ) { if (!bIsServer || !ComponentToGrip) { UE_LOG(LogTemp, Warning, TEXT("VRGripMotionController grab function was passed an invalid or already gripped component")); return false; } // Has to be movable to work if (ComponentToGrip->Mobility != EComponentMobility::Movable) { if (bAllowSetMobility) ComponentToGrip->SetMobility(EComponentMobility::Movable); else { UE_LOG(LogTemp, Warning, TEXT("VRGripMotionController tried to grip a component set to static mobility and bAllowSetMobility is false")); return false; // It is not movable, can't grip it } } ComponentToGrip->IgnoreActorWhenMoving(this->GetOwner(), true); // So that events caused by sweep and the like will trigger correctly ComponentToGrip->AddTickPrerequisiteComponent(this); FBPActorGripInformation newActorGrip; newActorGrip.GripCollisionType = GripCollisionType; newActorGrip.Component = ComponentToGrip; if(ComponentToGrip->GetOwner()) newActorGrip.bOriginalReplicatesMovement = ComponentToGrip->GetOwner()->bReplicateMovement; newActorGrip.Stiffness = GripStiffness; newActorGrip.Damping = GripDamping; newActorGrip.bTurnOffLateUpdateWhenColliding = bTurnOffLateUpdateWhenColliding; if (OptionalSnapToSocketName.IsValid() && ComponentToGrip->DoesSocketExist(OptionalSnapToSocketName)) { // I inverse it so that laying out the sockets makes sense FTransform sockTrans = ComponentToGrip->GetSocketTransform(OptionalSnapToSocketName, ERelativeTransformSpace::RTS_Component); newActorGrip.RelativeTransform = sockTrans.Inverse(); newActorGrip.RelativeTransform.SetScale3D(ComponentToGrip->GetComponentScale()); } else if (bWorldOffsetIsRelative) newActorGrip.RelativeTransform = WorldOffset; else newActorGrip.RelativeTransform = WorldOffset.GetRelativeTransform(this->GetComponentTransform()); NotifyGrip(newActorGrip); GrippedActors.Add(newActorGrip); return true; }
void USkeletalMeshComponent::PerformBlendPhysicsBones(const TArray<FBoneIndexType>& InRequiredBones, TArray<FTransform>& InLocalAtoms) { // Get drawscale from Owner (if there is one) FVector TotalScale3D = ComponentToWorld.GetScale3D(); FVector RecipScale3D = TotalScale3D.Reciprocal(); UPhysicsAsset * const PhysicsAsset = GetPhysicsAsset(); check( PhysicsAsset ); if (GetNumSpaceBases() == 0) { return; } // Get the scene, and do nothing if we can't get one. FPhysScene* PhysScene = nullptr; if (GetWorld() != nullptr) { PhysScene = GetWorld()->GetPhysicsScene(); } if (PhysScene == nullptr) { return; } // Make sure scratch space is big enough. TArray<FAssetWorldBoneTM> WorldBoneTMs; WorldBoneTMs.Reset(); WorldBoneTMs.AddZeroed(GetNumSpaceBases()); FTransform LocalToWorldTM = ComponentToWorld; LocalToWorldTM.RemoveScaling(); TArray<FTransform>& EditableSpaceBases = GetEditableSpaceBases(); struct FBodyTMPair { FBodyInstance* BI; FTransform TM; }; TArray<FBodyTMPair> PendingBodyTMs; #if WITH_PHYSX // Lock the scenes we need (flags set in InitArticulated) if (bHasBodiesInSyncScene) { SCENE_LOCK_READ(PhysScene->GetPhysXScene(PST_Sync)) } if (bHasBodiesInAsyncScene) { SCENE_LOCK_READ(PhysScene->GetPhysXScene(PST_Async)) } #endif // For each bone - see if we need to provide some data for it. for(int32 i=0; i<InRequiredBones.Num(); i++) { int32 BoneIndex = InRequiredBones[i]; // See if this is a physics bone.. int32 BodyIndex = PhysicsAsset ? PhysicsAsset->FindBodyIndex(SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex)) : INDEX_NONE; // need to update back to physX so that physX knows where it was after blending bool bUpdatePhysics = false; FBodyInstance* BodyInstance = NULL; // If so - get its world space matrix and its parents world space matrix and calc relative atom. if(BodyIndex != INDEX_NONE ) { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) // tracking down TTP 280421. Remove this if this doesn't happen. if ( !ensure(Bodies.IsValidIndex(BodyIndex)) ) { UE_LOG(LogPhysics, Warning, TEXT("%s(Mesh %s, PhysicsAsset %s)"), *GetName(), *GetNameSafe(SkeletalMesh), *GetNameSafe(PhysicsAsset)); if ( PhysicsAsset ) { UE_LOG(LogPhysics, Warning, TEXT(" - # of BodySetup (%d), # of Bodies (%d), Invalid BodyIndex(%d)"), PhysicsAsset->BodySetup.Num(), Bodies.Num(), BodyIndex); } continue; } #endif BodyInstance = Bodies[BodyIndex]; //if simulated body copy back and blend with animation if(BodyInstance->IsInstanceSimulatingPhysics()) { FTransform PhysTM = BodyInstance->GetUnrealWorldTransform_AssumesLocked(); // Store this world-space transform in cache. WorldBoneTMs[BoneIndex].TM = PhysTM; WorldBoneTMs[BoneIndex].bUpToDate = true; float UsePhysWeight = (bBlendPhysics)? 1.f : BodyInstance->PhysicsBlendWeight; // Find this bones parent matrix. FTransform ParentWorldTM; // if we wan't 'full weight' we just find if(UsePhysWeight > 0.f) { if(BoneIndex == 0) { ParentWorldTM = LocalToWorldTM; } else { // If not root, get parent TM from cache (making sure its up-to-date). int32 ParentIndex = SkeletalMesh->RefSkeleton.GetParentIndex(BoneIndex); UpdateWorldBoneTM(WorldBoneTMs, ParentIndex, this, LocalToWorldTM, TotalScale3D); ParentWorldTM = WorldBoneTMs[ParentIndex].TM; } // Then calc rel TM and convert to atom. FTransform RelTM = PhysTM.GetRelativeTransform(ParentWorldTM); RelTM.RemoveScaling(); FQuat RelRot(RelTM.GetRotation()); FVector RelPos = RecipScale3D * RelTM.GetLocation(); FTransform PhysAtom = FTransform(RelRot, RelPos, InLocalAtoms[BoneIndex].GetScale3D()); // Now blend in this atom. See if we are forcing this bone to always be blended in InLocalAtoms[BoneIndex].Blend( InLocalAtoms[BoneIndex], PhysAtom, UsePhysWeight ); if(BoneIndex == 0) { //We must update RecipScale3D based on the atom scale of the root TotalScale3D *= InLocalAtoms[0].GetScale3D(); RecipScale3D = TotalScale3D.Reciprocal(); } if (UsePhysWeight < 1.f) { bUpdatePhysics = true; } } } } // Update SpaceBases entry for this bone now if( BoneIndex == 0 ) { EditableSpaceBases[0] = InLocalAtoms[0]; } else { const int32 ParentIndex = SkeletalMesh->RefSkeleton.GetParentIndex(BoneIndex); EditableSpaceBases[BoneIndex] = InLocalAtoms[BoneIndex] * EditableSpaceBases[ParentIndex]; /** * Normalize rotations. * We want to remove any loss of precision due to accumulation of error. * i.e. A componentSpace transform is the accumulation of all of its local space parents. The further down the chain, the greater the error. * SpaceBases are used by external systems, we feed this to PhysX, send this to gameplay through bone and socket queries, etc. * So this is a good place to make sure all transforms are normalized. */ EditableSpaceBases[BoneIndex].NormalizeRotation(); } if (bUpdatePhysics && BodyInstance) { //This is extremely inefficient. We need to obtain a write lock which will block other threads from blending //For now I'm juts deferring it to the end of this loop, but in general we need to move it all out of here and do it when the blend task is done FBodyTMPair* BodyTMPair = new (PendingBodyTMs) FBodyTMPair; BodyTMPair->BI = BodyInstance; BodyTMPair->TM = EditableSpaceBases[BoneIndex] * ComponentToWorld; } } #if WITH_PHYSX //See above for read lock instead of write lock // Unlock the scenes if (bHasBodiesInSyncScene) { SCENE_UNLOCK_READ(PhysScene->GetPhysXScene(PST_Sync)) } if (bHasBodiesInAsyncScene) { SCENE_UNLOCK_READ(PhysScene->GetPhysXScene(PST_Async)) } if(PendingBodyTMs.Num()) { //This is extremely inefficient. We need to obtain a write lock which will block other threads from blending //For now I'm juts deferring it to the end of this loop, but in general we need to move it all out of here and do it when the blend task is done if (bHasBodiesInSyncScene) { SCENE_LOCK_WRITE(PhysScene->GetPhysXScene(PST_Sync)) } if (bHasBodiesInAsyncScene) { SCENE_LOCK_WRITE(PhysScene->GetPhysXScene(PST_Async)) } for (const FBodyTMPair& BodyTMPair : PendingBodyTMs) { BodyTMPair.BI->SetBodyTransform(BodyTMPair.TM, ETeleportType::TeleportPhysics); } if (bHasBodiesInSyncScene) { SCENE_UNLOCK_WRITE(PhysScene->GetPhysXScene(PST_Sync)) } if (bHasBodiesInAsyncScene) { SCENE_UNLOCK_WRITE(PhysScene->GetPhysXScene(PST_Async)) } } #endif // Transforms updated, cached local bounds are now out of date. InvalidateCachedBounds(); }
void RasterizeSegmentPoints(ULandscapeInfo* LandscapeInfo, TArray<FLandscapeSplineInterpPoint> Points, const FTransform& SplineToWorld, bool bRaiseTerrain, bool bLowerTerrain, ULandscapeLayerInfoObject* LayerInfo) { ALandscapeProxy* LandscapeProxy = LandscapeInfo->GetLandscapeProxy(); const FTransform SplineToLandscape = SplineToWorld.GetRelativeTransform(LandscapeProxy->LandscapeActorToWorld()); FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); TSet<ULandscapeComponent*> ModifiedComponents; // I'd dearly love to use FIntRect in this code, but Landscape works with "Inclusive Max" and FIntRect is "Exclusive Max" int32 LandscapeMinX, LandscapeMinY, LandscapeMaxX, LandscapeMaxY; if (!LandscapeInfo->GetLandscapeExtent(LandscapeMinX, LandscapeMinY, LandscapeMaxX, LandscapeMaxY)) { return; } FBox SegmentBounds = FBox(0); for (const FLandscapeSplineInterpPoint& Point : Points) { SegmentBounds += Point.FalloffLeft; SegmentBounds += Point.FalloffRight; } SegmentBounds = SegmentBounds.TransformBy(SplineToLandscape.ToMatrixWithScale()); int32 MinX = FMath::CeilToInt(SegmentBounds.Min.X); int32 MinY = FMath::CeilToInt(SegmentBounds.Min.Y); int32 MaxX = FMath::FloorToInt(SegmentBounds.Max.X); int32 MaxY = FMath::FloorToInt(SegmentBounds.Max.Y); MinX = FMath::Max(MinX, LandscapeMinX); MinY = FMath::Max(MinY, LandscapeMinY); MaxX = FMath::Min(MaxX, LandscapeMaxX); MaxY = FMath::Min(MaxY, LandscapeMaxY); if (MinX > MaxX || MinY > MaxY) { // The segment's bounds don't intersect the landscape, so skip it entirely return; } for (int32 j = 0; j < Points.Num(); j++) { Points[j].Center = SplineToLandscape.TransformPosition(Points[j].Center); Points[j].Left = SplineToLandscape.TransformPosition(Points[j].Left); Points[j].Right = SplineToLandscape.TransformPosition(Points[j].Right); Points[j].FalloffLeft = SplineToLandscape.TransformPosition(Points[j].FalloffLeft); Points[j].FalloffRight = SplineToLandscape.TransformPosition(Points[j].FalloffRight); // local-heights to texture value heights Points[j].Left.Z = Points[j].Left.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue; Points[j].Right.Z = Points[j].Right.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue; Points[j].FalloffLeft.Z = Points[j].FalloffLeft.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue; Points[j].FalloffRight.Z = Points[j].FalloffRight.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue; } // Heights raster if (bRaiseTerrain || bLowerTerrain) { RasterizeSegmentHeight(MinX, MinY, MaxX, MaxY, LandscapeEdit, Points, bRaiseTerrain, bLowerTerrain, ModifiedComponents); if (MinX > MaxX || MinY > MaxY) { // The segment's bounds don't intersect any data, so we skip it entirely // it wouldn't intersect any weightmap data either so we don't even bother trying } } // Blend layer raster if (LayerInfo != NULL) { RasterizeSegmentAlpha(MinX, MinY, MaxX, MaxY, LandscapeEdit, Points, LayerInfo, ModifiedComponents); } LandscapeEdit.Flush(); for (ULandscapeComponent* Component : ModifiedComponents) { // Recreate collision for modified components and update the navmesh ULandscapeHeightfieldCollisionComponent* CollisionComponent = Component->CollisionComponent.Get(); if (CollisionComponent) { CollisionComponent->RecreateCollision(); UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(Component); if (NavSys) { NavSys->UpdateNavOctree(CollisionComponent); } } } }
void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( UAnimSequence* AnimSeq, const TArray<FBoneData>& BoneData, TArray<FTranslationTrack>& PositionTracks, TArray<FRotationTrack>& RotationTracks, TArray<FScaleTrack>& ScaleTracks) { // extract all the data we'll need about the skeleton and animation sequence const int32 NumBones = BoneData.Num(); const int32 NumFrames = AnimSeq->NumFrames; const float SequenceLength = AnimSeq->SequenceLength; const int32 LastFrame = NumFrames-1; const float FrameRate = (float)(LastFrame) / SequenceLength; const float TimePerFrame = SequenceLength / (float)(LastFrame); const TArray<FTransform>& RefPose = AnimSeq->GetSkeleton()->GetRefLocalPoses(); const bool bHasScale = (ScaleTracks.Num() > 0); // make sure the parent key scale is properly bound to 1.0 or more ParentKeyScale = FMath::Max(ParentKeyScale, 1.0f); // generate the raw and compressed skeleton in world-space TArray<FTransform> RawWorldBones; TArray<FTransform> NewWorldBones; RawWorldBones.Empty(NumBones * NumFrames); NewWorldBones.Empty(NumBones * NumFrames); RawWorldBones.AddZeroed(NumBones * NumFrames); NewWorldBones.AddZeroed(NumBones * NumFrames); // generate an array to hold the indices of our end effectors TArray<int32> EndEffectors; EndEffectors.Empty(NumBones); // Create an array of FTransform to use as a workspace TArray<FTransform> BoneAtoms; // setup the raw bone transformation and find all end effectors for ( int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex ) { // get the raw world-atoms for this bone UpdateWorldBoneTransformTable( AnimSeq, BoneData, RefPose, BoneIndex, true, RawWorldBones); // also record all end-effectors we find const FBoneData& Bone = BoneData[BoneIndex]; if (Bone.IsEndEffector()) { EndEffectors.Add(BoneIndex); } } TArray<int32> TargetBoneIndices; // for each bone... for ( int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex ) { const FBoneData& Bone = BoneData[BoneIndex]; const int32 ParentBoneIndex = Bone.GetParent(); const int32 TrackIndex = AnimSeq->GetSkeleton()->GetAnimationTrackIndex(BoneIndex, AnimSeq); if (TrackIndex != INDEX_NONE) { // get the tracks we will be editing for this bone FRotationTrack& RotTrack = RotationTracks[TrackIndex]; FTranslationTrack& TransTrack = PositionTracks[TrackIndex]; const int32 NumRotKeys = RotTrack.RotKeys.Num(); const int32 NumPosKeys = TransTrack.PosKeys.Num(); const int32 NumScaleKeys = (bHasScale)? ScaleTracks[TrackIndex].ScaleKeys.Num() : 0; check( (NumPosKeys == 1) || (NumRotKeys == 1) || (NumPosKeys == NumRotKeys) ); // build an array of end effectors we need to monitor TargetBoneIndices.Reset(NumBones); int32 HighestTargetBoneIndex = BoneIndex; int32 FurthestTargetBoneIndex = BoneIndex; int32 ShortestChain = 0; float OffsetLength= -1.0f; for (int32 EffectorIndex=0; EffectorIndex < EndEffectors.Num(); ++EffectorIndex) { const int32 EffectorBoneIndex = EndEffectors[EffectorIndex]; const FBoneData& EffectorBoneData = BoneData[EffectorBoneIndex]; int32 RootIndex = EffectorBoneData.BonesToRoot.Find(BoneIndex); if (RootIndex != INDEX_NONE) { if (ShortestChain == 0 || (RootIndex+1) < ShortestChain) { ShortestChain = (RootIndex+1); } TargetBoneIndices.Add(EffectorBoneIndex); HighestTargetBoneIndex = FMath::Max(HighestTargetBoneIndex, EffectorBoneIndex); float ChainLength= 0.0f; for (long FamilyIndex=0; FamilyIndex < RootIndex; ++FamilyIndex) { const int32 NextParentBoneIndex= EffectorBoneData.BonesToRoot[FamilyIndex]; ChainLength += RefPose[NextParentBoneIndex].GetTranslation().Size(); } if (ChainLength > OffsetLength) { FurthestTargetBoneIndex = EffectorBoneIndex; OffsetLength = ChainLength; } } } // if requested, retarget the FBoneAtoms towards the target end effectors if (bRetarget) { if (NumScaleKeys > 0 && ParentBoneIndex != INDEX_NONE) { // update our bone table from the current bone through the last end effector we need to test UpdateWorldBoneTransformRange( AnimSeq, BoneData, RefPose, PositionTracks, RotationTracks, ScaleTracks, BoneIndex, HighestTargetBoneIndex, false, NewWorldBones); FScaleTrack& ScaleTrack = ScaleTracks[TrackIndex]; // adjust all translation keys to align better with the destination for ( int32 KeyIndex = 0; KeyIndex < NumScaleKeys; ++KeyIndex ) { FVector& Key= ScaleTrack.ScaleKeys[KeyIndex]; const int32 FrameIndex= FMath::Clamp(KeyIndex, 0, LastFrame); const FTransform& NewWorldParent = NewWorldBones[(ParentBoneIndex*NumFrames) + FrameIndex]; const FTransform& RawWorldChild = RawWorldBones[(BoneIndex*NumFrames) + FrameIndex]; const FTransform& RelTM = (RawWorldChild.GetRelativeTransform(NewWorldParent)); const FTransform Delta = FTransform(RelTM); Key = Delta.GetScale3D(); } } if (NumRotKeys > 0 && ParentBoneIndex != INDEX_NONE) { if (HighestTargetBoneIndex == BoneIndex) { for ( int32 KeyIndex = 0; KeyIndex < NumRotKeys; ++KeyIndex ) { FQuat& Key = RotTrack.RotKeys[KeyIndex]; check(ParentBoneIndex != INDEX_NONE); const int32 FrameIndex = FMath::Clamp(KeyIndex, 0, LastFrame); FTransform NewWorldParent = NewWorldBones[(ParentBoneIndex*NumFrames) + FrameIndex]; FTransform RawWorldChild = RawWorldBones[(BoneIndex*NumFrames) + FrameIndex]; const FTransform& RelTM = (RawWorldChild.GetRelativeTransform(NewWorldParent)); FQuat Rot = FTransform(RelTM).GetRotation(); const FQuat& AlignedKey = EnforceShortestArc(Key, Rot); Key = AlignedKey; } } else { // update our bone table from the current bone through the last end effector we need to test UpdateWorldBoneTransformRange( AnimSeq, BoneData, RefPose, PositionTracks, RotationTracks, ScaleTracks, BoneIndex, HighestTargetBoneIndex, false, NewWorldBones); // adjust all rotation keys towards the end effector target for ( int32 KeyIndex = 0; KeyIndex < NumRotKeys; ++KeyIndex ) { FQuat& Key = RotTrack.RotKeys[KeyIndex]; const int32 FrameIndex = FMath::Clamp(KeyIndex, 0, LastFrame); const FTransform& NewWorldTransform = NewWorldBones[(BoneIndex*NumFrames) + FrameIndex]; const FTransform& DesiredChildTransform = RawWorldBones[(FurthestTargetBoneIndex*NumFrames) + FrameIndex].GetRelativeTransform(NewWorldTransform); const FTransform& CurrentChildTransform = NewWorldBones[(FurthestTargetBoneIndex*NumFrames) + FrameIndex].GetRelativeTransform(NewWorldTransform); // find the two vectors which represent the angular error we are trying to correct const FVector& CurrentHeading = CurrentChildTransform.GetTranslation(); const FVector& DesiredHeading = DesiredChildTransform.GetTranslation(); // if these are valid, we can continue if (!CurrentHeading.IsNearlyZero() && !DesiredHeading.IsNearlyZero()) { const float DotResult = CurrentHeading.SafeNormal() | DesiredHeading.SafeNormal(); // limit the range we will retarget to something reasonable (~60 degrees) if (DotResult < 1.0f && DotResult > 0.5f) { FQuat Adjustment= FQuat::FindBetween(CurrentHeading, DesiredHeading); Adjustment.Normalize(); Adjustment= EnforceShortestArc(FQuat::Identity, Adjustment); const FVector Test = Adjustment.RotateVector(CurrentHeading); const float Delta = (Test - DesiredHeading).Size(); if (Delta < 0.001f) { FQuat NewKey = Adjustment * Key; NewKey.Normalize(); const FQuat& AlignedKey = EnforceShortestArc(Key, NewKey); Key = AlignedKey; } } } } } } if (NumPosKeys > 0 && ParentBoneIndex != INDEX_NONE) { // update our bone table from the current bone through the last end effector we need to test UpdateWorldBoneTransformRange( AnimSeq, BoneData, RefPose, PositionTracks, RotationTracks, ScaleTracks, BoneIndex, HighestTargetBoneIndex, false, NewWorldBones); // adjust all translation keys to align better with the destination for ( int32 KeyIndex = 0; KeyIndex < NumPosKeys; ++KeyIndex ) { FVector& Key= TransTrack.PosKeys[KeyIndex]; const int32 FrameIndex= FMath::Clamp(KeyIndex, 0, LastFrame); FTransform NewWorldParent = NewWorldBones[(ParentBoneIndex*NumFrames) + FrameIndex]; FTransform RawWorldChild = RawWorldBones[(BoneIndex*NumFrames) + FrameIndex]; const FTransform& RelTM = RawWorldChild.GetRelativeTransform(NewWorldParent); const FTransform Delta = FTransform(RelTM); ensure (!Delta.ContainsNaN()); Key = Delta.GetTranslation(); } } } // look for a parent track to reference as a guide int32 GuideTrackIndex = INDEX_NONE; if (ParentKeyScale > 1.0f) { for (long FamilyIndex=0; (FamilyIndex < Bone.BonesToRoot.Num()) && (GuideTrackIndex == INDEX_NONE); ++FamilyIndex) { const int32 NextParentBoneIndex= Bone.BonesToRoot[FamilyIndex]; GuideTrackIndex = AnimSeq->GetSkeleton()->GetAnimationTrackIndex(NextParentBoneIndex, AnimSeq); } } // update our bone table from the current bone through the last end effector we need to test UpdateWorldBoneTransformRange( AnimSeq, BoneData, RefPose, PositionTracks, RotationTracks, ScaleTracks, BoneIndex, HighestTargetBoneIndex, false, NewWorldBones); // rebuild the BoneAtoms table using the current set of keys UpdateBoneAtomList(AnimSeq, BoneIndex, TrackIndex, NumFrames, TimePerFrame, BoneAtoms); // determine the EndEffectorTolerance. // We use the Maximum value by default, and the Minimum value // as we approach the end effectors float EndEffectorTolerance = MaxEffectorDiff; if (ShortestChain <= 1) { EndEffectorTolerance = MinEffectorDiff; } // Determine if a guidance track should be used to aid in choosing keys to retain TArray<float>* GuidanceTrack = NULL; float GuidanceScale = 1.0f; if (GuideTrackIndex != INDEX_NONE) { FTranslationTrack& GuideTransTrack = PositionTracks[GuideTrackIndex]; GuidanceTrack = &GuideTransTrack.Times; GuidanceScale = ParentKeyScale; } // if the TargetBoneIndices array is empty, then this bone is an end effector. // so we add it to the list to maintain our tolerance checks if (TargetBoneIndices.Num() == 0) { TargetBoneIndices.Add(BoneIndex); } if (bActuallyFilterLinearKeys) { if (bHasScale) { FScaleTrack& ScaleTrack = ScaleTracks[TrackIndex]; // filter out translations we can approximate through interpolation FilterLinearKeysTemplate<FVector>( ScaleTrack.ScaleKeys, ScaleTrack.Times, BoneAtoms, GuidanceTrack, RawWorldBones, NewWorldBones, TargetBoneIndices, NumFrames, BoneIndex, ParentBoneIndex, GuidanceScale, MaxScaleDiff, EndEffectorTolerance, EffectorDiffSocket, BoneData); // update our bone table from the current bone through the last end effector we need to test UpdateWorldBoneTransformRange( AnimSeq, BoneData, RefPose, PositionTracks, RotationTracks, ScaleTracks, BoneIndex, HighestTargetBoneIndex, false, NewWorldBones); // rebuild the BoneAtoms table using the current set of keys UpdateBoneAtomList(AnimSeq, BoneIndex, TrackIndex, NumFrames, TimePerFrame, BoneAtoms); } // filter out translations we can approximate through interpolation FilterLinearKeysTemplate<FVector>( TransTrack.PosKeys, TransTrack.Times, BoneAtoms, GuidanceTrack, RawWorldBones, NewWorldBones, TargetBoneIndices, NumFrames, BoneIndex, ParentBoneIndex, GuidanceScale, MaxPosDiff, EndEffectorTolerance, EffectorDiffSocket, BoneData); // update our bone table from the current bone through the last end effector we need to test UpdateWorldBoneTransformRange( AnimSeq, BoneData, RefPose, PositionTracks, RotationTracks, ScaleTracks, BoneIndex, HighestTargetBoneIndex, false, NewWorldBones); // rebuild the BoneAtoms table using the current set of keys UpdateBoneAtomList(AnimSeq, BoneIndex, TrackIndex, NumFrames, TimePerFrame, BoneAtoms); // filter out rotations we can approximate through interpolation FilterLinearKeysTemplate<FQuat>( RotTrack.RotKeys, RotTrack.Times, BoneAtoms, GuidanceTrack, RawWorldBones, NewWorldBones, TargetBoneIndices, NumFrames, BoneIndex, ParentBoneIndex, GuidanceScale, MaxAngleDiff, EndEffectorTolerance, EffectorDiffSocket, BoneData); } } // make sure the final compressed keys are repesented in our NewWorldBones table UpdateWorldBoneTransformRange( AnimSeq, BoneData, RefPose, PositionTracks, RotationTracks, ScaleTracks, BoneIndex, BoneIndex, false, NewWorldBones); } };
void FilterLinearKeysTemplate( TArray<T>& Keys, TArray<float>& Times, TArray<FTransform>& BoneAtoms, const TArray<float>* ParentTimes, const TArray<FTransform>& RawWorldBones, const TArray<FTransform>& NewWorldBones, const TArray<int32>& TargetBoneIndices, int32 NumFrames, int32 BoneIndex, int32 ParentBoneIndex, float ParentScale, float MaxDelta, float MaxTargetDelta, float EffectorDiffSocket, const TArray<FBoneData>& BoneData ) { const int32 KeyCount = Keys.Num(); check( Keys.Num() == Times.Num() ); check( KeyCount >= 1 ); // generate new arrays we will fill with the final keys TArray<T> NewKeys; TArray<float> NewTimes; NewKeys.Empty(KeyCount); NewTimes.Empty(KeyCount); // Only bother doing anything if we have some keys! if(KeyCount > 0) { int32 LowKey = 0; int32 HighKey = KeyCount-1; int32 PrevKey = 0; // copy the low key (this one is a given) NewTimes.Add(Times[0]); NewKeys.Add(Keys[0]); FTransform DummyBone(FQuat::Identity, FVector(END_EFFECTOR_SOCKET_DUMMY_BONE_SIZE, END_EFFECTOR_SOCKET_DUMMY_BONE_SIZE, END_EFFECTOR_SOCKET_DUMMY_BONE_SIZE)); float const DeltaThreshold = (BoneData[BoneIndex].IsEndEffector() && (BoneData[BoneIndex].bHasSocket || BoneData[BoneIndex].bKeyEndEffector)) ? EffectorDiffSocket : MaxTargetDelta; // We will test within a sliding window between LowKey and HighKey. // Therefore, we are done when the LowKey exceeds the range while (LowKey < KeyCount-1) { // high key always starts at the top of the range HighKey = KeyCount-1; // keep testing until the window is closed while (HighKey > LowKey+1) { // get the parameters of the window we are testing const float LowTime = Times[LowKey]; const float HighTime = Times[HighKey]; const T LowValue = Keys[LowKey]; const T HighValue = Keys[HighKey]; const float Range = HighTime - LowTime; const float InvRange = 1.0f/Range; // iterate through all interpolated members of the window to // compute the error when compared to the original raw values float MaxLerpError = 0.0f; float MaxTargetError = 0.0f; for (int32 TestKey = LowKey+1; TestKey< HighKey; ++TestKey) { // get the parameters of the member being tested float TestTime = Times[TestKey]; T TestValue = Keys[TestKey]; // compute the proposed, interpolated value for the key const float Alpha = (TestTime - LowTime) * InvRange; const T LerpValue = Interpolate(LowValue, HighValue, Alpha); // compute the error between our interpolated value and the desired value float LerpError = CalcDelta(TestValue, LerpValue); // if the local-space lerp error is within our tolerances, we will also check the // effect this interpolated key will have on our target end effectors float TargetError = -1.0f; if (LerpError <= MaxDelta) { // get the raw world transform for this bone (the original world-space position) const int32 FrameIndex = TestKey; const FTransform& RawBase = RawWorldBones[(BoneIndex*NumFrames) + FrameIndex]; // generate the proposed local bone atom and transform (local space) FTransform ProposedTM = UpdateBoneAtom(BoneIndex, BoneAtoms[FrameIndex], LerpValue); // convert the proposed local transform to world space using this bone's parent transform const FTransform& CurrentParent = ParentBoneIndex != INDEX_NONE ? NewWorldBones[(ParentBoneIndex*NumFrames) + FrameIndex] : FTransform::Identity; FTransform ProposedBase = ProposedTM * CurrentParent; // for each target end effector, compute the error we would introduce with our proposed key for (int32 TargetIndex=0; TargetIndex<TargetBoneIndices.Num(); ++TargetIndex) { // find the offset transform from the raw base to the end effector const int32 TargetBoneIndex = TargetBoneIndices[TargetIndex]; FTransform RawTarget = RawWorldBones[(TargetBoneIndex*NumFrames) + FrameIndex]; const FTransform& RelTM = RawTarget.GetRelativeTransform(RawBase); // forecast where the new end effector would be using our proposed key FTransform ProposedTarget = RelTM * ProposedBase; // If this is an EndEffector with a Socket attached to it, add an extra bone, to measure error introduced by effector rotation compression. if( BoneData[TargetIndex].bHasSocket || BoneData[TargetIndex].bKeyEndEffector ) { ProposedTarget = DummyBone * ProposedTarget; RawTarget = DummyBone * RawTarget; } // determine the extend of error at the target end effector float ThisError = (ProposedTarget.GetTranslation() - RawTarget.GetTranslation()).Size(); TargetError = FMath::Max(TargetError, ThisError); // exit early when we encounter a large delta float const TargetDeltaThreshold = BoneData[TargetIndex].bHasSocket ? EffectorDiffSocket : DeltaThreshold; if( TargetError > TargetDeltaThreshold ) { break; } } } // If the parent has a key at this time, we'll scale our error values as requested. // This increases the odds that we will choose keys on the same frames as our parent bone, // making the skeleton more uniform in key distribution. if (ParentTimes) { if (ParentTimes->Find(TestTime) != INDEX_NONE) { // our parent has a key at this time, // inflate our perceived error to increase our sensitivity // for also retaining a key at this time LerpError *= ParentScale; TargetError *= ParentScale; } } // keep track of the worst errors encountered for both // the local-space 'lerp' error and the end effector drift we will cause MaxLerpError = FMath::Max(MaxLerpError, LerpError); MaxTargetError = FMath::Max(MaxTargetError, TargetError); // exit early if we have failed in this span if (MaxLerpError > MaxDelta || MaxTargetError > DeltaThreshold) { break; } } // determine if the span succeeded. That is, the worst errors found are within tolerances if (MaxLerpError <= MaxDelta && MaxTargetError <= DeltaThreshold) { // save the high end of the test span as our next key NewTimes.Add(Times[HighKey]); NewKeys.Add(Keys[HighKey]); // start testing a new span LowKey = HighKey; HighKey = KeyCount-1; } else { // we failed, shrink the test span window and repeat --HighKey; } } // if the test window is still valid, accept the high key if (HighKey > LowKey) { NewTimes.Add(Times[HighKey]); NewKeys.Add(Keys[HighKey]); } LowKey= HighKey; } // The process has ended, but we must make sure the last key is accounted for if (NewTimes.Last() != Times.Last() && CalcDelta(Keys.Last(), NewKeys.Last()) >= MaxDelta ) { NewTimes.Add(Times.Last()); NewKeys.Add(Keys.Last()); } // return the new key set to the caller Times= NewTimes; Keys= NewKeys; } }
bool FEdMode::InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale) { if(UsesPropertyWidgets()) { AActor* SelectedActor = GetFirstSelectedActorInstance(); if(SelectedActor != NULL && InViewportClient->GetCurrentWidgetAxis() != EAxisList::None) { GEditor->NoteActorMovement(); if (EditedPropertyName != TEXT("")) { FTransform LocalTM = FTransform::Identity; if(bEditedPropertyIsTransform) { LocalTM = GetPropertyValueByName<FTransform>(SelectedActor, EditedPropertyName, EditedPropertyIndex); } else { FVector LocalPos = GetPropertyValueByName<FVector>(SelectedActor, EditedPropertyName, EditedPropertyIndex); LocalTM = FTransform(LocalPos); } // Get actor transform (actor to world) FTransform ActorTM = SelectedActor->ActorToWorld(); // Calculate world transform FTransform WorldTM = LocalTM * ActorTM; // Calc delta specified by drag //FTransform DeltaTM(InRot.Quaternion(), InDrag); // Apply delta in world space WorldTM.SetTranslation(WorldTM.GetTranslation() + InDrag); WorldTM.SetRotation(InRot.Quaternion() * WorldTM.GetRotation()); // Convert new world transform back into local space LocalTM = WorldTM.GetRelativeTransform(ActorTM); // Apply delta scale LocalTM.SetScale3D(LocalTM.GetScale3D() + InScale); SelectedActor->PreEditChange(NULL); if(bEditedPropertyIsTransform) { SetPropertyValueByName<FTransform>(SelectedActor, EditedPropertyName, EditedPropertyIndex, LocalTM); } else { SetPropertyValueByName<FVector>(SelectedActor, EditedPropertyName, EditedPropertyIndex, LocalTM.GetLocation()); } SelectedActor->PostEditChange(); return true; } } } if( GetCurrentTool() ) { return GetCurrentTool()->InputDelta(InViewportClient,InViewport,InDrag,InRot,InScale); } return 0; }