void ADynamicWeather::LoadWeatherDescriptionsFromFile( const FString &MapName, TArray<FWeatherDescription> &Descriptions) { // Try to load config file. FString DefaultFilePath; if (GetWeatherIniFilePath(GetIniFileName(), DefaultFilePath)) { UE_LOG(LogCarla, Log, TEXT("Loading weather description from %s"), *DefaultFilePath); FIniFile ConfigFile(DefaultFilePath); { // Override map specific presets. FString MapOverridesFilePath; if (GetWeatherIniFilePath(GetIniFileName(MapName), MapOverridesFilePath)) { UE_LOG(LogCarla, Log, TEXT("Loading weather description from %s"), *MapOverridesFilePath); ConfigFile.Combine(MapOverridesFilePath); } } // For every section in the config file add a weather description. for (auto &Item : ConfigFile.GetFConfigFile()) { Descriptions.AddDefaulted(1u); Descriptions.Last().ReadFromConfigFile(ConfigFile, Item.Key); } } // If no description was found, append a defaulted one. if (Descriptions.Num() == 0) { UE_LOG(LogCarla, Warning, TEXT("No weather description found")); Descriptions.AddDefaulted(1u); Descriptions.Last().Name = TEXT("Default"); } }
/** * Returns a list of all assets referenced by the specified UObject. */ void FFindReferencedAssets::BuildAssetList(UObject *Object, const TArray<UClass*>& IgnoreClasses, const TArray<UObject*>& IgnorePackages, TSet<UObject*>& ReferencedAssets, bool bIncludeDefaultRefs) { TArray<FReferencedAssets> LocalReferencers; // Create a new entry for this actor. new( LocalReferencers ) FReferencedAssets( Object ); for (FObjectIterator It; It; ++It) { // Skip the level, world, and any packages that should be ignored if ( ShouldSearchForAssets(*It, IgnoreClasses, IgnorePackages, bIncludeDefaultRefs) ) { It->Mark(OBJECTMARK_TagExp); } else { It->UnMark(OBJECTMARK_TagExp); } } // Add to the list of referenced assets. FFindAssetsArchive( Object, LocalReferencers.Last().AssetList, NULL, /*MaxRecursion=*/0, /*bIncludeClasses=*/true, bIncludeDefaultRefs ); ReferencedAssets = LocalReferencers.Last().AssetList; }
void UPathFindingComponent::DrawPath(FVector start, TArray<FVector>& route, FColor color, float duration, float thickness) { if (duration <= 0.0f) { DrawDebugLine(GetWorld(), start, route.Last(), color, true, -1.0f, (uint8)'\000', thickness); for (auto index = 0; index < route.Num() - 1; index++) DrawDebugLine(GetWorld(), route[index], route[index + 1], color, true, -1.0f, (uint8)'\000', thickness); } else { DrawDebugLine(GetWorld(), start, route.Last(), color, false, duration, (uint8)'\000', thickness); for (auto index = 0; index < route.Num() - 1; index++) DrawDebugLine(GetWorld(), route[index], route[index + 1], color, false, duration, (uint8)'\000', thickness); } }
void UMaterialParameterCollectionInstance::GetParameterData(TArray<FVector4>& ParameterData) const { // The memory layout created here must match the index assignment in UMaterialParameterCollection::GetParameterIndex if (Collection) { ParameterData.Empty(FMath::DivideAndRoundUp(Collection->ScalarParameters.Num(), 4) + Collection->VectorParameters.Num()); for (int32 ParameterIndex = 0; ParameterIndex < Collection->ScalarParameters.Num(); ParameterIndex++) { const FCollectionScalarParameter& Parameter = Collection->ScalarParameters[ParameterIndex]; // Add a new vector for each packed vector if (ParameterIndex % 4 == 0) { ParameterData.Add(FVector4(0, 0, 0, 0)); } FVector4& CurrentVector = ParameterData.Last(); const float* InstanceData = ScalarParameterValues.Find(Parameter.ParameterName); // Pack into the appropriate component of this packed vector CurrentVector[ParameterIndex % 4] = InstanceData ? *InstanceData : Parameter.DefaultValue; } for (int32 ParameterIndex = 0; ParameterIndex < Collection->VectorParameters.Num(); ParameterIndex++) { const FCollectionVectorParameter& Parameter = Collection->VectorParameters[ParameterIndex]; const FLinearColor* InstanceData = VectorParameterValues.Find(Parameter.ParameterName); ParameterData.Add(InstanceData ? *InstanceData : Parameter.DefaultValue); } } }
void SSequencerLabelBrowser::ReloadLabelList(bool FullyReload) { LabelList.Reset(); LabelList.Add(MakeShareable(new FSequencerLabelTreeNode(FString(), FText::GetEmpty()))); TArray<FString> AllLabels; if (Sequencer.IsValid() && Sequencer.Pin()->GetLabelManager().GetAllLabels(AllLabels) > 0) { for (const auto& Label : AllLabels) { // create new leaf node TArray<FString> Strings; Label.ParseIntoArray(Strings, TEXT("."), true); TSharedRef<FSequencerLabelTreeNode> NewNode = MakeShareable( new FSequencerLabelTreeNode(Label, FText::FromString(Strings.Last()))); // insert node into tree TArray<TSharedPtr<FSequencerLabelTreeNode>>* ParentNodes = &LabelList; int32 Index = 0; while (Index < Strings.Num() - 1) { TSharedPtr<FSequencerLabelTreeNode> Parent; for (const auto& Node : *ParentNodes) { if (Node->Label == Strings[Index]) { Parent = Node; break; } } // create interior node if needed if (!Parent.IsValid()) { FString ParentLabel = Strings[0]; for (int32 SubIndex = 1; SubIndex <= Index; ++SubIndex) { ParentLabel += TEXT(".") + Strings[SubIndex]; } Parent = MakeShareable(new FSequencerLabelTreeNode(ParentLabel, FText::FromString(Strings[Index]))); ParentNodes->Add(Parent); } ParentNodes = &Parent->Children; ++Index; } // insert node into tree ParentNodes->Add(NewNode); } } LabelTreeView->RequestTreeRefresh(); }
FVector FSCSEditorViewportClient::GetWidgetLocation() const { FVector Location = FVector::ZeroVector; AActor* PreviewActor = GetPreviewActor(); if(PreviewActor) { TArray<FSCSEditorTreeNodePtrType> SelectedNodes = BlueprintEditorPtr.Pin()->GetSelectedSCSEditorTreeNodes(); if(SelectedNodes.Num() > 0) { // Use the last selected item for the widget location USceneComponent* SceneComp = Cast<USceneComponent>(SelectedNodes.Last().Get()->FindComponentInstanceInActor(PreviewActor)); if( SceneComp ) { TSharedPtr<ISCSEditorCustomization> Customization = BlueprintEditorPtr.Pin()->CustomizeSCSEditor(SceneComp); FVector CustomLocation; if(Customization.IsValid() && Customization->HandleGetWidgetLocation(SceneComp, CustomLocation)) { Location = CustomLocation; } else { Location = SceneComp->GetComponentLocation(); } } } } return Location; }
static void BuildDependencyMapAndCompile(const TArray<UUserDefinedStruct*>& ChangedStructs, FCompilerResultsLog& MessageLog) { struct FDependencyMapEntry { UUserDefinedStruct* Struct; TSet<UUserDefinedStruct*> StructuresToWaitFor; FDependencyMapEntry() : Struct(NULL) {} void Initialize(UUserDefinedStruct* ChangedStruct, const TArray<UUserDefinedStruct*>& AllChangedStructs) { Struct = ChangedStruct; check(Struct); auto Schema = GetDefault<UEdGraphSchema_K2>(); for (auto& VarDesc : FStructureEditorUtils::GetVarDesc(Struct)) { auto StructType = Cast<UUserDefinedStruct>(VarDesc.SubCategoryObject.Get()); if (StructType && (VarDesc.Category == Schema->PC_Struct) && AllChangedStructs.Contains(StructType)) { StructuresToWaitFor.Add(StructType); } } } }; TArray<FDependencyMapEntry> DependencyMap; for (auto Iter = ChangedStructs.CreateConstIterator(); Iter; ++Iter) { DependencyMap.Add(FDependencyMapEntry()); DependencyMap.Last().Initialize(*Iter, ChangedStructs); } while (DependencyMap.Num()) { int32 StructureToCompileIndex = INDEX_NONE; for (int32 EntryIndex = 0; EntryIndex < DependencyMap.Num(); ++EntryIndex) { if(0 == DependencyMap[EntryIndex].StructuresToWaitFor.Num()) { StructureToCompileIndex = EntryIndex; break; } } check(INDEX_NONE != StructureToCompileIndex); UUserDefinedStruct* Struct = DependencyMap[StructureToCompileIndex].Struct; check(Struct); FUserDefinedStructureCompilerInner::CleanAndSanitizeStruct(Struct); FUserDefinedStructureCompilerInner::InnerCompileStruct(Struct, GetDefault<UEdGraphSchema_K2>(), MessageLog); DependencyMap.RemoveAtSwap(StructureToCompileIndex); for (auto EntryIter = DependencyMap.CreateIterator(); EntryIter; ++EntryIter) { (*EntryIter).StructuresToWaitFor.Remove(Struct); } } }
/** Generates a Sample from the given step 1D probability distribution function. */ void Sample1dCDF(const TArray<float>& PDFArray, const TArray<float>& CDFArray, float UnnormalizedIntegral, FLMRandomStream& RandomStream, float& PDF, float& Sample) { checkSlow(PDFArray.Num() > 0); checkSlow(PDFArray.Num() == CDFArray.Num()); // See pages 641-644 of the "Physically Based Rendering" book for an excellent description of // How to sample from a piecewise-constant 1d function, which this implementation is based on. if (PDFArray.Num() > 1) { // Get a uniformly distributed pseudo-random number const float RandomFraction = RandomStream.GetFraction(); int32 GreaterElementIndex = -1; // Find the index of where the step function becomes greater or equal to the generated number //@todo - CDFArray is monotonically increasing so we can do better than a linear time search for (int32 i = 1; i < CDFArray.Num(); i++) { if (CDFArray[i] >= RandomFraction) { GreaterElementIndex = i; break; } } if (GreaterElementIndex >= 0) { check(GreaterElementIndex >= 1 && GreaterElementIndex < CDFArray.Num()); // Find the fraction that the generated number is from the element before the greater or equal element. const float OffsetAlongCDFSegment = (RandomFraction - CDFArray[GreaterElementIndex - 1]) / (CDFArray[GreaterElementIndex] - CDFArray[GreaterElementIndex - 1]); // Access the probability that this element was selected and normalize it PDF = PDFArray[GreaterElementIndex - 1] / UnnormalizedIntegral; Sample = (GreaterElementIndex - 1 + OffsetAlongCDFSegment) / (float)CDFArray.Num(); } else { // The last element in the 1d CDF was selected const float OffsetAlongCDFSegment = (RandomFraction - CDFArray.Last()) / (1.0f - CDFArray.Last()); PDF = PDFArray.Last() / UnnormalizedIntegral; Sample = FMath::Clamp((CDFArray.Num() - 1 + OffsetAlongCDFSegment) / (float)CDFArray.Num(), 0.0f, 1.0f - DELTA); } } else { PDF = 1.0f; Sample = 0; } }
bool FRawCurveTracks::DuplicateCurveDataImpl(TArray<DataType> & Curves, USkeleton::AnimCurveUID ToCopyUid, USkeleton::AnimCurveUID NewUid) { DataType* ExistingCurve = GetCurveDataImpl<DataType>(Curves, ToCopyUid); if(ExistingCurve && GetCurveDataImpl<DataType>(Curves, NewUid) == NULL) { // Add the curve to the track and set its data to the existing curve Curves.Add(DataType(NewUid, ExistingCurve->GetCurveTypeFlags())); Curves.Last().CopyCurve(*ExistingCurve); return true; } return false; }
TArray<FString> USkeleUtilityFunctionLibrary::GetDefaultPlayerNamesFromFile() { FString filePath = FPaths::GameDir() + "Config/DefaultPlayerNames.ini"; FString fileContents = ""; FFileHelper::LoadFileToString(fileContents, *filePath); TArray<FString> items; int32 itemCount = fileContents.ParseIntoArray(items, TEXT(","), true); if (items.Last().Contains(TEXT("\n"))) { items.Pop(); } return items; }
//------------------------------------------------------------------------------ int32 FLinkerPlaceholderBase::ResolvePlaceholderPropertyValues(UObject* NewObjectValue) { int32 ResolvedTotal = 0; UObject* ThisAsUObject = GetPlaceholderAsUObject(); for (auto& ReferencingPair : ReferencingContainers) { TWeakObjectPtr<UObject> ContainerPtr = ReferencingPair.Key; if (!ContainerPtr.IsValid()) { continue; } UObject* Container = ContainerPtr.Get(); for (const UObjectProperty* Property : ReferencingPair.Value) { #if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS check(Property->GetOwnerClass() == Container->GetClass()); #endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS TArray<const UProperty*> PropertyChain; LinkerPlaceholderObjectImpl::BuildPropertyChain(Property, PropertyChain); const UProperty* OutermostProperty = PropertyChain.Last(); uint8* OutermostAddress = OutermostProperty->ContainerPtrToValuePtr<uint8>((uint8*)Container, /*ArrayIndex =*/0); int32 ResolvedCount = LinkerPlaceholderObjectImpl::ResolvePlaceholderValues(PropertyChain, PropertyChain.Num() - 1, OutermostAddress, ThisAsUObject, NewObjectValue); ResolvedTotal += ResolvedCount; #if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS // we expect that (because we have had ReferencingProperties added) // there should be at least one reference that is resolved... if // there were none, then a property could have changed its value // after it was set to this // // NOTE: this may seem it can be resolved by properties removing // themselves from ReferencingProperties, but certain // properties may be the inner of a UArrayProperty (meaning // there could be multiple references per property)... we'd // have to inc/decrement a property ref-count to resolve that // scenario check(ResolvedCount > 0); #endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS } } return ResolvedTotal; }
bool UAirBlueprintLib::GetLastObstaclePosition(const AActor* actor, const FVector& start, const FVector& end, FHitResult& hit, const AActor* ignore_actor, ECollisionChannel collision_channel) { TArray<FHitResult> hits; FCollisionQueryParams trace_params; trace_params.AddIgnoredActor(actor); if (ignore_actor != nullptr) trace_params.AddIgnoredActor(ignore_actor); bool has_hit = actor->GetWorld()->LineTraceMultiByChannel(hits, start, end, collision_channel, trace_params); if (hits.Num()) hit = hits.Last(0); return has_hit; }
void ParseString(const FString& StringToParse) { DataIndex = 0; DataString = StringToParse; if (DataIndex >= DataString.Len()) { return; } const FString TabString(TEXT(" ")); FColor TagColor; FNode CurrentNode(DefaultColor); for (EStringParserToken Token = ReadToken(); Token != EStringParserToken::EndOfString; Token = ReadToken()) { switch (Token) { case EStringParserToken::RegularChar: CurrentNode.String.AppendChar(DataString[DataIndex]); break; case EStringParserToken::NewLine: NodeList.Add(CurrentNode); CurrentNode = FNode(NodeList.Last().Color); CurrentNode.bNewLine = true; break; case EStringParserToken::Tab: CurrentNode.String.Append(TabString); break; case EStringParserToken::OpenTag: if (ParseTag(TagColor)) { NodeList.Add(CurrentNode); CurrentNode = FNode(TagColor); } break; } DataIndex++; } NodeList.Add(CurrentNode); }
bool UCrowdManager::SetAgentMovePath(const UCrowdFollowingComponent* AgentComponent, const FNavMeshPath* Path, int32 PathSectionStart, int32 PathSectionEnd, const FVector& PathSectionEndLocation) const { SCOPE_CYCLE_COUNTER(STAT_AI_Crowd_AgentUpdateTime); bool bSuccess = false; #if WITH_RECAST const FCrowdAgentData* AgentData = ActiveAgents.Find(AgentComponent); ARecastNavMesh* RecastNavData = Cast<ARecastNavMesh>(MyNavData); if (AgentData && AgentData->bIsSimulated && AgentData->IsValid() && DetourCrowd && RecastNavData && Path && (Path->GetPathPoints().Num() > 1) && Path->PathCorridor.IsValidIndex(PathSectionStart) && Path->PathCorridor.IsValidIndex(PathSectionEnd)) { FVector TargetPos = PathSectionEndLocation; if (PathSectionEnd < (Path->PathCorridor.Num() - 1)) { RecastNavData->GetPolyCenter(Path->PathCorridor[PathSectionEnd], TargetPos); } TArray<dtPolyRef> PathRefs; for (int32 Idx = PathSectionStart; Idx <= PathSectionEnd; Idx++) { PathRefs.Add(Path->PathCorridor[Idx]); } const INavigationQueryFilterInterface* NavFilter = Path->GetFilter().IsValid() ? Path->GetFilter()->GetImplementation() : MyNavData->GetDefaultQueryFilterImpl(); const dtQueryFilter* DetourFilter = ((const FRecastQueryFilter*)NavFilter)->GetAsDetourQueryFilter(); DetourCrowd->updateAgentFilter(AgentData->AgentIndex, DetourFilter); DetourCrowd->updateAgentState(AgentData->AgentIndex, false); const FVector RcTargetPos = Unreal2RecastPoint(TargetPos); bSuccess = DetourCrowd->requestMoveTarget(AgentData->AgentIndex, PathRefs.Last(), &RcTargetPos.X); if (bSuccess) { bSuccess = DetourCrowd->setAgentCorridor(AgentData->AgentIndex, PathRefs.GetData(), PathRefs.Num()); } } #endif return bSuccess; }
/** Calculates the step 1D cumulative distribution function for the given unnormalized probability distribution function. */ void CalculateStep1dCDF(const TArray<float>& PDF, TArray<float>& CDF, float& UnnormalizedIntegral) { CDF.Empty(PDF.Num()); float RunningUnnormalizedIntegral = 0; CDF.Add(0.0f); for (int32 i = 1; i < PDF.Num(); i++) { RunningUnnormalizedIntegral += PDF[i - 1]; CDF.Add(RunningUnnormalizedIntegral); } UnnormalizedIntegral = RunningUnnormalizedIntegral + PDF.Last(); if (UnnormalizedIntegral > 0.0f) { // Normalize the CDF for (int32 i = 1; i < CDF.Num(); i++) { CDF[i] /= UnnormalizedIntegral; } } check(CDF.Num() == PDF.Num()); }
void FSCSEditorViewportClient::FocusViewportToSelection() { AActor* PreviewActor = GetPreviewActor(); if(PreviewActor) { TArray<FSCSEditorTreeNodePtrType> SelectedNodes = BlueprintEditorPtr.Pin()->GetSelectedSCSEditorTreeNodes(); if(SelectedNodes.Num() > 0) { // Use the last selected item for the widget location USceneComponent* SceneComp = Cast<USceneComponent>(SelectedNodes.Last()->FindComponentInstanceInActor(PreviewActor)); if( SceneComp ) { FocusViewportOnBox( SceneComp->Bounds.GetBox() ); } } else { FocusViewportOnBox( PreviewActor->GetComponentsBoundingBox( true ) ); } } }
void FGroupedKeyCollection::UpdateIndex() const { auto& IndexEntry = GlobalIndex.FindOrAdd(IndexKey); TArray<FKeyHandle> NewKeyHandles; TArray<float> NewRepresentativeTimes; IndexEntry.HandleToGroup.Reset(); for (int32 GroupIndex = 0; GroupIndex < Groups.Num(); ++GroupIndex) { float RepresentativeTime = Groups[GroupIndex].RepresentativeTime; // Find a key handle we can recycle int32 RecycledIndex = IndexEntry.RepresentativeTimes.IndexOfByPredicate([&](float Time){ // Must be an *exact* match to recycle return Time == RepresentativeTime; }); if (RecycledIndex != INDEX_NONE) { NewKeyHandles.Add(IndexEntry.GroupHandles[RecycledIndex]); NewRepresentativeTimes.Add(IndexEntry.RepresentativeTimes[RecycledIndex]); IndexEntry.GroupHandles.RemoveAt(RecycledIndex, 1, false); IndexEntry.RepresentativeTimes.RemoveAt(RecycledIndex, 1, false); } else { NewKeyHandles.Add(FKeyHandle()); NewRepresentativeTimes.Add(RepresentativeTime); } IndexEntry.HandleToGroup.Add(NewKeyHandles.Last(), GroupIndex); } IndexEntry.GroupHandles = MoveTemp(NewKeyHandles); IndexEntry.RepresentativeTimes = MoveTemp(NewRepresentativeTimes); }
FMatrix FSCSEditorViewportClient::GetWidgetCoordSystem() const { FMatrix Matrix = FMatrix::Identity; if( GetWidgetCoordSystemSpace() == COORD_Local ) { AActor* PreviewActor = GetPreviewActor(); auto BlueprintEditor = BlueprintEditorPtr.Pin(); if (PreviewActor && BlueprintEditor.IsValid()) { TArray<FSCSEditorTreeNodePtrType> SelectedNodes = BlueprintEditor->GetSelectedSCSEditorTreeNodes(); if(SelectedNodes.Num() > 0) { const auto SelectedNode = SelectedNodes.Last(); USceneComponent* SceneComp = SelectedNode.IsValid() ? Cast<USceneComponent>(SelectedNode->FindComponentInstanceInActor(PreviewActor)) : NULL; if( SceneComp ) { TSharedPtr<ISCSEditorCustomization> Customization = BlueprintEditor->CustomizeSCSEditor(SceneComp); FMatrix CustomTransform; if(Customization.IsValid() && Customization->HandleGetWidgetTransform(SceneComp, CustomTransform)) { Matrix = CustomTransform; } else { Matrix = FRotationMatrix( SceneComp->GetComponentRotation() ); } } } } } if(!Matrix.Equals(FMatrix::Identity)) { Matrix.RemoveScaling(); } return Matrix; }
void FRawProfilerSession::ProcessStatPacketArray( const FStatPacketArray& StatPacketArray, FProfilerFrame& out_ProfilerFrame, int32 FrameIndex ) { // @TODO yrx 2014-03-24 Standardize thread names and id // @TODO yrx 2014-04-22 Remove all references to the data provider, event graph etc once data graph can visualize. // Raw stats callstack for this stat packet array. TMap<FName,FProfilerStackNode*> ThreadNodes; const FProfilerStatMetaDataRef MetaData = GetMetaData(); FProfilerSampleArray& MutableCollection = const_cast<FProfilerSampleArray&>(DataProvider->GetCollection()); // Add a root sample for this frame. const uint32 FrameRootSampleIndex = DataProvider->AddHierarchicalSample( 0, MetaData->GetStatByID( 1 ).OwningGroup().ID(), 1, 0.0f, 0.0f, 1 ); // Iterate through all stats packets and raw stats messages. FName GameThreadFName = NAME_None; for( int32 PacketIndex = 0; PacketIndex < StatPacketArray.Packets.Num(); PacketIndex++ ) { const FStatPacket& StatPacket = *StatPacketArray.Packets[PacketIndex]; FName ThreadFName = StatsThreadStats.Threads.FindChecked( StatPacket.ThreadId ); const uint32 NewThreadID = MetaData->ThreadIDtoStatID.FindChecked( StatPacket.ThreadId ); // @TODO yrx 2014-04-29 Only game or render thread is supported at this moment. if( StatPacket.ThreadType != EThreadType::Game && StatPacket.ThreadType != EThreadType::Renderer ) { continue; } // Workaround for issue with rendering thread names. if( StatPacket.ThreadType == EThreadType::Renderer ) { ThreadFName = NAME_RenderThread; } else if( StatPacket.ThreadType == EThreadType::Game ) { GameThreadFName = ThreadFName; } FProfilerStackNode* ThreadNode = ThreadNodes.FindRef( ThreadFName ); if( !ThreadNode ) { FString ThreadIdName = FStatsUtils::BuildUniqueThreadName( StatPacket.ThreadId ); FStatMessage ThreadMessage( ThreadFName, EStatDataType::ST_int64, STAT_GROUP_TO_FStatGroup( STATGROUP_Threads )::GetGroupName(), STAT_GROUP_TO_FStatGroup( STATGROUP_Threads )::GetGroupCategory(), *ThreadIdName, true, true ); //FStatMessage ThreadMessage( ThreadFName, EStatDataType::ST_int64, nullptr, nullptr, TEXT( "" ), true, true ); ThreadMessage.NameAndInfo.SetFlag( EStatMetaFlags::IsPackedCCAndDuration, true ); ThreadMessage.Clear(); // Add a thread sample. const uint32 ThreadRootSampleIndex = DataProvider->AddHierarchicalSample ( NewThreadID, MetaData->GetStatByID( NewThreadID ).OwningGroup().ID(), NewThreadID, -1.0f, -1.0f, 1, FrameRootSampleIndex ); ThreadNode = ThreadNodes.Add( ThreadFName, new FProfilerStackNode( nullptr, ThreadMessage, ThreadRootSampleIndex, FrameIndex ) ); } TArray<const FStatMessage*> StartStack; TArray<FProfilerStackNode*> Stack; Stack.Add( ThreadNode ); FProfilerStackNode* Current = Stack.Last(); const FStatMessagesArray& Data = StatPacket.StatMessages; for( int32 Index = 0; Index < Data.Num(); Index++ ) { const FStatMessage& Item = Data[Index]; const EStatOperation::Type Op = Item.NameAndInfo.GetField<EStatOperation>(); const FName LongName = Item.NameAndInfo.GetRawName(); const FName ShortName = Item.NameAndInfo.GetShortName(); const FName RenderingThreadTickCommandName = TEXT("RenderingThreadTickCommand"); // Workaround for render thread hierarchy. EStatOperation::AdvanceFrameEventRenderThread is called within the scope. if( ShortName == RenderingThreadTickCommandName ) { continue; } if( Op == EStatOperation::CycleScopeStart || Op == EStatOperation::CycleScopeEnd || Op == EStatOperation::AdvanceFrameEventRenderThread ) { //check( Item.NameAndInfo.GetFlag( EStatMetaFlags::IsCycle ) ); if( Op == EStatOperation::CycleScopeStart ) { FProfilerStackNode* ChildNode = new FProfilerStackNode( Current, Item, -1, FrameIndex ); Current->Children.Add( ChildNode ); // Add a child sample. const uint32 SampleIndex = DataProvider->AddHierarchicalSample ( NewThreadID, MetaData->GetStatByFName( ShortName ).OwningGroup().ID(), // GroupID MetaData->GetStatByFName( ShortName ).ID(), // StatID MetaData->ConvertCyclesToMS( ChildNode->CyclesStart ), // StartMS MetaData->ConvertCyclesToMS( 0 ), // DurationMS 1, Current->SampleIndex ); ChildNode->SampleIndex = SampleIndex; Stack.Add( ChildNode ); StartStack.Add( &Item ); Current = ChildNode; } // Workaround for render thread hierarchy. EStatOperation::AdvanceFrameEventRenderThread is called within the scope. if( Op == EStatOperation::AdvanceFrameEventRenderThread ) { int k=0;k++; } if( Op == EStatOperation::CycleScopeEnd ) { const FStatMessage ScopeStart = *StartStack.Pop(); const FStatMessage ScopeEnd = Item; const int64 Delta = int32( uint32( ScopeEnd.GetValue_int64() ) - uint32( ScopeStart.GetValue_int64() ) ); Current->CyclesEnd = Current->CyclesStart + Delta; Current->CycleCounterStartTimeMS = MetaData->ConvertCyclesToMS( Current->CyclesStart ); Current->CycleCounterEndTimeMS = MetaData->ConvertCyclesToMS( Current->CyclesEnd ); if( Current->CycleCounterStartTimeMS > Current->CycleCounterEndTimeMS ) { int k=0;k++; } check( Current->CycleCounterEndTimeMS >= Current->CycleCounterStartTimeMS ); FProfilerStackNode* ChildNode = Current; // Update the child sample's DurationMS. MutableCollection[ChildNode->SampleIndex].SetDurationMS( MetaData->ConvertCyclesToMS( Delta ) ); verify( Current == Stack.Pop() ); Current = Stack.Last(); } } } } // Calculate thread times. for( auto It = ThreadNodes.CreateIterator(); It; ++It ) { FProfilerStackNode& ThreadNode = *It.Value(); const int32 ChildrenNum = ThreadNode.Children.Num(); if( ChildrenNum > 0 ) { const int32 LastChildIndex = ThreadNode.Children.Num() - 1; ThreadNode.CyclesStart = ThreadNode.Children[0]->CyclesStart; ThreadNode.CyclesEnd = ThreadNode.Children[LastChildIndex]->CyclesEnd; ThreadNode.CycleCounterStartTimeMS = MetaData->ConvertCyclesToMS( ThreadNode.CyclesStart ); ThreadNode.CycleCounterEndTimeMS = MetaData->ConvertCyclesToMS( ThreadNode.CyclesEnd ); FProfilerSample& ProfilerSample = MutableCollection[ThreadNode.SampleIndex]; ProfilerSample.SetStartAndEndMS( MetaData->ConvertCyclesToMS( ThreadNode.CyclesStart ), MetaData->ConvertCyclesToMS( ThreadNode.CyclesEnd ) ); } } // Get the game thread time. check( GameThreadFName != NAME_None ); const FProfilerStackNode& GameThreadNode = *ThreadNodes.FindChecked( GameThreadFName ); const double GameThreadStartMS = MetaData->ConvertCyclesToMS( GameThreadNode.CyclesStart ); const double GameThreadEndMS = MetaData->ConvertCyclesToMS( GameThreadNode.CyclesEnd ); MutableCollection[FrameRootSampleIndex].SetStartAndEndMS( GameThreadStartMS, GameThreadEndMS ); // Advance frame const uint32 LastFrameIndex = DataProvider->GetNumFrames(); DataProvider->AdvanceFrame( GameThreadEndMS - GameThreadStartMS ); // Update aggregated stats UpdateAggregatedStats( LastFrameIndex ); // Update aggregated events. UpdateAggregatedEventGraphData( LastFrameIndex ); // RootNode is the same as the game thread node. out_ProfilerFrame.Root->CycleCounterStartTimeMS = GameThreadStartMS; out_ProfilerFrame.Root->CycleCounterEndTimeMS = GameThreadEndMS; for( auto It = ThreadNodes.CreateIterator(); It; ++It ) { out_ProfilerFrame.AddChild( It.Value() ); } out_ProfilerFrame.SortChildren(); }
static void BuildDependencyMapAndCompile(TArray<UBlueprintGeneratedStruct*>& ChangedStructs, FCompilerResultsLog& MessageLog) { struct FFindStructureDescriptionPred { const UBlueprintGeneratedStruct* const Struct; FFindStructureDescriptionPred(const UBlueprintGeneratedStruct* InStruct) : Struct(InStruct) { check(Struct); } bool operator()(const FBPStructureDescription& Desc) { return (Desc.CompiledStruct == Struct); } }; struct FDependencyMapEntry { UBlueprintGeneratedStruct* Struct; TSet<UBlueprintGeneratedStruct*> StructuresToWaitFor; FDependencyMapEntry() : Struct(NULL) {} void Initialize(UBlueprintGeneratedStruct* ChangedStruct, TArray<UBlueprintGeneratedStruct*>& AllChangedStructs) { Struct = ChangedStruct; check(Struct && Struct->StructGeneratedBy); FBPStructureDescription* StructureDescription = Struct->StructGeneratedBy->UserDefinedStructures.FindByPredicate(FFindStructureDescriptionPred(Struct)); check(StructureDescription); auto Schema = GetDefault<UEdGraphSchema_K2>(); for (auto FieldIter = StructureDescription->Fields.CreateIterator(); FieldIter; ++FieldIter) { FEdGraphPinType FieldType = (*FieldIter).VarType; auto StructType = Cast<UBlueprintGeneratedStruct>(FieldType.PinSubCategoryObject.Get()); if (StructType && (FieldType.PinCategory == Schema->PC_Struct) && AllChangedStructs.Contains(StructType)) { StructuresToWaitFor.Add(StructType); } } } }; TArray<FDependencyMapEntry> DependencyMap; for (auto Iter = ChangedStructs.CreateIterator(); Iter; ++Iter) { DependencyMap.Add(FDependencyMapEntry()); DependencyMap.Last().Initialize(*Iter, ChangedStructs); } while (DependencyMap.Num()) { int32 StructureToCompileIndex = INDEX_NONE; for (int32 EntryIndex = 0; EntryIndex < DependencyMap.Num(); ++EntryIndex) { if(0 == DependencyMap[EntryIndex].StructuresToWaitFor.Num()) { StructureToCompileIndex = EntryIndex; break; } } check(INDEX_NONE != StructureToCompileIndex); UBlueprintGeneratedStruct* Struct = DependencyMap[StructureToCompileIndex].Struct; check(Struct->StructGeneratedBy); FBPStructureDescription* StructureDescription = Struct->StructGeneratedBy->UserDefinedStructures.FindByPredicate(FFindStructureDescriptionPred(Struct)); check(StructureDescription); FUserDefinedStructureCompilerInner::CleanAndSanitizeStruct(Struct); FUserDefinedStructureCompilerInner::InnerCompileStruct(*StructureDescription, GetDefault<UEdGraphSchema_K2>(), MessageLog); DependencyMap.RemoveAtSwap(StructureToCompileIndex); for (auto EntryIter = DependencyMap.CreateIterator(); EntryIter; ++EntryIter) { (*EntryIter).StructuresToWaitFor.Remove(Struct); } } }
/** * Dumps the information held within the EditorPerfCaptureParameters struct into a CSV file. * @param EditorPerfStats is the name of the struct that holds the needed performance information. */ void EditorPerfDump(EditorPerfCaptureParameters& EditorPerfStats) { UE_LOG(LogEditorAutomationTests, Log, TEXT("Begin generating the editor performance charts.")); //The file location where to save the data. FString DataFileLocation = FPaths::Combine(*FPaths::AutomationLogDir(), TEXT("Performance"), *EditorPerfStats.MapName); //Get the map load time (in seconds) from the text file that is created when the load map latent command is ran. EditorPerfStats.MapLoadTime = 0; FString MapLoadTimeFileLocation = FPaths::Combine(*DataFileLocation, TEXT("RAWMapLoadTime.txt")); if (FPaths::FileExists(*MapLoadTimeFileLocation)) { TArray<FString> SavedMapLoadTimes; FAutomationEditorCommonUtils::CreateArrayFromFile(MapLoadTimeFileLocation, SavedMapLoadTimes); EditorPerfStats.MapLoadTime = FCString::Atof(*SavedMapLoadTimes.Last()); } //Filename for the RAW csv which holds the data gathered from a single test ran. FString RAWCSVFilePath = FString::Printf(TEXT("%s/RAW_%s_%s.csv"), *DataFileLocation, *EditorPerfStats.MapName, *FDateTime::Now().ToString()); //Filename for the pretty csv file. FString PerfCSVFilePath = FString::Printf(TEXT("%s/%s_Performance.csv"), *DataFileLocation, *EditorPerfStats.MapName); //Create the raw csv and then add the title row it. FArchive* RAWCSVArchive = IFileManager::Get().CreateFileWriter(*RAWCSVFilePath); FString RAWCSVLine = (TEXT("Map Name, Changelist, Time Stamp, Map Load Time, Average FPS, Frame Time, Used Physical Memory, Used Virtual Memory, Used Peak Physical, Used Peak Virtual, Available Physical Memory, Available Virtual Memory\n")); RAWCSVArchive->Serialize(TCHAR_TO_ANSI(*RAWCSVLine), RAWCSVLine.Len()); //Dump the stats from each run to the raw csv file and then close it. for (int32 i = 0; i < EditorPerfStats.TimeStamp.Num(); i++) { //If the raw file isn't available to write to then we'll fail back this test. if ( FAutomationEditorCommonUtils::IsArchiveWriteable(RAWCSVFilePath, RAWCSVArchive)) { RAWCSVLine = FString::Printf(TEXT("%s,%s,%s,%.3f,%.1f,%.1f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f%s"), *EditorPerfStats.MapName, *FEngineVersion::Current().ToString(EVersionComponent::Changelist), *EditorPerfStats.FormattedTimeStamp[i], EditorPerfStats.MapLoadTime, EditorPerfStats.AverageFPS[i], EditorPerfStats.AverageFrameTime[i], EditorPerfStats.UsedPhysical[i], EditorPerfStats.UsedVirtual[i], EditorPerfStats.PeakUsedPhysical[i], EditorPerfStats.PeakUsedVirtual[i], EditorPerfStats.AvailablePhysical[i], EditorPerfStats.AvailableVirtual[i], LINE_TERMINATOR); RAWCSVArchive->Serialize(TCHAR_TO_ANSI(*RAWCSVLine), RAWCSVLine.Len()); } } RAWCSVArchive->Close(); //Get the final pretty data for the Performance csv file. float AverageFPS = FAutomationEditorCommonUtils::TotalFromFloatArray(EditorPerfStats.AverageFPS, true); float AverageFrameTime = FAutomationEditorCommonUtils::TotalFromFloatArray(EditorPerfStats.AverageFrameTime, true); float MemoryUsedPhysical = FAutomationEditorCommonUtils::TotalFromFloatArray(EditorPerfStats.UsedPhysical, true); float MemoryAvailPhysAvg = FAutomationEditorCommonUtils::TotalFromFloatArray(EditorPerfStats.AvailablePhysical, true); float MemoryAvailVirtualAvg = FAutomationEditorCommonUtils::TotalFromFloatArray(EditorPerfStats.AvailableVirtual, true); float MemoryUsedVirtualAvg = FAutomationEditorCommonUtils::TotalFromFloatArray(EditorPerfStats.UsedVirtual, true); float MemoryUsedPeak = FAutomationEditorCommonUtils::LargestValueInFloatArray(EditorPerfStats.PeakUsedPhysical); float MemoryUsedPeakVirtual = FAutomationEditorCommonUtils::LargestValueInFloatArray(EditorPerfStats.PeakUsedVirtual); //TestRunDuration is the length of time the test lasted in ticks. FTimespan TestRunDuration = (EditorPerfStats.TimeStamp.Last().GetTicks() - EditorPerfStats.TimeStamp[0].GetTicks()) + ETimespan::TicksPerSecond; //The performance csv file will be created if it didn't exist prior to the start of this test. if (!FPaths::FileExists(*PerfCSVFilePath)) { FArchive* FinalCSVArchive = IFileManager::Get().CreateFileWriter(*PerfCSVFilePath); if ( FAutomationEditorCommonUtils::IsArchiveWriteable(PerfCSVFilePath, FinalCSVArchive)) { FString FinalCSVLine = (TEXT("Date, Map Name, Changelist, Test Run Time , Map Load Time, Average FPS, Average MS, Used Physical KB, Used Virtual KB, Used Peak Physcial KB, Used Peak Virtual KB, Available Physical KB, Available Virtual KB\n")); FinalCSVArchive->Serialize(TCHAR_TO_ANSI(*FinalCSVLine), FinalCSVLine.Len()); FinalCSVArchive->Close(); } } //Load the existing performance csv so that it doesn't get saved over and lost. FString OldPerformanceCSVFile; FFileHelper::LoadFileToString(OldPerformanceCSVFile, *PerfCSVFilePath); FArchive* FinalCSVArchive = IFileManager::Get().CreateFileWriter(*PerfCSVFilePath); if ( FAutomationEditorCommonUtils::IsArchiveWriteable(PerfCSVFilePath, FinalCSVArchive)) { //Dump the old performance csv file data to the new csv file. FinalCSVArchive->Serialize(TCHAR_TO_ANSI(*OldPerformanceCSVFile), OldPerformanceCSVFile.Len()); //Dump the pretty stats to the Performance CSV file and then close it so we can edit it while the engine is still running. FString FinalCSVLine = FString::Printf(TEXT("%s,%s,%s,%.0f,%.3f,%.1f,%.1f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f%s"), *FDateTime::Now().ToString(), *EditorPerfStats.MapName, *FEngineVersion::Current().ToString(EVersionComponent::Changelist), TestRunDuration.GetTotalSeconds(), EditorPerfStats.MapLoadTime, AverageFPS, AverageFrameTime, MemoryUsedPhysical, MemoryUsedVirtualAvg, MemoryUsedPeak, MemoryUsedPeakVirtual, MemoryAvailPhysAvg, MemoryAvailVirtualAvg, LINE_TERMINATOR); FinalCSVArchive->Serialize(TCHAR_TO_ANSI(*FinalCSVLine), FinalCSVLine.Len()); FinalCSVArchive->Close(); } //Display the test results to the user. UE_LOG(LogEditorAutomationTests, Display, TEXT("AVG FPS: '%.1f'"), AverageFPS); UE_LOG(LogEditorAutomationTests, Display, TEXT("AVG Frame Time: '%.1f' ms"), AverageFrameTime); UE_LOG(LogEditorAutomationTests, Display, TEXT("AVG Used Physical Memory: '%.0f' kb"), MemoryUsedPhysical); UE_LOG(LogEditorAutomationTests, Display, TEXT("AVG Used Virtual Memory: '%.0f' kb"), MemoryUsedVirtualAvg); UE_LOG(LogEditorAutomationTests, Display, TEXT("Performance csv file is located here: %s"), *FPaths::ConvertRelativePathToFull(PerfCSVFilePath)); UE_LOG(LogEditorAutomationTests, Log, TEXT("Performance csv file is located here: %s"), *FPaths::ConvertRelativePathToFull(PerfCSVFilePath)); UE_LOG(LogEditorAutomationTests, Log, TEXT("Raw performance csv file is located here: %s"), *FPaths::ConvertRelativePathToFull(RAWCSVFilePath)); }
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 UBTCompositeNode::DoDecoratorsAllowExecution(class UBehaviorTreeComponent* OwnerComp, int32 InstanceIdx, int32 ChildIdx) const { const FBTCompositeChild& ChildInfo = Children[ChildIdx]; bool bResult = true; if (ChildInfo.Decorators.Num() == 0) { return bResult; } FBehaviorTreeInstance& MyInstance = OwnerComp->InstanceStack[InstanceIdx]; if (ChildInfo.DecoratorOps.Num() == 0) { // simple check: all decorators must agree for (int32 i = 0; i < ChildInfo.Decorators.Num(); i++) { const UBTDecorator* TestDecorator = ChildInfo.Decorators[i]; const bool bIsAllowed = TestDecorator->CanExecute(OwnerComp, TestDecorator->GetNodeMemory<uint8>(MyInstance)); OwnerComp->StoreDebuggerSearchStep(TestDecorator, InstanceIdx, bIsAllowed); if (!bIsAllowed) { UE_VLOG(OwnerComp->GetOwner(), LogBehaviorTree, Verbose, TEXT("Child[%d] execution forbidden by %s"), ChildIdx, *UBehaviorTreeTypes::DescribeNodeHelper(TestDecorator)); bResult = false; break; } } } else { // advanced check: follow decorator logic operations (composite decorator on child link) UE_VLOG(OwnerComp->GetOwner(), LogBehaviorTree, Verbose, TEXT("Child[%d] execution test with logic operations"), ChildIdx); TArray<FOperationStackInfo> OperationStack; FString Indent; // debugger data collection: // - get index of each decorator from main AND test, they will match graph nodes // - if first operator is not AND it means, that there's only single composite decorator on line // - while updating top level stack, grab index of first failed node int32 NodeDecoratorIdx = INDEX_NONE; int32 FailedDecoratorIdx = INDEX_NONE; bool bShouldStoreNodeIndex = true; for (int32 i = 0; i < ChildInfo.DecoratorOps.Num(); i++) { const FBTDecoratorLogic& DecoratorOp = ChildInfo.DecoratorOps[i]; if (IsLogicOp(DecoratorOp)) { OperationStack.Add(FOperationStackInfo(DecoratorOp)); Indent += TEXT(" "); UE_VLOG(OwnerComp->GetOwner(), LogBehaviorTree, Verbose, TEXT("%spushed %s:%d"), *Indent, *DescribeLogicOp(DecoratorOp.Operation), DecoratorOp.Number); } else if (DecoratorOp.Operation == EBTDecoratorLogic::Test) { const bool bHasOverride = OperationStack.Num() ? OperationStack.Last().bHasForcedResult : false; const bool bCurrentOverride = OperationStack.Num() ? OperationStack.Last().bForcedResult : false; // debugger: store first decorator of graph node if (bShouldStoreNodeIndex) { bShouldStoreNodeIndex = false; NodeDecoratorIdx = DecoratorOp.Number; } UBTDecorator* TestDecorator = ChildInfo.Decorators[DecoratorOp.Number]; const bool bIsAllowed = bHasOverride ? bCurrentOverride : TestDecorator->CanExecute(OwnerComp, TestDecorator->GetNodeMemory<uint8>(MyInstance)); UE_VLOG(OwnerComp->GetOwner(), LogBehaviorTree, Verbose, TEXT("%s%s %s: %s"), *Indent, bHasOverride ? TEXT("skipping") : TEXT("testing"), *UBehaviorTreeTypes::DescribeNodeHelper(TestDecorator), bIsAllowed ? TEXT("allowed") : TEXT("forbidden")); bResult = UpdateOperationStack(OwnerComp, Indent, OperationStack, bIsAllowed, FailedDecoratorIdx, NodeDecoratorIdx, bShouldStoreNodeIndex); if (OperationStack.Num() == 0) { UE_VLOG(OwnerComp->GetOwner(), LogBehaviorTree, Verbose, TEXT("finished execution test: %s"), bResult ? TEXT("allowed") : TEXT("forbidden")); OwnerComp->StoreDebuggerSearchStep(ChildInfo.Decorators[FMath::Max(0, FailedDecoratorIdx)], InstanceIdx, bResult); break; } } } } return bResult; }
static bool UpdateOperationStack(const class UBehaviorTreeComponent* OwnerComp, FString& Indent, TArray<FOperationStackInfo>& Stack, bool bTestResult, int32& FailedDecoratorIdx, int32& NodeDecoratorIdx, bool& bShouldStoreNodeIndex) { if (Stack.Num() == 0) { return bTestResult; } FOperationStackInfo& CurrentOp = Stack.Last(); CurrentOp.NumLeft--; if (CurrentOp.Op == EBTDecoratorLogic::And) { if (!CurrentOp.bHasForcedResult && !bTestResult) { CurrentOp.bHasForcedResult = true; CurrentOp.bForcedResult = bTestResult; } } else if (CurrentOp.Op == EBTDecoratorLogic::Or) { if (!CurrentOp.bHasForcedResult && bTestResult) { CurrentOp.bHasForcedResult = true; CurrentOp.bForcedResult = bTestResult; } } else if (CurrentOp.Op == EBTDecoratorLogic::Not) { bTestResult = !bTestResult; } // update debugger while processing top level stack if (Stack.Num() == 1) { // reset node flag and grab next decorator index bShouldStoreNodeIndex = true; // store first failed node if (!bTestResult && FailedDecoratorIdx == INDEX_NONE) { FailedDecoratorIdx = NodeDecoratorIdx; } } if (CurrentOp.bHasForcedResult) { bTestResult = CurrentOp.bForcedResult; } if (CurrentOp.NumLeft == 0) { UE_VLOG(OwnerComp->GetOwner(), LogBehaviorTree, Verbose, TEXT("%s%s finished: %s"), *Indent, *DescribeLogicOp(CurrentOp.Op), bTestResult ? TEXT("allowed") : TEXT("forbidden")); Indent = Indent.LeftChop(2); Stack.RemoveAt(Stack.Num() - 1); return UpdateOperationStack(OwnerComp, Indent, Stack, bTestResult, FailedDecoratorIdx, NodeDecoratorIdx, bShouldStoreNodeIndex); } return bTestResult; }
// Reads a set of specifiers (with optional values) inside the () of a new-style metadata macro like UPROPERTY or UFUNCTION void FBaseParser::ReadSpecifierSetInsideMacro(TArray<FPropertySpecifier>& SpecifiersFound, const FString& TypeOfSpecifier, TMap<FName, FString>& MetaData) { int32 FoundSpecifierCount = 0; FString ErrorMessage = FString::Printf(TEXT("%s declaration specifier"), *TypeOfSpecifier); RequireSymbol(TEXT("("), *ErrorMessage); while (!MatchSymbol(TEXT(")"))) { if (FoundSpecifierCount > 0) { RequireSymbol(TEXT(","), *ErrorMessage); } ++FoundSpecifierCount; // Read the specifier key FToken Specifier; if (!GetToken(Specifier)) { FError::Throwf(TEXT("Expected %s"), *ErrorMessage); } if (Specifier.Matches(TEXT("meta"))) { RequireSymbol(TEXT("="), *ErrorMessage); RequireSymbol(TEXT("("), *ErrorMessage); // Keep reading comma-separated metadata pairs do { // Read a key FToken MetaKeyToken; if (!GetIdentifier(MetaKeyToken)) { FError::Throwf(TEXT("Expected a metadata key")); } FString Key = MetaKeyToken.Identifier; // Potentially read a value FString Value; if (MatchSymbol(TEXT("="))) { Value = ReadNewStyleValue(TypeOfSpecifier); } // Validate the value is a valid type for the key and insert it into the map InsertMetaDataPair(MetaData, Key, Value); } while ( MatchSymbol(TEXT(",")) ); RequireSymbol(TEXT(")"), *ErrorMessage); } // Look up specifier in metadata dictionary else if (FMetadataKeyword* MetadataKeyword = GetMetadataKeyword(Specifier.Identifier)) { if (MatchSymbol(TEXT("="))) { if (MetadataKeyword->ValueArgument == EMetadataValueArgument::None) { FError::Throwf(TEXT("Incorrect = after metadata specifier '%s'"), Specifier.Identifier); } FString Value = ReadNewStyleValue(TypeOfSpecifier); MetadataKeyword->ApplyToMetadata(MetaData, &Value); } else { if (MetadataKeyword->ValueArgument == EMetadataValueArgument::Required) { FError::Throwf(TEXT("Missing = after metadata specifier '%s'"), Specifier.Identifier); } MetadataKeyword->ApplyToMetadata(MetaData); } } else { // Creating a new specifier SpecifiersFound.Emplace(Specifier.Identifier); // Look for a value for this specifier if (MatchSymbol(TEXT("=")) || PeekSymbol(TEXT("("))) { TArray<FString>& NewPairValues = SpecifiersFound.Last().Values; if (!ReadOptionalCommaSeparatedListInParens(NewPairValues, TypeOfSpecifier)) { FString Value = ReadNewStyleValue(TypeOfSpecifier); NewPairValues.Add(Value); } } } } }
void FRCPassPostProcessVisualizeBuffer::Process(FRenderingCompositePassContext& Context) { SCOPED_DRAW_EVENT(VisualizeBuffer, DEC_SCENE_ITEMS); const FPooledRenderTargetDesc* InputDesc = GetInputDesc(ePId_Input0); if(!InputDesc) { // input is not hooked up correctly return; } const FSceneView& View = Context.View; const FSceneViewFamily& ViewFamily = *(View.Family); FIntRect SrcRect = View.ViewRect; FIntRect DestRect = View.ViewRect; FIntPoint SrcSize = InputDesc->Extent; const FSceneRenderTargetItem& DestRenderTarget = PassOutputs[0].RequestSurface(Context); // Set the view family's render target/viewport. RHISetRenderTarget(DestRenderTarget.TargetableTexture, FTextureRHIRef()); Context.SetViewportAndCallRHI(DestRect); // set the state RHISetBlendState(TStaticBlendState<>::GetRHI()); RHISetRasterizerState(TStaticRasterizerState<>::GetRHI()); RHISetDepthStencilState(TStaticDepthStencilState<false,CF_Always>::GetRHI()); SetShaderTempl<false>(Context); // Draw a quad mapping scene color to the view's render target DrawRectangle( 0, 0, DestRect.Width(), DestRect.Height(), SrcRect.Min.X, SrcRect.Min.Y, SrcRect.Width(), SrcRect.Height(), DestRect.Size(), SrcSize, EDRF_UseTriangleOptimization); // Now draw the requested tiles into the grid TShaderMapRef<FPostProcessVS> VertexShader(GetGlobalShaderMap()); TShaderMapRef<FPostProcessVisualizeBufferPS<true> > PixelShader(GetGlobalShaderMap()); RHISetBlendState(TStaticBlendState<CW_RGB, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha>::GetRHI()); static FGlobalBoundShaderState BoundShaderState; SetGlobalBoundShaderState(BoundShaderState, GFilterVertexDeclaration.VertexDeclarationRHI, *VertexShader, *PixelShader); PixelShader->SetPS(Context); // Track the name and position of each tile we draw so we can write text labels over them struct LabelRecord { FString Label; int32 LocationX; int32 LocationY; }; TArray<LabelRecord> Labels; const int32 MaxTilesX = 4; const int32 MaxTilesY = 4; const int32 TileWidth = DestRect.Width() / MaxTilesX; const int32 TileHeight = DestRect.Height() / MaxTilesY; int32 CurrentTileIndex = 0; for (TArray<TileData>::TConstIterator It = Tiles.CreateConstIterator(); It; ++It, ++CurrentTileIndex) { FRenderingCompositeOutputRef Tile = It->Source; if (Tile.IsValid()) { FTextureRHIRef Texture = Tile.GetOutput()->PooledRenderTarget->GetRenderTargetItem().TargetableTexture; int32 TileX = CurrentTileIndex % MaxTilesX; int32 TileY = CurrentTileIndex / MaxTilesX; PixelShader->SetSourceTexture(Texture); DrawRectangle( TileX * TileWidth, TileY * TileHeight, TileWidth, TileHeight, SrcRect.Min.X, SrcRect.Min.Y, SrcRect.Width(), SrcRect.Height(), DestRect.Size(), SrcSize, EDRF_Default); Labels.Add(LabelRecord()); Labels.Last().Label = It->Name; Labels.Last().LocationX = 8 + TileX * TileWidth; Labels.Last().LocationY = (TileY + 1) * TileHeight - 19; } } // Draw tile labels // this is a helper class for FCanvas to be able to get screen size class FRenderTargetTemp : public FRenderTarget { public: const FSceneView& View; const FTexture2DRHIRef Texture; FRenderTargetTemp(const FSceneView& InView, const FTexture2DRHIRef InTexture) : View(InView), Texture(InTexture) { } virtual FIntPoint GetSizeXY() const { return View.ViewRect.Size(); }; virtual const FTexture2DRHIRef& GetRenderTargetTexture() const { return Texture; } } TempRenderTarget(View, (const FTexture2DRHIRef&)DestRenderTarget.TargetableTexture); FCanvas Canvas(&TempRenderTarget, NULL, ViewFamily.CurrentRealTime, ViewFamily.CurrentWorldTime, ViewFamily.DeltaWorldTime); FLinearColor LabelColor(1, 1, 0); for (auto It = Labels.CreateConstIterator(); It; ++It) { Canvas.DrawShadowedString(It->LocationX, It->LocationY, *It->Label, GetStatsFont(), LabelColor); } Canvas.Flush(); RHICopyToResolveTarget(DestRenderTarget.TargetableTexture, DestRenderTarget.ShaderResourceTexture, false, FResolveParams()); }
static void ParseUpdateStatusResults(const FP4RecordSet& InRecords, const TArray<FText>& ErrorMessages, TArray<FPerforceSourceControlState>& OutStates) { // Iterate over each record found as a result of the command, parsing it for relevant information for (int32 Index = 0; Index < InRecords.Num(); ++Index) { const FP4Record& ClientRecord = InRecords[Index]; FString FileName = ClientRecord(TEXT("clientFile")); FString DepotFileName = ClientRecord(TEXT("depotFile")); FString HeadRev = ClientRecord(TEXT("headRev")); FString HaveRev = ClientRecord(TEXT("haveRev")); FString OtherOpen = ClientRecord(TEXT("otherOpen")); FString OpenType = ClientRecord(TEXT("type")); FString HeadAction = ClientRecord(TEXT("headAction")); FString Action = ClientRecord(TEXT("action")); FString HeadType = ClientRecord(TEXT("headType")); const bool bUnresolved = ClientRecord.Contains(TEXT("unresolved")); FString FullPath(FileName); FPaths::NormalizeFilename(FullPath); OutStates.Add(FPerforceSourceControlState(FullPath)); FPerforceSourceControlState& State = OutStates.Last(); State.DepotFilename = DepotFileName; State.State = EPerforceState::ReadOnly; if (Action.Len() > 0 && Action == TEXT("add")) { State.State = EPerforceState::OpenForAdd; } else if (Action.Len() > 0 && Action == TEXT("delete")) { State.State = EPerforceState::MarkedForDelete; } else if (OpenType.Len() > 0) { if(Action.Len() > 0 && Action == TEXT("branch")) { State.State = EPerforceState::Branched; } else { State.State = EPerforceState::CheckedOut; } } else if (OtherOpen.Len() > 0) { // OtherOpen just reports the number of developers that have a file open, now add a string for every entry int32 OtherOpenNum = FCString::Atoi(*OtherOpen); for ( int32 OpenIdx = 0; OpenIdx < OtherOpenNum; ++OpenIdx ) { const FString OtherOpenRecordKey = FString::Printf(TEXT("otherOpen%d"), OpenIdx); const FString OtherOpenRecordValue = ClientRecord(OtherOpenRecordKey); State.OtherUserCheckedOut += OtherOpenRecordValue; if(OpenIdx < OtherOpenNum - 1) { State.OtherUserCheckedOut += TEXT(", "); } } State.State = EPerforceState::CheckedOutOther; } //file has been previously deleted, ok to add again else if (HeadAction.Len() > 0 && HeadAction == TEXT("delete")) { State.State = EPerforceState::NotInDepot; } if (HeadRev.Len() > 0 && HaveRev.Len() > 0) { TTypeFromString<int>::FromString(State.DepotRevNumber, *HeadRev); TTypeFromString<int>::FromString(State.LocalRevNumber, *HaveRev); if( bUnresolved ) { int32 ResolveActionNumber = 0; for (;;) { // Extract the revision number FString VarName = FString::Printf(TEXT("resolveAction%d"), ResolveActionNumber); if (!ClientRecord.Contains(*VarName)) { // No more revisions ensureMsgf( ResolveActionNumber > 0, TEXT("Resolve is pending but no resolve actions for file %s"), *FileName ); break; } VarName = FString::Printf(TEXT("resolveBaseFile%d"), ResolveActionNumber); FString ResolveBaseFile = ClientRecord(VarName); VarName = FString::Printf(TEXT("resolveFromFile%d"), ResolveActionNumber); FString ResolveFromFile = ClientRecord(VarName); if(!ensureMsgf( ResolveFromFile == ResolveBaseFile, TEXT("Text cannot resolve %s with %s, we do not support cross file merging"), *ResolveBaseFile, *ResolveFromFile ) ) { break; } VarName = FString::Printf(TEXT("resolveBaseRev%d"), ResolveActionNumber); FString ResolveBaseRev = ClientRecord(VarName); TTypeFromString<int>::FromString(State.PendingResolveRevNumber, *ResolveBaseRev); ++ResolveActionNumber; } } } // Check binary status State.bBinary = false; if (HeadType.Len() > 0 && HeadType.Contains(TEXT("binary"))) { State.bBinary = true; } // Check exclusive checkout flag State.bExclusiveCheckout = false; if (HeadType.Len() > 0 && HeadType.Contains(TEXT("+l"))) { State.bExclusiveCheckout = true; } } // also see if we can glean anything from the error messages for (int32 Index = 0; Index < ErrorMessages.Num(); ++Index) { const FText& Error = ErrorMessages[Index]; //@todo P4 could be returning localized error messages int32 NoSuchFilePos = Error.ToString().Find(TEXT(" - no such file(s).\n"), ESearchCase::IgnoreCase, ESearchDir::FromStart); if(NoSuchFilePos != INDEX_NONE) { // found an error about a file that is not in the depot FString FullPath(Error.ToString().Left(NoSuchFilePos)); FPaths::NormalizeFilename(FullPath); OutStates.Add(FPerforceSourceControlState(FullPath)); FPerforceSourceControlState& State = OutStates.Last(); State.State = EPerforceState::NotInDepot; } //@todo P4 could be returning localized error messages int32 NotUnderClientRootPos = Error.ToString().Find(TEXT("' is not under client's root"), ESearchCase::IgnoreCase, ESearchDir::FromStart); if(NotUnderClientRootPos != INDEX_NONE) { // found an error about a file that is not under the client root static const FString Prefix(TEXT("Path \'")); FString FullPath(Error.ToString().Mid(Prefix.Len(), NotUnderClientRootPos - Prefix.Len())); FPaths::NormalizeFilename(FullPath); OutStates.Add(FPerforceSourceControlState(FullPath)); FPerforceSourceControlState& State = OutStates.Last(); State.State = EPerforceState::NotUnderClientRoot; } } }
TOptional<FExpressionError> CompileGroup(const FExpressionToken* GroupStart, const FGuid* StopAt) { enum class EState { PreUnary, PostUnary, Binary }; TArray<FWrappedOperator> OperatorStack; OperatorStack.Reserve(Tokens.Num() - CurrentTokenIndex); bool bFoundEndOfGroup = StopAt == nullptr; // Start off looking for a unary operator EState State = EState::PreUnary; for (; CurrentTokenIndex < Tokens.Num(); ++CurrentTokenIndex) { auto& Token = Tokens[CurrentTokenIndex]; const auto& TypeId = Token.Node.GetTypeId(); if (const FGuid* GroupingEnd = Grammar.GetGrouping(TypeId)) { // Ignore this token CurrentTokenIndex++; // Start of group - recurse auto Error = CompileGroup(&Token, GroupingEnd); if (Error.IsSet()) { return Error; } State = EState::PostUnary; } else if (StopAt && TypeId == *StopAt) { // End of group bFoundEndOfGroup = true; break; } else if (State == EState::PreUnary) { if (Grammar.HasPreUnaryOperator(TypeId)) { // Make this a unary op OperatorStack.Emplace(FCompiledToken(FCompiledToken::PreUnaryOperator, MoveTemp(Token))); } else if (Grammar.GetBinaryOperatorPrecedence(TypeId)) { return FExpressionError(FText::Format(LOCTEXT("SyntaxError_NoBinaryOperand", "Syntax error: No operand specified for operator '{0}'"), FText::FromString(Token.Context.GetString()))); } else if (Grammar.HasPostUnaryOperator(TypeId)) { // Found a post-unary operator for the preceeding token State = EState::PostUnary; // Pop off any pending unary operators while (OperatorStack.Num() > 0 && OperatorStack.Last().Precedence <= 0) { Commands.Add(OperatorStack.Pop(false).Steal()); } // Make this a post-unary op OperatorStack.Emplace(FCompiledToken(FCompiledToken::PostUnaryOperator, MoveTemp(Token))); } else { // Not an operator, so treat it as an ordinary token Commands.Add(FCompiledToken(FCompiledToken::Operand, MoveTemp(Token))); State = EState::PostUnary; } } else if (State == EState::PostUnary) { if (Grammar.HasPostUnaryOperator(TypeId)) { // Pop off any pending unary operators while (OperatorStack.Num() > 0 && OperatorStack.Last().Precedence <= 0) { Commands.Add(OperatorStack.Pop(false).Steal()); } // Make this a post-unary op OperatorStack.Emplace(FCompiledToken(FCompiledToken::PostUnaryOperator, MoveTemp(Token))); } else { // Checking for binary operators if (const int32* Prec = Grammar.GetBinaryOperatorPrecedence(TypeId)) { // Pop off anything of higher precedence than this one onto the command stack while (OperatorStack.Num() > 0 && OperatorStack.Last().Precedence < *Prec) { Commands.Add(OperatorStack.Pop(false).Steal()); } // Add the operator itself to the op stack OperatorStack.Emplace(FCompiledToken(FCompiledToken::BinaryOperator, MoveTemp(Token)), *Prec); // Check for a unary op again State = EState::PreUnary; } else { // Just add the token. It's possible that this is a syntax error (there's no binary operator specified between two tokens), // But we don't have enough information at this point to say whether or not it is an error Commands.Add(FCompiledToken(FCompiledToken::Operand, MoveTemp(Token))); State = EState::PreUnary; } } } } if (!bFoundEndOfGroup) { return FExpressionError(FText::Format(LOCTEXT("SyntaxError_UnmatchedGroup", "Syntax error: Reached end of expression before matching end of group '{0}' at line {1}:{2}"), FText::FromString(GroupStart->Context.GetString()), FText::AsNumber(GroupStart->Context.GetLineNumber()), FText::AsNumber(GroupStart->Context.GetCharacterIndex()) )); } // Pop everything off the operator stack, onto the command stack while (OperatorStack.Num() > 0) { Commands.Add(OperatorStack.Pop(false).Token); } return TOptional<FExpressionError>(); }
bool GenerateAirMeshes(const TArray<FVector>& Vertices, const TArray<int32>& Indices, TArray<TStaticArray<int32, 4u>>& OutTetrahedra) { check(Indices.Num() % 3 == 0); #if WITH_EDITOR namespace tw = tetgen_wrapper; // ================================================================ // Convert vertex positions to the format readable by tetgen // ================================================================ TArray<double> VerticesForTetgen; // 8 points are for bounding box VerticesForTetgen.Empty((Vertices.Num() + 8) * 3); for (const auto& Vertex : Vertices) { VerticesForTetgen.Add(Vertex.X); VerticesForTetgen.Add(Vertex.Y); VerticesForTetgen.Add(Vertex.Z); } // Generate bounding 8 points FBox BoundingBox(Vertices); float Extension = BoundingBox.GetExtent().GetAbsMax() * 0.02f; BoundingBox.Min -= FVector(Extension); BoundingBox.Max += FVector(Extension); VerticesForTetgen.Add(BoundingBox.Min.X); VerticesForTetgen.Add(BoundingBox.Min.Y); VerticesForTetgen.Add(BoundingBox.Min.Z); VerticesForTetgen.Add(BoundingBox.Max.X); VerticesForTetgen.Add(BoundingBox.Min.Y); VerticesForTetgen.Add(BoundingBox.Min.Z); VerticesForTetgen.Add(BoundingBox.Min.X); VerticesForTetgen.Add(BoundingBox.Max.Y); VerticesForTetgen.Add(BoundingBox.Min.Z); VerticesForTetgen.Add(BoundingBox.Max.X); VerticesForTetgen.Add(BoundingBox.Max.Y); VerticesForTetgen.Add(BoundingBox.Min.Z); VerticesForTetgen.Add(BoundingBox.Min.X); VerticesForTetgen.Add(BoundingBox.Min.Y); VerticesForTetgen.Add(BoundingBox.Max.Z); VerticesForTetgen.Add(BoundingBox.Max.X); VerticesForTetgen.Add(BoundingBox.Min.Y); VerticesForTetgen.Add(BoundingBox.Max.Z); VerticesForTetgen.Add(BoundingBox.Min.X); VerticesForTetgen.Add(BoundingBox.Max.Y); VerticesForTetgen.Add(BoundingBox.Max.Z); VerticesForTetgen.Add(BoundingBox.Max.X); VerticesForTetgen.Add(BoundingBox.Max.Y); VerticesForTetgen.Add(BoundingBox.Max.Z); // ================================================================ // Generate polygons for tetgen // ================================================================ int32 NumTriangles = Indices.Num() / 3; TArray<tw::polygon> PolygonsForTetgen; PolygonsForTetgen.Empty(NumTriangles + 6); // 6 are for bounding box faces for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; TriangleIndex++) { PolygonsForTetgen.AddUninitialized(); auto& Added = PolygonsForTetgen.Last(); Added.num_vertices = 3; Added.vertex_list = const_cast<tw::int32*>(&Indices[TriangleIndex * 3]); } // Indices for bounding box int32 BoundingBoxIndices[] = { 0, 1, 3, 2, 1, 5, 7, 3, 5, 4, 6, 7, 4, 0, 2, 6, 4, 5, 1, 0, 2, 3, 7, 6, }; check(ARRAYSIZE(BoundingBoxIndices) == 24); // add offset for (auto& BoundingBoxIndex : BoundingBoxIndices) { BoundingBoxIndex += Vertices.Num(); } for (int32 FaceIndex = 0; FaceIndex < ARRAYSIZE(BoundingBoxIndices) / 4; FaceIndex++) { PolygonsForTetgen.AddUninitialized(); auto& Added = PolygonsForTetgen.Last(); Added.num_vertices = 4; Added.vertex_list = &BoundingBoxIndices[FaceIndex * 4]; } // ================================================================ // Generate facets for tetgen // ================================================================ TArray<tw::facet> FacetsForTetgen; FacetsForTetgen.Empty(PolygonsForTetgen.Num()); for (auto& Polygon : PolygonsForTetgen) { FacetsForTetgen.AddUninitialized(); auto& Added = FacetsForTetgen.Last(); Added.hole_list = nullptr; Added.num_holes = 0; Added.polygon_list = &Polygon; Added.num_polygons = 1; } // ================================================================ // Build tetgen input // ================================================================ tw::input_output InTetgen, OutTetgen; InTetgen.automatic_deallocation = false; // pointers are on stack or managed by TArray InTetgen.point_list = VerticesForTetgen.GetData(); InTetgen.num_points = VerticesForTetgen.Num() / 3; InTetgen.facet_list = FacetsForTetgen.GetData(); InTetgen.num_facets = FacetsForTetgen.Num(); // ================================================================ // Tetrahedralize // ================================================================ if (0 != tw::tetrahedralize("", InTetgen, OutTetgen)) { return false; } // ================================================================ // Gather relevant tetrahedra // ================================================================ int32 NumTetrahedra = 0; // Count tetrahedra for (tw::int32 TetIndex = 0; TetIndex < OutTetgen.num_tetrahedra; TetIndex++) { if (OutTetgen.tetrahedron_list[TetIndex * 4 + 0] >= Vertices.Num()) { continue; } if (OutTetgen.tetrahedron_list[TetIndex * 4 + 1] >= Vertices.Num()) { continue; } if (OutTetgen.tetrahedron_list[TetIndex * 4 + 2] >= Vertices.Num()) { continue; } if (OutTetgen.tetrahedron_list[TetIndex * 4 + 3] >= Vertices.Num()) { continue; } NumTetrahedra++; } // Gather tetrahedra OutTetrahedra.Empty(NumTetrahedra); for (tw::int32 TetIndex = 0; TetIndex < OutTetgen.num_tetrahedra; TetIndex++) { if (OutTetgen.tetrahedron_list[TetIndex * 4 + 0] >= Vertices.Num()) { continue; } if (OutTetgen.tetrahedron_list[TetIndex * 4 + 1] >= Vertices.Num()) { continue; } if (OutTetgen.tetrahedron_list[TetIndex * 4 + 2] >= Vertices.Num()) { continue; } if (OutTetgen.tetrahedron_list[TetIndex * 4 + 3] >= Vertices.Num()) { continue; } OutTetrahedra.AddUninitialized(); auto& Added = OutTetrahedra.Last(); FMemory::Memcpy(&Added[0], OutTetgen.tetrahedron_list + TetIndex * 4, sizeof(Added)); } for (auto& Tet : OutTetrahedra) { const auto& P3 = Vertices[Tet[3]]; const auto& P23 = Vertices[Tet[2]] - P3; const auto& P13 = Vertices[Tet[1]] - P3; const auto& P03 = Vertices[Tet[0]] - P3; float TetVolume = FVector::DotProduct(P03, FVector::CrossProduct(P13, P23)); if (TetVolume < 0.0f) { Swap(Tet[2], Tet[3]); } } return true; #else // On UE4Game, tetrahedra are deserialized UE_LOG(LogAirMeshCloth, Warning, TEXT("Tetrahedron generation is disabled on UE4Game.")); return false; #endif }
void UnchunkSkeletalModel(TArray<FSkinnedMeshChunk*>& Chunks, TArray<int32>& PointToOriginalMap, const FSkinnedModelData& SrcModel) { #if WITH_EDITORONLY_DATA const TArray<FSoftSkinVertex>& SrcVertices = SrcModel.Vertices; const TArray<uint32>& SrcIndices = SrcModel.Indices; TArray<uint32> IndexMap; check(Chunks.Num() == 0); check(PointToOriginalMap.Num() == 0); IndexMap.Empty(SrcVertices.Num()); IndexMap.AddUninitialized(SrcVertices.Num()); for (int32 SectionIndex = 0; SectionIndex < SrcModel.Sections.Num(); ++SectionIndex) { const FSkelMeshSection& Section = SrcModel.Sections[SectionIndex]; const TArray<FBoneIndexType>& BoneMap = SrcModel.BoneMaps[SectionIndex]; FSkinnedMeshChunk* DestChunk = Chunks.Num() ? Chunks.Last() : NULL; if (DestChunk == NULL || DestChunk->MaterialIndex != Section.MaterialIndex) { DestChunk = new FSkinnedMeshChunk(); Chunks.Add(DestChunk); DestChunk->MaterialIndex = Section.MaterialIndex; DestChunk->OriginalSectionIndex = SectionIndex; // When starting a new chunk reset the index map. FMemory::Memset(IndexMap.GetData(),0xff,IndexMap.Num()*IndexMap.GetTypeSize()); } int32 NumIndicesThisSection = Section.NumTriangles * 3; for (uint32 SrcIndex = Section.BaseIndex; SrcIndex < Section.BaseIndex + NumIndicesThisSection; ++SrcIndex) { uint32 VertexIndex = SrcIndices[SrcIndex]; uint32 DestVertexIndex = IndexMap[VertexIndex]; if (DestVertexIndex == INDEX_NONE) { FSoftSkinBuildVertex NewVertex; const FSoftSkinVertex& SrcVertex = SrcVertices[VertexIndex]; NewVertex.Position = SrcVertex.Position; NewVertex.TangentX = SrcVertex.TangentX; NewVertex.TangentY = SrcVertex.TangentY; NewVertex.TangentZ = SrcVertex.TangentZ; FMemory::Memcpy(NewVertex.UVs, SrcVertex.UVs, sizeof(FVector2D)*MAX_TEXCOORDS); NewVertex.Color = SrcVertex.Color; for (int32 i = 0; i < MAX_TOTAL_INFLUENCES; ++i) { uint8 BoneIndex = SrcVertex.InfluenceBones[i]; check(BoneMap.IsValidIndex(BoneIndex)); NewVertex.InfluenceBones[i] = BoneMap[BoneIndex]; NewVertex.InfluenceWeights[i] = SrcVertex.InfluenceWeights[i]; } NewVertex.PointWedgeIdx = SrcModel.RawPointIndices.Num() ? SrcModel.RawPointIndices[VertexIndex] : 0; int32 RawVertIndex = SrcModel.MeshToImportVertexMap.Num() ? SrcModel.MeshToImportVertexMap[VertexIndex] : INDEX_NONE; if ((int32)NewVertex.PointWedgeIdx >= PointToOriginalMap.Num()) { PointToOriginalMap.AddZeroed(NewVertex.PointWedgeIdx + 1 - PointToOriginalMap.Num()); } PointToOriginalMap[NewVertex.PointWedgeIdx] = RawVertIndex; DestVertexIndex = AddSkinVertex(DestChunk->Vertices,NewVertex,/*bKeepOverlappingVertices=*/ false); IndexMap[VertexIndex] = DestVertexIndex; } DestChunk->Indices.Add(DestVertexIndex); } } #endif // #if WITH_EDITORONLY_DATA }