Merge remote-tracking branch 'origin/nestor' into xisco
This commit is contained in:
commit
bdfbf53670
|
@ -0,0 +1,32 @@
|
||||||
|
## CARLA 0.2.3
|
||||||
|
|
||||||
|
* Fixed rounding errors in HUD (100% was shown as 99%, 30 FPS as 29 FPS).
|
||||||
|
* Fixed crash when player goes out of road map.
|
||||||
|
* Fixed several issues related to the transform of the road map (wasn't working in CARLA_ORIGIN_1).
|
||||||
|
|
||||||
|
## CARLA 0.2.2
|
||||||
|
|
||||||
|
* Implemented signals for off-road and opposite lane invasion
|
||||||
|
* Fixed linking issues (use Unreal's libpng)
|
||||||
|
* Fixed memory leak in PNG compression
|
||||||
|
|
||||||
|
## CARLA 0.2.1
|
||||||
|
|
||||||
|
* Fixed the memory leak related to protobuf issues
|
||||||
|
* Fixed color shift in semantic segmentation and depth
|
||||||
|
* Added in-game timestamp (now sending both OS and in-game)
|
||||||
|
|
||||||
|
## CARLA 0.2.0
|
||||||
|
|
||||||
|
* Fixed Depth issues
|
||||||
|
* Added semantic segmentation
|
||||||
|
* Changed codification to PNG
|
||||||
|
* Camera configuration through config INI file
|
||||||
|
|
||||||
|
## CARLA 0.1.1
|
||||||
|
|
||||||
|
* Added build system for Windows and Linux
|
||||||
|
|
||||||
|
## CARLA 0.1.0
|
||||||
|
|
||||||
|
* Added basic functionality
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"FileVersion": 3,
|
"FileVersion": 3,
|
||||||
"Version": 1,
|
"Version": 1,
|
||||||
"VersionName": "0.2.1",
|
"VersionName": "0.2.3",
|
||||||
"FriendlyName": "CARLA",
|
"FriendlyName": "CARLA",
|
||||||
"Description": "",
|
"Description": "",
|
||||||
"Category": "Science",
|
"Category": "Science",
|
||||||
|
|
|
@ -3,6 +3,8 @@ CARLA UE4 Plugin
|
||||||
|
|
||||||
Plugin for Unreal Engine 4.
|
Plugin for Unreal Engine 4.
|
||||||
|
|
||||||
|
See [CHANGELOG](CHANGELOG.md).
|
||||||
|
|
||||||
Settings
|
Settings
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
|
|
@ -9,3 +9,14 @@ Other CARLA related command-line options
|
||||||
|
|
||||||
* `-carla-settings=<ini-file-path>` Load settings from the given INI file. See Example.CarlaSettings.ini.
|
* `-carla-settings=<ini-file-path>` Load settings from the given INI file. See Example.CarlaSettings.ini.
|
||||||
* `-world-port=<port-number>` Listen for client connections at <port-number>, write and read ports are set to <port-number>+1 and <port-number>+2 respectively.
|
* `-world-port=<port-number>` Listen for client connections at <port-number>, write and read ports are set to <port-number>+1 and <port-number>+2 respectively.
|
||||||
|
|
||||||
|
To activate semantic segmentation
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
In the config file `CarlaUE4/Saved/Config/LinuxNoEditor/Engine.ini`, add the
|
||||||
|
following
|
||||||
|
|
||||||
|
```
|
||||||
|
[/Script/Engine.RendererSettings]
|
||||||
|
r.CustomDepth=3
|
||||||
|
```
|
||||||
|
|
|
@ -4,6 +4,12 @@
|
||||||
#include "CityMapGenerator.h"
|
#include "CityMapGenerator.h"
|
||||||
|
|
||||||
#include "MapGen/GraphGenerator.h"
|
#include "MapGen/GraphGenerator.h"
|
||||||
|
#include "MapGen/RoadMap.h"
|
||||||
|
#include "Tagger.h"
|
||||||
|
|
||||||
|
#include "Components/InstancedStaticMeshComponent.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "Paths.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
@ -16,35 +22,59 @@
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
ACityMapGenerator::ACityMapGenerator(const FObjectInitializer& ObjectInitializer)
|
ACityMapGenerator::ACityMapGenerator(const FObjectInitializer& ObjectInitializer)
|
||||||
: Super(ObjectInitializer) {}
|
: Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
RoadMap = ObjectInitializer.CreateDefaultSubobject<URoadMap>(this, TEXT("RoadMap"));
|
||||||
|
}
|
||||||
|
|
||||||
ACityMapGenerator::~ACityMapGenerator() {}
|
ACityMapGenerator::~ACityMapGenerator() {}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// -- Map construction and update related methods ------------------------------
|
// -- Overriden from UObject ---------------------------------------------------
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
void ACityMapGenerator::UpdateMap() {
|
void ACityMapGenerator::PreSave(const ITargetPlatform *TargetPlatform)
|
||||||
|
{
|
||||||
|
if (bGenerateRoadMapOnSave) {
|
||||||
|
check(RoadMap != nullptr);
|
||||||
|
GenerateRoadMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Super::PreSave(TargetPlatform);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// -- Overriden from ACityMapMeshHolder ----------------------------------------
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
void ACityMapGenerator::UpdateMap()
|
||||||
|
{
|
||||||
UpdateSeeds();
|
UpdateSeeds();
|
||||||
GenerateGraph();
|
GenerateGraph();
|
||||||
if (bGenerateRoads) {
|
if (bGenerateRoads) {
|
||||||
GenerateRoads();
|
GenerateRoads();
|
||||||
}
|
}
|
||||||
|
if (bTriggerRoadMapGeneration) {
|
||||||
|
bTriggerRoadMapGeneration = false;
|
||||||
|
GenerateRoadMap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ACityMapGenerator::UpdateSeeds() {
|
// =============================================================================
|
||||||
|
// -- Map construction and update related methods ------------------------------
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
void ACityMapGenerator::UpdateSeeds()
|
||||||
|
{
|
||||||
if (!bUseFixedSeed) {
|
if (!bUseFixedSeed) {
|
||||||
bUseMultipleFixedSeeds = false;
|
|
||||||
FRandomStream randomStream;
|
FRandomStream randomStream;
|
||||||
randomStream.GenerateNewSeed();
|
randomStream.GenerateNewSeed();
|
||||||
Seed = randomStream.GetCurrentSeed();
|
Seed = randomStream.GetCurrentSeed();
|
||||||
}
|
}
|
||||||
if (!bUseMultipleFixedSeeds) {
|
|
||||||
RoadPlanningSeed = Seed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ACityMapGenerator::GenerateGraph() {
|
void ACityMapGenerator::GenerateGraph()
|
||||||
|
{
|
||||||
if ((MapSizeX < 5u) || (MapSizeY < 5u)) {
|
if ((MapSizeX < 5u) || (MapSizeY < 5u)) {
|
||||||
MapSizeX = 5u;
|
MapSizeX = 5u;
|
||||||
MapSizeY = 5u;
|
MapSizeY = 5u;
|
||||||
|
@ -54,7 +84,7 @@ void ACityMapGenerator::GenerateGraph() {
|
||||||
// Delete the dcel before the new one is created so indices are restored.
|
// Delete the dcel before the new one is created so indices are restored.
|
||||||
Dcel.Reset(nullptr);
|
Dcel.Reset(nullptr);
|
||||||
#endif // CARLA_ROAD_GENERATOR_EXTRA_LOG
|
#endif // CARLA_ROAD_GENERATOR_EXTRA_LOG
|
||||||
Dcel = MapGen::GraphGenerator::Generate(MapSizeX, MapSizeY, RoadPlanningSeed);
|
Dcel = MapGen::GraphGenerator::Generate(MapSizeX, MapSizeY, Seed);
|
||||||
UE_LOG(LogCarla, Log,
|
UE_LOG(LogCarla, Log,
|
||||||
TEXT("Generated DCEL with: { %d vertices, %d half-edges, %d faces }"),
|
TEXT("Generated DCEL with: { %d vertices, %d half-edges, %d faces }"),
|
||||||
Dcel->CountNodes(),
|
Dcel->CountNodes(),
|
||||||
|
@ -87,7 +117,8 @@ void ACityMapGenerator::GenerateGraph() {
|
||||||
#endif // CARLA_ROAD_GENERATOR_EXTRA_LOG
|
#endif // CARLA_ROAD_GENERATOR_EXTRA_LOG
|
||||||
}
|
}
|
||||||
|
|
||||||
void ACityMapGenerator::GenerateRoads() {
|
void ACityMapGenerator::GenerateRoads()
|
||||||
|
{
|
||||||
check(Dcel != nullptr);
|
check(Dcel != nullptr);
|
||||||
using Graph = MapGen::DoublyConnectedEdgeList;
|
using Graph = MapGen::DoublyConnectedEdgeList;
|
||||||
const Graph &graph = *Dcel;
|
const Graph &graph = *Dcel;
|
||||||
|
@ -157,3 +188,115 @@ void ACityMapGenerator::GenerateRoads() {
|
||||||
|
|
||||||
#undef ADD_INTERSECTION
|
#undef ADD_INTERSECTION
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find first component of type road (checking at its stencil value).
|
||||||
|
static bool LineTrace(
|
||||||
|
UWorld *World,
|
||||||
|
const FVector &Start,
|
||||||
|
const FVector &End,
|
||||||
|
FHitResult &HitResult)
|
||||||
|
{
|
||||||
|
TArray <FHitResult> OutHits;
|
||||||
|
static FName TraceTag = FName(TEXT("RoadTrace"));
|
||||||
|
const bool Success = World->LineTraceMultiByObjectType(
|
||||||
|
OutHits,
|
||||||
|
Start,
|
||||||
|
End,
|
||||||
|
FCollisionObjectQueryParams(ECollisionChannel::ECC_WorldDynamic),
|
||||||
|
FCollisionQueryParams(TraceTag, true));
|
||||||
|
|
||||||
|
if (Success) {
|
||||||
|
for (FHitResult &Item : OutHits) {
|
||||||
|
if (Item.Component->CustomDepthStencilValue == static_cast<uint8>(CityObjectLabel::Roads)) {
|
||||||
|
HitResult = Item;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACityMapGenerator::GenerateRoadMap()
|
||||||
|
{
|
||||||
|
UE_LOG(LogCarla, Log, TEXT("Generating road map..."));
|
||||||
|
|
||||||
|
auto World = GetWorld();
|
||||||
|
check(GetWorld() != nullptr);
|
||||||
|
check(RoadMap != nullptr);
|
||||||
|
|
||||||
|
ATagger::TagActorsInLevel(*GetWorld()); // We need the tags.
|
||||||
|
|
||||||
|
const float IntersectionSize = CityMapMeshTag::GetRoadIntersectionSize();
|
||||||
|
const uint32 Margin = IntersectionSize / 2u;
|
||||||
|
const float Offset = GetMapScale() * Margin;
|
||||||
|
|
||||||
|
const float CmPerPixel = GetMapScale() / static_cast<float>(PixelsPerMapUnit);
|
||||||
|
|
||||||
|
const uint32 SizeX = PixelsPerMapUnit * (MapSizeX + 2u * Margin);
|
||||||
|
const uint32 SizeY = PixelsPerMapUnit * (MapSizeY + 2u * Margin);
|
||||||
|
|
||||||
|
const FTransform &ActorTransform = GetActorTransform();
|
||||||
|
|
||||||
|
RoadMap->RoadMap.Empty();
|
||||||
|
for (uint32 PixelY = 0u; PixelY < SizeY; ++PixelY) {
|
||||||
|
for (uint32 PixelX = 0u; PixelX < SizeX; ++PixelX) {
|
||||||
|
const float X = static_cast<float>(PixelX) * CmPerPixel - Offset;
|
||||||
|
const float Y = static_cast<float>(PixelY) * CmPerPixel - Offset;
|
||||||
|
const FVector Start = ActorTransform.TransformPosition(FVector(X, Y, 50.0f));
|
||||||
|
const FVector End = ActorTransform.TransformPosition(FVector(X, Y, -50.0f));
|
||||||
|
|
||||||
|
bool Success = false;
|
||||||
|
|
||||||
|
// Do the ray tracing.
|
||||||
|
FHitResult Hit;
|
||||||
|
if (LineTrace(World, Start, End, Hit)) {
|
||||||
|
auto InstancedStaticMeshComponent = Cast<UInstancedStaticMeshComponent>(Hit.Component.Get());
|
||||||
|
if (InstancedStaticMeshComponent == nullptr) {
|
||||||
|
UE_LOG(LogCarla, Error, TEXT("Road component is not UInstancedStaticMeshComponent"));
|
||||||
|
} else {
|
||||||
|
FTransform InstanceTransform;
|
||||||
|
if (!InstancedStaticMeshComponent->GetInstanceTransform(Hit.Item, InstanceTransform, true)) {
|
||||||
|
UE_LOG(LogCarla, Error, TEXT("Failed to get instance's transform"));
|
||||||
|
} else {
|
||||||
|
RoadMap->AppendPixel(
|
||||||
|
GetTag(*InstancedStaticMeshComponent->GetStaticMesh()),
|
||||||
|
InstanceTransform,
|
||||||
|
bLeftHandTraffic);
|
||||||
|
Success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Success) {
|
||||||
|
RoadMap->AppendEmptyPixel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FVector MapOffset(-Offset, -Offset, 0.0f);
|
||||||
|
RoadMap->Set(SizeX, SizeY, 1.0f / CmPerPixel, ActorTransform.Inverse(), MapOffset);
|
||||||
|
const float MapSizeInMB = // Only map data, not the class itself.
|
||||||
|
static_cast<float>(sizeof(FRoadMapPixelData) * RoadMap->RoadMap.Num()) /
|
||||||
|
(1024.0f * 1024.0f);
|
||||||
|
UE_LOG(
|
||||||
|
LogCarla,
|
||||||
|
Log,
|
||||||
|
TEXT("Generated road map %dx%d (%.2fMB) with %.2f cm/pixel"),
|
||||||
|
RoadMap->GetWidth(),
|
||||||
|
RoadMap->GetHeight(),
|
||||||
|
MapSizeInMB,
|
||||||
|
CmPerPixel);
|
||||||
|
|
||||||
|
if (!RoadMap->IsValid()) {
|
||||||
|
UE_LOG(LogCarla, Error, TEXT("Error generating road map"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bSaveRoadMapToDisk) {
|
||||||
|
const FString MapName = World->GetMapName() + TEXT(".png");
|
||||||
|
const FString FilePath = FPaths::Combine(FPaths::GameSavedDir(), MapName);
|
||||||
|
RoadMap->SaveAsPNG(FilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
RoadMap->DrawDebugPixelsToLevel(GetWorld(), !bDrawDebugPixelsToLevel);
|
||||||
|
}
|
||||||
|
|
|
@ -7,10 +7,12 @@
|
||||||
#include "MapGen/GraphParser.h"
|
#include "MapGen/GraphParser.h"
|
||||||
#include "CityMapGenerator.generated.h"
|
#include "CityMapGenerator.generated.h"
|
||||||
|
|
||||||
|
class URoadMap;
|
||||||
|
|
||||||
/// Generates a random city using the meshes provided.
|
/// Generates a random city using the meshes provided.
|
||||||
///
|
///
|
||||||
/// @note At this point it only generates roads and sidewalks.
|
/// @note At this point it only generates roads and sidewalks.
|
||||||
UCLASS(HideCategories=(Rendering, Input))
|
UCLASS(HideCategories=(Input,Rendering,Actor))
|
||||||
class CARLA_API ACityMapGenerator : public ACityMapMeshHolder
|
class CARLA_API ACityMapGenerator : public ACityMapMeshHolder
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
@ -27,14 +29,42 @@ public:
|
||||||
|
|
||||||
/// @}
|
/// @}
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
/// @name Map construction and update related methods
|
/// @name Overriden from UObject
|
||||||
|
// ===========================================================================
|
||||||
|
/// @{
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual void PreSave(const ITargetPlatform *TargetPlatform) override;
|
||||||
|
|
||||||
|
/// @}
|
||||||
|
// ===========================================================================
|
||||||
|
/// @name Overriden from ACityMapMeshHolder
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
/// @{
|
/// @{
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/// Update the map based on the current settings.
|
|
||||||
virtual void UpdateMap() override;
|
virtual void UpdateMap() override;
|
||||||
|
|
||||||
|
/// @}
|
||||||
|
// ===========================================================================
|
||||||
|
/// @name Road map
|
||||||
|
// ===========================================================================
|
||||||
|
/// @{
|
||||||
|
public:
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable)
|
||||||
|
URoadMap *GetRoadMap()
|
||||||
|
{
|
||||||
|
return RoadMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @}
|
||||||
|
// ===========================================================================
|
||||||
|
/// @name Map construction and update related methods
|
||||||
|
// ===========================================================================
|
||||||
|
/// @{
|
||||||
|
private:
|
||||||
|
|
||||||
/// Update the random seeds. Generate random if no fixed seed is used.
|
/// Update the random seeds. Generate random if no fixed seed is used.
|
||||||
void UpdateSeeds();
|
void UpdateSeeds();
|
||||||
|
|
||||||
|
@ -44,6 +74,9 @@ private:
|
||||||
/// Add the road meshes to the scene based on the current DCEL.
|
/// Add the road meshes to the scene based on the current DCEL.
|
||||||
void GenerateRoads();
|
void GenerateRoads();
|
||||||
|
|
||||||
|
/// Generate the road map image and save to disk if requested.
|
||||||
|
void GenerateRoadMap();
|
||||||
|
|
||||||
/// @}
|
/// @}
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
/// @name Map generation properties
|
/// @name Map generation properties
|
||||||
|
@ -51,33 +84,76 @@ private:
|
||||||
/// @{
|
/// @{
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
/** Size X of the map in map units. The map unit is calculated based in the
|
||||||
|
* tile mesh of the road (see Map Scale).
|
||||||
|
*/
|
||||||
UPROPERTY(Category = "Map Generation", EditAnywhere, meta = (ClampMin = "10", ClampMax = "200"))
|
UPROPERTY(Category = "Map Generation", EditAnywhere, meta = (ClampMin = "10", ClampMax = "200"))
|
||||||
uint32 MapSizeX = 20u;
|
uint32 MapSizeX = 20u;
|
||||||
|
|
||||||
|
/** Size Y of the map in map units. The map unit is calculated based in the
|
||||||
|
* tile mesh of the road (see Map Scale).
|
||||||
|
*/
|
||||||
UPROPERTY(Category = "Map Generation", EditAnywhere, meta = (ClampMin = "10", ClampMax = "200"))
|
UPROPERTY(Category = "Map Generation", EditAnywhere, meta = (ClampMin = "10", ClampMax = "200"))
|
||||||
uint32 MapSizeY = 20u;
|
uint32 MapSizeY = 20u;
|
||||||
|
|
||||||
|
/** If false, no mesh is added, only the internal representation of road is
|
||||||
|
* generated.
|
||||||
|
*/
|
||||||
UPROPERTY(Category = "Map Generation", EditAnywhere)
|
UPROPERTY(Category = "Map Generation", EditAnywhere)
|
||||||
bool bGenerateRoads = true;
|
bool bGenerateRoads = true;
|
||||||
|
|
||||||
|
/** If false, a random seed is generated each time. */
|
||||||
UPROPERTY(Category = "Map Generation", EditAnywhere)
|
UPROPERTY(Category = "Map Generation", EditAnywhere)
|
||||||
bool bUseFixedSeed = true;
|
bool bUseFixedSeed = true;
|
||||||
|
|
||||||
|
/** Seed of the random map generated. */
|
||||||
UPROPERTY(Category = "Map Generation", EditAnywhere, meta = (EditCondition = bUseFixedSeed))
|
UPROPERTY(Category = "Map Generation", EditAnywhere, meta = (EditCondition = bUseFixedSeed))
|
||||||
int32 Seed = 123456789;
|
int32 Seed = 123456789;
|
||||||
|
|
||||||
/// @}
|
/// @}
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
/// @name Map generation properties - advance display
|
/// @name Road Map
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
/// @{
|
/// @{
|
||||||
private:
|
private:
|
||||||
|
|
||||||
UPROPERTY(Category = "Map Generation", EditAnywhere, AdvancedDisplay, meta = (EditCondition = bUseFixedSeed))
|
/** Trigger the generation a the road map image of the current layout (used
|
||||||
bool bUseMultipleFixedSeeds = false;
|
* for off-road and opposite lane invasion detection).
|
||||||
|
*/
|
||||||
|
UPROPERTY(Category = "Road Map", EditAnywhere)
|
||||||
|
bool bTriggerRoadMapGeneration = false;
|
||||||
|
|
||||||
UPROPERTY(Category = "Map Generation", EditAnywhere, AdvancedDisplay, meta = (EditCondition = bUseMultipleFixedSeeds))
|
/** The resolution in pixels per map unit of the road map. The map unit is
|
||||||
int32 RoadPlanningSeed;
|
* calculated based in the tile mesh of the road (see Map Scale).
|
||||||
|
*/
|
||||||
|
UPROPERTY(Category = "Road Map", EditAnywhere, meta = (ClampMin = "1", ClampMax = "500"))
|
||||||
|
uint32 PixelsPerMapUnit = 50u;
|
||||||
|
|
||||||
|
/** Whether the road map should be generated based on left-hand traffic. */
|
||||||
|
UPROPERTY(Category = "Road Map", EditAnywhere)
|
||||||
|
bool bLeftHandTraffic = false;
|
||||||
|
|
||||||
|
/** If true, the road map encoded as an image is saved to disk. The image is
|
||||||
|
* saved to the "Saved" folder of the project.
|
||||||
|
*/
|
||||||
|
UPROPERTY(Category = "Road Map", EditAnywhere)
|
||||||
|
bool bSaveRoadMapToDisk = true;
|
||||||
|
|
||||||
|
/** If true, a debug point is drawn in the level for each pixel of the road
|
||||||
|
* map.
|
||||||
|
*/
|
||||||
|
UPROPERTY(Category = "Road Map", EditAnywhere)
|
||||||
|
bool bDrawDebugPixelsToLevel = false;
|
||||||
|
|
||||||
|
/** The road map is re-computed on save so we always store an up-to-date
|
||||||
|
* version. Uncheck this only for testing purposes as the road map might get
|
||||||
|
* out-of-sync with the current road layout.
|
||||||
|
*/
|
||||||
|
UPROPERTY(Category = "Road Map", EditAnywhere, AdvancedDisplay)
|
||||||
|
bool bGenerateRoadMapOnSave = true;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
URoadMap *RoadMap;
|
||||||
|
|
||||||
/// @}
|
/// @}
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
|
@ -70,7 +70,7 @@ void ACarlaGameMode::RestartPlayer(AController* NewPlayer)
|
||||||
void ACarlaGameMode::BeginPlay()
|
void ACarlaGameMode::BeginPlay()
|
||||||
{
|
{
|
||||||
Super::BeginPlay();
|
Super::BeginPlay();
|
||||||
TagObjectsForSemanticSegmentation();
|
TagActorsForSemanticSegmentation();
|
||||||
GameController->BeginPlay();
|
GameController->BeginPlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,11 +102,10 @@ void ACarlaGameMode::AttachCaptureCamerasToPlayer(AController &Player)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ACarlaGameMode::TagObjectsForSemanticSegmentation()
|
void ACarlaGameMode::TagActorsForSemanticSegmentation()
|
||||||
{
|
{
|
||||||
auto Tagger = GetWorld()->SpawnActor<ATagger>();
|
check(GetWorld() != nullptr);
|
||||||
Tagger->TagObjects();
|
ATagger::TagActorsInLevel(*GetWorld());
|
||||||
Tagger->Destroy(); // We don't need you anymore.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
APlayerStart *ACarlaGameMode::FindUnOccupiedStartPoints(
|
APlayerStart *ACarlaGameMode::FindUnOccupiedStartPoints(
|
||||||
|
|
|
@ -35,7 +35,7 @@ private:
|
||||||
|
|
||||||
void AttachCaptureCamerasToPlayer(AController &Player);
|
void AttachCaptureCamerasToPlayer(AController &Player);
|
||||||
|
|
||||||
void TagObjectsForSemanticSegmentation();
|
void TagActorsForSemanticSegmentation();
|
||||||
|
|
||||||
/// Iterate all the APlayerStart present in the world and add the ones with
|
/// Iterate all the APlayerStart present in the world and add the ones with
|
||||||
/// unoccupied locations to @a UnOccupiedStartPoints.
|
/// unoccupied locations to @a UnOccupiedStartPoints.
|
||||||
|
@ -47,7 +47,9 @@ private:
|
||||||
|
|
||||||
CarlaGameControllerBase *GameController;
|
CarlaGameControllerBase *GameController;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
UCarlaGameInstance *GameInstance;
|
UCarlaGameInstance *GameInstance;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
AController *PlayerController;
|
AController *PlayerController;
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
static FText RoundedFloatAsText(float Value)
|
static FText RoundedFloatAsText(float Value)
|
||||||
{
|
{
|
||||||
return FText::AsNumber(FMath::FloorToInt(Value));
|
return FText::AsNumber(FMath::RoundHalfFromZero(Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
static FText GetVectorAsText(const FVector &Vector)
|
static FText GetVectorAsText(const FVector &Vector)
|
||||||
|
|
|
@ -3,10 +3,13 @@
|
||||||
#include "Carla.h"
|
#include "Carla.h"
|
||||||
#include "CarlaVehicleController.h"
|
#include "CarlaVehicleController.h"
|
||||||
|
|
||||||
|
#include "SceneCaptureCamera.h"
|
||||||
|
|
||||||
#include "Camera/CameraComponent.h"
|
#include "Camera/CameraComponent.h"
|
||||||
|
#include "Components/BoxComponent.h"
|
||||||
|
#include "EngineUtils.h"
|
||||||
#include "GameFramework/Pawn.h"
|
#include "GameFramework/Pawn.h"
|
||||||
#include "GameFramework/SpringArmComponent.h"
|
#include "GameFramework/SpringArmComponent.h"
|
||||||
#include "SceneCaptureCamera.h"
|
|
||||||
#include "WheeledVehicle.h"
|
#include "WheeledVehicle.h"
|
||||||
#include "WheeledVehicleMovementComponent.h"
|
#include "WheeledVehicleMovementComponent.h"
|
||||||
|
|
||||||
|
@ -82,6 +85,14 @@ void ACarlaVehicleController::Possess(APawn *aPawn)
|
||||||
// Get vehicle movement component.
|
// Get vehicle movement component.
|
||||||
MovementComponent = WheeledVehicle->GetVehicleMovementComponent();
|
MovementComponent = WheeledVehicle->GetVehicleMovementComponent();
|
||||||
check(MovementComponent != nullptr);
|
check(MovementComponent != nullptr);
|
||||||
|
// Get vehicle box component.
|
||||||
|
TArray<UBoxComponent *> BoundingBoxes;
|
||||||
|
WheeledVehicle->GetComponents<UBoxComponent>(BoundingBoxes);
|
||||||
|
if (BoundingBoxes.Num() > 0) {
|
||||||
|
VehicleBounds = BoundingBoxes[0];
|
||||||
|
} else {
|
||||||
|
UE_LOG(LogCarla, Error, TEXT("Pawn is missing the bounding box!"));
|
||||||
|
}
|
||||||
// Get custom player state.
|
// Get custom player state.
|
||||||
CarlaPlayerState = Cast<ACarlaPlayerState>(PlayerState);
|
CarlaPlayerState = Cast<ACarlaPlayerState>(PlayerState);
|
||||||
check(CarlaPlayerState != nullptr);
|
check(CarlaPlayerState != nullptr);
|
||||||
|
@ -111,6 +122,11 @@ void ACarlaVehicleController::BeginPlay()
|
||||||
Image.PostProcessEffect = Camera->GetPostProcessEffect();
|
Image.PostProcessEffect = Camera->GetPostProcessEffect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TActorIterator<ACityMapGenerator> It(GetWorld());
|
||||||
|
if (It) {
|
||||||
|
RoadMap = It->GetRoadMap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ACarlaVehicleController::Tick(float DeltaTime)
|
void ACarlaVehicleController::Tick(float DeltaTime)
|
||||||
|
@ -126,7 +142,7 @@ void ACarlaVehicleController::Tick(float DeltaTime)
|
||||||
const FVector CurrentSpeed = CarlaPlayerState->ForwardSpeed * CarlaPlayerState->Orientation;
|
const FVector CurrentSpeed = CarlaPlayerState->ForwardSpeed * CarlaPlayerState->Orientation;
|
||||||
CarlaPlayerState->Acceleration = (CurrentSpeed - PreviousSpeed) / DeltaTime;
|
CarlaPlayerState->Acceleration = (CurrentSpeed - PreviousSpeed) / DeltaTime;
|
||||||
CarlaPlayerState->CurrentGear = GetVehicleCurrentGear();
|
CarlaPlayerState->CurrentGear = GetVehicleCurrentGear();
|
||||||
/// @todo #15 Set intersection factors.
|
IntersectPlayerWithRoadMap();
|
||||||
const auto NumberOfCameras = SceneCaptureCameras.Num();
|
const auto NumberOfCameras = SceneCaptureCameras.Num();
|
||||||
check(NumberOfCameras == CarlaPlayerState->Images.Num());
|
check(NumberOfCameras == CarlaPlayerState->Images.Num());
|
||||||
for (auto i = 0; i < NumberOfCameras; ++i) {
|
for (auto i = 0; i < NumberOfCameras; ++i) {
|
||||||
|
@ -283,3 +299,27 @@ void ACarlaVehicleController::ChangeCameraRight(float Value)
|
||||||
Rotation.Yaw -= Value;
|
Rotation.Yaw -= Value;
|
||||||
SpringArm->SetRelativeRotation(Rotation);
|
SpringArm->SetRelativeRotation(Rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// -- Other --------------------------------------------------------------------
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
void ACarlaVehicleController::IntersectPlayerWithRoadMap()
|
||||||
|
{
|
||||||
|
if (RoadMap == nullptr) {
|
||||||
|
UE_LOG(LogCarla, Error, TEXT("Controller doesn't have a road map"));
|
||||||
|
return;
|
||||||
|
} else if (VehicleBounds == nullptr) {
|
||||||
|
UE_LOG(LogCarla, Error, TEXT("Vehicle doesn't have a bounding box"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr float ChecksPerCentimeter = 0.1f;
|
||||||
|
auto Result = RoadMap->Intersect(
|
||||||
|
GetPawn()->GetTransform(),
|
||||||
|
VehicleBounds->GetScaledBoxExtent(),
|
||||||
|
ChecksPerCentimeter);
|
||||||
|
|
||||||
|
CarlaPlayerState->OffRoadIntersectionFactor = Result.OffRoad;
|
||||||
|
CarlaPlayerState->OtherLaneIntersectionFactor = Result.OppositeLane;
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,9 @@
|
||||||
class ACarlaHUD;
|
class ACarlaHUD;
|
||||||
class ACarlaPlayerState;
|
class ACarlaPlayerState;
|
||||||
class ASceneCaptureCamera;
|
class ASceneCaptureCamera;
|
||||||
|
class UBoxComponent;
|
||||||
class UCameraComponent;
|
class UCameraComponent;
|
||||||
|
class URoadMap;
|
||||||
class USpringArmComponent;
|
class USpringArmComponent;
|
||||||
class UWheeledVehicleMovementComponent;
|
class UWheeledVehicleMovementComponent;
|
||||||
struct FCameraDescription;
|
struct FCameraDescription;
|
||||||
|
@ -160,6 +162,22 @@ private:
|
||||||
|
|
||||||
void SetupControllerInput();
|
void SetupControllerInput();
|
||||||
|
|
||||||
|
/// @}
|
||||||
|
// ===========================================================================
|
||||||
|
/// @name Other
|
||||||
|
// ===========================================================================
|
||||||
|
/// @{
|
||||||
|
public:
|
||||||
|
|
||||||
|
void SetRoadMap(URoadMap *inRoadMap)
|
||||||
|
{
|
||||||
|
RoadMap = inRoadMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void IntersectPlayerWithRoadMap();
|
||||||
|
|
||||||
/// @}
|
/// @}
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// -- Member variables -------------------------------------------------------
|
// -- Member variables -------------------------------------------------------
|
||||||
|
@ -181,6 +199,12 @@ private:
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TArray<ASceneCaptureCamera *> SceneCaptureCameras;
|
TArray<ASceneCaptureCamera *> SceneCaptureCameras;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
URoadMap *RoadMap;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
UBoxComponent *VehicleBounds;
|
||||||
|
|
||||||
// Cast for quick access to the custom player state.
|
// Cast for quick access to the custom player state.
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
ACarlaPlayerState *CarlaPlayerState;
|
ACarlaPlayerState *CarlaPlayerState;
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
|
||||||
|
|
||||||
#include "Carla.h"
|
|
||||||
#include "RoadMap.h"
|
|
||||||
|
|
||||||
#include "CapturedImage.h"
|
|
||||||
|
|
||||||
static FVector2D Decode(const FColor &Color);
|
|
||||||
|
|
||||||
// void RoadMap::SetCapture(FCapturedImage &inMapCapture, const FVector2D &inMapSizeInCm)
|
|
||||||
// {
|
|
||||||
// MapCapture = &inMapCapture;
|
|
||||||
// MapSizeInCm = inMapSizeInCm
|
|
||||||
// }
|
|
||||||
|
|
||||||
// bool RoadMap::IsValid() const
|
|
||||||
// {
|
|
||||||
// return ((MapCapture != nullptr) &&
|
|
||||||
// (MapCapture->BitMap.Num() > 0) &&
|
|
||||||
// (MapSizeInCm.X > 0.0f) &&
|
|
||||||
// (MapSizeInCm.Y > 0.0f));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// FVector2D RoadMap::GetDirectionAt(const FVector &Location) const
|
|
||||||
// {
|
|
||||||
// check(IsValid());
|
|
||||||
// return FVector2D();
|
|
||||||
// }
|
|
|
@ -1,29 +0,0 @@
|
||||||
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Math/Vector.h"
|
|
||||||
|
|
||||||
struct FCapturedImage;
|
|
||||||
|
|
||||||
/// Road map of the level. Contains information in 2D of which areas are road
|
|
||||||
/// and lane directions.
|
|
||||||
class CARLA_API RoadMap
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
void SetCapture(
|
|
||||||
const FCapturedImage &MapCapture,
|
|
||||||
const FVector2D &Origin,
|
|
||||||
const FVector2D &MapExtent);
|
|
||||||
|
|
||||||
bool IsValid() const;
|
|
||||||
|
|
||||||
FVector2D GetDirectionAt(const FVector &Location) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
uint32 GetPixelIndexAt(const FVector2D &Location) const;
|
|
||||||
|
|
||||||
TArray<FVector2D> DirectionMap;
|
|
||||||
};
|
|
|
@ -67,6 +67,9 @@ FVector ACityMapMeshHolder::GetTileLocation(uint32 X, uint32 Y) const
|
||||||
void ACityMapMeshHolder::SetStaticMesh(ECityMapMeshTag Tag, UStaticMesh *Mesh)
|
void ACityMapMeshHolder::SetStaticMesh(ECityMapMeshTag Tag, UStaticMesh *Mesh)
|
||||||
{
|
{
|
||||||
StaticMeshes[Tag] = Mesh;
|
StaticMeshes[Tag] = Mesh;
|
||||||
|
if (Mesh != nullptr) {
|
||||||
|
TagMap.Add(Mesh, Tag);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UStaticMesh *ACityMapMeshHolder::GetStaticMesh(ECityMapMeshTag Tag)
|
UStaticMesh *ACityMapMeshHolder::GetStaticMesh(ECityMapMeshTag Tag)
|
||||||
|
@ -79,6 +82,12 @@ const UStaticMesh *ACityMapMeshHolder::GetStaticMesh(ECityMapMeshTag Tag) const
|
||||||
return StaticMeshes[Tag];
|
return StaticMeshes[Tag];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ECityMapMeshTag ACityMapMeshHolder::GetTag(const UStaticMesh &StaticMesh) const
|
||||||
|
{
|
||||||
|
const ECityMapMeshTag *Tag = TagMap.Find(&StaticMesh);
|
||||||
|
return (Tag != nullptr ? *Tag : ECityMapMeshTag::INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
void ACityMapMeshHolder::AddInstance(ECityMapMeshTag Tag, uint32 X, uint32 Y)
|
void ACityMapMeshHolder::AddInstance(ECityMapMeshTag Tag, uint32 X, uint32 Y)
|
||||||
{
|
{
|
||||||
AddInstance(Tag, FTransform(GetTileLocation(X, Y)));
|
AddInstance(Tag, FTransform(GetTileLocation(X, Y)));
|
||||||
|
|
|
@ -38,6 +38,11 @@ protected:
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
float GetMapScale() const
|
||||||
|
{
|
||||||
|
return MapScale;
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the 3D world location (relative to this actor) of the given 2D
|
/// Return the 3D world location (relative to this actor) of the given 2D
|
||||||
/// tile.
|
/// tile.
|
||||||
FVector GetTileLocation(uint32 X, uint32 Y) const;
|
FVector GetTileLocation(uint32 X, uint32 Y) const;
|
||||||
|
@ -51,6 +56,9 @@ protected:
|
||||||
/// Return the static mesh corresponding to @a Tag.
|
/// Return the static mesh corresponding to @a Tag.
|
||||||
const UStaticMesh *GetStaticMesh(ECityMapMeshTag Tag) const;
|
const UStaticMesh *GetStaticMesh(ECityMapMeshTag Tag) const;
|
||||||
|
|
||||||
|
/// Return the tag corresponding to @a StaticMesh.
|
||||||
|
ECityMapMeshTag GetTag(const UStaticMesh &StaticMesh) const;
|
||||||
|
|
||||||
/// Add an instance of a mesh with a given tile location.
|
/// Add an instance of a mesh with a given tile location.
|
||||||
/// @param Tag The mesh' tag
|
/// @param Tag The mesh' tag
|
||||||
/// @param X Tile coordinate X
|
/// @param X Tile coordinate X
|
||||||
|
@ -89,12 +97,15 @@ private:
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
USceneComponent *SceneRootComponent;
|
USceneComponent *SceneRootComponent;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Map Generation", VisibleAnywhere)
|
||||||
|
float MapScale;
|
||||||
|
|
||||||
UPROPERTY(Category = "Meshes", EditAnywhere)
|
UPROPERTY(Category = "Meshes", EditAnywhere)
|
||||||
TMap<ECityMapMeshTag, UStaticMesh *> StaticMeshes;
|
TMap<ECityMapMeshTag, UStaticMesh *> StaticMeshes;
|
||||||
|
|
||||||
UPROPERTY(Category = "Meshes|Debug", VisibleAnywhere)
|
UPROPERTY()
|
||||||
float MapScale;
|
TMap<UStaticMesh *, ECityMapMeshTag> TagMap;
|
||||||
|
|
||||||
UPROPERTY(Category = "Meshes|Debug", VisibleAnywhere)
|
UPROPERTY(Category = "Meshes", VisibleAnywhere)
|
||||||
TArray<UInstancedStaticMeshComponent *> MeshInstatiators;
|
TArray<UInstancedStaticMeshComponent *> MeshInstatiators;
|
||||||
};
|
};
|
||||||
|
|
|
@ -46,7 +46,8 @@ enum class ECityMapMeshTag : uint8
|
||||||
RoadXIntersection_Sidewalk3 UMETA(DisplayName = "Road: X-Intersection - Sidewalk 3"),
|
RoadXIntersection_Sidewalk3 UMETA(DisplayName = "Road: X-Intersection - Sidewalk 3"),
|
||||||
RoadXIntersection_LaneMarking UMETA(DisplayName = "Road: X-Intersection - Lane Marking"),
|
RoadXIntersection_LaneMarking UMETA(DisplayName = "Road: X-Intersection - Lane Marking"),
|
||||||
|
|
||||||
NUMBER_OF_TAGS UMETA(Hidden)
|
NUMBER_OF_TAGS UMETA(Hidden),
|
||||||
|
INVALID UMETA(Hidden)
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Helper class for working with ECityMapMeshTag.
|
/// Helper class for working with ECityMapMeshTag.
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#include "Carla.h"
|
||||||
|
#include "RoadMap.h"
|
||||||
|
|
||||||
|
#include "DrawDebugHelpers.h"
|
||||||
|
#include "HighResScreenshot.h"
|
||||||
|
|
||||||
|
static uint32 ClampFloatToUInt(const float Value, int32 Min, int32 Max)
|
||||||
|
{
|
||||||
|
return FMath::Clamp(FMath::FloorToInt(Value), Min, Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a valid empty map (every point is off-road).
|
||||||
|
URoadMap::URoadMap(const FObjectInitializer& ObjectInitializer) :
|
||||||
|
Super(ObjectInitializer),
|
||||||
|
PixelsPerCentimeter(1.0f),
|
||||||
|
Width(1u),
|
||||||
|
Height(1u)
|
||||||
|
{
|
||||||
|
AppendEmptyPixel();
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector URoadMap::GetWorldLocation(uint32 PixelX, uint32 PixelY) const
|
||||||
|
{
|
||||||
|
const FVector RelativePosition(
|
||||||
|
static_cast<float>(PixelX) / PixelsPerCentimeter,
|
||||||
|
static_cast<float>(PixelY) / PixelsPerCentimeter,
|
||||||
|
0.0f);
|
||||||
|
return WorldToMap.InverseTransformPosition(RelativePosition + MapOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FRoadMapPixelData &URoadMap::GetDataAt(const FVector &WorldLocation) const
|
||||||
|
{
|
||||||
|
check(IsValid());
|
||||||
|
const FVector Location = WorldToMap.TransformPosition(WorldLocation) - MapOffset;
|
||||||
|
uint32 X = ClampFloatToUInt(PixelsPerCentimeter * Location.X, 0, Width - 1);
|
||||||
|
uint32 Y = ClampFloatToUInt(PixelsPerCentimeter * Location.Y, 0, Height - 1);
|
||||||
|
return GetDataAt(X, Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
FRoadMapIntersectionResult URoadMap::Intersect(
|
||||||
|
const FTransform &BoxTransform,
|
||||||
|
const FVector &BoxExtent,
|
||||||
|
float ChecksPerCentimeter) const
|
||||||
|
{
|
||||||
|
const auto &DirectionOfMovement = BoxTransform.GetRotation().GetForwardVector();
|
||||||
|
uint32 CheckCount = 0u;
|
||||||
|
FRoadMapIntersectionResult Result = {0.0f, 0.0f};
|
||||||
|
const float Step = 1.0f / ChecksPerCentimeter;
|
||||||
|
for (float X = -BoxExtent.X; X < BoxExtent.X; X += Step) {
|
||||||
|
for (float Y = -BoxExtent.Y; Y < BoxExtent.Y; Y += Step) {
|
||||||
|
++CheckCount;
|
||||||
|
auto Location = BoxTransform.TransformPosition(FVector(X, Y, 0.0f));
|
||||||
|
auto &Data = GetDataAt(Location);
|
||||||
|
if (Data.bIsOffRoad) {
|
||||||
|
Result.OffRoad += 1.0f;
|
||||||
|
} else if (Data.bHasDirection &&
|
||||||
|
0.0f < FVector::DotProduct(Data.Direction, DirectionOfMovement)) {
|
||||||
|
Result.OppositeLane += 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (CheckCount > 0u) {
|
||||||
|
Result.OffRoad /= static_cast<float>(CheckCount);
|
||||||
|
Result.OppositeLane /= static_cast<float>(CheckCount);
|
||||||
|
} else {
|
||||||
|
UE_LOG(LogCarla, Warning, TEXT("URoadMap::Intersect did zero checks"));
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FColor Encode(const FRoadMapPixelData &Data)
|
||||||
|
{
|
||||||
|
if (Data.bIsOffRoad) {
|
||||||
|
return FColor(0u, 0u, 0u, 255u);
|
||||||
|
} else if (!Data.bHasDirection) {
|
||||||
|
return FColor(255u, 255u, 255u, 255u);
|
||||||
|
} else {
|
||||||
|
// Assumes normalized direction.
|
||||||
|
auto ToColor = [](float X){
|
||||||
|
return FMath::FloorToInt(255.0 * (X + 1.0f) / 2.0f);
|
||||||
|
};
|
||||||
|
return FColor(ToColor(Data.Direction.X), ToColor(Data.Direction.Y), ToColor(Data.Direction.Z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URoadMap::SaveAsPNG(const FString &Path) const
|
||||||
|
{
|
||||||
|
if (!IsValid()) {
|
||||||
|
UE_LOG(LogCarla, Error, TEXT("Cannot save invalid road map to disk"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FColor> BitMap;
|
||||||
|
for (auto &Data : RoadMap) {
|
||||||
|
BitMap.Emplace(Encode(Data));
|
||||||
|
}
|
||||||
|
|
||||||
|
FIntPoint DestSize(Width, Height);
|
||||||
|
FString ResultPath;
|
||||||
|
FHighResScreenshotConfig& HighResScreenshotConfig = GetHighResScreenshotConfig();
|
||||||
|
HighResScreenshotConfig.SaveImage(Path, BitMap, DestSize, &ResultPath);
|
||||||
|
UE_LOG(LogCarla, Log, TEXT("Saved road map to \"%s\""), *ResultPath);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void URoadMap::DrawDebugPixelsToLevel(UWorld *World, const bool bJustFlushDoNotDraw) const
|
||||||
|
{
|
||||||
|
FlushPersistentDebugLines(World);
|
||||||
|
if (!bJustFlushDoNotDraw) {
|
||||||
|
for (auto X = 0u; X < Width; ++X) {
|
||||||
|
for (auto Y = 0u; Y < Height; ++Y) {
|
||||||
|
auto Location = GetWorldLocation(X, Y);
|
||||||
|
auto Color = Encode(GetDataAt(X, Y));
|
||||||
|
DrawDebugPoint(World, Location, 20.0f, Color, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void URoadMap::AppendPixel(
|
||||||
|
ECityMapMeshTag Tag,
|
||||||
|
const FTransform &Transform,
|
||||||
|
const bool bInvertDirection)
|
||||||
|
{
|
||||||
|
AppendEmptyPixel();
|
||||||
|
auto &Data = RoadMap.Last();
|
||||||
|
Data.bIsOffRoad = false;
|
||||||
|
|
||||||
|
auto Rotator = Transform.GetRotation().Rotator();
|
||||||
|
switch (Tag) {
|
||||||
|
case ECityMapMeshTag::RoadTwoLanes_LaneRight:
|
||||||
|
case ECityMapMeshTag::Road90DegTurn_Lane0:
|
||||||
|
Data.bHasDirection = true;
|
||||||
|
break;
|
||||||
|
case ECityMapMeshTag::RoadTwoLanes_LaneLeft:
|
||||||
|
case ECityMapMeshTag::Road90DegTurn_Lane1:
|
||||||
|
Rotator.Yaw += 180.0f;
|
||||||
|
Data.bHasDirection = true;
|
||||||
|
break;
|
||||||
|
case ECityMapMeshTag::Road90DegTurn_Lane2:
|
||||||
|
Rotator.Yaw += 90.0f;
|
||||||
|
Data.bHasDirection = true;
|
||||||
|
break;
|
||||||
|
case ECityMapMeshTag::Road90DegTurn_Lane3:
|
||||||
|
Rotator.Yaw += 270.0f;
|
||||||
|
Data.bHasDirection = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (Data.bHasDirection) {
|
||||||
|
FQuat Rotation(Rotator);
|
||||||
|
Data.Direction = Rotation.GetForwardVector();
|
||||||
|
if (bInvertDirection) {
|
||||||
|
Data.Direction *= -1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void URoadMap::Set(
|
||||||
|
const uint32 inWidth,
|
||||||
|
const uint32 inHeight,
|
||||||
|
const float inPinxelsPerCentimeter,
|
||||||
|
const FTransform &inWorldToMap,
|
||||||
|
const FVector &inMapOffset)
|
||||||
|
{
|
||||||
|
Width = inWidth;
|
||||||
|
Height = inHeight;
|
||||||
|
PixelsPerCentimeter = inPinxelsPerCentimeter;
|
||||||
|
WorldToMap = inWorldToMap;
|
||||||
|
MapOffset = inMapOffset;
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "UObject/NoExportTypes.h"
|
||||||
|
#include "RoadMap.generated.h"
|
||||||
|
|
||||||
|
USTRUCT()
|
||||||
|
struct FRoadMapIntersectionResult
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/** Percentage of the box lying off-road */
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||||
|
float OffRoad;
|
||||||
|
|
||||||
|
/** Percentage of the box invading opposite lane (wrong direction) */
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||||
|
float OppositeLane;
|
||||||
|
};
|
||||||
|
|
||||||
|
USTRUCT()
|
||||||
|
struct FRoadMapPixelData
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||||
|
bool bIsOffRoad = true;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||||
|
bool bHasDirection = false;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||||
|
FVector Direction;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Road map of the level. Contains information in 2D of which areas are road
|
||||||
|
/// and lane directions.
|
||||||
|
UCLASS()
|
||||||
|
class CARLA_API URoadMap : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
URoadMap(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable)
|
||||||
|
bool IsValid() const
|
||||||
|
{
|
||||||
|
return ((RoadMap.Num() > 0) && (RoadMap.Num() == Height * Width));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 GetWidth() const
|
||||||
|
{
|
||||||
|
return Width;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 GetHeight() const
|
||||||
|
{
|
||||||
|
return Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector GetWorldLocation(uint32 PixelX, uint32 PixelY) const;
|
||||||
|
|
||||||
|
const FRoadMapPixelData &GetDataAt(uint32 PixelX, uint32 PixelY) const
|
||||||
|
{
|
||||||
|
check(IsValid());
|
||||||
|
return RoadMap[PixelX + Width * PixelY];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clamps value if lies outside map limits */
|
||||||
|
UFUNCTION(BlueprintCallable)
|
||||||
|
const FRoadMapPixelData &GetDataAt(const FVector &WorldLocation) const;
|
||||||
|
|
||||||
|
/** Intersect actor bounds with map.
|
||||||
|
*
|
||||||
|
* Bounds box is projected to the map and checked against it for possible
|
||||||
|
* intersections with off-road areas and opposite lanes.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable)
|
||||||
|
FRoadMapIntersectionResult Intersect(
|
||||||
|
const FTransform &BoxTransform,
|
||||||
|
const FVector &BoxExtent,
|
||||||
|
float ChecksPerCentimeter) const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable)
|
||||||
|
bool SaveAsPNG(const FString &Path) const;
|
||||||
|
|
||||||
|
/** Draw every pixel of the image as debug point */
|
||||||
|
UFUNCTION(BlueprintCallable)
|
||||||
|
void DrawDebugPixelsToLevel(UWorld *World, bool bJustFlushDoNotDraw = false) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class ACityMapGenerator;
|
||||||
|
|
||||||
|
void AppendEmptyPixel()
|
||||||
|
{
|
||||||
|
RoadMap.AddDefaulted(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendPixel(
|
||||||
|
ECityMapMeshTag Tag,
|
||||||
|
const FTransform &Transform,
|
||||||
|
bool bInvertDirection);
|
||||||
|
|
||||||
|
void Set(
|
||||||
|
uint32 Width,
|
||||||
|
uint32 Height,
|
||||||
|
float PixelsPerCentimeter,
|
||||||
|
const FTransform &WorldToMap,
|
||||||
|
const FVector &MapOffset);
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere)
|
||||||
|
FTransform WorldToMap;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere)
|
||||||
|
FVector MapOffset;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere)
|
||||||
|
float PixelsPerCentimeter;
|
||||||
|
|
||||||
|
/** Width of the map in pixels */
|
||||||
|
UPROPERTY(VisibleAnywhere)
|
||||||
|
uint32 Width;
|
||||||
|
|
||||||
|
/** Height of the map in pixels */
|
||||||
|
UPROPERTY(VisibleAnywhere)
|
||||||
|
uint32 Height;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<FRoadMapPixelData> RoadMap;
|
||||||
|
};
|
|
@ -0,0 +1,57 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#include "Carla.h"
|
||||||
|
#include "StaticMeshCollection.h"
|
||||||
|
|
||||||
|
#include "Components/InstancedStaticMeshComponent.h"
|
||||||
|
#include "Engine/StaticMesh.h"
|
||||||
|
|
||||||
|
AStaticMeshCollection::AStaticMeshCollection(
|
||||||
|
const FObjectInitializer& ObjectInitializer) :
|
||||||
|
Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
PrimaryActorTick.bCanEverTick = false;
|
||||||
|
RootComponent =
|
||||||
|
ObjectInitializer.CreateDefaultSubobject<USceneComponent>(this, TEXT("SceneComponent"));
|
||||||
|
RootComponent->SetMobility(EComponentMobility::Static);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AStaticMeshCollection::PushBackInstantiator(UStaticMesh *Mesh)
|
||||||
|
{
|
||||||
|
auto Instantiator = NewObject<UInstancedStaticMeshComponent>(this);
|
||||||
|
check(Instantiator != nullptr);
|
||||||
|
Instantiator->SetMobility(EComponentMobility::Static);
|
||||||
|
Instantiator->SetupAttachment(RootComponent);
|
||||||
|
Instantiator->SetStaticMesh(Mesh);
|
||||||
|
Instantiator->RegisterComponent();
|
||||||
|
MeshInstantiators.Add(Instantiator);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AStaticMeshCollection::SetStaticMesh(uint32 i, UStaticMesh *Mesh)
|
||||||
|
{
|
||||||
|
if ((GetNumberOfInstantiators() > i) && (MeshInstantiators[i] != nullptr)) {
|
||||||
|
MeshInstantiators[i]->SetStaticMesh(Mesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AStaticMeshCollection::AddInstance(uint32 i, const FTransform &Transform)
|
||||||
|
{
|
||||||
|
if ((GetNumberOfInstantiators() > i) && (MeshInstantiators[i] != nullptr)) {
|
||||||
|
MeshInstantiators[i]->AddInstance(Transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AStaticMeshCollection::ClearInstances()
|
||||||
|
{
|
||||||
|
for (auto *Instantiator : MeshInstantiators) {
|
||||||
|
if (Instantiator != nullptr) {
|
||||||
|
Instantiator->ClearInstances();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AStaticMeshCollection::ClearInstantiators()
|
||||||
|
{
|
||||||
|
ClearInstances();
|
||||||
|
MeshInstantiators.Empty();
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "StaticMeshCollection.generated.h"
|
||||||
|
|
||||||
|
class UInstancedStaticMeshComponent;
|
||||||
|
|
||||||
|
/// Holds static mesh instatiators.
|
||||||
|
UCLASS(Abstract)
|
||||||
|
class CARLA_API AStaticMeshCollection : public AActor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
AStaticMeshCollection(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
uint32 GetNumberOfInstantiators() const
|
||||||
|
{
|
||||||
|
return MeshInstantiators.Num();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PushBackInstantiator(UStaticMesh *Mesh);
|
||||||
|
|
||||||
|
void SetStaticMesh(uint32 i, UStaticMesh *Mesh);
|
||||||
|
|
||||||
|
void AddInstance(uint32 i, const FTransform &Transform);
|
||||||
|
|
||||||
|
void ClearInstances();
|
||||||
|
|
||||||
|
/// Clear the instances too.
|
||||||
|
void ClearInstantiators();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Instanced Static Mesh Collection", VisibleAnywhere)
|
||||||
|
TArray<UInstancedStaticMeshComponent *> MeshInstantiators;
|
||||||
|
};
|
|
@ -0,0 +1,61 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#include "Carla.h"
|
||||||
|
#include "RoadIntersection.h"
|
||||||
|
|
||||||
|
ARoadIntersection::ARoadIntersection(const FObjectInitializer& ObjectInitializer) :
|
||||||
|
Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
PrimaryActorTick.bCanEverTick = false;
|
||||||
|
|
||||||
|
RootComponent =
|
||||||
|
ObjectInitializer.CreateDefaultSubobject<USceneComponent>(this, TEXT("RootComponent"));
|
||||||
|
RootComponent->SetMobility(EComponentMobility::Static);
|
||||||
|
|
||||||
|
#define CARLA_CREATE_STATIC_MESH_COMPONENT(Mesh) \
|
||||||
|
{ \
|
||||||
|
auto Component = CreateDefaultSubobject<UStaticMeshComponent>(TEXT(#Mesh) TEXT("Component")); \
|
||||||
|
Component->SetMobility(EComponentMobility::Static); \
|
||||||
|
Component->SetupAttachment(RootComponent); \
|
||||||
|
StaticMeshComponents.Add(Component); \
|
||||||
|
StaticMeshes.Add(ERoadIntersectionItem:: Mesh, nullptr); \
|
||||||
|
}
|
||||||
|
CARLA_CREATE_STATIC_MESH_COMPONENT(Lane0)
|
||||||
|
CARLA_CREATE_STATIC_MESH_COMPONENT(Lane1)
|
||||||
|
CARLA_CREATE_STATIC_MESH_COMPONENT(Lane2)
|
||||||
|
CARLA_CREATE_STATIC_MESH_COMPONENT(Lane3)
|
||||||
|
CARLA_CREATE_STATIC_MESH_COMPONENT(Sidewalk0)
|
||||||
|
CARLA_CREATE_STATIC_MESH_COMPONENT(Sidewalk1)
|
||||||
|
CARLA_CREATE_STATIC_MESH_COMPONENT(Sidewalk2)
|
||||||
|
CARLA_CREATE_STATIC_MESH_COMPONENT(Sidewalk3)
|
||||||
|
CARLA_CREATE_STATIC_MESH_COMPONENT(LaneMarking)
|
||||||
|
#undef CARLA_CREATE_STATIC_MESH_COMPONENT
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
void ARoadIntersection::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
||||||
|
{
|
||||||
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
||||||
|
if (PropertyChangedEvent.Property) {
|
||||||
|
UpdateMeshes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
|
||||||
|
void ARoadIntersection::SetStaticMesh(ERoadIntersectionItem Item, UStaticMesh *StaticMesh)
|
||||||
|
{
|
||||||
|
if (static_cast<uint8>(Item) < StaticMeshes.Num()) {
|
||||||
|
StaticMeshes[Item] = StaticMesh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARoadIntersection::UpdateMeshes()
|
||||||
|
{
|
||||||
|
check(StaticMeshes.Num() == StaticMeshComponents.Num());
|
||||||
|
int32 i = 0;
|
||||||
|
for (auto Item : StaticMeshes) {
|
||||||
|
check(StaticMeshComponents[i] != nullptr);
|
||||||
|
StaticMeshComponents[i]->SetStaticMesh(Item.Value);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "Components/StaticMeshComponent.h"
|
||||||
|
#include "RoadIntersection.generated.h"
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class ERoadIntersectionItem : uint8
|
||||||
|
{
|
||||||
|
Lane0 UMETA(DisplayName = "Lane 1"),
|
||||||
|
Lane1 UMETA(DisplayName = "Lane 2"),
|
||||||
|
Lane2 UMETA(DisplayName = "Lane 3"),
|
||||||
|
Lane3 UMETA(DisplayName = "Lane 4"),
|
||||||
|
Sidewalk0 UMETA(DisplayName = "Sidewalk 1"),
|
||||||
|
Sidewalk1 UMETA(DisplayName = "Sidewalk 2"),
|
||||||
|
Sidewalk2 UMETA(DisplayName = "Sidewalk 3"),
|
||||||
|
Sidewalk3 UMETA(DisplayName = "Sidewalk 4"),
|
||||||
|
LaneMarking UMETA(DisplayName = "LaneMarking"),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A road intersection.
|
||||||
|
UCLASS()
|
||||||
|
class CARLA_API ARoadIntersection : public AActor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
ARoadIntersection(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable)
|
||||||
|
void SetStaticMesh(ERoadIntersectionItem Item, UStaticMesh *StaticMesh);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void UpdateMeshes();
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<UStaticMeshComponent *> StaticMeshComponents;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Meshes", EditAnywhere)
|
||||||
|
TMap<ERoadIntersectionItem, UStaticMesh *> StaticMeshes;
|
||||||
|
};
|
|
@ -0,0 +1,84 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#include "Carla.h"
|
||||||
|
#include "RoadSegment.h"
|
||||||
|
|
||||||
|
#include "Engine/StaticMesh.h"
|
||||||
|
|
||||||
|
enum RoadSegmentItems {
|
||||||
|
ELaneLeft,
|
||||||
|
ELaneRight,
|
||||||
|
ESidewalkLeft,
|
||||||
|
ESidewalkRight,
|
||||||
|
ELaneMarkingSolid,
|
||||||
|
ELaneMarkingBroken,
|
||||||
|
NUMBER_OF_ITEMS
|
||||||
|
};
|
||||||
|
|
||||||
|
ARoadSegment::ARoadSegment(const FObjectInitializer& ObjectInitializer) :
|
||||||
|
Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
PrimaryActorTick.bCanEverTick = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARoadSegment::OnConstruction(const FTransform &Transform)
|
||||||
|
{
|
||||||
|
Super::OnConstruction(Transform);
|
||||||
|
UpdateMeshes();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
void ARoadSegment::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
||||||
|
{
|
||||||
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
||||||
|
if (PropertyChangedEvent.Property) {
|
||||||
|
GenerateRoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
|
||||||
|
void ARoadSegment::GenerateRoad()
|
||||||
|
{
|
||||||
|
UpdateMeshes();
|
||||||
|
UpdateRoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARoadSegment::UpdateMeshes()
|
||||||
|
{
|
||||||
|
if (GetNumberOfInstantiators() != NUMBER_OF_ITEMS) {
|
||||||
|
ClearInstantiators();
|
||||||
|
for (auto i = 0u; i < NUMBER_OF_ITEMS; ++i) {
|
||||||
|
PushBackInstantiator(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetStaticMesh(ELaneLeft, LaneLeft);
|
||||||
|
SetStaticMesh(ELaneRight, LaneRight);
|
||||||
|
SetStaticMesh(ESidewalkLeft, SidewalkLeft);
|
||||||
|
SetStaticMesh(ESidewalkRight, SidewalkRight);
|
||||||
|
SetStaticMesh(ELaneMarkingSolid, LaneMarkingSolid);
|
||||||
|
SetStaticMesh(ELaneMarkingBroken, LaneMarkingBroken);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARoadSegment::UpdateRoad()
|
||||||
|
{
|
||||||
|
ClearInstances();
|
||||||
|
Scale = (LaneLeft != nullptr ? LaneLeft->GetBoundingBox().GetSize().X : 1.0f);
|
||||||
|
FVector Translation(0.0f, 0.0f, 0.0f);
|
||||||
|
for (auto &Item : RoadDescription) {
|
||||||
|
FTransform Transform{Translation};
|
||||||
|
AddInstance(ELaneLeft, Transform);
|
||||||
|
AddInstance(ELaneRight, Transform);
|
||||||
|
if (Item.bHasRightSidewalk) {
|
||||||
|
AddInstance(ESidewalkRight, Transform);
|
||||||
|
}
|
||||||
|
if (Item.bHasLeftSidewalk) {
|
||||||
|
AddInstance(ESidewalkLeft, Transform);
|
||||||
|
}
|
||||||
|
if (Item.LaneMarking == ELaneMarkingType::Solid) {
|
||||||
|
AddInstance(ELaneMarkingSolid, Transform);
|
||||||
|
} else if (Item.LaneMarking == ELaneMarkingType::Broken) {
|
||||||
|
AddInstance(ELaneMarkingBroken, Transform);
|
||||||
|
}
|
||||||
|
Translation.X += Scale;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "MapGen/StaticMeshCollection.h"
|
||||||
|
#include "RoadSegment.generated.h"
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class ELaneMarkingType : uint8
|
||||||
|
{
|
||||||
|
None UMETA(DisplayName = "None"),
|
||||||
|
Solid UMETA(DisplayName = "Solid Lane Marking"),
|
||||||
|
Broken UMETA(DisplayName = "Broken Lane Marking")
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Description of a road segment piece.
|
||||||
|
USTRUCT()
|
||||||
|
struct FRoadSegmentPiece
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
bool bHasLeftSidewalk = true;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
bool bHasRightSidewalk = true;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
ELaneMarkingType LaneMarking = ELaneMarkingType::Solid;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A straight segment of road.
|
||||||
|
///
|
||||||
|
/// Please call GenerateRoad after modifying it.
|
||||||
|
UCLASS()
|
||||||
|
class CARLA_API ARoadSegment : public AStaticMeshCollection
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
ARoadSegment(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
virtual void OnConstruction(const FTransform &Transform) override;
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Road Description")
|
||||||
|
void GenerateRoad();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Road Description")
|
||||||
|
int32 GetNumberOfPieces() const
|
||||||
|
{
|
||||||
|
return RoadDescription.Num();
|
||||||
|
}
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Road Description")
|
||||||
|
void AppendPiece(const FRoadSegmentPiece &RoadSegmentPiece)
|
||||||
|
{
|
||||||
|
RoadDescription.Add(RoadSegmentPiece);
|
||||||
|
}
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Road Description")
|
||||||
|
void RemoveAllPieces()
|
||||||
|
{
|
||||||
|
RoadDescription.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Set Static Mesh")
|
||||||
|
void SetStaticMesh_LaneLeft(UStaticMesh *StaticMesh)
|
||||||
|
{
|
||||||
|
LaneLeft = StaticMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Set Static Mesh")
|
||||||
|
void SetStaticMesh_LaneRight(UStaticMesh *StaticMesh)
|
||||||
|
{
|
||||||
|
LaneRight = StaticMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Set Static Mesh")
|
||||||
|
void SetStaticMesh_SidewalkLeft(UStaticMesh *StaticMesh)
|
||||||
|
{
|
||||||
|
SidewalkLeft = StaticMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Set Static Mesh")
|
||||||
|
void SetStaticMesh_SidewalkRight(UStaticMesh *StaticMesh)
|
||||||
|
{
|
||||||
|
SidewalkRight = StaticMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Set Static Mesh")
|
||||||
|
void SetStaticMesh_LaneMarkingSolid(UStaticMesh *StaticMesh)
|
||||||
|
{
|
||||||
|
LaneMarkingSolid = StaticMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Set Static Mesh")
|
||||||
|
void SetStaticMesh_LaneMarkingBroken(UStaticMesh *StaticMesh)
|
||||||
|
{
|
||||||
|
LaneMarkingBroken = StaticMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void UpdateMeshes();
|
||||||
|
|
||||||
|
void UpdateRoad();
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Road Description", EditAnywhere)
|
||||||
|
TArray<FRoadSegmentPiece> RoadDescription;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Road Description", AdvancedDisplay, EditAnywhere)
|
||||||
|
float Scale = 1.0f;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Meshes", EditAnywhere)
|
||||||
|
UStaticMesh *LaneLeft;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Meshes", EditAnywhere)
|
||||||
|
UStaticMesh *LaneRight;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Meshes", EditAnywhere)
|
||||||
|
UStaticMesh *SidewalkLeft;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Meshes", EditAnywhere)
|
||||||
|
UStaticMesh *SidewalkRight;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Meshes", EditAnywhere)
|
||||||
|
UStaticMesh *LaneMarkingSolid;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Meshes", EditAnywhere)
|
||||||
|
UStaticMesh *LaneMarkingBroken;
|
||||||
|
};
|
|
@ -9,43 +9,112 @@
|
||||||
#include "Components/SkeletalMeshComponent.h"
|
#include "Components/SkeletalMeshComponent.h"
|
||||||
#include "PhysicsEngine/PhysicsAsset.h"
|
#include "PhysicsEngine/PhysicsAsset.h"
|
||||||
|
|
||||||
enum class Label : uint8
|
#ifdef CARLA_TAGGER_EXTRA_LOG
|
||||||
|
static FString GetLabelAsString(const CityObjectLabel Label)
|
||||||
{
|
{
|
||||||
None = 0u,
|
switch (Label) {
|
||||||
Buildings = 1u,
|
#define CARLA_GET_LABEL_STR(lbl) case CityObjectLabel:: lbl : return #lbl;
|
||||||
Fences = 2u,
|
default:
|
||||||
Other = 3u,
|
CARLA_GET_LABEL_STR(None)
|
||||||
Pedestrians = 4u,
|
CARLA_GET_LABEL_STR(Buildings)
|
||||||
Poles = 5u,
|
CARLA_GET_LABEL_STR(Fences)
|
||||||
RoadLines = 6u,
|
CARLA_GET_LABEL_STR(Other)
|
||||||
Roads = 7u,
|
CARLA_GET_LABEL_STR(Pedestrians)
|
||||||
Sidewalks = 8u,
|
CARLA_GET_LABEL_STR(Poles)
|
||||||
Vegetation = 9u,
|
CARLA_GET_LABEL_STR(RoadLines)
|
||||||
Vehicles = 10u,
|
CARLA_GET_LABEL_STR(Roads)
|
||||||
Walls = 11u,
|
CARLA_GET_LABEL_STR(Sidewalks)
|
||||||
};
|
CARLA_GET_LABEL_STR(Vegetation)
|
||||||
|
CARLA_GET_LABEL_STR(Vehicles)
|
||||||
static Label GetLabel(const FString &str) {
|
CARLA_GET_LABEL_STR(Walls)
|
||||||
if (str == "Buildings") return Label::Buildings;
|
#undef CARLA_GET_LABEL_STR
|
||||||
else if (str == "Fences") return Label::Fences;
|
|
||||||
else if (str == "Pedestrians") return Label::Pedestrians;
|
|
||||||
else if (str == "Pole") return Label::Poles;
|
|
||||||
else if (str == "Props") return Label::Other;
|
|
||||||
else if (str == "Road") return Label::Roads;
|
|
||||||
else if (str == "RoadLines") return Label::RoadLines;
|
|
||||||
else if (str == "SideWalk") return Label::Sidewalks;
|
|
||||||
else if (str == "Vegetation") return Label::Vegetation;
|
|
||||||
else if (str == "Vehicles") return Label::Vehicles;
|
|
||||||
else if (str == "Walls") return Label::Walls;
|
|
||||||
else return Label::None;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
#endif // CARLA_TAGGER_EXTRA_LOG
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static auto cast(T label)
|
static auto CastEnum(T label)
|
||||||
{
|
{
|
||||||
return static_cast<typename std::underlying_type<T>::type>(label);
|
return static_cast<typename std::underlying_type<T>::type>(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static CityObjectLabel GetLabelByFolderName(const FString &String) {
|
||||||
|
if (String == "Buildings") return CityObjectLabel::Buildings;
|
||||||
|
else if (String == "Fences") return CityObjectLabel::Fences;
|
||||||
|
else if (String == "Pedestrians") return CityObjectLabel::Pedestrians;
|
||||||
|
else if (String == "Pole") return CityObjectLabel::Poles;
|
||||||
|
else if (String == "Props") return CityObjectLabel::Other;
|
||||||
|
else if (String == "Road") return CityObjectLabel::Roads;
|
||||||
|
else if (String == "RoadLines") return CityObjectLabel::RoadLines;
|
||||||
|
else if (String == "SideWalk") return CityObjectLabel::Sidewalks;
|
||||||
|
else if (String == "Vegetation") return CityObjectLabel::Vegetation;
|
||||||
|
else if (String == "Vehicles") return CityObjectLabel::Vehicles;
|
||||||
|
else if (String == "Walls") return CityObjectLabel::Walls;
|
||||||
|
else return CityObjectLabel::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static CityObjectLabel GetLabelByPath(const T *Object)
|
||||||
|
{
|
||||||
|
const FString Path = Object->GetPathName();
|
||||||
|
TArray<FString> StringArray;
|
||||||
|
Path.ParseIntoArray(StringArray, TEXT("/"), false);
|
||||||
|
return (StringArray.Num() > 3 ? GetLabelByFolderName(StringArray[3]) : CityObjectLabel::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetStencilValue(UPrimitiveComponent *comp, const CityObjectLabel &Label) {
|
||||||
|
if (Label != CityObjectLabel::None) {
|
||||||
|
comp->SetRenderCustomDepth(true);
|
||||||
|
comp->SetCustomDepthStencilValue(CastEnum(Label));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// -- static ATagger functions -------------------------------------------------
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
void ATagger::TagActor(const AActor &Actor)
|
||||||
|
{
|
||||||
|
#ifdef CARLA_TAGGER_EXTRA_LOG
|
||||||
|
UE_LOG(LogCarla, Log, TEXT("Actor: %s"), *Actor.GetName());
|
||||||
|
#endif // CARLA_TAGGER_EXTRA_LOG
|
||||||
|
|
||||||
|
// Iterate static meshes.
|
||||||
|
TArray<UStaticMeshComponent *> StaticMeshComponents;
|
||||||
|
Actor.GetComponents<UStaticMeshComponent>(StaticMeshComponents);
|
||||||
|
for (UStaticMeshComponent *Component : StaticMeshComponents) {
|
||||||
|
const auto Label = GetLabelByPath(Component->GetStaticMesh());
|
||||||
|
SetStencilValue(Component, Label);
|
||||||
|
#ifdef CARLA_TAGGER_EXTRA_LOG
|
||||||
|
UE_LOG(LogCarla, Log, TEXT(" + StaticMeshComponent: %s"), *Component->GetName());
|
||||||
|
UE_LOG(LogCarla, Log, TEXT(" - Label: \"%s\""), *GetLabelAsString(Label));
|
||||||
|
#endif // CARLA_TAGGER_EXTRA_LOG
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate skeletal meshes.
|
||||||
|
TArray<USkeletalMeshComponent *> SkeletalMeshComponents;
|
||||||
|
Actor.GetComponents<USkeletalMeshComponent>(SkeletalMeshComponents);
|
||||||
|
for (USkeletalMeshComponent *Component : SkeletalMeshComponents) {
|
||||||
|
const auto Label = GetLabelByPath(Component->GetPhysicsAsset());
|
||||||
|
SetStencilValue(Component, Label);
|
||||||
|
#ifdef CARLA_TAGGER_EXTRA_LOG
|
||||||
|
UE_LOG(LogCarla, Log, TEXT(" + SkeletalMeshComponent: %s"), *Component->GetName());
|
||||||
|
UE_LOG(LogCarla, Log, TEXT(" - Label: \"%s\""), *GetLabelAsString(Label));
|
||||||
|
#endif // CARLA_TAGGER_EXTRA_LOG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ATagger::TagActorsInLevel(UWorld &World)
|
||||||
|
{
|
||||||
|
for (TActorIterator<AActor> it(&World); it; ++it) {
|
||||||
|
TagActor(**it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// -- non-static ATagger functions ---------------------------------------------
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
ATagger::ATagger()
|
ATagger::ATagger()
|
||||||
{
|
{
|
||||||
PrimaryActorTick.bCanEverTick = false;
|
PrimaryActorTick.bCanEverTick = false;
|
||||||
|
@ -56,81 +125,10 @@ void ATagger::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent
|
||||||
{
|
{
|
||||||
Super::PostEditChangeProperty(PropertyChangedEvent);
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
||||||
if (PropertyChangedEvent.Property) {
|
if (PropertyChangedEvent.Property) {
|
||||||
if (bTriggerTagObjects) {
|
if (bTriggerTagObjects && (GetWorld() != nullptr)) {
|
||||||
TagObjects();
|
TagActorsInLevel(*GetWorld());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bTriggerTagObjects = false;
|
bTriggerTagObjects = false;
|
||||||
}
|
}
|
||||||
#endif // WITH_EDITOR
|
#endif // WITH_EDITOR
|
||||||
|
|
||||||
static void setStencilValue(UPrimitiveComponent *comp, const Label &label) {
|
|
||||||
if (label != Label::None)
|
|
||||||
{
|
|
||||||
comp->SetRenderCustomDepth(true);
|
|
||||||
comp->SetCustomDepthStencilValue(cast(label));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ATagger::TagObjects()
|
|
||||||
{
|
|
||||||
for (TActorIterator<AActor> it(GetWorld()); it; ++it) {
|
|
||||||
|
|
||||||
#ifdef CARLA_TAGGER_EXTRA_LOG
|
|
||||||
UE_LOG(LogCarla, Warning, TEXT("Actor: %s"), *it->GetName());
|
|
||||||
#endif // CARLA_TAGGER_EXTRA_LOG
|
|
||||||
|
|
||||||
/// get UStaticMeshComponents
|
|
||||||
TArray<UStaticMeshComponent*> staticComponents;
|
|
||||||
it->GetComponents<UStaticMeshComponent>(staticComponents);
|
|
||||||
|
|
||||||
for (auto& meshIt : staticComponents)
|
|
||||||
{
|
|
||||||
|
|
||||||
#ifdef CARLA_TAGGER_EXTRA_LOG
|
|
||||||
UE_LOG(LogCarla, Warning, TEXT(" + StaticMeshComponent: %s"), *meshIt->GetName());
|
|
||||||
#endif // CARLA_TAGGER_EXTRA_LOG
|
|
||||||
|
|
||||||
FString Path = meshIt->GetStaticMesh()->GetPathName();
|
|
||||||
TArray<FString> stringArray;
|
|
||||||
Path.ParseIntoArray(stringArray, TEXT("/"), false);
|
|
||||||
/*for (int32 i = 0; i < stringArray.Num(); i++) {
|
|
||||||
UE_LOG(LogCarla, Warning, TEXT(" -\"%s\""), *stringArray[i]);
|
|
||||||
}*/
|
|
||||||
if (stringArray.Num() > 3)
|
|
||||||
{
|
|
||||||
Label lab = GetLabel(stringArray[3]);
|
|
||||||
|
|
||||||
#ifdef CARLA_TAGGER_EXTRA_LOG
|
|
||||||
UE_LOG(LogCarla, Warning, TEXT(" - Label: \"%s\""), *stringArray[3]);
|
|
||||||
#endif // CARLA_TAGGER_EXTRA_LOG
|
|
||||||
|
|
||||||
setStencilValue(meshIt, lab);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// get USkeletalMeshComponents
|
|
||||||
TArray<USkeletalMeshComponent*> skeletalComponents;
|
|
||||||
it->GetComponents<USkeletalMeshComponent>(skeletalComponents);
|
|
||||||
|
|
||||||
for (auto& meshIt : skeletalComponents)
|
|
||||||
{
|
|
||||||
#ifdef CARLA_TAGGER_EXTRA_LOG
|
|
||||||
UE_LOG(LogCarla, Warning, TEXT(" + SkeletalMeshComponent: %s"), *meshIt->GetName());
|
|
||||||
#endif // CARLA_TAGGER_EXTRA_LOG
|
|
||||||
FString Path = meshIt->GetPhysicsAsset()->GetPathName();
|
|
||||||
TArray<FString> stringArray;
|
|
||||||
Path.ParseIntoArray(stringArray, TEXT("/"), false);
|
|
||||||
if (stringArray.Num() > 3)
|
|
||||||
{
|
|
||||||
Label lab = GetLabel(stringArray[3]);
|
|
||||||
|
|
||||||
#ifdef CARLA_TAGGER_EXTRA_LOG
|
|
||||||
UE_LOG(LogCarla, Warning, TEXT(" - Label: \"%s\""), *stringArray[3]);
|
|
||||||
#endif // CARLA_TAGGER_EXTRA_LOG
|
|
||||||
|
|
||||||
setStencilValue(meshIt, lab);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,6 +5,27 @@
|
||||||
#include "GameFramework/Actor.h"
|
#include "GameFramework/Actor.h"
|
||||||
#include "Tagger.generated.h"
|
#include "Tagger.generated.h"
|
||||||
|
|
||||||
|
enum class CityObjectLabel : uint8
|
||||||
|
{
|
||||||
|
None = 0u,
|
||||||
|
Buildings = 1u,
|
||||||
|
Fences = 2u,
|
||||||
|
Other = 3u,
|
||||||
|
Pedestrians = 4u,
|
||||||
|
Poles = 5u,
|
||||||
|
RoadLines = 6u,
|
||||||
|
Roads = 7u,
|
||||||
|
Sidewalks = 8u,
|
||||||
|
Vegetation = 9u,
|
||||||
|
Vehicles = 10u,
|
||||||
|
Walls = 11u,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Sets actors' custom depth stencil value for semantic segmentation according
|
||||||
|
/// to their meshes.
|
||||||
|
///
|
||||||
|
/// Non-static functions present so it can be dropped into the scene for testing
|
||||||
|
/// purposes.
|
||||||
UCLASS()
|
UCLASS()
|
||||||
class CARLA_API ATagger : public AActor
|
class CARLA_API ATagger : public AActor
|
||||||
{
|
{
|
||||||
|
@ -12,9 +33,11 @@ class CARLA_API ATagger : public AActor
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
ATagger();
|
static void TagActor(const AActor &Actor);
|
||||||
|
|
||||||
void TagObjects();
|
static void TagActorsInLevel(UWorld &World);
|
||||||
|
|
||||||
|
ATagger();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue