#pragma once #include "Math/MathFwd.h" #include "StreetMap.generated.h" USTRUCT(BlueprintType) struct STREETMAPRUNTIME_API FStreetMapCollisionSettings { GENERATED_USTRUCT_BODY() public: /** Uses triangle mesh data for collision data. (Cannot be used for physics simulation). */ UPROPERTY(EditAnywhere, Category = "StreetMap") uint32 bGenerateCollision : 1; /** * If true, the physics triangle mesh will use double sided faces when doing scene queries. * This is useful for planes and single sided meshes that need traces to work on both sides. */ UPROPERTY(EditAnywhere, Category = "StreetMap", meta = (editcondition = "bGenerateCollision")) uint32 bAllowDoubleSidedGeometry : 1; FStreetMapCollisionSettings() : bGenerateCollision(false), bAllowDoubleSidedGeometry(false) { } }; /** Mesh generation settings */ USTRUCT(BlueprintType) struct STREETMAPRUNTIME_API FStreetMapMeshBuildSettings { GENERATED_USTRUCT_BODY() public: /** Roads base vertical offset */ UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0"), DisplayName = "Road Vertical Offset") float RoadOffesetZ; /** if true buildings mesh will be 3D instead of flat representation. */ UPROPERTY(Category = StreetMap, EditAnywhere, DisplayName = "Create 3D Buildings") uint32 bWant3DBuildings : 1; /** building level floor conversion factor in centimeters @todo: harmonize with OSMToCentimetersScaleFactor refactoring */ UPROPERTY(Category = StreetMap, EditAnywhere, DisplayName = "Building Level Floor Factor") float BuildingLevelFloorFactor = 300.0f; /** * If true, buildings mesh will receive light information. * Lit buildings can't share vertices beyond quads (all quads have their own face normals), so this uses a lot more geometry. */ UPROPERTY(Category = StreetMap, EditAnywhere, DisplayName = "Lit buildings") uint32 bWantLitBuildings : 1; /** Streets thickness */ UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) float StreetThickness; /** Street vertex color */ UPROPERTY(Category = StreetMap, EditAnywhere) FLinearColor StreetColor; /** Major road thickness */ UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) float MajorRoadThickness; /** Major road vertex color */ UPROPERTY(Category = StreetMap, EditAnywhere) FLinearColor MajorRoadColor; /** Highway thickness */ UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) float HighwayThickness; /** Highway vertex color */ UPROPERTY(Category = StreetMap, EditAnywhere) FLinearColor HighwayColor; /** Streets Thickness */ UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) float BuildingBorderThickness; /** Building border vertex color */ UPROPERTY(Category = StreetMap, EditAnywhere) FLinearColor BuildingBorderLinearColor; /** Buildings border vertical offset */ UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) float BuildingBorderZ; /** Water vertex color */ UPROPERTY(Category = StreetMap, EditAnywhere) FLinearColor WaterColor; /** Park vertex color */ UPROPERTY(Category = StreetMap, EditAnywhere) FLinearColor ParkColor; FStreetMapMeshBuildSettings() : RoadOffesetZ(0.0f), bWant3DBuildings(true), bWantLitBuildings(true), StreetThickness(800.0f), StreetColor(0.93f, 0.90f, 0.87f), MajorRoadThickness(1000.0f), MajorRoadColor(0.92f, 0.91f, 0.88f), HighwayThickness(1400.0f), HighwayColor(FLinearColor(0.91f, 0.90f, 0.87f)), BuildingBorderThickness(20.0f), BuildingBorderLinearColor(0.85f, 0.85f, 0.85f), BuildingBorderZ(10.0f), WaterColor(0.26f, 0.75f, 0.98f), ParkColor(0.00f, 0.55f, 0.27f) { } }; /** Types of roads */ UENUM( BlueprintType ) enum EStreetMapRoadType { /** Small road or residential street */ Street, /** Major road or minor state highway */ MajorRoad, /** Highway */ Highway, /** Other (path, bus route, etc) */ Other, }; /** A road */ USTRUCT( BlueprintType ) struct STREETMAPRUNTIME_API FStreetMapRoad { GENERATED_USTRUCT_BODY() /** Name of the road */ UPROPERTY( Category=StreetMap, EditAnywhere, BlueprintReadWrite ) FString RoadName; /** Type of road */ UPROPERTY( Category=StreetMap, EditAnywhere ) TEnumAsByte RoadType; /** Nodes along this road, one at each point in the RoadPoints list */ UPROPERTY( Category=StreetMap, EditAnywhere ) TArray NodeIndices; /** List of all of the points on this road, one for each node in the NodeIndices list */ UPROPERTY( Category=StreetMap, EditAnywhere, BlueprintReadWrite ) TArray RoadPoints; // @todo: Performance: Bounding information could be computed at load time if we want to avoid the memory cost of storing it /** 2D bounds (min) of this road's points */ UPROPERTY( Category=StreetMap, EditAnywhere ) FVector2D BoundsMin; /** 2D bounds (max) of this road's points */ UPROPERTY( Category=StreetMap, EditAnywhere ) FVector2D BoundsMax; /** True if this node is a one way. One way nodes are only traversable in the order the nodes are listed in the above array. */ UPROPERTY( Category=StreetMap, EditAnywhere ) uint8 bIsOneWay : 1; /** Returns this node's index */ inline int32 GetRoadIndex( const class UStreetMap& StreetMap ) const; /** Gets the node for the specified point, or the node that came before that if the specified point doesn't have a node */ inline const struct FStreetMapNode& GetNodeAtPointIndexOrEarlier( const class UStreetMap& StreetMap, const int32 PointIndex, int32& OutNodeAtPointIndex ) const; /** Gets the node for the specified point, or the node that comes next after that if the specified point doesn't have a node */ inline const struct FStreetMapNode& GetNodeAtPointIndexOrLater( const class UStreetMap& StreetMap, const int32 PointIndex, int32& OutNodeAtPointIndex ) const; /** Computes the total length of this road by following along all of it's points */ float ComputeLengthOfRoad( const class UStreetMap& StreetMap ) const; /** Computes the distance along the road between two points on the road. Be careful! The same node can appear on a road twice. */ float ComputeDistanceBetweenNodesOnRoad( const class UStreetMap& StreetMap, const int32 NodePointIndexA, const int32 NodePointIndexB ) const; /** Given a position along the road, finds the nodes that come earlier and later on that road */ void FindEarlierAndLaterNodesForPositionAlongRoad( const class UStreetMap& StreetMap, const float PositionAlongRoad, const FStreetMapNode*& OutEarlierNode, float& OutEarlierNodePositionAlongRoad, const FStreetMapNode*& OutLaterNode, float& OutLaterNodePositionAlongRoad ) const; /** Given a node that exists at a point index on this road, finds the nodes that are immediately earlier and later to it (adjacent.) Will set a nullptr if there are no earlier or later nodes */ void FindEarlierAndLaterNodes( const class UStreetMap& StreetMap, const int32 RoadPointIndex, const FStreetMapNode*& OutEarlierNode, float& OutEarlierNodePositionAlongRoad, const FStreetMapNode*& OutLaterNode, float& OutLaterNodePositionAlongRoad ) const; // NOTE: You may notice there is no "FindPointIndexForNode()" method in this class. This is because // the same node may appear more than once on any single road, so it's never safe to ask for // a single point index on a road. /** Given a node that exists on this road, computes the position along this road of that node */ float FindPositionAlongRoadForNode( const class UStreetMap& StreetMap, const int32 PointIndexForNode ) const; /** Computes the location of a point along this road, given a distance along this road from the road's beginning */ FVector2D MakeLocationAlongRoad( const class UStreetMap& StreetMap, const float PositionAlongRoad ) const; /** @return True if this is a one way road */ inline bool IsOneWay() const { return bIsOneWay == 1 ? true : false; } FStreetMapRoad() : RoadName(), RoadType(EStreetMapRoadType::Street), NodeIndices(), RoadPoints(), BoundsMin(FVector2D::ZeroVector), BoundsMax(FVector2D::ZeroVector), bIsOneWay(0) { } }; /** Nodes have a list of road refs, one for each road that intersects this node. Each road ref references a road and also the point along that road where this node exists. */ USTRUCT( BlueprintType ) struct STREETMAPRUNTIME_API FStreetMapRoadRef { GENERATED_USTRUCT_BODY() /** Index of road in the list of all roads in this street map */ UPROPERTY( Category=StreetMap, EditAnywhere ) int32 RoadIndex = INDEX_NONE; /** Index of the point along road where this node exists */ UPROPERTY( Category=StreetMap, EditAnywhere ) int32 RoadPointIndex = INDEX_NONE; }; /** Describes a node on a road. Nodes usually connect at least two roads together, but they might also exist at the end of a dead-end street. They are sort of like an "intersection". */ USTRUCT( BlueprintType ) struct STREETMAPRUNTIME_API FStreetMapNode { GENERATED_USTRUCT_BODY() /** All of the roads that intersect this node. We have references to each of these roads, as well as the point along each road where this node exists */ UPROPERTY( Category=StreetMap, EditAnywhere ) TArray RoadRefs; /** Returns this node's index */ inline int32 GetNodeIndex( const UStreetMap& StreetMap ) const; /** Gets the location of this node */ inline FVector2D GetLocation( const UStreetMap& StreetMap ) const; /// /// Utility functions which may be useful for pathfinding algorithms (not used internally.) /// /** Pathfinding: Given a node that is known to connect to this node via some road, searches for the road and returns it */ inline const FStreetMapRoad& GetShortestCostRoadToNode( UStreetMap& StreetMap, const FStreetMapNode& OtherNode, const bool bIsTravelingForward, int32& OutPointIndexOnRoad ) const; /** Pathfinding: Returns true if this node is the end point on a road with no connections */ inline bool IsDeadEnd( const UStreetMap& StreetMap ) const; /** Pathfinding: Returns the number of connections between this node and other roads, taking into account the direction of travel */ inline int32 GetConnectionCount( const class UStreetMap& StreetMap, const bool bIsTravelingForward ) const; /** Pathfinding: Returns a connected node by index (between 0 and GetConnectionCount() - 1 ), taking into account the direction of travel. Also returns the connecting road and wherabouts on the road the connection occurs */ inline const FStreetMapNode* GetConnection( const class UStreetMap& StreetMap, const int32 ConnectionIndex, const bool bIsTravelingForward, const struct FStreetMapRoad** OutConnectingRoad = nullptr, int32* OutPointIndexOnRoad = nullptr, int32* OutConnectedNodePointIndexOnRoad = nullptr ) const; /** Pathfinding: Estimates the 'cost' of the specified connected by index (between 0 and GetConnectionCount() - 1) */ inline float GetConnectionCost( const class UStreetMap& StreetMap, const int32 ConnectionIndex, const bool bIsTravelingForward ) const; }; /** A building */ USTRUCT( BlueprintType ) struct STREETMAPRUNTIME_API FStreetMapBuilding { GENERATED_USTRUCT_BODY() /** Name of the building */ UPROPERTY( Category=StreetMap, EditAnywhere, BlueprintReadWrite ) FString BuildingName; /** Polygon points that define the perimeter of the building */ UPROPERTY( Category=StreetMap, EditAnywhere, BlueprintReadWrite ) TArray BuildingPoints; /** Height of the building in meters (if known, otherwise zero) */ UPROPERTY( Category=StreetMap, EditAnywhere ) double Height = 0.0; /** Levels of the building (if known, otherwise zero) */ UPROPERTY(Category = StreetMap, EditAnywhere) int BuildingLevels = 0; /** The amenity of the building */ UPROPERTY( Category=StreetMap, EditAnywhere, BlueprintReadWrite ) FString Amenity; /** The building type */ UPROPERTY( Category=StreetMap, EditAnywhere, BlueprintReadWrite ) FString Building; // @todo: Performance: Bounding information could be computed at load time if we want to avoid the memory cost of storing it /** 2D bounds (min) of this building's points */ UPROPERTY( Category=StreetMap, EditAnywhere ) FVector2D BoundsMin = FVector2D::ZeroVector; /** 2D bounds (max) of this building's points */ UPROPERTY( Category=StreetMap, EditAnywhere ) FVector2D BoundsMax = FVector2D::ZeroVector; }; // Vinfamy added USTRUCT( BlueprintType ) struct STREETMAPRUNTIME_API FStreetMapWater { GENERATED_USTRUCT_BODY() /** Name of the water */ UPROPERTY( Category=StreetMap, EditAnywhere) FString WaterName; /** Polygon points that define the perimeter of the water */ UPROPERTY( Category=StreetMap, EditAnywhere ) TArray WaterPoints; // @todo: Performance: Bounding information could be computed at load time if we want to avoid the memory cost of storing it /** 2D bounds (min) of this water's points */ UPROPERTY( Category=StreetMap, EditAnywhere ) FVector2D BoundsMin = FVector2D::ZeroVector; /** 2D bounds (max) of this water's points */ UPROPERTY( Category=StreetMap, EditAnywhere ) FVector2D BoundsMax = FVector2D::ZeroVector; }; USTRUCT( BlueprintType ) struct STREETMAPRUNTIME_API FStreetMapPark { GENERATED_USTRUCT_BODY() /** Name of the park */ UPROPERTY( Category=StreetMap, EditAnywhere) FString ParkName; /** Polygon points that define the perimeter of the park */ UPROPERTY( Category=StreetMap, EditAnywhere ) TArray ParkPoints; // @todo: Performance: Bounding information could be computed at load time if we want to avoid the memory cost of storing it /** 2D bounds (min) of this water's points */ UPROPERTY( Category=StreetMap, EditAnywhere ) FVector2D BoundsMin = FVector2D::ZeroVector; /** 2D bounds (max) of this water's points */ UPROPERTY( Category=StreetMap, EditAnywhere ) FVector2D BoundsMax = FVector2D::ZeroVector; }; /** A loaded street map */ UCLASS(Blueprintable, BlueprintType) class STREETMAPRUNTIME_API UStreetMap : public UObject { GENERATED_BODY() public: /** Default constructor for UStreetMap */ UStreetMap(); // UObject overrides // Portage 5.5 : la surcharge TArray& est depreciee (C4996) et // disparait a la version suivante du moteur. FAssetRegistryTagsContext est declare // par UObject/Object.h, aucun include supplementaire n'est necessaire ici. virtual void GetAssetRegistryTags( FAssetRegistryTagsContext Context ) const override; /** Gets the roads in this street map (read only) */ const TArray& GetRoads() const { return Roads; } /** Gets the roads in this street map */ TArray& GetRoads() { return Roads; } /** Gets the nodes on the map (read only.) Nodes describe intersections between roads */ const TArray& GetNodes() const { return Nodes; } /** Gets the nodes on the map. Nodes describe intersections between roads */ TArray& GetNodes() { return Nodes; } /** Gets all of the buildings (read only) */ const TArray& GetBuildings() const { return Buildings; } /** Gets all of the buildings */ TArray& GetBuildings() { return Buildings; } /** vinfamy Gets all of the waters (read only) */ const TArray& GetWaters() const { return Waters; } /** Gets all of the waters */ TArray& GetWaters() { return Waters; } /** vinfamy Gets all of the parks (read only) */ const TArray& GetParks() const { return Parks; } /** Gets all of the parks */ TArray& GetParks() { return Parks; } /** Gets the bounding box of the map */ FVector2D GetBoundsMin() const { return BoundsMin; } FVector2D GetBoundsMax() const { return BoundsMax; } /** Vinfamy added */ UFUNCTION(BlueprintCallable) TArray FindBuilding(const FVector2D Point); UFUNCTION(BlueprintCallable) FStreetMapRoad FindRoad(const FVector2D Point); UFUNCTION(BlueprintCallable) FStreetMapBuilding FindBuildingFromArray(const FVector2D Point, TArray SmallBuildings); UFUNCTION(BlueprintCallable) TArray SearchByBuildingName(FString SearchTerm, FString Amenity) const; UFUNCTION(BlueprintCallable) TArray SearchByBuildingNameFromArray(FString SearchTerm, FString Amenity, TArray SmallBuildings) const; UFUNCTION(BlueprintCallable) TArray SearchByAmenity(FString Amenity) const; TArray FilterByAmenity(FString Amenity, TArray SmallBuildings) const; protected: /** List of roads */ UPROPERTY( Category=StreetMap, EditAnywhere, BlueprintReadWrite) TArray Roads; /** List of nodes on this map. Nodes describe interesting points along roads, usually where roads intersect or at the end of a dead-end street */ UPROPERTY( Category=StreetMap, VisibleAnywhere ) TArray Nodes; /** Vinfamy added - List of waters (natural = water) */ UPROPERTY( Category=StreetMap, VisibleAnywhere ) TArray Waters; /** List of parks (leisure = park) */ UPROPERTY( Category=StreetMap, VisibleAnywhere ) TArray Parks; /** List of all buildings on the street map */ UPROPERTY( Category=StreetMap, EditAnywhere, BlueprintReadWrite) TArray Buildings; /** 2D bounds (min) of this map's roads and buildings */ UPROPERTY( Category=StreetMap, VisibleAnywhere) FVector2D BoundsMin; /** 2D bounds (max) of this map's roads and buildings */ UPROPERTY( Category=StreetMap, VisibleAnywhere) FVector2D BoundsMax; #if WITH_EDITORONLY_DATA /** Importing data and options used for this mesh */ UPROPERTY( VisibleAnywhere, Instanced, Category=ImportSettings ) class UAssetImportData* AssetImportData; friend class UStreetMapFactory; friend class UStreetMapReimportFactory; friend class FStreetMapAssetTypeActions; #endif // WITH_EDITORONLY_DATA }; inline int32 FStreetMapRoad::GetRoadIndex( const UStreetMap& StreetMap ) const { // Pointer arithmetic based on array start const int32 RoadIndex = this - StreetMap.GetRoads().GetData(); return RoadIndex; } inline const FStreetMapNode& FStreetMapRoad::GetNodeAtPointIndexOrEarlier( const UStreetMap& StreetMap, const int32 PointIndex, int32& OutNodeAtPointIndex ) const { const FStreetMapNode* CurrentOrEarlierPointNode = nullptr; for( int32 NodePointIndex = PointIndex; NodePointIndex >= 0; --NodePointIndex ) { if( NodeIndices[ NodePointIndex ] != INDEX_NONE ) { CurrentOrEarlierPointNode = &StreetMap.GetNodes()[ NodeIndices[ NodePointIndex ] ]; OutNodeAtPointIndex = NodePointIndex; break; } } return *CurrentOrEarlierPointNode; } inline const FStreetMapNode& FStreetMapRoad::GetNodeAtPointIndexOrLater( const UStreetMap& StreetMap, const int32 PointIndex, int32& OutNodeAtPointIndex ) const { const FStreetMapNode* NextOrUpcomingNode = nullptr; for( int32 NodePointIndex = PointIndex; NodePointIndex < RoadPoints.Num(); ++NodePointIndex ) { if( NodeIndices[ NodePointIndex ] != INDEX_NONE ) { NextOrUpcomingNode = &StreetMap.GetNodes()[ NodeIndices[ NodePointIndex ] ]; OutNodeAtPointIndex = NodePointIndex; break; } } return *NextOrUpcomingNode; } inline float FStreetMapRoad::ComputeLengthOfRoad( const class UStreetMap& StreetMap ) const { // @todo: Performance: We could cache the road's total length at load time to avoid having to compute it, // or we could save it right into the asset file return ComputeDistanceBetweenNodesOnRoad( StreetMap, 0, this->NodeIndices.Num() - 1 ); } inline float FStreetMapRoad::ComputeDistanceBetweenNodesOnRoad( const class UStreetMap& StreetMap, const int32 NodePointIndexA, const int32 NodePointIndexB ) const { float TotalDistanceSoFar = 0.0f; // NOTE: It is very important that we use the actual road point indices here and not nodes directly, because the same node can appear // more than once on a single road! // @todo: Performance: We can cache distances between connected nodes rather than computing them every time. This // can be computed at load time or at import time (and stored in the asset). Most of the other functions // in this class that perform Size() computations could be changed to use cached distances also! const int32 SmallerPointIndex = FMath::Max( 0, FMath::Min( NodePointIndexA, NodePointIndexB ) ); const int32 LargerPointIndex = FMath::Min( RoadPoints.Num() - 1, FMath::Max( NodePointIndexA, NodePointIndexB ) ); for( int32 PointIndex = SmallerPointIndex; PointIndex < LargerPointIndex; ++PointIndex ) { const FVector2D PointLocation = RoadPoints[ PointIndex ]; const FVector2D NextPointLocation = RoadPoints[ PointIndex + 1 ]; const float DistanceBetweenPoints = ( NextPointLocation - PointLocation ).Size(); TotalDistanceSoFar += DistanceBetweenPoints; } // @todo: Malformed data can cause this assertion to trigger. This could be a single road with at least two adjacent nodes // at the exact same location. We need to filter this out at load time probably. // check( TotalDistanceSoFar > 0.0f ); return TotalDistanceSoFar; } inline void FStreetMapRoad::FindEarlierAndLaterNodesForPositionAlongRoad( const class UStreetMap& StreetMap, const float PositionAlongRoad, const FStreetMapNode*& OutEarlierNode, float& OutEarlierNodePositionAlongRoad, const FStreetMapNode*& OutLaterNode, float& OutLaterNodePositionAlongRoad ) const { float CurrentPointPositionAlongRoad = 0.0f; const FStreetMapNode* EarlierStreetMapNode = nullptr; const FStreetMapNode* LaterStreetMapNode = nullptr; const int32 NumPoints = RoadPoints.Num(); for( int32 CurrentPointIndex = 0; CurrentPointIndex < NumPoints - 1; ++CurrentPointIndex ) { if( this->NodeIndices[ CurrentPointIndex ] != INDEX_NONE ) { EarlierStreetMapNode = &StreetMap.GetNodes()[ this->NodeIndices[ CurrentPointIndex ] ]; OutEarlierNodePositionAlongRoad = CurrentPointPositionAlongRoad; } const int32 NextPointIndex = CurrentPointIndex + 1; const FVector2D CurrentPointLocation = RoadPoints[ CurrentPointIndex ]; const FVector2D NextPointLocation = RoadPoints[ NextPointIndex ]; const float DistanceBetweenPoints = ( NextPointLocation - CurrentPointLocation ).Size(); const float NextPointPositionAlongRoad = CurrentPointPositionAlongRoad + DistanceBetweenPoints; if( NextPointPositionAlongRoad >= PositionAlongRoad ) { if( NodeIndices[ NextPointIndex ] != INDEX_NONE ) { LaterStreetMapNode = &StreetMap.GetNodes()[ this->NodeIndices[ NextPointIndex ] ]; OutLaterNodePositionAlongRoad = NextPointPositionAlongRoad; break; } } CurrentPointPositionAlongRoad = NextPointPositionAlongRoad; } check( EarlierStreetMapNode != nullptr && LaterStreetMapNode != nullptr ); OutEarlierNode = EarlierStreetMapNode; OutLaterNode = LaterStreetMapNode; } inline void FStreetMapRoad::FindEarlierAndLaterNodes( const class UStreetMap& StreetMap, const int32 RoadPointIndex, const FStreetMapNode*& OutEarlierNode, float& OutEarlierNodePositionAlongRoad, const FStreetMapNode*& OutLaterNode, float& OutLaterNodePositionAlongRoad ) const { OutEarlierNode = nullptr; OutEarlierNodePositionAlongRoad = -1.0f; OutLaterNode = nullptr; OutLaterNodePositionAlongRoad = -1.0f; for( int32 EarlierPointIndex = RoadPointIndex - 1; EarlierPointIndex >= 0; --EarlierPointIndex ) { if( this->NodeIndices[ EarlierPointIndex ] != INDEX_NONE ) { OutEarlierNode = &StreetMap.GetNodes()[ this->NodeIndices[ EarlierPointIndex ] ]; OutEarlierNodePositionAlongRoad = FindPositionAlongRoadForNode( StreetMap, EarlierPointIndex ); break; } } for( int32 LaterPointIndex = RoadPointIndex + 1; LaterPointIndex < this->RoadPoints.Num(); ++LaterPointIndex ) { if( this->NodeIndices[ LaterPointIndex ] != INDEX_NONE ) { OutLaterNode = &StreetMap.GetNodes()[ this->NodeIndices[ LaterPointIndex ] ]; OutLaterNodePositionAlongRoad = FindPositionAlongRoadForNode( StreetMap, LaterPointIndex ); break; } } } inline float FStreetMapRoad::FindPositionAlongRoadForNode( const class UStreetMap& StreetMap, const int32 PointIndexForNode ) const { float CurrentPointPositionAlongRoad = 0.0f; for( int32 CurrentPointIndex = 0; CurrentPointIndex < PointIndexForNode; ++CurrentPointIndex ) { const FVector2D CurrentPointLocation = RoadPoints[ CurrentPointIndex ]; const FVector2D NextPointLocation = RoadPoints[ CurrentPointIndex + 1 ]; const float DistanceBetweenPoints = ( NextPointLocation - CurrentPointLocation ).Size(); const float NextPointPositionAlongRoad = CurrentPointPositionAlongRoad + DistanceBetweenPoints; CurrentPointPositionAlongRoad = NextPointPositionAlongRoad; } return CurrentPointPositionAlongRoad; } inline FVector2D FStreetMapRoad::MakeLocationAlongRoad( const class UStreetMap& StreetMap, const float PositionAlongRoad ) const { FVector2D LocationAlongRoad = FVector2D::ZeroVector; float CurrentPointPositionAlongRoad = 0.0f; bool bFoundLocation = false; const int32 NumPoints = RoadPoints.Num(); for( int32 CurrentPointIndex = 0; CurrentPointIndex < NumPoints - 1; ++CurrentPointIndex ) { const FVector2D CurrentPointLocation = RoadPoints[ CurrentPointIndex ]; const FVector2D NextPointLocation = RoadPoints[ CurrentPointIndex + 1 ]; const float DistanceBetweenPoints = ( NextPointLocation - CurrentPointLocation ).Size(); const float NextPointPositionAlongRoad = CurrentPointPositionAlongRoad + DistanceBetweenPoints; if( NextPointPositionAlongRoad >= PositionAlongRoad ) { const float LerpAlpha = ( PositionAlongRoad - CurrentPointPositionAlongRoad ) / DistanceBetweenPoints; LocationAlongRoad = FMath::Lerp( CurrentPointLocation, NextPointLocation, LerpAlpha ); bFoundLocation = true; break; } CurrentPointPositionAlongRoad = NextPointPositionAlongRoad; } check( bFoundLocation == true ); return LocationAlongRoad; } inline int32 FStreetMapNode::GetNodeIndex( const UStreetMap& StreetMap ) const { // Pointer arithmetic based on array start const int32 NodeIndex = this - StreetMap.GetNodes().GetData(); return NodeIndex; } inline bool FStreetMapNode::IsDeadEnd( const UStreetMap& StreetMap ) const { if( RoadRefs.Num() == 1 ) { // @todo: If this road only connects to dead end roads that oppose the direction, we need to treat this road // as a dead end. This case should be extremely uncommon, though! const FStreetMapRoadRef& SoleRoadRef = RoadRefs[ 0 ]; const FStreetMapRoad& SoleRoad = StreetMap.GetRoads()[ SoleRoadRef.RoadIndex ]; if( SoleRoadRef.RoadPointIndex == 0 || SoleRoadRef.RoadPointIndex == ( SoleRoad.NodeIndices.Num() - 1 ) ) { // The node is attached to only one road, and the node is at the very end of one of the ends of the road return true; } } return false; } inline FVector2D FStreetMapNode::GetLocation( const UStreetMap& StreetMap ) const { const FStreetMapRoadRef& MyFirstRoadRef = RoadRefs[ 0 ]; const FVector2D Location = StreetMap.GetRoads()[ MyFirstRoadRef.RoadIndex ].RoadPoints[ MyFirstRoadRef.RoadPointIndex ]; return Location; } inline const FStreetMapRoad& FStreetMapNode::GetShortestCostRoadToNode( UStreetMap& StreetMap, const FStreetMapNode& OtherNode, const bool bIsTravelingForward, int32& OutPointIndexOnRoad ) const { const FStreetMapRoad* ConnectingRoad = nullptr; float BestConnectionCost = TNumericLimits::Max(); int32 BestConnectionIndex = INDEX_NONE; int32 BestConnectionPointIndex = INDEX_NONE; const int32 ConnectionCount = GetConnectionCount( StreetMap, bIsTravelingForward ); for( int32 ConnectionIndex = 0; ConnectionIndex < ConnectionCount; ++ConnectionIndex ) { int32 MyPointIndexOnRoad; int32 ConnectedNodePointIndexOnRoad; const FStreetMapRoad* CurrentConnectingRoad; const FStreetMapNode* ConnectedNode = GetConnection( StreetMap, ConnectionIndex, bIsTravelingForward, /* Out */ &CurrentConnectingRoad, /* Out */ &MyPointIndexOnRoad, /* Out */ &ConnectedNodePointIndexOnRoad ); if( ConnectedNode == &OtherNode ) { if( BestConnectionIndex != INDEX_NONE ) { // We evaluate cost lazily, only in the unusual case of the two nodes being connected by multiple roads BestConnectionCost = GetConnectionCost( StreetMap, BestConnectionIndex, bIsTravelingForward ); if( GetConnectionCost( StreetMap, ConnectionIndex, bIsTravelingForward ) < BestConnectionCost ) { ConnectingRoad = CurrentConnectingRoad; BestConnectionIndex = ConnectionIndex; BestConnectionPointIndex = MyPointIndexOnRoad; } } else { ConnectingRoad = CurrentConnectingRoad; BestConnectionIndex = ConnectionIndex; BestConnectionPointIndex = MyPointIndexOnRoad; } } } check( ConnectingRoad != nullptr ); OutPointIndexOnRoad = BestConnectionPointIndex; return *ConnectingRoad; } inline int32 FStreetMapNode::GetConnectionCount( const UStreetMap& StreetMap, const bool bIsTravelingForward ) const { // NOTE: We're iterating here in the exact same order as in the GetConnection() function below! That's critically important! int32 TotalConnections = 0; for( const FStreetMapRoadRef& RoadRef : RoadRefs ) { const FStreetMapRoad& Road = StreetMap.GetRoads()[ RoadRef.RoadIndex ]; if( RoadRef.RoadPointIndex > 0 && ( !bIsTravelingForward || !Road.IsOneWay() ) ) { // We connect to a node earlier up this road ++TotalConnections; } if( RoadRef.RoadPointIndex < ( Road.NodeIndices.Num() - 1 ) && ( bIsTravelingForward || !Road.IsOneWay() ) ) { // We connect to a node further down this road ++TotalConnections; } } return TotalConnections; } inline const FStreetMapNode* FStreetMapNode::GetConnection( const UStreetMap& StreetMap, const int32 ConnectionIndex, const bool bIsTravelingForward, const FStreetMapRoad** OutConnectingRoad, int32* OutPointIndexOnRoad, int32* OutConnectedNodePointIndexOnRoad ) const { if( OutConnectingRoad != nullptr ) { *OutConnectingRoad = nullptr; } const FStreetMapNode* ConnectedNode = nullptr; // @todo: Performance: We can improve performance by caching additional connectivity information right on // the node itself. This function would be a hot spot for any sort of pathfinding computation // NOTE: We're iterating here in the exact same order as in the GetConnectionCount() function above! That's critically important! int32 CurrentConnectionIndex = 0; for( const FStreetMapRoadRef& RoadRef : RoadRefs ) { const FStreetMapRoad& Road = StreetMap.GetRoads()[ RoadRef.RoadIndex ]; // @todo: Performance: We could avoid the "while" loops below by not storing INDEX_NONEs in the NodeIndices array, // but instead mapping them to points by going through the node itself, then back to a road if( RoadRef.RoadPointIndex > 0 && ( !bIsTravelingForward || !Road.IsOneWay() ) ) { // We connect to an earlier node up this road if( CurrentConnectionIndex == ConnectionIndex ) { int32 EarlierNodeRoadPointIndex = RoadRef.RoadPointIndex - 1; while( Road.NodeIndices[ EarlierNodeRoadPointIndex ] == INDEX_NONE ) { --EarlierNodeRoadPointIndex; } const int32 EarlierNodeIndex = Road.NodeIndices[ EarlierNodeRoadPointIndex ]; const FStreetMapNode& EarlierNode = StreetMap.GetNodes()[ EarlierNodeIndex ]; ConnectedNode = &EarlierNode; if( OutConnectingRoad != nullptr ) { *OutConnectingRoad = &Road; } if( OutPointIndexOnRoad != nullptr ) { *OutPointIndexOnRoad = RoadRef.RoadPointIndex; } if( OutConnectedNodePointIndexOnRoad != nullptr ) { *OutConnectedNodePointIndexOnRoad = EarlierNodeRoadPointIndex; } // Got it! break; } ++CurrentConnectionIndex; } if( RoadRef.RoadPointIndex < ( Road.NodeIndices.Num() - 1 ) && ( bIsTravelingForward || !Road.IsOneWay() ) ) { // We connect to node further down this road if( CurrentConnectionIndex == ConnectionIndex ) { int32 LaterNodeRoadPointIndex = RoadRef.RoadPointIndex + 1; while( Road.NodeIndices[ LaterNodeRoadPointIndex ] == INDEX_NONE ) { ++LaterNodeRoadPointIndex; } const int32 LaterNodeIndex = Road.NodeIndices[ LaterNodeRoadPointIndex ]; const FStreetMapNode& LaterNode = StreetMap.GetNodes()[ LaterNodeIndex ]; ConnectedNode = &LaterNode; if( OutConnectingRoad != nullptr ) { *OutConnectingRoad = &Road; } if( OutPointIndexOnRoad != nullptr ) { *OutPointIndexOnRoad = RoadRef.RoadPointIndex; } if( OutConnectedNodePointIndexOnRoad != nullptr ) { *OutConnectedNodePointIndexOnRoad = LaterNodeRoadPointIndex; } // Got it! break; } ++CurrentConnectionIndex; } } check( ConnectedNode != nullptr ); return ConnectedNode; } inline float FStreetMapNode::GetConnectionCost( const UStreetMap& StreetMap, const int32 ConnectionIndex, const bool bIsTravelingForward ) const { ///////////////////////////////////////////////////////// // Tweakables for connection cost estimation // const float MaxSpeedLimit = 120.0f; // 120 Km/hr const float HighwaySpeed = 110.0f; const float HighwayTrafficFactor = 0.0; const float MajorRoadSpeed = 70.0f; const float MajorRoadTrafficFactor = 0.2f; const float StreetSpeed = 40.0f; const float StreetTrafficFactor = 1.0f; ///////////////////////////////////////////////////////// // @todo: Street map pathfinding is a grand art in itself, and estimating cost of connections is // a very complicated problem. We're only doing some basic estimates for now, but in the // future we could consider taking into account the cost of different types of turns and // intersections, lane counts, actual speed limits, etc. int32 MyPointIndexOnRoad; int32 ConnectedNodePointIndexOnRoad; const FStreetMapRoad* ConnectingRoad = nullptr; const FStreetMapNode& ConnectedNode = *GetConnection( StreetMap, ConnectionIndex, bIsTravelingForward, /* Out */ &ConnectingRoad, /* Out */ &MyPointIndexOnRoad, /* Out */ &ConnectedNodePointIndexOnRoad ); const float DistanceBetweenNodes = ConnectingRoad->ComputeDistanceBetweenNodesOnRoad( StreetMap, MyPointIndexOnRoad, ConnectedNodePointIndexOnRoad ); float TotalCost = DistanceBetweenNodes; // Apply some scaling to the cost of traveling between these nodes { float SpeedLimit = 0.0f; float TrafficFactor = 0.0f; switch( ConnectingRoad->RoadType ) { case EStreetMapRoadType::Highway: SpeedLimit = HighwaySpeed; TrafficFactor = HighwayTrafficFactor; break; case EStreetMapRoadType::MajorRoad: SpeedLimit = MajorRoadSpeed; TrafficFactor = MajorRoadTrafficFactor; break; case EStreetMapRoadType::Street: case EStreetMapRoadType::Other: SpeedLimit = StreetSpeed; TrafficFactor = StreetTrafficFactor; break; default: check( 0 ); break; } const float RoadSpeedCostScale = ( 1.0f - ( SpeedLimit / MaxSpeedLimit ) ); TotalCost *= 1.0f + RoadSpeedCostScale * 15.0f * ( 0.5f + TrafficFactor * 0.5f ); } return TotalCost; }