diff --git a/Content/BPs/Birdview.uasset b/Content/BPs/Birdview.uasset index f6ee28f..f2a667e 100644 Binary files a/Content/BPs/Birdview.uasset and b/Content/BPs/Birdview.uasset differ diff --git a/Content/BPs/StreetMap_Static.uasset b/Content/BPs/StreetMap_Static.uasset index a5fb551..837210e 100644 Binary files a/Content/BPs/StreetMap_Static.uasset and b/Content/BPs/StreetMap_Static.uasset differ diff --git a/Content/Materials/PlaneMat.uasset b/Content/Materials/PlaneMat.uasset deleted file mode 100644 index 3ed2b8c..0000000 Binary files a/Content/Materials/PlaneMat.uasset and /dev/null differ diff --git a/Content/Scenes/CentralLondon.umap b/Content/Scenes/CentralLondon.umap index 3a30f22..6210e9f 100644 Binary files a/Content/Scenes/CentralLondon.umap and b/Content/Scenes/CentralLondon.umap differ diff --git a/Content/Scenes/CentralLondon_BuiltData.uasset b/Content/Scenes/CentralLondon_BuiltData.uasset index 09e7716..b8a6c81 100644 Binary files a/Content/Scenes/CentralLondon_BuiltData.uasset and b/Content/Scenes/CentralLondon_BuiltData.uasset differ diff --git a/Plugins/StreetMap/Content/StreetMapDefaultMaterial.uasset b/Plugins/StreetMap/Content/StreetMapDefaultMaterial.uasset index b812044..96c2c3a 100644 Binary files a/Plugins/StreetMap/Content/StreetMapDefaultMaterial.uasset and b/Plugins/StreetMap/Content/StreetMapDefaultMaterial.uasset differ diff --git a/Plugins/StreetMap/Source/StreetMapImporting/OSMFile.cpp b/Plugins/StreetMap/Source/StreetMapImporting/OSMFile.cpp index d386227..d217b7d 100644 --- a/Plugins/StreetMap/Source/StreetMapImporting/OSMFile.cpp +++ b/Plugins/StreetMap/Source/StreetMapImporting/OSMFile.cpp @@ -335,6 +335,72 @@ bool FOSMFile::ProcessAttribute( const TCHAR* AttributeName, const TCHAR* Attrib // Other type that we don't recognize yet. See http://wiki.openstreetmap.org/wiki/Key:building } } + + + /** + else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "natural" ) ) ) + { + if( !FCString::Stricmp( AttributeValue, TEXT( "water" ) ) ) + { + CurrentWayInfo->WayType = EOSMWayType::Water; + } + else + { + // Other natural type that we don't recognize yet. See http://wiki.openstreetmap.org/wiki/Key:building + } + } + + else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "waterway" ) ) ) + { + CurrentWayInfo->WayType = EOSMWayType::Water; + } + + else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "water" ) ) ) + { + CurrentWayInfo->WayType = EOSMWayType::Water; + } + + else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "landuse" ) ) ) + { + if( !FCString::Stricmp( AttributeValue, TEXT( "basin" ) ) ) + { + CurrentWayInfo->WayType = EOSMWayType::Water; + } + + else if( !FCString::Stricmp( AttributeValue, TEXT( "reservoir" ) ) ) + { + CurrentWayInfo->WayType = EOSMWayType::Water; + } + + else if( !FCString::Stricmp( AttributeValue, TEXT( "pond" ) ) ) + { + CurrentWayInfo->WayType = EOSMWayType::Water; + } + + } + */ + + else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "leisure" ) ) ) + { + if( !FCString::Stricmp( AttributeValue, TEXT( "park" ) ) ) + { + CurrentWayInfo->WayType = EOSMWayType::Park; + } + + else if( !FCString::Stricmp( AttributeValue, TEXT( "fitness_centre" ) ) ) + { + CurrentWayInfo->WayType = EOSMWayType::Building; + CurrentWayInfo->Amenity = AttributeValue; + } + + else + { + // Other natural type that we don't recognize yet. See http://wiki.openstreetmap.org/wiki/Key:building + } + } + + + else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "height" ) ) ) { // Check to see if there is a space character in the height value. For now, we're looking diff --git a/Plugins/StreetMap/Source/StreetMapImporting/OSMFile.h b/Plugins/StreetMap/Source/StreetMapImporting/OSMFile.h index 98895db..2b1aafd 100644 --- a/Plugins/StreetMap/Source/StreetMapImporting/OSMFile.h +++ b/Plugins/StreetMap/Source/StreetMapImporting/OSMFile.h @@ -129,6 +129,12 @@ public: /** Default type of building. A general catch-all. */ Building, + + /** Default type of building. A general catch-all. */ + Water, + + /** Default type of building. A general catch-all. */ + Park, /// diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapFactory.cpp b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapFactory.cpp index c4fc945..61e3831 100644 --- a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapFactory.cpp +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapFactory.cpp @@ -292,7 +292,7 @@ bool UStreetMapFactory::LoadFromOpenStreetMapXMLFile( UStreetMap* StreetMap, FSt // Vinfamy added if ( NewBuilding.BuildingLevels == 0 ) { - NewBuilding.BuildingLevels = rand() % 5 + 1; + NewBuilding.BuildingLevels = rand() % 10 + 3; } NewBuilding.Amenity = OSMWay.Amenity; NewBuilding.Building = OSMWay.Building; @@ -316,6 +316,207 @@ bool UStreetMapFactory::LoadFromOpenStreetMapXMLFile( UStreetMap* StreetMap, FSt return false; }; + + + + // Adds a water to the street map using the OpenStreetMap data, flattening the road's coordinates into our map's space + auto AddWaterForWay = [ConvertLatLongToMetersRelative, OSMToCentimetersScaleFactor]( + const FOSMFile& OSMFile, + UStreetMap& StreetMapRef, + const FOSMFile::FOSMWayInfo& OSMWay ) -> bool + { + if( OSMWay.WayType == FOSMFile::EOSMWayType::Water ) + { + // Require at least three points so that we don't have degenerate polygon! + if( OSMWay.Nodes.Num() > 2 ) + { + // Create a Water for this way + FStreetMapWater& NewWater = *new( StreetMapRef.Waters )FStreetMapWater(); + + FVector2D BoundsMin( TNumericLimits::Max(), TNumericLimits::Max() ); + FVector2D BoundsMax( TNumericLimits::Lowest(), TNumericLimits::Lowest() ); + + NewWater.WaterPoints.AddUninitialized( OSMWay.Nodes.Num() ); + int32 CurWaterPoint = 0; + + for( const FOSMFile::FOSMNodeInfo* OSMNodePtr : OSMWay.Nodes ) + { + const FOSMFile::FOSMNodeInfo& OSMNode = *OSMNodePtr; + + // Transform all points relative to the center of the latitude/longitude bounds, so that + // we get as much precision as possible. + const double RelativeToLatitude = OSMFile.AverageLatitude; + const double RelativeToLongitude = OSMFile.AverageLongitude; + const FVector2D NodePos = ConvertLatLongToMetersRelative( + OSMNode.Latitude, + OSMNode.Longitude, + RelativeToLatitude, + RelativeToLongitude ) * OSMToCentimetersScaleFactor; + + // Update bounding box + { + if( NodePos.X < BoundsMin.X ) + { + BoundsMin.X = NodePos.X; + } + if( NodePos.Y < BoundsMin.Y ) + { + BoundsMin.Y = NodePos.Y; + } + if( NodePos.X > BoundsMax.X ) + { + BoundsMax.X = NodePos.X; + } + if( NodePos.Y > BoundsMax.Y ) + { + BoundsMax.Y = NodePos.Y; + } + } + + // Fill in the points + NewWater.WaterPoints[ CurWaterPoint++ ] = NodePos; + } + + // Make sure the Water ended up with a closed polygon, then remove the final (redundant) point + const bool bIsClosed = NewWater.WaterPoints[ 0 ].Equals( NewWater.WaterPoints[ NewWater.WaterPoints.Num() - 1 ], KINDA_SMALL_NUMBER ); + if( bIsClosed ) + { + // Remove the final redundant point + NewWater.WaterPoints.Pop(); + } + else + { + // Wasn't expecting to have an unclosed shape. Our tolerances might be off, or the data was malformed. + // Either way, it shouldn't be a problem as we'll close the shape ourselves below. + // @todo: Log this for the user as an import warning + } + + NewWater.WaterName = OSMWay.Name; + if( NewWater.WaterName.IsEmpty() ) + { + NewWater.WaterName = OSMWay.Ref; + } + + NewWater.BoundsMin = BoundsMin; + NewWater.BoundsMax = BoundsMax; + + StreetMapRef.BoundsMin.X = FMath::Min( StreetMapRef.BoundsMin.X, BoundsMin.X ); + StreetMapRef.BoundsMin.Y = FMath::Min( StreetMapRef.BoundsMin.Y, BoundsMin.Y ); + StreetMapRef.BoundsMax.X = FMath::Max( StreetMapRef.BoundsMax.X, BoundsMax.X ); + StreetMapRef.BoundsMax.Y = FMath::Max( StreetMapRef.BoundsMax.Y, BoundsMax.Y ); + + return true; + } + else + { + // NOTE: Skipped adding Water for way because it has less than 3 points + // @todo: Log this for the user as an import warning + } + } + + return false; + }; + + + // Adds a Park to the street map using the OpenStreetMap data, flattening the road's coordinates into our map's space + auto AddParkForWay = [ConvertLatLongToMetersRelative, OSMToCentimetersScaleFactor]( + const FOSMFile& OSMFile, + UStreetMap& StreetMapRef, + const FOSMFile::FOSMWayInfo& OSMWay ) -> bool + { + if( OSMWay.WayType == FOSMFile::EOSMWayType::Park ) + { + // Require at least three points so that we don't have degenerate polygon! + if( OSMWay.Nodes.Num() > 2 ) + { + // Create a Park for this way + FStreetMapPark& NewPark = *new( StreetMapRef.Parks )FStreetMapPark(); + + FVector2D BoundsMin( TNumericLimits::Max(), TNumericLimits::Max() ); + FVector2D BoundsMax( TNumericLimits::Lowest(), TNumericLimits::Lowest() ); + + NewPark.ParkPoints.AddUninitialized( OSMWay.Nodes.Num() ); + int32 CurParkPoint = 0; + + for( const FOSMFile::FOSMNodeInfo* OSMNodePtr : OSMWay.Nodes ) + { + const FOSMFile::FOSMNodeInfo& OSMNode = *OSMNodePtr; + + // Transform all points relative to the center of the latitude/longitude bounds, so that + // we get as much precision as possible. + const double RelativeToLatitude = OSMFile.AverageLatitude; + const double RelativeToLongitude = OSMFile.AverageLongitude; + const FVector2D NodePos = ConvertLatLongToMetersRelative( + OSMNode.Latitude, + OSMNode.Longitude, + RelativeToLatitude, + RelativeToLongitude ) * OSMToCentimetersScaleFactor; + + // Update bounding box + { + if( NodePos.X < BoundsMin.X ) + { + BoundsMin.X = NodePos.X; + } + if( NodePos.Y < BoundsMin.Y ) + { + BoundsMin.Y = NodePos.Y; + } + if( NodePos.X > BoundsMax.X ) + { + BoundsMax.X = NodePos.X; + } + if( NodePos.Y > BoundsMax.Y ) + { + BoundsMax.Y = NodePos.Y; + } + } + + // Fill in the points + NewPark.ParkPoints[ CurParkPoint++ ] = NodePos; + } + + // Make sure the Park ended up with a closed polygon, then remove the final (redundant) point + const bool bIsClosed = NewPark.ParkPoints[ 0 ].Equals( NewPark.ParkPoints[ NewPark.ParkPoints.Num() - 1 ], KINDA_SMALL_NUMBER ); + if( bIsClosed ) + { + // Remove the final redundant point + NewPark.ParkPoints.Pop(); + } + else + { + // Wasn't expecting to have an unclosed shape. Our tolerances might be off, or the data was malformed. + // Either way, it shouldn't be a problem as we'll close the shape ourselves below. + // @todo: Log this for the user as an import warning + } + + NewPark.ParkName = OSMWay.Name; + if( NewPark.ParkName.IsEmpty() ) + { + NewPark.ParkName = OSMWay.Ref; + } + + NewPark.BoundsMin = BoundsMin; + NewPark.BoundsMax = BoundsMax; + + StreetMapRef.BoundsMin.X = FMath::Min( StreetMapRef.BoundsMin.X, BoundsMin.X ); + StreetMapRef.BoundsMin.Y = FMath::Min( StreetMapRef.BoundsMin.Y, BoundsMin.Y ); + StreetMapRef.BoundsMax.X = FMath::Max( StreetMapRef.BoundsMax.X, BoundsMax.X ); + StreetMapRef.BoundsMax.Y = FMath::Max( StreetMapRef.BoundsMax.Y, BoundsMax.Y ); + + return true; + } + else + { + // NOTE: Skipped adding Park for way because it has less than 3 points + // @todo: Log this for the user as an import warning + } + } + + return false; + }; + + // Load up the OSM file. It's in XML format. @@ -349,6 +550,26 @@ bool UStreetMapFactory::LoadFromOpenStreetMapXMLFile( UStreetMap* StreetMap, FSt // ... } } + + + + else if( OSMWay->WayType == FOSMFile::EOSMWayType::Water ) + { + if( AddWaterForWay( OSMFile, *StreetMap, *OSMWay ) ) + { + // ... + } + } + + else if( OSMWay->WayType == FOSMFile::EOSMWayType::Park ) + { + if( AddParkForWay( OSMFile, *StreetMap, *OSMWay ) ) + { + // ... + } + } + + else { int32 RoadIndex = INDEX_NONE; diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/Public/StreetMap.h b/Plugins/StreetMap/Source/StreetMapRuntime/Public/StreetMap.h index bc2ec3d..290a249 100644 --- a/Plugins/StreetMap/Source/StreetMapRuntime/Public/StreetMap.h +++ b/Plugins/StreetMap/Source/StreetMapRuntime/Public/StreetMap.h @@ -99,20 +99,32 @@ public: /** 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.05f, 0.75f, 0.05f), + StreetColor(0.93f, 0.90f, 0.87f), MajorRoadThickness(1000.0f), - MajorRoadColor(0.15f, 0.85f, 0.15f), + MajorRoadColor(0.92f, 0.91f, 0.88f), HighwayThickness(1400.0f), - HighwayColor(FLinearColor(0.25f, 0.95f, 0.25f)), + HighwayColor(FLinearColor(0.91f, 0.90f, 0.87f)), BuildingBorderThickness(20.0f), BuildingBorderLinearColor(0.85f, 0.85f, 0.85f), - BuildingBorderZ(10.0f) + BuildingBorderZ(10.0f), + ParkColor(0.00f, 0.55f, 0.27f), + WaterColor(0.26f, 0.75f, 0.98f) { } @@ -312,6 +324,57 @@ struct STREETMAPRUNTIME_API FStreetMapBuilding }; +// 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; + + /** 2D bounds (max) of this water's points */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + FVector2D BoundsMax; +}; + +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; + + /** 2D bounds (max) of this water's points */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + FVector2D BoundsMax; +}; + + /** A loaded street map */ UCLASS(Blueprintable, BlueprintType) class STREETMAPRUNTIME_API UStreetMap : public UObject @@ -361,6 +424,32 @@ public: { 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 @@ -397,6 +486,14 @@ protected: /** 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) diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapComponent.cpp b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapComponent.cpp index 680dfaa..b3bd517 100644 --- a/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapComponent.cpp +++ b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapComponent.cpp @@ -232,6 +232,10 @@ void UStreetMapComponent::GenerateMesh() const float BuildingBorderZ = MeshBuildSettings.BuildingBorderZ; const FColor BuildingBorderColor( BuildingBorderLinearColor.ToFColor( false ) ); const FColor BuildingFillColor( FLinearColor( BuildingBorderLinearColor * 0.33f ).CopyWithNewOpacity( 1.0f ).ToFColor( false ) ); + + const FColor WaterColor = MeshBuildSettings.WaterColor.ToFColor( false ); + const FColor ParkColor = MeshBuildSettings.ParkColor.ToFColor( false ); + ///////////////////////////////////////////////////////// @@ -247,6 +251,8 @@ void UStreetMapComponent::GenerateMesh() const auto& Roads = StreetMap->GetRoads(); const auto& Nodes = StreetMap->GetNodes(); const auto& Buildings = StreetMap->GetBuildings(); + const auto& Waters = StreetMap->GetWaters(); + const auto& Parks = StreetMap->GetParks(); for( const auto& Road : Roads ) { @@ -430,6 +436,99 @@ void UStreetMapComponent::GenerateMesh() } } } + + // Vinfamy added + + TArray< int32 > TempIndicesW; + TArray< int32 > TriangulatedVertexIndicesW; + TArray< FVector > TempPointsW; + for( int32 WaterIndex = 0; WaterIndex < Waters.Num(); ++WaterIndex ) + { + const auto& Water = Waters[ WaterIndex ]; + + // Water mesh (or filled area, if the Water has no height) + + // Triangulate this Water + // @todo: Performance: Triangulating lots of Water polygons is quite slow. We could easily do this + // as part of the import process and store tessellated geometry instead of doing this at load time. + bool WindsClockwise; + if( FPolygonTools::TriangulatePolygon( Water.WaterPoints, TempIndicesW, /* Out */ TriangulatedVertexIndicesW, /* Out */ WindsClockwise ) ) + { + // @todo: Performance: We could preprocess the Water shapes so that the points always wind + // in a consistent direction, so we can skip determining the winding above. + + const int32 FirstTopVertexIndex = this->Vertices.Num(); + + // calculate fill Z for Waters + // either use the defined height or extrapolate from Water level count + float WaterFillZ = 0.0f; + + // Top of Water + { + TempPointsW.SetNum( Water.WaterPoints.Num(), false ); + for( int32 PointIndex = 0; PointIndex < Water.WaterPoints.Num(); ++PointIndex ) + { + TempPointsW[ PointIndex ] = FVector( Water.WaterPoints[ ( Water.WaterPoints.Num() - PointIndex ) - 1 ], WaterFillZ ); + } + AddTriangles( TempPointsW, TriangulatedVertexIndicesW, FVector::ForwardVector, FVector::UpVector, WaterColor, MeshBoundingBox ); + } + + } + else + { + // @todo: Triangulation failed for some reason, possibly due to degenerate polygons. We can + // probably improve the algorithm to avoid this happening. + } + + // Water border + } + + + + + TArray< int32 > TempIndicesP; + TArray< int32 > TriangulatedVertexIndicesP; + TArray< FVector > TempPointsP; + for( int32 ParkIndex = 0; ParkIndex < Parks.Num(); ++ParkIndex ) + { + const auto& Park = Parks[ ParkIndex ]; + + // Park mesh (or filled area, if the Park has no height) + + // Triangulate this Park + // @todo: Performance: Triangulating lots of Park polygons is quite slow. We could easily do this + // as part of the import process and store tessellated geometry instead of doing this at load time. + bool WindsClockwise; + if( FPolygonTools::TriangulatePolygon( Park.ParkPoints, TempIndicesP, /* Out */ TriangulatedVertexIndicesP, /* Out */ WindsClockwise ) ) + { + // @todo: Performance: We could preprocess the Park shapes so that the points always wind + // in a consistent direction, so we can skip determining the winding above. + + const int32 FirstTopVertexIndex = this->Vertices.Num(); + + // calculate fill Z for Parks + // either use the defined height or extrapolate from Park level count + float ParkFillZ = 0.0f; + + // Top of Park + { + TempPointsP.SetNum( Park.ParkPoints.Num(), false ); + for( int32 PointIndex = 0; PointIndex < Park.ParkPoints.Num(); ++PointIndex ) + { + TempPointsP[ PointIndex ] = FVector( Park.ParkPoints[ ( Park.ParkPoints.Num() - PointIndex ) - 1 ], ParkFillZ ); + } + AddTriangles( TempPointsP, TriangulatedVertexIndicesP, FVector::ForwardVector, FVector::UpVector, ParkColor, MeshBoundingBox ); + } + + } + else + { + // @todo: Triangulation failed for some reason, possibly due to degenerate polygons. We can + // probably improve the algorithm to avoid this happening. + } + + // Park border + } CachedLocalBounds = MeshBoundingBox; }