commit 6f0f7f641bfc2bce9cc2278af0dfcad3b5843387 Author: vinfamy Date: Sat Dec 9 23:33:11 2017 +0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a884bd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +Binaries +DerivedDataCache +Intermediate +Saved +.vscode +.vs +*.VC.db +*.opensdf +*.opendb +*.sdf +*.sln +*.suo +*.xcodeproj +*.xcworkspace + +Content/StreetMap/* +Plugins/StreetMap/Binaries/* +Plugins/StreetMap/Intermediate/* +Content/StreetMap/CentralLondon.uasset +Content/StreetMap/Static/CentralLondon0.uasset +*.png diff --git a/Config/DefaultEditor.ini b/Config/DefaultEditor.ini new file mode 100644 index 0000000..139597f --- /dev/null +++ b/Config/DefaultEditor.ini @@ -0,0 +1,2 @@ + + diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini new file mode 100644 index 0000000..c6fd571 --- /dev/null +++ b/Config/DefaultEngine.ini @@ -0,0 +1,56 @@ +[URL] + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +AppliedTargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Maximum +AppliedDefaultGraphicsPerformance=Maximum + +[/Script/EngineSettings.GameMapsSettings] +GlobalDefaultGameMode=/Game/BPs/GM.GM_C +GameInstanceClass=/Game/BPs/GI.GI_C +GameDefaultMap=/Game/Scenes/CentralLondon.CentralLondon +EditorStartupMap=/Game/Scenes/CentralLondon.CentralLondon + +[/Script/Engine.PhysicsSettings] +DefaultGravityZ=-980.000000 +DefaultTerminalVelocity=4000.000000 +DefaultFluidFriction=0.300000 +SimulateScratchMemorySize=262144 +RagdollAggregateThreshold=4 +TriangleMeshTriangleMinAreaThreshold=5.000000 +bEnableAsyncScene=False +bEnableShapeSharing=False +bEnablePCM=True +bEnableStabilization=False +bWarnMissingLocks=True +bEnable2DPhysics=False +LockedAxis=Invalid +DefaultDegreesOfFreedom=Full3D +BounceThresholdVelocity=200.000000 +FrictionCombineMode=Average +RestitutionCombineMode=Average +MaxAngularVelocity=3600.000000 +MaxDepenetrationVelocity=0.000000 +ContactOffsetMultiplier=0.020000 +MinContactOffset=2.000000 +MaxContactOffset=8.000000 +bSimulateSkeletalMeshOnDedicatedServer=True +DefaultShapeComplexity=CTF_UseSimpleAndComplex +bDefaultHasComplexCollision=True +bSuppressFaceRemapTable=False +bSupportUVFromHitResults=False +bDisableActiveActors=False +bDisableCCD=False +bEnableEnhancedDeterminism=False +MaxPhysicsDeltaTime=0.033333 +bSubstepping=False +bSubsteppingAsync=False +MaxSubstepDeltaTime=0.016667 +MaxSubsteps=6 +SyncSceneSmoothingFactor=0.000000 +AsyncSceneSmoothingFactor=0.990000 +InitialAverageFrameRate=0.016667 +PhysXTreeRebuildRate=10 + + diff --git a/Config/DefaultGame.ini b/Config/DefaultGame.ini new file mode 100644 index 0000000..37f56e9 --- /dev/null +++ b/Config/DefaultGame.ini @@ -0,0 +1,2 @@ +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=543F4F9E4DE525696C82F5A52303C15C diff --git a/Config/DefaultInput.ini b/Config/DefaultInput.ini new file mode 100644 index 0000000..19a4576 --- /dev/null +++ b/Config/DefaultInput.ini @@ -0,0 +1,63 @@ + +[/Script/Engine.InputSettings] +-AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) ++AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_TriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_Grip1Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_Grip2Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_TriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_Grip1Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_Grip2Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) +bAltEnterTogglesFullscreen=True +bF11TogglesFullscreen=True +bUseMouseForTouch=False +bEnableMouseSmoothing=True +bEnableFOVScaling=True +FOVScale=0.011110 +DoubleClickTime=0.200000 +bCaptureMouseOnLaunch=True +DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown +bDefaultViewportMouseLock=False +DefaultViewportMouseLockMode=LockOnCapture ++ActionMappings=(ActionName="RotateCamera",Key=MiddleMouseButton,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="CircleCamera",Key=C,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="ActionX",Key=MouseX,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++ActionMappings=(ActionName="ActionY",Key=MouseY,bShift=False,bCtrl=False,bAlt=False,bCmd=False) ++AxisMappings=(AxisName="LeftRight",Key=Right,Scale=1.000000) ++AxisMappings=(AxisName="LeftRight",Key=Left,Scale=-1.000000) ++AxisMappings=(AxisName="DownUp",Key=Up,Scale=1.000000) ++AxisMappings=(AxisName="DownUp",Key=Down,Scale=-1.000000) ++AxisMappings=(AxisName="ScrollDownUp",Key=MouseWheelAxis,Scale=1.000000) ++AxisMappings=(AxisName="LeftRight",Key=D,Scale=1.000000) ++AxisMappings=(AxisName="LeftRight",Key=A,Scale=-1.000000) ++AxisMappings=(AxisName="DownUp",Key=W,Scale=1.000000) ++AxisMappings=(AxisName="DownUp",Key=S,Scale=-1.000000) ++AxisMappings=(AxisName="CursorX",Key=MouseX,Scale=1.000000) ++AxisMappings=(AxisName="CursorY",Key=MouseY,Scale=1.000000) +bAlwaysShowTouchInterface=False +bShowConsoleOnFourFingerTap=True +DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks +ConsoleKey=None +-ConsoleKeys=Tilde ++ConsoleKeys=Tilde + + diff --git a/Content/BPs/BallTest.uasset b/Content/BPs/BallTest.uasset new file mode 100644 index 0000000..c8c6898 Binary files /dev/null and b/Content/BPs/BallTest.uasset differ diff --git a/Content/BPs/Birdview.uasset b/Content/BPs/Birdview.uasset new file mode 100644 index 0000000..864c49a Binary files /dev/null and b/Content/BPs/Birdview.uasset differ diff --git a/Content/BPs/ExampleActor.uasset b/Content/BPs/ExampleActor.uasset new file mode 100644 index 0000000..d6cee86 Binary files /dev/null and b/Content/BPs/ExampleActor.uasset differ diff --git a/Content/BPs/ExampleStreetMap.uasset b/Content/BPs/ExampleStreetMap.uasset new file mode 100644 index 0000000..d203621 Binary files /dev/null and b/Content/BPs/ExampleStreetMap.uasset differ diff --git a/Content/BPs/GI.uasset b/Content/BPs/GI.uasset new file mode 100644 index 0000000..2372a2e Binary files /dev/null and b/Content/BPs/GI.uasset differ diff --git a/Content/BPs/GM.uasset b/Content/BPs/GM.uasset new file mode 100644 index 0000000..28a3c9f Binary files /dev/null and b/Content/BPs/GM.uasset differ diff --git a/Content/BPs/PC.uasset b/Content/BPs/PC.uasset new file mode 100644 index 0000000..3adc3b5 Binary files /dev/null and b/Content/BPs/PC.uasset differ diff --git a/Content/BPs/Pawn.uasset b/Content/BPs/Pawn.uasset new file mode 100644 index 0000000..2c5ae39 Binary files /dev/null and b/Content/BPs/Pawn.uasset differ diff --git a/Content/BPs/StreetMap_Static.uasset b/Content/BPs/StreetMap_Static.uasset new file mode 100644 index 0000000..23a4c73 Binary files /dev/null and b/Content/BPs/StreetMap_Static.uasset differ diff --git a/Content/Scenes/CentralLondon.umap b/Content/Scenes/CentralLondon.umap new file mode 100644 index 0000000..089454e Binary files /dev/null and b/Content/Scenes/CentralLondon.umap differ diff --git a/Content/Scenes/CentralLondon_BuiltData.uasset b/Content/Scenes/CentralLondon_BuiltData.uasset new file mode 100644 index 0000000..722da30 Binary files /dev/null and b/Content/Scenes/CentralLondon_BuiltData.uasset differ diff --git a/LifePlay.uproject b/LifePlay.uproject new file mode 100644 index 0000000..7e03a12 --- /dev/null +++ b/LifePlay.uproject @@ -0,0 +1,13 @@ +{ + "FileVersion": 3, + "EngineAssociation": "4.18", + "Category": "", + "Description": "", + "Modules": [ + { + "Name": "LifePlay", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ] +} \ No newline at end of file diff --git a/Plugins/StreetMap/.gitignore b/Plugins/StreetMap/.gitignore new file mode 100644 index 0000000..cfbcb6f --- /dev/null +++ b/Plugins/StreetMap/.gitignore @@ -0,0 +1,2 @@ +Binaries/ +Intermediate/ diff --git a/Plugins/StreetMap/Content/StreetMapDefaultMaterial.uasset b/Plugins/StreetMap/Content/StreetMapDefaultMaterial.uasset new file mode 100644 index 0000000..c296801 Binary files /dev/null and b/Plugins/StreetMap/Content/StreetMapDefaultMaterial.uasset differ diff --git a/Plugins/StreetMap/LICENSE.txt b/Plugins/StreetMap/LICENSE.txt new file mode 100644 index 0000000..006e478 --- /dev/null +++ b/Plugins/StreetMap/LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2017 Mike Fricker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/Plugins/StreetMap/README.md b/Plugins/StreetMap/README.md new file mode 100644 index 0000000..9d0f5ef --- /dev/null +++ b/Plugins/StreetMap/README.md @@ -0,0 +1,144 @@ +# Street Map Plugin for UE4 + +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. + +![UE4OSMBrooklyn](Docs/UE4OSMBrooklyn.png) + +![UE4OSMRaleigh](Docs/UE4OSMRaleigh.png) + +Have fun!! --[Mike](http://twitter.com/mike_fricker) + +*(Note: This plugin is a just a fun weekend project and not officially supported by Epic.)* + + +## Quick Start + +It's easy to get up and running: + +* Download the StreetMap plugin source from this page (click **Clone or download** -> **Download ZIP**). + +* Unzip the files into a new **StreetMap** sub-folder under your project's **Plugins** folder. It should end up looking like *"/MyProject/Plugins/StreetMap/"* + +* **Rebuild** your C++ project. The new plugin will be compiled too! + +* Load the editor. You can now drag and drop **OpenStreetMap XML files** (.osm) into Content Browser to import map data! + +* 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. + +![UE4OSMManhattan](Docs/UE4OSMActor.png) + + +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. + +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). + + +## Getting OpenStreetMap Data + +**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). + +![UE4OSMExport](Docs/UE4OSMExport.png) + +Here's how to get data for a location you're interested in: + +**For larger areas (more than a neighborhood or small town) you should use [Mapzen Extracts](https://mapzen.com/data/metro-extracts).** + +* Go to [OpenStreetMap.org](http://www.openstreetmap.org) and use the search feature to navigate to your *favorite location on Earth*. + +* Click the **Export** button on navigation bar at the top of the page to go into *Export Mode*. + +* 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. + +* When you're ready, click **Export** on the left. OpenStreetMap will **generate an XML file** and initiate the download soon. + +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. + +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. + +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. + +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. + +## Editing OpenStreetMap + +**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. + +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: +* http://learnosm.org +* https://wiki.openstreetmap.org/wiki/Video_tutorials + +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! + +## Plugin Details + +### Street Map Assets + +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. + +Roads are imported with *full connectivity data*! This means you can design your own navigation algorithms pretty easily. + +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. + +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. + + +### Street Map Components + +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. + +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. + +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. + +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. + +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. + +*(Street Map Component also serves as a straightforward example of how to write your own primitive components in UE4.)* + + +### OSM Files + +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. + +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.) + +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. + + +### Known Issues + +There are various loose ends. + +* Importing files larger than 2GB will crash. This is a current UE4 limitation. + +* Some variants of generated OSM XML files won't load correctly. For example, single-quote delimeters around values are not supported yet. + +* 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. + +* 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. + +* Runtime data structures are setup to support pathfinding (see **FStreetMapNode** member functions), but no example implementation of a GPS algorithm is included yet. + +* 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. + +* You can search for **@todo** in the plugin source code for other minor improvements that could be made. + + +### Compatibility + +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)! + +The Street Map plugin should work on all platforms that UE4 supports, but the latest version has not been tested on every platform. + +We'll try to keep the source code up to date so that it works with new versions Unreal Engine as they are released. + + +## Support + +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. + +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). + +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. + diff --git a/Plugins/StreetMap/Source/StreetMapImporting/OSMFile.cpp b/Plugins/StreetMap/Source/StreetMapImporting/OSMFile.cpp new file mode 100644 index 0000000..d386227 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/OSMFile.cpp @@ -0,0 +1,411 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#include "StreetMapImporting.h" +#include "OSMFile.h" + + +FOSMFile::FOSMFile() + : ParsingState( ParsingState::Root ) +{ +} + + +FOSMFile::~FOSMFile() +{ + // Clean up time + { + for( auto* Way : Ways ) + { + delete Way; + } + Ways.Empty(); + + for( auto HashPair : NodeMap ) + { + FOSMNodeInfo* NodeInfo = HashPair.Value; + delete NodeInfo; + } + NodeMap.Empty(); + } +} + + +bool FOSMFile::LoadOpenStreetMapFile( FString& OSMFilePath, const bool bIsFilePathActuallyTextBuffer, FFeedbackContext* FeedbackContext ) +{ + const bool bShowSlowTaskDialog = true; + const bool bShowCancelButton = true; + + FText ErrorMessage; + int32 ErrorLineNumber; + if( FFastXml::ParseXmlFile( + this, + bIsFilePathActuallyTextBuffer ? nullptr : *OSMFilePath, + bIsFilePathActuallyTextBuffer ? OSMFilePath.GetCharArray().GetData() : nullptr, + FeedbackContext, + bShowSlowTaskDialog, + bShowCancelButton, + /* Out */ ErrorMessage, + /* Out */ ErrorLineNumber ) ) + { + if( NodeMap.Num() > 0 ) + { + AverageLatitude /= NodeMap.Num(); + AverageLongitude /= NodeMap.Num(); + } + + return true; + } + + if( FeedbackContext != nullptr ) + { + FeedbackContext->Logf( + ELogVerbosity::Error, + TEXT( "Failed to load OpenStreetMap XML file ('%s', Line %i)" ), + *ErrorMessage.ToString(), + ErrorLineNumber ); + } + + return false; +} + + +bool FOSMFile::ProcessXmlDeclaration( const TCHAR* ElementData, int32 XmlFileLineNumber ) +{ + // Don't care about XML declaration + return true; +} + + +bool FOSMFile::ProcessComment( const TCHAR* Comment ) +{ + // Don't care about comments + return true; +} + + +bool FOSMFile::ProcessElement( const TCHAR* ElementName, const TCHAR* ElementData, int32 XmlFileLineNumber ) +{ + if( ParsingState == ParsingState::Root ) + { + if( !FCString::Stricmp( ElementName, TEXT( "node" ) ) ) + { + ParsingState = ParsingState::Node; + CurrentNodeInfo = new FOSMNodeInfo(); + CurrentNodeInfo->Latitude = 0.0; + CurrentNodeInfo->Longitude = 0.0; + } + else if( !FCString::Stricmp( ElementName, TEXT( "way" ) ) ) + { + ParsingState = ParsingState::Way; + CurrentWayInfo = new FOSMWayInfo(); + CurrentWayInfo->Name.Empty(); + CurrentWayInfo->Ref.Empty(); + CurrentWayInfo->WayType = EOSMWayType::Other; + CurrentWayInfo->Height = 0.0; + CurrentWayInfo->bIsOneWay = false; + + // @todo: We're currently ignoring the "visible" tag on ways, which means that roads will always + // be included in our data set. It might be nice to make this an import option. + } + } + else if( ParsingState == ParsingState::Way ) + { + if( !FCString::Stricmp( ElementName, TEXT( "nd" ) ) ) + { + ParsingState = ParsingState::Way_NodeRef; + } + else if( !FCString::Stricmp( ElementName, TEXT( "tag" ) ) ) + { + ParsingState = ParsingState::Way_Tag; + } + } + + return true; +} + + +bool FOSMFile::ProcessAttribute( const TCHAR* AttributeName, const TCHAR* AttributeValue ) +{ + if( ParsingState == ParsingState::Node ) + { + if( !FCString::Stricmp( AttributeName, TEXT( "id" ) ) ) + { + CurrentNodeID = FPlatformString::Atoi64( AttributeValue ); + } + else if( !FCString::Stricmp( AttributeName, TEXT( "lat" ) ) ) + { + CurrentNodeInfo->Latitude = FPlatformString::Atod( AttributeValue ); + + AverageLatitude += CurrentNodeInfo->Latitude; + + // Update minimum and maximum latitude + // @todo: Performance: Instead of computing our own bounding box, we could parse the "minlat" and + // "minlon" tags from the OSM file + if( CurrentNodeInfo->Latitude < MinLatitude ) + { + MinLatitude = CurrentNodeInfo->Latitude; + } + if( CurrentNodeInfo->Latitude > MaxLatitude ) + { + MaxLatitude = CurrentNodeInfo->Latitude; + } + } + else if( !FCString::Stricmp( AttributeName, TEXT( "lon" ) ) ) + { + CurrentNodeInfo->Longitude = FPlatformString::Atod( AttributeValue ); + + AverageLongitude += CurrentNodeInfo->Longitude; + + // Update minimum and maximum longitude + if( CurrentNodeInfo->Longitude < MinLongitude ) + { + MinLongitude = CurrentNodeInfo->Longitude; + } + if( CurrentNodeInfo->Longitude > MaxLongitude ) + { + MaxLongitude = CurrentNodeInfo->Longitude; + } + } + } + else if( ParsingState == ParsingState::Way ) + { + // ... + } + else if( ParsingState == ParsingState::Way_NodeRef ) + { + if( !FCString::Stricmp( AttributeName, TEXT( "ref" ) ) ) + { + FOSMNodeInfo* ReferencedNode = NodeMap.FindRef( FPlatformString::Atoi64( AttributeValue ) ); + const int NewNodeIndex = CurrentWayInfo->Nodes.Num(); + CurrentWayInfo->Nodes.Add( ReferencedNode ); + + // Update the node with information about the way that is referencing it + { + FOSMWayRef NewWayRef; + NewWayRef.Way = CurrentWayInfo; + NewWayRef.NodeIndex = NewNodeIndex; + ReferencedNode->WayRefs.Add( NewWayRef ); + } + } + } + else if( ParsingState == ParsingState::Way_Tag ) + { + if( !FCString::Stricmp( AttributeName, TEXT( "k" ) ) ) + { + CurrentWayTagKey = AttributeValue; + } + else if( !FCString::Stricmp( AttributeName, TEXT( "v" ) ) ) + { + if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "name" ) ) ) + { + CurrentWayInfo->Name = AttributeValue; + } + else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "ref" ) ) ) + { + CurrentWayInfo->Ref = AttributeValue; + } + else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "highway" ) ) ) + { + EOSMWayType WayType = EOSMWayType::Other; + + if( !FCString::Stricmp( AttributeValue, TEXT( "motorway" ) ) ) + { + WayType = EOSMWayType::Motorway; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "motorway_link" ) ) ) + { + WayType = EOSMWayType::Motorway_Link; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "trunk" ) ) ) + { + WayType = EOSMWayType::Trunk; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "trunk_link" ) ) ) + { + WayType = EOSMWayType::Trunk_Link; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "primary" ) ) ) + { + WayType = EOSMWayType::Primary; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "primary_link" ) ) ) + { + WayType = EOSMWayType::Primary_Link; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "secondary" ) ) ) + { + WayType = EOSMWayType::Secondary; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "secondary_link" ) ) ) + { + WayType = EOSMWayType::Secondary_Link; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "tertiary" ) ) ) + { + WayType = EOSMWayType::Tertiary; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "tertiary_link" ) ) ) + { + WayType = EOSMWayType::Tertiary_Link; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "residential" ) ) ) + { + WayType = EOSMWayType::Residential; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "service" ) ) ) + { + WayType = EOSMWayType::Service; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "unclassified" ) ) ) + { + WayType = EOSMWayType::Unclassified; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "living_street" ) ) ) + { + WayType = EOSMWayType::Living_Street; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "pedestrian" ) ) ) + { + WayType = EOSMWayType::Pedestrian; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "track" ) ) ) + { + WayType = EOSMWayType::Track; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "bus_guideway" ) ) ) + { + WayType = EOSMWayType::Bus_Guideway; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "raceway" ) ) ) + { + WayType = EOSMWayType::Raceway; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "road" ) ) ) + { + WayType = EOSMWayType::Road; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "footway" ) ) ) + { + WayType = EOSMWayType::Footway; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "cycleway" ) ) ) + { + WayType = EOSMWayType::Cycleway; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "bridleway" ) ) ) + { + WayType = EOSMWayType::Bridleway; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "steps" ) ) ) + { + WayType = EOSMWayType::Steps; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "path" ) ) ) + { + WayType = EOSMWayType::Path; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "proposed" ) ) ) + { + WayType = EOSMWayType::Proposed; + } + else if( !FCString::Stricmp( AttributeValue, TEXT( "construction" ) ) ) + { + WayType = EOSMWayType::Construction; + } + else + { + // Other type that we don't recognize yet. See http://wiki.openstreetmap.org/wiki/Key:highway + } + + + CurrentWayInfo->WayType = WayType; + } + else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "building" ) ) ) + { + CurrentWayInfo->WayType = EOSMWayType::Building; + // Vinfamy added + CurrentWayInfo->Building = AttributeValue; + + if( !FCString::Stricmp( AttributeValue, TEXT( "yes" ) ) ) + { + CurrentWayInfo->WayType = EOSMWayType::Building; + } + else + { + // Other type that we don't recognize yet. See http://wiki.openstreetmap.org/wiki/Key:building + } + } + else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "height" ) ) ) + { + // Check to see if there is a space character in the height value. For now, we're looking + // for straight-up floating point values. + if( !FString( AttributeValue ).Contains( TEXT( " " ) ) ) + { + // Okay, no space character. So this has got to be a floating point number. The OSM + // spec says that the height values are in meters. + CurrentWayInfo->Height = FPlatformString::Atod( AttributeValue ); + } + else + { + // Looks like the height value contains units of some sort. + // @todo: Add support for interpreting unit strings and converting the values + } + } + else if (!FCString::Stricmp(CurrentWayTagKey, TEXT("building:levels"))) + { + CurrentWayInfo->BuildingLevels = FPlatformString::Atoi(AttributeValue); + } + + // Vinfamy added + else if (!FCString::Stricmp(CurrentWayTagKey, TEXT("amenity"))) + { + CurrentWayInfo->Amenity = AttributeValue; + } + + else if( !FCString::Stricmp( CurrentWayTagKey, TEXT( "oneway" ) ) ) + { + if( !FCString::Stricmp( AttributeValue, TEXT( "yes" ) ) ) + { + CurrentWayInfo->bIsOneWay = true; + } + else + { + CurrentWayInfo->bIsOneWay = false; + } + } + } + } + + return true; +} + + +bool FOSMFile::ProcessClose( const TCHAR* Element ) +{ + if( ParsingState == ParsingState::Node ) + { + NodeMap.Add( CurrentNodeID, CurrentNodeInfo ); + CurrentNodeID = 0; + CurrentNodeInfo = nullptr; + + ParsingState = ParsingState::Root; + } + else if( ParsingState == ParsingState::Way ) + { + Ways.Add( CurrentWayInfo ); + CurrentWayInfo = nullptr; + + ParsingState = ParsingState::Root; + } + else if( ParsingState == ParsingState::Way_NodeRef ) + { + ParsingState = ParsingState::Way; + } + else if( ParsingState == ParsingState::Way_Tag ) + { + CurrentWayTagKey = TEXT( "" ); + ParsingState = ParsingState::Way; + } + + return true; +} diff --git a/Plugins/StreetMap/Source/StreetMapImporting/OSMFile.h b/Plugins/StreetMap/Source/StreetMapImporting/OSMFile.h new file mode 100644 index 0000000..98895db --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/OSMFile.h @@ -0,0 +1,229 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. +#pragma once + +#include "FastXml.h" + + +/** OpenStreetMap file loader */ +class FOSMFile : public IFastXmlCallback +{ + +public: + + /** Default constructor for FOSMFile */ + FOSMFile(); + + /** Destructor for FOSMFile */ + virtual ~FOSMFile(); + + /** 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. */ + bool LoadOpenStreetMapFile( FString& OSMFilePath, const bool bIsFilePathActuallyTextBuffer, class FFeedbackContext* FeedbackContext ); + + + struct FOSMWayInfo; + + /** Types of ways */ + enum class EOSMWayType + { + /// + /// ROADS + /// + + /** A restricted access major divided highway, normally with 2 or more running lanes plus emergency hard shoulder. Equivalent to the Freeway, Autobahn, etc. */ + Motorway, + + /** The link roads (sliproads/ramps) leading to/from a motorway from/to a motorway or lower class highway. Normally with the same motorway restrictions. */ + Motorway_Link, + + /** The most important roads in a country's system that aren't motorways. (Need not necessarily be a divided highway.) */ + Trunk, + + /** The link roads (sliproads/ramps) leading to/from a trunk road from/to a trunk road or lower class highway. */ + Trunk_Link, + + /** The next most important roads in a country's system. (Often link larger towns.) */ + Primary, + + /** The link roads (sliproads/ramps) leading to/from a primary road from/to a primary road or lower class highway. */ + Primary_Link, + + /** The next most important roads in a country's system. (Often link smaller towns and villages.) */ + Secondary, + + /** The link roads (sliproads/ramps) leading to/from a secondary road from/to a secondary road or lower class highway. */ + Secondary_Link, + + /** The next most important roads in a country's system. */ + Tertiary, + + /** The link roads (sliproads/ramps) leading to/from a tertiary road from/to a tertiary road or lower class highway. */ + Tertiary_Link, + + /** Roads which are primarily lined with and serve as an access to housing. */ + Residential, + + /** For access roads to, or within an industrial estate, camp site, business park, car park etc. */ + Service, + + /** 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. */ + Unclassified, + + + /// + /// NON-ROADS + /// + + /** Residential streets where pedestrians have legal priority over cars, speeds are kept very low and where children are allowed to play on the street. */ + Living_Street, + + /** 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. */ + Pedestrian, + + /** 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.) */ + Track, + + /** A busway where the vehicle guided by the way (though not a railway) and is not suitable for other traffic. */ + Bus_Guideway, + + /** A course or track for (motor) racing */ + Raceway, + + /** A road where the mapper is unable to ascertain the classification from the information available. */ + Road, + + + /// + /// PATHS + /// + + /** For designated footpaths; i.e., mainly/exclusively for pedestrians. This includes walking tracks and gravel paths. */ + Footway, + + /** For designated cycleways. */ + Cycleway, + + /** Paths normally used by horses */ + Bridleway, + + /** For flights of steps (stairs) on footways. */ + Steps, + + /** A non-specific path. */ + Path, + + + /// + /// LIFECYCLE + /// + + /** For planned roads, use with proposed=* and also proposed=* with a value of the proposed highway value. */ + Proposed, + + /** For roads under construction. */ + Construction, + + + /// + /// BUILDINGS + /// + + /** Default type of building. A general catch-all. */ + Building, + + + /// + /// UNSUPPORTED + /// + + /** Currently unrecognized type */ + Other, + }; + + + struct FOSMWayRef + { + // Way that we're referencing at this node + FOSMWayInfo* Way; + + // Index of the node in the way's array of nodes + int32 NodeIndex; + }; + + + struct FOSMNodeInfo + { + double Latitude; + double Longitude; + TArray WayRefs; + }; + + + struct FOSMWayInfo + { + FString Name; + FString Ref; + TArray Nodes; + EOSMWayType WayType; + double Height; + int32 BuildingLevels; + FString Amenity; + FString Building; + + // If true, way is only traversable in the order the nodes are listed in the Nodes list + uint8 bIsOneWay : 1; + }; + + // Minimum latitude/longitude bounds + double MinLatitude = MAX_dbl; + double MinLongitude = MAX_dbl; + double MaxLatitude = -MAX_dbl; + double MaxLongitude = -MAX_dbl; + + // Average Latitude (roughly the center of the map) + double AverageLatitude = 0.0; + double AverageLongitude = 0.0; + + // All ways we've parsed + TArray Ways; + + // Maps node IDs to info about each node + TMap NodeMap; + +protected: + + // IFastXmlCallback overrides + virtual bool ProcessXmlDeclaration( const TCHAR* ElementData, int32 XmlFileLineNumber ) override; + virtual bool ProcessComment( const TCHAR* Comment ) override; + virtual bool ProcessElement( const TCHAR* ElementName, const TCHAR* ElementData, int32 XmlFileLineNumber ) override; + virtual bool ProcessAttribute( const TCHAR* AttributeName, const TCHAR* AttributeValue ) override; + virtual bool ProcessClose( const TCHAR* Element ) override; + + +protected: + + enum class ParsingState + { + Root, + Node, + Way, + Way_NodeRef, + Way_Tag + }; + + // Current state of parser + ParsingState ParsingState; + + // ID of node that is currently being parsed + int64 CurrentNodeID; + + // Node that is currently being parsed + FOSMNodeInfo* CurrentNodeInfo; + + // Way that is currently being parsed + FOSMWayInfo* CurrentWayInfo; + + // Current way's tag key string + const TCHAR* CurrentWayTagKey; +}; + + diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapActorFactory.cpp b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapActorFactory.cpp new file mode 100644 index 0000000..774e2e8 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapActorFactory.cpp @@ -0,0 +1,49 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "StreetMapImporting.h" +#include "AssetData.h" +#include "StreetMapActorFactory.h" +#include "StreetMapActor.h" +#include "StreetMapComponent.h" +#include "StreetMap.h" + + +////////////////////////////////////////////////////////////////////////// +// UStreetMapActorFactory + +UStreetMapActorFactory::UStreetMapActorFactory(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + DisplayName = NSLOCTEXT("StreetMap", "StreetMapFactoryDisplayName", "Add StreetMap Actor"); + NewActorClass = AStreetMapActor::StaticClass(); +} + +void UStreetMapActorFactory::PostSpawnActor(UObject* Asset, AActor* NewActor) +{ + Super::PostSpawnActor(Asset, NewActor); + + if (UStreetMap* StreetMapAsset = Cast(Asset)) + { + AStreetMapActor* StreetMapActor = CastChecked(NewActor); + UStreetMapComponent* StreetMapComponent = StreetMapActor->GetStreetMapComponent(); + StreetMapComponent->SetStreetMap(StreetMapAsset, false, true); + } +} + +void UStreetMapActorFactory::PostCreateBlueprint(UObject* Asset, AActor* CDO) +{ + if (Asset != nullptr && CDO != nullptr) + { + UStreetMap* StreetMapAsset = CastChecked(Asset); + AStreetMapActor* StreetMapActor = CastChecked(CDO); + UStreetMapComponent* StreetMapComponent = StreetMapActor->GetStreetMapComponent(); + StreetMapComponent->SetStreetMap(StreetMapAsset, true, false); + } +} + +bool UStreetMapActorFactory::CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) +{ + return (AssetData.IsValid() && AssetData.GetClass()->IsChildOf(UStreetMap::StaticClass())); +} diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapActorFactory.h b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapActorFactory.h new file mode 100644 index 0000000..273d8c1 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapActorFactory.h @@ -0,0 +1,17 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#pragma once + +#include "StreetMapActorFactory.generated.h" + +UCLASS() +class UStreetMapActorFactory : public UActorFactory +{ + GENERATED_UCLASS_BODY() + + //~ Begin UActorFactory Interface + virtual void PostSpawnActor(UObject* Asset, AActor* NewActor) override; + virtual void PostCreateBlueprint(UObject* Asset, AActor* CDO) override; + virtual bool CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) override; + //~ End UActorFactory Interface +}; diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapAssetTypeActions.cpp b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapAssetTypeActions.cpp new file mode 100644 index 0000000..16dd2e1 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapAssetTypeActions.cpp @@ -0,0 +1,66 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#include "StreetMapImporting.h" +#include "StreetMapAssetTypeActions.h" +#include "StreetMap.h" +#include "AssetData.h" + + +#define LOCTEXT_NAMESPACE "StreetMapImporting" + + +FText FStreetMapAssetTypeActions::GetName() const +{ + return LOCTEXT("StreetMapAssetTypeActionsName", "Street Map"); +} + + +FColor FStreetMapAssetTypeActions::GetTypeColor() const +{ + return FColor(50, 255, 120); +} + + +UClass* FStreetMapAssetTypeActions::GetSupportedClass() const +{ + return UStreetMap::StaticClass(); +} + + +void FStreetMapAssetTypeActions::OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor) +{ + FSimpleAssetEditor::CreateEditor( EToolkitMode::Standalone, EditWithinLevelEditor, InObjects ); +} + + +uint32 FStreetMapAssetTypeActions::GetCategories() +{ + return EAssetTypeCategories::Misc; +} + + +FText FStreetMapAssetTypeActions::GetAssetDescription(const FAssetData& AssetData) const +{ + return FText::GetEmpty(); +} + + +bool FStreetMapAssetTypeActions::IsImportedAsset() const +{ + return true; +} + + +void FStreetMapAssetTypeActions::GetResolvedSourceFilePaths(const TArray& TypeAssets, TArray& OutSourceFilePaths) const +{ + for (auto* Asset : TypeAssets) + { + const auto* StreetMap = CastChecked(Asset); + if( !StreetMap->AssetImportData->GetFirstFilename().IsEmpty() ) + { + OutSourceFilePaths.Add( StreetMap->AssetImportData->GetFirstFilename() ); + } + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapAssetTypeActions.h b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapAssetTypeActions.h new file mode 100644 index 0000000..294936d --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapAssetTypeActions.h @@ -0,0 +1,19 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. +#pragma once + +#include "AssetTypeActions_Base.h" + +class FStreetMapAssetTypeActions : public FAssetTypeActions_Base +{ +public: + + // IAssetTypeActions interface + virtual FText GetName() const override; + virtual FColor GetTypeColor() const override; + virtual UClass* GetSupportedClass() const override; + virtual void OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr()) override; + virtual uint32 GetCategories() override; + virtual FText GetAssetDescription(const FAssetData& AssetData) const override; + virtual bool IsImportedAsset() const override; + virtual void GetResolvedSourceFilePaths( const TArray& TypeAssets, TArray& OutSourceFilePaths ) const override; +}; diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapComponentDetails.cpp b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapComponentDetails.cpp new file mode 100644 index 0000000..e17000c --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapComponentDetails.cpp @@ -0,0 +1,379 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#include "StreetMapImporting.h" + +#include "StreetMapComponentDetails.h" + +#include "SlateBasics.h" +#include "RawMesh.h" +#include "PropertyEditorModule.h" +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" +#include "DetailWidgetRow.h" +#include "PropertyCustomizationHelpers.h" +#include "IDetailsView.h" +#include "IDetailCustomization.h" +#include "AssetRegistryModule.h" +#include "DlgPickAssetPath.h" +#include "IDetailCustomization.h" +#include "SNotificationList.h" +#include "NotificationManager.h" +#include "AssertionMacros.h" + + +#include "StreetMapComponent.h" + + +#define LOCTEXT_NAMESPACE "StreetMapComponentDetails" + + +FStreetMapComponentDetails::FStreetMapComponentDetails() : + SelectedStreetMapComponent(nullptr), + LastDetailBuilderPtr(nullptr) +{ + +} + +TSharedRef FStreetMapComponentDetails::MakeInstance() +{ + return MakeShareable(new FStreetMapComponentDetails()); +} + +void FStreetMapComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + LastDetailBuilderPtr = &DetailBuilder; + + TArray > SelectedObjects = DetailBuilder.GetDetailsView()->GetSelectedObjects(); + + for (const TWeakObjectPtr& Object : SelectedObjects) + { + UStreetMapComponent* TempStreetMapComp = Cast(Object.Get()); + if (TempStreetMapComp != nullptr && !TempStreetMapComp->IsTemplate()) + { + SelectedStreetMapComponent = TempStreetMapComp; + break; + } + } + + + if (SelectedStreetMapComponent == nullptr) + { + TArray> SelectedActors = DetailBuilder.GetDetailsView()->GetSelectedActors(); + + for (const TWeakObjectPtr& Object : SelectedObjects) + { + AActor* TempActor = Cast(Object.Get()); + if (TempActor != nullptr && !TempActor->IsTemplate()) + { + UStreetMapComponent* TempStreetMapComp = TempActor->FindComponentByClass(); + if (TempStreetMapComp != nullptr && !TempStreetMapComp->IsTemplate()) + { + SelectedStreetMapComponent = TempStreetMapComp; + break; + } + break; + } + } + } + + + + if (SelectedStreetMapComponent == nullptr) + { + return; + } + + + IDetailCategoryBuilder& StreetMapCategory = DetailBuilder.EditCategory("StreetMap", FText::GetEmpty(), ECategoryPriority::Important); + StreetMapCategory.InitiallyCollapsed(false); + + + const bool bCanRebuildMesh = HasValidMapObject(); + const bool bCanClearMesh = HasValidMeshData(); + const bool bCanCreateMeshAsset = HasValidMeshData(); + + TSharedPtr< SHorizontalBox > TempHorizontalBox; + + StreetMapCategory.AddCustomRow(FText::GetEmpty(), false) + [ + SAssignNew(TempHorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("GenerateMesh_Tooltip", "Generate a cached mesh from raw street map data.")) + .OnClicked(this, &FStreetMapComponentDetails::OnBuildMeshClicked) + .IsEnabled(bCanRebuildMesh) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text(bCanClearMesh ? LOCTEXT("RebuildMesh", "Rebuild Mesh") : LOCTEXT("BuildMesh", "Build Mesh")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ]; + + TempHorizontalBox->AddSlot() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("ClearMesh_Tooltip", "Clear current mesh data , in case we have a valid mesh ")) + .OnClicked(this, &FStreetMapComponentDetails::OnClearMeshClicked) + .IsEnabled(bCanClearMesh) + [ + SNew(STextBlock) + .Text(LOCTEXT("ClearMesh", "Clear Mesh")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ]; + + + StreetMapCategory.AddCustomRow(FText::GetEmpty(), false) + [ + SAssignNew(TempHorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("CreateStaticMeshAsset_Tooltip", "Create a new Static Mesh Asset from selected StreetMapComponent.")) + .OnClicked(this, &FStreetMapComponentDetails::OnCreateStaticMeshAssetClicked) + .IsEnabled(bCanCreateMeshAsset) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("CreateStaticMeshAsset", "Create Static Mesh Asset")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ]; + + if (bCanCreateMeshAsset) + { + + const int32 NumVertices = SelectedStreetMapComponent->GetRawMeshVertices().Num(); + const FString NumVerticesToString = TEXT("Vertex Count : ") + FString::FromInt(NumVertices); + + const int32 NumTriangles = SelectedStreetMapComponent->GetRawMeshIndices().Num() / 3; + const FString NumTrianglesToString = TEXT("Triangle Count : ") + FString::FromInt(NumTriangles); + + const bool bCollisionEnabled = SelectedStreetMapComponent->IsCollisionEnabled(); + const FString CollisionStatusToString = bCollisionEnabled ? TEXT("Collision : ON") : TEXT("Collision : OFF"); + + StreetMapCategory.AddCustomRow(FText::GetEmpty(), true) + [ + SAssignNew(TempHorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Font(FSlateFontInfo("Verdana", 8)) + .Text(FText::FromString(NumVerticesToString)) + ] + + SHorizontalBox::Slot() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Font(FSlateFontInfo("Verdana", 8)) + .Text(FText::FromString(NumTrianglesToString)) + ] + + SHorizontalBox::Slot() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Font(FSlateFontInfo("Verdana", 8)) + .Text(FText::FromString(CollisionStatusToString)) + ] + ]; + } + +} + +bool FStreetMapComponentDetails::HasValidMeshData() const +{ + return SelectedStreetMapComponent != nullptr && SelectedStreetMapComponent->HasValidMesh(); +} + + +bool FStreetMapComponentDetails::HasValidMapObject() const +{ + return SelectedStreetMapComponent != nullptr && SelectedStreetMapComponent->GetStreetMap() != nullptr; +} + +FReply FStreetMapComponentDetails::OnCreateStaticMeshAssetClicked() +{ + if (SelectedStreetMapComponent != nullptr) + { + FString NewNameSuggestion = SelectedStreetMapComponent->GetStreetMapAssetName(); + FString PackageName = FString(TEXT("/Game/Meshes/")) + NewNameSuggestion; + FString Name; + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + AssetToolsModule.Get().CreateUniqueAssetName(PackageName, TEXT(""), PackageName, Name); + + TSharedPtr PickAssetPathWidget = + SNew(SDlgPickAssetPath) + .Title(LOCTEXT("ConvertToStaticMeshPickName", "Choose New StaticMesh Location")) + .DefaultAssetPath(FText::FromString(PackageName)); + + if (PickAssetPathWidget->ShowModal() == EAppReturnType::Ok) + { + // Get the full name of where we want to create the physics asset. + FString UserPackageName = PickAssetPathWidget->GetFullAssetPath().ToString(); + FName MeshName(*FPackageName::GetLongPackageAssetName(UserPackageName)); + + // Check if the user inputed a valid asset name, if they did not, give it the generated default name + if (MeshName == NAME_None) + { + // Use the defaults that were already generated. + UserPackageName = PackageName; + MeshName = *Name; + } + + // Raw mesh data we are filling in + FRawMesh RawMesh; + // Materials to apply to new mesh + TArray MeshMaterials = SelectedStreetMapComponent->GetMaterials(); + + + const TArray RawMeshVertices = SelectedStreetMapComponent->GetRawMeshVertices(); + const TArray< uint32 > RawMeshIndices = SelectedStreetMapComponent->GetRawMeshIndices(); + + + // Copy verts + for (int32 VertIndex = 0; VertIndex < RawMeshVertices.Num();VertIndex++) + { + RawMesh.VertexPositions.Add(RawMeshVertices[VertIndex].Position); + } + + // Copy 'wedge' info + int32 NumIndices = RawMeshIndices.Num(); + for (int32 IndexIdx = 0; IndexIdx < NumIndices; IndexIdx++) + { + int32 VertexIndex = RawMeshIndices[IndexIdx]; + + RawMesh.WedgeIndices.Add(VertexIndex); + + const FStreetMapVertex& StreetMapVertex = RawMeshVertices[VertexIndex]; + + FVector TangentX = StreetMapVertex.TangentX; + FVector TangentZ = StreetMapVertex.TangentZ; + FVector TangentY = (TangentX ^ TangentZ).GetSafeNormal(); + + RawMesh.WedgeTangentX.Add(TangentX); + RawMesh.WedgeTangentY.Add(TangentY); + RawMesh.WedgeTangentZ.Add(TangentZ); + + RawMesh.WedgeTexCoords[0].Add(StreetMapVertex.TextureCoordinate); + RawMesh.WedgeColors.Add(StreetMapVertex.Color); + } + + // copy face info + int32 NumTris = NumIndices / 3; + for (int32 TriIdx = 0; TriIdx < NumTris; TriIdx++) + { + RawMesh.FaceMaterialIndices.Add(0); + RawMesh.FaceSmoothingMasks.Add(0); // Assume this is ignored as bRecomputeNormals is false + } + + // If we got some valid data. + if (RawMesh.VertexPositions.Num() > 3 && RawMesh.WedgeIndices.Num() > 3) + { + // Then find/create it. + UPackage* Package = CreatePackage(NULL, *UserPackageName); + check(Package); + + // Create StaticMesh object + UStaticMesh* StaticMesh = NewObject(Package, MeshName, RF_Public | RF_Standalone); + StaticMesh->InitResources(); + + StaticMesh->LightingGuid = FGuid::NewGuid(); + + // Add source to new StaticMesh + FStaticMeshSourceModel* SrcModel = new (StaticMesh->SourceModels) FStaticMeshSourceModel(); + SrcModel->BuildSettings.bRecomputeNormals = false; + SrcModel->BuildSettings.bRecomputeTangents = false; + SrcModel->BuildSettings.bRemoveDegenerates = false; + SrcModel->BuildSettings.bUseHighPrecisionTangentBasis = false; + SrcModel->BuildSettings.bUseFullPrecisionUVs = false; + SrcModel->BuildSettings.bGenerateLightmapUVs = true; + SrcModel->BuildSettings.SrcLightmapIndex = 0; + SrcModel->BuildSettings.DstLightmapIndex = 1; + SrcModel->RawMeshBulkData->SaveRawMesh(RawMesh); + + // Copy materials to new mesh + for (UMaterialInterface* Material : MeshMaterials) + { + StaticMesh->StaticMaterials.Add(FStaticMaterial(Material)); + } + + //Set the Imported version before calling the build + StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion; + + // Build mesh from source + StaticMesh->Build(/** bSilent =*/ false); + StaticMesh->PostEditChange(); + + StaticMesh->MarkPackageDirty(); + + // Notify asset registry of new asset + FAssetRegistryModule::AssetCreated(StaticMesh); + + + // Display notification so users can quickly access the mesh + if (GIsEditor) + { + FNotificationInfo Info(FText::Format(LOCTEXT("StreetMapMeshConverted", "Successfully Converted Mesh"), FText::FromString(StaticMesh->GetName()))); + Info.ExpireDuration = 8.0f; + Info.bUseLargeFont = false; + Info.Hyperlink = FSimpleDelegate::CreateLambda([=]() { FAssetEditorManager::Get().OpenEditorForAssets(TArray({ StaticMesh })); }); + Info.HyperlinkText = FText::Format(LOCTEXT("OpenNewAnimationHyperlink", "Open {0}"), FText::FromString(StaticMesh->GetName())); + TSharedPtr Notification = FSlateNotificationManager::Get().AddNotification(Info); + if (Notification.IsValid()) + { + Notification->SetCompletionState(SNotificationItem::CS_Success); + } + } + } + } + } + + return FReply::Handled(); +} + +FReply FStreetMapComponentDetails::OnBuildMeshClicked() +{ + + if(SelectedStreetMapComponent != nullptr) + { + // + SelectedStreetMapComponent->BuildMesh(); + + // regenerates details panel layouts , to take in consideration new changes. + RefreshDetails(); + } + + return FReply::Handled(); +} + +FReply FStreetMapComponentDetails::OnClearMeshClicked() +{ + if (SelectedStreetMapComponent != nullptr) + { + // + SelectedStreetMapComponent->InvalidateMesh(); + + // regenerates details panel layouts , to take in consideration new changes. + RefreshDetails(); + } + + return FReply::Handled(); +} + +void FStreetMapComponentDetails::RefreshDetails() +{ + if(LastDetailBuilderPtr != nullptr) + { + LastDetailBuilderPtr->ForceRefreshDetails(); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapComponentDetails.h b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapComponentDetails.h new file mode 100644 index 0000000..4967bbf --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapComponentDetails.h @@ -0,0 +1,45 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#pragma once + +#include "IDetailCustomization.h" + +class FStreetMapComponentDetails : public IDetailCustomization +{ +public: + + FStreetMapComponentDetails(); + + /** Makes a new instance of this detail layout class for a specific detail view requesting it. */ + static TSharedRef MakeInstance(); + + /** IDetailCustomization interface */ + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; + + /** Returns true if we selected a valid cached streetMapComp , having valid cached mesh data. */ + bool HasValidMeshData() const; + + /** Returns true if we selected a valid cached streetMapComp having valid street map representation. */ + bool HasValidMapObject() const; + + /** Handles create static mesh asset button clicking */ + FReply OnCreateStaticMeshAssetClicked(); + + /** Handles build/rebuild mesh button clicking */ + FReply OnBuildMeshClicked(); + + /** Handles clear mesh button clicking */ + FReply OnClearMeshClicked(); + + /** Refreshes the details view and regenerates all the customized layouts. */ + void RefreshDetails(); + + +protected: + /** Holds Selected Street Map Component */ + class UStreetMapComponent* SelectedStreetMapComponent; + + /** Holds Last Detail Builder Pointer */ + IDetailLayoutBuilder* LastDetailBuilderPtr; + +}; diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapFactory.cpp b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapFactory.cpp new file mode 100644 index 0000000..c4fc945 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapFactory.cpp @@ -0,0 +1,446 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#include "StreetMapImporting.h" +#include "StreetMapFactory.h" +#include "OSMFile.h" +#include "StreetMap.h" + + +// Latitude/longitude scale factor +// - https://en.wikipedia.org/wiki/Equator#Exact_length +static const double EarthCircumference = 40075036.0; +const double UStreetMapFactory::LatitudeLongitudeScale = EarthCircumference / 360.0; // meters per degree + + +UStreetMapFactory::UStreetMapFactory(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UStreetMap::StaticClass(); + + Formats.Add( TEXT( "osm;OpenStreetMap XML" ) ); + bCreateNew = false; + bEditorImport = true; + bEditAfterNew = false; + bText = true; +} + + +UObject* UStreetMapFactory::FactoryCreateText( UClass* Class, UObject* Parent, FName Name, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn ) +{ + UStreetMap* StreetMap = NewObject( Parent, Name, Flags | RF_Transactional ); + + StreetMap->AssetImportData->Update( this->GetCurrentFilename() ); + + // @todo: Performance: This will copy the entire text buffer into an FString. We need to do this + // because the FFastXml parser is expecting a buffer that it can mutate as it parses. + const int32 CharacterCount = BufferEnd - Buffer; + FString MutableTextBuffer( CharacterCount, Buffer ); + + const bool bIsFilePathActuallyTextBuffer = true; + const bool bLoadedOkay = LoadFromOpenStreetMapXMLFile( StreetMap, MutableTextBuffer, bIsFilePathActuallyTextBuffer, Warn ); + + if( !bLoadedOkay ) + { + StreetMap->MarkPendingKill(); + StreetMap = nullptr; + } + + return StreetMap; +} + + +bool UStreetMapFactory::LoadFromOpenStreetMapXMLFile( UStreetMap* StreetMap, FString& OSMFilePath, const bool bIsFilePathActuallyTextBuffer, FFeedbackContext* FeedbackContext ) +{ + // OSM data is stored in meters. This is the scale factor to convert those units into UE4's native units (cm) + // Keep in mind that if this is changed, UStreetMapComponent sizes for roads may need to be updated too! + // @todo: We should make this scale factor customizable as an import option + const float OSMToCentimetersScaleFactor = 100.0f; + + + // Converts latitude to meters + auto ConvertLatitudeToMeters = []( const double Latitude ) -> double + { + return -Latitude * LatitudeLongitudeScale; + }; + + // Converts longitude to meters + auto ConvertLongitudeToMeters = []( const double Longitude, const double Latitude ) -> double + { + return Longitude * LatitudeLongitudeScale * FMath::Cos( FMath::DegreesToRadians( Latitude ) ); + }; + + // Converts latitude and longitude to X/Y coordinates, relative to some other latitude/longitude + auto ConvertLatLongToMetersRelative = [ConvertLatitudeToMeters, ConvertLongitudeToMeters]( + const double Latitude, + const double Longitude, + const double RelativeToLatitude, + const double RelativeToLongitude ) -> FVector2D + { + // Applies Sanson-Flamsteed (sinusoidal) Projection (see http://www.progonos.com/furuti/MapProj/Normal/CartHow/HowSanson/howSanson.html) + return FVector2D( + (float)( ConvertLongitudeToMeters( Longitude, Latitude ) - ConvertLongitudeToMeters( RelativeToLongitude, Latitude ) ), + (float)( ConvertLatitudeToMeters( Latitude ) - ConvertLatitudeToMeters( RelativeToLatitude ) ) ); + }; + + // Adds a road to the street map using the OpenStreetMap data, flattening the road's coordinates into our map's space + auto AddRoadForWay = [ConvertLatLongToMetersRelative, OSMToCentimetersScaleFactor]( + const FOSMFile& OSMFile, + UStreetMap& StreetMapRef, + const FOSMFile::FOSMWayInfo& OSMWay, + int32& OutRoadIndex ) -> bool + { + EStreetMapRoadType RoadType = EStreetMapRoadType::Other; + switch( OSMWay.WayType ) + { + case FOSMFile::EOSMWayType::Motorway: + case FOSMFile::EOSMWayType::Motorway_Link: + case FOSMFile::EOSMWayType::Trunk: + case FOSMFile::EOSMWayType::Trunk_Link: + case FOSMFile::EOSMWayType::Primary: + case FOSMFile::EOSMWayType::Primary_Link: + RoadType = EStreetMapRoadType::Highway; + break; + + case FOSMFile::EOSMWayType::Secondary: + case FOSMFile::EOSMWayType::Secondary_Link: + case FOSMFile::EOSMWayType::Tertiary: + case FOSMFile::EOSMWayType::Tertiary_Link: + RoadType = EStreetMapRoadType::MajorRoad; + break; + + case FOSMFile::EOSMWayType::Residential: + case FOSMFile::EOSMWayType::Service: + case FOSMFile::EOSMWayType::Unclassified: + 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 + RoadType = EStreetMapRoadType::Street; + break; + } + + if( RoadType != EStreetMapRoadType::Other ) + { + // Require at least two points! + if( OSMWay.Nodes.Num() > 1 ) + { + // Create a road for this way + OutRoadIndex = StreetMapRef.Roads.Num(); + FStreetMapRoad& NewRoad = *new( StreetMapRef.Roads )FStreetMapRoad(); + + FVector2D BoundsMin( TNumericLimits::Max(), TNumericLimits::Max() ); + FVector2D BoundsMax( TNumericLimits::Lowest(), TNumericLimits::Lowest() ); + + NewRoad.RoadPoints.AddUninitialized( OSMWay.Nodes.Num() ); + int32 CurRoadPoint = 0; + + // Set defaults for each node index on this road. INDEX_NONE means the node is not valid, which may be the case + // for nodes that we filter out entirely. This will be filled in by valid indices to nodes later on. + NewRoad.NodeIndices.AddUninitialized( OSMWay.Nodes.Num() ); + for( int32& NodeIndex : NewRoad.NodeIndices ) + { + NodeIndex = INDEX_NONE; + } + + + for( const FOSMFile::FOSMNodeInfo* OSMNodePtr : OSMWay.Nodes ) + { + const FOSMFile::FOSMNodeInfo& OSMNode = *OSMNodePtr; + + // Transform all points relative to the center of the latitude/longitude bounds, so that + // we get as much precision as possible. + const double RelativeToLatitude = OSMFile.AverageLatitude; + const double RelativeToLongitude = OSMFile.AverageLongitude; + const FVector2D NodePos = ConvertLatLongToMetersRelative( + OSMNode.Latitude, + OSMNode.Longitude, + RelativeToLatitude, + RelativeToLongitude ) * OSMToCentimetersScaleFactor; + + // Update bounding box + { + if( NodePos.X < BoundsMin.X ) + { + BoundsMin.X = NodePos.X; + } + if( NodePos.Y < BoundsMin.Y ) + { + BoundsMin.Y = NodePos.Y; + } + if( NodePos.X > BoundsMax.X ) + { + BoundsMax.X = NodePos.X; + } + if( NodePos.Y > BoundsMax.Y ) + { + BoundsMax.Y = NodePos.Y; + } + } + + // Fill in the points + NewRoad.RoadPoints[ CurRoadPoint++ ] = NodePos; + } + + + NewRoad.RoadName = OSMWay.Name; + if( NewRoad.RoadName.IsEmpty() ) + { + NewRoad.RoadName = OSMWay.Ref; + } + NewRoad.RoadType = RoadType; + NewRoad.BoundsMin = BoundsMin; + NewRoad.BoundsMax = BoundsMax; + + NewRoad.bIsOneWay = OSMWay.bIsOneWay; + + StreetMapRef.BoundsMin.X = FMath::Min( StreetMapRef.BoundsMin.X, BoundsMin.X ); + StreetMapRef.BoundsMin.Y = FMath::Min( StreetMapRef.BoundsMin.Y, BoundsMin.Y ); + StreetMapRef.BoundsMax.X = FMath::Max( StreetMapRef.BoundsMax.X, BoundsMax.X ); + StreetMapRef.BoundsMax.Y = FMath::Max( StreetMapRef.BoundsMax.Y, BoundsMax.Y ); + + return true; + } + else + { + // NOTE: Skipped adding road for way because it has less than 2 points + // @todo: Log this for the user as an import warning + } + } + + return false; + }; + + + // Adds a building to the street map using the OpenStreetMap data, flattening the road's coordinates into our map's space + auto AddBuildingForWay = [ConvertLatLongToMetersRelative, OSMToCentimetersScaleFactor]( + const FOSMFile& OSMFile, + UStreetMap& StreetMapRef, + const FOSMFile::FOSMWayInfo& OSMWay ) -> bool + { + if( OSMWay.WayType == FOSMFile::EOSMWayType::Building ) + { + // Require at least three points so that we don't have degenerate polygon! + if( OSMWay.Nodes.Num() > 2 ) + { + // Create a building for this way + FStreetMapBuilding& NewBuilding = *new( StreetMapRef.Buildings )FStreetMapBuilding(); + + FVector2D BoundsMin( TNumericLimits::Max(), TNumericLimits::Max() ); + FVector2D BoundsMax( TNumericLimits::Lowest(), TNumericLimits::Lowest() ); + + NewBuilding.BuildingPoints.AddUninitialized( OSMWay.Nodes.Num() ); + int32 CurBuildingPoint = 0; + + for( const FOSMFile::FOSMNodeInfo* OSMNodePtr : OSMWay.Nodes ) + { + const FOSMFile::FOSMNodeInfo& OSMNode = *OSMNodePtr; + + // Transform all points relative to the center of the latitude/longitude bounds, so that + // we get as much precision as possible. + const double RelativeToLatitude = OSMFile.AverageLatitude; + const double RelativeToLongitude = OSMFile.AverageLongitude; + const FVector2D NodePos = ConvertLatLongToMetersRelative( + OSMNode.Latitude, + OSMNode.Longitude, + RelativeToLatitude, + RelativeToLongitude ) * OSMToCentimetersScaleFactor; + + // Update bounding box + { + if( NodePos.X < BoundsMin.X ) + { + BoundsMin.X = NodePos.X; + } + if( NodePos.Y < BoundsMin.Y ) + { + BoundsMin.Y = NodePos.Y; + } + if( NodePos.X > BoundsMax.X ) + { + BoundsMax.X = NodePos.X; + } + if( NodePos.Y > BoundsMax.Y ) + { + BoundsMax.Y = NodePos.Y; + } + } + + // Fill in the points + NewBuilding.BuildingPoints[ CurBuildingPoint++ ] = NodePos; + } + + // Make sure the building ended up with a closed polygon, then remove the final (redundant) point + const bool bIsClosed = NewBuilding.BuildingPoints[ 0 ].Equals( NewBuilding.BuildingPoints[ NewBuilding.BuildingPoints.Num() - 1 ], KINDA_SMALL_NUMBER ); + if( bIsClosed ) + { + // Remove the final redundant point + NewBuilding.BuildingPoints.Pop(); + } + else + { + // Wasn't expecting to have an unclosed shape. Our tolerances might be off, or the data was malformed. + // Either way, it shouldn't be a problem as we'll close the shape ourselves below. + // @todo: Log this for the user as an import warning + } + + NewBuilding.BuildingName = OSMWay.Name; + if( NewBuilding.BuildingName.IsEmpty() ) + { + NewBuilding.BuildingName = OSMWay.Ref; + } + + NewBuilding.Height = OSMWay.Height * OSMToCentimetersScaleFactor; + NewBuilding.BuildingLevels = OSMWay.BuildingLevels; + + // Vinfamy added + if ( NewBuilding.BuildingLevels == 0 ) + { + NewBuilding.BuildingLevels = rand() % 5 + 1; + } + NewBuilding.Amenity = OSMWay.Amenity; + NewBuilding.Building = OSMWay.Building; + + NewBuilding.BoundsMin = BoundsMin; + NewBuilding.BoundsMax = BoundsMax; + + StreetMapRef.BoundsMin.X = FMath::Min( StreetMapRef.BoundsMin.X, BoundsMin.X ); + StreetMapRef.BoundsMin.Y = FMath::Min( StreetMapRef.BoundsMin.Y, BoundsMin.Y ); + StreetMapRef.BoundsMax.X = FMath::Max( StreetMapRef.BoundsMax.X, BoundsMax.X ); + StreetMapRef.BoundsMax.Y = FMath::Max( StreetMapRef.BoundsMax.Y, BoundsMax.Y ); + + return true; + } + else + { + // NOTE: Skipped adding building for way because it has less than 3 points + // @todo: Log this for the user as an import warning + } + } + + return false; + }; + + + // Load up the OSM file. It's in XML format. + FOSMFile OSMFile; + if( !OSMFile.LoadOpenStreetMapFile( OSMFilePath, bIsFilePathActuallyTextBuffer, FeedbackContext ) ) + { + // Loading failed. The actual error message will be sent to the FeedbackContext's log. + return false; + } + + // @todo: The loaded OSMFile stores data in double precision, but our runtime representation (UStreetMap) + // truncates everything to single precision, after transposing coordinates to be relative to the + // center of the map's 2D bounds. Large maps will suffer from floating point precision issues. + // To solve this we'd need to either store everything in double precision, or store map elements + // in integral grid cells with coordinates relative to their cell. Of course, there will be many + // other considerations for handling huge maps (loading, rendering, collision, etc.) + + // Maps OSMWayInfos to the RoadIndex we created for that way + TMap< const FOSMFile::FOSMWayInfo*, int32 > OSMWayToRoadIndexMap; + + StreetMap->BoundsMin = FVector2D( TNumericLimits::Max(), TNumericLimits::Max() ); + StreetMap->BoundsMax = FVector2D( TNumericLimits::Lowest(), TNumericLimits::Lowest() ); + + for( const FOSMFile::FOSMWayInfo* OSMWay : OSMFile.Ways ) + { + // Handle buildings differently than roads + if( OSMWay->WayType == FOSMFile::EOSMWayType::Building ) + { + if( AddBuildingForWay( OSMFile, *StreetMap, *OSMWay ) ) + { + // ... + } + } + else + { + int32 RoadIndex = INDEX_NONE; + if( AddRoadForWay( OSMFile, *StreetMap, *OSMWay, RoadIndex ) ) + { + OSMWayToRoadIndexMap.Add( OSMWay, RoadIndex ); + } + } + } + + for( const auto& NodeMapHashPair : OSMFile.NodeMap ) + { + const FOSMFile::FOSMNodeInfo& OSMNode = *NodeMapHashPair.Value; + + // Any ways touching this node? + if( OSMNode.WayRefs.Num() > 0 ) + { + FStreetMapNode NewNode; + + for( const FOSMFile::FOSMWayRef& OSMWayRef : OSMNode.WayRefs ) + { + const int32* FoundRoundIndexPtr = OSMWayToRoadIndexMap.Find( OSMWayRef.Way ); + if( FoundRoundIndexPtr != nullptr ) + { + const int32 FoundRoadIndex = *FoundRoundIndexPtr; + + FStreetMapRoadRef RoadRef; + RoadRef.RoadIndex = FoundRoadIndex; + + const int32 RoadPointIndex = OSMWayRef.NodeIndex; + RoadRef.RoadPointIndex = RoadPointIndex; + NewNode.RoadRefs.Add( RoadRef ); + } + else + { + // Skipped ref because we didn't keep this road in our data set + } + } + + // Only store nodes that are attached to at least one road. We must have at least a connection to a single + // road, otherwise we've filtered this node's road out and there's no point in wasting memory on the node itself. + if( NewNode.RoadRefs.Num() > 0 ) + { + // Most nodes from OpenStreetMap will only be touching a single road. These nodes usually make up the points + // along the length of the road, even for roads with no intersections except at the beginning and end. We + // don't need to store these points unless they are at the ends of the road. Keeping the points at the + // beginning and end of the road is useful when calculating navigation data, but the other nodes can go! + // In the road's NodeIndices array, any nodes we filter out here will simply have an INDEX_NONE value in that + // array, and we'll only store the positions of the road at these points in the road's RoadPoints array. + + const FStreetMapRoadRef& FirstRoadRef = NewNode.RoadRefs[ 0 ]; + const FStreetMapRoad& FirstRoad = StreetMap->Roads[ FirstRoadRef.RoadIndex ]; + + if( NewNode.RoadRefs.Num() > 1 || // Does the node connect to more than one road? + FirstRoadRef.RoadPointIndex == 0 || // Does the node connect to the beginning of the road? + FirstRoadRef.RoadPointIndex == ( FirstRoad.NodeIndices.Num() - 1 ) ) // Does the node connect to the end of the road? + { + const int32 NewNodeIndex = StreetMap->Nodes.Num(); + StreetMap->Nodes.Add( NewNode ); + + // Update the roads that are overlapping this node + for( const FStreetMapRoadRef& RoadRef : NewNode.RoadRefs ) + { + FStreetMapRoad& Road = StreetMap->Roads[ RoadRef.RoadIndex ]; + check( Road.NodeIndices[ RoadRef.RoadPointIndex ] == INDEX_NONE ); + Road.NodeIndices[ RoadRef.RoadPointIndex ] = NewNodeIndex; + } + } + else + { + // Node has only one road that is references, and it wasn't the beginning or end of the road, so filter it out! + } + } + else + { + // Node doesn't reference any roads that we kept, or the data was malformed. Filter it out. + } + } + } + + // Validation test: Make sure that all roads have at least two nodes referencing them, one at the beginning and + // one at the end. + for( const FStreetMapRoad& Road : StreetMap->Roads ) + { + const bool bHasNodeAtBeginning = Road.NodeIndices[ 0 ] != INDEX_NONE; + const bool bHasNodeAtEnd = Road.NodeIndices[ Road.NodeIndices.Num() - 1 ] != INDEX_NONE; + + // All roads should have at least two nodes referencing them, one at the beginning and one at the end + ensure( bHasNodeAtBeginning && bHasNodeAtEnd ); + } + + return true; +} + + diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapFactory.h b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapFactory.h new file mode 100644 index 0000000..a9df2ff --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapFactory.h @@ -0,0 +1,32 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. +#pragma once + +#include "Factories/Factory.h" +#include "StreetMapFactory.generated.h" + + +/** + * Import factory object for OpenStreetMap assets + */ +UCLASS() +class UStreetMapFactory : public UFactory +{ + GENERATED_BODY() + +public: + + /** UStreetMapFactory constructor */ + UStreetMapFactory( const class FObjectInitializer& ObjectInitializer ); + +protected: + + // UFactory overrides + 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; + + /** 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. */ + bool LoadFromOpenStreetMapXMLFile( class UStreetMap* StreetMap, FString& OSMFilePath, const bool bIsFilePathActuallyTextBuffer, class FFeedbackContext* FeedbackContext ); + + /** Static: Latitude/longitude scale factor */ + static const double LatitudeLongitudeScale; +}; + diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapImporting.Build.cs b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapImporting.Build.cs new file mode 100644 index 0000000..6637a4d --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapImporting.Build.cs @@ -0,0 +1,34 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class StreetMapImporting : ModuleRules + { + public StreetMapImporting(ReadOnlyTargetRules Target) + : base(Target) + { + PrivateDependencyModuleNames.AddRange( + new string[] { + "Core", + "CoreUObject", + "Engine", + "UnrealEd", + "XmlParser", + "AssetTools", + "Projects", + "Slate", + "EditorStyle", + "SlateCore", + "PropertyEditor", + "RenderCore", + "ShaderCore", + "RHI", + "RawMesh", + "AssetTools", + "AssetRegistry", + "StreetMapRuntime" + } + ); + } + } +} \ No newline at end of file diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapImporting.cpp b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapImporting.cpp new file mode 100644 index 0000000..4115829 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapImporting.cpp @@ -0,0 +1,63 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#include "StreetMapImporting.h" +#include "StreetMapAssetTypeActions.h" +#include "ModuleManager.h" +#include "StreetMapStyle.h" +#include "StreetMapComponentDetails.h" + + +class FStreetMapImportingModule : public IModuleInterface +{ + +public: + + // IModuleInterface interface + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + TSharedPtr< FStreetMapAssetTypeActions > StreetMapAssetTypeActions; +}; + + +IMPLEMENT_MODULE( FStreetMapImportingModule, StreetMapImporting ) + + + +void FStreetMapImportingModule::StartupModule() +{ + // Register asset types + IAssetTools& AssetTools = FModuleManager::LoadModuleChecked( "AssetTools" ).Get(); + StreetMapAssetTypeActions = MakeShareable( new FStreetMapAssetTypeActions() ); + AssetTools.RegisterAssetTypeActions( StreetMapAssetTypeActions.ToSharedRef() ); + + // Initialize & Register StreetMap Style + FStreetMapStyle::Initialize(); + + // Register StreetMapComponent Detail Customization + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + PropertyModule.RegisterCustomClassLayout("StreetMapComponent", FOnGetDetailCustomizationInstance::CreateStatic(&FStreetMapComponentDetails::MakeInstance)); + PropertyModule.NotifyCustomizationModuleChanged(); +} + + +void FStreetMapImportingModule::ShutdownModule() +{ + // Unregister all the asset types that we registered + if( FModuleManager::Get().IsModuleLoaded( "AssetTools" ) ) + { + IAssetTools& AssetTools = FModuleManager::GetModuleChecked( "AssetTools" ).Get(); + AssetTools.UnregisterAssetTypeActions( StreetMapAssetTypeActions.ToSharedRef() ); + StreetMapAssetTypeActions.Reset(); + } + + // Unregister StreetMap Style + FStreetMapStyle::Shutdown(); + + if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) + { + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + PropertyModule.UnregisterCustomClassLayout("StreetMapComponent"); + PropertyModule.NotifyCustomizationModuleChanged(); + } +} diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapImporting.h b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapImporting.h new file mode 100644 index 0000000..07df060 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapImporting.h @@ -0,0 +1,5 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. +#pragma once + +#include "UnrealEd.h" + diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapReimportFactory.cpp b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapReimportFactory.cpp new file mode 100644 index 0000000..94b1bed --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapReimportFactory.cpp @@ -0,0 +1,60 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#include "StreetMapImporting.h" +#include "StreetMapReimportFactory.h" +#include "StreetMap.h" + + +UStreetMapReimportFactory::UStreetMapReimportFactory(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + + +bool UStreetMapReimportFactory::CanReimport( UObject* Obj, TArray& OutFilenames ) +{ + UStreetMap* StreetMap = Cast( Obj ); + if( StreetMap != nullptr ) + { + OutFilenames.Add( StreetMap->AssetImportData->GetFirstFilename() ); + } + return true; +} + + +void UStreetMapReimportFactory::SetReimportPaths( UObject* Obj, const TArray& NewReimportPaths ) +{ + UStreetMap* StreetMap = CastChecked( Obj ); + StreetMap->Modify(); + StreetMap->AssetImportData->Update( NewReimportPaths[0] ); +} + + +EReimportResult::Type UStreetMapReimportFactory::Reimport( UObject* Obj ) +{ + UStreetMap* StreetMap = CastChecked( Obj ); + + const FString Filename = StreetMap->AssetImportData->GetFirstFilename(); + const FString FileExtension = FPaths::GetExtension(Filename); + + // If there is no file path provided, can't reimport from source + if ( !Filename.Len() ) + { + return EReimportResult::Failed; + } + + if( UFactory::StaticImportObject( StreetMap->GetClass(), StreetMap->GetOuter(), *StreetMap->GetName(), RF_Public|RF_Standalone, *Filename, nullptr, this ) ) + { + // Mark the package dirty after the successful import + StreetMap->MarkPackageDirty(); + return EReimportResult::Succeeded; + } + + return EReimportResult::Failed; +} + + +int32 UStreetMapReimportFactory::GetPriority() const +{ + return 0; +} diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapReimportFactory.h b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapReimportFactory.h new file mode 100644 index 0000000..1218746 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapReimportFactory.h @@ -0,0 +1,31 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. +#pragma once + +#include "StreetMapFactory.h" +#include "EditorReimportHandler.h" +#include "StreetMapReimportFactory.generated.h" + + +/** + * Import factory object for OpenStreetMap assets + */ +UCLASS() +class UStreetMapReimportFactory : public UStreetMapFactory, public FReimportHandler +{ + GENERATED_BODY() + +public: + + /** UStreetMapReimportFactory constructor */ + UStreetMapReimportFactory( const class FObjectInitializer& ObjectInitializer ); + +protected: + + // FReimportHandler overrides + virtual bool CanReimport( UObject* Obj, TArray& OutFilenames ) override; + virtual void SetReimportPaths( UObject* Obj, const TArray& NewReimportPaths ) override; + virtual EReimportResult::Type Reimport( UObject* Obj ) override; + virtual int32 GetPriority() const override; + +}; + diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapStyle.cpp b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapStyle.cpp new file mode 100644 index 0000000..ed9df1b --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapStyle.cpp @@ -0,0 +1,58 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#include "StreetMapImporting.h" +#include "StreetMapStyle.h" +#include "SlateStyle.h" +#include "IPluginManager.h" + +#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( FStreetMapStyle::InContent( RelativePath, ".png" ), __VA_ARGS__ ) + +FString FStreetMapStyle::InContent(const FString& RelativePath, const ANSICHAR* Extension) +{ + static FString IconsDir = IPluginManager::Get().FindPlugin(TEXT("StreetMap"))->GetContentDir() / TEXT("Icons"); + return (IconsDir / RelativePath) + Extension; +} + +TSharedPtr< FSlateStyleSet > FStreetMapStyle::StyleSet = NULL; +TSharedPtr< class ISlateStyle > FStreetMapStyle::Get() { return StyleSet; } + +void FStreetMapStyle::Initialize() +{ + // Const icon & thumbnail sizes + const FVector2D Icon16x16(16.0f, 16.0f); + const FVector2D Icon128x128(128.0f, 128.0f); + + // Only register once + if (StyleSet.IsValid()) + { + return; + } + + StyleSet = MakeShareable(new FSlateStyleSet("StreetMapStyle")); + FString ContentDir = IPluginManager::Get().FindPlugin(TEXT("StreetMap"))->GetContentDir(); + StyleSet->SetContentRoot(ContentDir); + + StyleSet->Set("ClassIcon.StreetMap", new IMAGE_BRUSH("sm_asset_icon_32", Icon16x16)); + StyleSet->Set("ClassThumbnail.StreetMap", new IMAGE_BRUSH("sm_asset_icon_128", Icon128x128)); + + StyleSet->Set("ClassIcon.StreetMapActor", new IMAGE_BRUSH("sm_actor_icon_32", Icon16x16)); + StyleSet->Set("ClassThumbnail.StreetMapActor", new IMAGE_BRUSH("sm_actor_icon_128", Icon128x128)); + + StyleSet->Set("ClassIcon.StreetMapComponent", new IMAGE_BRUSH("sm_component_icon_32", Icon16x16)); + StyleSet->Set("ClassThumbnail.StreetMapComponent", new IMAGE_BRUSH("sm_component_icon_128", Icon128x128)); + + FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); +}; + +#undef IMAGE_BRUSH + + +void FStreetMapStyle::Shutdown() +{ + if (StyleSet.IsValid()) + { + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); + ensure(StyleSet.IsUnique()); + StyleSet.Reset(); + } +} \ No newline at end of file diff --git a/Plugins/StreetMap/Source/StreetMapImporting/StreetMapStyle.h b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapStyle.h new file mode 100644 index 0000000..831666d --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapImporting/StreetMapStyle.h @@ -0,0 +1,22 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#pragma once + +/** StreetMap Editor Style Helper Class. */ +class FStreetMapStyle +{ +public: + + static void Initialize(); + static void Shutdown(); + + static TSharedPtr< class ISlateStyle > Get(); + +private: + + static FString InContent(const FString& RelativePath, const ANSICHAR* Extension); + +private: + + static TSharedPtr< class FSlateStyleSet > StyleSet; +}; \ No newline at end of file diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/PolygonTools.cpp b/Plugins/StreetMap/Source/StreetMapRuntime/PolygonTools.cpp new file mode 100644 index 0000000..5f115cb --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapRuntime/PolygonTools.cpp @@ -0,0 +1,78 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#include "StreetMapRuntime.h" +#include "PolygonTools.h" + + +// Based off "Efficient Polygon Triangulation" algorithm by John W. Ratcliff (http://flipcode.net/archives/Efficient_Polygon_Triangulation.shtml) +bool FPolygonTools::TriangulatePolygon( const TArray& Polygon, TArray& TempIndices, TArray& TriangulatedIndices, bool& OutWindsClockwise ) +{ + checkSlow( &TempIndices != &TriangulatedIndices ); + TriangulatedIndices.Reset(); + OutWindsClockwise = false; + + int32 NumVertices = Polygon.Num(); + if( NumVertices < 3 ) + { + return false; + } + + // Allocate and initialize a list of vertex indices for the new polygon + TempIndices.SetNumUninitialized( NumVertices ); + int32* VertexIndices = TempIndices.GetData(); + + // We want a counter-clockwise polygon + OutWindsClockwise = Area( Polygon ) < 0.0f; + if( !OutWindsClockwise ) + { + for( int32 PointIndex = 0; PointIndex < NumVertices; PointIndex++ ) + { + VertexIndices[ PointIndex ] = PointIndex; + } + } + else + { + for( int32 PointIndex = 0; PointIndex < NumVertices; PointIndex++ ) + { + VertexIndices[ PointIndex ] = ( NumVertices - 1 ) - PointIndex; + } + } + + // Remove NumVertices-2 vertices, creating one triangle every time + int32 ErrorDetectionCounter = 2 * NumVertices; + for( int32 V = NumVertices - 1; NumVertices > 2; ) + { + // If we loop, it is probably a non-simple polygon + if( --ErrorDetectionCounter <= 0 ) + { + // Bad polygon! This can happen if there are adjacent points that are exactly overlapping + return false; + } + + // Three consecutive vertices in current polygon, + const int32 U = ( V < NumVertices ) ? V : 0; + V = ( ( U + 1 ) < NumVertices ) ? U + 1 : 0; + const int32 W = ( ( V + 1 ) < NumVertices ) ? V + 1 : 0; + + if( Snip( Polygon, U, V, W, NumVertices, VertexIndices ) ) + { + // Output triangle as indices into the original polygon array + TriangulatedIndices.Add( VertexIndices[ U ] ); + TriangulatedIndices.Add( VertexIndices[ V ] ); + TriangulatedIndices.Add( VertexIndices[ W ] ); + + /* Remove V from remaining polygon */ + for( int32 S = V, T = V + 1; T < NumVertices; S++, T++ ) + { + VertexIndices[ S ] = VertexIndices[ T ]; + } + NumVertices--; + + // Reset error detection counter + ErrorDetectionCounter = 2 * NumVertices; + } + } + + return true; +} + diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/PolygonTools.h b/Plugins/StreetMap/Source/StreetMapRuntime/PolygonTools.h new file mode 100644 index 0000000..ce18b8d --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapRuntime/PolygonTools.h @@ -0,0 +1,113 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. +#pragma once + +class FPolygonTools +{ + +public: + + /** 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. */ + static bool TriangulatePolygon( const TArray& Polygon, TArray& TempIndices, TArray& TriangulatedIndices, bool& OutWindsClockwise ); + + /** Compute area of a polygon */ + static inline float Area( const TArray& Polygon ); + + /** Determines if the specified point is inside the triangle defined by the three triangle corners */ + static inline bool IsPointInsideTriangle( const FVector2D TriangleA, const FVector2D TriangleB, const FVector2D TriangleC, const FVector2D Point ); + + /** 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. */ + static inline bool IsPointInsidePolygon( const TArray& Polygon, const FVector2D Point ); + + +private: + + /** Clips a polygon */ + static inline bool Snip( const TArray& Polygon, const int32 U, const int32 V, const int32 W, const int32 PointCount, const int32* VertexIndices ); +}; + + +float FPolygonTools::Area( const TArray& Polygon ) +{ + const int32 PointCount = Polygon.Num(); + + float HalfArea = 0.0f; + for( int32 P = PointCount - 1, Q = 0; Q < PointCount; P = Q++ ) + { + HalfArea += Polygon[ P ].X * Polygon[ Q ].Y - Polygon[ Q ].X * Polygon[ P ].Y; + } + return HalfArea * 0.5f; +} + + +bool FPolygonTools::IsPointInsideTriangle( const FVector2D TriangleA, const FVector2D TriangleB, const FVector2D TriangleC, const FVector2D Point ) +{ + const FVector2D BToC = TriangleC - TriangleB; + const FVector2D CToA = TriangleA - TriangleC; + const FVector2D AToB = TriangleB - TriangleA; + + const FVector2D AToP = Point - TriangleA; + const FVector2D BToP = Point - TriangleB; + const FVector2D CToP = Point - TriangleC; + + const float ACrossBP = BToC ^ BToP; + const float CCrossAP = AToB ^ AToP; + const float BCrossCP = CToA ^ CToP; + + return ( ( ACrossBP >= -SMALL_NUMBER ) && ( BCrossCP >= -SMALL_NUMBER ) && ( CCrossAP >= -SMALL_NUMBER ) ); +}; + + +bool FPolygonTools::IsPointInsidePolygon( const TArray& Polygon, const FVector2D Point ) +{ + const int NumCorners = Polygon.Num(); + int PreviousCornerIndex = NumCorners - 1; + bool bIsInside = false; + + for( int CornerIndex = 0; CornerIndex < NumCorners; CornerIndex++ ) + { + if( ( ( Polygon[ CornerIndex ].Y < Point.Y && Polygon[ PreviousCornerIndex ].Y >= Point.Y ) || ( Polygon[ PreviousCornerIndex ].Y < Point.Y && Polygon[ CornerIndex ].Y >= Point.Y ) ) && + ( Polygon[ CornerIndex ].X <= Point.X || Polygon[ PreviousCornerIndex ].X <= Point.X ) ) + { + bIsInside ^= ( Polygon[ CornerIndex ].X + ( Point.Y - Polygon[ CornerIndex ].Y ) / ( Polygon[ PreviousCornerIndex ].Y - Polygon[ CornerIndex ].Y )*( Polygon[ PreviousCornerIndex ].X - Polygon[ CornerIndex ].X ) < Point.X ); + } + + PreviousCornerIndex = CornerIndex; + } + + return bIsInside; +} + + +bool FPolygonTools::Snip( const TArray& Polygon, const int32 U, const int32 V, const int32 W, const int32 PointCount, const int32* VertexIndices ) +{ + const FVector2D A = Polygon[ VertexIndices[ U ] ]; + const FVector2D B = Polygon[ VertexIndices[ V ] ]; + const FVector2D C = Polygon[ VertexIndices[ W ] ]; + + if( SMALL_NUMBER > ( ( ( B.X - A.X ) * ( C.Y - A.Y ) ) - ( ( B.Y - A.Y ) * ( C.X - A.X ) ) ) && + + // Don't fail because of degenerate points + ( A - B ).SizeSquared() > SMALL_NUMBER && + ( A - C ).SizeSquared() > SMALL_NUMBER && + ( C - A ).SizeSquared() > SMALL_NUMBER ) + { + return false; + } + + for( int32 PointIndex = 0; PointIndex < PointCount; ++PointIndex ) + { + if( ( PointIndex == U ) || ( PointIndex == V ) || ( PointIndex == W ) ) + { + continue; + } + + if( IsPointInsideTriangle( A, B, C, Polygon[ VertexIndices[ PointIndex ] ] ) ) + { + return false; + } + } + + return true; +} + + diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/Public/StreetMap.h b/Plugins/StreetMap/Source/StreetMapRuntime/Public/StreetMap.h new file mode 100644 index 0000000..bc2ec3d --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapRuntime/Public/StreetMap.h @@ -0,0 +1,893 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#pragma once + +#include "PolygonTools.h" +#include "StreetMap.generated.h" + + +USTRUCT(BlueprintType) +struct STREETMAPRUNTIME_API FStreetMapCollisionSettings +{ + GENERATED_USTRUCT_BODY() + +public: + + + /** Uses triangle mesh data for collision data. (Cannot be used for physics simulation). */ + UPROPERTY(EditAnywhere, Category = "StreetMap") + uint32 bGenerateCollision : 1; + + /** + * If true, the physics triangle mesh will use double sided faces when doing scene queries. + * This is useful for planes and single sided meshes that need traces to work on both sides. + */ + UPROPERTY(EditAnywhere, Category = "StreetMap", meta = (editcondition = "bGenerateCollision")) + uint32 bAllowDoubleSidedGeometry : 1; + + + FStreetMapCollisionSettings() : + bGenerateCollision(false), + bAllowDoubleSidedGeometry(false) + { + + } + +}; + +/** Mesh generation settings */ +USTRUCT(BlueprintType) +struct STREETMAPRUNTIME_API FStreetMapMeshBuildSettings +{ + GENERATED_USTRUCT_BODY() + +public: + + /** Roads base vertical offset */ + UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0"), DisplayName = "Road Vertical Offset") + float RoadOffesetZ; + + /** if true buildings mesh will be 3D instead of flat representation. */ + UPROPERTY(Category = StreetMap, EditAnywhere, DisplayName = "Create 3D Buildings") + uint32 bWant3DBuildings : 1; + + /** building level floor conversion factor in centimeters + @todo: harmonize with OSMToCentimetersScaleFactor refactoring + */ + UPROPERTY(Category = StreetMap, EditAnywhere, DisplayName = "Building Level Floor Factor") + float BuildingLevelFloorFactor = 300.0f; + + /** + * If true, buildings mesh will receive light information. + * Lit buildings can't share vertices beyond quads (all quads have their own face normals), so this uses a lot more geometry. + */ + UPROPERTY(Category = StreetMap, EditAnywhere, DisplayName = "Lit buildings") + uint32 bWantLitBuildings : 1; + + /** Streets thickness */ + UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) + float StreetThickness; + + /** Street vertex color */ + UPROPERTY(Category = StreetMap, EditAnywhere) + FLinearColor StreetColor; + + /** Major road thickness */ + UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) + float MajorRoadThickness; + + /** Major road vertex color */ + UPROPERTY(Category = StreetMap, EditAnywhere) + FLinearColor MajorRoadColor; + + /** Highway thickness */ + UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) + float HighwayThickness; + + /** Highway vertex color */ + UPROPERTY(Category = StreetMap, EditAnywhere) + FLinearColor HighwayColor; + + /** Streets Thickness */ + UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) + float BuildingBorderThickness; + + /** Building border vertex color */ + UPROPERTY(Category = StreetMap, EditAnywhere) + FLinearColor BuildingBorderLinearColor; + + /** Buildings border vertical offset */ + UPROPERTY(Category = StreetMap, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) + float BuildingBorderZ; + + FStreetMapMeshBuildSettings() : + RoadOffesetZ(0.0f), + bWant3DBuildings(true), + bWantLitBuildings(true), + StreetThickness(800.0f), + StreetColor(0.05f, 0.75f, 0.05f), + MajorRoadThickness(1000.0f), + MajorRoadColor(0.15f, 0.85f, 0.15f), + HighwayThickness(1400.0f), + HighwayColor(FLinearColor(0.25f, 0.95f, 0.25f)), + BuildingBorderThickness(20.0f), + BuildingBorderLinearColor(0.85f, 0.85f, 0.85f), + BuildingBorderZ(10.0f) + { + } + +}; + + + +/** Types of roads */ +UENUM( BlueprintType ) +enum EStreetMapRoadType +{ + /** Small road or residential street */ + Street, + + /** Major road or minor state highway */ + MajorRoad, + + /** Highway */ + Highway, + + /** Other (path, bus route, etc) */ + Other, +}; + + +/** A road */ +USTRUCT( BlueprintType ) +struct STREETMAPRUNTIME_API FStreetMapRoad +{ + GENERATED_USTRUCT_BODY() + + /** Name of the road */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + FString RoadName; + + /** Type of road */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + TEnumAsByte RoadType; + + /** Nodes along this road, one at each point in the RoadPoints list */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + TArray NodeIndices; + + /** List of all of the points on this road, one for each node in the NodeIndices list */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + TArray RoadPoints; + + // @todo: Performance: Bounding information could be computed at load time if we want to avoid the memory cost of storing it + + /** 2D bounds (min) of this road's points */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + FVector2D BoundsMin; + + /** 2D bounds (max) of this road's points */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + FVector2D BoundsMax; + + /** True if this node is a one way. One way nodes are only traversable in the order the nodes are listed in the above array. */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + uint8 bIsOneWay : 1; + + + /** Returns this node's index */ + inline int32 GetRoadIndex( const class UStreetMap& StreetMap ) const; + + /** Gets the node for the specified point, or the node that came before that if the specified point doesn't have a node */ + inline const struct FStreetMapNode& GetNodeAtPointIndexOrEarlier( const class UStreetMap& StreetMap, const int32 PointIndex, int32& OutNodeAtPointIndex ) const; + + /** Gets the node for the specified point, or the node that comes next after that if the specified point doesn't have a node */ + inline const struct FStreetMapNode& GetNodeAtPointIndexOrLater( const class UStreetMap& StreetMap, const int32 PointIndex, int32& OutNodeAtPointIndex ) const; + + /** Computes the total length of this road by following along all of it's points */ + float ComputeLengthOfRoad( const class UStreetMap& StreetMap ) const; + + /** Computes the distance along the road between two points on the road. Be careful! The same node can appear on a road twice. */ + float ComputeDistanceBetweenNodesOnRoad( const class UStreetMap& StreetMap, const int32 NodePointIndexA, const int32 NodePointIndexB ) const; + + /** Given a position along the road, finds the nodes that come earlier and later on that road */ + void FindEarlierAndLaterNodesForPositionAlongRoad( const class UStreetMap& StreetMap, const float PositionAlongRoad, const FStreetMapNode*& OutEarlierNode, float& OutEarlierNodePositionAlongRoad, const FStreetMapNode*& OutLaterNode, float& OutLaterNodePositionAlongRoad ) const; + + /** Given a node that exists at a point index on this road, finds the nodes that are immediately earlier and later to it (adjacent.) Will set a nullptr if there are no earlier or later nodes */ + void FindEarlierAndLaterNodes( const class UStreetMap& StreetMap, const int32 RoadPointIndex, const FStreetMapNode*& OutEarlierNode, float& OutEarlierNodePositionAlongRoad, const FStreetMapNode*& OutLaterNode, float& OutLaterNodePositionAlongRoad ) const; + + // NOTE: You may notice there is no "FindPointIndexForNode()" method in this class. This is because + // the same node may appear more than once on any single road, so it's never safe to ask for + // a single point index on a road. + + /** Given a node that exists on this road, computes the position along this road of that node */ + float FindPositionAlongRoadForNode( const class UStreetMap& StreetMap, const int32 PointIndexForNode ) const; + + /** Computes the location of a point along this road, given a distance along this road from the road's beginning */ + FVector2D MakeLocationAlongRoad( const class UStreetMap& StreetMap, const float PositionAlongRoad ) const; + + /** @return True if this is a one way road */ + inline bool IsOneWay() const + { + return bIsOneWay == 1 ? true : false; + } +}; + + +/** Nodes have a list of road refs, one for each road that intersects this node. Each road ref references a road and also the + point along that road where this node exists. */ +USTRUCT( BlueprintType ) +struct STREETMAPRUNTIME_API FStreetMapRoadRef +{ + GENERATED_USTRUCT_BODY() + + /** Index of road in the list of all roads in this street map */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + int32 RoadIndex; + + /** Index of the point along road where this node exists */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + int32 RoadPointIndex; +}; + + +/** Describes a node on a road. Nodes usually connect at least two roads together, but they might also exist at the end of a dead-end street. They are sort of like an "intersection". */ +USTRUCT( BlueprintType ) +struct STREETMAPRUNTIME_API FStreetMapNode +{ + GENERATED_USTRUCT_BODY() + + /** All of the roads that intersect this node. We have references to each of these roads, as well as the point along each + road where this node exists */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + TArray RoadRefs; + + /** Returns this node's index */ + inline int32 GetNodeIndex( const UStreetMap& StreetMap ) const; + + /** Gets the location of this node */ + inline FVector2D GetLocation( const UStreetMap& StreetMap ) const; + + + /// + /// Utility functions which may be useful for pathfinding algorithms (not used internally.) + /// + + /** Pathfinding: Given a node that is known to connect to this node via some road, searches for the road and returns it */ + inline const FStreetMapRoad& GetShortestCostRoadToNode( UStreetMap& StreetMap, const FStreetMapNode& OtherNode, const bool bIsTravelingForward, int32& OutPointIndexOnRoad ) const; + + /** Pathfinding: Returns true if this node is the end point on a road with no connections */ + inline bool IsDeadEnd( const UStreetMap& StreetMap ) const; + + /** Pathfinding: Returns the number of connections between this node and other roads, taking into account the direction of travel */ + inline int32 GetConnectionCount( const class UStreetMap& StreetMap, const bool bIsTravelingForward ) const; + + /** Pathfinding: Returns a connected node by index (between 0 and GetConnectionCount() - 1 ), taking into account the direction of travel. Also returns the connecting road and wherabouts on the road the connection occurs */ + inline const FStreetMapNode* GetConnection( const class UStreetMap& StreetMap, const int32 ConnectionIndex, const bool bIsTravelingForward, const struct FStreetMapRoad** OutConnectingRoad = nullptr, int32* OutPointIndexOnRoad = nullptr, int32* OutConnectedNodePointIndexOnRoad = nullptr ) const; + + /** Pathfinding: Estimates the 'cost' of the specified connected by index (between 0 and GetConnectionCount() - 1) */ + inline float GetConnectionCost( const class UStreetMap& StreetMap, const int32 ConnectionIndex, const bool bIsTravelingForward ) const; +}; + + +/** A building */ +USTRUCT( BlueprintType ) +struct STREETMAPRUNTIME_API FStreetMapBuilding +{ + GENERATED_USTRUCT_BODY() + + /** Name of the building */ + UPROPERTY( Category=StreetMap, EditAnywhere, BlueprintReadWrite ) + FString BuildingName; + + /** Polygon points that define the perimeter of the building */ + UPROPERTY( Category=StreetMap, EditAnywhere, BlueprintReadWrite ) + TArray BuildingPoints; + + /** Height of the building in meters (if known, otherwise zero) */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + float Height; + + /** Levels of the building (if known, otherwise zero) */ + UPROPERTY(Category = StreetMap, EditAnywhere) + int BuildingLevels; + + /** The amenity of the building */ + UPROPERTY( Category=StreetMap, EditAnywhere, BlueprintReadWrite ) + FString Amenity; + + /** The building type */ + UPROPERTY( Category=StreetMap, EditAnywhere, BlueprintReadWrite ) + FString Building; + + // @todo: Performance: Bounding information could be computed at load time if we want to avoid the memory cost of storing it + + /** 2D bounds (min) of this building's points */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + FVector2D BoundsMin; + + /** 2D bounds (max) of this building's points */ + UPROPERTY( Category=StreetMap, EditAnywhere ) + FVector2D BoundsMax; +}; + + +/** A loaded street map */ +UCLASS(Blueprintable, BlueprintType) +class STREETMAPRUNTIME_API UStreetMap : public UObject +{ + GENERATED_BODY() + +public: + + /** Default constructor for UStreetMap */ + UStreetMap(); + + // UObject overrides + virtual void GetAssetRegistryTags( TArray& OutTags ) const override; + + /** Gets the roads in this street map (read only) */ + const TArray& GetRoads() const + { + return Roads; + } + + /** Gets the roads in this street map */ + TArray& GetRoads() + { + return Roads; + } + + /** Gets the nodes on the map (read only.) Nodes describe intersections between roads */ + const TArray& GetNodes() const + { + return Nodes; + } + + /** Gets the nodes on the map. Nodes describe intersections between roads */ + TArray& GetNodes() + { + return Nodes; + } + + /** Gets all of the buildings (read only) */ + const TArray& GetBuildings() const + { + return Buildings; + } + + /** Gets all of the buildings */ + TArray& GetBuildings() + { + return Buildings; + } + + /** Gets the bounding box of the map */ + FVector2D GetBoundsMin() const + { + return BoundsMin; + } + FVector2D GetBoundsMax() const + { + return BoundsMax; + } + + /** Vinfamy added */ + UFUNCTION(BlueprintCallable) + FStreetMapBuilding FindBuilding(const FVector2D Point) + { + FStreetMapBuilding emptyBuilding; + for (const auto &element : Buildings) + { + if (FPolygonTools::IsPointInsidePolygon(element.BuildingPoints, Point)) + { + return element; + } + } + return emptyBuilding; + } + + +protected: + + /** List of roads */ + UPROPERTY( Category=StreetMap, VisibleAnywhere ) + TArray Roads; + + /** List of nodes on this map. Nodes describe interesting points along roads, usually where roads intersect or at the end of a dead-end street */ + UPROPERTY( Category=StreetMap, VisibleAnywhere ) + TArray Nodes; + + /** List of all buildings on the street map */ + UPROPERTY( Category=StreetMap, EditAnywhere, BlueprintReadWrite) + TArray Buildings; + + /** 2D bounds (min) of this map's roads and buildings */ + UPROPERTY( Category=StreetMap, VisibleAnywhere) + FVector2D BoundsMin; + + /** 2D bounds (max) of this map's roads and buildings */ + UPROPERTY( Category=StreetMap, VisibleAnywhere) + FVector2D BoundsMax; + +#if WITH_EDITORONLY_DATA + /** Importing data and options used for this mesh */ + UPROPERTY( VisibleAnywhere, Instanced, Category=ImportSettings ) + class UAssetImportData* AssetImportData; + + friend class UStreetMapFactory; + friend class UStreetMapReimportFactory; + friend class FStreetMapAssetTypeActions; +#endif // WITH_EDITORONLY_DATA + +}; + + +inline int32 FStreetMapRoad::GetRoadIndex( const UStreetMap& StreetMap ) const +{ + // Pointer arithmetic based on array start + const int32 RoadIndex = this - StreetMap.GetRoads().GetData(); + return RoadIndex; +} + + +inline const FStreetMapNode& FStreetMapRoad::GetNodeAtPointIndexOrEarlier( const UStreetMap& StreetMap, const int32 PointIndex, int32& OutNodeAtPointIndex ) const +{ + const FStreetMapNode* CurrentOrEarlierPointNode = nullptr; + for( int32 NodePointIndex = PointIndex; NodePointIndex >= 0; --NodePointIndex ) + { + if( NodeIndices[ NodePointIndex ] != INDEX_NONE ) + { + CurrentOrEarlierPointNode = &StreetMap.GetNodes()[ NodeIndices[ NodePointIndex ] ]; + OutNodeAtPointIndex = NodePointIndex; + break; + } + } + return *CurrentOrEarlierPointNode; +} + + +inline const FStreetMapNode& FStreetMapRoad::GetNodeAtPointIndexOrLater( const UStreetMap& StreetMap, const int32 PointIndex, int32& OutNodeAtPointIndex ) const +{ + const FStreetMapNode* NextOrUpcomingNode = nullptr; + for( int32 NodePointIndex = PointIndex; NodePointIndex < RoadPoints.Num(); ++NodePointIndex ) + { + if( NodeIndices[ NodePointIndex ] != INDEX_NONE ) + { + NextOrUpcomingNode = &StreetMap.GetNodes()[ NodeIndices[ NodePointIndex ] ]; + OutNodeAtPointIndex = NodePointIndex; + break; + } + } + + return *NextOrUpcomingNode; +} + + +inline float FStreetMapRoad::ComputeLengthOfRoad( const class UStreetMap& StreetMap ) const +{ + // @todo: Performance: We could cache the road's total length at load time to avoid having to compute it, + // or we could save it right into the asset file + + return ComputeDistanceBetweenNodesOnRoad( StreetMap, 0, this->NodeIndices.Num() - 1 ); +} + + +inline float FStreetMapRoad::ComputeDistanceBetweenNodesOnRoad( const class UStreetMap& StreetMap, const int32 NodePointIndexA, const int32 NodePointIndexB ) const +{ + float TotalDistanceSoFar = 0.0f; + + // NOTE: It is very important that we use the actual road point indices here and not nodes directly, because the same node can appear + // more than once on a single road! + + // @todo: Performance: We can cache distances between connected nodes rather than computing them every time. This + // can be computed at load time or at import time (and stored in the asset). Most of the other functions + // in this class that perform Size() computations could be changed to use cached distances also! + + const int32 SmallerPointIndex = FMath::Max( 0, FMath::Min( NodePointIndexA, NodePointIndexB ) ); + const int32 LargerPointIndex = FMath::Min( RoadPoints.Num() - 1, FMath::Max( NodePointIndexA, NodePointIndexB ) ); + + for( int32 PointIndex = SmallerPointIndex; PointIndex < LargerPointIndex; ++PointIndex ) + { + const FVector2D PointLocation = RoadPoints[ PointIndex ]; + const FVector2D NextPointLocation = RoadPoints[ PointIndex + 1 ]; + + const float DistanceBetweenPoints = ( NextPointLocation - PointLocation ).Size(); + + TotalDistanceSoFar += DistanceBetweenPoints; + } + + // @todo: Malformed data can cause this assertion to trigger. This could be a single road with at least two adjacent nodes + // at the exact same location. We need to filter this out at load time probably. + // check( TotalDistanceSoFar > 0.0f ); + + return TotalDistanceSoFar; +} + + +inline void FStreetMapRoad::FindEarlierAndLaterNodesForPositionAlongRoad( const class UStreetMap& StreetMap, const float PositionAlongRoad, const FStreetMapNode*& OutEarlierNode, float& OutEarlierNodePositionAlongRoad, const FStreetMapNode*& OutLaterNode, float& OutLaterNodePositionAlongRoad ) const +{ + float CurrentPointPositionAlongRoad = 0.0f; + + const FStreetMapNode* EarlierStreetMapNode = nullptr; + const FStreetMapNode* LaterStreetMapNode = nullptr; + + const int32 NumPoints = RoadPoints.Num(); + for( int32 CurrentPointIndex = 0; CurrentPointIndex < NumPoints - 1; ++CurrentPointIndex ) + { + if( this->NodeIndices[ CurrentPointIndex ] != INDEX_NONE ) + { + EarlierStreetMapNode = &StreetMap.GetNodes()[ this->NodeIndices[ CurrentPointIndex ] ]; + OutEarlierNodePositionAlongRoad = CurrentPointPositionAlongRoad; + } + + const int32 NextPointIndex = CurrentPointIndex + 1; + const FVector2D CurrentPointLocation = RoadPoints[ CurrentPointIndex ]; + const FVector2D NextPointLocation = RoadPoints[ NextPointIndex ]; + + const float DistanceBetweenPoints = ( NextPointLocation - CurrentPointLocation ).Size(); + const float NextPointPositionAlongRoad = CurrentPointPositionAlongRoad + DistanceBetweenPoints; + + if( NextPointPositionAlongRoad >= PositionAlongRoad ) + { + if( NodeIndices[ NextPointIndex ] != INDEX_NONE ) + { + LaterStreetMapNode = &StreetMap.GetNodes()[ this->NodeIndices[ NextPointIndex ] ]; + OutLaterNodePositionAlongRoad = NextPointPositionAlongRoad; + break; + } + } + + CurrentPointPositionAlongRoad = NextPointPositionAlongRoad; + } + + check( EarlierStreetMapNode != nullptr && LaterStreetMapNode != nullptr ); + OutEarlierNode = EarlierStreetMapNode; + OutLaterNode = LaterStreetMapNode; +} + + +inline void FStreetMapRoad::FindEarlierAndLaterNodes( const class UStreetMap& StreetMap, const int32 RoadPointIndex, const FStreetMapNode*& OutEarlierNode, float& OutEarlierNodePositionAlongRoad, const FStreetMapNode*& OutLaterNode, float& OutLaterNodePositionAlongRoad ) const +{ + OutEarlierNode = nullptr; + OutEarlierNodePositionAlongRoad = -1.0f; + OutLaterNode = nullptr; + OutLaterNodePositionAlongRoad = -1.0f; + + for( int32 EarlierPointIndex = RoadPointIndex - 1; EarlierPointIndex >= 0; --EarlierPointIndex ) + { + if( this->NodeIndices[ EarlierPointIndex ] != INDEX_NONE ) + { + OutEarlierNode = &StreetMap.GetNodes()[ this->NodeIndices[ EarlierPointIndex ] ]; + OutEarlierNodePositionAlongRoad = FindPositionAlongRoadForNode( StreetMap, EarlierPointIndex ); + break; + } + } + + for( int32 LaterPointIndex = RoadPointIndex + 1; LaterPointIndex < this->RoadPoints.Num(); ++LaterPointIndex ) + { + if( this->NodeIndices[ LaterPointIndex ] != INDEX_NONE ) + { + OutLaterNode = &StreetMap.GetNodes()[ this->NodeIndices[ LaterPointIndex ] ]; + OutLaterNodePositionAlongRoad = FindPositionAlongRoadForNode( StreetMap, LaterPointIndex ); + break; + } + } +} + + +inline float FStreetMapRoad::FindPositionAlongRoadForNode( const class UStreetMap& StreetMap, const int32 PointIndexForNode ) const +{ + float CurrentPointPositionAlongRoad = 0.0f; + + bool bFoundLocation = false; + const int32 NumPoints = RoadPoints.Num(); + for( int32 CurrentPointIndex = 0; CurrentPointIndex < PointIndexForNode; ++CurrentPointIndex ) + { + const FVector2D CurrentPointLocation = RoadPoints[ CurrentPointIndex ]; + const FVector2D NextPointLocation = RoadPoints[ CurrentPointIndex + 1 ]; + + const float DistanceBetweenPoints = ( NextPointLocation - CurrentPointLocation ).Size(); + const float NextPointPositionAlongRoad = CurrentPointPositionAlongRoad + DistanceBetweenPoints; + + CurrentPointPositionAlongRoad = NextPointPositionAlongRoad; + } + + return CurrentPointPositionAlongRoad; +} + + +inline FVector2D FStreetMapRoad::MakeLocationAlongRoad( const class UStreetMap& StreetMap, const float PositionAlongRoad ) const +{ + FVector2D LocationAlongRoad = FVector2D::ZeroVector; + float CurrentPointPositionAlongRoad = 0.0f; + + bool bFoundLocation = false; + const int32 NumPoints = RoadPoints.Num(); + for( int32 CurrentPointIndex = 0; CurrentPointIndex < NumPoints - 1; ++CurrentPointIndex ) + { + const FVector2D CurrentPointLocation = RoadPoints[ CurrentPointIndex ]; + const FVector2D NextPointLocation = RoadPoints[ CurrentPointIndex + 1 ]; + + const float DistanceBetweenPoints = ( NextPointLocation - CurrentPointLocation ).Size(); + const float NextPointPositionAlongRoad = CurrentPointPositionAlongRoad + DistanceBetweenPoints; + + if( NextPointPositionAlongRoad >= PositionAlongRoad ) + { + const float LerpAlpha = ( PositionAlongRoad - CurrentPointPositionAlongRoad ) / DistanceBetweenPoints; + LocationAlongRoad = FMath::Lerp( CurrentPointLocation, NextPointLocation, LerpAlpha ); + bFoundLocation = true; + break; + } + + CurrentPointPositionAlongRoad = NextPointPositionAlongRoad; + } + + check( bFoundLocation == true ); + + return LocationAlongRoad; +} + + +inline int32 FStreetMapNode::GetNodeIndex( const UStreetMap& StreetMap ) const +{ + // Pointer arithmetic based on array start + const int32 NodeIndex = this - StreetMap.GetNodes().GetData(); + return NodeIndex; +} + + +inline bool FStreetMapNode::IsDeadEnd( const UStreetMap& StreetMap ) const +{ + if( RoadRefs.Num() == 1 ) + { + // @todo: If this road only connects to dead end roads that oppose the direction, we need to treat this road + // as a dead end. This case should be extremely uncommon, though! + + const FStreetMapRoadRef& SoleRoadRef = RoadRefs[ 0 ]; + const FStreetMapRoad& SoleRoad = StreetMap.GetRoads()[ SoleRoadRef.RoadIndex ]; + if( SoleRoadRef.RoadPointIndex == 0 || SoleRoadRef.RoadPointIndex == ( SoleRoad.NodeIndices.Num() - 1 ) ) + { + // The node is attached to only one road, and the node is at the very end of one of the ends of the road + return true; + } + } + + return false; +} + + +inline FVector2D FStreetMapNode::GetLocation( const UStreetMap& StreetMap ) const +{ + const FStreetMapRoadRef& MyFirstRoadRef = RoadRefs[ 0 ]; + const FVector2D Location = StreetMap.GetRoads()[ MyFirstRoadRef.RoadIndex ].RoadPoints[ MyFirstRoadRef.RoadPointIndex ]; + return Location; +} + + +inline const FStreetMapRoad& FStreetMapNode::GetShortestCostRoadToNode( UStreetMap& StreetMap, const FStreetMapNode& OtherNode, const bool bIsTravelingForward, int32& OutPointIndexOnRoad ) const +{ + const FStreetMapRoad* ConnectingRoad = nullptr; + + float BestConnectionCost = TNumericLimits::Max(); + int32 BestConnectionIndex = INDEX_NONE; + int32 BestConnectionPointIndex = INDEX_NONE; + + const int32 ConnectionCount = GetConnectionCount( StreetMap, bIsTravelingForward ); + for( int32 ConnectionIndex = 0; ConnectionIndex < ConnectionCount; ++ConnectionIndex ) + { + int32 MyPointIndexOnRoad; + int32 ConnectedNodePointIndexOnRoad; + + const FStreetMapRoad* CurrentConnectingRoad; + const FStreetMapNode* ConnectedNode = GetConnection( StreetMap, ConnectionIndex, bIsTravelingForward, /* Out */ &CurrentConnectingRoad, /* Out */ &MyPointIndexOnRoad, /* Out */ &ConnectedNodePointIndexOnRoad ); + if( ConnectedNode == &OtherNode ) + { + if( BestConnectionIndex != INDEX_NONE ) + { + // We evaluate cost lazily, only in the unusual case of the two nodes being connected by multiple roads + BestConnectionCost = GetConnectionCost( StreetMap, BestConnectionIndex, bIsTravelingForward ); + if( GetConnectionCost( StreetMap, ConnectionIndex, bIsTravelingForward ) < BestConnectionCost ) + { + ConnectingRoad = CurrentConnectingRoad; + BestConnectionIndex = ConnectionIndex; + BestConnectionPointIndex = MyPointIndexOnRoad; + } + } + else + { + ConnectingRoad = CurrentConnectingRoad; + BestConnectionIndex = ConnectionIndex; + BestConnectionPointIndex = MyPointIndexOnRoad; + } + } + } + + check( ConnectingRoad != nullptr ); + OutPointIndexOnRoad = BestConnectionPointIndex; + return *ConnectingRoad; +} + +inline int32 FStreetMapNode::GetConnectionCount( const UStreetMap& StreetMap, const bool bIsTravelingForward ) const +{ + // NOTE: We're iterating here in the exact same order as in the GetConnection() function below! That's critically important! + int32 TotalConnections = 0; + for( const FStreetMapRoadRef& RoadRef : RoadRefs ) + { + const FStreetMapRoad& Road = StreetMap.GetRoads()[ RoadRef.RoadIndex ]; + + if( RoadRef.RoadPointIndex > 0 && ( !bIsTravelingForward || !Road.IsOneWay() ) ) + { + // We connect to a node earlier up this road + ++TotalConnections; + } + + if( RoadRef.RoadPointIndex < ( Road.NodeIndices.Num() - 1 ) && ( bIsTravelingForward || !Road.IsOneWay() ) ) + { + // We connect to a node further down this road + ++TotalConnections; + } + } + + return TotalConnections; +} + + +inline const FStreetMapNode* FStreetMapNode::GetConnection( const UStreetMap& StreetMap, const int32 ConnectionIndex, const bool bIsTravelingForward, const FStreetMapRoad** OutConnectingRoad, int32* OutPointIndexOnRoad, int32* OutConnectedNodePointIndexOnRoad ) const +{ + if( OutConnectingRoad != nullptr ) + { + *OutConnectingRoad = nullptr; + } + const FStreetMapNode* ConnectedNode = nullptr; + + // @todo: Performance: We can improve performance by caching additional connectivity information right on + // the node itself. This function would be a hot spot for any sort of pathfinding computation + + // NOTE: We're iterating here in the exact same order as in the GetConnectionCount() function above! That's critically important! + int32 CurrentConnectionIndex = 0; + for( const FStreetMapRoadRef& RoadRef : RoadRefs ) + { + const FStreetMapRoad& Road = StreetMap.GetRoads()[ RoadRef.RoadIndex ]; + + // @todo: Performance: We could avoid the "while" loops below by not storing INDEX_NONEs in the NodeIndices array, + // but instead mapping them to points by going through the node itself, then back to a road + + if( RoadRef.RoadPointIndex > 0 && ( !bIsTravelingForward || !Road.IsOneWay() ) ) + { + // We connect to an earlier node up this road + if( CurrentConnectionIndex == ConnectionIndex ) + { + int32 EarlierNodeRoadPointIndex = RoadRef.RoadPointIndex - 1; + while( Road.NodeIndices[ EarlierNodeRoadPointIndex ] == INDEX_NONE ) + { + --EarlierNodeRoadPointIndex; + } + const int32 EarlierNodeIndex = Road.NodeIndices[ EarlierNodeRoadPointIndex ]; + + const FStreetMapNode& EarlierNode = StreetMap.GetNodes()[ EarlierNodeIndex ]; + ConnectedNode = &EarlierNode; + if( OutConnectingRoad != nullptr ) + { + *OutConnectingRoad = &Road; + } + if( OutPointIndexOnRoad != nullptr ) + { + *OutPointIndexOnRoad = RoadRef.RoadPointIndex; + } + if( OutConnectedNodePointIndexOnRoad != nullptr ) + { + *OutConnectedNodePointIndexOnRoad = EarlierNodeRoadPointIndex; + } + + // Got it! + break; + } + + ++CurrentConnectionIndex; + } + + if( RoadRef.RoadPointIndex < ( Road.NodeIndices.Num() - 1 ) && ( bIsTravelingForward || !Road.IsOneWay() ) ) + { + // We connect to node further down this road + if( CurrentConnectionIndex == ConnectionIndex ) + { + int32 LaterNodeRoadPointIndex = RoadRef.RoadPointIndex + 1; + while( Road.NodeIndices[ LaterNodeRoadPointIndex ] == INDEX_NONE ) + { + ++LaterNodeRoadPointIndex; + } + const int32 LaterNodeIndex = Road.NodeIndices[ LaterNodeRoadPointIndex ]; + + const FStreetMapNode& LaterNode = StreetMap.GetNodes()[ LaterNodeIndex ]; + ConnectedNode = &LaterNode; + if( OutConnectingRoad != nullptr ) + { + *OutConnectingRoad = &Road; + } + if( OutPointIndexOnRoad != nullptr ) + { + *OutPointIndexOnRoad = RoadRef.RoadPointIndex; + } + if( OutConnectedNodePointIndexOnRoad != nullptr ) + { + *OutConnectedNodePointIndexOnRoad = LaterNodeRoadPointIndex; + } + + // Got it! + break; + } + + ++CurrentConnectionIndex; + } + } + + check( ConnectedNode != nullptr ); + return ConnectedNode; +} + + +inline float FStreetMapNode::GetConnectionCost( const UStreetMap& StreetMap, const int32 ConnectionIndex, const bool bIsTravelingForward ) const +{ + ///////////////////////////////////////////////////////// + // Tweakables for connection cost estimation + // + const float MaxSpeedLimit = 120.0f; // 120 Km/hr + const float HighwaySpeed = 110.0f; + const float HighwayTrafficFactor = 0.0; + const float MajorRoadSpeed = 70.0f; + const float MajorRoadTrafficFactor = 0.2f; + const float StreetSpeed = 40.0f; + const float StreetTrafficFactor = 1.0f; + ///////////////////////////////////////////////////////// + + // @todo: Street map pathfinding is a grand art in itself, and estimating cost of connections is + // a very complicated problem. We're only doing some basic estimates for now, but in the + // future we could consider taking into account the cost of different types of turns and + // intersections, lane counts, actual speed limits, etc. + + int32 MyPointIndexOnRoad; + int32 ConnectedNodePointIndexOnRoad; + + const FStreetMapRoad* ConnectingRoad = nullptr; + const FStreetMapNode& ConnectedNode = *GetConnection( StreetMap, ConnectionIndex, bIsTravelingForward, /* Out */ &ConnectingRoad, /* Out */ &MyPointIndexOnRoad, /* Out */ &ConnectedNodePointIndexOnRoad ); + + const float DistanceBetweenNodes = ConnectingRoad->ComputeDistanceBetweenNodesOnRoad( StreetMap, MyPointIndexOnRoad, ConnectedNodePointIndexOnRoad ); + + float TotalCost = DistanceBetweenNodes; + + // Apply some scaling to the cost of traveling between these nodes + { + float SpeedLimit = 0.0f; + float TrafficFactor = 0.0f; + switch( ConnectingRoad->RoadType ) + { + case EStreetMapRoadType::Highway: + SpeedLimit = HighwaySpeed; + TrafficFactor = HighwayTrafficFactor; + break; + + case EStreetMapRoadType::MajorRoad: + SpeedLimit = MajorRoadSpeed; + TrafficFactor = MajorRoadTrafficFactor; + break; + + case EStreetMapRoadType::Street: + case EStreetMapRoadType::Other: + SpeedLimit = StreetSpeed; + TrafficFactor = StreetTrafficFactor; + break; + + default: + check( 0 ); + break; + } + + const float RoadSpeedCostScale = ( 1.0f - ( SpeedLimit / MaxSpeedLimit ) ); + TotalCost *= 1.0f + RoadSpeedCostScale * 15.0f * ( 0.5f + TrafficFactor * 0.5f ); + } + + return TotalCost; +} + + diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/Public/StreetMapActor.h b/Plugins/StreetMap/Source/StreetMapRuntime/Public/StreetMapActor.h new file mode 100644 index 0000000..e8bae48 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapRuntime/Public/StreetMapActor.h @@ -0,0 +1,19 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#pragma once + +#include "StreetMapActor.generated.h" + +/** An actor that renders a street map mesh component */ +UCLASS(hidecategories = (Physics)) // Physics category in detail panel is hidden. Our component/Actor is not simulated ! +class STREETMAPRUNTIME_API AStreetMapActor : public AActor +{ + GENERATED_UCLASS_BODY() + + /** Component that represents a section of street map roads and buildings */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "StreetMap") + class UStreetMapComponent* StreetMapComponent; + +public: + FORCEINLINE class UStreetMapComponent* GetStreetMapComponent() { return StreetMapComponent; } +}; diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/Public/StreetMapComponent.h b/Plugins/StreetMap/Source/StreetMapRuntime/Public/StreetMapComponent.h new file mode 100644 index 0000000..ea7ebb1 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapRuntime/Public/StreetMapComponent.h @@ -0,0 +1,188 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. +#pragma once + +#include "StreetMap.h" +#include "Components/MeshComponent.h" +#include "Interfaces/Interface_CollisionDataProvider.h" +#include "StreetMapSceneProxy.h" +#include "StreetMapComponent.generated.h" + + + +class UBodySetup; + +/** + * Component that represents a section of street map roads and buildings + */ +UCLASS( meta=(BlueprintSpawnableComponent) , hidecategories = (Physics)) +class STREETMAPRUNTIME_API UStreetMapComponent : public UMeshComponent, public IInterface_CollisionDataProvider +{ + GENERATED_BODY() + +public: + + /** UStreetMapComponent constructor */ + UStreetMapComponent(const class FObjectInitializer& ObjectInitializer); + + /** @return Gets the street map object associated with this component */ + UStreetMap* GetStreetMap() + { + return StreetMap; + } + + /** Returns StreetMap asset object name */ + FString GetStreetMapAssetName() const; + + /** Returns true if we have valid cached mesh data from our assigned street map asset */ + bool HasValidMesh() const + { + return Vertices.Num() != 0 && Indices.Num() != 0; + } + + /** Returns Cached raw mesh vertices */ + TArray< struct FStreetMapVertex > GetRawMeshVertices() const + { + return Vertices; + } + + /** Returns Cached raw mesh triangle indices */ + TArray< uint32 > GetRawMeshIndices() const + { + return Indices; + } + + /** + * Returns StreetMap Default Material if a valid one is found in plugin's content folder. + * Otherwise , it returns the default surface 3d material. + */ + UMaterialInterface* GetDefaultMaterial() const + { + return StreetMapDefaultMaterial != nullptr ? StreetMapDefaultMaterial : UMaterial::GetDefaultMaterial(MD_Surface); + } + + /** Returns true, if the input PropertyName correspond to a collision property. */ + bool IsCollisionProperty(const FName& PropertyName) const + { + return PropertyName == TEXT("bGenerateCollision") || PropertyName == TEXT("bAllowDoubleSidedGeometry"); + } + + /** + * Returns sub-meshes count. + * In this case we are creating one single mesh section, so it will return 1. + * If cached mesh data are not valid , it will return 0. + */ + int32 GetNumMeshSections() const + { + return HasValidMesh() ? 1 : 0; + } + + /** + * Assigns a street map asset to this component. Render state will be updated immediately. + * + * @param NewStreetMap The street map to use + * + * @param bRebuildMesh : Rebuilds map mesh based on the new map asset + * + * @return Sets the street map object + */ + UFUNCTION(BlueprintCallable, Category = "StreetMap") + void SetStreetMap(UStreetMap* NewStreetMap, bool bClearPreviousMeshIfAny = false, bool bRebuildMesh = false); + + + + //** Begin Interface_CollisionDataProvider Interface */ + virtual bool GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData) override; + virtual bool ContainsPhysicsTriMeshData(bool InUseAllTriData) const override; + virtual bool WantsNegXTriMesh() override; + //** End Interface_CollisionDataProvider Interface *// + +protected: + + /** + * Ensures the body setup is initialized/configured and updates it if needed. + * @param bForceCreation : Force new BodySetup creation even if a valid one already exists. + */ + void CreateBodySetupIfNeeded(bool bForceCreation = false); + + /** Marks collision data as dirty, and re-create on instance if necessary */ + void GenerateCollision(); + + /** Wipes out and invalidate collision data. */ + void ClearCollision(); + +public: + + // UPrimitiveComponent interface + virtual UBodySetup* GetBodySetup() override; + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override; + virtual int32 GetNumMaterials() const override; +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + + /** Wipes out our cached mesh data. Designed to be called on demand.*/ + void InvalidateMesh(); + + /** Rebuilds the graphics and physics mesh representation if we don't have one right now. Designed to be called on demand. */ + void BuildMesh(); + + + +protected: + + /** Giving a default material to the mesh if no valid material is already assigned or materials array is empty. */ + void AssignDefaultMaterialIfNeeded(); + + /** Updating navoctree entry for this component , if need/possible. */ + void UpdateNavigationIfNeeded(); + + /** Generates a cached mesh from raw street map data */ + void GenerateMesh(); + + /** Adds a 2D line to the raw mesh */ + void AddThick2DLine(const FVector2D Start, const FVector2D End, const float Z, const float Thickness, const FColor& StartColor, const FColor& EndColor, FBox& MeshBoundingBox); + + /** Adds 3D triangles to the raw mesh */ + void AddTriangles(const TArray& Points, const TArray& PointIndices, const FVector& ForwardVector, const FVector& UpVector, const FColor& Color, FBox& MeshBoundingBox); + + +protected: + + /** The street map we're representing. */ + UPROPERTY(EditAnywhere, Category = "StreetMap") + UStreetMap* StreetMap; + + UPROPERTY(EditAnywhere, Category = "StreetMap") + FStreetMapMeshBuildSettings MeshBuildSettings; + + UPROPERTY(EditAnywhere, Category = "StreetMap") + FStreetMapCollisionSettings CollisionSettings; + + //** Physics data for mesh collision. */ + UPROPERTY(Transient) + UBodySetup* StreetMapBodySetup; + + +protected: + // + // Cached mesh representation + // + + /** Cached raw mesh vertices */ + UPROPERTY() + TArray< struct FStreetMapVertex > Vertices; + + /** Cached raw mesh triangle indices */ + UPROPERTY() + TArray< uint32 > Indices; + + /** Cached bounding box */ + UPROPERTY() + FBoxSphereBounds CachedLocalBounds; + + /** Cached StreetMap DefaultMaterial */ + UPROPERTY() + UMaterialInterface* StreetMapDefaultMaterial; + +}; diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/StreetMap.cpp b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMap.cpp new file mode 100644 index 0000000..06c1834 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMap.cpp @@ -0,0 +1,29 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#include "StreetMapRuntime.h" +#include "StreetMap.h" +#include "EditorFramework/AssetImportData.h" + + +UStreetMap::UStreetMap() +{ +#if WITH_EDITORONLY_DATA + if( !HasAnyFlags( RF_ClassDefaultObject ) ) + { + AssetImportData = NewObject( this, TEXT( "AssetImportData" ) ); + } +#endif +} + + +void UStreetMap::GetAssetRegistryTags( TArray& OutTags ) const +{ +#if WITH_EDITORONLY_DATA + if( AssetImportData ) + { + OutTags.Add( FAssetRegistryTag( SourceFileTagName(), AssetImportData->GetSourceData().ToJson(), FAssetRegistryTag::TT_Hidden ) ); + } +#endif + + Super::GetAssetRegistryTags( OutTags ); +} diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapActor.cpp b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapActor.cpp new file mode 100644 index 0000000..735f8a0 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapActor.cpp @@ -0,0 +1,13 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "StreetMapRuntime.h" +#include "StreetMapActor.h" +#include "StreetMapComponent.h" + + +AStreetMapActor::AStreetMapActor(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + StreetMapComponent = CreateDefaultSubobject(TEXT("StreetMapComp")); + RootComponent = StreetMapComponent; +} \ No newline at end of file diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapComponent.cpp b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapComponent.cpp new file mode 100644 index 0000000..680dfaa --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapComponent.cpp @@ -0,0 +1,633 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#include "StreetMapRuntime.h" +#include "StreetMapComponent.h" +#include "StreetMapSceneProxy.h" +#include "Runtime/Engine/Classes/Engine/StaticMesh.h" +#include "Runtime/Engine/Public/StaticMeshResources.h" +#include "PolygonTools.h" + +#include "PhysicsEngine/BodySetup.h" + +#if WITH_EDITOR +#include "ModuleManager.h" +#include "PropertyEditorModule.h" +#endif //WITH_EDITOR + + + +UStreetMapComponent::UStreetMapComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer), + StreetMap(nullptr), + CachedLocalBounds(ForceInit) +{ + // We make sure our mesh collision profile name is set to NoCollisionProfileName at initialization. + // Because we don't have collision data yet! + SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); + + // We don't currently need to be ticked. This can be overridden in a derived class though. + PrimaryComponentTick.bCanEverTick = false; + this->bAutoActivate = false; // NOTE: Components instantiated through C++ are not automatically active, so they'll only tick once and then go to sleep! + + // We don't currently need InitializeComponent() to be called on us. This can be overridden in a + // derived class though. + bWantsInitializeComponent = false; + + // Turn on shadows. It looks better. + CastShadow = true; + + // Our mesh is too complicated to be a useful occluder. + bUseAsOccluder = false; + + // Our mesh can influence navigation. + bCanEverAffectNavigation = true; + + static ConstructorHelpers::FObjectFinder DefaultMaterialAsset(TEXT("/StreetMap/StreetMapDefaultMaterial")); + StreetMapDefaultMaterial = DefaultMaterialAsset.Object; + +} + + +FPrimitiveSceneProxy* UStreetMapComponent::CreateSceneProxy() +{ + FStreetMapSceneProxy* StreetMapSceneProxy = nullptr; + + if( HasValidMesh() ) + { + StreetMapSceneProxy = new FStreetMapSceneProxy( this ); + StreetMapSceneProxy->Init( this, Vertices, Indices ); + } + + return StreetMapSceneProxy; +} + + +int32 UStreetMapComponent::GetNumMaterials() const +{ + // NOTE: This is a bit of a weird thing about Unreal that we need to deal with when defining a component that + // can have materials assigned. UPrimitiveComponent::GetNumMaterials() will return 0, so we need to override it + // to return the number of overridden materials, which are the actual materials assigned to the component. + return HasValidMesh() ? GetNumMeshSections() : GetNumOverrideMaterials(); +} + + +void UStreetMapComponent::SetStreetMap(class UStreetMap* NewStreetMap, bool bClearPreviousMeshIfAny /*= false*/, bool bRebuildMesh /*= false */) +{ + if (StreetMap != NewStreetMap) + { + StreetMap = NewStreetMap; + + if (bClearPreviousMeshIfAny) + InvalidateMesh(); + + if (bRebuildMesh) + BuildMesh(); + } +} + + +bool UStreetMapComponent::GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData) +{ + + if (!CollisionSettings.bGenerateCollision || !HasValidMesh()) + { + return false; + } + + // Copy vertices data + const int32 NumVertices = Vertices.Num(); + CollisionData->Vertices.Empty(); + CollisionData->Vertices.AddUninitialized(NumVertices); + + for (int32 VertexIndex = 0; VertexIndex < NumVertices; VertexIndex++) + { + CollisionData->Vertices[VertexIndex] = Vertices[VertexIndex].Position; + } + + // Copy indices data + const int32 NumTriangles = Indices.Num() / 3; + FTriIndices TempTriangle; + for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles * 3; TriangleIndex += 3) + { + + TempTriangle.v0 = Indices[TriangleIndex + 0]; + TempTriangle.v1 = Indices[TriangleIndex + 1]; + TempTriangle.v2 = Indices[TriangleIndex + 2]; + + + CollisionData->Indices.Add(TempTriangle); + CollisionData->MaterialIndices.Add(0); + } + + CollisionData->bFlipNormals = true; + CollisionData->bDeformableMesh = true; + + return HasValidMesh(); +} + + +bool UStreetMapComponent::ContainsPhysicsTriMeshData(bool InUseAllTriData) const +{ + return HasValidMesh() && CollisionSettings.bGenerateCollision; +} + + +bool UStreetMapComponent::WantsNegXTriMesh() +{ + return false; +} + + +void UStreetMapComponent::CreateBodySetupIfNeeded(bool bForceCreation /*= false*/) +{ + if (StreetMapBodySetup == nullptr || bForceCreation == true) + { + // Creating new BodySetup Object. + StreetMapBodySetup = NewObject(this); + StreetMapBodySetup->BodySetupGuid = FGuid::NewGuid(); + StreetMapBodySetup->bDoubleSidedGeometry = CollisionSettings.bAllowDoubleSidedGeometry; + + // shapes per poly shape for collision (Not working in simulation mode). + StreetMapBodySetup->CollisionTraceFlag = CTF_UseComplexAsSimple; + } +} + + +void UStreetMapComponent::GenerateCollision() +{ + if (!CollisionSettings.bGenerateCollision || !HasValidMesh()) + { + return; + } + + // create a new body setup + CreateBodySetupIfNeeded(true); + + + if (GetCollisionProfileName() == UCollisionProfile::NoCollision_ProfileName) + { + SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName); + } + + // Rebuild the body setup +#if WITH_EDITOR || WITH_RUNTIME_PHYSICS_COOKING + StreetMapBodySetup->InvalidatePhysicsData(); +#endif + StreetMapBodySetup->CreatePhysicsMeshes(); + + UpdateNavigationIfNeeded(); +} + + +void UStreetMapComponent::ClearCollision() +{ + + if (StreetMapBodySetup != nullptr) + { + StreetMapBodySetup->InvalidatePhysicsData(); + StreetMapBodySetup = nullptr; + } + + if (GetCollisionProfileName() != UCollisionProfile::NoCollision_ProfileName) + { + SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); + } + + UpdateNavigationIfNeeded(); +} + +class UBodySetup* UStreetMapComponent::GetBodySetup() +{ + if (CollisionSettings.bGenerateCollision == true) + { + // checking if we have a valid body setup. + // A new one is created only if a valid body setup is not found. + CreateBodySetupIfNeeded(); + return StreetMapBodySetup; + } + + if (StreetMapBodySetup != nullptr) StreetMapBodySetup = nullptr; + + return nullptr; +} + +void UStreetMapComponent::GenerateMesh() +{ + ///////////////////////////////////////////////////////// + // Visual tweakables for generated Street Map mesh + // + const float RoadZ = MeshBuildSettings.RoadOffesetZ; + const bool bWant3DBuildings = MeshBuildSettings.bWant3DBuildings; + const float BuildingLevelFloorFactor = MeshBuildSettings.BuildingLevelFloorFactor; + const bool bWantLitBuildings = MeshBuildSettings.bWantLitBuildings; + const bool bWantBuildingBorderOnGround = !bWant3DBuildings; + const float StreetThickness = MeshBuildSettings.StreetThickness; + const FColor StreetColor = MeshBuildSettings.StreetColor.ToFColor( false ); + const float MajorRoadThickness = MeshBuildSettings.MajorRoadThickness; + const FColor MajorRoadColor = MeshBuildSettings.MajorRoadColor.ToFColor( false ); + const float HighwayThickness = MeshBuildSettings.HighwayThickness; + const FColor HighwayColor = MeshBuildSettings.HighwayColor.ToFColor( false ); + const float BuildingBorderThickness = MeshBuildSettings.BuildingBorderThickness; + FLinearColor BuildingBorderLinearColor = MeshBuildSettings.BuildingBorderLinearColor; + const float BuildingBorderZ = MeshBuildSettings.BuildingBorderZ; + const FColor BuildingBorderColor( BuildingBorderLinearColor.ToFColor( false ) ); + const FColor BuildingFillColor( FLinearColor( BuildingBorderLinearColor * 0.33f ).CopyWithNewOpacity( 1.0f ).ToFColor( false ) ); + ///////////////////////////////////////////////////////// + + + CachedLocalBounds = FBox( ForceInit ); + Vertices.Reset(); + Indices.Reset(); + + if( StreetMap != nullptr ) + { + FBox MeshBoundingBox; + MeshBoundingBox.Init(); + + const auto& Roads = StreetMap->GetRoads(); + const auto& Nodes = StreetMap->GetNodes(); + const auto& Buildings = StreetMap->GetBuildings(); + + for( const auto& Road : Roads ) + { + float RoadThickness = StreetThickness; + FColor RoadColor = StreetColor; + switch( Road.RoadType ) + { + case EStreetMapRoadType::Highway: + RoadThickness = HighwayThickness; + RoadColor = HighwayColor; + break; + + case EStreetMapRoadType::MajorRoad: + RoadThickness = MajorRoadThickness; + RoadColor = MajorRoadColor; + break; + + case EStreetMapRoadType::Street: + case EStreetMapRoadType::Other: + break; + + default: + check( 0 ); + break; + } + + for( int32 PointIndex = 0; PointIndex < Road.RoadPoints.Num() - 1; ++PointIndex ) + { + AddThick2DLine( + Road.RoadPoints[ PointIndex ], + Road.RoadPoints[ PointIndex + 1 ], + RoadZ, + RoadThickness, + RoadColor, + RoadColor, + MeshBoundingBox ); + } + } + + TArray< int32 > TempIndices; + TArray< int32 > TriangulatedVertexIndices; + TArray< FVector > TempPoints; + for( int32 BuildingIndex = 0; BuildingIndex < Buildings.Num(); ++BuildingIndex ) + { + const auto& Building = Buildings[ BuildingIndex ]; + + // Building mesh (or filled area, if the building has no height) + + // Triangulate this building + // @todo: Performance: Triangulating lots of building polygons is quite slow. We could easily do this + // as part of the import process and store tessellated geometry instead of doing this at load time. + bool WindsClockwise; + if( FPolygonTools::TriangulatePolygon( Building.BuildingPoints, TempIndices, /* Out */ TriangulatedVertexIndices, /* Out */ WindsClockwise ) ) + { + // @todo: Performance: We could preprocess the building shapes so that the points always wind + // in a consistent direction, so we can skip determining the winding above. + + const int32 FirstTopVertexIndex = this->Vertices.Num(); + + // calculate fill Z for buildings + // either use the defined height or extrapolate from building level count + float BuildingFillZ = 0.0f; + if (bWant3DBuildings) { + if (Building.Height > 0) { + BuildingFillZ = Building.Height; + } + else if (Building.BuildingLevels > 0) { + BuildingFillZ = (float)Building.BuildingLevels * BuildingLevelFloorFactor; + } + } + + // Top of building + { + TempPoints.SetNum( Building.BuildingPoints.Num(), false ); + for( int32 PointIndex = 0; PointIndex < Building.BuildingPoints.Num(); ++PointIndex ) + { + TempPoints[ PointIndex ] = FVector( Building.BuildingPoints[ ( Building.BuildingPoints.Num() - PointIndex ) - 1 ], BuildingFillZ ); + } + AddTriangles( TempPoints, TriangulatedVertexIndices, FVector::ForwardVector, FVector::UpVector, BuildingFillColor, MeshBoundingBox ); + } + + if( bWant3DBuildings && (Building.Height > KINDA_SMALL_NUMBER || Building.BuildingLevels > 0) ) + { + // NOTE: Lit buildings can't share vertices beyond quads (all quads have their own face normals), so this uses a lot more geometry! + if( bWantLitBuildings ) + { + // Create edges for the walls of the 3D buildings + for( int32 LeftPointIndex = 0; LeftPointIndex < Building.BuildingPoints.Num(); ++LeftPointIndex ) + { + const int32 RightPointIndex = ( LeftPointIndex + 1 ) % Building.BuildingPoints.Num(); + + TempPoints.SetNum( 4, false ); + + const int32 TopLeftVertexIndex = 0; + TempPoints[ TopLeftVertexIndex ] = FVector( Building.BuildingPoints[ WindsClockwise ? RightPointIndex : LeftPointIndex ], BuildingFillZ ); + + const int32 TopRightVertexIndex = 1; + TempPoints[ TopRightVertexIndex ] = FVector( Building.BuildingPoints[ WindsClockwise ? LeftPointIndex : RightPointIndex ], BuildingFillZ ); + + const int32 BottomRightVertexIndex = 2; + TempPoints[ BottomRightVertexIndex ] = FVector( Building.BuildingPoints[ WindsClockwise ? LeftPointIndex : RightPointIndex ], 0.0f ); + + const int32 BottomLeftVertexIndex = 3; + TempPoints[ BottomLeftVertexIndex ] = FVector( Building.BuildingPoints[ WindsClockwise ? RightPointIndex : LeftPointIndex ], 0.0f ); + + + TempIndices.SetNum( 6, false ); + + TempIndices[ 0 ] = BottomLeftVertexIndex; + TempIndices[ 1 ] = TopLeftVertexIndex; + TempIndices[ 2 ] = BottomRightVertexIndex; + + TempIndices[ 3 ] = BottomRightVertexIndex; + TempIndices[ 4 ] = TopLeftVertexIndex; + TempIndices[ 5 ] = TopRightVertexIndex; + + const FVector FaceNormal = FVector::CrossProduct( ( TempPoints[ 0 ] - TempPoints[ 2 ] ).GetSafeNormal(), ( TempPoints[ 0 ] - TempPoints[ 1 ] ).GetSafeNormal() ); + const FVector ForwardVector = FVector::UpVector; + const FVector UpVector = FaceNormal; + AddTriangles( TempPoints, TempIndices, ForwardVector, UpVector, BuildingFillColor, MeshBoundingBox ); + } + } + else + { + // Create vertices for the bottom + const int32 FirstBottomVertexIndex = this->Vertices.Num(); + for( int32 PointIndex = 0; PointIndex < Building.BuildingPoints.Num(); ++PointIndex ) + { + const FVector2D Point = Building.BuildingPoints[ PointIndex ]; + + FStreetMapVertex& NewVertex = *new( this->Vertices )FStreetMapVertex(); + NewVertex.Position = FVector( Point, 0.0f ); + NewVertex.TextureCoordinate = FVector2D( 0.0f, 0.0f ); // NOTE: We're not using texture coordinates for anything yet + NewVertex.TangentX = FVector::ForwardVector; // NOTE: Tangents aren't important for these unlit buildings + NewVertex.TangentZ = FVector::UpVector; + NewVertex.Color = BuildingFillColor; + + MeshBoundingBox += NewVertex.Position; + } + + // Create edges for the walls of the 3D buildings + for( int32 LeftPointIndex = 0; LeftPointIndex < Building.BuildingPoints.Num(); ++LeftPointIndex ) + { + const int32 RightPointIndex = ( LeftPointIndex + 1 ) % Building.BuildingPoints.Num(); + + const int32 BottomLeftVertexIndex = FirstBottomVertexIndex + LeftPointIndex; + const int32 BottomRightVertexIndex = FirstBottomVertexIndex + RightPointIndex; + const int32 TopRightVertexIndex = FirstTopVertexIndex + RightPointIndex; + const int32 TopLeftVertexIndex = FirstTopVertexIndex + LeftPointIndex; + + this->Indices.Add( BottomLeftVertexIndex ); + this->Indices.Add( TopLeftVertexIndex ); + this->Indices.Add( BottomRightVertexIndex ); + + this->Indices.Add( BottomRightVertexIndex ); + this->Indices.Add( TopLeftVertexIndex ); + this->Indices.Add( TopRightVertexIndex ); + } + } + } + } + else + { + // @todo: Triangulation failed for some reason, possibly due to degenerate polygons. We can + // probably improve the algorithm to avoid this happening. + } + + // Building border + if( bWantBuildingBorderOnGround ) + { + for( int32 PointIndex = 0; PointIndex < Building.BuildingPoints.Num(); ++PointIndex ) + { + AddThick2DLine( + Building.BuildingPoints[ PointIndex ], + Building.BuildingPoints[ ( PointIndex + 1 ) % Building.BuildingPoints.Num() ], + BuildingBorderZ, + BuildingBorderThickness, // Thickness + BuildingBorderColor, + BuildingBorderColor, + MeshBoundingBox ); + } + } + } + + CachedLocalBounds = MeshBoundingBox; + } +} + + +#if WITH_EDITOR +void UStreetMapComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + bool bNeedRefreshCustomizationModule = false; + + // Check to see if the "StreetMap" property changed. + if (PropertyChangedEvent.Property != nullptr) + { + const FName PropertyName(PropertyChangedEvent.Property->GetFName()); + if (PropertyName == GET_MEMBER_NAME_CHECKED(UStreetMapComponent, StreetMap)) + { + bNeedRefreshCustomizationModule = true; + } + else if (IsCollisionProperty(PropertyName)) // For some unknown reason , GET_MEMBER_NAME_CHECKED(UStreetMapComponent, CollisionSettings) is not working ??? "TO CHECK LATER" + { + if (CollisionSettings.bGenerateCollision == true) + { + GenerateCollision(); + } + else + { + ClearCollision(); + } + bNeedRefreshCustomizationModule = true; + } + } + + if (bNeedRefreshCustomizationModule) + { + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + PropertyModule.NotifyCustomizationModuleChanged(); + } + + // Call the parent implementation of this function + Super::PostEditChangeProperty(PropertyChangedEvent); +} +#endif // WITH_EDITOR + + +void UStreetMapComponent::BuildMesh() +{ + // Wipes out our cached mesh data. Maybe unnecessary in case GenerateMesh is clearing cached mesh data and creating a new SceneProxy ! + InvalidateMesh(); + + GenerateMesh(); + + if (HasValidMesh()) + { + // We have a new bounding box + UpdateBounds(); + } + else + { + // No mesh was generated + } + + GenerateCollision(); + + // Mark our render state dirty so that CreateSceneProxy can refresh it on demand + MarkRenderStateDirty(); + + AssignDefaultMaterialIfNeeded(); + + Modify(); +} + + +void UStreetMapComponent::AssignDefaultMaterialIfNeeded() +{ + if (this->GetNumMaterials() == 0 || this->GetMaterial(0) == nullptr) + { + if (!HasValidMesh() || GetDefaultMaterial() == nullptr) + return; + + this->SetMaterial(0, GetDefaultMaterial()); + } +} + + +void UStreetMapComponent::UpdateNavigationIfNeeded() +{ + if (bCanEverAffectNavigation || bNavigationRelevant) + { + UNavigationSystem::UpdateComponentInNavOctree(*this); + } +} + +void UStreetMapComponent::InvalidateMesh() +{ + Vertices.Reset(); + Indices.Reset(); + CachedLocalBounds = FBoxSphereBounds(FBox(ForceInit)); + ClearCollision(); + // Mark our render state dirty so that CreateSceneProxy can refresh it on demand + MarkRenderStateDirty(); + Modify(); +} + +FBoxSphereBounds UStreetMapComponent::CalcBounds( const FTransform& LocalToWorld ) const +{ + if( HasValidMesh() ) + { + FBoxSphereBounds WorldSpaceBounds = CachedLocalBounds.TransformBy( LocalToWorld ); + WorldSpaceBounds.BoxExtent *= BoundsScale; + WorldSpaceBounds.SphereRadius *= BoundsScale; + return WorldSpaceBounds; + } + else + { + return FBoxSphereBounds( LocalToWorld.GetLocation(), FVector::ZeroVector, 0.0f ); + } +} + + +void UStreetMapComponent::AddThick2DLine( const FVector2D Start, const FVector2D End, const float Z, const float Thickness, const FColor& StartColor, const FColor& EndColor, FBox& MeshBoundingBox ) +{ + const float HalfThickness = Thickness * 0.5f; + + const FVector2D LineDirection = ( End - Start ).GetSafeNormal(); + const FVector2D RightVector( -LineDirection.Y, LineDirection.X ); + + const int32 BottomLeftVertexIndex = Vertices.Num(); + FStreetMapVertex& BottomLeftVertex = *new( Vertices )FStreetMapVertex(); + BottomLeftVertex.Position = FVector( Start - RightVector * HalfThickness, Z ); + BottomLeftVertex.TextureCoordinate = FVector2D( 0.0f, 0.0f ); + BottomLeftVertex.TangentX = FVector( LineDirection, 0.0f ); + BottomLeftVertex.TangentZ = FVector::UpVector; + BottomLeftVertex.Color = StartColor; + MeshBoundingBox += BottomLeftVertex.Position; + + const int32 BottomRightVertexIndex = Vertices.Num(); + FStreetMapVertex& BottomRightVertex = *new( Vertices )FStreetMapVertex(); + BottomRightVertex.Position = FVector( Start + RightVector * HalfThickness, Z ); + BottomRightVertex.TextureCoordinate = FVector2D( 1.0f, 0.0f ); + BottomRightVertex.TangentX = FVector( LineDirection, 0.0f ); + BottomRightVertex.TangentZ = FVector::UpVector; + BottomRightVertex.Color = StartColor; + MeshBoundingBox += BottomRightVertex.Position; + + const int32 TopRightVertexIndex = Vertices.Num(); + FStreetMapVertex& TopRightVertex = *new( Vertices )FStreetMapVertex(); + TopRightVertex.Position = FVector( End + RightVector * HalfThickness, Z ); + TopRightVertex.TextureCoordinate = FVector2D( 1.0f, 1.0f ); + TopRightVertex.TangentX = FVector( LineDirection, 0.0f ); + TopRightVertex.TangentZ = FVector::UpVector; + TopRightVertex.Color = EndColor; + MeshBoundingBox += TopRightVertex.Position; + + const int32 TopLeftVertexIndex = Vertices.Num(); + FStreetMapVertex& TopLeftVertex = *new( Vertices )FStreetMapVertex(); + TopLeftVertex.Position = FVector( End - RightVector * HalfThickness, Z ); + TopLeftVertex.TextureCoordinate = FVector2D( 0.0f, 1.0f ); + TopLeftVertex.TangentX = FVector( LineDirection, 0.0f ); + TopLeftVertex.TangentZ = FVector::UpVector; + TopLeftVertex.Color = EndColor; + MeshBoundingBox += TopLeftVertex.Position; + + Indices.Add( BottomLeftVertexIndex ); + Indices.Add( BottomRightVertexIndex ); + Indices.Add( TopRightVertexIndex ); + + Indices.Add( BottomLeftVertexIndex ); + Indices.Add( TopRightVertexIndex ); + Indices.Add( TopLeftVertexIndex ); +}; + + +void UStreetMapComponent::AddTriangles( const TArray& Points, const TArray& PointIndices, const FVector& ForwardVector, const FVector& UpVector, const FColor& Color, FBox& MeshBoundingBox ) +{ + const int32 FirstVertexIndex = Vertices.Num(); + + for( FVector Point : Points ) + { + FStreetMapVertex& NewVertex = *new( Vertices )FStreetMapVertex(); + NewVertex.Position = Point; + NewVertex.TextureCoordinate = FVector2D( 0.0f, 0.0f ); // NOTE: We're not using texture coordinates for anything yet + NewVertex.TangentX = ForwardVector; + NewVertex.TangentZ = UpVector; + NewVertex.Color = Color; + + MeshBoundingBox += NewVertex.Position; + } + + for( int32 PointIndex : PointIndices ) + { + Indices.Add( FirstVertexIndex + PointIndex ); + } +}; + + +FString UStreetMapComponent::GetStreetMapAssetName() const +{ + return StreetMap != nullptr ? StreetMap->GetName() : FString(TEXT("NONE")); +} + diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapRuntime.Build.cs b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapRuntime.Build.cs new file mode 100644 index 0000000..7203c3a --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapRuntime.Build.cs @@ -0,0 +1,23 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class StreetMapRuntime : ModuleRules + { + public StreetMapRuntime(ReadOnlyTargetRules Target) + : base(Target) + { + PrivateDependencyModuleNames.AddRange( + new string[] { + "Core", + "CoreUObject", + "Engine", + "RHI", + "RenderCore", + "ShaderCore", + "PropertyEditor" + } + ); + } + } +} \ No newline at end of file diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapRuntime.cpp b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapRuntime.cpp new file mode 100644 index 0000000..1b41c09 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapRuntime.cpp @@ -0,0 +1,31 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#include "StreetMapRuntime.h" +#include "ModuleManager.h" + + +class FStreetMapRuntimeModule : public IModuleInterface +{ + +public: + + // IModuleInterface interface + virtual void StartupModule() override; + virtual void ShutdownModule() override; + +}; + + +IMPLEMENT_MODULE( FStreetMapRuntimeModule, StreetMapRuntime ) + + + +void FStreetMapRuntimeModule::StartupModule() +{ +} + + +void FStreetMapRuntimeModule::ShutdownModule() +{ +} + diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapRuntime.h b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapRuntime.h new file mode 100644 index 0000000..cf30db0 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapRuntime.h @@ -0,0 +1,7 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. +#pragma once + +#include "EngineMinimal.h" +#include "Classes/Engine/Engine.h" // For UEngine +#include "EngineGlobals.h" // For GEngine + diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapSceneProxy.cpp b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapSceneProxy.cpp new file mode 100644 index 0000000..e49de13 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapSceneProxy.cpp @@ -0,0 +1,280 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. + +#include "StreetMapRuntime.h" +#include "StreetMapSceneProxy.h" +#include "StreetMapComponent.h" +#include "Runtime/Engine/Public/SceneManagement.h" + + +void FStreetMapVertexBuffer::InitRHI() +{ + if( Vertices.Num() > 0 ) + { + // Allocate our vertex buffer + FRHIResourceCreateInfo CreateInfo; + VertexBufferRHI = RHICreateVertexBuffer( Vertices.Num() * sizeof( Vertices[0] ), BUF_Static, CreateInfo ); + + // Load the vertex buffer with data + void* VertexBufferData = RHILockVertexBuffer( VertexBufferRHI, 0, Vertices.Num() * sizeof( Vertices[0] ), RLM_WriteOnly ); + FMemory::Memcpy( VertexBufferData, Vertices.GetData(), Vertices.Num() * sizeof( FStreetMapVertex ) ); + RHIUnlockVertexBuffer( VertexBufferRHI ); + } +} + + +void FStreetMapIndexBuffer::InitRHI() +{ + const int IndexCount = FMath::Max( Indices16.Num(), Indices32.Num() ); + if( IndexCount > 0 ) + { + const bool b32BitIndices = Indices32.Num() > Indices16.Num(); + const uint8 IndexSize = b32BitIndices ? sizeof( Indices32[ 0 ] ) : sizeof( Indices16[ 0 ] ); + const void* IndexSourceData; + if( b32BitIndices ) + { + IndexSourceData = Indices32.GetData(); + } + else + { + IndexSourceData = Indices16.GetData(); + } + + // Allocate our index buffer and load it with data + FRHIResourceCreateInfo CreateInfo; + IndexBufferRHI = RHICreateIndexBuffer( IndexSize, IndexCount * IndexSize, BUF_Static, CreateInfo ); + void* IndexBufferData = RHILockIndexBuffer( IndexBufferRHI, 0, IndexCount * IndexSize, RLM_WriteOnly ); + FMemory::Memcpy( IndexBufferData, IndexSourceData, IndexCount * IndexSize ); + RHIUnlockIndexBuffer( IndexBufferRHI ); + } +} + + +void FStreetMapVertexFactory::InitVertexFactory( const FStreetMapVertexBuffer& VertexBuffer ) +{ + // Setup the vertex factory streams + FDataType DataType; + DataType.PositionComponent = STRUCTMEMBER_VERTEXSTREAMCOMPONENT( &VertexBuffer, FStreetMapVertex, Position, VET_Float3 ); + DataType.TextureCoordinates.Add( STRUCTMEMBER_VERTEXSTREAMCOMPONENT( &VertexBuffer, FStreetMapVertex, TextureCoordinate, VET_Float2 ) ); + DataType.TangentBasisComponents[0] = STRUCTMEMBER_VERTEXSTREAMCOMPONENT( &VertexBuffer, FStreetMapVertex, TangentX, VET_PackedNormal); + DataType.TangentBasisComponents[1] = STRUCTMEMBER_VERTEXSTREAMCOMPONENT( &VertexBuffer, FStreetMapVertex, TangentZ, VET_PackedNormal); + DataType.ColorComponent = STRUCTMEMBER_VERTEXSTREAMCOMPONENT( &VertexBuffer, FStreetMapVertex, Color, VET_Color ); + + // Send it off to the rendering thread + check( !IsInActualRenderingThread() ); + ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER( + InitStreetMapVertexFactory, + FStreetMapVertexFactory*, VertexFactory, this, + FDataType, DataType, DataType, + { + VertexFactory->SetData( DataType ); + }); +} + + +FStreetMapSceneProxy::FStreetMapSceneProxy(const UStreetMapComponent* InComponent) + : FPrimitiveSceneProxy(InComponent), + StreetMapComp(InComponent), + CollisionResponse(InComponent->GetCollisionResponseToChannels()) +{ + +} + + +void FStreetMapSceneProxy::Init( const UStreetMapComponent* InComponent, const TArray< FStreetMapVertex >& Vertices, const TArray< uint16 >& Indices ) +{ + // Copy 16-bit index data + IndexBuffer.Indices16 = Indices; + + InitAfterIndexBuffer( InComponent, Vertices ); +} + + +void FStreetMapSceneProxy::Init( const UStreetMapComponent* InComponent, const TArray< FStreetMapVertex >& Vertices, const TArray< uint32 >& Indices ) +{ + // If we fit into a 16-bit index buffer, just use that + if( Vertices.Num() < 0xffff ) + { + const int32 IndexCount = Indices.Num(); + IndexBuffer.Indices16.AddUninitialized( IndexCount ); + for( int32 Index = 0; Index < IndexCount; ++Index ) + { + IndexBuffer.Indices16[ Index ] = Indices[ Index ]; + } + } + else + { + // Copy 32-bit index data + IndexBuffer.Indices32 = Indices; + } + + InitAfterIndexBuffer( InComponent, Vertices ); +} + +void FStreetMapSceneProxy::InitAfterIndexBuffer( const UStreetMapComponent* StreetMapComponent, const TArray< FStreetMapVertex >& Vertices ) +{ + MaterialInterface = nullptr; + this->MaterialRelevance = StreetMapComponent->GetMaterialRelevance( GetScene().GetFeatureLevel() ); + + // Copy vertex data + VertexBuffer.Vertices = Vertices; + InitResources(); + + // Set a material + { + if( StreetMapComponent->GetNumMaterials() > 0 ) + { + MaterialInterface = StreetMapComponent->GetMaterial( 0 ); + } + + // Use the default material if we don't have one set + if( MaterialInterface == nullptr ) + { + MaterialInterface = UMaterial::GetDefaultMaterial( MD_Surface ); + } + } +} + + + +FStreetMapSceneProxy::~FStreetMapSceneProxy() +{ + VertexBuffer.ReleaseResource(); + IndexBuffer.ReleaseResource(); + VertexFactory.ReleaseResource(); +} + + +void FStreetMapSceneProxy::InitResources() +{ + // Start initializing our vertex buffer, index buffer, and vertex factory. This will be kicked off on the render thread. + BeginInitResource( &VertexBuffer ); + BeginInitResource( &IndexBuffer ); + + VertexFactory.InitVertexFactory( VertexBuffer ); + BeginInitResource( &VertexFactory ); +} + + +bool FStreetMapSceneProxy::MustDrawMeshDynamically( const FSceneView& View ) const +{ + return ( AllowDebugViewmodes() && View.Family->EngineShowFlags.Wireframe ) || IsSelected(); +} + + +bool FStreetMapSceneProxy::IsInCollisionView(const FEngineShowFlags& EngineShowFlags) const +{ + return EngineShowFlags.CollisionVisibility || EngineShowFlags.CollisionPawn; +} + +FPrimitiveViewRelevance FStreetMapSceneProxy::GetViewRelevance( const FSceneView* View ) const +{ + FPrimitiveViewRelevance Result; + Result.bDrawRelevance = IsShown(View); + Result.bShadowRelevance = IsShadowCast(View); + + const bool bAlwaysHasDynamicData = false; + + // Only draw dynamically if we're drawing in wireframe or we're selected in the editor + Result.bDynamicRelevance = MustDrawMeshDynamically( *View ) || bAlwaysHasDynamicData; + Result.bStaticRelevance = !MustDrawMeshDynamically( *View ); + + MaterialRelevance.SetPrimitiveViewRelevance(Result); + return Result; +} + + +bool FStreetMapSceneProxy::CanBeOccluded() const +{ + return !MaterialRelevance.bDisableDepthTest; +} + + +void FStreetMapSceneProxy::MakeMeshBatch( FMeshBatch& Mesh, FMaterialRenderProxy* WireframeMaterialRenderProxyOrNull, bool bDrawCollision) const +{ + FMaterialRenderProxy* MaterialProxy = NULL; + if( WireframeMaterialRenderProxyOrNull != nullptr ) + { + MaterialProxy = WireframeMaterialRenderProxyOrNull; + } + else + { + if (bDrawCollision) + { + MaterialProxy = new FColoredMaterialRenderProxy(GEngine->ShadedLevelColorationUnlitMaterial->GetRenderProxy(IsSelected(), IsHovered()), FColor::Cyan); + } + else if (MaterialProxy == nullptr) + { + MaterialProxy = StreetMapComp->GetDefaultMaterial()->GetRenderProxy(IsSelected()); + } + } + + FMeshBatchElement& BatchElement = Mesh.Elements[0]; + BatchElement.IndexBuffer = &IndexBuffer; + Mesh.bWireframe = WireframeMaterialRenderProxyOrNull != nullptr; + Mesh.VertexFactory = &VertexFactory; + Mesh.MaterialRenderProxy = MaterialProxy; + Mesh.CastShadow = true; + BatchElement.PrimitiveUniformBuffer = CreatePrimitiveUniformBufferImmediate(GetLocalToWorld(), GetBounds(), GetLocalBounds(), true, UseEditorDepthTest()); + BatchElement.FirstIndex = 0; + const int IndexCount = FMath::Max( IndexBuffer.Indices16.Num(), IndexBuffer.Indices32.Num() ); + BatchElement.NumPrimitives = IndexCount / 3; + BatchElement.MinVertexIndex = 0; + BatchElement.MaxVertexIndex = VertexBuffer.Vertices.Num() - 1; + Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative(); + Mesh.Type = PT_TriangleList; + Mesh.DepthPriorityGroup = SDPG_World; +} + + +void FStreetMapSceneProxy::DrawStaticElements( FStaticPrimitiveDrawInterface* PDI ) +{ + const int IndexCount = FMath::Max( IndexBuffer.Indices16.Num(), IndexBuffer.Indices32.Num() ); + if( VertexBuffer.Vertices.Num() > 0 && IndexCount > 0 ) + { + const float ScreenSize = 1.0f; + + FMeshBatch MeshBatch; + MakeMeshBatch( MeshBatch, nullptr); + PDI->DrawMesh( MeshBatch, ScreenSize ); + } +} + + +void FStreetMapSceneProxy::GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, class FMeshElementCollector& Collector) const +{ + const int IndexCount = FMath::Max(IndexBuffer.Indices16.Num(), IndexBuffer.Indices32.Num()); + if (VertexBuffer.Vertices.Num() > 0 && IndexCount > 0) + { + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex) + { + const FSceneView& View = *Views[ViewIndex]; + + const bool bIsWireframe = AllowDebugViewmodes() && View.Family->EngineShowFlags.Wireframe; + + FColoredMaterialRenderProxy* WireframeMaterialRenderProxy = GEngine->WireframeMaterial && bIsWireframe ? new FColoredMaterialRenderProxy(GEngine->WireframeMaterial->GetRenderProxy(IsSelected()), FLinearColor(0, 0.5f, 1.f)) : NULL; + + + if (MustDrawMeshDynamically(View)) + { + const bool bInCollisionView = IsInCollisionView(ViewFamily.EngineShowFlags); + const bool bCanDrawCollision = bInCollisionView && IsCollisionEnabled(); + + if (!IsCollisionEnabled() && bInCollisionView) + { + continue; + } + + // Draw the mesh! + FMeshBatch& MeshBatch = Collector.AllocateMesh(); + MakeMeshBatch(MeshBatch, WireframeMaterialRenderProxy, bCanDrawCollision); + Collector.AddMesh(ViewIndex, MeshBatch); + } + } + } +} + + +uint32 FStreetMapSceneProxy::GetMemoryFootprint( void ) const +{ + return sizeof( *this ) + GetAllocatedSize(); +} diff --git a/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapSceneProxy.h b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapSceneProxy.h new file mode 100644 index 0000000..feb17f5 --- /dev/null +++ b/Plugins/StreetMap/Source/StreetMapRuntime/StreetMapSceneProxy.h @@ -0,0 +1,180 @@ +// Copyright 2017 Mike Fricker. All Rights Reserved. +#pragma once + +#include "Runtime/Engine/Public/PrimitiveSceneProxy.h" +#include "Runtime/Engine/Public/LocalVertexFactory.h" +#include "StreetMapSceneProxy.generated.h" + +/** A single vertex on a street map mesh */ +USTRUCT() +struct FStreetMapVertex +{ + + GENERATED_USTRUCT_BODY() + + /** Location of the vertex in local space */ + UPROPERTY() + FVector Position; + + /** Texture coordinate */ + UPROPERTY() + FVector2D TextureCoordinate; + + /** Tangent vector X */ + UPROPERTY() + FVector TangentX; + + /** Tangent vector Z (normal) */ + UPROPERTY() + FVector TangentZ; + + /** Color */ + UPROPERTY() + FColor Color; + + + /** Default constructor, leaves everything uninitialized */ + FStreetMapVertex() + { + } + + /** Construct with a supplied position and tangents for the vertex */ + FStreetMapVertex(const FVector InitLocation, const FVector2D InitTextureCoordinate, const FVector InitTangentX, const FVector InitTangentZ, const FColor InitColor) + : Position(InitLocation), + TextureCoordinate(InitTextureCoordinate), + TangentX(InitTangentX), + TangentZ(InitTangentZ), + Color(InitColor) + { + } +}; + + +/** Street map mesh vertex buffer */ +class FStreetMapVertexBuffer : public FVertexBuffer +{ + +public: + + /** All of the vertices in this mesh */ + TArray< FStreetMapVertex > Vertices; + + + // FRenderResource interface + virtual void InitRHI() override; +}; + + +/** Street map mesh index buffer */ +class FStreetMapIndexBuffer : public FIndexBuffer +{ + +public: + + /** 16-bit indices */ + TArray< uint16 > Indices16; + + /** 32-bit indices */ + TArray< uint32 > Indices32; + + + // FRenderResource interface + virtual void InitRHI() override; +}; + + +/** Street map mesh vertex factory */ +class FStreetMapVertexFactory : public FLocalVertexFactory +{ + +public: + + /** Initialize this vertex factory */ + void InitVertexFactory( const FStreetMapVertexBuffer& VertexBuffer ); +}; + + +/** Scene proxy for rendering a section of a street map mesh on the rendering thread */ +class FStreetMapSceneProxy : public FPrimitiveSceneProxy +{ + +public: + + /** Construct this scene proxy */ + FStreetMapSceneProxy(const class UStreetMapComponent* InComponent); + + /** + * Init this street map mesh scene proxy for the specified component (32-bit indices) + * + * @param InComponent The street map mesh component to initialize this with + * @param Vertices The vertices for this street map mesh + * @param Indices The vertex indices for this street map mesh + */ + void Init(const UStreetMapComponent* InComponent, const TArray< FStreetMapVertex >& Vertices, const TArray< uint32 >& Indices); + + /** + * Init this street map mesh scene proxy for the specified component (16-bit indices) + * + * @param InComponent The street map mesh component to initialize this with + * @param Vertices The vertices for this street map mesh + * @param Indices The vertex indices for this street map mesh + */ + void Init(const UStreetMapComponent* InComponent, const TArray< FStreetMapVertex >& Vertices, const TArray< uint16 >& Indices); + + /** Destructor that cleans up our rendering data */ + virtual ~FStreetMapSceneProxy(); + + +protected: + + /** Called from the constructor to finish construction after the index buffer is setup */ + void InitAfterIndexBuffer(const class UStreetMapComponent* StreetMapComponent, const TArray< FStreetMapVertex >& Vertices); + + /** Initializes this scene proxy's vertex buffer, index buffer and vertex factory (on the render thread.) */ + void InitResources(); + + /** Makes a MeshBatch for rendering. Called every time the mesh is drawn */ + void MakeMeshBatch(struct FMeshBatch& Mesh, class FMaterialRenderProxy* WireframeMaterialRenderProxyOrNull, bool bDrawCollision = false) const; + + /** Checks to see if this mesh must be drawn during the dynamic pass. Note that even when this returns false, we may still + have other (debug) geometry to render as dynamic */ + bool MustDrawMeshDynamically(const class FSceneView& View) const; + + /** Returns true , if in a collision view */ + bool IsInCollisionView(const FEngineShowFlags& EngineShowFlags) const; + + // FPrimitiveSceneProxy interface + virtual void DrawStaticElements(class FStaticPrimitiveDrawInterface* PDI) override; + virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, class FMeshElementCollector& Collector) const override; + virtual uint32 GetMemoryFootprint(void) const override; + virtual FPrimitiveViewRelevance GetViewRelevance(const class FSceneView* View) const override; + virtual bool CanBeOccluded() const override; + + + +protected: + + + + /** Contains all of the vertices in our street map mesh */ + FStreetMapVertexBuffer VertexBuffer; + + /** All of the vertex indices in our street map mesh */ + FStreetMapIndexBuffer IndexBuffer; + + /** Our vertex factory specific to street map meshes */ + FStreetMapVertexFactory VertexFactory; + + /** Cached material relevance */ + FMaterialRelevance MaterialRelevance; + + /** The material we'll use to render this street map mesh */ + class UMaterialInterface* MaterialInterface; + + const UStreetMapComponent* StreetMapComp; + + // The Collision Response of the component being proxied + FCollisionResponseContainer CollisionResponse; + + +}; diff --git a/Plugins/StreetMap/StreetMap.uplugin b/Plugins/StreetMap/StreetMap.uplugin new file mode 100644 index 0000000..e8f033c --- /dev/null +++ b/Plugins/StreetMap/StreetMap.uplugin @@ -0,0 +1,31 @@ +{ + "FileVersion" : 3, + "Version" : 1, + "VersionName" : "1.0", + "FriendlyName" : "Street Map", + "Description" : "Import and render OpenStreetMap data", + "Category" : "Importers", + "CreatedBy" : "Mike Fricker", + "CreatedByURL" : "http://twitter.com/mike_fricker", + "DocsURL" : "http://github.com/ue4plugins/StreetMap", + "MarketplaceURL" : "", + "SupportURL" : "", + "EnabledByDefault" : true, + "CanContainContent" : true, + "IsBetaVersion" : false, + "Installed" : false, + "Modules" : + [ + { + "Name" : "StreetMapRuntime", + "Type" : "Runtime", + "LoadingPhase" : "Default" + }, + + { + "Name" : "StreetMapImporting", + "Type" : "Editor", + "LoadingPhase" : "Default" + } + ] +} \ No newline at end of file diff --git a/Source/LifePlay.Target.cs b/Source/LifePlay.Target.cs new file mode 100644 index 0000000..c74f460 --- /dev/null +++ b/Source/LifePlay.Target.cs @@ -0,0 +1,14 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class LifePlayTarget : TargetRules +{ + public LifePlayTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Game; + + ExtraModuleNames.AddRange( new string[] { "LifePlay" } ); + } +} diff --git a/Source/LifePlay/LifePlay.Build.cs b/Source/LifePlay/LifePlay.Build.cs new file mode 100644 index 0000000..da64430 --- /dev/null +++ b/Source/LifePlay/LifePlay.Build.cs @@ -0,0 +1,23 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; + +public class LifePlay : ModuleRules +{ + public LifePlay(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); + + PrivateDependencyModuleNames.AddRange(new string[] { }); + + // Uncomment if you are using Slate UI + // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); + + // Uncomment if you are using online features + // PrivateDependencyModuleNames.Add("OnlineSubsystem"); + + // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true + } +} diff --git a/Source/LifePlay/LifePlay.cpp b/Source/LifePlay/LifePlay.cpp new file mode 100644 index 0000000..53b68d2 --- /dev/null +++ b/Source/LifePlay/LifePlay.cpp @@ -0,0 +1,6 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "LifePlay.h" +#include "Modules/ModuleManager.h" + +IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, LifePlay, "LifePlay" ); diff --git a/Source/LifePlay/LifePlay.h b/Source/LifePlay/LifePlay.h new file mode 100644 index 0000000..90aad9e --- /dev/null +++ b/Source/LifePlay/LifePlay.h @@ -0,0 +1,6 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" + diff --git a/Source/LifePlay/LifePlayGameModeBase.cpp b/Source/LifePlay/LifePlayGameModeBase.cpp new file mode 100644 index 0000000..83d8340 --- /dev/null +++ b/Source/LifePlay/LifePlayGameModeBase.cpp @@ -0,0 +1,7 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "LifePlayGameModeBase.h" + + + + diff --git a/Source/LifePlay/LifePlayGameModeBase.h b/Source/LifePlay/LifePlayGameModeBase.h new file mode 100644 index 0000000..3b4c3cb --- /dev/null +++ b/Source/LifePlay/LifePlayGameModeBase.h @@ -0,0 +1,20 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "LifePlayGameModeBase.generated.h" + +/** + * + */ +UCLASS() +class LIFEPLAY_API ALifePlayGameModeBase : public AGameModeBase +{ + GENERATED_BODY() + + + + +}; diff --git a/Source/LifePlayEditor.Target.cs b/Source/LifePlayEditor.Target.cs new file mode 100644 index 0000000..205e2bb --- /dev/null +++ b/Source/LifePlayEditor.Target.cs @@ -0,0 +1,14 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class LifePlayEditorTarget : TargetRules +{ + public LifePlayEditorTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Editor; + + ExtraModuleNames.AddRange( new string[] { "LifePlay" } ); + } +}