void FICUCamelCaseBreakIterator::TokenizeString(TArray<FToken>& OutTokens) { OutTokens.Empty(String.Len()); FICUTextCharacterIterator CharIter(String); for(CharIter.setToStart(); CharIter.current32() != FICUTextCharacterIterator::DONE; CharIter.next32PostInc()) { const UChar32 CurrentChar = CharIter.current32(); ETokenType TokenType = ETokenType::Other; if(u_isULowercase(CurrentChar)) { TokenType = ETokenType::Lowercase; } else if(u_isUUppercase(CurrentChar)) { TokenType = ETokenType::Uppercase; } else if(u_isdigit(CurrentChar)) { TokenType = ETokenType::Digit; } const int32 CharIndex = CharIter.InternalIndexToSourceIndex(CharIter.getIndex()); OutTokens.Emplace(FToken(TokenType, CharIndex)); } OutTokens.Emplace(FToken(ETokenType::Null, String.Len())); // There should always be at least one token for the end of the string check(OutTokens.Num()); }
virtual void FireEvent_FlushChatStats() override { if (AnalyticsProvider.IsValid()) { IOnlineIdentityPtr OnlineIdentity = Online::GetIdentityInterface(TEXT("MCP")); if (OnlineIdentity.IsValid()) { TSharedPtr<const FUniqueNetId> UserId = OnlineIdentity->GetUniquePlayerId(0); if (UserId.IsValid()) { auto RecordSocialChatCountsEvents = [=](const TMap<FString, int32>& ChatCounts, const FString& ChatType) { if (ChatCounts.Num()) { TArray<FAnalyticsEventAttribute> Attributes; for (const auto& Pair : ChatCounts) { Attributes.Empty(3); Attributes.Emplace(TEXT("Name"), Pair.Key); Attributes.Emplace(TEXT("Type"), ChatType); Attributes.Emplace(TEXT("Count"), Pair.Value); AnalyticsProvider->RecordEvent("Social.Chat.Counts.2", Attributes); } } }; RecordSocialChatCountsEvents(ChannelChatCounts, TEXT("Channel")); RecordSocialChatCountsEvents(PrivateChatCounts, TEXT("Private")); } } } ChannelChatCounts.Empty(); PrivateChatCounts.Empty(); }
void FAssetEditorManager::OnExit() { SaveOpenAssetEditors(true); TGuardValue<bool> GuardOnShutdown(bSavingOnShutdown, true); CloseAllAssetEditors(); // Don't attempt to report usage stats if analytics isn't available if (FEngineAnalytics::IsAvailable()) { TArray<FAnalyticsEventAttribute> EditorUsageAttribs; EditorUsageAttribs.Empty(2); for (auto Iter = EditorUsageAnalytics.CreateConstIterator(); Iter; ++Iter) { const FAssetEditorAnalyticInfo& Data = Iter.Value(); EditorUsageAttribs.Reset(); EditorUsageAttribs.Emplace(TEXT("TotalDuration.Seconds"), FString::Printf(TEXT("%.1f"), Data.SumDuration.GetTotalSeconds())); EditorUsageAttribs.Emplace(TEXT("OpenedInstances.Count"), FString::Printf(TEXT("%d"), Data.NumTimesOpened)); const FString EventName = FString::Printf(TEXT("Editor.Usage.%s"), *Iter.Key().ToString()); FEngineAnalytics::GetProvider().RecordEvent(EventName, EditorUsageAttribs); } } }
void FLegacyCamelCaseBreakIterator::TokenizeString(TArray<FToken>& OutTokens) { OutTokens.Empty(String.Len()); for(int32 CurrentCharIndex = 0; CurrentCharIndex < String.Len(); ++CurrentCharIndex) { const TCHAR CurrentChar = String[CurrentCharIndex]; ETokenType TokenType = ETokenType::Other; if(FChar::IsLower(CurrentChar)) { TokenType = ETokenType::Lowercase; } else if(FChar::IsUpper(CurrentChar)) { TokenType = ETokenType::Uppercase; } else if(FChar::IsDigit(CurrentChar)) { TokenType = ETokenType::Digit; } OutTokens.Emplace(FToken(TokenType, CurrentCharIndex)); } OutTokens.Emplace(FToken(ETokenType::Null, String.Len())); // There should always be at least one token for the end of the string check(OutTokens.Num()); }
TSharedRef< FRichTextSyntaxHighlighterTextLayoutMarshaller > FRichTextSyntaxHighlighterTextLayoutMarshaller::Create(const FSyntaxTextStyle& InSyntaxTextStyle) { TArray<FSyntaxTokenizer::FRule> TokenizerRules; TokenizerRules.Emplace(FSyntaxTokenizer::FRule(TEXT("</>"))); TokenizerRules.Emplace(FSyntaxTokenizer::FRule(TEXT("<"))); TokenizerRules.Emplace(FSyntaxTokenizer::FRule(TEXT(">"))); TokenizerRules.Emplace(FSyntaxTokenizer::FRule(TEXT("="))); TokenizerRules.Emplace(FSyntaxTokenizer::FRule(TEXT("\""))); return MakeShareable(new FRichTextSyntaxHighlighterTextLayoutMarshaller(FSyntaxTokenizer::Create(TokenizerRules), InSyntaxTextStyle)); }
bool FHttpServiceTracker::Tick(float DeltaTime) { // flush events at the specified interval. if (FPlatformTime::Seconds() > NextFlushTime) { if (AnalyticsProvider.IsValid()) { TArray<FAnalyticsEventAttribute> Attrs; Attrs.Reserve(10); // one event per endpoint. for (const auto& MetricsMapPair : EndpointMetricsMap) { Attrs.Reset(); Attrs.Emplace(TEXT("DomainName"), MetricsMapPair.Value.LastAnalyticsName); Attrs.Emplace(TEXT("FailCount"), MetricsMapPair.Value.FailCount); Attrs.Emplace(TEXT("SuccessCount"), MetricsMapPair.Value.SuccessCount); // We may have had no successful requests, so these values would be undefined. if (MetricsMapPair.Value.SuccessCount > 0) { Attrs.Emplace(TEXT("DownloadBytesSuccessTotal"), MetricsMapPair.Value.DownloadBytesSuccessTotal); Attrs.Emplace(TEXT("ElapsedTimeSuccessTotal"), MetricsMapPair.Value.ElapsedTimeSuccessTotal); Attrs.Emplace(TEXT("ElapsedTimeSuccessMin"), MetricsMapPair.Value.ElapsedTimeSuccessMin); Attrs.Emplace(TEXT("ElapsedTimeSuccessMax"), MetricsMapPair.Value.ElapsedTimeSuccessMax); } if (MetricsMapPair.Value.FailCount > 0) { Attrs.Emplace(TEXT("DownloadBytesFailTotal"), MetricsMapPair.Value.DownloadBytesFailTotal); Attrs.Emplace(TEXT("ElapsedTimeFailTotal"), MetricsMapPair.Value.ElapsedTimeFailTotal); Attrs.Emplace(TEXT("ElapsedTimeFailMin"), MetricsMapPair.Value.ElapsedTimeFailMin); Attrs.Emplace(TEXT("ElapsedTimeFailMax"), MetricsMapPair.Value.ElapsedTimeFailMax); } // one attribute per response code. for (const auto& ResponseCodeMapPair : MetricsMapPair.Value.ResponseCodes) { Attrs.Emplace(FString(TEXT("Code-")) + LexicalConversion::ToString(ResponseCodeMapPair.Key), ResponseCodeMapPair.Value); } AnalyticsProvider->RecordEvent(MetricsMapPair.Key.ToString(), Attrs); } // force an immediate flush always. We already summarized. AnalyticsProvider->FlushEvents(); } EndpointMetricsMap.Reset(); NextFlushTime += FlushIntervalSec; } return true; }
TArray<FPathAndMountPoint> FAutoReimportManager::GetMonitoredDirectories() const { TArray<FPathAndMountPoint> Dirs; for (const auto& Monitor : DirectoryMonitors) { Dirs.Emplace(Monitor.GetDirectory(), Monitor.GetMountPoint()); } return Dirs; }
void FAssetFixUpRedirectors::ExecuteFixUp(TArray<TWeakObjectPtr<UObjectRedirector>> Objects) const { TArray<FRedirectorRefs> RedirectorRefsList; for (auto Object : Objects) { auto ObjectRedirector = Object.Get(); if (ObjectRedirector) { RedirectorRefsList.Emplace(ObjectRedirector); } } if ( RedirectorRefsList.Num() > 0 ) { // Gather all referencing packages for all redirectors that are being fixed. PopulateRedirectorReferencers(RedirectorRefsList); // Update Package Status for all selected redirectors if SCC is enabled if ( UpdatePackageStatus(RedirectorRefsList) ) { // Load all referencing packages. TArray<UPackage*> ReferencingPackagesToSave; LoadReferencingPackages(RedirectorRefsList, ReferencingPackagesToSave); // Prompt to check out all referencing packages, leave redirectors for assets referenced by packages that are not checked out and remove those packages from the save list. const bool bUserAcceptedCheckout = CheckOutReferencingPackages(RedirectorRefsList, ReferencingPackagesToSave); if ( bUserAcceptedCheckout ) { // If any referencing packages are left read-only, the checkout failed or SCC was not enabled. Trim them from the save list and leave redirectors. DetectReadOnlyPackages(RedirectorRefsList, ReferencingPackagesToSave); // Fix up referencing FStringAssetReferences FixUpStringAssetReferences(RedirectorRefsList, ReferencingPackagesToSave); // Save all packages that were referencing any of the assets that were moved without redirectors TArray<UPackage*> FailedToSave; SaveReferencingPackages(ReferencingPackagesToSave, FailedToSave); // Save any collections that were referencing any of the redirectors SaveReferencingCollections(RedirectorRefsList); // Wait for package referencers to be updated UpdateAssetReferencers(RedirectorRefsList); // Delete any redirectors that are no longer referenced DeleteRedirectors(RedirectorRefsList, FailedToSave); // Finally, report any failures that happened during the rename ReportFailures(RedirectorRefsList); } } } }
void FAutoReimportManager::SetUpDirectoryMonitors() { struct FParsedSettings { FString SourceDirectory; FString MountPoint; FMatchRules Rules; }; TArray<FParsedSettings> FinalArray; auto SupportedExtensions = GetAllFactoryExtensions(); for (const auto& Setting : GetDefault<UEditorLoadingSavingSettings>()->AutoReimportDirectorySettings) { FParsedSettings NewMapping; NewMapping.SourceDirectory = Setting.SourceDirectory; NewMapping.MountPoint = Setting.MountPoint; if (!FAutoReimportDirectoryConfig::ParseSourceDirectoryAndMountPoint(NewMapping.SourceDirectory, NewMapping.MountPoint)) { continue; } // Only include extensions that match a factory NewMapping.Rules.SetApplicableExtensions(SupportedExtensions); for (const auto& WildcardConfig : Setting.Wildcards) { NewMapping.Rules.AddWildcardRule(WildcardConfig.Wildcard, WildcardConfig.bInclude); } FinalArray.Add(NewMapping); } for (int32 Index = 0; Index < FinalArray.Num(); ++Index) { const auto& Mapping = FinalArray[Index]; // We only create a directory monitor if there are no other's watching parent directories of this one for (int32 OtherIndex = Index + 1; OtherIndex < FinalArray.Num(); ++OtherIndex) { if (FinalArray[Index].SourceDirectory.StartsWith(FinalArray[OtherIndex].SourceDirectory)) { UE_LOG(LogAutoReimportManager, Warning, TEXT("Unable to watch directory %s as it will conflict with another watching %s."), *FinalArray[Index].SourceDirectory, *FinalArray[OtherIndex].SourceDirectory); goto next; } } DirectoryMonitors.Emplace(Mapping.SourceDirectory, Mapping.Rules, Mapping.MountPoint); next: continue; } }
void FRichTextMarkupProcessing::CalculateLineRanges(const FString& Input, TArray<FTextRange>& LineRanges) const { // Iterate over each line break candidate, adding ranges from after the end of the last line added to before the newline or end of string. FLineBreakIterator LBI(Input); int32 RangeBegin = LBI.GetCurrentPosition(); for(;;) { const int32 BreakIndex = LBI.MoveToNext(); // If beginning or potential end is invalid, cease. if(RangeBegin == INDEX_NONE || BreakIndex == INDEX_NONE) { break; } const int32 BreakingCharacterIndex = BreakIndex - 1; if(BreakingCharacterIndex >= RangeBegin) // Valid index check. { const TCHAR BreakingCharacter = Input[BreakingCharacterIndex]; // If line break candidate is after a newline, add the range as a new line. if(FChar::IsLinebreak(BreakingCharacter)) { LineRanges.Emplace( FTextRange(RangeBegin, BreakingCharacterIndex) ); } // If the line break candidate is the end of the string, add the range as a new line. else if(BreakIndex == Input.Len()) { LineRanges.Emplace( FTextRange(RangeBegin, BreakIndex) ); } else { continue; } RangeBegin = BreakIndex; // The next line begins after the end of the current line. } } }
TSharedRef< FCPPRichTextSyntaxHighlighterTextLayoutMarshaller > FCPPRichTextSyntaxHighlighterTextLayoutMarshaller::Create(const FSyntaxTextStyle& InSyntaxTextStyle) { TArray<FSyntaxTokenizer::FRule> TokenizerRules; // operators for(const auto& Operator : Operators) { TokenizerRules.Emplace(FSyntaxTokenizer::FRule(Operator)); } // keywords for(const auto& Keyword : Keywords) { TokenizerRules.Emplace(FSyntaxTokenizer::FRule(Keyword)); } // Pre-processor Keywords for(const auto& PreProcessorKeyword : PreProcessorKeywords) { TokenizerRules.Emplace(FSyntaxTokenizer::FRule(PreProcessorKeyword)); } return MakeShareable(new FCPPRichTextSyntaxHighlighterTextLayoutMarshaller(FSyntaxTokenizer::Create(TokenizerRules), InSyntaxTextStyle)); }
// Called when the game starts or when spawned void ABot::BeginPlay() { progression = new FParagonProgression(); TArray<Goal*> goals; Goal *g1 = new Goal_Exp(((FParagonProgression*)progression)->getExpForNextLevel()); Goal *g2 = new Goal_Gold(((FParagonProgression*)progression)->gold); goals.Emplace(g1); goals.Emplace(g2); worldModel = WorldModel(goals); planner = new ActionPlanner(); Super::BeginPlay(); }
void FPlainTextLayoutMarshaller::SetText(const FString& SourceString, FTextLayout& TargetTextLayout) { const FTextBlockStyle& DefaultTextStyle = static_cast<FSlateTextLayout&>(TargetTextLayout).GetDefaultTextStyle(); TArray<FTextRange> LineRanges; FTextRange::CalculateLineRangesFromString(SourceString, LineRanges); TArray<FTextLayout::FNewLineData> LinesToAdd; LinesToAdd.Reserve(LineRanges.Num()); TArray<FTextLineHighlight> LineHighlightsToAdd; TSharedPtr<FSlateTextUnderlineLineHighlighter> UnderlineLineHighlighter; if (!DefaultTextStyle.UnderlineBrush.GetResourceName().IsNone()) { UnderlineLineHighlighter = FSlateTextUnderlineLineHighlighter::Create(DefaultTextStyle.UnderlineBrush, DefaultTextStyle.Font, DefaultTextStyle.ColorAndOpacity, DefaultTextStyle.ShadowOffset, DefaultTextStyle.ShadowColorAndOpacity); } const bool bUsePasswordRun = bIsPassword.Get(false); for (int32 LineIndex = 0; LineIndex < LineRanges.Num(); ++LineIndex) { const FTextRange& LineRange = LineRanges[LineIndex]; TSharedRef<FString> LineText = MakeShareable(new FString(SourceString.Mid(LineRange.BeginIndex, LineRange.Len()))); TArray<TSharedRef<IRun>> Runs; if (bUsePasswordRun) { Runs.Add(FSlatePasswordRun::Create(FRunInfo(), LineText, DefaultTextStyle)); } else { Runs.Add(FSlateTextRun::Create(FRunInfo(), LineText, DefaultTextStyle)); } if (UnderlineLineHighlighter.IsValid()) { LineHighlightsToAdd.Add(FTextLineHighlight(LineIndex, FTextRange(0, LineRange.Len()), FSlateTextUnderlineLineHighlighter::DefaultZIndex, UnderlineLineHighlighter.ToSharedRef())); } LinesToAdd.Emplace(MoveTemp(LineText), MoveTemp(Runs)); } TargetTextLayout.AddLines(LinesToAdd); TargetTextLayout.SetLineHighlights(LineHighlightsToAdd); }
TArray<FLayoutGeometry> SSplitter2x2::ArrangeChildrenForLayout( const FGeometry& AllottedGeometry ) const { check( Children.Num() == 4 ); TArray<FLayoutGeometry> Result; Result.Empty(Children.Num()); int32 NumNonCollapsedChildren = 0; FVector2D CoefficientTotal(0,0); // The allotted space for our children is our geometry minus a little space to show splitter handles const FVector2D SpaceAllottedForChildren = AllottedGeometry.Size - FVector2D(SplitterHandleSize,SplitterHandleSize); // The current offset that the next child should be positioned at. FVector2D Offset(0,0); for (int32 ChildIndex=0; ChildIndex < Children.Num(); ++ChildIndex) { const FSlot& CurSlot = Children[ChildIndex]; // Calculate the amount of space that this child should take up. // It is based on the current percentage of space it should take up which is defined by a user moving the splitters const FVector2D ChildSpace = SpaceAllottedForChildren * CurSlot.PercentageAttribute.Get(); // put them in their spot Result.Emplace(FSlateLayoutTransform(Offset), ChildSpace); // Advance to the next slot. If the child is collapsed, it takes up no room and does not need a splitter if( ChildIndex == 1 ) { // ChildIndex of 1 means we are starting the next column so reset the Y offset. Offset.Y = 0.0f; Offset += FVector2D( ChildSpace.X + SplitterHandleSize, 0); } else { Offset += FVector2D( 0, ChildSpace.Y + SplitterHandleSize ); } } return Result; }
TArray<FAssetData> FAssetSourceFilenameCache::GetAssetsPertainingToFile(const IAssetRegistry& Registry, const FString& AbsoluteFilename) const { TArray<FAssetData> Assets; if (const auto* ObjectPaths = SourceFileToObjectPathCache.Find(FPaths::GetCleanFilename(AbsoluteFilename))) { for (const FName& Path : *ObjectPaths) { FAssetData Asset = Registry.GetAssetByObjectPath(Path); TOptional<FAssetImportInfo> ImportInfo = ExtractAssetImportInfo(Asset.TagsAndValues); if (ImportInfo.IsSet()) { auto AssetPackagePath = FPackageName::LongPackageNameToFilename(Asset.PackagePath.ToString() / TEXT("")); // Attempt to find the matching source filename in the list of imported sorce files (generally there are only one of these) const bool bWasImportedFromFile = ImportInfo->SourceFiles.ContainsByPredicate([&](const FAssetImportInfo::FSourceFile& File){ if (AbsoluteFilename == FPaths::ConvertRelativePathToFull(AssetPackagePath / File.RelativeFilename) || AbsoluteFilename == FPaths::ConvertRelativePathToFull(File.RelativeFilename)) { return true; } return false; }); if (bWasImportedFromFile) { Assets.Emplace(); Swap(Asset, Assets[Assets.Num() - 1]); } } } } return Assets; }
EConvertQueryResult ConvertRaycastResults(bool& OutHasValidBlockingHit, const UWorld* World, int32 NumHits, PxRaycastHit* Hits, float CheckLength, const PxFilterData& QueryFilter, TArray<FHitResult>& OutHits, const FVector& StartLoc, const FVector& EndLoc, bool bReturnFaceIndex, bool bReturnPhysMat) { #if PLATFORM_LINUX // to narrow down OR-24947 _Pragma("clang optimize off"); #endif // PLATFORM_LINUX OutHits.Reserve(OutHits.Num() + NumHits); EConvertQueryResult ConvertResult = EConvertQueryResult::Valid; bool bHadBlockingHit = false; PxTransform PStartTM(U2PVector(StartLoc)); for(int32 i=0; i<NumHits; i++) { FHitResult& NewResult = OutHits[OutHits.Emplace()]; const PxRaycastHit& PHit = Hits[i]; if (ConvertQueryImpactHit(World, PHit, NewResult, CheckLength, QueryFilter, StartLoc, EndLoc, NULL, PStartTM, bReturnFaceIndex, bReturnPhysMat) == EConvertQueryResult::Valid) { bHadBlockingHit |= NewResult.bBlockingHit; } else { // Reject invalid result (this should be rare). Remove from the results. OutHits.Pop(/*bAllowShrinking=*/ false); ConvertResult = EConvertQueryResult::Invalid; } } // Sort results from first to last hit OutHits.Sort( FCompareFHitResultTime() ); OutHasValidBlockingHit = bHadBlockingHit; return ConvertResult; #if PLATFORM_LINUX // to narrow down OR-24947 _Pragma("clang optimize on"); #endif // PLATFORM_LINUX }
void ExtractSourceFilePaths(UObject* Object, TArray<FString>& OutSourceFiles) { TArray<UObject::FAssetRegistryTag> TagList; Object->GetAssetRegistryTags(TagList); const FName TagName = UObject::SourceFileTagName(); for (const auto& Tag : TagList) { if (Tag.Name == TagName) { int32 PreviousNum = OutSourceFiles.Num(); TOptional<FAssetImportInfo> ImportInfo = FAssetImportInfo::FromJson(Tag.Value); if (ImportInfo.IsSet()) { for (const auto& File : ImportInfo->SourceFiles) { OutSourceFiles.Emplace(UAssetImportData::ResolveImportFilename(File.RelativeFilename, Object->GetOutermost())); } } break; } } }
FStaticBounds() { MetricDistance.Emplace(EUnit::Micrometers, 1000); MetricDistance.Emplace(EUnit::Millimeters, 10); MetricDistance.Emplace(EUnit::Centimeters, 100); MetricDistance.Emplace(EUnit::Meters, 1000); MetricDistance.Emplace(EUnit::Kilometers, 0); ImperialDistance.Emplace(EUnit::Inches, 12); ImperialDistance.Emplace(EUnit::Feet, 3); ImperialDistance.Emplace(EUnit::Yards, 1760); ImperialDistance.Emplace(EUnit::Miles, 0); MetricMass.Emplace(EUnit::Micrograms, 1000); MetricMass.Emplace(EUnit::Milligrams, 1000); MetricMass.Emplace(EUnit::Grams, 1000); MetricMass.Emplace(EUnit::Kilograms, 1000); MetricMass.Emplace(EUnit::MetricTons, 0); ImperialMass.Emplace(EUnit::Ounces, 16); ImperialMass.Emplace(EUnit::Pounds, 14); ImperialMass.Emplace(EUnit::Stones, 0); Frequency.Emplace(EUnit::Hertz, 1000); Frequency.Emplace(EUnit::Kilohertz, 1000); Frequency.Emplace(EUnit::Megahertz, 1000); Frequency.Emplace(EUnit::Gigahertz, 0); DataSize.Emplace(EUnit::Bytes, 1000); DataSize.Emplace(EUnit::Kilobytes, 1000); DataSize.Emplace(EUnit::Megabytes, 1000); DataSize.Emplace(EUnit::Gigabytes, 1000); DataSize.Emplace(EUnit::Terabytes, 0); Time.Emplace(EUnit::Milliseconds, 1000); Time.Emplace(EUnit::Seconds, 60); Time.Emplace(EUnit::Minutes, 60); Time.Emplace(EUnit::Hours, 24); Time.Emplace(EUnit::Days, 365.242f / 12); Time.Emplace(EUnit::Months, 12); Time.Emplace(EUnit::Years, 0); }
TArray<FLayoutGeometry> SSplitter::ArrangeChildrenForLayout(const FGeometry& AllottedGeometry) const { TArray<FLayoutGeometry> Result; Result.Empty(Children.Num()); const int32 AxisIndex = (Orientation == Orient_Horizontal) ? 0 : 1; // Splitters divide the space between their children proportionately based on size coefficients. // The size coefficients are usually determined by a user, who grabs the handled between the child elements // and moves them to resize the space available to the children. // Some children are sized automatically based on their content; those children cannot be resized. // // e.g. _____________________________________ Children // / / / // v v v // + - - - - - + + - - - + + - - - - - - - - - - - - - - + // | | | | | | // | Child 0 | |Child1 | | Child2 | // + - - - - - + + - - - + + - - - - - - - - - - - - - - + // ^ ^ // \_________\___________ Resize handles. int32 NumNonCollapsedChildren = 0; int32 NumResizeableChildren = 0; float CoefficientTotal = 0; // Some space is claimed by non-resizeable elements (auto-sized elements) float NonResizeableSpace = 0; { for (int32 ChildIndex=0; ChildIndex < Children.Num(); ++ChildIndex) { if (Children[ChildIndex].GetWidget()->GetVisibility() != EVisibility::Collapsed) { ++NumNonCollapsedChildren; if ( Children[ChildIndex].SizingRule == SSplitter::SizeToContent ) { NonResizeableSpace += Children[ChildIndex].GetWidget()->GetDesiredSize()[AxisIndex]; } else // SizingRule == SSplitter::FractionOfParent { CoefficientTotal += Children[ChildIndex].SizeValue.Get(); } } } } // The user-sizeable children must make room for the resize handles and for auto-sized children. const float SpaceNeededForHandles = FMath::Max(0, NumNonCollapsedChildren-1) * PhysicalSplitterHandleSize; const float ResizeableSpace = AllottedGeometry.Size.Component(AxisIndex) - SpaceNeededForHandles - NonResizeableSpace; // Arrange the children horizontally or vertically. float XOffset = 0; for (int32 ChildIndex=0; ChildIndex < Children.Num(); ++ChildIndex) { const FSlot& CurSlot = Children[ChildIndex]; const float ChildSpace = ( CurSlot.SizingRule == SSplitter::SizeToContent ) ? CurSlot.GetWidget()->GetDesiredSize()[AxisIndex] : ResizeableSpace * CurSlot.SizeValue.Get() / CoefficientTotal; const EVisibility ChildVisibility = CurSlot.GetWidget()->GetVisibility(); FVector2D ChildOffset = Orientation == Orient_Horizontal ? FVector2D(XOffset, 0) : FVector2D(0, XOffset); FVector2D ChildSize = Orientation == Orient_Horizontal ? FVector2D(ChildSpace, AllottedGeometry.Size.Y) : FVector2D(AllottedGeometry.Size.X, ChildSpace); Result.Emplace(FSlateLayoutTransform(ChildOffset), ChildSize); // Advance to the next slot. If the child is collapsed, it takes up no room and does not need a splitter if ( ChildVisibility != EVisibility::Collapsed) { XOffset += ChildSpace + PhysicalSplitterHandleSize; } } return Result; }
void FContentDirectoryMonitor::ProcessModifications(const DirectoryWatcher::FTimeLimit& TimeLimit, TArray<UPackage*>& OutPackagesToSave, FReimportFeedbackContext& Context) { auto* ReimportManager = FReimportManager::Instance(); for (int32 Index = 0; Index < ModifiedFiles.Num(); ++Index) { Context.MainTask->EnterProgressFrame(); auto& Change = ModifiedFiles[Index]; const FString FullFilename = Cache.GetDirectory() + Change.Filename.Get(); // Move the asset before reimporting it. We always reimport moved assets to ensure that their import path is up to date if (Change.Action == DirectoryWatcher::EFileAction::Moved) { const FString OldFilename = Cache.GetDirectory() + Change.MovedFromFilename.Get(); const auto Assets = Utils::FindAssetsPertainingToFile(*Registry, OldFilename); if (Assets.Num() == 1) { UObject* Asset = Assets[0].GetAsset(); if (Asset && Utils::ExtractSourceFilePaths(Asset).Num() == 1) { UPackage* ExistingPackage = Asset->GetOutermost(); const bool bAssetWasDirty = IsAssetDirty(Asset); const FString NewAssetName = ObjectTools::SanitizeObjectName(FPaths::GetBaseFilename(Change.Filename.Get())); const FString PackagePath = PackageTools::SanitizePackageName(MountedContentPath / FPaths::GetPath(Change.Filename.Get())); const FString FullDestPath = PackagePath / NewAssetName; if (ExistingPackage && ExistingPackage->FileName.ToString() == FullDestPath) { // No need to process this asset - it's already been moved to the right location Cache.CompleteTransaction(MoveTemp(Change)); continue; } const FText SrcPathText = FText::FromString(Assets[0].PackageName.ToString()), DstPathText = FText::FromString(FullDestPath); if (FPackageName::DoesPackageExist(*FullDestPath)) { Context.AddMessage(EMessageSeverity::Warning, FText::Format(LOCTEXT("MoveWarning_ExistingAsset", "Can't move {0} to {1} - one already exists."), SrcPathText, DstPathText)); } else { TArray<FAssetRenameData> RenameData; RenameData.Emplace(Asset, PackagePath, NewAssetName); Context.AddMessage(EMessageSeverity::Info, FText::Format(LOCTEXT("Success_MovedAsset", "Moving asset {0} to {1}."), SrcPathText, DstPathText)); FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get().RenameAssets(RenameData); TArray<FString> Filenames; Filenames.Add(FullFilename); // Update the reimport file names FReimportManager::Instance()->UpdateReimportPaths(Asset, Filenames); Asset->MarkPackageDirty(); if (!bAssetWasDirty) { OutPackagesToSave.Add(Asset->GetOutermost()); } } } } } else { // Modifications or additions are treated the same by this point for (const auto& AssetData : Utils::FindAssetsPertainingToFile(*Registry, FullFilename)) { if (UObject* Asset = AssetData.GetAsset()) { ReimportAsset(Asset, FullFilename, OutPackagesToSave, Context); } } } // Let the cache know that we've dealt with this change Cache.CompleteTransaction(MoveTemp(Change)); if (TimeLimit.Exceeded()) { ModifiedFiles.RemoveAt(0, Index + 1); return; } } ModifiedFiles.Empty(); }
FLongPackagePathsSingleton() { EngineRootPath = TEXT("/Engine/"); GameRootPath = TEXT("/Game/"); ScriptRootPath = TEXT("/Script/"); TempRootPath = TEXT("/Temp/"); EngineContentPath = FPaths::EngineContentDir(); ContentPathShort = TEXT("../../Content/"); EngineShadersPath = FPaths::EngineDir() / TEXT("Shaders/"); EngineShadersPathShort = TEXT("../../Shaders/"); GameContentPath = FPaths::GameContentDir(); GameScriptPath = FPaths::GameDir() / TEXT("Script/"); GameSavedPath = FPaths::GameSavedDir(); FString RebasedGameDir = FString::Printf(TEXT("../../../%s/"), GGameName); GameContentPathRebased = RebasedGameDir / TEXT("Content/"); GameScriptPathRebased = RebasedGameDir / TEXT("Script/"); GameSavedPathRebased = RebasedGameDir / TEXT("Saved/"); ContentPathToRoot.Empty(10); ContentPathToRoot.Emplace(EngineRootPath, EngineContentPath); #if IS_MONOLITHIC ContentPathToRoot.Emplace(GameRootPath, ContentPathShort); #else ContentPathToRoot.Emplace(EngineRootPath, ContentPathShort); #endif ContentPathToRoot.Emplace(EngineRootPath, EngineShadersPath); ContentPathToRoot.Emplace(EngineRootPath, EngineShadersPathShort); ContentPathToRoot.Emplace(GameRootPath, GameContentPath); ContentPathToRoot.Emplace(ScriptRootPath, GameScriptPath); ContentPathToRoot.Emplace(TempRootPath, GameSavedPath); ContentPathToRoot.Emplace(GameRootPath, GameContentPathRebased); ContentPathToRoot.Emplace(ScriptRootPath, GameScriptPathRebased); ContentPathToRoot.Emplace(TempRootPath, GameSavedPathRebased); ContentRootToPath.Empty(8); ContentRootToPath.Emplace(EngineRootPath, EngineContentPath); ContentRootToPath.Emplace(EngineRootPath, EngineShadersPath); ContentRootToPath.Emplace(GameRootPath, GameContentPath); ContentRootToPath.Emplace(ScriptRootPath, GameScriptPath); ContentRootToPath.Emplace(TempRootPath, GameSavedPath); ContentRootToPath.Emplace(GameRootPath, GameContentPathRebased); ContentRootToPath.Emplace(ScriptRootPath, GameScriptPathRebased); ContentRootToPath.Emplace(TempRootPath, GameSavedPathRebased); // Allow the plugin manager to mount new content paths by exposing access through a delegate. PluginManager is // a Core class, but content path functionality is added at the CoreUObject level. IPluginManager::Get().SetRegisterMountPointDelegate( IPluginManager::FRegisterMountPointDelegate::CreateStatic( &FPackageName::RegisterMountPoint ) ); }
void FEngineAnalytics::Initialize() { checkf(!bIsInitialized, TEXT("FEngineAnalytics::Initialize called more than once.")); check(GEngine); // this will only be true for builds that have editor support (currently PC, Mac, Linux) // The idea here is to only send editor events for actual editor runs, not for things like -game runs of the editor. const bool bIsEditorRun = WITH_EDITOR && GIsEditor && !IsRunningCommandlet(); // We also want to identify a real run of a game, which is NOT necessarily the opposite of an editor run. // Ideally we'd be able to tell explicitly, but with content-only games, it becomes difficult. // So we ensure we are not an editor run, we don't have EDITOR stuff compiled in, we are not running a commandlet, // we are not a generic, utility program, and we require cooked data. const bool bIsGameRun = !WITH_EDITOR && !IsRunningCommandlet() && !FPlatformProperties::IsProgram() && FPlatformProperties::RequiresCookedData(); const bool bShouldInitAnalytics = bIsEditorRun || bIsGameRun; // Outside of the editor, the only engine analytics usage is the hardware survey bShouldSendUsageEvents = bIsEditorRun ? GEngine->AreEditorAnalyticsEnabled() : bIsGameRun ? GEngine->bHardwareSurveyEnabled : false; if (bShouldInitAnalytics) { { // Setup some default engine analytics if there is nothing custom bound FAnalytics::FProviderConfigurationDelegate DefaultEngineAnalyticsConfig; DefaultEngineAnalyticsConfig.BindLambda( [=]( const FString& KeyName, bool bIsValueRequired ) -> FString { static TMap<FString, FString> ConfigMap; if (ConfigMap.Num() == 0) { ConfigMap.Add(TEXT("ProviderModuleName"), TEXT("AnalyticsET")); ConfigMap.Add(TEXT("APIServerET"), TEXT("http://etsource.epicgames.com/ET2/")); // We always use the "Release" analytics account unless we're running in analytics test mode (usually with // a command-line parameter), or we're an internal Epic build const FAnalytics::BuildType AnalyticsBuildType = FAnalytics::Get().GetBuildType(); const bool bUseReleaseAccount = (AnalyticsBuildType == FAnalytics::Development || AnalyticsBuildType == FAnalytics::Release) && !FEngineBuildSettings::IsInternalBuild(); // Internal Epic build const TCHAR* BuildTypeStr = bUseReleaseAccount ? TEXT("Release") : TEXT("Dev"); const TCHAR* UE4TypeStr = FRocketSupport::IsRocket() ? TEXT("Rocket") : FEngineBuildSettings::IsPerforceBuild() ? TEXT("Perforce") : TEXT("UnrealEngine"); if (bIsEditorRun) { ConfigMap.Add(TEXT("APIKeyET"), FString::Printf(TEXT("UEEditor.%s.%s"), UE4TypeStr, BuildTypeStr)); } else { const UGeneralProjectSettings& ProjectSettings = *GetDefault<UGeneralProjectSettings>(); ConfigMap.Add(TEXT("APIKeyET"), FString::Printf(TEXT("UEGame.%s.%s|%s|%s"), UE4TypeStr, BuildTypeStr, *ProjectSettings.ProjectID.ToString(), *ProjectSettings.ProjectName)); } } // Check for overrides if( GetEngineAnalyticsOverrideConfigDelegate().IsBound() ) { const FString OverrideValue = GetEngineAnalyticsOverrideConfigDelegate().Execute( KeyName, bIsValueRequired ); if( !OverrideValue.IsEmpty() ) { return OverrideValue; } } FString* ConfigValue = ConfigMap.Find(KeyName); return ConfigValue != NULL ? *ConfigValue : TEXT(""); } ); // Connect the engine analytics provider (if there is a configuration delegate installed) Analytics = FAnalytics::Get().CreateAnalyticsProvider( FName(*DefaultEngineAnalyticsConfig.Execute(TEXT("ProviderModuleName"), true)), DefaultEngineAnalyticsConfig); if (Analytics.IsValid()) { Analytics->SetUserID(FString::Printf(TEXT("%s|%s|%s"), *FPlatformMisc::GetMachineId().ToString(EGuidFormats::Digits).ToLower(), *FPlatformMisc::GetEpicAccountId(), *FPlatformMisc::GetOperatingSystemId())); TArray<FAnalyticsEventAttribute> StartSessionAttributes; GEngine->CreateStartupAnalyticsAttributes( StartSessionAttributes ); // Add project info whether we are in editor or game. const UGeneralProjectSettings& ProjectSettings = *GetDefault<UGeneralProjectSettings>(); StartSessionAttributes.Emplace(TEXT("ProjectName"), ProjectSettings.ProjectName); StartSessionAttributes.Emplace(TEXT("ProjectID"), ProjectSettings.ProjectID); StartSessionAttributes.Emplace(TEXT("ProjectDescription"), ProjectSettings.Description); StartSessionAttributes.Emplace(TEXT("ProjectVersion"), ProjectSettings.ProjectVersion); Analytics->StartSession( StartSessionAttributes ); } } } bIsInitialized = true; }
FExpressionResult Evaluate(const TArray<FCompiledToken>& CompiledTokens, const IOperatorEvaluationEnvironment& InEnvironment) { // Evaluation strategy: the supplied compiled tokens are const. To avoid copying the whole array, we store a separate array of // any tokens that are generated at runtime by the evaluator. The operand stack will consist of indices into either the CompiledTokens // array, or the RuntimeGeneratedTokens (where Index >= CompiledTokens.Num()) TArray<FExpressionToken> RuntimeGeneratedTokens; TArray<int32> OperandStack; /** Get the token pertaining to the specified operand index */ auto GetToken = [&](int32 Index) -> const FExpressionToken& { if (Index < CompiledTokens.Num()) { return CompiledTokens[Index]; } return RuntimeGeneratedTokens[Index - CompiledTokens.Num()]; }; /** Add a new token to the runtime generated array */ auto AddToken = [&](FExpressionToken&& In) -> int32 { auto Index = CompiledTokens.Num() + RuntimeGeneratedTokens.Num(); RuntimeGeneratedTokens.Emplace(MoveTemp(In)); return Index; }; for (int32 Index = 0; Index < CompiledTokens.Num(); ++Index) { const auto& Token = CompiledTokens[Index]; switch(Token.Type) { case FCompiledToken::Benign: continue; case FCompiledToken::Operand: OperandStack.Push(Index); continue; case FCompiledToken::BinaryOperator: if (OperandStack.Num() >= 2) { // Binary const auto& R = GetToken(OperandStack.Pop()); const auto& L = GetToken(OperandStack.Pop()); auto OpResult = InEnvironment.ExecBinary(Token, L, R); if (OpResult.IsValid()) { // Inherit the LHS context OperandStack.Push(AddToken(FExpressionToken(L.Context, MoveTemp(OpResult.GetValue())))); } else { return MakeError(OpResult.GetError()); } } else { FFormatOrderedArguments Args; Args.Add(FText::FromString(Token.Context.GetString())); return MakeError(FText::Format(LOCTEXT("SyntaxError_NotEnoughOperandsBinary", "Not enough operands for binary operator {0}"), Args)); } break; case FCompiledToken::PostUnaryOperator: case FCompiledToken::PreUnaryOperator: if (OperandStack.Num() >= 1) { const auto& Operand = GetToken(OperandStack.Pop()); FExpressionResult OpResult = (Token.Type == FCompiledToken::PreUnaryOperator) ? InEnvironment.ExecPreUnary(Token, Operand) : InEnvironment.ExecPostUnary(Token, Operand); if (OpResult.IsValid()) { // Inherit the LHS context OperandStack.Push(AddToken(FExpressionToken(Operand.Context, MoveTemp(OpResult.GetValue())))); } else { return MakeError(OpResult.GetError()); } } else { FFormatOrderedArguments Args; Args.Add(FText::FromString(Token.Context.GetString())); return MakeError(FText::Format(LOCTEXT("SyntaxError_NoUnaryOperand", "No operand for unary operator {0}"), Args)); } break; } } if (OperandStack.Num() == 1) { return MakeValue(GetToken(OperandStack[0]).Node.Copy()); } return MakeError(LOCTEXT("SyntaxError_InvalidExpression", "Could not evaluate expression")); }
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>(); }
// 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 UtilitySystem::finishPrint(FString str) { str.Trim(); str.Shrink(); FStrPrintArray.Emplace(str); }
void Pointify(const FInterpCurveVector& SplineInfo, TArray<FLandscapeSplineInterpPoint>& Points, int32 NumSubdivisions, float StartFalloffFraction, float EndFalloffFraction, const float StartWidth, const float EndWidth, const float StartSideFalloff, const float EndSideFalloff, const float StartRollDegrees, const float EndRollDegrees) { // Stop the start and end fall-off overlapping const float TotalFalloff = StartFalloffFraction + EndFalloffFraction; if (TotalFalloff > 1.0f) { StartFalloffFraction /= TotalFalloff; EndFalloffFraction /= TotalFalloff; } const float StartRoll = FMath::DegreesToRadians(StartRollDegrees); const float EndRoll = FMath::DegreesToRadians(EndRollDegrees); float OldKeyTime = 0; for (int32 i = 0; i < SplineInfo.Points.Num(); i++) { const float NewKeyTime = SplineInfo.Points[i].InVal; const float NewKeyCosInterp = 0.5f - 0.5f * FMath::Cos(NewKeyTime * PI); const float NewKeyWidth = FMath::Lerp(StartWidth, EndWidth, NewKeyCosInterp); const float NewKeyFalloff = FMath::Lerp(StartSideFalloff, EndSideFalloff, NewKeyCosInterp); const float NewKeyRoll = FMath::Lerp(StartRoll, EndRoll, NewKeyCosInterp); const FVector NewKeyPos = SplineInfo.Eval(NewKeyTime, FVector::ZeroVector); const FVector NewKeyTangent = SplineInfo.EvalDerivative(NewKeyTime, FVector::ZeroVector).GetSafeNormal(); const FVector NewKeyBiNormal = FQuat(NewKeyTangent, -NewKeyRoll).RotateVector((NewKeyTangent ^ FVector(0, 0, -1)).GetSafeNormal()); const FVector NewKeyLeftPos = NewKeyPos - NewKeyBiNormal * NewKeyWidth; const FVector NewKeyRightPos = NewKeyPos + NewKeyBiNormal * NewKeyWidth; const FVector NewKeyFalloffLeftPos = NewKeyPos - NewKeyBiNormal * (NewKeyWidth + NewKeyFalloff); const FVector NewKeyFalloffRightPos = NewKeyPos + NewKeyBiNormal * (NewKeyWidth + NewKeyFalloff); const float NewKeyStartEndFalloff = FMath::Min((StartFalloffFraction > 0 ? NewKeyTime / StartFalloffFraction : 1.0f), (EndFalloffFraction > 0 ? (1 - NewKeyTime) / EndFalloffFraction : 1.0f)); // If not the first keypoint, interp from the last keypoint. if (i > 0) { const int32 NumSteps = FMath::CeilToInt((NewKeyTime - OldKeyTime) * NumSubdivisions); const float DrawSubstep = (NewKeyTime - OldKeyTime) / NumSteps; // Add a point for each substep, except the ends because that's the point added outside the interp'ing. for (int32 j = 1; j < NumSteps; j++) { const float NewTime = OldKeyTime + j*DrawSubstep; const float NewCosInterp = 0.5f - 0.5f * FMath::Cos(NewTime * PI); const float NewWidth = FMath::Lerp(StartWidth, EndWidth, NewCosInterp); const float NewFalloff = FMath::Lerp(StartSideFalloff, EndSideFalloff, NewCosInterp); const float NewRoll = FMath::Lerp(StartRoll, EndRoll, NewCosInterp); const FVector NewPos = SplineInfo.Eval(NewTime, FVector::ZeroVector); const FVector NewTangent = SplineInfo.EvalDerivative(NewTime, FVector::ZeroVector).GetSafeNormal(); const FVector NewBiNormal = FQuat(NewTangent, -NewRoll).RotateVector((NewTangent ^ FVector(0, 0, -1)).GetSafeNormal()); const FVector NewLeftPos = NewPos - NewBiNormal * NewWidth; const FVector NewRightPos = NewPos + NewBiNormal * NewWidth; const FVector NewFalloffLeftPos = NewPos - NewBiNormal * (NewWidth + NewFalloff); const FVector NewFalloffRightPos = NewPos + NewBiNormal * (NewWidth + NewFalloff); const float NewStartEndFalloff = FMath::Min((StartFalloffFraction > 0 ? NewTime / StartFalloffFraction : 1.0f), (EndFalloffFraction > 0 ? (1 - NewTime) / EndFalloffFraction : 1.0f)); Points.Emplace(NewPos, NewLeftPos, NewRightPos, NewFalloffLeftPos, NewFalloffRightPos, NewStartEndFalloff); } } Points.Emplace(NewKeyPos, NewKeyLeftPos, NewKeyRightPos, NewKeyFalloffLeftPos, NewKeyFalloffRightPos, NewKeyStartEndFalloff); OldKeyTime = NewKeyTime; } // Handle self-intersection errors due to tight turns FixSelfIntersection(Points, &FLandscapeSplineInterpPoint::Left); FixSelfIntersection(Points, &FLandscapeSplineInterpPoint::Right); FixSelfIntersection(Points, &FLandscapeSplineInterpPoint::FalloffLeft); FixSelfIntersection(Points, &FLandscapeSplineInterpPoint::FalloffRight); }