├── .gitignore ├── Content ├── Icons │ ├── sm_actor_icon_128.png │ ├── sm_actor_icon_32.png │ ├── sm_asset_icon_128.png │ ├── sm_asset_icon_32.png │ ├── sm_component_icon_128.png │ └── sm_component_icon_32.png └── StreetMapDefaultMaterial.uasset ├── Docs ├── UE4OSMActor.png ├── UE4OSMBrooklyn.png ├── UE4OSMExport.png ├── UE4OSMManhattan.png └── UE4OSMRaleigh.png ├── LICENSE.txt ├── README.md ├── Resources └── Icon128.png ├── Source ├── StreetMapImporting │ ├── Private │ │ ├── OSMFile.cpp │ │ ├── StreetMapActorFactory.cpp │ │ ├── StreetMapAssetTypeActions.cpp │ │ ├── StreetMapComponentDetails.cpp │ │ ├── StreetMapFactory.cpp │ │ ├── StreetMapImporting.cpp │ │ ├── StreetMapReimportFactory.cpp │ │ └── StreetMapStyle.cpp │ ├── Public │ │ ├── OSMFile.h │ │ ├── StreetMapActorFactory.h │ │ ├── StreetMapAssetTypeActions.h │ │ ├── StreetMapComponentDetails.h │ │ ├── StreetMapFactory.h │ │ ├── StreetMapImporting.h │ │ ├── StreetMapReimportFactory.h │ │ └── StreetMapStyle.h │ └── StreetMapImporting.Build.cs └── StreetMapRuntime │ ├── Private │ ├── PolygonTools.cpp │ ├── StreetMap.cpp │ ├── StreetMapActor.cpp │ ├── StreetMapComponent.cpp │ ├── StreetMapRuntime.cpp │ └── StreetMapSceneProxy.cpp │ ├── Public │ ├── PolygonTools.h │ ├── StreetMap.h │ ├── StreetMapActor.h │ ├── StreetMapComponent.h │ ├── StreetMapRuntime.h │ └── StreetMapSceneProxy.h │ └── StreetMapRuntime.Build.cs └── StreetMap.uplugin /.gitignore: -------------------------------------------------------------------------------- 1 | Binaries/ 2 | Intermediate/ 3 | -------------------------------------------------------------------------------- /Content/Icons/sm_actor_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GameInstitute/StreetMap/ed1178521001027af5e5c0943bffa2fdc2f05d50/Content/Icons/sm_actor_icon_128.png -------------------------------------------------------------------------------- /Content/Icons/sm_actor_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GameInstitute/StreetMap/ed1178521001027af5e5c0943bffa2fdc2f05d50/Content/Icons/sm_actor_icon_32.png -------------------------------------------------------------------------------- /Content/Icons/sm_asset_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GameInstitute/StreetMap/ed1178521001027af5e5c0943bffa2fdc2f05d50/Content/Icons/sm_asset_icon_128.png -------------------------------------------------------------------------------- /Content/Icons/sm_asset_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GameInstitute/StreetMap/ed1178521001027af5e5c0943bffa2fdc2f05d50/Content/Icons/sm_asset_icon_32.png -------------------------------------------------------------------------------- /Content/Icons/sm_component_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GameInstitute/StreetMap/ed1178521001027af5e5c0943bffa2fdc2f05d50/Content/Icons/sm_component_icon_128.png -------------------------------------------------------------------------------- /Content/Icons/sm_component_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GameInstitute/StreetMap/ed1178521001027af5e5c0943bffa2fdc2f05d50/Content/Icons/sm_component_icon_32.png -------------------------------------------------------------------------------- /Content/StreetMapDefaultMaterial.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GameInstitute/StreetMap/ed1178521001027af5e5c0943bffa2fdc2f05d50/Content/StreetMapDefaultMaterial.uasset -------------------------------------------------------------------------------- /Docs/UE4OSMActor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GameInstitute/StreetMap/ed1178521001027af5e5c0943bffa2fdc2f05d50/Docs/UE4OSMActor.png -------------------------------------------------------------------------------- /Docs/UE4OSMBrooklyn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GameInstitute/StreetMap/ed1178521001027af5e5c0943bffa2fdc2f05d50/Docs/UE4OSMBrooklyn.png -------------------------------------------------------------------------------- /Docs/UE4OSMExport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GameInstitute/StreetMap/ed1178521001027af5e5c0943bffa2fdc2f05d50/Docs/UE4OSMExport.png -------------------------------------------------------------------------------- /Docs/UE4OSMManhattan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GameInstitute/StreetMap/ed1178521001027af5e5c0943bffa2fdc2f05d50/Docs/UE4OSMManhattan.png -------------------------------------------------------------------------------- /Docs/UE4OSMRaleigh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GameInstitute/StreetMap/ed1178521001027af5e5c0943bffa2fdc2f05d50/Docs/UE4OSMRaleigh.png -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Mike Fricker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Street Map Plugin for UE4 2 | 3 | This plugin allows you to import **OpenStreetMap** XML data into your **Unreal Engine 4** project as a new StreetMap asset type. You can use the example **Street Map Component** to render streets and buildings. 4 | 5 | ![UE4OSMBrooklyn](Docs/UE4OSMBrooklyn.png) 6 | 7 | ![UE4OSMRaleigh](Docs/UE4OSMRaleigh.png) 8 | 9 | Have fun!! --[Mike](http://twitter.com/mike_fricker) 10 | 11 | *(Note: This plugin is a just a fun weekend project and not officially supported by Epic.)* 12 | 13 | 14 | ## Quick Start 15 | 16 | It's easy to get up and running: 17 | 18 | * Download the StreetMap plugin source from this page (click **Clone or download** -> **Download ZIP**). 19 | 20 | * Unzip the files into a new **StreetMap** sub-folder under your project's **Plugins** folder. It should end up looking like *"/MyProject/Plugins/StreetMap/"* 21 | 22 | * **Rebuild** your C++ project. The new plugin will be compiled too! 23 | 24 | * Load the editor. You can now drag and drop **OpenStreetMap XML files** (.osm) into Content Browser to import map data! 25 | 26 | * Drag and Drop imported **Street Map Data Asset** into the viewport and a **Street Map Actor** will be automatically generated. You should now see your streets and buildings in the 3D viewport. 27 | 28 | ![UE4OSMManhattan](Docs/UE4OSMActor.png) 29 | 30 | 31 | If the rebuild was successful but you don't see the new features, double check that the **Street Map** plugin is enabled by clicking the **Settings** toolbar button, then click **Plugins**. Locate the **Street Map** plugin and make sure **Enabled** is checked. 32 | 33 | If you're new to plugins in UE4, you can find lots of information [right here](https://wiki.unrealengine.com/An_Introduction_to_UE4_Plugins). 34 | 35 | 36 | ## Getting OpenStreetMap Data 37 | 38 | **Legal:** OpenStreetMap data is licensed under the [ODC Open Database License (ODbL)](http://opendatacommons.org/licenses/odbl/). If you use this data in your project, *make sure you understand and comply with the terms of that license* e.g. lookup the [Legal FAQ](https://wiki.openstreetmap.org/wiki/Legal_FAQ). 39 | 40 | ![UE4OSMExport](Docs/UE4OSMExport.png) 41 | 42 | Here's how to get data for a location you're interested in: 43 | 44 | **For larger areas (more than a neighborhood or small town) you should use [Mapzen Extracts](https://mapzen.com/data/metro-extracts).** 45 | 46 | * Go to [OpenStreetMap.org](http://www.openstreetmap.org) and use the search feature to navigate to your *favorite location on Earth*. 47 | 48 | * Click the **Export** button on navigation bar at the top of the page to go into *Export Mode*. 49 | 50 | * Scroll and zoom such that the region you want to export fills your browser window. Start with something reasonably small so that the export and download will complete quickly. Try zooming in on a small town or a city block. 51 | 52 | * When you're ready, click **Export** on the left. OpenStreetMap will **generate an XML file** and initiate the download soon. 53 | 54 | If you want to fine tune the rectangle that's saved, you can click "Manually select a different area" in the OpenStreetMap window, and adjust a rectangle over the map that will be exported. 55 | 56 | Keep in mind that many locations may have limited information about building geometry. In particular, the heights of buildings may be missing or incorrect in many cities. 57 | 58 | If you receive an error message after clicking **Export**, OpenStreetMap may be too busy to accomodate the request. Try clicking **Overpass API** or check one of the other sources. Make sure the downloaded file has the extension ".osm", as this is what the plugin will be expecting. You can rename the downloaded file as needed. 59 | 60 | Of course, there are many other places you can find raw OpenStreetMap XML data on the web also, but keep in mind the plugin has only been tested with files exported directly from OpenStreetMap so far. 61 | 62 | ## Editing OpenStreetMap 63 | 64 | **Attention:** OSM covers the real world and includes only fact based knowledge. If you like to build up an fictional map, you can use the [JOSM offline editor](https://wiki.openstreetmap.org/wiki/JOSM), to create an local XML file, which you don't upload(!) to the project. 65 | 66 | You can easily contribute back to OSM, for example to improve your hometown. Just signup at www.openstreetmap.org and click at the edit tab. The online iD editor allows you to trace aerial imagery and to add POIs easily. To learn more details, just look over here: 67 | * http://learnosm.org 68 | * https://wiki.openstreetmap.org/wiki/Video_tutorials 69 | 70 | Please be aware, that the project community (the inhabitants!) is the essential part. Thus it's wise to [get in touch](https://wiki.openstreetmap.org/wiki/Contact_channels) with mappers close to you, to get more tips on local tagging, or unwritten rules. Happy mapping! 71 | 72 | ## Plugin Details 73 | 74 | ### Street Map Assets 75 | 76 | When you **import an OSM** file, the plugin will create a new **Street Map asset** to represent the map data in UE4. You can assign these to **Street Map Components**, or directly interact with the map data in C++ code. 77 | 78 | Roads are imported with *full connectivity data*! This means you can design your own navigation algorithms pretty easily. 79 | 80 | OpenStreetMap positional data is stored in *geographic coordinates* (latitude and longitude), but UE4 doesn't support that coordinate system natively. That is, we can't easily deal with spherical worlds in UE4 currently. So during the import process, we project all map coordinates to a flat 2D plane. 81 | 82 | The OSM data is imported at double precision, but we truncate everything to single precision floating point before saving our UE4 street map asset. If you're planning to work with enormous map data sets at runtime, you'll need to modify this. 83 | 84 | 85 | ### Street Map Components 86 | 87 | An example implementation of a **Street Map Component** is included that generates a renderable mesh from loaded street and building data. This is a very simple component that you can use as a starting point. 88 | 89 | The example implementation creates a custom primitive component mesh instead of a traditional static mesh. The reason for this was to allow for more flexible rendering behavior of city streets and buildings, or even dynamic aspects. 90 | 91 | All mesh data is generated at load time from the cartographic data in the map asset, including colorized road strips and simple building meshes with triangulated roof polygons. No spline interpolation is performed on the roads. 92 | 93 | The generated street map mesh has vertex colors and normals, and you can assign a custom material to it. If you want to use the built-in colors, make sure your material multiplies Vertex Color with Base Color. The mesh is setup to render very efficiently in a single draw call. Roads are represented as simple quad strips (no tesselation). Texture coordinates are not supported yet. 94 | 95 | There are various "tweakable" variables to control how the renderable mesh is generated. You can find these at the top of the *UStreetMapComponent::GenerateMesh()* function body. 96 | 97 | *(Street Map Component also serves as a straightforward example of how to write your own primitive components in UE4.)* 98 | 99 | 100 | ### OSM Files 101 | 102 | While importing OpenStreetMap XML files, we store all of the data that's interesting to us in an **FOSMFile** data structure in memory. This contains data that is very close to raw representation in the XML file. Coordinates are stored as geographic positions in double precision floating point. 103 | 104 | After loading everything into **FOSMFile**, we digest the data and convert it to a format that can be serialized to disk and loaded efficiently at runtime (the **UStreetMap** class.) 105 | 106 | Depending on your use case, you may want to heavily customize the **UStreetMap** class to store data that is more close to the raw representation of the map. For example, if you wanted to perform large-scale GPS navigation, you'd want higher precision data available at runtime. 107 | 108 | 109 | ### Known Issues 110 | 111 | There are various loose ends. 112 | 113 | * Importing files larger than 2GB will crash. This is a current UE4 limitation. 114 | 115 | * Some variants of generated OSM XML files won't load correctly. For example, single-quote delimeters around values are not supported yet. 116 | 117 | * Street Map APIs should be easy to use from C++, but Blueprint support hasn't been a focus for this plugin. Many methods are inlined for high performance. Blueprint scripting hooks could be added if there is demand for it, though. 118 | 119 | * As mentioned above, coordinates are truncated to single-precision which won't be sufficient for advanced use cases. Similarly, geographic coordinates are not retained beyond the initial import phase. All coordinates are projected onto a plane and transposed to be relative to the center of the map's bounding rectangle. 120 | 121 | * Runtime data structures are setup to support pathfinding (see **FStreetMapNode** member functions), but no example implementation of a GPS algorithm is included yet. 122 | 123 | * Generated mesh data is currently very simple and lacks collision information, navigation mesh support and has no texture coordinates. This is really just designed to serve as an example. For more rendering flexibility and faster performance, the importer could be changed to generate actual Static Mesh assets for map geometry. 124 | 125 | * You can search for **@todo** in the plugin source code for other minor improvements that could be made. 126 | 127 | 128 | ### Compatibility 129 | 130 | This plug-in requires Visual Studio and either a C++ code project or the full Unreal Engine 4 source code from GitHub. If you are new to programming in UE4, please see the official [Programming Guide](https://docs.unrealengine.com/latest/INT/Programming/index.html)! 131 | 132 | The Street Map plugin should work on all platforms that UE4 supports, but the latest version has not been tested on every platform. 133 | 134 | We'll try to keep the source code up to date so that it works with new versions Unreal Engine as they are released. 135 | 136 | 137 | ## Support 138 | 139 | I'm not planning to actively update the plugin on a regular basis, but if any critical fixes are contributed, I'll certainly try to review and integrate them. 140 | 141 | For bugs, please [file an issue](https://github.com/ue4plugins/StreetMap/issues), submit a [pull request](https://github.com/ue4plugins/StreetMap/pulls?q=is%3Aopen+is%3Apr) or catch me [on Twitter](http://twitter.com/mike_fricker). 142 | 143 | Finally, a **big thanks** to the [OpenStreetMap Foundation](http://wiki.osmfoundation.org/wiki/Main_Page) and the fantastic community who contribute map data and maintain the database. 144 | 145 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GameInstitute/StreetMap/ed1178521001027af5e5c0943bffa2fdc2f05d50/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/StreetMapImporting/Private/OSMFile.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #include "OSMFile.h" 4 | #include "StreetMapImporting.h" 5 | 6 | 7 | FOSMFile::FOSMFile() 8 | : ParsingState( ParsingState::Root ) 9 | { 10 | } 11 | 12 | 13 | FOSMFile::~FOSMFile() 14 | { 15 | // Clean up time 16 | { 17 | for( auto* Way : Ways ) 18 | { 19 | delete Way; 20 | } 21 | Ways.Empty(); 22 | 23 | for( auto HashPair : NodeMap ) 24 | { 25 | FOSMNodeInfo* NodeInfo = HashPair.Value; 26 | delete NodeInfo; 27 | } 28 | NodeMap.Empty(); 29 | } 30 | } 31 | 32 | 33 | bool FOSMFile::LoadOpenStreetMapFile( FString& OSMFilePath, const bool bIsFilePathActuallyTextBuffer, FFeedbackContext* FeedbackContext ) 34 | { 35 | const bool bShowSlowTaskDialog = true; 36 | const bool bShowCancelButton = true; 37 | 38 | FText ErrorMessage; 39 | int32 ErrorLineNumber; 40 | if( FFastXml::ParseXmlFile( 41 | this, 42 | bIsFilePathActuallyTextBuffer ? nullptr : *OSMFilePath, 43 | bIsFilePathActuallyTextBuffer ? OSMFilePath.GetCharArray().GetData() : nullptr, 44 | FeedbackContext, 45 | bShowSlowTaskDialog, 46 | bShowCancelButton, 47 | /* Out */ ErrorMessage, 48 | /* Out */ ErrorLineNumber ) ) 49 | { 50 | if( NodeMap.Num() > 0 ) 51 | { 52 | AverageLatitude /= NodeMap.Num(); 53 | AverageLongitude /= NodeMap.Num(); 54 | } 55 | 56 | return true; 57 | } 58 | 59 | if( FeedbackContext != nullptr ) 60 | { 61 | FeedbackContext->Logf( 62 | ELogVerbosity::Error, 63 | TEXT( "Failed to load OpenStreetMap XML file ('%s', Line %i)" ), 64 | *ErrorMessage.ToString(), 65 | ErrorLineNumber ); 66 | } 67 | 68 | return false; 69 | } 70 | 71 | 72 | bool FOSMFile::ProcessXmlDeclaration( const TCHAR* ElementData, int32 XmlFileLineNumber ) 73 | { 74 | // Don't care about XML declaration 75 | return true; 76 | } 77 | 78 | 79 | bool FOSMFile::ProcessComment( const TCHAR* Comment ) 80 | { 81 | // Don't care about comments 82 | return true; 83 | } 84 | 85 | 86 | bool FOSMFile::ProcessElement( const TCHAR* ElementName, const TCHAR* ElementData, int32 XmlFileLineNumber ) 87 | { 88 | if( ParsingState == ParsingState::Root ) 89 | { 90 | if( !FCString::Stricmp( ElementName, TEXT( "node" ) ) ) 91 | { 92 | ParsingState = ParsingState::Node; 93 | CurrentNodeInfo = new FOSMNodeInfo(); 94 | CurrentNodeInfo->Latitude = 0.0; 95 | CurrentNodeInfo->Longitude = 0.0; 96 | } 97 | else if( !FCString::Stricmp( ElementName, TEXT( "way" ) ) ) 98 | { 99 | ParsingState = ParsingState::Way; 100 | CurrentWayInfo = new FOSMWayInfo(); 101 | CurrentWayInfo->Name.Empty(); 102 | CurrentWayInfo->Ref.Empty(); 103 | CurrentWayInfo->WayType = EOSMWayType::Other; 104 | CurrentWayInfo->Height = 0.0; 105 | CurrentWayInfo->bIsOneWay = false; 106 | 107 | // @todo: We're currently ignoring the "visible" tag on ways, which means that roads will always 108 | // be included in our data set. It might be nice to make this an import option. 109 | } 110 | } 111 | else if( ParsingState == ParsingState::Way ) 112 | { 113 | if( !FCString::Stricmp( ElementName, TEXT( "nd" ) ) ) 114 | { 115 | ParsingState = ParsingState::Way_NodeRef; 116 | } 117 | else if( !FCString::Stricmp( ElementName, TEXT( "tag" ) ) ) 118 | { 119 | ParsingState = ParsingState::Way_Tag; 120 | } 121 | } 122 | 123 | return true; 124 | } 125 | 126 | 127 | bool FOSMFile::ProcessAttribute( const TCHAR* AttributeName, const TCHAR* AttributeValue ) 128 | { 129 | if( ParsingState == ParsingState::Node ) 130 | { 131 | if( !FCString::Stricmp( AttributeName, TEXT( "id" ) ) ) 132 | { 133 | CurrentNodeID = FPlatformString::Atoi64( AttributeValue ); 134 | } 135 | else if( !FCString::Stricmp( AttributeName, TEXT( "lat" ) ) ) 136 | { 137 | CurrentNodeInfo->Latitude = FPlatformString::Atod( AttributeValue ); 138 | 139 | AverageLatitude += CurrentNodeInfo->Latitude; 140 | 141 | // Update minimum and maximum latitude 142 | // @todo: Performance: Instead of computing our own bounding box, we could parse the "minlat" and 143 | // "minlon" tags from the OSM file 144 | if( CurrentNodeInfo->Latitude < MinLatitude ) 145 | { 146 | MinLatitude = CurrentNodeInfo->Latitude; 147 | } 148 | if( CurrentNodeInfo->Latitude > MaxLatitude ) 149 | { 150 | MaxLatitude = CurrentNodeInfo->Latitude; 151 | } 152 | } 153 | else if( !FCString::Stricmp( AttributeName, TEXT( "lon" ) ) ) 154 | { 155 | CurrentNodeInfo->Longitude = FPlatformString::Atod( AttributeValue ); 156 | 157 | AverageLongitude += CurrentNodeInfo->Longitude; 158 | 159 | // Update minimum and maximum longitude 160 | if( CurrentNodeInfo->Longitude < MinLongitude ) 161 | { 162 | MinLongitude = CurrentNodeInfo->Longitude; 163 | } 164 | if( CurrentNodeInfo->Longitude > MaxLongitude ) 165 | { 166 | MaxLongitude = CurrentNodeInfo->Longitude; 167 | } 168 | } 169 | } 170 | else if( ParsingState == ParsingState::Way ) 171 | { 172 | // ... 173 | } 174 | else if( ParsingState == ParsingState::Way_NodeRef ) 175 | { 176 | if( !FCString::Stricmp( AttributeName, TEXT( "ref" ) ) ) 177 | { 178 | FOSMNodeInfo* ReferencedNode = NodeMap.FindRef( FPlatformString::Atoi64( AttributeValue ) ); 179 | const int NewNodeIndex = CurrentWayInfo->Nodes.Num(); 180 | CurrentWayInfo->Nodes.Add( ReferencedNode ); 181 | 182 | // Update the node with information about the way that is referencing it 183 | { 184 | FOSMWayRef NewWayRef; 185 | NewWayRef.Way = CurrentWayInfo; 186 | NewWayRef.NodeIndex = NewNodeIndex; 187 | ReferencedNode->WayRefs.Add( NewWayRef ); 188 | } 189 | } 190 | } 191 | else if( ParsingState == ParsingState::Way_Tag ) 192 | { 193 | if( !FCString::Stricmp( AttributeName, TEXT( "k" ) ) ) 194 | { 195 | CurrentWayTagKey = AttributeValue; 196 | } 197 | else if( !FCString::Stricmp( AttributeName, TEXT( "v" ) ) ) 198 | { 199 | if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "name" ) ) ) 200 | { 201 | CurrentWayInfo->Name = AttributeValue; 202 | } 203 | else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "ref" ) ) ) 204 | { 205 | CurrentWayInfo->Ref = AttributeValue; 206 | } 207 | else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "highway" ) ) ) 208 | { 209 | EOSMWayType WayType = EOSMWayType::Other; 210 | 211 | if( !FCString::Stricmp( AttributeValue, TEXT( "motorway" ) ) ) 212 | { 213 | WayType = EOSMWayType::Motorway; 214 | } 215 | else if( !FCString::Stricmp( AttributeValue, TEXT( "motorway_link" ) ) ) 216 | { 217 | WayType = EOSMWayType::Motorway_Link; 218 | } 219 | else if( !FCString::Stricmp( AttributeValue, TEXT( "trunk" ) ) ) 220 | { 221 | WayType = EOSMWayType::Trunk; 222 | } 223 | else if( !FCString::Stricmp( AttributeValue, TEXT( "trunk_link" ) ) ) 224 | { 225 | WayType = EOSMWayType::Trunk_Link; 226 | } 227 | else if( !FCString::Stricmp( AttributeValue, TEXT( "primary" ) ) ) 228 | { 229 | WayType = EOSMWayType::Primary; 230 | } 231 | else if( !FCString::Stricmp( AttributeValue, TEXT( "primary_link" ) ) ) 232 | { 233 | WayType = EOSMWayType::Primary_Link; 234 | } 235 | else if( !FCString::Stricmp( AttributeValue, TEXT( "secondary" ) ) ) 236 | { 237 | WayType = EOSMWayType::Secondary; 238 | } 239 | else if( !FCString::Stricmp( AttributeValue, TEXT( "secondary_link" ) ) ) 240 | { 241 | WayType = EOSMWayType::Secondary_Link; 242 | } 243 | else if( !FCString::Stricmp( AttributeValue, TEXT( "tertiary" ) ) ) 244 | { 245 | WayType = EOSMWayType::Tertiary; 246 | } 247 | else if( !FCString::Stricmp( AttributeValue, TEXT( "tertiary_link" ) ) ) 248 | { 249 | WayType = EOSMWayType::Tertiary_Link; 250 | } 251 | else if( !FCString::Stricmp( AttributeValue, TEXT( "residential" ) ) ) 252 | { 253 | WayType = EOSMWayType::Residential; 254 | } 255 | else if( !FCString::Stricmp( AttributeValue, TEXT( "service" ) ) ) 256 | { 257 | WayType = EOSMWayType::Service; 258 | } 259 | else if( !FCString::Stricmp( AttributeValue, TEXT( "unclassified" ) ) ) 260 | { 261 | WayType = EOSMWayType::Unclassified; 262 | } 263 | else if( !FCString::Stricmp( AttributeValue, TEXT( "living_street" ) ) ) 264 | { 265 | WayType = EOSMWayType::Living_Street; 266 | } 267 | else if( !FCString::Stricmp( AttributeValue, TEXT( "pedestrian" ) ) ) 268 | { 269 | WayType = EOSMWayType::Pedestrian; 270 | } 271 | else if( !FCString::Stricmp( AttributeValue, TEXT( "track" ) ) ) 272 | { 273 | WayType = EOSMWayType::Track; 274 | } 275 | else if( !FCString::Stricmp( AttributeValue, TEXT( "bus_guideway" ) ) ) 276 | { 277 | WayType = EOSMWayType::Bus_Guideway; 278 | } 279 | else if( !FCString::Stricmp( AttributeValue, TEXT( "raceway" ) ) ) 280 | { 281 | WayType = EOSMWayType::Raceway; 282 | } 283 | else if( !FCString::Stricmp( AttributeValue, TEXT( "road" ) ) ) 284 | { 285 | WayType = EOSMWayType::Road; 286 | } 287 | else if( !FCString::Stricmp( AttributeValue, TEXT( "footway" ) ) ) 288 | { 289 | WayType = EOSMWayType::Footway; 290 | } 291 | else if( !FCString::Stricmp( AttributeValue, TEXT( "cycleway" ) ) ) 292 | { 293 | WayType = EOSMWayType::Cycleway; 294 | } 295 | else if( !FCString::Stricmp( AttributeValue, TEXT( "bridleway" ) ) ) 296 | { 297 | WayType = EOSMWayType::Bridleway; 298 | } 299 | else if( !FCString::Stricmp( AttributeValue, TEXT( "steps" ) ) ) 300 | { 301 | WayType = EOSMWayType::Steps; 302 | } 303 | else if( !FCString::Stricmp( AttributeValue, TEXT( "path" ) ) ) 304 | { 305 | WayType = EOSMWayType::Path; 306 | } 307 | else if( !FCString::Stricmp( AttributeValue, TEXT( "proposed" ) ) ) 308 | { 309 | WayType = EOSMWayType::Proposed; 310 | } 311 | else if( !FCString::Stricmp( AttributeValue, TEXT( "construction" ) ) ) 312 | { 313 | WayType = EOSMWayType::Construction; 314 | } 315 | else 316 | { 317 | // Other type that we don't recognize yet. See http://wiki.openstreetmap.org/wiki/Key:highway 318 | } 319 | 320 | 321 | CurrentWayInfo->WayType = WayType; 322 | } 323 | else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "building" ) ) ) 324 | { 325 | CurrentWayInfo->WayType = EOSMWayType::Building; 326 | 327 | if( !FCString::Stricmp( AttributeValue, TEXT( "yes" ) ) ) 328 | { 329 | CurrentWayInfo->WayType = EOSMWayType::Building; 330 | } 331 | else 332 | { 333 | // Other type that we don't recognize yet. See http://wiki.openstreetmap.org/wiki/Key:building 334 | } 335 | } 336 | else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "height" ) ) ) 337 | { 338 | // Check to see if there is a space character in the height value. For now, we're looking 339 | // for straight-up floating point values. 340 | if( !FString( AttributeValue ).Contains( TEXT( " " ) ) ) 341 | { 342 | // Okay, no space character. So this has got to be a floating point number. The OSM 343 | // spec says that the height values are in meters. 344 | CurrentWayInfo->Height = FPlatformString::Atod( AttributeValue ); 345 | } 346 | else 347 | { 348 | // Looks like the height value contains units of some sort. 349 | // @todo: Add support for interpreting unit strings and converting the values 350 | } 351 | } 352 | else if (!FCString::Stricmp(CurrentWayTagKey, TEXT("building:levels"))) 353 | { 354 | CurrentWayInfo->BuildingLevels = FPlatformString::Atoi(AttributeValue); 355 | } 356 | else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "oneway" ) ) ) 357 | { 358 | if( !FCString::Stricmp( AttributeValue, TEXT( "yes" ) ) ) 359 | { 360 | CurrentWayInfo->bIsOneWay = true; 361 | } 362 | else 363 | { 364 | CurrentWayInfo->bIsOneWay = false; 365 | } 366 | } 367 | } 368 | } 369 | 370 | return true; 371 | } 372 | 373 | 374 | bool FOSMFile::ProcessClose( const TCHAR* Element ) 375 | { 376 | if( ParsingState == ParsingState::Node ) 377 | { 378 | NodeMap.Add( CurrentNodeID, CurrentNodeInfo ); 379 | CurrentNodeID = 0; 380 | CurrentNodeInfo = nullptr; 381 | 382 | ParsingState = ParsingState::Root; 383 | } 384 | else if( ParsingState == ParsingState::Way ) 385 | { 386 | Ways.Add( CurrentWayInfo ); 387 | CurrentWayInfo = nullptr; 388 | 389 | ParsingState = ParsingState::Root; 390 | } 391 | else if( ParsingState == ParsingState::Way_NodeRef ) 392 | { 393 | ParsingState = ParsingState::Way; 394 | } 395 | else if( ParsingState == ParsingState::Way_Tag ) 396 | { 397 | CurrentWayTagKey = TEXT( "" ); 398 | ParsingState = ParsingState::Way; 399 | } 400 | 401 | return true; 402 | } 403 | -------------------------------------------------------------------------------- /Source/StreetMapImporting/Private/StreetMapActorFactory.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | // Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. 4 | 5 | #include "StreetMapActorFactory.h" 6 | #include "StreetMapImporting.h" 7 | #include "AssetData.h" 8 | #include "StreetMapActor.h" 9 | #include "StreetMapComponent.h" 10 | #include "StreetMap.h" 11 | 12 | 13 | ////////////////////////////////////////////////////////////////////////// 14 | // UStreetMapActorFactory 15 | 16 | UStreetMapActorFactory::UStreetMapActorFactory(const FObjectInitializer& ObjectInitializer) 17 | : Super(ObjectInitializer) 18 | { 19 | DisplayName = NSLOCTEXT("StreetMap", "StreetMapFactoryDisplayName", "Add StreetMap Actor"); 20 | NewActorClass = AStreetMapActor::StaticClass(); 21 | } 22 | 23 | void UStreetMapActorFactory::PostSpawnActor(UObject* Asset, AActor* NewActor) 24 | { 25 | Super::PostSpawnActor(Asset, NewActor); 26 | 27 | if (UStreetMap* StreetMapAsset = Cast(Asset)) 28 | { 29 | AStreetMapActor* StreetMapActor = CastChecked(NewActor); 30 | UStreetMapComponent* StreetMapComponent = StreetMapActor->GetStreetMapComponent(); 31 | StreetMapComponent->SetStreetMap(StreetMapAsset, false, true); 32 | } 33 | } 34 | 35 | void UStreetMapActorFactory::PostCreateBlueprint(UObject* Asset, AActor* CDO) 36 | { 37 | if (Asset != nullptr && CDO != nullptr) 38 | { 39 | UStreetMap* StreetMapAsset = CastChecked(Asset); 40 | AStreetMapActor* StreetMapActor = CastChecked(CDO); 41 | UStreetMapComponent* StreetMapComponent = StreetMapActor->GetStreetMapComponent(); 42 | StreetMapComponent->SetStreetMap(StreetMapAsset, true, false); 43 | } 44 | } 45 | 46 | bool UStreetMapActorFactory::CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) 47 | { 48 | return (AssetData.IsValid() && AssetData.GetClass()->IsChildOf(UStreetMap::StaticClass())); 49 | } 50 | -------------------------------------------------------------------------------- /Source/StreetMapImporting/Private/StreetMapAssetTypeActions.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #include "StreetMapAssetTypeActions.h" 4 | #include "StreetMapImporting.h" 5 | #include "StreetMap.h" 6 | #include "AssetData.h" 7 | 8 | 9 | #define LOCTEXT_NAMESPACE "StreetMapImporting" 10 | 11 | 12 | FText FStreetMapAssetTypeActions::GetName() const 13 | { 14 | return LOCTEXT("StreetMapAssetTypeActionsName", "Street Map"); 15 | } 16 | 17 | 18 | FColor FStreetMapAssetTypeActions::GetTypeColor() const 19 | { 20 | return FColor(50, 255, 120); 21 | } 22 | 23 | 24 | UClass* FStreetMapAssetTypeActions::GetSupportedClass() const 25 | { 26 | return UStreetMap::StaticClass(); 27 | } 28 | 29 | 30 | void FStreetMapAssetTypeActions::OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor) 31 | { 32 | FSimpleAssetEditor::CreateEditor( EToolkitMode::Standalone, EditWithinLevelEditor, InObjects ); 33 | } 34 | 35 | 36 | uint32 FStreetMapAssetTypeActions::GetCategories() 37 | { 38 | return EAssetTypeCategories::Misc; 39 | } 40 | 41 | 42 | FText FStreetMapAssetTypeActions::GetAssetDescription(const FAssetData& AssetData) const 43 | { 44 | return FText::GetEmpty(); 45 | } 46 | 47 | 48 | bool FStreetMapAssetTypeActions::IsImportedAsset() const 49 | { 50 | return true; 51 | } 52 | 53 | 54 | void FStreetMapAssetTypeActions::GetResolvedSourceFilePaths(const TArray& TypeAssets, TArray& OutSourceFilePaths) const 55 | { 56 | for (auto* Asset : TypeAssets) 57 | { 58 | const auto* StreetMap = CastChecked(Asset); 59 | if( !StreetMap->AssetImportData->GetFirstFilename().IsEmpty() ) 60 | { 61 | OutSourceFilePaths.Add( StreetMap->AssetImportData->GetFirstFilename() ); 62 | } 63 | } 64 | } 65 | 66 | #undef LOCTEXT_NAMESPACE 67 | -------------------------------------------------------------------------------- /Source/StreetMapImporting/Private/StreetMapComponentDetails.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #include "StreetMapComponentDetails.h" 4 | #include "StreetMapImporting.h" 5 | 6 | #include "SlateBasics.h" 7 | #include "RawMesh.h" 8 | #include "PropertyEditorModule.h" 9 | #include "DetailLayoutBuilder.h" 10 | #include "DetailCategoryBuilder.h" 11 | #include "DetailWidgetRow.h" 12 | #include "PropertyCustomizationHelpers.h" 13 | #include "IDetailsView.h" 14 | #include "IDetailCustomization.h" 15 | #include "AssetRegistryModule.h" 16 | #include "Dialogs/DlgPickAssetPath.h" 17 | #include "IDetailCustomization.h" 18 | #include "Widgets/Notifications/SNotificationList.h" 19 | #include "Framework/Notifications/NotificationManager.h" 20 | #include "Misc/AssertionMacros.h" 21 | 22 | 23 | #include "StreetMapComponent.h" 24 | 25 | 26 | #define LOCTEXT_NAMESPACE "StreetMapComponentDetails" 27 | 28 | 29 | FStreetMapComponentDetails::FStreetMapComponentDetails() : 30 | SelectedStreetMapComponent(nullptr), 31 | LastDetailBuilderPtr(nullptr) 32 | { 33 | 34 | } 35 | 36 | TSharedRef FStreetMapComponentDetails::MakeInstance() 37 | { 38 | return MakeShareable(new FStreetMapComponentDetails()); 39 | } 40 | 41 | void FStreetMapComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) 42 | { 43 | LastDetailBuilderPtr = &DetailBuilder; 44 | 45 | TArray > SelectedObjects = DetailBuilder.GetDetailsView()->GetSelectedObjects(); 46 | 47 | for (const TWeakObjectPtr& Object : SelectedObjects) 48 | { 49 | UStreetMapComponent* TempStreetMapComp = Cast(Object.Get()); 50 | if (TempStreetMapComp != nullptr && !TempStreetMapComp->IsTemplate()) 51 | { 52 | SelectedStreetMapComponent = TempStreetMapComp; 53 | break; 54 | } 55 | } 56 | 57 | 58 | if (SelectedStreetMapComponent == nullptr) 59 | { 60 | TArray> SelectedActors = DetailBuilder.GetDetailsView()->GetSelectedActors(); 61 | 62 | for (const TWeakObjectPtr& Object : SelectedObjects) 63 | { 64 | AActor* TempActor = Cast(Object.Get()); 65 | if (TempActor != nullptr && !TempActor->IsTemplate()) 66 | { 67 | UStreetMapComponent* TempStreetMapComp = TempActor->FindComponentByClass(); 68 | if (TempStreetMapComp != nullptr && !TempStreetMapComp->IsTemplate()) 69 | { 70 | SelectedStreetMapComponent = TempStreetMapComp; 71 | break; 72 | } 73 | break; 74 | } 75 | } 76 | } 77 | 78 | 79 | 80 | if (SelectedStreetMapComponent == nullptr) 81 | { 82 | return; 83 | } 84 | 85 | 86 | IDetailCategoryBuilder& StreetMapCategory = DetailBuilder.EditCategory("StreetMap", FText::GetEmpty(), ECategoryPriority::Important); 87 | StreetMapCategory.InitiallyCollapsed(false); 88 | 89 | 90 | const bool bCanRebuildMesh = HasValidMapObject(); 91 | const bool bCanClearMesh = HasValidMeshData(); 92 | const bool bCanCreateMeshAsset = HasValidMeshData(); 93 | 94 | TSharedPtr< SHorizontalBox > TempHorizontalBox; 95 | 96 | StreetMapCategory.AddCustomRow(FText::GetEmpty(), false) 97 | [ 98 | SAssignNew(TempHorizontalBox, SHorizontalBox) 99 | + SHorizontalBox::Slot() 100 | [ 101 | SNew(SButton) 102 | .ToolTipText(LOCTEXT("GenerateMesh_Tooltip", "Generate a cached mesh from raw street map data.")) 103 | .OnClicked(this, &FStreetMapComponentDetails::OnBuildMeshClicked) 104 | .IsEnabled(bCanRebuildMesh) 105 | .HAlign(HAlign_Center) 106 | [ 107 | SNew(STextBlock) 108 | .Text(bCanClearMesh ? LOCTEXT("RebuildMesh", "Rebuild Mesh") : LOCTEXT("BuildMesh", "Build Mesh")) 109 | .Font(IDetailLayoutBuilder::GetDetailFont()) 110 | ] 111 | ] 112 | ]; 113 | 114 | TempHorizontalBox->AddSlot() 115 | [ 116 | SNew(SButton) 117 | .ToolTipText(LOCTEXT("ClearMesh_Tooltip", "Clear current mesh data , in case we have a valid mesh ")) 118 | .OnClicked(this, &FStreetMapComponentDetails::OnClearMeshClicked) 119 | .IsEnabled(bCanClearMesh) 120 | [ 121 | SNew(STextBlock) 122 | .Text(LOCTEXT("ClearMesh", "Clear Mesh")) 123 | .Font(IDetailLayoutBuilder::GetDetailFont()) 124 | ] 125 | ]; 126 | 127 | 128 | StreetMapCategory.AddCustomRow(FText::GetEmpty(), false) 129 | [ 130 | SAssignNew(TempHorizontalBox, SHorizontalBox) 131 | + SHorizontalBox::Slot() 132 | [ 133 | SNew(SButton) 134 | .ToolTipText(LOCTEXT("CreateStaticMeshAsset_Tooltip", "Create a new Static Mesh Asset from selected StreetMapComponent.")) 135 | .OnClicked(this, &FStreetMapComponentDetails::OnCreateStaticMeshAssetClicked) 136 | .IsEnabled(bCanCreateMeshAsset) 137 | .HAlign(HAlign_Center) 138 | [ 139 | SNew(STextBlock) 140 | .Text(LOCTEXT("CreateStaticMeshAsset", "Create Static Mesh Asset")) 141 | .Font(IDetailLayoutBuilder::GetDetailFont()) 142 | ] 143 | ] 144 | ]; 145 | 146 | if (bCanCreateMeshAsset) 147 | { 148 | 149 | const int32 NumVertices = SelectedStreetMapComponent->GetRawMeshVertices().Num(); 150 | const FString NumVerticesToString = TEXT("Vertex Count : ") + FString::FromInt(NumVertices); 151 | 152 | const int32 NumTriangles = SelectedStreetMapComponent->GetRawMeshIndices().Num() / 3; 153 | const FString NumTrianglesToString = TEXT("Triangle Count : ") + FString::FromInt(NumTriangles); 154 | 155 | const bool bCollisionEnabled = SelectedStreetMapComponent->IsCollisionEnabled(); 156 | const FString CollisionStatusToString = bCollisionEnabled ? TEXT("Collision : ON") : TEXT("Collision : OFF"); 157 | 158 | StreetMapCategory.AddCustomRow(FText::GetEmpty(), true) 159 | [ 160 | SAssignNew(TempHorizontalBox, SHorizontalBox) 161 | + SHorizontalBox::Slot() 162 | .HAlign(HAlign_Left) 163 | .VAlign(VAlign_Center) 164 | [ 165 | SNew(STextBlock) 166 | .Font(FSlateFontInfo("Verdana", 8)) 167 | .Text(FText::FromString(NumVerticesToString)) 168 | ] 169 | + SHorizontalBox::Slot() 170 | .HAlign(HAlign_Left) 171 | .VAlign(VAlign_Center) 172 | [ 173 | SNew(STextBlock) 174 | .Font(FSlateFontInfo("Verdana", 8)) 175 | .Text(FText::FromString(NumTrianglesToString)) 176 | ] 177 | + SHorizontalBox::Slot() 178 | .HAlign(HAlign_Left) 179 | .VAlign(VAlign_Center) 180 | [ 181 | SNew(STextBlock) 182 | .Font(FSlateFontInfo("Verdana", 8)) 183 | .Text(FText::FromString(CollisionStatusToString)) 184 | ] 185 | ]; 186 | } 187 | 188 | } 189 | 190 | bool FStreetMapComponentDetails::HasValidMeshData() const 191 | { 192 | return SelectedStreetMapComponent != nullptr && SelectedStreetMapComponent->HasValidMesh(); 193 | } 194 | 195 | 196 | bool FStreetMapComponentDetails::HasValidMapObject() const 197 | { 198 | return SelectedStreetMapComponent != nullptr && SelectedStreetMapComponent->GetStreetMap() != nullptr; 199 | } 200 | 201 | FReply FStreetMapComponentDetails::OnCreateStaticMeshAssetClicked() 202 | { 203 | if (SelectedStreetMapComponent != nullptr) 204 | { 205 | FString NewNameSuggestion = SelectedStreetMapComponent->GetStreetMapAssetName(); 206 | FString PackageName = FString(TEXT("/Game/Meshes/")) + NewNameSuggestion; 207 | FString Name; 208 | FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); 209 | AssetToolsModule.Get().CreateUniqueAssetName(PackageName, TEXT(""), PackageName, Name); 210 | 211 | TSharedPtr PickAssetPathWidget = 212 | SNew(SDlgPickAssetPath) 213 | .Title(LOCTEXT("ConvertToStaticMeshPickName", "Choose New StaticMesh Location")) 214 | .DefaultAssetPath(FText::FromString(PackageName)); 215 | 216 | if (PickAssetPathWidget->ShowModal() == EAppReturnType::Ok) 217 | { 218 | // Get the full name of where we want to create the physics asset. 219 | FString UserPackageName = PickAssetPathWidget->GetFullAssetPath().ToString(); 220 | FName MeshName(*FPackageName::GetLongPackageAssetName(UserPackageName)); 221 | 222 | // Check if the user inputed a valid asset name, if they did not, give it the generated default name 223 | if (MeshName == NAME_None) 224 | { 225 | // Use the defaults that were already generated. 226 | UserPackageName = PackageName; 227 | MeshName = *Name; 228 | } 229 | 230 | // Raw mesh data we are filling in 231 | FRawMesh RawMesh; 232 | // Materials to apply to new mesh 233 | TArray MeshMaterials = SelectedStreetMapComponent->GetMaterials(); 234 | 235 | 236 | const TArray RawMeshVertices = SelectedStreetMapComponent->GetRawMeshVertices(); 237 | const TArray< uint32 > RawMeshIndices = SelectedStreetMapComponent->GetRawMeshIndices(); 238 | 239 | 240 | // Copy verts 241 | for (int32 VertIndex = 0; VertIndex < RawMeshVertices.Num();VertIndex++) 242 | { 243 | RawMesh.VertexPositions.Add(RawMeshVertices[VertIndex].Position); 244 | } 245 | 246 | // Copy 'wedge' info 247 | int32 NumIndices = RawMeshIndices.Num(); 248 | for (int32 IndexIdx = 0; IndexIdx < NumIndices; IndexIdx++) 249 | { 250 | int32 VertexIndex = RawMeshIndices[IndexIdx]; 251 | 252 | RawMesh.WedgeIndices.Add(VertexIndex); 253 | 254 | const FStreetMapVertex& StreetMapVertex = RawMeshVertices[VertexIndex]; 255 | 256 | FVector TangentX = StreetMapVertex.TangentX; 257 | FVector TangentZ = StreetMapVertex.TangentZ; 258 | FVector TangentY = (TangentX ^ TangentZ).GetSafeNormal(); 259 | 260 | RawMesh.WedgeTangentX.Add(TangentX); 261 | RawMesh.WedgeTangentY.Add(TangentY); 262 | RawMesh.WedgeTangentZ.Add(TangentZ); 263 | 264 | RawMesh.WedgeTexCoords[0].Add(StreetMapVertex.TextureCoordinate); 265 | RawMesh.WedgeColors.Add(StreetMapVertex.Color); 266 | } 267 | 268 | // copy face info 269 | int32 NumTris = NumIndices / 3; 270 | for (int32 TriIdx = 0; TriIdx < NumTris; TriIdx++) 271 | { 272 | RawMesh.FaceMaterialIndices.Add(0); 273 | RawMesh.FaceSmoothingMasks.Add(0); // Assume this is ignored as bRecomputeNormals is false 274 | } 275 | 276 | // If we got some valid data. 277 | if (RawMesh.VertexPositions.Num() > 3 && RawMesh.WedgeIndices.Num() > 3) 278 | { 279 | // Then find/create it. 280 | UPackage* Package = CreatePackage(NULL, *UserPackageName); 281 | check(Package); 282 | 283 | // Create StaticMesh object 284 | UStaticMesh* StaticMesh = NewObject(Package, MeshName, RF_Public | RF_Standalone); 285 | StaticMesh->InitResources(); 286 | 287 | StaticMesh->LightingGuid = FGuid::NewGuid(); 288 | 289 | // Add source to new StaticMesh 290 | FStaticMeshSourceModel* SrcModel = new (StaticMesh->SourceModels) FStaticMeshSourceModel(); 291 | SrcModel->BuildSettings.bRecomputeNormals = false; 292 | SrcModel->BuildSettings.bRecomputeTangents = false; 293 | SrcModel->BuildSettings.bRemoveDegenerates = false; 294 | SrcModel->BuildSettings.bUseHighPrecisionTangentBasis = false; 295 | SrcModel->BuildSettings.bUseFullPrecisionUVs = false; 296 | SrcModel->BuildSettings.bGenerateLightmapUVs = true; 297 | SrcModel->BuildSettings.SrcLightmapIndex = 0; 298 | SrcModel->BuildSettings.DstLightmapIndex = 1; 299 | SrcModel->RawMeshBulkData->SaveRawMesh(RawMesh); 300 | 301 | // Copy materials to new mesh 302 | for (UMaterialInterface* Material : MeshMaterials) 303 | { 304 | StaticMesh->StaticMaterials.Add(FStaticMaterial(Material)); 305 | } 306 | 307 | //Set the Imported version before calling the build 308 | StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion; 309 | 310 | // Build mesh from source 311 | StaticMesh->Build(/** bSilent =*/ false); 312 | StaticMesh->PostEditChange(); 313 | 314 | StaticMesh->MarkPackageDirty(); 315 | 316 | // Notify asset registry of new asset 317 | FAssetRegistryModule::AssetCreated(StaticMesh); 318 | 319 | 320 | // Display notification so users can quickly access the mesh 321 | if (GIsEditor) 322 | { 323 | FNotificationInfo Info(FText::Format(LOCTEXT("StreetMapMeshConverted", "Successfully Converted Mesh"), FText::FromString(StaticMesh->GetName()))); 324 | Info.ExpireDuration = 8.0f; 325 | Info.bUseLargeFont = false; 326 | Info.Hyperlink = FSimpleDelegate::CreateLambda([=]() { FAssetEditorManager::Get().OpenEditorForAssets(TArray({ StaticMesh })); }); 327 | Info.HyperlinkText = FText::Format(LOCTEXT("OpenNewAnimationHyperlink", "Open {0}"), FText::FromString(StaticMesh->GetName())); 328 | TSharedPtr Notification = FSlateNotificationManager::Get().AddNotification(Info); 329 | if (Notification.IsValid()) 330 | { 331 | Notification->SetCompletionState(SNotificationItem::CS_Success); 332 | } 333 | } 334 | } 335 | } 336 | } 337 | 338 | return FReply::Handled(); 339 | } 340 | 341 | FReply FStreetMapComponentDetails::OnBuildMeshClicked() 342 | { 343 | 344 | if(SelectedStreetMapComponent != nullptr) 345 | { 346 | // 347 | SelectedStreetMapComponent->BuildMesh(); 348 | 349 | // regenerates details panel layouts , to take in consideration new changes. 350 | RefreshDetails(); 351 | } 352 | 353 | return FReply::Handled(); 354 | } 355 | 356 | FReply FStreetMapComponentDetails::OnClearMeshClicked() 357 | { 358 | if (SelectedStreetMapComponent != nullptr) 359 | { 360 | // 361 | SelectedStreetMapComponent->InvalidateMesh(); 362 | 363 | // regenerates details panel layouts , to take in consideration new changes. 364 | RefreshDetails(); 365 | } 366 | 367 | return FReply::Handled(); 368 | } 369 | 370 | void FStreetMapComponentDetails::RefreshDetails() 371 | { 372 | if(LastDetailBuilderPtr != nullptr) 373 | { 374 | LastDetailBuilderPtr->ForceRefreshDetails(); 375 | } 376 | } 377 | 378 | #undef LOCTEXT_NAMESPACE 379 | -------------------------------------------------------------------------------- /Source/StreetMapImporting/Private/StreetMapFactory.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #include "StreetMapFactory.h" 4 | #include "StreetMapImporting.h" 5 | #include "OSMFile.h" 6 | #include "StreetMap.h" 7 | 8 | 9 | // Latitude/longitude scale factor 10 | // - https://en.wikipedia.org/wiki/Equator#Exact_length 11 | static const double EarthCircumference = 40075036.0; 12 | const double UStreetMapFactory::LatitudeLongitudeScale = EarthCircumference / 360.0; // meters per degree 13 | 14 | 15 | UStreetMapFactory::UStreetMapFactory(const FObjectInitializer& ObjectInitializer) 16 | : Super(ObjectInitializer) 17 | { 18 | SupportedClass = UStreetMap::StaticClass(); 19 | 20 | Formats.Add( TEXT( "osm;OpenStreetMap XML" ) ); 21 | bCreateNew = false; 22 | bEditorImport = true; 23 | bEditAfterNew = false; 24 | bText = true; 25 | } 26 | 27 | 28 | UObject* UStreetMapFactory::FactoryCreateText( UClass* Class, UObject* Parent, FName Name, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn ) 29 | { 30 | UStreetMap* StreetMap = NewObject( Parent, Name, Flags | RF_Transactional ); 31 | 32 | StreetMap->AssetImportData->Update( this->GetCurrentFilename() ); 33 | 34 | // @todo: Performance: This will copy the entire text buffer into an FString. We need to do this 35 | // because the FFastXml parser is expecting a buffer that it can mutate as it parses. 36 | const int32 CharacterCount = BufferEnd - Buffer; 37 | FString MutableTextBuffer( CharacterCount, Buffer ); 38 | 39 | const bool bIsFilePathActuallyTextBuffer = true; 40 | const bool bLoadedOkay = LoadFromOpenStreetMapXMLFile( StreetMap, MutableTextBuffer, bIsFilePathActuallyTextBuffer, Warn ); 41 | 42 | if( !bLoadedOkay ) 43 | { 44 | StreetMap->MarkPendingKill(); 45 | StreetMap = nullptr; 46 | } 47 | 48 | return StreetMap; 49 | } 50 | 51 | 52 | bool UStreetMapFactory::LoadFromOpenStreetMapXMLFile( UStreetMap* StreetMap, FString& OSMFilePath, const bool bIsFilePathActuallyTextBuffer, FFeedbackContext* FeedbackContext ) 53 | { 54 | // OSM data is stored in meters. This is the scale factor to convert those units into UE4's native units (cm) 55 | // Keep in mind that if this is changed, UStreetMapComponent sizes for roads may need to be updated too! 56 | // @todo: We should make this scale factor customizable as an import option 57 | const float OSMToCentimetersScaleFactor = 100.0f; 58 | 59 | 60 | // Converts latitude to meters 61 | auto ConvertLatitudeToMeters = []( const double Latitude ) -> double 62 | { 63 | return -Latitude * LatitudeLongitudeScale; 64 | }; 65 | 66 | // Converts longitude to meters 67 | auto ConvertLongitudeToMeters = []( const double Longitude, const double Latitude ) -> double 68 | { 69 | return Longitude * LatitudeLongitudeScale * FMath::Cos( FMath::DegreesToRadians( Latitude ) ); 70 | }; 71 | 72 | // Converts latitude and longitude to X/Y coordinates, relative to some other latitude/longitude 73 | auto ConvertLatLongToMetersRelative = [ConvertLatitudeToMeters, ConvertLongitudeToMeters]( 74 | const double Latitude, 75 | const double Longitude, 76 | const double RelativeToLatitude, 77 | const double RelativeToLongitude ) -> FVector2D 78 | { 79 | // Applies Sanson-Flamsteed (sinusoidal) Projection (see http://www.progonos.com/furuti/MapProj/Normal/CartHow/HowSanson/howSanson.html) 80 | return FVector2D( 81 | (float)( ConvertLongitudeToMeters( Longitude, Latitude ) - ConvertLongitudeToMeters( RelativeToLongitude, Latitude ) ), 82 | (float)( ConvertLatitudeToMeters( Latitude ) - ConvertLatitudeToMeters( RelativeToLatitude ) ) ); 83 | }; 84 | 85 | // Adds a road to the street map using the OpenStreetMap data, flattening the road's coordinates into our map's space 86 | auto AddRoadForWay = [ConvertLatLongToMetersRelative, OSMToCentimetersScaleFactor]( 87 | const FOSMFile& OSMFile, 88 | UStreetMap& StreetMapRef, 89 | const FOSMFile::FOSMWayInfo& OSMWay, 90 | int32& OutRoadIndex ) -> bool 91 | { 92 | EStreetMapRoadType RoadType = EStreetMapRoadType::Other; 93 | switch( OSMWay.WayType ) 94 | { 95 | case FOSMFile::EOSMWayType::Motorway: 96 | case FOSMFile::EOSMWayType::Motorway_Link: 97 | case FOSMFile::EOSMWayType::Trunk: 98 | case FOSMFile::EOSMWayType::Trunk_Link: 99 | case FOSMFile::EOSMWayType::Primary: 100 | case FOSMFile::EOSMWayType::Primary_Link: 101 | RoadType = EStreetMapRoadType::Highway; 102 | break; 103 | 104 | case FOSMFile::EOSMWayType::Secondary: 105 | case FOSMFile::EOSMWayType::Secondary_Link: 106 | case FOSMFile::EOSMWayType::Tertiary: 107 | case FOSMFile::EOSMWayType::Tertiary_Link: 108 | RoadType = EStreetMapRoadType::MajorRoad; 109 | break; 110 | 111 | case FOSMFile::EOSMWayType::Residential: 112 | case FOSMFile::EOSMWayType::Service: 113 | case FOSMFile::EOSMWayType::Unclassified: 114 | case FOSMFile::EOSMWayType::Road: // @todo: Consider excluding "Road" from our data set, as it could be a highway that wasn't properly tagged in OSM yet 115 | RoadType = EStreetMapRoadType::Street; 116 | break; 117 | } 118 | 119 | if( RoadType != EStreetMapRoadType::Other ) 120 | { 121 | // Require at least two points! 122 | if( OSMWay.Nodes.Num() > 1 ) 123 | { 124 | // Create a road for this way 125 | OutRoadIndex = StreetMapRef.Roads.Num(); 126 | FStreetMapRoad& NewRoad = *new( StreetMapRef.Roads )FStreetMapRoad(); 127 | 128 | FVector2D BoundsMin( TNumericLimits::Max(), TNumericLimits::Max() ); 129 | FVector2D BoundsMax( TNumericLimits::Lowest(), TNumericLimits::Lowest() ); 130 | 131 | NewRoad.RoadPoints.AddUninitialized( OSMWay.Nodes.Num() ); 132 | int32 CurRoadPoint = 0; 133 | 134 | // Set defaults for each node index on this road. INDEX_NONE means the node is not valid, which may be the case 135 | // for nodes that we filter out entirely. This will be filled in by valid indices to nodes later on. 136 | NewRoad.NodeIndices.AddUninitialized( OSMWay.Nodes.Num() ); 137 | for( int32& NodeIndex : NewRoad.NodeIndices ) 138 | { 139 | NodeIndex = INDEX_NONE; 140 | } 141 | 142 | 143 | for( const FOSMFile::FOSMNodeInfo* OSMNodePtr : OSMWay.Nodes ) 144 | { 145 | const FOSMFile::FOSMNodeInfo& OSMNode = *OSMNodePtr; 146 | 147 | // Transform all points relative to the center of the latitude/longitude bounds, so that 148 | // we get as much precision as possible. 149 | const double RelativeToLatitude = OSMFile.AverageLatitude; 150 | const double RelativeToLongitude = OSMFile.AverageLongitude; 151 | const FVector2D NodePos = ConvertLatLongToMetersRelative( 152 | OSMNode.Latitude, 153 | OSMNode.Longitude, 154 | RelativeToLatitude, 155 | RelativeToLongitude ) * OSMToCentimetersScaleFactor; 156 | 157 | // Update bounding box 158 | { 159 | if( NodePos.X < BoundsMin.X ) 160 | { 161 | BoundsMin.X = NodePos.X; 162 | } 163 | if( NodePos.Y < BoundsMin.Y ) 164 | { 165 | BoundsMin.Y = NodePos.Y; 166 | } 167 | if( NodePos.X > BoundsMax.X ) 168 | { 169 | BoundsMax.X = NodePos.X; 170 | } 171 | if( NodePos.Y > BoundsMax.Y ) 172 | { 173 | BoundsMax.Y = NodePos.Y; 174 | } 175 | } 176 | 177 | // Fill in the points 178 | NewRoad.RoadPoints[ CurRoadPoint++ ] = NodePos; 179 | } 180 | 181 | 182 | NewRoad.RoadName = OSMWay.Name; 183 | if( NewRoad.RoadName.IsEmpty() ) 184 | { 185 | NewRoad.RoadName = OSMWay.Ref; 186 | } 187 | NewRoad.RoadType = RoadType; 188 | NewRoad.BoundsMin = BoundsMin; 189 | NewRoad.BoundsMax = BoundsMax; 190 | 191 | NewRoad.bIsOneWay = OSMWay.bIsOneWay; 192 | 193 | StreetMapRef.BoundsMin.X = FMath::Min( StreetMapRef.BoundsMin.X, BoundsMin.X ); 194 | StreetMapRef.BoundsMin.Y = FMath::Min( StreetMapRef.BoundsMin.Y, BoundsMin.Y ); 195 | StreetMapRef.BoundsMax.X = FMath::Max( StreetMapRef.BoundsMax.X, BoundsMax.X ); 196 | StreetMapRef.BoundsMax.Y = FMath::Max( StreetMapRef.BoundsMax.Y, BoundsMax.Y ); 197 | 198 | return true; 199 | } 200 | else 201 | { 202 | // NOTE: Skipped adding road for way because it has less than 2 points 203 | // @todo: Log this for the user as an import warning 204 | } 205 | } 206 | 207 | return false; 208 | }; 209 | 210 | 211 | // Adds a building to the street map using the OpenStreetMap data, flattening the road's coordinates into our map's space 212 | auto AddBuildingForWay = [ConvertLatLongToMetersRelative, OSMToCentimetersScaleFactor]( 213 | const FOSMFile& OSMFile, 214 | UStreetMap& StreetMapRef, 215 | const FOSMFile::FOSMWayInfo& OSMWay ) -> bool 216 | { 217 | if( OSMWay.WayType == FOSMFile::EOSMWayType::Building ) 218 | { 219 | // Require at least three points so that we don't have degenerate polygon! 220 | if( OSMWay.Nodes.Num() > 2 ) 221 | { 222 | // Create a building for this way 223 | FStreetMapBuilding& NewBuilding = *new( StreetMapRef.Buildings )FStreetMapBuilding(); 224 | 225 | FVector2D BoundsMin( TNumericLimits::Max(), TNumericLimits::Max() ); 226 | FVector2D BoundsMax( TNumericLimits::Lowest(), TNumericLimits::Lowest() ); 227 | 228 | NewBuilding.BuildingPoints.AddUninitialized( OSMWay.Nodes.Num() ); 229 | int32 CurBuildingPoint = 0; 230 | 231 | for( const FOSMFile::FOSMNodeInfo* OSMNodePtr : OSMWay.Nodes ) 232 | { 233 | const FOSMFile::FOSMNodeInfo& OSMNode = *OSMNodePtr; 234 | 235 | // Transform all points relative to the center of the latitude/longitude bounds, so that 236 | // we get as much precision as possible. 237 | const double RelativeToLatitude = OSMFile.AverageLatitude; 238 | const double RelativeToLongitude = OSMFile.AverageLongitude; 239 | const FVector2D NodePos = ConvertLatLongToMetersRelative( 240 | OSMNode.Latitude, 241 | OSMNode.Longitude, 242 | RelativeToLatitude, 243 | RelativeToLongitude ) * OSMToCentimetersScaleFactor; 244 | 245 | // Update bounding box 246 | { 247 | if( NodePos.X < BoundsMin.X ) 248 | { 249 | BoundsMin.X = NodePos.X; 250 | } 251 | if( NodePos.Y < BoundsMin.Y ) 252 | { 253 | BoundsMin.Y = NodePos.Y; 254 | } 255 | if( NodePos.X > BoundsMax.X ) 256 | { 257 | BoundsMax.X = NodePos.X; 258 | } 259 | if( NodePos.Y > BoundsMax.Y ) 260 | { 261 | BoundsMax.Y = NodePos.Y; 262 | } 263 | } 264 | 265 | // Fill in the points 266 | NewBuilding.BuildingPoints[ CurBuildingPoint++ ] = NodePos; 267 | } 268 | 269 | // Make sure the building ended up with a closed polygon, then remove the final (redundant) point 270 | const bool bIsClosed = NewBuilding.BuildingPoints[ 0 ].Equals( NewBuilding.BuildingPoints[ NewBuilding.BuildingPoints.Num() - 1 ], KINDA_SMALL_NUMBER ); 271 | if( bIsClosed ) 272 | { 273 | // Remove the final redundant point 274 | NewBuilding.BuildingPoints.Pop(); 275 | } 276 | else 277 | { 278 | // Wasn't expecting to have an unclosed shape. Our tolerances might be off, or the data was malformed. 279 | // Either way, it shouldn't be a problem as we'll close the shape ourselves below. 280 | // @todo: Log this for the user as an import warning 281 | } 282 | 283 | NewBuilding.BuildingName = OSMWay.Name; 284 | if( NewBuilding.BuildingName.IsEmpty() ) 285 | { 286 | NewBuilding.BuildingName = OSMWay.Ref; 287 | } 288 | 289 | NewBuilding.Height = OSMWay.Height * OSMToCentimetersScaleFactor; 290 | NewBuilding.BuildingLevels = OSMWay.BuildingLevels; 291 | 292 | NewBuilding.BoundsMin = BoundsMin; 293 | NewBuilding.BoundsMax = BoundsMax; 294 | 295 | StreetMapRef.BoundsMin.X = FMath::Min( StreetMapRef.BoundsMin.X, BoundsMin.X ); 296 | StreetMapRef.BoundsMin.Y = FMath::Min( StreetMapRef.BoundsMin.Y, BoundsMin.Y ); 297 | StreetMapRef.BoundsMax.X = FMath::Max( StreetMapRef.BoundsMax.X, BoundsMax.X ); 298 | StreetMapRef.BoundsMax.Y = FMath::Max( StreetMapRef.BoundsMax.Y, BoundsMax.Y ); 299 | 300 | return true; 301 | } 302 | else 303 | { 304 | // NOTE: Skipped adding building for way because it has less than 3 points 305 | // @todo: Log this for the user as an import warning 306 | } 307 | } 308 | 309 | return false; 310 | }; 311 | 312 | 313 | // Load up the OSM file. It's in XML format. 314 | FOSMFile OSMFile; 315 | if( !OSMFile.LoadOpenStreetMapFile( OSMFilePath, bIsFilePathActuallyTextBuffer, FeedbackContext ) ) 316 | { 317 | // Loading failed. The actual error message will be sent to the FeedbackContext's log. 318 | return false; 319 | } 320 | 321 | // @todo: The loaded OSMFile stores data in double precision, but our runtime representation (UStreetMap) 322 | // truncates everything to single precision, after transposing coordinates to be relative to the 323 | // center of the map's 2D bounds. Large maps will suffer from floating point precision issues. 324 | // To solve this we'd need to either store everything in double precision, or store map elements 325 | // in integral grid cells with coordinates relative to their cell. Of course, there will be many 326 | // other considerations for handling huge maps (loading, rendering, collision, etc.) 327 | 328 | // Maps OSMWayInfos to the RoadIndex we created for that way 329 | TMap< const FOSMFile::FOSMWayInfo*, int32 > OSMWayToRoadIndexMap; 330 | 331 | StreetMap->BoundsMin = FVector2D( TNumericLimits::Max(), TNumericLimits::Max() ); 332 | StreetMap->BoundsMax = FVector2D( TNumericLimits::Lowest(), TNumericLimits::Lowest() ); 333 | 334 | for( const FOSMFile::FOSMWayInfo* OSMWay : OSMFile.Ways ) 335 | { 336 | // Handle buildings differently than roads 337 | if( OSMWay->WayType == FOSMFile::EOSMWayType::Building ) 338 | { 339 | if( AddBuildingForWay( OSMFile, *StreetMap, *OSMWay ) ) 340 | { 341 | // ... 342 | } 343 | } 344 | else 345 | { 346 | int32 RoadIndex = INDEX_NONE; 347 | if( AddRoadForWay( OSMFile, *StreetMap, *OSMWay, RoadIndex ) ) 348 | { 349 | OSMWayToRoadIndexMap.Add( OSMWay, RoadIndex ); 350 | } 351 | } 352 | } 353 | 354 | for( const auto& NodeMapHashPair : OSMFile.NodeMap ) 355 | { 356 | const FOSMFile::FOSMNodeInfo& OSMNode = *NodeMapHashPair.Value; 357 | 358 | // Any ways touching this node? 359 | if( OSMNode.WayRefs.Num() > 0 ) 360 | { 361 | FStreetMapNode NewNode; 362 | 363 | for( const FOSMFile::FOSMWayRef& OSMWayRef : OSMNode.WayRefs ) 364 | { 365 | const int32* FoundRoundIndexPtr = OSMWayToRoadIndexMap.Find( OSMWayRef.Way ); 366 | if( FoundRoundIndexPtr != nullptr ) 367 | { 368 | const int32 FoundRoadIndex = *FoundRoundIndexPtr; 369 | 370 | FStreetMapRoadRef RoadRef; 371 | RoadRef.RoadIndex = FoundRoadIndex; 372 | 373 | const int32 RoadPointIndex = OSMWayRef.NodeIndex; 374 | RoadRef.RoadPointIndex = RoadPointIndex; 375 | NewNode.RoadRefs.Add( RoadRef ); 376 | } 377 | else 378 | { 379 | // Skipped ref because we didn't keep this road in our data set 380 | } 381 | } 382 | 383 | // Only store nodes that are attached to at least one road. We must have at least a connection to a single 384 | // road, otherwise we've filtered this node's road out and there's no point in wasting memory on the node itself. 385 | if( NewNode.RoadRefs.Num() > 0 ) 386 | { 387 | // Most nodes from OpenStreetMap will only be touching a single road. These nodes usually make up the points 388 | // along the length of the road, even for roads with no intersections except at the beginning and end. We 389 | // don't need to store these points unless they are at the ends of the road. Keeping the points at the 390 | // beginning and end of the road is useful when calculating navigation data, but the other nodes can go! 391 | // In the road's NodeIndices array, any nodes we filter out here will simply have an INDEX_NONE value in that 392 | // array, and we'll only store the positions of the road at these points in the road's RoadPoints array. 393 | 394 | const FStreetMapRoadRef& FirstRoadRef = NewNode.RoadRefs[ 0 ]; 395 | const FStreetMapRoad& FirstRoad = StreetMap->Roads[ FirstRoadRef.RoadIndex ]; 396 | 397 | if( NewNode.RoadRefs.Num() > 1 || // Does the node connect to more than one road? 398 | FirstRoadRef.RoadPointIndex == 0 || // Does the node connect to the beginning of the road? 399 | FirstRoadRef.RoadPointIndex == ( FirstRoad.NodeIndices.Num() - 1 ) ) // Does the node connect to the end of the road? 400 | { 401 | const int32 NewNodeIndex = StreetMap->Nodes.Num(); 402 | StreetMap->Nodes.Add( NewNode ); 403 | 404 | // Update the roads that are overlapping this node 405 | for( const FStreetMapRoadRef& RoadRef : NewNode.RoadRefs ) 406 | { 407 | FStreetMapRoad& Road = StreetMap->Roads[ RoadRef.RoadIndex ]; 408 | check( Road.NodeIndices[ RoadRef.RoadPointIndex ] == INDEX_NONE ); 409 | Road.NodeIndices[ RoadRef.RoadPointIndex ] = NewNodeIndex; 410 | } 411 | } 412 | else 413 | { 414 | // Node has only one road that is references, and it wasn't the beginning or end of the road, so filter it out! 415 | } 416 | } 417 | else 418 | { 419 | // Node doesn't reference any roads that we kept, or the data was malformed. Filter it out. 420 | } 421 | } 422 | } 423 | 424 | // Validation test: Make sure that all roads have at least two nodes referencing them, one at the beginning and 425 | // one at the end. 426 | for( const FStreetMapRoad& Road : StreetMap->Roads ) 427 | { 428 | const bool bHasNodeAtBeginning = Road.NodeIndices[ 0 ] != INDEX_NONE; 429 | const bool bHasNodeAtEnd = Road.NodeIndices[ Road.NodeIndices.Num() - 1 ] != INDEX_NONE; 430 | 431 | // All roads should have at least two nodes referencing them, one at the beginning and one at the end 432 | ensure( bHasNodeAtBeginning && bHasNodeAtEnd ); 433 | } 434 | 435 | return true; 436 | } 437 | 438 | 439 | -------------------------------------------------------------------------------- /Source/StreetMapImporting/Private/StreetMapImporting.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #include "StreetMapImporting.h" 4 | #include "StreetMapAssetTypeActions.h" 5 | #include "Modules/ModuleManager.h" 6 | #include "StreetMapStyle.h" 7 | #include "StreetMapComponentDetails.h" 8 | 9 | 10 | class FStreetMapImportingModule : public IModuleInterface 11 | { 12 | 13 | public: 14 | 15 | // IModuleInterface interface 16 | virtual void StartupModule() override; 17 | virtual void ShutdownModule() override; 18 | 19 | TSharedPtr< FStreetMapAssetTypeActions > StreetMapAssetTypeActions; 20 | }; 21 | 22 | 23 | IMPLEMENT_MODULE( FStreetMapImportingModule, StreetMapImporting ) 24 | 25 | 26 | 27 | void FStreetMapImportingModule::StartupModule() 28 | { 29 | // Register asset types 30 | IAssetTools& AssetTools = FModuleManager::LoadModuleChecked( "AssetTools" ).Get(); 31 | StreetMapAssetTypeActions = MakeShareable( new FStreetMapAssetTypeActions() ); 32 | AssetTools.RegisterAssetTypeActions( StreetMapAssetTypeActions.ToSharedRef() ); 33 | 34 | // Initialize & Register StreetMap Style 35 | FStreetMapStyle::Initialize(); 36 | 37 | // Register StreetMapComponent Detail Customization 38 | FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); 39 | PropertyModule.RegisterCustomClassLayout("StreetMapComponent", FOnGetDetailCustomizationInstance::CreateStatic(&FStreetMapComponentDetails::MakeInstance)); 40 | PropertyModule.NotifyCustomizationModuleChanged(); 41 | } 42 | 43 | 44 | void FStreetMapImportingModule::ShutdownModule() 45 | { 46 | // Unregister all the asset types that we registered 47 | if( FModuleManager::Get().IsModuleLoaded( "AssetTools" ) ) 48 | { 49 | IAssetTools& AssetTools = FModuleManager::GetModuleChecked( "AssetTools" ).Get(); 50 | AssetTools.UnregisterAssetTypeActions( StreetMapAssetTypeActions.ToSharedRef() ); 51 | StreetMapAssetTypeActions.Reset(); 52 | } 53 | 54 | // Unregister StreetMap Style 55 | FStreetMapStyle::Shutdown(); 56 | 57 | if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) 58 | { 59 | FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); 60 | PropertyModule.UnregisterCustomClassLayout("StreetMapComponent"); 61 | PropertyModule.NotifyCustomizationModuleChanged(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Source/StreetMapImporting/Private/StreetMapReimportFactory.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #include "StreetMapReimportFactory.h" 4 | #include "StreetMapImporting.h" 5 | #include "StreetMap.h" 6 | 7 | 8 | UStreetMapReimportFactory::UStreetMapReimportFactory(const FObjectInitializer& ObjectInitializer) 9 | : Super(ObjectInitializer) 10 | { 11 | } 12 | 13 | 14 | bool UStreetMapReimportFactory::CanReimport( UObject* Obj, TArray& OutFilenames ) 15 | { 16 | UStreetMap* StreetMap = Cast( Obj ); 17 | if( StreetMap != nullptr ) 18 | { 19 | OutFilenames.Add( StreetMap->AssetImportData->GetFirstFilename() ); 20 | } 21 | return true; 22 | } 23 | 24 | 25 | void UStreetMapReimportFactory::SetReimportPaths( UObject* Obj, const TArray& NewReimportPaths ) 26 | { 27 | UStreetMap* StreetMap = CastChecked( Obj ); 28 | StreetMap->Modify(); 29 | StreetMap->AssetImportData->Update( NewReimportPaths[0] ); 30 | } 31 | 32 | 33 | EReimportResult::Type UStreetMapReimportFactory::Reimport( UObject* Obj ) 34 | { 35 | UStreetMap* StreetMap = CastChecked( Obj ); 36 | 37 | const FString Filename = StreetMap->AssetImportData->GetFirstFilename(); 38 | const FString FileExtension = FPaths::GetExtension(Filename); 39 | 40 | // If there is no file path provided, can't reimport from source 41 | if ( !Filename.Len() ) 42 | { 43 | return EReimportResult::Failed; 44 | } 45 | 46 | if( UFactory::StaticImportObject( StreetMap->GetClass(), StreetMap->GetOuter(), *StreetMap->GetName(), RF_Public|RF_Standalone, *Filename, nullptr, this ) ) 47 | { 48 | // Mark the package dirty after the successful import 49 | StreetMap->MarkPackageDirty(); 50 | return EReimportResult::Succeeded; 51 | } 52 | 53 | return EReimportResult::Failed; 54 | } 55 | 56 | 57 | int32 UStreetMapReimportFactory::GetPriority() const 58 | { 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /Source/StreetMapImporting/Private/StreetMapStyle.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #include "StreetMapStyle.h" 4 | #include "StreetMapImporting.h" 5 | #include "Styling/SlateStyle.h" 6 | #include "Interfaces/IPluginManager.h" 7 | 8 | #define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( FStreetMapStyle::InContent( RelativePath, ".png" ), __VA_ARGS__ ) 9 | 10 | FString FStreetMapStyle::InContent(const FString& RelativePath, const ANSICHAR* Extension) 11 | { 12 | static FString IconsDir = IPluginManager::Get().FindPlugin(TEXT("StreetMap"))->GetContentDir() / TEXT("Icons"); 13 | return (IconsDir / RelativePath) + Extension; 14 | } 15 | 16 | TSharedPtr< FSlateStyleSet > FStreetMapStyle::StyleSet = NULL; 17 | TSharedPtr< class ISlateStyle > FStreetMapStyle::Get() { return StyleSet; } 18 | 19 | void FStreetMapStyle::Initialize() 20 | { 21 | // Const icon & thumbnail sizes 22 | const FVector2D Icon16x16(16.0f, 16.0f); 23 | const FVector2D Icon128x128(128.0f, 128.0f); 24 | 25 | // Only register once 26 | if (StyleSet.IsValid()) 27 | { 28 | return; 29 | } 30 | 31 | StyleSet = MakeShareable(new FSlateStyleSet("StreetMapStyle")); 32 | FString ContentDir = IPluginManager::Get().FindPlugin(TEXT("StreetMap"))->GetContentDir(); 33 | StyleSet->SetContentRoot(ContentDir); 34 | 35 | StyleSet->Set("ClassIcon.StreetMap", new IMAGE_BRUSH("sm_asset_icon_32", Icon16x16)); 36 | StyleSet->Set("ClassThumbnail.StreetMap", new IMAGE_BRUSH("sm_asset_icon_128", Icon128x128)); 37 | 38 | StyleSet->Set("ClassIcon.StreetMapActor", new IMAGE_BRUSH("sm_actor_icon_32", Icon16x16)); 39 | StyleSet->Set("ClassThumbnail.StreetMapActor", new IMAGE_BRUSH("sm_actor_icon_128", Icon128x128)); 40 | 41 | StyleSet->Set("ClassIcon.StreetMapComponent", new IMAGE_BRUSH("sm_component_icon_32", Icon16x16)); 42 | StyleSet->Set("ClassThumbnail.StreetMapComponent", new IMAGE_BRUSH("sm_component_icon_128", Icon128x128)); 43 | 44 | FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); 45 | }; 46 | 47 | #undef IMAGE_BRUSH 48 | 49 | 50 | void FStreetMapStyle::Shutdown() 51 | { 52 | if (StyleSet.IsValid()) 53 | { 54 | FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); 55 | ensure(StyleSet.IsUnique()); 56 | StyleSet.Reset(); 57 | } 58 | } -------------------------------------------------------------------------------- /Source/StreetMapImporting/Public/OSMFile.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | #pragma once 3 | 4 | #include "FastXml.h" 5 | 6 | 7 | /** OpenStreetMap file loader */ 8 | class FOSMFile : public IFastXmlCallback 9 | { 10 | 11 | public: 12 | 13 | /** Default constructor for FOSMFile */ 14 | FOSMFile(); 15 | 16 | /** Destructor for FOSMFile */ 17 | virtual ~FOSMFile(); 18 | 19 | /** Loads the map from an OpenStreetMap XML file. Note that in the case of the file path containing the XML data, the string must be mutable for us to parse it quickly. */ 20 | bool LoadOpenStreetMapFile( FString& OSMFilePath, const bool bIsFilePathActuallyTextBuffer, class FFeedbackContext* FeedbackContext ); 21 | 22 | 23 | struct FOSMWayInfo; 24 | 25 | /** Types of ways */ 26 | enum class EOSMWayType 27 | { 28 | /// 29 | /// ROADS 30 | /// 31 | 32 | /** A restricted access major divided highway, normally with 2 or more running lanes plus emergency hard shoulder. Equivalent to the Freeway, Autobahn, etc. */ 33 | Motorway, 34 | 35 | /** The link roads (sliproads/ramps) leading to/from a motorway from/to a motorway or lower class highway. Normally with the same motorway restrictions. */ 36 | Motorway_Link, 37 | 38 | /** The most important roads in a country's system that aren't motorways. (Need not necessarily be a divided highway.) */ 39 | Trunk, 40 | 41 | /** The link roads (sliproads/ramps) leading to/from a trunk road from/to a trunk road or lower class highway. */ 42 | Trunk_Link, 43 | 44 | /** The next most important roads in a country's system. (Often link larger towns.) */ 45 | Primary, 46 | 47 | /** The link roads (sliproads/ramps) leading to/from a primary road from/to a primary road or lower class highway. */ 48 | Primary_Link, 49 | 50 | /** The next most important roads in a country's system. (Often link smaller towns and villages.) */ 51 | Secondary, 52 | 53 | /** The link roads (sliproads/ramps) leading to/from a secondary road from/to a secondary road or lower class highway. */ 54 | Secondary_Link, 55 | 56 | /** The next most important roads in a country's system. */ 57 | Tertiary, 58 | 59 | /** The link roads (sliproads/ramps) leading to/from a tertiary road from/to a tertiary road or lower class highway. */ 60 | Tertiary_Link, 61 | 62 | /** Roads which are primarily lined with and serve as an access to housing. */ 63 | Residential, 64 | 65 | /** For access roads to, or within an industrial estate, camp site, business park, car park etc. */ 66 | Service, 67 | 68 | /** The least most important through roads in a country's system, i.e. minor roads of a lower classification than tertiary, but which serve a purpose other than access to properties. */ 69 | Unclassified, 70 | 71 | 72 | /// 73 | /// NON-ROADS 74 | /// 75 | 76 | /** Residential streets where pedestrians have legal priority over cars, speeds are kept very low and where children are allowed to play on the street. */ 77 | Living_Street, 78 | 79 | /** For roads used mainly/exclusively for pedestrians in shopping and some residential areas which may allow access by motorised vehicles only for very limited periods of the day. */ 80 | Pedestrian, 81 | 82 | /** Roads for agricultural or forestry uses etc, often rough with unpaved/unsealed surfaces, that can be used only by off-road vehicles (4WD, tractors, ATVs, etc.) */ 83 | Track, 84 | 85 | /** A busway where the vehicle guided by the way (though not a railway) and is not suitable for other traffic. */ 86 | Bus_Guideway, 87 | 88 | /** A course or track for (motor) racing */ 89 | Raceway, 90 | 91 | /** A road where the mapper is unable to ascertain the classification from the information available. */ 92 | Road, 93 | 94 | 95 | /// 96 | /// PATHS 97 | /// 98 | 99 | /** For designated footpaths; i.e., mainly/exclusively for pedestrians. This includes walking tracks and gravel paths. */ 100 | Footway, 101 | 102 | /** For designated cycleways. */ 103 | Cycleway, 104 | 105 | /** Paths normally used by horses */ 106 | Bridleway, 107 | 108 | /** For flights of steps (stairs) on footways. */ 109 | Steps, 110 | 111 | /** A non-specific path. */ 112 | Path, 113 | 114 | 115 | /// 116 | /// LIFECYCLE 117 | /// 118 | 119 | /** For planned roads, use with proposed=* and also proposed=* with a value of the proposed highway value. */ 120 | Proposed, 121 | 122 | /** For roads under construction. */ 123 | Construction, 124 | 125 | 126 | /// 127 | /// BUILDINGS 128 | /// 129 | 130 | /** Default type of building. A general catch-all. */ 131 | Building, 132 | 133 | 134 | /// 135 | /// UNSUPPORTED 136 | /// 137 | 138 | /** Currently unrecognized type */ 139 | Other, 140 | }; 141 | 142 | 143 | struct FOSMWayRef 144 | { 145 | // Way that we're referencing at this node 146 | FOSMWayInfo* Way; 147 | 148 | // Index of the node in the way's array of nodes 149 | int32 NodeIndex; 150 | }; 151 | 152 | 153 | struct FOSMNodeInfo 154 | { 155 | double Latitude; 156 | double Longitude; 157 | TArray WayRefs; 158 | }; 159 | 160 | 161 | struct FOSMWayInfo 162 | { 163 | FString Name; 164 | FString Ref; 165 | TArray Nodes; 166 | EOSMWayType WayType; 167 | double Height; 168 | int32 BuildingLevels; 169 | 170 | // If true, way is only traversable in the order the nodes are listed in the Nodes list 171 | uint8 bIsOneWay : 1; 172 | }; 173 | 174 | // Minimum latitude/longitude bounds 175 | double MinLatitude = MAX_dbl; 176 | double MinLongitude = MAX_dbl; 177 | double MaxLatitude = -MAX_dbl; 178 | double MaxLongitude = -MAX_dbl; 179 | 180 | // Average Latitude (roughly the center of the map) 181 | double AverageLatitude = 0.0; 182 | double AverageLongitude = 0.0; 183 | 184 | // All ways we've parsed 185 | TArray Ways; 186 | 187 | // Maps node IDs to info about each node 188 | TMap NodeMap; 189 | 190 | protected: 191 | 192 | // IFastXmlCallback overrides 193 | virtual bool ProcessXmlDeclaration( const TCHAR* ElementData, int32 XmlFileLineNumber ) override; 194 | virtual bool ProcessComment( const TCHAR* Comment ) override; 195 | virtual bool ProcessElement( const TCHAR* ElementName, const TCHAR* ElementData, int32 XmlFileLineNumber ) override; 196 | virtual bool ProcessAttribute( const TCHAR* AttributeName, const TCHAR* AttributeValue ) override; 197 | virtual bool ProcessClose( const TCHAR* Element ) override; 198 | 199 | 200 | protected: 201 | 202 | enum class ParsingState 203 | { 204 | Root, 205 | Node, 206 | Way, 207 | Way_NodeRef, 208 | Way_Tag 209 | }; 210 | 211 | // Current state of parser 212 | ParsingState ParsingState; 213 | 214 | // ID of node that is currently being parsed 215 | int64 CurrentNodeID; 216 | 217 | // Node that is currently being parsed 218 | FOSMNodeInfo* CurrentNodeInfo; 219 | 220 | // Way that is currently being parsed 221 | FOSMWayInfo* CurrentWayInfo; 222 | 223 | // Current way's tag key string 224 | const TCHAR* CurrentWayTagKey; 225 | }; 226 | 227 | 228 | -------------------------------------------------------------------------------- /Source/StreetMapImporting/Public/StreetMapActorFactory.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #pragma once 4 | #include "ActorFactories/ActorFactory.h" 5 | #include "StreetMapActorFactory.generated.h" 6 | 7 | UCLASS() 8 | class UStreetMapActorFactory : public UActorFactory 9 | { 10 | GENERATED_UCLASS_BODY() 11 | 12 | //~ Begin UActorFactory Interface 13 | virtual void PostSpawnActor(UObject* Asset, AActor* NewActor) override; 14 | virtual void PostCreateBlueprint(UObject* Asset, AActor* CDO) override; 15 | virtual bool CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) override; 16 | //~ End UActorFactory Interface 17 | }; 18 | -------------------------------------------------------------------------------- /Source/StreetMapImporting/Public/StreetMapAssetTypeActions.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | #pragma once 3 | 4 | #include "AssetTypeActions_Base.h" 5 | 6 | class FStreetMapAssetTypeActions : public FAssetTypeActions_Base 7 | { 8 | public: 9 | 10 | // IAssetTypeActions interface 11 | virtual FText GetName() const override; 12 | virtual FColor GetTypeColor() const override; 13 | virtual UClass* GetSupportedClass() const override; 14 | virtual void OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr()) override; 15 | virtual uint32 GetCategories() override; 16 | virtual FText GetAssetDescription(const FAssetData& AssetData) const override; 17 | virtual bool IsImportedAsset() const override; 18 | virtual void GetResolvedSourceFilePaths( const TArray& TypeAssets, TArray& OutSourceFilePaths ) const override; 19 | }; 20 | -------------------------------------------------------------------------------- /Source/StreetMapImporting/Public/StreetMapComponentDetails.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "IDetailCustomization.h" 6 | 7 | class FStreetMapComponentDetails : public IDetailCustomization 8 | { 9 | public: 10 | 11 | FStreetMapComponentDetails(); 12 | 13 | /** Makes a new instance of this detail layout class for a specific detail view requesting it. */ 14 | static TSharedRef MakeInstance(); 15 | 16 | /** IDetailCustomization interface */ 17 | virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; 18 | 19 | /** Returns true if we selected a valid cached streetMapComp , having valid cached mesh data. */ 20 | bool HasValidMeshData() const; 21 | 22 | /** Returns true if we selected a valid cached streetMapComp having valid street map representation. */ 23 | bool HasValidMapObject() const; 24 | 25 | /** Handles create static mesh asset button clicking */ 26 | FReply OnCreateStaticMeshAssetClicked(); 27 | 28 | /** Handles build/rebuild mesh button clicking */ 29 | FReply OnBuildMeshClicked(); 30 | 31 | /** Handles clear mesh button clicking */ 32 | FReply OnClearMeshClicked(); 33 | 34 | /** Refreshes the details view and regenerates all the customized layouts. */ 35 | void RefreshDetails(); 36 | 37 | 38 | protected: 39 | /** Holds Selected Street Map Component */ 40 | class UStreetMapComponent* SelectedStreetMapComponent; 41 | 42 | /** Holds Last Detail Builder Pointer */ 43 | IDetailLayoutBuilder* LastDetailBuilderPtr; 44 | 45 | }; 46 | -------------------------------------------------------------------------------- /Source/StreetMapImporting/Public/StreetMapFactory.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | #pragma once 3 | 4 | #include "Factories/Factory.h" 5 | #include "StreetMapFactory.generated.h" 6 | 7 | 8 | /** 9 | * Import factory object for OpenStreetMap assets 10 | */ 11 | UCLASS() 12 | class UStreetMapFactory : public UFactory 13 | { 14 | GENERATED_BODY() 15 | 16 | public: 17 | 18 | /** UStreetMapFactory constructor */ 19 | UStreetMapFactory( const class FObjectInitializer& ObjectInitializer ); 20 | 21 | protected: 22 | 23 | // UFactory overrides 24 | virtual UObject* FactoryCreateText( UClass* Class, UObject* Parent, FName Name, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn ) override; 25 | 26 | /** Loads the street map from an OpenStreetMap XML file. Note that in the case of the file path containing the XML data, the string must be mutable for us to parse it quickly. */ 27 | bool LoadFromOpenStreetMapXMLFile( class UStreetMap* StreetMap, FString& OSMFilePath, const bool bIsFilePathActuallyTextBuffer, class FFeedbackContext* FeedbackContext ); 28 | 29 | /** Static: Latitude/longitude scale factor */ 30 | static const double LatitudeLongitudeScale; 31 | }; 32 | 33 | -------------------------------------------------------------------------------- /Source/StreetMapImporting/Public/StreetMapImporting.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | #pragma once 3 | 4 | #include "UnrealEd.h" 5 | 6 | -------------------------------------------------------------------------------- /Source/StreetMapImporting/Public/StreetMapReimportFactory.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | #pragma once 3 | 4 | #include "StreetMapFactory.h" 5 | #include "EditorReimportHandler.h" 6 | #include "StreetMapReimportFactory.generated.h" 7 | 8 | 9 | /** 10 | * Import factory object for OpenStreetMap assets 11 | */ 12 | UCLASS() 13 | class UStreetMapReimportFactory : public UStreetMapFactory, public FReimportHandler 14 | { 15 | GENERATED_BODY() 16 | 17 | public: 18 | 19 | /** UStreetMapReimportFactory constructor */ 20 | UStreetMapReimportFactory( const class FObjectInitializer& ObjectInitializer ); 21 | 22 | protected: 23 | 24 | // FReimportHandler overrides 25 | virtual bool CanReimport( UObject* Obj, TArray& OutFilenames ) override; 26 | virtual void SetReimportPaths( UObject* Obj, const TArray& NewReimportPaths ) override; 27 | virtual EReimportResult::Type Reimport( UObject* Obj ) override; 28 | virtual int32 GetPriority() const override; 29 | 30 | }; 31 | 32 | -------------------------------------------------------------------------------- /Source/StreetMapImporting/Public/StreetMapStyle.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | /** StreetMap Editor Style Helper Class. */ 6 | class FStreetMapStyle 7 | { 8 | public: 9 | 10 | static void Initialize(); 11 | static void Shutdown(); 12 | 13 | static TSharedPtr< class ISlateStyle > Get(); 14 | 15 | private: 16 | 17 | static FString InContent(const FString& RelativePath, const ANSICHAR* Extension); 18 | 19 | private: 20 | 21 | static TSharedPtr< class FSlateStyleSet > StyleSet; 22 | }; -------------------------------------------------------------------------------- /Source/StreetMapImporting/StreetMapImporting.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | namespace UnrealBuildTool.Rules 4 | { 5 | public class StreetMapImporting : ModuleRules 6 | { 7 | public StreetMapImporting(ReadOnlyTargetRules Target) 8 | : base(Target) 9 | { 10 | PrivateDependencyModuleNames.AddRange( 11 | new string[] { 12 | "Core", 13 | "CoreUObject", 14 | "Engine", 15 | "UnrealEd", 16 | "XmlParser", 17 | "AssetTools", 18 | "Projects", 19 | "Slate", 20 | "EditorStyle", 21 | "SlateCore", 22 | "PropertyEditor", 23 | "RenderCore", 24 | "RHI", 25 | "RawMesh", 26 | "AssetTools", 27 | "AssetRegistry", 28 | "StreetMapRuntime" 29 | } 30 | ); 31 | 32 | PrivateIncludePaths.AddRange(new string[]{"StreetMapImporting/Private"}); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Source/StreetMapRuntime/Private/PolygonTools.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #include "PolygonTools.h" 4 | #include "StreetMapRuntime.h" 5 | 6 | 7 | // Based off "Efficient Polygon Triangulation" algorithm by John W. Ratcliff (http://flipcode.net/archives/Efficient_Polygon_Triangulation.shtml) 8 | bool FPolygonTools::TriangulatePolygon( const TArray& Polygon, TArray& TempIndices, TArray& TriangulatedIndices, bool& OutWindsClockwise ) 9 | { 10 | checkSlow( &TempIndices != &TriangulatedIndices ); 11 | TriangulatedIndices.Reset(); 12 | OutWindsClockwise = false; 13 | 14 | int32 NumVertices = Polygon.Num(); 15 | if( NumVertices < 3 ) 16 | { 17 | return false; 18 | } 19 | 20 | // Allocate and initialize a list of vertex indices for the new polygon 21 | TempIndices.SetNumUninitialized( NumVertices ); 22 | int32* VertexIndices = TempIndices.GetData(); 23 | 24 | // We want a counter-clockwise polygon 25 | OutWindsClockwise = Area( Polygon ) < 0.0f; 26 | if( !OutWindsClockwise ) 27 | { 28 | for( int32 PointIndex = 0; PointIndex < NumVertices; PointIndex++ ) 29 | { 30 | VertexIndices[ PointIndex ] = PointIndex; 31 | } 32 | } 33 | else 34 | { 35 | for( int32 PointIndex = 0; PointIndex < NumVertices; PointIndex++ ) 36 | { 37 | VertexIndices[ PointIndex ] = ( NumVertices - 1 ) - PointIndex; 38 | } 39 | } 40 | 41 | // Remove NumVertices-2 vertices, creating one triangle every time 42 | int32 ErrorDetectionCounter = 2 * NumVertices; 43 | for( int32 V = NumVertices - 1; NumVertices > 2; ) 44 | { 45 | // If we loop, it is probably a non-simple polygon 46 | if( --ErrorDetectionCounter <= 0 ) 47 | { 48 | // Bad polygon! This can happen if there are adjacent points that are exactly overlapping 49 | return false; 50 | } 51 | 52 | // Three consecutive vertices in current polygon, 53 | const int32 U = ( V < NumVertices ) ? V : 0; 54 | V = ( ( U + 1 ) < NumVertices ) ? U + 1 : 0; 55 | const int32 W = ( ( V + 1 ) < NumVertices ) ? V + 1 : 0; 56 | 57 | if( Snip( Polygon, U, V, W, NumVertices, VertexIndices ) ) 58 | { 59 | // Output triangle as indices into the original polygon array 60 | TriangulatedIndices.Add( VertexIndices[ U ] ); 61 | TriangulatedIndices.Add( VertexIndices[ V ] ); 62 | TriangulatedIndices.Add( VertexIndices[ W ] ); 63 | 64 | /* Remove V from remaining polygon */ 65 | for( int32 S = V, T = V + 1; T < NumVertices; S++, T++ ) 66 | { 67 | VertexIndices[ S ] = VertexIndices[ T ]; 68 | } 69 | NumVertices--; 70 | 71 | // Reset error detection counter 72 | ErrorDetectionCounter = 2 * NumVertices; 73 | } 74 | } 75 | 76 | return true; 77 | } 78 | 79 | -------------------------------------------------------------------------------- /Source/StreetMapRuntime/Private/StreetMap.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #include "StreetMap.h" 4 | #include "StreetMapRuntime.h" 5 | #include "EditorFramework/AssetImportData.h" 6 | 7 | 8 | UStreetMap::UStreetMap() 9 | { 10 | #if WITH_EDITORONLY_DATA 11 | if( !HasAnyFlags( RF_ClassDefaultObject ) ) 12 | { 13 | AssetImportData = NewObject( this, TEXT( "AssetImportData" ) ); 14 | } 15 | #endif 16 | } 17 | 18 | 19 | void UStreetMap::GetAssetRegistryTags( TArray& OutTags ) const 20 | { 21 | #if WITH_EDITORONLY_DATA 22 | if( AssetImportData ) 23 | { 24 | OutTags.Add( FAssetRegistryTag( SourceFileTagName(), AssetImportData->GetSourceData().ToJson(), FAssetRegistryTag::TT_Hidden ) ); 25 | } 26 | #endif 27 | 28 | Super::GetAssetRegistryTags( OutTags ); 29 | } 30 | -------------------------------------------------------------------------------- /Source/StreetMapRuntime/Private/StreetMapActor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "StreetMapActor.h" 4 | #include "StreetMapRuntime.h" 5 | #include "StreetMapComponent.h" 6 | 7 | 8 | AStreetMapActor::AStreetMapActor(const FObjectInitializer& ObjectInitializer) 9 | : Super(ObjectInitializer) 10 | { 11 | StreetMapComponent = CreateDefaultSubobject(TEXT("StreetMapComp")); 12 | RootComponent = StreetMapComponent; 13 | } -------------------------------------------------------------------------------- /Source/StreetMapRuntime/Private/StreetMapComponent.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #include "StreetMapComponent.h" 4 | #include "StreetMapRuntime.h" 5 | #include "StreetMapSceneProxy.h" 6 | #include "Runtime/Engine/Classes/Engine/StaticMesh.h" 7 | #include "Runtime/Engine/Public/StaticMeshResources.h" 8 | #include "PolygonTools.h" 9 | 10 | #include "PhysicsEngine/BodySetup.h" 11 | 12 | #if WITH_EDITOR 13 | #include "Modules/ModuleManager.h" 14 | #include "PropertyEditorModule.h" 15 | #endif //WITH_EDITOR 16 | 17 | 18 | 19 | UStreetMapComponent::UStreetMapComponent(const FObjectInitializer& ObjectInitializer) 20 | : Super(ObjectInitializer), 21 | StreetMap(nullptr), 22 | CachedLocalBounds(ForceInit) 23 | { 24 | // We make sure our mesh collision profile name is set to NoCollisionProfileName at initialization. 25 | // Because we don't have collision data yet! 26 | SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); 27 | 28 | // We don't currently need to be ticked. This can be overridden in a derived class though. 29 | PrimaryComponentTick.bCanEverTick = false; 30 | this->bAutoActivate = false; // NOTE: Components instantiated through C++ are not automatically active, so they'll only tick once and then go to sleep! 31 | 32 | // We don't currently need InitializeComponent() to be called on us. This can be overridden in a 33 | // derived class though. 34 | bWantsInitializeComponent = false; 35 | 36 | // Turn on shadows. It looks better. 37 | CastShadow = true; 38 | 39 | // Our mesh is too complicated to be a useful occluder. 40 | bUseAsOccluder = false; 41 | 42 | // Our mesh can influence navigation. 43 | bCanEverAffectNavigation = true; 44 | 45 | static ConstructorHelpers::FObjectFinder DefaultMaterialAsset(TEXT("/StreetMap/StreetMapDefaultMaterial")); 46 | StreetMapDefaultMaterial = DefaultMaterialAsset.Object; 47 | 48 | } 49 | 50 | 51 | FPrimitiveSceneProxy* UStreetMapComponent::CreateSceneProxy() 52 | { 53 | FStreetMapSceneProxy* StreetMapSceneProxy = nullptr; 54 | 55 | if( HasValidMesh() ) 56 | { 57 | StreetMapSceneProxy = new FStreetMapSceneProxy( this ); 58 | StreetMapSceneProxy->Init( this, Vertices, Indices ); 59 | } 60 | 61 | return StreetMapSceneProxy; 62 | } 63 | 64 | 65 | int32 UStreetMapComponent::GetNumMaterials() const 66 | { 67 | // NOTE: This is a bit of a weird thing about Unreal that we need to deal with when defining a component that 68 | // can have materials assigned. UPrimitiveComponent::GetNumMaterials() will return 0, so we need to override it 69 | // to return the number of overridden materials, which are the actual materials assigned to the component. 70 | return HasValidMesh() ? GetNumMeshSections() : GetNumOverrideMaterials(); 71 | } 72 | 73 | 74 | void UStreetMapComponent::SetStreetMap(class UStreetMap* NewStreetMap, bool bClearPreviousMeshIfAny /*= false*/, bool bRebuildMesh /*= false */) 75 | { 76 | if (StreetMap != NewStreetMap) 77 | { 78 | StreetMap = NewStreetMap; 79 | 80 | if (bClearPreviousMeshIfAny) 81 | InvalidateMesh(); 82 | 83 | if (bRebuildMesh) 84 | BuildMesh(); 85 | } 86 | } 87 | 88 | 89 | bool UStreetMapComponent::GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData) 90 | { 91 | 92 | if (!CollisionSettings.bGenerateCollision || !HasValidMesh()) 93 | { 94 | return false; 95 | } 96 | 97 | // Copy vertices data 98 | const int32 NumVertices = Vertices.Num(); 99 | CollisionData->Vertices.Empty(); 100 | CollisionData->Vertices.AddUninitialized(NumVertices); 101 | 102 | for (int32 VertexIndex = 0; VertexIndex < NumVertices; VertexIndex++) 103 | { 104 | CollisionData->Vertices[VertexIndex] = Vertices[VertexIndex].Position; 105 | } 106 | 107 | // Copy indices data 108 | const int32 NumTriangles = Indices.Num() / 3; 109 | FTriIndices TempTriangle; 110 | for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles * 3; TriangleIndex += 3) 111 | { 112 | 113 | TempTriangle.v0 = Indices[TriangleIndex + 0]; 114 | TempTriangle.v1 = Indices[TriangleIndex + 1]; 115 | TempTriangle.v2 = Indices[TriangleIndex + 2]; 116 | 117 | 118 | CollisionData->Indices.Add(TempTriangle); 119 | CollisionData->MaterialIndices.Add(0); 120 | } 121 | 122 | CollisionData->bFlipNormals = true; 123 | CollisionData->bDeformableMesh = true; 124 | 125 | return HasValidMesh(); 126 | } 127 | 128 | 129 | bool UStreetMapComponent::ContainsPhysicsTriMeshData(bool InUseAllTriData) const 130 | { 131 | return HasValidMesh() && CollisionSettings.bGenerateCollision; 132 | } 133 | 134 | 135 | bool UStreetMapComponent::WantsNegXTriMesh() 136 | { 137 | return false; 138 | } 139 | 140 | 141 | void UStreetMapComponent::CreateBodySetupIfNeeded(bool bForceCreation /*= false*/) 142 | { 143 | if (StreetMapBodySetup == nullptr || bForceCreation == true) 144 | { 145 | // Creating new BodySetup Object. 146 | StreetMapBodySetup = NewObject(this); 147 | StreetMapBodySetup->BodySetupGuid = FGuid::NewGuid(); 148 | StreetMapBodySetup->bDoubleSidedGeometry = CollisionSettings.bAllowDoubleSidedGeometry; 149 | 150 | // shapes per poly shape for collision (Not working in simulation mode). 151 | StreetMapBodySetup->CollisionTraceFlag = CTF_UseComplexAsSimple; 152 | } 153 | } 154 | 155 | 156 | void UStreetMapComponent::GenerateCollision() 157 | { 158 | if (!CollisionSettings.bGenerateCollision || !HasValidMesh()) 159 | { 160 | return; 161 | } 162 | 163 | // create a new body setup 164 | CreateBodySetupIfNeeded(true); 165 | 166 | 167 | if (GetCollisionProfileName() == UCollisionProfile::NoCollision_ProfileName) 168 | { 169 | SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName); 170 | } 171 | 172 | // Rebuild the body setup 173 | StreetMapBodySetup->InvalidatePhysicsData(); 174 | StreetMapBodySetup->CreatePhysicsMeshes(); 175 | UpdateNavigationIfNeeded(); 176 | } 177 | 178 | 179 | void UStreetMapComponent::ClearCollision() 180 | { 181 | 182 | if (StreetMapBodySetup != nullptr) 183 | { 184 | StreetMapBodySetup->InvalidatePhysicsData(); 185 | StreetMapBodySetup = nullptr; 186 | } 187 | 188 | if (GetCollisionProfileName() != UCollisionProfile::NoCollision_ProfileName) 189 | { 190 | SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); 191 | } 192 | 193 | UpdateNavigationIfNeeded(); 194 | } 195 | 196 | class UBodySetup* UStreetMapComponent::GetBodySetup() 197 | { 198 | if (CollisionSettings.bGenerateCollision == true) 199 | { 200 | // checking if we have a valid body setup. 201 | // A new one is created only if a valid body setup is not found. 202 | CreateBodySetupIfNeeded(); 203 | return StreetMapBodySetup; 204 | } 205 | 206 | if (StreetMapBodySetup != nullptr) StreetMapBodySetup = nullptr; 207 | 208 | return nullptr; 209 | } 210 | 211 | void UStreetMapComponent::GenerateMesh() 212 | { 213 | ///////////////////////////////////////////////////////// 214 | // Visual tweakables for generated Street Map mesh 215 | // 216 | const float RoadZ = MeshBuildSettings.RoadOffesetZ; 217 | const bool bWant3DBuildings = MeshBuildSettings.bWant3DBuildings; 218 | const float BuildingLevelFloorFactor = MeshBuildSettings.BuildingLevelFloorFactor; 219 | const bool bWantLitBuildings = MeshBuildSettings.bWantLitBuildings; 220 | const bool bWantBuildingBorderOnGround = !bWant3DBuildings; 221 | const float StreetThickness = MeshBuildSettings.StreetThickness; 222 | const FColor StreetColor = MeshBuildSettings.StreetColor.ToFColor( false ); 223 | const float MajorRoadThickness = MeshBuildSettings.MajorRoadThickness; 224 | const FColor MajorRoadColor = MeshBuildSettings.MajorRoadColor.ToFColor( false ); 225 | const float HighwayThickness = MeshBuildSettings.HighwayThickness; 226 | const FColor HighwayColor = MeshBuildSettings.HighwayColor.ToFColor( false ); 227 | const float BuildingBorderThickness = MeshBuildSettings.BuildingBorderThickness; 228 | FLinearColor BuildingBorderLinearColor = MeshBuildSettings.BuildingBorderLinearColor; 229 | const float BuildingBorderZ = MeshBuildSettings.BuildingBorderZ; 230 | const FColor BuildingBorderColor( BuildingBorderLinearColor.ToFColor( false ) ); 231 | const FColor BuildingFillColor( FLinearColor( BuildingBorderLinearColor * 0.33f ).CopyWithNewOpacity( 1.0f ).ToFColor( false ) ); 232 | ///////////////////////////////////////////////////////// 233 | 234 | 235 | CachedLocalBounds = FBox( ForceInit ); 236 | Vertices.Reset(); 237 | Indices.Reset(); 238 | 239 | if( StreetMap != nullptr ) 240 | { 241 | FBox MeshBoundingBox; 242 | MeshBoundingBox.Init(); 243 | 244 | const auto& Roads = StreetMap->GetRoads(); 245 | const auto& Nodes = StreetMap->GetNodes(); 246 | const auto& Buildings = StreetMap->GetBuildings(); 247 | 248 | for( const auto& Road : Roads ) 249 | { 250 | float RoadThickness = StreetThickness; 251 | FColor RoadColor = StreetColor; 252 | switch( Road.RoadType ) 253 | { 254 | case EStreetMapRoadType::Highway: 255 | RoadThickness = HighwayThickness; 256 | RoadColor = HighwayColor; 257 | break; 258 | 259 | case EStreetMapRoadType::MajorRoad: 260 | RoadThickness = MajorRoadThickness; 261 | RoadColor = MajorRoadColor; 262 | break; 263 | 264 | case EStreetMapRoadType::Street: 265 | case EStreetMapRoadType::Other: 266 | break; 267 | 268 | default: 269 | check( 0 ); 270 | break; 271 | } 272 | 273 | for( int32 PointIndex = 0; PointIndex < Road.RoadPoints.Num() - 1; ++PointIndex ) 274 | { 275 | AddThick2DLine( 276 | Road.RoadPoints[ PointIndex ], 277 | Road.RoadPoints[ PointIndex + 1 ], 278 | RoadZ, 279 | RoadThickness, 280 | RoadColor, 281 | RoadColor, 282 | MeshBoundingBox ); 283 | } 284 | } 285 | 286 | TArray< int32 > TempIndices; 287 | TArray< int32 > TriangulatedVertexIndices; 288 | TArray< FVector > TempPoints; 289 | for( int32 BuildingIndex = 0; BuildingIndex < Buildings.Num(); ++BuildingIndex ) 290 | { 291 | const auto& Building = Buildings[ BuildingIndex ]; 292 | 293 | // Building mesh (or filled area, if the building has no height) 294 | 295 | // Triangulate this building 296 | // @todo: Performance: Triangulating lots of building polygons is quite slow. We could easily do this 297 | // as part of the import process and store tessellated geometry instead of doing this at load time. 298 | bool WindsClockwise; 299 | if( FPolygonTools::TriangulatePolygon( Building.BuildingPoints, TempIndices, /* Out */ TriangulatedVertexIndices, /* Out */ WindsClockwise ) ) 300 | { 301 | // @todo: Performance: We could preprocess the building shapes so that the points always wind 302 | // in a consistent direction, so we can skip determining the winding above. 303 | 304 | const int32 FirstTopVertexIndex = this->Vertices.Num(); 305 | 306 | // calculate fill Z for buildings 307 | // either use the defined height or extrapolate from building level count 308 | float BuildingFillZ = 0.0f; 309 | if (bWant3DBuildings) { 310 | if (Building.Height > 0) { 311 | BuildingFillZ = Building.Height; 312 | } 313 | else if (Building.BuildingLevels > 0) { 314 | BuildingFillZ = (float)Building.BuildingLevels * BuildingLevelFloorFactor; 315 | } 316 | } 317 | 318 | // Top of building 319 | { 320 | TempPoints.SetNum( Building.BuildingPoints.Num(), false ); 321 | for( int32 PointIndex = 0; PointIndex < Building.BuildingPoints.Num(); ++PointIndex ) 322 | { 323 | TempPoints[ PointIndex ] = FVector( Building.BuildingPoints[ ( Building.BuildingPoints.Num() - PointIndex ) - 1 ], BuildingFillZ ); 324 | } 325 | AddTriangles( TempPoints, TriangulatedVertexIndices, FVector::ForwardVector, FVector::UpVector, BuildingFillColor, MeshBoundingBox ); 326 | } 327 | 328 | if( bWant3DBuildings && (Building.Height > KINDA_SMALL_NUMBER || Building.BuildingLevels > 0) ) 329 | { 330 | // NOTE: Lit buildings can't share vertices beyond quads (all quads have their own face normals), so this uses a lot more geometry! 331 | if( bWantLitBuildings ) 332 | { 333 | // Create edges for the walls of the 3D buildings 334 | for( int32 LeftPointIndex = 0; LeftPointIndex < Building.BuildingPoints.Num(); ++LeftPointIndex ) 335 | { 336 | const int32 RightPointIndex = ( LeftPointIndex + 1 ) % Building.BuildingPoints.Num(); 337 | 338 | TempPoints.SetNum( 4, false ); 339 | 340 | const int32 TopLeftVertexIndex = 0; 341 | TempPoints[ TopLeftVertexIndex ] = FVector( Building.BuildingPoints[ WindsClockwise ? RightPointIndex : LeftPointIndex ], BuildingFillZ ); 342 | 343 | const int32 TopRightVertexIndex = 1; 344 | TempPoints[ TopRightVertexIndex ] = FVector( Building.BuildingPoints[ WindsClockwise ? LeftPointIndex : RightPointIndex ], BuildingFillZ ); 345 | 346 | const int32 BottomRightVertexIndex = 2; 347 | TempPoints[ BottomRightVertexIndex ] = FVector( Building.BuildingPoints[ WindsClockwise ? LeftPointIndex : RightPointIndex ], 0.0f ); 348 | 349 | const int32 BottomLeftVertexIndex = 3; 350 | TempPoints[ BottomLeftVertexIndex ] = FVector( Building.BuildingPoints[ WindsClockwise ? RightPointIndex : LeftPointIndex ], 0.0f ); 351 | 352 | 353 | TempIndices.SetNum( 6, false ); 354 | 355 | TempIndices[ 0 ] = BottomLeftVertexIndex; 356 | TempIndices[ 1 ] = TopLeftVertexIndex; 357 | TempIndices[ 2 ] = BottomRightVertexIndex; 358 | 359 | TempIndices[ 3 ] = BottomRightVertexIndex; 360 | TempIndices[ 4 ] = TopLeftVertexIndex; 361 | TempIndices[ 5 ] = TopRightVertexIndex; 362 | 363 | const FVector FaceNormal = FVector::CrossProduct( ( TempPoints[ 0 ] - TempPoints[ 2 ] ).GetSafeNormal(), ( TempPoints[ 0 ] - TempPoints[ 1 ] ).GetSafeNormal() ); 364 | const FVector ForwardVector = FVector::UpVector; 365 | const FVector UpVector = FaceNormal; 366 | AddTriangles( TempPoints, TempIndices, ForwardVector, UpVector, BuildingFillColor, MeshBoundingBox ); 367 | } 368 | } 369 | else 370 | { 371 | // Create vertices for the bottom 372 | const int32 FirstBottomVertexIndex = this->Vertices.Num(); 373 | for( int32 PointIndex = 0; PointIndex < Building.BuildingPoints.Num(); ++PointIndex ) 374 | { 375 | const FVector2D Point = Building.BuildingPoints[ PointIndex ]; 376 | 377 | FStreetMapVertex& NewVertex = *new( this->Vertices )FStreetMapVertex(); 378 | NewVertex.Position = FVector( Point, 0.0f ); 379 | NewVertex.TextureCoordinate = FVector2D( 0.0f, 0.0f ); // NOTE: We're not using texture coordinates for anything yet 380 | NewVertex.TangentX = FVector::ForwardVector; // NOTE: Tangents aren't important for these unlit buildings 381 | NewVertex.TangentZ = FVector::UpVector; 382 | NewVertex.Color = BuildingFillColor; 383 | 384 | MeshBoundingBox += NewVertex.Position; 385 | } 386 | 387 | // Create edges for the walls of the 3D buildings 388 | for( int32 LeftPointIndex = 0; LeftPointIndex < Building.BuildingPoints.Num(); ++LeftPointIndex ) 389 | { 390 | const int32 RightPointIndex = ( LeftPointIndex + 1 ) % Building.BuildingPoints.Num(); 391 | 392 | const int32 BottomLeftVertexIndex = FirstBottomVertexIndex + LeftPointIndex; 393 | const int32 BottomRightVertexIndex = FirstBottomVertexIndex + RightPointIndex; 394 | const int32 TopRightVertexIndex = FirstTopVertexIndex + RightPointIndex; 395 | const int32 TopLeftVertexIndex = FirstTopVertexIndex + LeftPointIndex; 396 | 397 | this->Indices.Add( BottomLeftVertexIndex ); 398 | this->Indices.Add( TopLeftVertexIndex ); 399 | this->Indices.Add( BottomRightVertexIndex ); 400 | 401 | this->Indices.Add( BottomRightVertexIndex ); 402 | this->Indices.Add( TopLeftVertexIndex ); 403 | this->Indices.Add( TopRightVertexIndex ); 404 | } 405 | } 406 | } 407 | } 408 | else 409 | { 410 | // @todo: Triangulation failed for some reason, possibly due to degenerate polygons. We can 411 | // probably improve the algorithm to avoid this happening. 412 | } 413 | 414 | // Building border 415 | if( bWantBuildingBorderOnGround ) 416 | { 417 | for( int32 PointIndex = 0; PointIndex < Building.BuildingPoints.Num(); ++PointIndex ) 418 | { 419 | AddThick2DLine( 420 | Building.BuildingPoints[ PointIndex ], 421 | Building.BuildingPoints[ ( PointIndex + 1 ) % Building.BuildingPoints.Num() ], 422 | BuildingBorderZ, 423 | BuildingBorderThickness, // Thickness 424 | BuildingBorderColor, 425 | BuildingBorderColor, 426 | MeshBoundingBox ); 427 | } 428 | } 429 | } 430 | 431 | CachedLocalBounds = MeshBoundingBox; 432 | } 433 | } 434 | 435 | 436 | #if WITH_EDITOR 437 | void UStreetMapComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) 438 | { 439 | bool bNeedRefreshCustomizationModule = false; 440 | 441 | // Check to see if the "StreetMap" property changed. 442 | if (PropertyChangedEvent.Property != nullptr) 443 | { 444 | const FName PropertyName(PropertyChangedEvent.Property->GetFName()); 445 | if (PropertyName == GET_MEMBER_NAME_CHECKED(UStreetMapComponent, StreetMap)) 446 | { 447 | bNeedRefreshCustomizationModule = true; 448 | } 449 | else if (IsCollisionProperty(PropertyName)) // For some unknown reason , GET_MEMBER_NAME_CHECKED(UStreetMapComponent, CollisionSettings) is not working ??? "TO CHECK LATER" 450 | { 451 | if (CollisionSettings.bGenerateCollision == true) 452 | { 453 | GenerateCollision(); 454 | } 455 | else 456 | { 457 | ClearCollision(); 458 | } 459 | bNeedRefreshCustomizationModule = true; 460 | } 461 | } 462 | 463 | if (bNeedRefreshCustomizationModule) 464 | { 465 | FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); 466 | PropertyModule.NotifyCustomizationModuleChanged(); 467 | } 468 | 469 | // Call the parent implementation of this function 470 | Super::PostEditChangeProperty(PropertyChangedEvent); 471 | } 472 | #endif // WITH_EDITOR 473 | 474 | 475 | void UStreetMapComponent::BuildMesh() 476 | { 477 | // Wipes out our cached mesh data. Maybe unnecessary in case GenerateMesh is clearing cached mesh data and creating a new SceneProxy ! 478 | InvalidateMesh(); 479 | 480 | GenerateMesh(); 481 | 482 | if (HasValidMesh()) 483 | { 484 | // We have a new bounding box 485 | UpdateBounds(); 486 | } 487 | else 488 | { 489 | // No mesh was generated 490 | } 491 | 492 | GenerateCollision(); 493 | 494 | // Mark our render state dirty so that CreateSceneProxy can refresh it on demand 495 | MarkRenderStateDirty(); 496 | 497 | AssignDefaultMaterialIfNeeded(); 498 | 499 | Modify(); 500 | } 501 | 502 | 503 | void UStreetMapComponent::AssignDefaultMaterialIfNeeded() 504 | { 505 | if (this->GetNumMaterials() == 0 || this->GetMaterial(0) == nullptr) 506 | { 507 | if (!HasValidMesh() || GetDefaultMaterial() == nullptr) 508 | return; 509 | 510 | this->SetMaterial(0, GetDefaultMaterial()); 511 | } 512 | } 513 | 514 | 515 | void UStreetMapComponent::UpdateNavigationIfNeeded() 516 | { 517 | if (bCanEverAffectNavigation || bNavigationRelevant) 518 | { 519 | FNavigationSystem::UpdateComponentData(*this); 520 | } 521 | } 522 | 523 | void UStreetMapComponent::InvalidateMesh() 524 | { 525 | Vertices.Reset(); 526 | Indices.Reset(); 527 | CachedLocalBounds = FBoxSphereBounds(FBox(ForceInit)); 528 | ClearCollision(); 529 | // Mark our render state dirty so that CreateSceneProxy can refresh it on demand 530 | MarkRenderStateDirty(); 531 | Modify(); 532 | } 533 | 534 | FBoxSphereBounds UStreetMapComponent::CalcBounds( const FTransform& LocalToWorld ) const 535 | { 536 | if( HasValidMesh() ) 537 | { 538 | FBoxSphereBounds WorldSpaceBounds = CachedLocalBounds.TransformBy( LocalToWorld ); 539 | WorldSpaceBounds.BoxExtent *= BoundsScale; 540 | WorldSpaceBounds.SphereRadius *= BoundsScale; 541 | return WorldSpaceBounds; 542 | } 543 | else 544 | { 545 | return FBoxSphereBounds( LocalToWorld.GetLocation(), FVector::ZeroVector, 0.0f ); 546 | } 547 | } 548 | 549 | 550 | void UStreetMapComponent::AddThick2DLine( const FVector2D Start, const FVector2D End, const float Z, const float Thickness, const FColor& StartColor, const FColor& EndColor, FBox& MeshBoundingBox ) 551 | { 552 | const float HalfThickness = Thickness * 0.5f; 553 | 554 | const FVector2D LineDirection = ( End - Start ).GetSafeNormal(); 555 | const FVector2D RightVector( -LineDirection.Y, LineDirection.X ); 556 | 557 | const int32 BottomLeftVertexIndex = Vertices.Num(); 558 | FStreetMapVertex& BottomLeftVertex = *new( Vertices )FStreetMapVertex(); 559 | BottomLeftVertex.Position = FVector( Start - RightVector * HalfThickness, Z ); 560 | BottomLeftVertex.TextureCoordinate = FVector2D( 0.0f, 0.0f ); 561 | BottomLeftVertex.TangentX = FVector( LineDirection, 0.0f ); 562 | BottomLeftVertex.TangentZ = FVector::UpVector; 563 | BottomLeftVertex.Color = StartColor; 564 | MeshBoundingBox += BottomLeftVertex.Position; 565 | 566 | const int32 BottomRightVertexIndex = Vertices.Num(); 567 | FStreetMapVertex& BottomRightVertex = *new( Vertices )FStreetMapVertex(); 568 | BottomRightVertex.Position = FVector( Start + RightVector * HalfThickness, Z ); 569 | BottomRightVertex.TextureCoordinate = FVector2D( 1.0f, 0.0f ); 570 | BottomRightVertex.TangentX = FVector( LineDirection, 0.0f ); 571 | BottomRightVertex.TangentZ = FVector::UpVector; 572 | BottomRightVertex.Color = StartColor; 573 | MeshBoundingBox += BottomRightVertex.Position; 574 | 575 | const int32 TopRightVertexIndex = Vertices.Num(); 576 | FStreetMapVertex& TopRightVertex = *new( Vertices )FStreetMapVertex(); 577 | TopRightVertex.Position = FVector( End + RightVector * HalfThickness, Z ); 578 | TopRightVertex.TextureCoordinate = FVector2D( 1.0f, 1.0f ); 579 | TopRightVertex.TangentX = FVector( LineDirection, 0.0f ); 580 | TopRightVertex.TangentZ = FVector::UpVector; 581 | TopRightVertex.Color = EndColor; 582 | MeshBoundingBox += TopRightVertex.Position; 583 | 584 | const int32 TopLeftVertexIndex = Vertices.Num(); 585 | FStreetMapVertex& TopLeftVertex = *new( Vertices )FStreetMapVertex(); 586 | TopLeftVertex.Position = FVector( End - RightVector * HalfThickness, Z ); 587 | TopLeftVertex.TextureCoordinate = FVector2D( 0.0f, 1.0f ); 588 | TopLeftVertex.TangentX = FVector( LineDirection, 0.0f ); 589 | TopLeftVertex.TangentZ = FVector::UpVector; 590 | TopLeftVertex.Color = EndColor; 591 | MeshBoundingBox += TopLeftVertex.Position; 592 | 593 | Indices.Add( BottomLeftVertexIndex ); 594 | Indices.Add( BottomRightVertexIndex ); 595 | Indices.Add( TopRightVertexIndex ); 596 | 597 | Indices.Add( BottomLeftVertexIndex ); 598 | Indices.Add( TopRightVertexIndex ); 599 | Indices.Add( TopLeftVertexIndex ); 600 | }; 601 | 602 | 603 | void UStreetMapComponent::AddTriangles( const TArray& Points, const TArray& PointIndices, const FVector& ForwardVector, const FVector& UpVector, const FColor& Color, FBox& MeshBoundingBox ) 604 | { 605 | const int32 FirstVertexIndex = Vertices.Num(); 606 | 607 | for( FVector Point : Points ) 608 | { 609 | FStreetMapVertex& NewVertex = *new( Vertices )FStreetMapVertex(); 610 | NewVertex.Position = Point; 611 | NewVertex.TextureCoordinate = FVector2D( 0.0f, 0.0f ); // NOTE: We're not using texture coordinates for anything yet 612 | NewVertex.TangentX = ForwardVector; 613 | NewVertex.TangentZ = UpVector; 614 | NewVertex.Color = Color; 615 | 616 | MeshBoundingBox += NewVertex.Position; 617 | } 618 | 619 | for( int32 PointIndex : PointIndices ) 620 | { 621 | Indices.Add( FirstVertexIndex + PointIndex ); 622 | } 623 | }; 624 | 625 | 626 | FString UStreetMapComponent::GetStreetMapAssetName() const 627 | { 628 | return StreetMap != nullptr ? StreetMap->GetName() : FString(TEXT("NONE")); 629 | } 630 | 631 | -------------------------------------------------------------------------------- /Source/StreetMapRuntime/Private/StreetMapRuntime.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #include "StreetMapRuntime.h" 4 | #include "Modules/ModuleManager.h" 5 | 6 | 7 | class FStreetMapRuntimeModule : public IModuleInterface 8 | { 9 | 10 | public: 11 | 12 | // IModuleInterface interface 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | 16 | }; 17 | 18 | 19 | IMPLEMENT_MODULE( FStreetMapRuntimeModule, StreetMapRuntime ) 20 | 21 | 22 | 23 | void FStreetMapRuntimeModule::StartupModule() 24 | { 25 | } 26 | 27 | 28 | void FStreetMapRuntimeModule::ShutdownModule() 29 | { 30 | } 31 | 32 | -------------------------------------------------------------------------------- /Source/StreetMapRuntime/Private/StreetMapSceneProxy.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #include "StreetMapSceneProxy.h" 4 | #include "StreetMapRuntime.h" 5 | #include "StreetMapComponent.h" 6 | #include "Runtime/Engine/Public/SceneManagement.h" 7 | #include "Runtime/Renderer/Public/MeshPassProcessor.h" 8 | #include "Runtime/Renderer/Public/PrimitiveSceneInfo.h" 9 | 10 | 11 | FStreetMapSceneProxy::FStreetMapSceneProxy(const UStreetMapComponent* InComponent) 12 | : FPrimitiveSceneProxy(InComponent), 13 | VertexFactory(GetScene().GetFeatureLevel(), "FStreetMapSceneProxy"), 14 | StreetMapComp(InComponent), 15 | CollisionResponse(InComponent->GetCollisionResponseToChannels()) 16 | { 17 | 18 | } 19 | 20 | void FStreetMapSceneProxy::Init(const UStreetMapComponent* InComponent, const TArray< FStreetMapVertex >& Vertices, const TArray< uint32 >& Indices) 21 | { 22 | // Copy index buffer 23 | IndexBuffer32.Indices = Indices; 24 | 25 | MaterialInterface = nullptr; 26 | this->MaterialRelevance = InComponent->GetMaterialRelevance(GetScene().GetFeatureLevel()); 27 | 28 | // Copy vertex data 29 | const int32 NumVerts = Vertices.Num(); 30 | TArray DynamicVertices; 31 | DynamicVertices.SetNumUninitialized(NumVerts); 32 | 33 | for (int VertIdx = 0; VertIdx < NumVerts; VertIdx++) 34 | { 35 | const FStreetMapVertex& StreetMapVert = Vertices[VertIdx]; 36 | FDynamicMeshVertex& Vert = DynamicVertices[VertIdx]; 37 | Vert.Position = StreetMapVert.Position; 38 | Vert.Color = StreetMapVert.Color; 39 | Vert.TextureCoordinate[0] = StreetMapVert.TextureCoordinate; 40 | Vert.TangentX = StreetMapVert.TangentX; 41 | Vert.TangentZ = StreetMapVert.TangentZ; 42 | } 43 | 44 | VertexBuffer.InitFromDynamicVertex(&VertexFactory, DynamicVertices); 45 | 46 | // Enqueue initialization of render resource 47 | InitResources(); 48 | 49 | // Set a material 50 | { 51 | if (InComponent->GetNumMaterials() > 0) 52 | { 53 | MaterialInterface = InComponent->GetMaterial(0); 54 | } 55 | 56 | // Use the default material if we don't have one set 57 | if (MaterialInterface == nullptr) 58 | { 59 | MaterialInterface = UMaterial::GetDefaultMaterial(MD_Surface); 60 | } 61 | } 62 | } 63 | 64 | FStreetMapSceneProxy::~FStreetMapSceneProxy() 65 | { 66 | VertexBuffer.PositionVertexBuffer.ReleaseResource(); 67 | VertexBuffer.StaticMeshVertexBuffer.ReleaseResource(); 68 | VertexBuffer.ColorVertexBuffer.ReleaseResource(); 69 | IndexBuffer32.ReleaseResource(); 70 | VertexFactory.ReleaseResource(); 71 | } 72 | 73 | 74 | SIZE_T FStreetMapSceneProxy::GetTypeHash() const 75 | { 76 | static size_t UniquePointer; 77 | return reinterpret_cast(&UniquePointer); 78 | } 79 | 80 | void FStreetMapSceneProxy::InitResources() 81 | { 82 | // Start initializing our vertex buffer, index buffer, and vertex factory. This will be kicked off on the render thread. 83 | BeginInitResource(&VertexBuffer.PositionVertexBuffer); 84 | BeginInitResource(&VertexBuffer.StaticMeshVertexBuffer); 85 | BeginInitResource(&VertexBuffer.ColorVertexBuffer); 86 | BeginInitResource(&IndexBuffer32); 87 | BeginInitResource(&VertexFactory); 88 | } 89 | 90 | bool FStreetMapSceneProxy::MustDrawMeshDynamically( const FSceneView& View ) const 91 | { 92 | return ( AllowDebugViewmodes() && View.Family->EngineShowFlags.Wireframe ) || IsSelected(); 93 | } 94 | 95 | 96 | bool FStreetMapSceneProxy::IsInCollisionView(const FEngineShowFlags& EngineShowFlags) const 97 | { 98 | return EngineShowFlags.CollisionVisibility || EngineShowFlags.CollisionPawn; 99 | } 100 | 101 | FPrimitiveViewRelevance FStreetMapSceneProxy::GetViewRelevance( const FSceneView* View ) const 102 | { 103 | FPrimitiveViewRelevance Result; 104 | Result.bDrawRelevance = IsShown(View); 105 | Result.bShadowRelevance = IsShadowCast(View); 106 | 107 | const bool bAlwaysHasDynamicData = false; 108 | 109 | // Only draw dynamically if we're drawing in wireframe or we're selected in the editor 110 | Result.bDynamicRelevance = MustDrawMeshDynamically( *View ) || bAlwaysHasDynamicData; 111 | Result.bStaticRelevance = !MustDrawMeshDynamically( *View ); 112 | 113 | MaterialRelevance.SetPrimitiveViewRelevance(Result); 114 | return Result; 115 | } 116 | 117 | 118 | bool FStreetMapSceneProxy::CanBeOccluded() const 119 | { 120 | return !MaterialRelevance.bDisableDepthTest; 121 | } 122 | 123 | 124 | void FStreetMapSceneProxy::MakeMeshBatch( FMeshBatch& Mesh, class FMeshElementCollector& Collector, FMaterialRenderProxy* WireframeMaterialRenderProxyOrNull, bool bDrawCollision) const 125 | { 126 | FMaterialRenderProxy* MaterialProxy = NULL; 127 | if( WireframeMaterialRenderProxyOrNull != nullptr ) 128 | { 129 | MaterialProxy = WireframeMaterialRenderProxyOrNull; 130 | } 131 | else 132 | { 133 | if (bDrawCollision) 134 | { 135 | MaterialProxy = new FColoredMaterialRenderProxy(GEngine->ShadedLevelColorationUnlitMaterial->GetRenderProxy(), FLinearColor::Blue); 136 | } 137 | else if (MaterialProxy == nullptr) 138 | { 139 | MaterialProxy = StreetMapComp->GetDefaultMaterial()->GetRenderProxy(); 140 | } 141 | } 142 | 143 | FMeshBatchElement& BatchElement = Mesh.Elements[0]; 144 | 145 | BatchElement.IndexBuffer = &IndexBuffer32; 146 | Mesh.bWireframe = WireframeMaterialRenderProxyOrNull != nullptr; 147 | Mesh.VertexFactory = &VertexFactory; 148 | Mesh.MaterialRenderProxy = MaterialProxy; 149 | Mesh.CastShadow = true; 150 | // BatchElement.PrimitiveUniformBufferResource = &GetUniformBuffer(); 151 | // BatchElement.PrimitiveUniformBuffer = CreatePrimitiveUniformBufferImmediate(GetLocalToWorld(), GetBounds(), GetLocalBounds(), true, UseEditorDepthTest()); 152 | 153 | FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource(); 154 | DynamicPrimitiveUniformBuffer.Set(GetLocalToWorld(), GetLocalToWorld(), GetBounds(), GetLocalBounds(), true, false, DrawsVelocity(), false); 155 | BatchElement.PrimitiveUniformBufferResource = &DynamicPrimitiveUniformBuffer.UniformBuffer; 156 | 157 | BatchElement.FirstIndex = 0; 158 | const int IndexCount = IndexBuffer32.Indices.Num(); 159 | BatchElement.NumPrimitives = IndexCount / 3; 160 | BatchElement.MinVertexIndex = 0; 161 | BatchElement.MaxVertexIndex = VertexBuffer.PositionVertexBuffer.GetNumVertices() - 1; 162 | Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative(); 163 | Mesh.Type = PT_TriangleList; 164 | Mesh.DepthPriorityGroup = SDPG_World; 165 | 166 | 167 | } 168 | 169 | /* 170 | void FStreetMapSceneProxy::DrawStaticElements( FStaticPrimitiveDrawInterface* PDI ) 171 | { 172 | const int IndexCount = IndexBuffer32.Indices.Num(); 173 | if( VertexBuffer.PositionVertexBuffer.GetNumVertices() > 0 && IndexCount > 0 ) 174 | { 175 | const float ScreenSize = 1.0f; 176 | FMeshBatch MeshBatch; 177 | MakeMeshBatch( MeshBatch, nullptr); 178 | PDI->DrawMesh( MeshBatch, ScreenSize ); 179 | 180 | } 181 | } 182 | */ 183 | 184 | void FStreetMapSceneProxy::GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, class FMeshElementCollector& Collector) const 185 | { 186 | const int IndexCount = IndexBuffer32.Indices.Num(); 187 | if (VertexBuffer.PositionVertexBuffer.GetNumVertices() > 0 && IndexCount > 0) 188 | { 189 | for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex) 190 | { 191 | const FSceneView& View = *Views[ViewIndex]; 192 | 193 | const bool bIsWireframe = AllowDebugViewmodes() && View.Family->EngineShowFlags.Wireframe; 194 | 195 | FColoredMaterialRenderProxy* WireframeMaterialRenderProxy = GEngine->WireframeMaterial && bIsWireframe ? new FColoredMaterialRenderProxy(GEngine->WireframeMaterial->GetRenderProxy(), FLinearColor(0, 0.5f, 1.f)) : NULL; 196 | 197 | 198 | if (MustDrawMeshDynamically(View)) 199 | { 200 | const bool bInCollisionView = IsInCollisionView(ViewFamily.EngineShowFlags); 201 | const bool bCanDrawCollision = bInCollisionView && IsCollisionEnabled(); 202 | 203 | if (!IsCollisionEnabled() && bInCollisionView) 204 | { 205 | continue; 206 | } 207 | 208 | // Draw the mesh! 209 | FMeshBatch& MeshBatch = Collector.AllocateMesh(); 210 | MakeMeshBatch(MeshBatch, Collector, WireframeMaterialRenderProxy, bCanDrawCollision); 211 | Collector.AddMesh(ViewIndex, MeshBatch); 212 | } 213 | } 214 | } 215 | } 216 | 217 | 218 | uint32 FStreetMapSceneProxy::GetMemoryFootprint( void ) const 219 | { 220 | return sizeof( *this ) + GetAllocatedSize(); 221 | } 222 | -------------------------------------------------------------------------------- /Source/StreetMapRuntime/Public/PolygonTools.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | #pragma once 3 | 4 | class FPolygonTools 5 | { 6 | 7 | public: 8 | 9 | /** Triangulate a polygon given a list of contour points, then places results as indices into the original polygon array. Does not support polygons with holes. */ 10 | static bool TriangulatePolygon( const TArray& Polygon, TArray& TempIndices, TArray& TriangulatedIndices, bool& OutWindsClockwise ); 11 | 12 | /** Compute area of a polygon */ 13 | static inline float Area( const TArray& Polygon ); 14 | 15 | /** Determines if the specified point is inside the triangle defined by the three triangle corners */ 16 | static inline bool IsPointInsideTriangle( const FVector2D TriangleA, const FVector2D TriangleB, const FVector2D TriangleC, const FVector2D Point ); 17 | 18 | /** Given a 2D polygon and a point, determines whether the point is inside the polygon. Supports convex polygons. If the point is exactly on the polygon boundary, the return value could be either false or true. */ 19 | static inline bool IsPointInsidePolygon( const TArray& Polygon, const FVector2D Point ); 20 | 21 | 22 | private: 23 | 24 | /** Clips a polygon */ 25 | static inline bool Snip( const TArray& Polygon, const int32 U, const int32 V, const int32 W, const int32 PointCount, const int32* VertexIndices ); 26 | }; 27 | 28 | 29 | float FPolygonTools::Area( const TArray& Polygon ) 30 | { 31 | const int32 PointCount = Polygon.Num(); 32 | 33 | float HalfArea = 0.0f; 34 | for( int32 P = PointCount - 1, Q = 0; Q < PointCount; P = Q++ ) 35 | { 36 | HalfArea += Polygon[ P ].X * Polygon[ Q ].Y - Polygon[ Q ].X * Polygon[ P ].Y; 37 | } 38 | return HalfArea * 0.5f; 39 | } 40 | 41 | 42 | bool FPolygonTools::IsPointInsideTriangle( const FVector2D TriangleA, const FVector2D TriangleB, const FVector2D TriangleC, const FVector2D Point ) 43 | { 44 | const FVector2D BToC = TriangleC - TriangleB; 45 | const FVector2D CToA = TriangleA - TriangleC; 46 | const FVector2D AToB = TriangleB - TriangleA; 47 | 48 | const FVector2D AToP = Point - TriangleA; 49 | const FVector2D BToP = Point - TriangleB; 50 | const FVector2D CToP = Point - TriangleC; 51 | 52 | const float ACrossBP = BToC ^ BToP; 53 | const float CCrossAP = AToB ^ AToP; 54 | const float BCrossCP = CToA ^ CToP; 55 | 56 | return ( ( ACrossBP >= -SMALL_NUMBER ) && ( BCrossCP >= -SMALL_NUMBER ) && ( CCrossAP >= -SMALL_NUMBER ) ); 57 | }; 58 | 59 | 60 | bool FPolygonTools::IsPointInsidePolygon( const TArray& Polygon, const FVector2D Point ) 61 | { 62 | const int NumCorners = Polygon.Num(); 63 | int PreviousCornerIndex = NumCorners - 1; 64 | bool bIsInside = false; 65 | 66 | for( int CornerIndex = 0; CornerIndex < NumCorners; CornerIndex++ ) 67 | { 68 | if( ( ( Polygon[ CornerIndex ].Y < Point.Y && Polygon[ PreviousCornerIndex ].Y >= Point.Y ) || ( Polygon[ PreviousCornerIndex ].Y < Point.Y && Polygon[ CornerIndex ].Y >= Point.Y ) ) && 69 | ( Polygon[ CornerIndex ].X <= Point.X || Polygon[ PreviousCornerIndex ].X <= Point.X ) ) 70 | { 71 | bIsInside ^= ( Polygon[ CornerIndex ].X + ( Point.Y - Polygon[ CornerIndex ].Y ) / ( Polygon[ PreviousCornerIndex ].Y - Polygon[ CornerIndex ].Y )*( Polygon[ PreviousCornerIndex ].X - Polygon[ CornerIndex ].X ) < Point.X ); 72 | } 73 | 74 | PreviousCornerIndex = CornerIndex; 75 | } 76 | 77 | return bIsInside; 78 | } 79 | 80 | 81 | bool FPolygonTools::Snip( const TArray& Polygon, const int32 U, const int32 V, const int32 W, const int32 PointCount, const int32* VertexIndices ) 82 | { 83 | const FVector2D A = Polygon[ VertexIndices[ U ] ]; 84 | const FVector2D B = Polygon[ VertexIndices[ V ] ]; 85 | const FVector2D C = Polygon[ VertexIndices[ W ] ]; 86 | 87 | if( SMALL_NUMBER > ( ( ( B.X - A.X ) * ( C.Y - A.Y ) ) - ( ( B.Y - A.Y ) * ( C.X - A.X ) ) ) && 88 | 89 | // Don't fail because of degenerate points 90 | ( A - B ).SizeSquared() > SMALL_NUMBER && 91 | ( A - C ).SizeSquared() > SMALL_NUMBER && 92 | ( C - A ).SizeSquared() > SMALL_NUMBER ) 93 | { 94 | return false; 95 | } 96 | 97 | for( int32 PointIndex = 0; PointIndex < PointCount; ++PointIndex ) 98 | { 99 | if( ( PointIndex == U ) || ( PointIndex == V ) || ( PointIndex == W ) ) 100 | { 101 | continue; 102 | } 103 | 104 | if( IsPointInsideTriangle( A, B, C, Polygon[ VertexIndices[ PointIndex ] ] ) ) 105 | { 106 | return false; 107 | } 108 | } 109 | 110 | return true; 111 | } 112 | 113 | 114 | -------------------------------------------------------------------------------- /Source/StreetMapRuntime/Public/StreetMap.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "StreetMap.generated.h" 6 | 7 | 8 | USTRUCT(BlueprintType) 9 | struct STREETMAPRUNTIME_API FStreetMapCollisionSettings 10 | { 11 | GENERATED_USTRUCT_BODY() 12 | 13 | public: 14 | 15 | 16 | /** Uses triangle mesh data for collision data. (Cannot be used for physics simulation). */ 17 | UPROPERTY(EditAnywhere, Category = "StreetMap") 18 | uint32 bGenerateCollision : 1; 19 | 20 | /** 21 | * If true, the physics triangle mesh will use double sided faces when doing scene queries. 22 | * This is useful for planes and single sided meshes that need traces to work on both sides. 23 | */ 24 | UPROPERTY(EditAnywhere, Category = "StreetMap", meta = (editcondition = "bGenerateCollision")) 25 | uint32 bAllowDoubleSidedGeometry : 1; 26 | 27 | 28 | FStreetMapCollisionSettings() : 29 | bGenerateCollision(false), 30 | bAllowDoubleSidedGeometry(false) 31 | { 32 | 33 | } 34 | 35 | }; 36 | 37 | /** Mesh generation settings */ 38 | USTRUCT(BlueprintType) 39 | struct STREETMAPRUNTIME_API FStreetMapMeshBuildSettings 40 | { 41 | GENERATED_USTRUCT_BODY() 42 | 43 | public: 44 | 45 | /** Roads base vertical offset */ 46 | UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0"), DisplayName = "Road Vertical Offset") 47 | float RoadOffesetZ; 48 | 49 | /** if true buildings mesh will be 3D instead of flat representation. */ 50 | UPROPERTY(Category = StreetMap, EditAnywhere, DisplayName = "Create 3D Buildings") 51 | uint32 bWant3DBuildings : 1; 52 | 53 | /** building level floor conversion factor in centimeters 54 | @todo: harmonize with OSMToCentimetersScaleFactor refactoring 55 | */ 56 | UPROPERTY(Category = StreetMap, EditAnywhere, DisplayName = "Building Level Floor Factor") 57 | float BuildingLevelFloorFactor = 300.0f; 58 | 59 | /** 60 | * If true, buildings mesh will receive light information. 61 | * Lit buildings can't share vertices beyond quads (all quads have their own face normals), so this uses a lot more geometry. 62 | */ 63 | UPROPERTY(Category = StreetMap, EditAnywhere, DisplayName = "Lit buildings") 64 | uint32 bWantLitBuildings : 1; 65 | 66 | /** Streets thickness */ 67 | UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) 68 | float StreetThickness; 69 | 70 | /** Street vertex color */ 71 | UPROPERTY(Category = StreetMap, EditAnywhere) 72 | FLinearColor StreetColor; 73 | 74 | /** Major road thickness */ 75 | UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) 76 | float MajorRoadThickness; 77 | 78 | /** Major road vertex color */ 79 | UPROPERTY(Category = StreetMap, EditAnywhere) 80 | FLinearColor MajorRoadColor; 81 | 82 | /** Highway thickness */ 83 | UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) 84 | float HighwayThickness; 85 | 86 | /** Highway vertex color */ 87 | UPROPERTY(Category = StreetMap, EditAnywhere) 88 | FLinearColor HighwayColor; 89 | 90 | /** Streets Thickness */ 91 | UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) 92 | float BuildingBorderThickness; 93 | 94 | /** Building border vertex color */ 95 | UPROPERTY(Category = StreetMap, EditAnywhere) 96 | FLinearColor BuildingBorderLinearColor; 97 | 98 | /** Buildings border vertical offset */ 99 | UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) 100 | float BuildingBorderZ; 101 | 102 | FStreetMapMeshBuildSettings() : 103 | RoadOffesetZ(0.0f), 104 | bWant3DBuildings(true), 105 | bWantLitBuildings(true), 106 | StreetThickness(800.0f), 107 | StreetColor(0.05f, 0.75f, 0.05f), 108 | MajorRoadThickness(1000.0f), 109 | MajorRoadColor(0.15f, 0.85f, 0.15f), 110 | HighwayThickness(1400.0f), 111 | HighwayColor(FLinearColor(0.25f, 0.95f, 0.25f)), 112 | BuildingBorderThickness(20.0f), 113 | BuildingBorderLinearColor(0.85f, 0.85f, 0.85f), 114 | BuildingBorderZ(10.0f) 115 | { 116 | } 117 | 118 | }; 119 | 120 | 121 | 122 | /** Types of roads */ 123 | UENUM( BlueprintType ) 124 | enum EStreetMapRoadType 125 | { 126 | /** Small road or residential street */ 127 | Street, 128 | 129 | /** Major road or minor state highway */ 130 | MajorRoad, 131 | 132 | /** Highway */ 133 | Highway, 134 | 135 | /** Other (path, bus route, etc) */ 136 | Other, 137 | }; 138 | 139 | 140 | /** A road */ 141 | USTRUCT( BlueprintType ) 142 | struct STREETMAPRUNTIME_API FStreetMapRoad 143 | { 144 | GENERATED_USTRUCT_BODY() 145 | 146 | /** Name of the road */ 147 | UPROPERTY( Category=StreetMap, EditAnywhere ) 148 | FString RoadName; 149 | 150 | /** Type of road */ 151 | UPROPERTY( Category=StreetMap, EditAnywhere ) 152 | TEnumAsByte RoadType; 153 | 154 | /** Nodes along this road, one at each point in the RoadPoints list */ 155 | UPROPERTY( Category=StreetMap, EditAnywhere ) 156 | TArray NodeIndices; 157 | 158 | /** List of all of the points on this road, one for each node in the NodeIndices list */ 159 | UPROPERTY( Category=StreetMap, EditAnywhere ) 160 | TArray RoadPoints; 161 | 162 | // @todo: Performance: Bounding information could be computed at load time if we want to avoid the memory cost of storing it 163 | 164 | /** 2D bounds (min) of this road's points */ 165 | UPROPERTY( Category=StreetMap, EditAnywhere ) 166 | FVector2D BoundsMin; 167 | 168 | /** 2D bounds (max) of this road's points */ 169 | UPROPERTY( Category=StreetMap, EditAnywhere ) 170 | FVector2D BoundsMax; 171 | 172 | /** 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. */ 173 | UPROPERTY( Category=StreetMap, EditAnywhere ) 174 | uint8 bIsOneWay : 1; 175 | 176 | 177 | /** Returns this node's index */ 178 | inline int32 GetRoadIndex( const class UStreetMap& StreetMap ) const; 179 | 180 | /** Gets the node for the specified point, or the node that came before that if the specified point doesn't have a node */ 181 | inline const struct FStreetMapNode& GetNodeAtPointIndexOrEarlier( const class UStreetMap& StreetMap, const int32 PointIndex, int32& OutNodeAtPointIndex ) const; 182 | 183 | /** Gets the node for the specified point, or the node that comes next after that if the specified point doesn't have a node */ 184 | inline const struct FStreetMapNode& GetNodeAtPointIndexOrLater( const class UStreetMap& StreetMap, const int32 PointIndex, int32& OutNodeAtPointIndex ) const; 185 | 186 | /** Computes the total length of this road by following along all of it's points */ 187 | float ComputeLengthOfRoad( const class UStreetMap& StreetMap ) const; 188 | 189 | /** Computes the distance along the road between two points on the road. Be careful! The same node can appear on a road twice. */ 190 | float ComputeDistanceBetweenNodesOnRoad( const class UStreetMap& StreetMap, const int32 NodePointIndexA, const int32 NodePointIndexB ) const; 191 | 192 | /** Given a position along the road, finds the nodes that come earlier and later on that road */ 193 | void FindEarlierAndLaterNodesForPositionAlongRoad( const class UStreetMap& StreetMap, const float PositionAlongRoad, const FStreetMapNode*& OutEarlierNode, float& OutEarlierNodePositionAlongRoad, const FStreetMapNode*& OutLaterNode, float& OutLaterNodePositionAlongRoad ) const; 194 | 195 | /** 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 */ 196 | void FindEarlierAndLaterNodes( const class UStreetMap& StreetMap, const int32 RoadPointIndex, const FStreetMapNode*& OutEarlierNode, float& OutEarlierNodePositionAlongRoad, const FStreetMapNode*& OutLaterNode, float& OutLaterNodePositionAlongRoad ) const; 197 | 198 | // NOTE: You may notice there is no "FindPointIndexForNode()" method in this class. This is because 199 | // the same node may appear more than once on any single road, so it's never safe to ask for 200 | // a single point index on a road. 201 | 202 | /** Given a node that exists on this road, computes the position along this road of that node */ 203 | float FindPositionAlongRoadForNode( const class UStreetMap& StreetMap, const int32 PointIndexForNode ) const; 204 | 205 | /** Computes the location of a point along this road, given a distance along this road from the road's beginning */ 206 | FVector2D MakeLocationAlongRoad( const class UStreetMap& StreetMap, const float PositionAlongRoad ) const; 207 | 208 | /** @return True if this is a one way road */ 209 | inline bool IsOneWay() const 210 | { 211 | return bIsOneWay == 1 ? true : false; 212 | } 213 | }; 214 | 215 | 216 | /** Nodes have a list of road refs, one for each road that intersects this node. Each road ref references a road and also the 217 | point along that road where this node exists. */ 218 | USTRUCT( BlueprintType ) 219 | struct STREETMAPRUNTIME_API FStreetMapRoadRef 220 | { 221 | GENERATED_USTRUCT_BODY() 222 | 223 | /** Index of road in the list of all roads in this street map */ 224 | UPROPERTY( Category=StreetMap, EditAnywhere ) 225 | int32 RoadIndex; 226 | 227 | /** Index of the point along road where this node exists */ 228 | UPROPERTY( Category=StreetMap, EditAnywhere ) 229 | int32 RoadPointIndex; 230 | }; 231 | 232 | 233 | /** 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". */ 234 | USTRUCT( BlueprintType ) 235 | struct STREETMAPRUNTIME_API FStreetMapNode 236 | { 237 | GENERATED_USTRUCT_BODY() 238 | 239 | /** All of the roads that intersect this node. We have references to each of these roads, as well as the point along each 240 | road where this node exists */ 241 | UPROPERTY( Category=StreetMap, EditAnywhere ) 242 | TArray RoadRefs; 243 | 244 | /** Returns this node's index */ 245 | inline int32 GetNodeIndex( const UStreetMap& StreetMap ) const; 246 | 247 | /** Gets the location of this node */ 248 | inline FVector2D GetLocation( const UStreetMap& StreetMap ) const; 249 | 250 | 251 | /// 252 | /// Utility functions which may be useful for pathfinding algorithms (not used internally.) 253 | /// 254 | 255 | /** Pathfinding: Given a node that is known to connect to this node via some road, searches for the road and returns it */ 256 | inline const FStreetMapRoad& GetShortestCostRoadToNode( UStreetMap& StreetMap, const FStreetMapNode& OtherNode, const bool bIsTravelingForward, int32& OutPointIndexOnRoad ) const; 257 | 258 | /** Pathfinding: Returns true if this node is the end point on a road with no connections */ 259 | inline bool IsDeadEnd( const UStreetMap& StreetMap ) const; 260 | 261 | /** Pathfinding: Returns the number of connections between this node and other roads, taking into account the direction of travel */ 262 | inline int32 GetConnectionCount( const class UStreetMap& StreetMap, const bool bIsTravelingForward ) const; 263 | 264 | /** 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 */ 265 | 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; 266 | 267 | /** Pathfinding: Estimates the 'cost' of the specified connected by index (between 0 and GetConnectionCount() - 1) */ 268 | inline float GetConnectionCost( const class UStreetMap& StreetMap, const int32 ConnectionIndex, const bool bIsTravelingForward ) const; 269 | }; 270 | 271 | 272 | /** A building */ 273 | USTRUCT( BlueprintType ) 274 | struct STREETMAPRUNTIME_API FStreetMapBuilding 275 | { 276 | GENERATED_USTRUCT_BODY() 277 | 278 | /** Name of the building */ 279 | UPROPERTY( Category=StreetMap, EditAnywhere ) 280 | FString BuildingName; 281 | 282 | /** Polygon points that define the perimeter of the building */ 283 | UPROPERTY( Category=StreetMap, EditAnywhere ) 284 | TArray BuildingPoints; 285 | 286 | /** Height of the building in meters (if known, otherwise zero) */ 287 | UPROPERTY( Category=StreetMap, EditAnywhere ) 288 | float Height; 289 | 290 | /** Levels of the building (if known, otherwise zero) */ 291 | UPROPERTY(Category = StreetMap, EditAnywhere) 292 | int BuildingLevels; 293 | 294 | // @todo: Performance: Bounding information could be computed at load time if we want to avoid the memory cost of storing it 295 | 296 | /** 2D bounds (min) of this building's points */ 297 | UPROPERTY( Category=StreetMap, EditAnywhere ) 298 | FVector2D BoundsMin; 299 | 300 | /** 2D bounds (max) of this building's points */ 301 | UPROPERTY( Category=StreetMap, EditAnywhere ) 302 | FVector2D BoundsMax; 303 | }; 304 | 305 | 306 | /** A loaded street map */ 307 | UCLASS() 308 | class STREETMAPRUNTIME_API UStreetMap : public UObject 309 | { 310 | GENERATED_BODY() 311 | 312 | public: 313 | 314 | /** Default constructor for UStreetMap */ 315 | UStreetMap(); 316 | 317 | // UObject overrides 318 | virtual void GetAssetRegistryTags( TArray& OutTags ) const override; 319 | 320 | /** Gets the roads in this street map (read only) */ 321 | const TArray& GetRoads() const 322 | { 323 | return Roads; 324 | } 325 | 326 | /** Gets the roads in this street map */ 327 | TArray& GetRoads() 328 | { 329 | return Roads; 330 | } 331 | 332 | /** Gets the nodes on the map (read only.) Nodes describe intersections between roads */ 333 | const TArray& GetNodes() const 334 | { 335 | return Nodes; 336 | } 337 | 338 | /** Gets the nodes on the map. Nodes describe intersections between roads */ 339 | TArray& GetNodes() 340 | { 341 | return Nodes; 342 | } 343 | 344 | /** Gets all of the buildings (read only) */ 345 | const TArray& GetBuildings() const 346 | { 347 | return Buildings; 348 | } 349 | 350 | /** Gets all of the buildings */ 351 | TArray& GetBuildings() 352 | { 353 | return Buildings; 354 | } 355 | 356 | /** Gets the bounding box of the map */ 357 | FVector2D GetBoundsMin() const 358 | { 359 | return BoundsMin; 360 | } 361 | FVector2D GetBoundsMax() const 362 | { 363 | return BoundsMax; 364 | } 365 | 366 | 367 | protected: 368 | 369 | /** List of roads */ 370 | UPROPERTY( Category=StreetMap, VisibleAnywhere ) 371 | TArray Roads; 372 | 373 | /** 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 */ 374 | UPROPERTY( Category=StreetMap, VisibleAnywhere ) 375 | TArray Nodes; 376 | 377 | /** List of all buildings on the street map */ 378 | UPROPERTY( Category=StreetMap, VisibleAnywhere) 379 | TArray Buildings; 380 | 381 | /** 2D bounds (min) of this map's roads and buildings */ 382 | UPROPERTY( Category=StreetMap, VisibleAnywhere) 383 | FVector2D BoundsMin; 384 | 385 | /** 2D bounds (max) of this map's roads and buildings */ 386 | UPROPERTY( Category=StreetMap, VisibleAnywhere) 387 | FVector2D BoundsMax; 388 | 389 | #if WITH_EDITORONLY_DATA 390 | /** Importing data and options used for this mesh */ 391 | UPROPERTY( VisibleAnywhere, Instanced, Category=ImportSettings ) 392 | class UAssetImportData* AssetImportData; 393 | 394 | friend class UStreetMapFactory; 395 | friend class UStreetMapReimportFactory; 396 | friend class FStreetMapAssetTypeActions; 397 | #endif // WITH_EDITORONLY_DATA 398 | 399 | }; 400 | 401 | 402 | inline int32 FStreetMapRoad::GetRoadIndex( const UStreetMap& StreetMap ) const 403 | { 404 | // Pointer arithmetic based on array start 405 | const int32 RoadIndex = this - StreetMap.GetRoads().GetData(); 406 | return RoadIndex; 407 | } 408 | 409 | 410 | inline const FStreetMapNode& FStreetMapRoad::GetNodeAtPointIndexOrEarlier( const UStreetMap& StreetMap, const int32 PointIndex, int32& OutNodeAtPointIndex ) const 411 | { 412 | const FStreetMapNode* CurrentOrEarlierPointNode = nullptr; 413 | for( int32 NodePointIndex = PointIndex; NodePointIndex >= 0; --NodePointIndex ) 414 | { 415 | if( NodeIndices[ NodePointIndex ] != INDEX_NONE ) 416 | { 417 | CurrentOrEarlierPointNode = &StreetMap.GetNodes()[ NodeIndices[ NodePointIndex ] ]; 418 | OutNodeAtPointIndex = NodePointIndex; 419 | break; 420 | } 421 | } 422 | return *CurrentOrEarlierPointNode; 423 | } 424 | 425 | 426 | inline const FStreetMapNode& FStreetMapRoad::GetNodeAtPointIndexOrLater( const UStreetMap& StreetMap, const int32 PointIndex, int32& OutNodeAtPointIndex ) const 427 | { 428 | const FStreetMapNode* NextOrUpcomingNode = nullptr; 429 | for( int32 NodePointIndex = PointIndex; NodePointIndex < RoadPoints.Num(); ++NodePointIndex ) 430 | { 431 | if( NodeIndices[ NodePointIndex ] != INDEX_NONE ) 432 | { 433 | NextOrUpcomingNode = &StreetMap.GetNodes()[ NodeIndices[ NodePointIndex ] ]; 434 | OutNodeAtPointIndex = NodePointIndex; 435 | break; 436 | } 437 | } 438 | 439 | return *NextOrUpcomingNode; 440 | } 441 | 442 | 443 | inline float FStreetMapRoad::ComputeLengthOfRoad( const class UStreetMap& StreetMap ) const 444 | { 445 | // @todo: Performance: We could cache the road's total length at load time to avoid having to compute it, 446 | // or we could save it right into the asset file 447 | 448 | return ComputeDistanceBetweenNodesOnRoad( StreetMap, 0, this->NodeIndices.Num() - 1 ); 449 | } 450 | 451 | 452 | inline float FStreetMapRoad::ComputeDistanceBetweenNodesOnRoad( const class UStreetMap& StreetMap, const int32 NodePointIndexA, const int32 NodePointIndexB ) const 453 | { 454 | float TotalDistanceSoFar = 0.0f; 455 | 456 | // NOTE: It is very important that we use the actual road point indices here and not nodes directly, because the same node can appear 457 | // more than once on a single road! 458 | 459 | // @todo: Performance: We can cache distances between connected nodes rather than computing them every time. This 460 | // can be computed at load time or at import time (and stored in the asset). Most of the other functions 461 | // in this class that perform Size() computations could be changed to use cached distances also! 462 | 463 | const int32 SmallerPointIndex = FMath::Max( 0, FMath::Min( NodePointIndexA, NodePointIndexB ) ); 464 | const int32 LargerPointIndex = FMath::Min( RoadPoints.Num() - 1, FMath::Max( NodePointIndexA, NodePointIndexB ) ); 465 | 466 | for( int32 PointIndex = SmallerPointIndex; PointIndex < LargerPointIndex; ++PointIndex ) 467 | { 468 | const FVector2D PointLocation = RoadPoints[ PointIndex ]; 469 | const FVector2D NextPointLocation = RoadPoints[ PointIndex + 1 ]; 470 | 471 | const float DistanceBetweenPoints = ( NextPointLocation - PointLocation ).Size(); 472 | 473 | TotalDistanceSoFar += DistanceBetweenPoints; 474 | } 475 | 476 | // @todo: Malformed data can cause this assertion to trigger. This could be a single road with at least two adjacent nodes 477 | // at the exact same location. We need to filter this out at load time probably. 478 | // check( TotalDistanceSoFar > 0.0f ); 479 | 480 | return TotalDistanceSoFar; 481 | } 482 | 483 | 484 | inline void FStreetMapRoad::FindEarlierAndLaterNodesForPositionAlongRoad( const class UStreetMap& StreetMap, const float PositionAlongRoad, const FStreetMapNode*& OutEarlierNode, float& OutEarlierNodePositionAlongRoad, const FStreetMapNode*& OutLaterNode, float& OutLaterNodePositionAlongRoad ) const 485 | { 486 | float CurrentPointPositionAlongRoad = 0.0f; 487 | 488 | const FStreetMapNode* EarlierStreetMapNode = nullptr; 489 | const FStreetMapNode* LaterStreetMapNode = nullptr; 490 | 491 | const int32 NumPoints = RoadPoints.Num(); 492 | for( int32 CurrentPointIndex = 0; CurrentPointIndex < NumPoints - 1; ++CurrentPointIndex ) 493 | { 494 | if( this->NodeIndices[ CurrentPointIndex ] != INDEX_NONE ) 495 | { 496 | EarlierStreetMapNode = &StreetMap.GetNodes()[ this->NodeIndices[ CurrentPointIndex ] ]; 497 | OutEarlierNodePositionAlongRoad = CurrentPointPositionAlongRoad; 498 | } 499 | 500 | const int32 NextPointIndex = CurrentPointIndex + 1; 501 | const FVector2D CurrentPointLocation = RoadPoints[ CurrentPointIndex ]; 502 | const FVector2D NextPointLocation = RoadPoints[ NextPointIndex ]; 503 | 504 | const float DistanceBetweenPoints = ( NextPointLocation - CurrentPointLocation ).Size(); 505 | const float NextPointPositionAlongRoad = CurrentPointPositionAlongRoad + DistanceBetweenPoints; 506 | 507 | if( NextPointPositionAlongRoad >= PositionAlongRoad ) 508 | { 509 | if( NodeIndices[ NextPointIndex ] != INDEX_NONE ) 510 | { 511 | LaterStreetMapNode = &StreetMap.GetNodes()[ this->NodeIndices[ NextPointIndex ] ]; 512 | OutLaterNodePositionAlongRoad = NextPointPositionAlongRoad; 513 | break; 514 | } 515 | } 516 | 517 | CurrentPointPositionAlongRoad = NextPointPositionAlongRoad; 518 | } 519 | 520 | check( EarlierStreetMapNode != nullptr && LaterStreetMapNode != nullptr ); 521 | OutEarlierNode = EarlierStreetMapNode; 522 | OutLaterNode = LaterStreetMapNode; 523 | } 524 | 525 | 526 | inline void FStreetMapRoad::FindEarlierAndLaterNodes( const class UStreetMap& StreetMap, const int32 RoadPointIndex, const FStreetMapNode*& OutEarlierNode, float& OutEarlierNodePositionAlongRoad, const FStreetMapNode*& OutLaterNode, float& OutLaterNodePositionAlongRoad ) const 527 | { 528 | OutEarlierNode = nullptr; 529 | OutEarlierNodePositionAlongRoad = -1.0f; 530 | OutLaterNode = nullptr; 531 | OutLaterNodePositionAlongRoad = -1.0f; 532 | 533 | for( int32 EarlierPointIndex = RoadPointIndex - 1; EarlierPointIndex >= 0; --EarlierPointIndex ) 534 | { 535 | if( this->NodeIndices[ EarlierPointIndex ] != INDEX_NONE ) 536 | { 537 | OutEarlierNode = &StreetMap.GetNodes()[ this->NodeIndices[ EarlierPointIndex ] ]; 538 | OutEarlierNodePositionAlongRoad = FindPositionAlongRoadForNode( StreetMap, EarlierPointIndex ); 539 | break; 540 | } 541 | } 542 | 543 | for( int32 LaterPointIndex = RoadPointIndex + 1; LaterPointIndex < this->RoadPoints.Num(); ++LaterPointIndex ) 544 | { 545 | if( this->NodeIndices[ LaterPointIndex ] != INDEX_NONE ) 546 | { 547 | OutLaterNode = &StreetMap.GetNodes()[ this->NodeIndices[ LaterPointIndex ] ]; 548 | OutLaterNodePositionAlongRoad = FindPositionAlongRoadForNode( StreetMap, LaterPointIndex ); 549 | break; 550 | } 551 | } 552 | } 553 | 554 | 555 | inline float FStreetMapRoad::FindPositionAlongRoadForNode( const class UStreetMap& StreetMap, const int32 PointIndexForNode ) const 556 | { 557 | float CurrentPointPositionAlongRoad = 0.0f; 558 | 559 | bool bFoundLocation = false; 560 | const int32 NumPoints = RoadPoints.Num(); 561 | for( int32 CurrentPointIndex = 0; CurrentPointIndex < PointIndexForNode; ++CurrentPointIndex ) 562 | { 563 | const FVector2D CurrentPointLocation = RoadPoints[ CurrentPointIndex ]; 564 | const FVector2D NextPointLocation = RoadPoints[ CurrentPointIndex + 1 ]; 565 | 566 | const float DistanceBetweenPoints = ( NextPointLocation - CurrentPointLocation ).Size(); 567 | const float NextPointPositionAlongRoad = CurrentPointPositionAlongRoad + DistanceBetweenPoints; 568 | 569 | CurrentPointPositionAlongRoad = NextPointPositionAlongRoad; 570 | } 571 | 572 | return CurrentPointPositionAlongRoad; 573 | } 574 | 575 | 576 | inline FVector2D FStreetMapRoad::MakeLocationAlongRoad( const class UStreetMap& StreetMap, const float PositionAlongRoad ) const 577 | { 578 | FVector2D LocationAlongRoad = FVector2D::ZeroVector; 579 | float CurrentPointPositionAlongRoad = 0.0f; 580 | 581 | bool bFoundLocation = false; 582 | const int32 NumPoints = RoadPoints.Num(); 583 | for( int32 CurrentPointIndex = 0; CurrentPointIndex < NumPoints - 1; ++CurrentPointIndex ) 584 | { 585 | const FVector2D CurrentPointLocation = RoadPoints[ CurrentPointIndex ]; 586 | const FVector2D NextPointLocation = RoadPoints[ CurrentPointIndex + 1 ]; 587 | 588 | const float DistanceBetweenPoints = ( NextPointLocation - CurrentPointLocation ).Size(); 589 | const float NextPointPositionAlongRoad = CurrentPointPositionAlongRoad + DistanceBetweenPoints; 590 | 591 | if( NextPointPositionAlongRoad >= PositionAlongRoad ) 592 | { 593 | const float LerpAlpha = ( PositionAlongRoad - CurrentPointPositionAlongRoad ) / DistanceBetweenPoints; 594 | LocationAlongRoad = FMath::Lerp( CurrentPointLocation, NextPointLocation, LerpAlpha ); 595 | bFoundLocation = true; 596 | break; 597 | } 598 | 599 | CurrentPointPositionAlongRoad = NextPointPositionAlongRoad; 600 | } 601 | 602 | check( bFoundLocation == true ); 603 | 604 | return LocationAlongRoad; 605 | } 606 | 607 | 608 | inline int32 FStreetMapNode::GetNodeIndex( const UStreetMap& StreetMap ) const 609 | { 610 | // Pointer arithmetic based on array start 611 | const int32 NodeIndex = this - StreetMap.GetNodes().GetData(); 612 | return NodeIndex; 613 | } 614 | 615 | 616 | inline bool FStreetMapNode::IsDeadEnd( const UStreetMap& StreetMap ) const 617 | { 618 | if( RoadRefs.Num() == 1 ) 619 | { 620 | // @todo: If this road only connects to dead end roads that oppose the direction, we need to treat this road 621 | // as a dead end. This case should be extremely uncommon, though! 622 | 623 | const FStreetMapRoadRef& SoleRoadRef = RoadRefs[ 0 ]; 624 | const FStreetMapRoad& SoleRoad = StreetMap.GetRoads()[ SoleRoadRef.RoadIndex ]; 625 | if( SoleRoadRef.RoadPointIndex == 0 || SoleRoadRef.RoadPointIndex == ( SoleRoad.NodeIndices.Num() - 1 ) ) 626 | { 627 | // The node is attached to only one road, and the node is at the very end of one of the ends of the road 628 | return true; 629 | } 630 | } 631 | 632 | return false; 633 | } 634 | 635 | 636 | inline FVector2D FStreetMapNode::GetLocation( const UStreetMap& StreetMap ) const 637 | { 638 | const FStreetMapRoadRef& MyFirstRoadRef = RoadRefs[ 0 ]; 639 | const FVector2D Location = StreetMap.GetRoads()[ MyFirstRoadRef.RoadIndex ].RoadPoints[ MyFirstRoadRef.RoadPointIndex ]; 640 | return Location; 641 | } 642 | 643 | 644 | inline const FStreetMapRoad& FStreetMapNode::GetShortestCostRoadToNode( UStreetMap& StreetMap, const FStreetMapNode& OtherNode, const bool bIsTravelingForward, int32& OutPointIndexOnRoad ) const 645 | { 646 | const FStreetMapRoad* ConnectingRoad = nullptr; 647 | 648 | float BestConnectionCost = TNumericLimits::Max(); 649 | int32 BestConnectionIndex = INDEX_NONE; 650 | int32 BestConnectionPointIndex = INDEX_NONE; 651 | 652 | const int32 ConnectionCount = GetConnectionCount( StreetMap, bIsTravelingForward ); 653 | for( int32 ConnectionIndex = 0; ConnectionIndex < ConnectionCount; ++ConnectionIndex ) 654 | { 655 | int32 MyPointIndexOnRoad; 656 | int32 ConnectedNodePointIndexOnRoad; 657 | 658 | const FStreetMapRoad* CurrentConnectingRoad; 659 | const FStreetMapNode* ConnectedNode = GetConnection( StreetMap, ConnectionIndex, bIsTravelingForward, /* Out */ &CurrentConnectingRoad, /* Out */ &MyPointIndexOnRoad, /* Out */ &ConnectedNodePointIndexOnRoad ); 660 | if( ConnectedNode == &OtherNode ) 661 | { 662 | if( BestConnectionIndex != INDEX_NONE ) 663 | { 664 | // We evaluate cost lazily, only in the unusual case of the two nodes being connected by multiple roads 665 | BestConnectionCost = GetConnectionCost( StreetMap, BestConnectionIndex, bIsTravelingForward ); 666 | if( GetConnectionCost( StreetMap, ConnectionIndex, bIsTravelingForward ) < BestConnectionCost ) 667 | { 668 | ConnectingRoad = CurrentConnectingRoad; 669 | BestConnectionIndex = ConnectionIndex; 670 | BestConnectionPointIndex = MyPointIndexOnRoad; 671 | } 672 | } 673 | else 674 | { 675 | ConnectingRoad = CurrentConnectingRoad; 676 | BestConnectionIndex = ConnectionIndex; 677 | BestConnectionPointIndex = MyPointIndexOnRoad; 678 | } 679 | } 680 | } 681 | 682 | check( ConnectingRoad != nullptr ); 683 | OutPointIndexOnRoad = BestConnectionPointIndex; 684 | return *ConnectingRoad; 685 | } 686 | 687 | inline int32 FStreetMapNode::GetConnectionCount( const UStreetMap& StreetMap, const bool bIsTravelingForward ) const 688 | { 689 | // NOTE: We're iterating here in the exact same order as in the GetConnection() function below! That's critically important! 690 | int32 TotalConnections = 0; 691 | for( const FStreetMapRoadRef& RoadRef : RoadRefs ) 692 | { 693 | const FStreetMapRoad& Road = StreetMap.GetRoads()[ RoadRef.RoadIndex ]; 694 | 695 | if( RoadRef.RoadPointIndex > 0 && ( !bIsTravelingForward || !Road.IsOneWay() ) ) 696 | { 697 | // We connect to a node earlier up this road 698 | ++TotalConnections; 699 | } 700 | 701 | if( RoadRef.RoadPointIndex < ( Road.NodeIndices.Num() - 1 ) && ( bIsTravelingForward || !Road.IsOneWay() ) ) 702 | { 703 | // We connect to a node further down this road 704 | ++TotalConnections; 705 | } 706 | } 707 | 708 | return TotalConnections; 709 | } 710 | 711 | 712 | inline const FStreetMapNode* FStreetMapNode::GetConnection( const UStreetMap& StreetMap, const int32 ConnectionIndex, const bool bIsTravelingForward, const FStreetMapRoad** OutConnectingRoad, int32* OutPointIndexOnRoad, int32* OutConnectedNodePointIndexOnRoad ) const 713 | { 714 | if( OutConnectingRoad != nullptr ) 715 | { 716 | *OutConnectingRoad = nullptr; 717 | } 718 | const FStreetMapNode* ConnectedNode = nullptr; 719 | 720 | // @todo: Performance: We can improve performance by caching additional connectivity information right on 721 | // the node itself. This function would be a hot spot for any sort of pathfinding computation 722 | 723 | // NOTE: We're iterating here in the exact same order as in the GetConnectionCount() function above! That's critically important! 724 | int32 CurrentConnectionIndex = 0; 725 | for( const FStreetMapRoadRef& RoadRef : RoadRefs ) 726 | { 727 | const FStreetMapRoad& Road = StreetMap.GetRoads()[ RoadRef.RoadIndex ]; 728 | 729 | // @todo: Performance: We could avoid the "while" loops below by not storing INDEX_NONEs in the NodeIndices array, 730 | // but instead mapping them to points by going through the node itself, then back to a road 731 | 732 | if( RoadRef.RoadPointIndex > 0 && ( !bIsTravelingForward || !Road.IsOneWay() ) ) 733 | { 734 | // We connect to an earlier node up this road 735 | if( CurrentConnectionIndex == ConnectionIndex ) 736 | { 737 | int32 EarlierNodeRoadPointIndex = RoadRef.RoadPointIndex - 1; 738 | while( Road.NodeIndices[ EarlierNodeRoadPointIndex ] == INDEX_NONE ) 739 | { 740 | --EarlierNodeRoadPointIndex; 741 | } 742 | const int32 EarlierNodeIndex = Road.NodeIndices[ EarlierNodeRoadPointIndex ]; 743 | 744 | const FStreetMapNode& EarlierNode = StreetMap.GetNodes()[ EarlierNodeIndex ]; 745 | ConnectedNode = &EarlierNode; 746 | if( OutConnectingRoad != nullptr ) 747 | { 748 | *OutConnectingRoad = &Road; 749 | } 750 | if( OutPointIndexOnRoad != nullptr ) 751 | { 752 | *OutPointIndexOnRoad = RoadRef.RoadPointIndex; 753 | } 754 | if( OutConnectedNodePointIndexOnRoad != nullptr ) 755 | { 756 | *OutConnectedNodePointIndexOnRoad = EarlierNodeRoadPointIndex; 757 | } 758 | 759 | // Got it! 760 | break; 761 | } 762 | 763 | ++CurrentConnectionIndex; 764 | } 765 | 766 | if( RoadRef.RoadPointIndex < ( Road.NodeIndices.Num() - 1 ) && ( bIsTravelingForward || !Road.IsOneWay() ) ) 767 | { 768 | // We connect to node further down this road 769 | if( CurrentConnectionIndex == ConnectionIndex ) 770 | { 771 | int32 LaterNodeRoadPointIndex = RoadRef.RoadPointIndex + 1; 772 | while( Road.NodeIndices[ LaterNodeRoadPointIndex ] == INDEX_NONE ) 773 | { 774 | ++LaterNodeRoadPointIndex; 775 | } 776 | const int32 LaterNodeIndex = Road.NodeIndices[ LaterNodeRoadPointIndex ]; 777 | 778 | const FStreetMapNode& LaterNode = StreetMap.GetNodes()[ LaterNodeIndex ]; 779 | ConnectedNode = &LaterNode; 780 | if( OutConnectingRoad != nullptr ) 781 | { 782 | *OutConnectingRoad = &Road; 783 | } 784 | if( OutPointIndexOnRoad != nullptr ) 785 | { 786 | *OutPointIndexOnRoad = RoadRef.RoadPointIndex; 787 | } 788 | if( OutConnectedNodePointIndexOnRoad != nullptr ) 789 | { 790 | *OutConnectedNodePointIndexOnRoad = LaterNodeRoadPointIndex; 791 | } 792 | 793 | // Got it! 794 | break; 795 | } 796 | 797 | ++CurrentConnectionIndex; 798 | } 799 | } 800 | 801 | check( ConnectedNode != nullptr ); 802 | return ConnectedNode; 803 | } 804 | 805 | 806 | inline float FStreetMapNode::GetConnectionCost( const UStreetMap& StreetMap, const int32 ConnectionIndex, const bool bIsTravelingForward ) const 807 | { 808 | ///////////////////////////////////////////////////////// 809 | // Tweakables for connection cost estimation 810 | // 811 | const float MaxSpeedLimit = 120.0f; // 120 Km/hr 812 | const float HighwaySpeed = 110.0f; 813 | const float HighwayTrafficFactor = 0.0; 814 | const float MajorRoadSpeed = 70.0f; 815 | const float MajorRoadTrafficFactor = 0.2f; 816 | const float StreetSpeed = 40.0f; 817 | const float StreetTrafficFactor = 1.0f; 818 | ///////////////////////////////////////////////////////// 819 | 820 | // @todo: Street map pathfinding is a grand art in itself, and estimating cost of connections is 821 | // a very complicated problem. We're only doing some basic estimates for now, but in the 822 | // future we could consider taking into account the cost of different types of turns and 823 | // intersections, lane counts, actual speed limits, etc. 824 | 825 | int32 MyPointIndexOnRoad; 826 | int32 ConnectedNodePointIndexOnRoad; 827 | 828 | const FStreetMapRoad* ConnectingRoad = nullptr; 829 | const FStreetMapNode& ConnectedNode = *GetConnection( StreetMap, ConnectionIndex, bIsTravelingForward, /* Out */ &ConnectingRoad, /* Out */ &MyPointIndexOnRoad, /* Out */ &ConnectedNodePointIndexOnRoad ); 830 | 831 | const float DistanceBetweenNodes = ConnectingRoad->ComputeDistanceBetweenNodesOnRoad( StreetMap, MyPointIndexOnRoad, ConnectedNodePointIndexOnRoad ); 832 | 833 | float TotalCost = DistanceBetweenNodes; 834 | 835 | // Apply some scaling to the cost of traveling between these nodes 836 | { 837 | float SpeedLimit = 0.0f; 838 | float TrafficFactor = 0.0f; 839 | switch( ConnectingRoad->RoadType ) 840 | { 841 | case EStreetMapRoadType::Highway: 842 | SpeedLimit = HighwaySpeed; 843 | TrafficFactor = HighwayTrafficFactor; 844 | break; 845 | 846 | case EStreetMapRoadType::MajorRoad: 847 | SpeedLimit = MajorRoadSpeed; 848 | TrafficFactor = MajorRoadTrafficFactor; 849 | break; 850 | 851 | case EStreetMapRoadType::Street: 852 | case EStreetMapRoadType::Other: 853 | SpeedLimit = StreetSpeed; 854 | TrafficFactor = StreetTrafficFactor; 855 | break; 856 | 857 | default: 858 | check( 0 ); 859 | break; 860 | } 861 | 862 | const float RoadSpeedCostScale = ( 1.0f - ( SpeedLimit / MaxSpeedLimit ) ); 863 | TotalCost *= 1.0f + RoadSpeedCostScale * 15.0f * ( 0.5f + TrafficFactor * 0.5f ); 864 | } 865 | 866 | return TotalCost; 867 | } 868 | 869 | 870 | -------------------------------------------------------------------------------- /Source/StreetMapRuntime/Public/StreetMapActor.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "StreetMapActor.generated.h" 6 | 7 | /** An actor that renders a street map mesh component */ 8 | UCLASS(hidecategories = (Physics)) // Physics category in detail panel is hidden. Our component/Actor is not simulated ! 9 | class STREETMAPRUNTIME_API AStreetMapActor : public AActor 10 | { 11 | GENERATED_UCLASS_BODY() 12 | 13 | /** Component that represents a section of street map roads and buildings */ 14 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "StreetMap") 15 | class UStreetMapComponent* StreetMapComponent; 16 | 17 | public: 18 | FORCEINLINE class UStreetMapComponent* GetStreetMapComponent() { return StreetMapComponent; } 19 | }; 20 | -------------------------------------------------------------------------------- /Source/StreetMapRuntime/Public/StreetMapComponent.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | #pragma once 3 | 4 | #include "StreetMap.h" 5 | #include "Components/MeshComponent.h" 6 | #include "Interfaces/Interface_CollisionDataProvider.h" 7 | #include "StreetMapSceneProxy.h" 8 | #include "StreetMapComponent.generated.h" 9 | 10 | 11 | 12 | class UBodySetup; 13 | 14 | /** 15 | * Component that represents a section of street map roads and buildings 16 | */ 17 | UCLASS( meta=(BlueprintSpawnableComponent) , hidecategories = (Physics)) 18 | class STREETMAPRUNTIME_API UStreetMapComponent : public UMeshComponent, public IInterface_CollisionDataProvider 19 | { 20 | GENERATED_BODY() 21 | 22 | public: 23 | 24 | /** UStreetMapComponent constructor */ 25 | UStreetMapComponent(const class FObjectInitializer& ObjectInitializer); 26 | 27 | /** @return Gets the street map object associated with this component */ 28 | UStreetMap* GetStreetMap() 29 | { 30 | return StreetMap; 31 | } 32 | 33 | /** Returns StreetMap asset object name */ 34 | FString GetStreetMapAssetName() const; 35 | 36 | /** Returns true if we have valid cached mesh data from our assigned street map asset */ 37 | bool HasValidMesh() const 38 | { 39 | return Vertices.Num() != 0 && Indices.Num() != 0; 40 | } 41 | 42 | /** Returns Cached raw mesh vertices */ 43 | TArray< struct FStreetMapVertex > GetRawMeshVertices() const 44 | { 45 | return Vertices; 46 | } 47 | 48 | /** Returns Cached raw mesh triangle indices */ 49 | TArray< uint32 > GetRawMeshIndices() const 50 | { 51 | return Indices; 52 | } 53 | 54 | /** 55 | * Returns StreetMap Default Material if a valid one is found in plugin's content folder. 56 | * Otherwise , it returns the default surface 3d material. 57 | */ 58 | UMaterialInterface* GetDefaultMaterial() const 59 | { 60 | return StreetMapDefaultMaterial != nullptr ? StreetMapDefaultMaterial : UMaterial::GetDefaultMaterial(MD_Surface); 61 | } 62 | 63 | /** Returns true, if the input PropertyName correspond to a collision property. */ 64 | bool IsCollisionProperty(const FName& PropertyName) const 65 | { 66 | return PropertyName == TEXT("bGenerateCollision") || PropertyName == TEXT("bAllowDoubleSidedGeometry"); 67 | } 68 | 69 | /** 70 | * Returns sub-meshes count. 71 | * In this case we are creating one single mesh section, so it will return 1. 72 | * If cached mesh data are not valid , it will return 0. 73 | */ 74 | int32 GetNumMeshSections() const 75 | { 76 | return HasValidMesh() ? 1 : 0; 77 | } 78 | 79 | /** 80 | * Assigns a street map asset to this component. Render state will be updated immediately. 81 | * 82 | * @param NewStreetMap The street map to use 83 | * 84 | * @param bRebuildMesh : Rebuilds map mesh based on the new map asset 85 | * 86 | * @return Sets the street map object 87 | */ 88 | UFUNCTION(BlueprintCallable, Category = "StreetMap") 89 | void SetStreetMap(UStreetMap* NewStreetMap, bool bClearPreviousMeshIfAny = false, bool bRebuildMesh = false); 90 | 91 | 92 | 93 | //** Begin Interface_CollisionDataProvider Interface */ 94 | virtual bool GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData) override; 95 | virtual bool ContainsPhysicsTriMeshData(bool InUseAllTriData) const override; 96 | virtual bool WantsNegXTriMesh() override; 97 | //** End Interface_CollisionDataProvider Interface *// 98 | 99 | protected: 100 | 101 | /** 102 | * Ensures the body setup is initialized/configured and updates it if needed. 103 | * @param bForceCreation : Force new BodySetup creation even if a valid one already exists. 104 | */ 105 | void CreateBodySetupIfNeeded(bool bForceCreation = false); 106 | 107 | /** Marks collision data as dirty, and re-create on instance if necessary */ 108 | void GenerateCollision(); 109 | 110 | /** Wipes out and invalidate collision data. */ 111 | void ClearCollision(); 112 | 113 | public: 114 | 115 | // UPrimitiveComponent interface 116 | virtual UBodySetup* GetBodySetup() override; 117 | virtual FPrimitiveSceneProxy* CreateSceneProxy() override; 118 | virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override; 119 | virtual int32 GetNumMaterials() const override; 120 | #if WITH_EDITOR 121 | virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; 122 | #endif 123 | 124 | /** Wipes out our cached mesh data. Designed to be called on demand.*/ 125 | void InvalidateMesh(); 126 | 127 | /** Rebuilds the graphics and physics mesh representation if we don't have one right now. Designed to be called on demand. */ 128 | void BuildMesh(); 129 | 130 | 131 | 132 | protected: 133 | 134 | /** Giving a default material to the mesh if no valid material is already assigned or materials array is empty. */ 135 | void AssignDefaultMaterialIfNeeded(); 136 | 137 | /** Updating navoctree entry for this component , if need/possible. */ 138 | void UpdateNavigationIfNeeded(); 139 | 140 | /** Generates a cached mesh from raw street map data */ 141 | void GenerateMesh(); 142 | 143 | /** Adds a 2D line to the raw mesh */ 144 | void AddThick2DLine(const FVector2D Start, const FVector2D End, const float Z, const float Thickness, const FColor& StartColor, const FColor& EndColor, FBox& MeshBoundingBox); 145 | 146 | /** Adds 3D triangles to the raw mesh */ 147 | void AddTriangles(const TArray& Points, const TArray& PointIndices, const FVector& ForwardVector, const FVector& UpVector, const FColor& Color, FBox& MeshBoundingBox); 148 | 149 | 150 | protected: 151 | 152 | /** The street map we're representing. */ 153 | UPROPERTY(EditAnywhere, Category = "StreetMap") 154 | UStreetMap* StreetMap; 155 | 156 | UPROPERTY(EditAnywhere, Category = "StreetMap") 157 | FStreetMapMeshBuildSettings MeshBuildSettings; 158 | 159 | UPROPERTY(EditAnywhere, Category = "StreetMap") 160 | FStreetMapCollisionSettings CollisionSettings; 161 | 162 | //** Physics data for mesh collision. */ 163 | UPROPERTY(Transient) 164 | UBodySetup* StreetMapBodySetup; 165 | 166 | 167 | protected: 168 | // 169 | // Cached mesh representation 170 | // 171 | 172 | /** Cached raw mesh vertices */ 173 | UPROPERTY() 174 | TArray< struct FStreetMapVertex > Vertices; 175 | 176 | /** Cached raw mesh triangle indices */ 177 | UPROPERTY() 178 | TArray< uint32 > Indices; 179 | 180 | /** Cached bounding box */ 181 | UPROPERTY() 182 | FBoxSphereBounds CachedLocalBounds; 183 | 184 | /** Cached StreetMap DefaultMaterial */ 185 | UPROPERTY() 186 | UMaterialInterface* StreetMapDefaultMaterial; 187 | 188 | }; 189 | -------------------------------------------------------------------------------- /Source/StreetMapRuntime/Public/StreetMapRuntime.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | #pragma once 3 | 4 | #include "EngineMinimal.h" 5 | #include "EngineGlobals.h" // For GEngine 6 | 7 | -------------------------------------------------------------------------------- /Source/StreetMapRuntime/Public/StreetMapSceneProxy.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | #pragma once 3 | 4 | #include "Runtime/Engine/Public/PrimitiveSceneProxy.h" 5 | #include "Runtime/Engine/Public/LocalVertexFactory.h" 6 | #include "Runtime/Engine/Public/DynamicMeshBuilder.h" 7 | #include "StreetMapSceneProxy.generated.h" 8 | 9 | /** A single vertex on a street map mesh */ 10 | USTRUCT() 11 | struct FStreetMapVertex 12 | { 13 | 14 | GENERATED_USTRUCT_BODY() 15 | 16 | /** Location of the vertex in local space */ 17 | UPROPERTY() 18 | FVector Position; 19 | 20 | /** Texture coordinate */ 21 | UPROPERTY() 22 | FVector2D TextureCoordinate; 23 | 24 | /** Tangent vector X */ 25 | UPROPERTY() 26 | FVector TangentX; 27 | 28 | /** Tangent vector Z (normal) */ 29 | UPROPERTY() 30 | FVector TangentZ; 31 | 32 | /** Color */ 33 | UPROPERTY() 34 | FColor Color; 35 | 36 | 37 | /** Default constructor, leaves everything uninitialized */ 38 | FStreetMapVertex() 39 | { 40 | } 41 | 42 | /** Construct with a supplied position and tangents for the vertex */ 43 | FStreetMapVertex(const FVector InitLocation, const FVector2D InitTextureCoordinate, const FVector InitTangentX, const FVector InitTangentZ, const FColor InitColor) 44 | : Position(InitLocation), 45 | TextureCoordinate(InitTextureCoordinate), 46 | TangentX(InitTangentX), 47 | TangentZ(InitTangentZ), 48 | Color(InitColor) 49 | { 50 | } 51 | }; 52 | 53 | 54 | /** Scene proxy for rendering a section of a street map mesh on the rendering thread */ 55 | class FStreetMapSceneProxy : public FPrimitiveSceneProxy 56 | { 57 | 58 | public: 59 | 60 | /** Construct this scene proxy */ 61 | FStreetMapSceneProxy(const class UStreetMapComponent* InComponent); 62 | 63 | /** 64 | * Init this street map mesh scene proxy for the specified component (32-bit indices) 65 | * 66 | * @param InComponent The street map mesh component to initialize this with 67 | * @param Vertices The vertices for this street map mesh 68 | * @param Indices The vertex indices for this street map mesh 69 | */ 70 | void Init(const UStreetMapComponent* InComponent, const TArray< FStreetMapVertex >& Vertices, const TArray< uint32 >& Indices); 71 | 72 | /** Destructor that cleans up our rendering data */ 73 | virtual ~FStreetMapSceneProxy(); 74 | 75 | 76 | /** Return a type (or subtype) specific hash for sorting purposes */ 77 | SIZE_T GetTypeHash() const override; 78 | 79 | protected: 80 | 81 | /** Initializes this scene proxy's vertex buffer, index buffer and vertex factory (on the render thread.) */ 82 | void InitResources(); 83 | 84 | /** Makes a MeshBatch for rendering. Called every time the mesh is drawn */ 85 | void MakeMeshBatch(struct FMeshBatch& Mesh, class FMeshElementCollector& Collector, class FMaterialRenderProxy* WireframeMaterialRenderProxyOrNull, bool bDrawCollision = false) const; 86 | 87 | /** Checks to see if this mesh must be drawn during the dynamic pass. Note that even when this returns false, we may still 88 | have other (debug) geometry to render as dynamic */ 89 | bool MustDrawMeshDynamically(const class FSceneView& View) const; 90 | 91 | /** Returns true , if in a collision view */ 92 | bool IsInCollisionView(const FEngineShowFlags& EngineShowFlags) const; 93 | 94 | // FPrimitiveSceneProxy interface 95 | //virtual void DrawStaticElements(class FStaticPrimitiveDrawInterface* PDI) override; 96 | virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, class FMeshElementCollector& Collector) const override; 97 | virtual uint32 GetMemoryFootprint(void) const override; 98 | virtual FPrimitiveViewRelevance GetViewRelevance(const class FSceneView* View) const override; 99 | virtual bool CanBeOccluded() const override; 100 | 101 | protected: 102 | 103 | /** Contains all of the vertices in our street map mesh */ 104 | FStaticMeshVertexBuffers VertexBuffer; 105 | 106 | /** All of the vertex indices32 in our street map mesh */ 107 | FDynamicMeshIndexBuffer32 IndexBuffer32; 108 | 109 | /** Our vertex factory specific to street map meshes */ 110 | FLocalVertexFactory VertexFactory; 111 | 112 | /** Cached material relevance */ 113 | FMaterialRelevance MaterialRelevance; 114 | 115 | /** The material we'll use to render this street map mesh */ 116 | class UMaterialInterface* MaterialInterface; 117 | 118 | const UStreetMapComponent* StreetMapComp; 119 | 120 | // The Collision Response of the component being proxied 121 | FCollisionResponseContainer CollisionResponse; 122 | }; 123 | -------------------------------------------------------------------------------- /Source/StreetMapRuntime/StreetMapRuntime.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Mike Fricker. All Rights Reserved. 2 | 3 | namespace UnrealBuildTool.Rules 4 | { 5 | public class StreetMapRuntime : ModuleRules 6 | { 7 | public StreetMapRuntime(ReadOnlyTargetRules Target) 8 | : base(Target) 9 | { 10 | PrivateDependencyModuleNames.AddRange( 11 | new string[] { 12 | "Core", 13 | "CoreUObject", 14 | "Engine", 15 | "RHI", 16 | "RenderCore", 17 | "PropertyEditor" 18 | } 19 | ); 20 | 21 | PrivateIncludePaths.AddRange(new string[]{"StreetMapRuntime/Private"}); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /StreetMap.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion" : 3, 3 | "Version" : 1, 4 | "VersionName" : "1.0", 5 | "FriendlyName" : "Street Map", 6 | "Description" : "Import and render OpenStreetMap data", 7 | "Category" : "Importers", 8 | "CreatedBy" : "Mike Fricker", 9 | "CreatedByURL" : "http://twitter.com/mike_fricker", 10 | "DocsURL" : "http://github.com/ue4plugins/StreetMap", 11 | "MarketplaceURL" : "", 12 | "SupportURL" : "", 13 | "EnabledByDefault" : true, 14 | "CanContainContent" : true, 15 | "IsBetaVersion" : false, 16 | "Installed" : false, 17 | "Modules" : 18 | [ 19 | { 20 | "Name" : "StreetMapRuntime", 21 | "Type" : "Runtime", 22 | "LoadingPhase" : "Default" 23 | }, 24 | 25 | { 26 | "Name" : "StreetMapImporting", 27 | "Type" : "Editor", 28 | "LoadingPhase" : "Default" 29 | } 30 | ] 31 | } --------------------------------------------------------------------------------