// Copyright Epic Games, Inc. All Rights Reserved. #include "PoseSearch/PoseSearchFeatureChannel_Heading.h" #include "Engine/BlueprintGeneratedClass.h" #include "PoseSearch/PoseSearchAssetIndexer.h" #include "PoseSearch/PoseSearchAssetSampler.h" #include "PoseSearch/PoseSearchContext.h" #include "PoseSearch/PoseSearchDatabase.h" #include "PoseSearch/PoseSearchSchema.h" #include "PoseSearch/PoseSearchFeatureChannel_Position.h" #if WITH_EDITOR #include "PropertyHandle.h" #endif // WITH_EDITOR UPoseSearchFeatureChannel_Heading::UPoseSearchFeatureChannel_Heading() { bUseBlueprintQueryOverride = Cast(GetClass()) != nullptr; } void UPoseSearchFeatureChannel_Heading::FindOrAddToSchema(UPoseSearchSchema* Schema, float SampleTimeOffset, const FName& BoneName, const UE::PoseSearch::FRole& Role, EHeadingAxis HeadingAxis, EPermutationTimeType PermutationTimeType) { if (!Schema->FindChannel([SampleTimeOffset, &BoneName, Role, HeadingAxis, PermutationTimeType](const UPoseSearchFeatureChannel* Channel) -> const UPoseSearchFeatureChannel_Heading* { if (const UPoseSearchFeatureChannel_Heading* Heading = Cast(Channel)) { // @todo: channels are already finalized, so we can use SchemaBoneIdx and SchemaOriginBoneIdx instead of Bone.BoneName and OriginBone.BoneName if (Heading->Bone.BoneName == BoneName && Heading->OriginBone.BoneName == NAME_None && Heading->SampleTimeOffset == SampleTimeOffset && Heading->OriginTimeOffset == 0.f && Heading->HeadingAxis == HeadingAxis && Heading->PermutationTimeType == PermutationTimeType && Heading->SampleRole == Role && Heading->OriginRole == Role && Heading->bDefaultWithRootBone) { return Heading; } } return nullptr; })) { UPoseSearchFeatureChannel_Heading* Heading = NewObject(Schema, NAME_None, RF_Transient); Heading->Bone.BoneName = BoneName; Heading->SampleRole = Role; Heading->OriginRole = Role; #if WITH_EDITORONLY_DATA Heading->Weight = 0.f; Heading->DebugColor = FLinearColor::Gray; #endif // WITH_EDITORONLY_DATA Heading->SampleTimeOffset = SampleTimeOffset; Heading->HeadingAxis = HeadingAxis; Heading->PermutationTimeType = PermutationTimeType; Schema->AddTemporaryChannel(Heading); } } bool UPoseSearchFeatureChannel_Heading::Finalize(UPoseSearchSchema* Schema) { using namespace UE::PoseSearch; ChannelDataOffset = Schema->SchemaCardinality; ChannelCardinality = UE::PoseSearch::FFeatureVectorHelper::GetVectorCardinality(ComponentStripping); Schema->SchemaCardinality += ChannelCardinality; SchemaBoneIdx = Schema->AddBoneReference(Bone, SampleRole, bDefaultWithRootBone); SchemaOriginBoneIdx = Schema->AddBoneReference(OriginBone, OriginRole, bDefaultWithRootBone); return SchemaBoneIdx != InvalidSchemaBoneIdx && SchemaOriginBoneIdx != InvalidSchemaBoneIdx; } void UPoseSearchFeatureChannel_Heading::AddDependentChannels(UPoseSearchSchema* Schema) const { if (Schema->bInjectAdditionalDebugChannels) { const EPermutationTimeType SamplePermutationTimeType = PermutationTimeType != EPermutationTimeType::UseSampleTime ? EPermutationTimeType::UseSampleToPermutationTime : EPermutationTimeType::UseSampleTime; UPoseSearchFeatureChannel_Position::FindOrAddToSchema(Schema, SampleTimeOffset, Bone.BoneName, SampleRole, SamplePermutationTimeType); // injecting 2 Heading channels to be able to reconstruct the Origin bone rotation const EPermutationTimeType OriginPermutationTimeType = PermutationTimeType == EPermutationTimeType::UsePermutationTime ? EPermutationTimeType::UseSampleToPermutationTime : EPermutationTimeType::UseSampleTime; UPoseSearchFeatureChannel_Heading::FindOrAddToSchema(Schema, OriginTimeOffset, Bone.BoneName, OriginRole, EHeadingAxis::X, OriginPermutationTimeType); UPoseSearchFeatureChannel_Heading::FindOrAddToSchema(Schema, OriginTimeOffset, Bone.BoneName, OriginRole, EHeadingAxis::Y, OriginPermutationTimeType); } } FVector UPoseSearchFeatureChannel_Heading::GetAxis(const FQuat& Rotation) const { switch (HeadingAxis) { case EHeadingAxis::X: return Rotation.GetAxisX(); case EHeadingAxis::Y: return Rotation.GetAxisY(); case EHeadingAxis::Z: return Rotation.GetAxisZ(); } checkNoEntry(); return FVector::XAxisVector; } void UPoseSearchFeatureChannel_Heading::BuildQuery(UE::PoseSearch::FSearchContext& SearchContext) const { using namespace UE::PoseSearch; const bool bIsRootBone = SchemaBoneIdx == RootSchemaBoneIdx; if (bUseBlueprintQueryOverride) { if (const UAnimInstance* AnimInstance = Cast(SearchContext.GetContext(SampleRole)->GetFirstObjectParam())) { const FQuat BoneRotationWorld = BP_GetWorldRotation(AnimInstance); const FQuat BoneRotation = SearchContext.GetSampleRotation(SampleTimeOffset, OriginTimeOffset, SchemaBoneIdx, SchemaOriginBoneIdx, SampleRole, OriginRole, EPermutationTimeType::UseSampleTime, &BoneRotationWorld); FFeatureVectorHelper::EncodeVector(SearchContext.EditFeatureVector(), ChannelDataOffset, GetAxis(BoneRotation), ComponentStripping, true); } else { // @todo: support non UAnimInstance anim contexts for AnimNext UE_LOG(LogPoseSearch, Warning, TEXT("UPoseSearchFeatureChannel_Heading::BuildQuery - unsupported null UAnimInstance: WIP support for AnimNext!")); } return; } // trying to get the BuildQuery data from another schema UPoseSearchFeatureChannel_Heading already cached in the SearchContext if (SearchContext.IsUseCachedChannelData()) { // composing a unique identifier to specify this channel with all the required properties to be able to share the query data with other channels of the same type uint32 UniqueIdentifier = GetClass()->GetUniqueID(); UniqueIdentifier = HashCombineFast(UniqueIdentifier, GetTypeHash(SampleRole)); UniqueIdentifier = HashCombineFast(UniqueIdentifier, GetTypeHash(OriginRole)); UniqueIdentifier = HashCombineFast(UniqueIdentifier, GetTypeHash(SamplingAttributeId)); UniqueIdentifier = HashCombineFast(UniqueIdentifier, GetTypeHash(SampleTimeOffset)); UniqueIdentifier = HashCombineFast(UniqueIdentifier, GetTypeHash(OriginTimeOffset)); UniqueIdentifier = HashCombineFast(UniqueIdentifier, GetTypeHash(HeadingAxis)); UniqueIdentifier = HashCombineFast(UniqueIdentifier, GetTypeHash(SchemaBoneIdx)); UniqueIdentifier = HashCombineFast(UniqueIdentifier, GetTypeHash(SchemaOriginBoneIdx)); UniqueIdentifier = HashCombineFast(UniqueIdentifier, GetTypeHash(InputQueryPose)); UniqueIdentifier = HashCombineFast(UniqueIdentifier, GetTypeHash(ComponentStripping)); UniqueIdentifier = HashCombineFast(UniqueIdentifier, GetTypeHash(PermutationTimeType)); TConstArrayView CachedChannelData; if (const UPoseSearchFeatureChannel* CachedChannel = SearchContext.GetCachedChannelData(UniqueIdentifier, this, CachedChannelData)) { #if DO_CHECK const UPoseSearchFeatureChannel_Heading* CachedHeadingChannel = Cast(CachedChannel); check(CachedHeadingChannel); check(CachedHeadingChannel->GetChannelCardinality() == ChannelCardinality); check(CachedChannelData.Num() == ChannelCardinality); // making sure there were no hash collisions check(CachedHeadingChannel->SampleRole == SampleRole); check(CachedHeadingChannel->OriginRole == OriginRole); check(CachedHeadingChannel->SamplingAttributeId == SamplingAttributeId); check(CachedHeadingChannel->SampleTimeOffset == SampleTimeOffset); check(CachedHeadingChannel->OriginTimeOffset == OriginTimeOffset); check(CachedHeadingChannel->HeadingAxis == HeadingAxis); check(CachedHeadingChannel->SchemaBoneIdx == SchemaBoneIdx); check(CachedHeadingChannel->SchemaOriginBoneIdx == SchemaOriginBoneIdx); check(CachedHeadingChannel->InputQueryPose == InputQueryPose); check(CachedHeadingChannel->ComponentStripping == ComponentStripping); check(CachedHeadingChannel->PermutationTimeType == PermutationTimeType); #endif //DO_CHECK // copying the CachedChannelData into this channel portion of the FeatureVectorBuilder FFeatureVectorHelper::Copy(SearchContext.EditFeatureVector().Slice(ChannelDataOffset, ChannelCardinality), 0, ChannelCardinality, CachedChannelData); return; } } const bool bCanUseCurrentResult = SearchContext.CanUseCurrentResult(); const bool bSkip = InputQueryPose != EInputQueryPose::UseCharacterPose && bCanUseCurrentResult && SampleRole == OriginRole; if (bSkip || (!SearchContext.ArePoseHistoriesValid() && !bIsRootBone)) { if (bCanUseCurrentResult) { FFeatureVectorHelper::Copy(SearchContext.EditFeatureVector(), ChannelDataOffset, ChannelCardinality, SearchContext.GetCurrentResultPoseVector()); return; } // we leave the SearchContext.EditFeatureVector() set to zero since the SearchContext.PoseHistory is invalid and it'll fail if we continue UE_LOG(LogPoseSearch, Error, TEXT("UPoseSearchFeatureChannel_Heading::BuildQuery - Failed because Pose History Node is missing.")); return; } // calculating the BoneRotation in component space for the bone indexed by SchemaBoneIdx const FQuat BoneRotation = SearchContext.GetSampleRotation(SampleTimeOffset, OriginTimeOffset, SchemaBoneIdx, SchemaOriginBoneIdx, SampleRole, OriginRole, PermutationTimeType); FFeatureVectorHelper::EncodeVector(SearchContext.EditFeatureVector(), ChannelDataOffset, GetAxis(BoneRotation), ComponentStripping,true); } #if ENABLE_DRAW_DEBUG void UPoseSearchFeatureChannel_Heading::DebugDraw(const UE::PoseSearch::FDebugDrawParams& DrawParams, TConstArrayView PoseVector) const { using namespace UE::PoseSearch; bool bDrawInjectAdditionalDebugChannels = false; #if WITH_EDITORONLY_DATA if (const UPoseSearchSchema* Schema = GetSchema()) { bDrawInjectAdditionalDebugChannels = Schema->bDrawInjectAdditionalDebugChannels; } #endif // WITH_EDITORONLY_DATA if (bDrawInjectAdditionalDebugChannels || DrawParams.IsAnyWeightRelevant(this)) { FColor Color; #if WITH_EDITORONLY_DATA Color = DebugColor.ToFColor(true); #else // WITH_EDITORONLY_DATA Color = FLinearColor::White.ToFColor(true); #endif // WITH_EDITORONLY_DATA float PermutationSampleTimeOffset = 0.f; float PermutationOriginTimeOffset = 0.f; UPoseSearchFeatureChannel::GetPermutationTimeOffsets(PermutationTimeType, DrawParams.ExtractPermutationTime(PoseVector), PermutationSampleTimeOffset, PermutationOriginTimeOffset); const EPermutationTimeType SamplePermutationTimeType = PermutationTimeType != EPermutationTimeType::UseSampleTime ? EPermutationTimeType::UseSampleToPermutationTime : EPermutationTimeType::UseSampleTime; const EPermutationTimeType OriginPermutationTimeType = PermutationTimeType == EPermutationTimeType::UsePermutationTime ? EPermutationTimeType::UseSampleToPermutationTime : EPermutationTimeType::UseSampleTime; const FVector FeaturesVector = FFeatureVectorHelper::DecodeVector(PoseVector, ChannelDataOffset, ComponentStripping); const FVector BoneHeading = DrawParams.ExtractRotation(PoseVector, OriginTimeOffset, SchemaOriginBoneIdx, OriginRole, OriginPermutationTimeType, INDEX_NONE, PermutationOriginTimeOffset).RotateVector(FeaturesVector); const FVector BonePos = DrawParams.ExtractPosition(PoseVector, SampleTimeOffset, SchemaBoneIdx, SampleRole, SamplePermutationTimeType, SamplingAttributeId, PermutationSampleTimeOffset); DrawParams.DrawPoint(BonePos, Color, 3.f); DrawParams.DrawLine(BonePos + BoneHeading * 4.f, BonePos + BoneHeading * 15.f, Color); } } #endif // ENABLE_DRAW_DEBUG #if WITH_EDITOR void UPoseSearchFeatureChannel_Heading::FillWeights(TArrayView Weights) const { for (int32 i = 0; i < ChannelCardinality; ++i) { Weights[ChannelDataOffset + i] = Weight; } } bool UPoseSearchFeatureChannel_Heading::IndexAsset(UE::PoseSearch::FAssetIndexer& Indexer) const { using namespace UE::PoseSearch; FQuat SampleRotation = FQuat::Identity; for (int32 SampleIdx = Indexer.GetBeginSampleIdx(); SampleIdx != Indexer.GetEndSampleIdx(); ++SampleIdx) { if (Indexer.GetSampleRotation(SampleRotation, SampleTimeOffset, OriginTimeOffset, SampleIdx, SchemaBoneIdx, SchemaOriginBoneIdx, SampleRole, OriginRole, PermutationTimeType, SamplingAttributeId)) { FFeatureVectorHelper::EncodeVector(Indexer.GetPoseVector(SampleIdx), ChannelDataOffset, GetAxis(SampleRotation), ComponentStripping, true); } else { return false; } } return true; } UE::PoseSearch::TLabelBuilder& UPoseSearchFeatureChannel_Heading::GetLabel(UE::PoseSearch::TLabelBuilder& LabelBuilder, UE::PoseSearch::ELabelFormat LabelFormat) const { using namespace UE::PoseSearch; GetOuterLabel(LabelBuilder, LabelFormat); AppendLabelSeparator(LabelBuilder, LabelFormat); LabelBuilder.Append(TEXT("Head")); switch (HeadingAxis) { case EHeadingAxis::X: LabelBuilder.Append(TEXT("X")); break; case EHeadingAxis::Y: LabelBuilder.Append(TEXT("Y")); break; case EHeadingAxis::Z: LabelBuilder.Append(TEXT("Z")); break; } if (ComponentStripping == EComponentStrippingVector::StripXY) { LabelBuilder.Append(TEXT("_z")); } else if (ComponentStripping == EComponentStrippingVector::StripZ) { LabelBuilder.Append(TEXT("_xy")); } const UPoseSearchSchema* Schema = GetSchema(); check(Schema); if (SchemaBoneIdx > RootSchemaBoneIdx) { LabelBuilder.Append(TEXT("_")); LabelBuilder.Append(Schema->GetBoneReferences(SampleRole)[SchemaBoneIdx].BoneName.ToString()); } else if (SchemaBoneIdx == TrajectorySchemaBoneIdx) { LabelBuilder.Append(TEXT("_Trj")); } if (SampleRole != DefaultRole) { LabelBuilder.Append(TEXT("[")); LabelBuilder.Append(SampleRole.ToString()); LabelBuilder.Append(TEXT("]")); } if (SchemaOriginBoneIdx > RootSchemaBoneIdx) { LabelBuilder.Append(TEXT("_")); LabelBuilder.Append(Schema->GetBoneReferences(OriginRole)[SchemaOriginBoneIdx].BoneName.ToString()); } else if (SchemaOriginBoneIdx == TrajectorySchemaBoneIdx) { LabelBuilder.Append(TEXT("_Trj")); } if (OriginRole != DefaultRole) { LabelBuilder.Append(TEXT("[")); LabelBuilder.Append(OriginRole.ToString()); LabelBuilder.Append(TEXT("]")); } if (PermutationTimeType == EPermutationTimeType::UsePermutationTime) { LabelBuilder.Append(TEXT("_PT")); } else if (PermutationTimeType == EPermutationTimeType::UseSampleToPermutationTime) { LabelBuilder.Append(TEXT("_SPT")); } AppendLabelSeparator(LabelBuilder, LabelFormat, true); LabelBuilder.Appendf(TEXT("%.2f"), SampleTimeOffset); if (!FMath::IsNearlyZero(OriginTimeOffset)) { LabelBuilder.Appendf(TEXT("-%.2f"), OriginTimeOffset); } return LabelBuilder; } USkeleton* UPoseSearchFeatureChannel_Heading::GetSkeleton(bool& bInvalidSkeletonIsError, const IPropertyHandle* PropertyHandle) { // blueprint generated classes don't have a schema, until they're instanced by the schema if (const UPoseSearchSchema* Schema = GetSchema()) { bInvalidSkeletonIsError = false; if (PropertyHandle) { if (PropertyHandle->GetProperty()->GetFName() == GET_MEMBER_NAME_CHECKED(UPoseSearchFeatureChannel_Heading, Bone)) { return Schema->GetSkeleton(SampleRole); } if (PropertyHandle->GetProperty()->GetFName() == GET_MEMBER_NAME_CHECKED(UPoseSearchFeatureChannel_Heading, OriginBone)) { return Schema->GetSkeleton(OriginRole); } } } return Super::GetSkeleton(bInvalidSkeletonIsError, PropertyHandle); } #endif