Port code for road generation
This commit is contained in:
parent
a79a62a354
commit
778d5cda20
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
#define LOCTEXT_NAMESPACE "FCarlaModule"
|
#define LOCTEXT_NAMESPACE "FCarlaModule"
|
||||||
|
|
||||||
|
DEFINE_LOG_CATEGORY(LogCarla);
|
||||||
|
|
||||||
void FCarlaModule::StartupModule()
|
void FCarlaModule::StartupModule()
|
||||||
{
|
{
|
||||||
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
||||||
|
@ -16,5 +18,5 @@ void FCarlaModule::ShutdownModule()
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef LOCTEXT_NAMESPACE
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
||||||
IMPLEMENT_MODULE(FCarlaModule, Carla)
|
IMPLEMENT_MODULE(FCarlaModule, Carla)
|
|
@ -1,9 +1,15 @@
|
||||||
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
// This file is included before any other file in every compile unit within the
|
||||||
|
// plugin.
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ModuleManager.h"
|
#include "ModuleManager.h"
|
||||||
|
|
||||||
|
#include "../Common/NonCopyable.h"
|
||||||
|
|
||||||
|
DECLARE_LOG_CATEGORY_EXTERN(LogCarla, Log, All);
|
||||||
|
|
||||||
class FCarlaModule : public IModuleInterface
|
class FCarlaModule : public IModuleInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -11,4 +17,4 @@ public:
|
||||||
/** IModuleInterface implementation */
|
/** IModuleInterface implementation */
|
||||||
virtual void StartupModule() override;
|
virtual void StartupModule() override;
|
||||||
virtual void ShutdownModule() override;
|
virtual void ShutdownModule() override;
|
||||||
};
|
};
|
|
@ -0,0 +1,169 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#include "Carla.h"
|
||||||
|
#include "CityMapGenerator.h"
|
||||||
|
|
||||||
|
#include "MapGen/GraphGenerator.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#ifdef WITH_EDITOR
|
||||||
|
#include <sstream>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// -- Constructor and destructor -----------------------------------------------
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
ACityMapGenerator::ACityMapGenerator(const FObjectInitializer& ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
UpdateMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
ACityMapGenerator::~ACityMapGenerator() {}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// -- Private methods ----------------------------------------------------------
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
void ACityMapGenerator::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
||||||
|
{
|
||||||
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
||||||
|
if (PropertyChangedEvent.Property) {
|
||||||
|
UpdateMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void ACityMapGenerator::UpdateMap() {
|
||||||
|
UpdateSeeds();
|
||||||
|
GenerateGraph();
|
||||||
|
if (bGenerateRoads) {
|
||||||
|
GenerateRoads();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACityMapGenerator::UpdateSeeds() {
|
||||||
|
if (!bUseFixedSeed) {
|
||||||
|
bUseMultipleFixedSeeds = false;
|
||||||
|
FRandomStream randomStream;
|
||||||
|
randomStream.GenerateNewSeed();
|
||||||
|
Seed = randomStream.GetCurrentSeed();
|
||||||
|
}
|
||||||
|
if (!bUseMultipleFixedSeeds) {
|
||||||
|
RoadPlanningSeed = Seed;
|
||||||
|
BuildingGenerationSeed = Seed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACityMapGenerator::GenerateGraph() {
|
||||||
|
if ((MapSizeX < 5u) || (MapSizeY < 5u)) {
|
||||||
|
MapSizeX = 5u;
|
||||||
|
MapSizeY = 5u;
|
||||||
|
UE_LOG(LogCarla, Warning, TEXT("Map size changed, was too small"));
|
||||||
|
}
|
||||||
|
#ifdef WITH_EDITOR
|
||||||
|
// Delete the dcel before the new one is created so indices are restored.
|
||||||
|
Dcel.Reset(nullptr);
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
Dcel = MapGen::GraphGenerator::Generate(MapSizeX, MapSizeY, RoadPlanningSeed);
|
||||||
|
UE_LOG(LogCarla, Log,
|
||||||
|
TEXT("Generated DCEL with: { %d vertices, %d half-edges, %d faces }"),
|
||||||
|
Dcel->CountNodes(),
|
||||||
|
Dcel->CountHalfEdges(),
|
||||||
|
Dcel->CountFaces());
|
||||||
|
DcelParser = MakeUnique<MapGen::GraphParser>(*Dcel);
|
||||||
|
#ifdef WITH_EDITOR
|
||||||
|
{ // print the results of the parser.
|
||||||
|
std::wstringstream sout;
|
||||||
|
sout << "\nGenerated " << DcelParser->CityAreaCount() << " city areas: ";
|
||||||
|
for (auto i = 0u; i < DcelParser->CityAreaCount(); ++i) {
|
||||||
|
sout << "{ ";
|
||||||
|
auto &cityArea = DcelParser->GetCityAreaAt(i);
|
||||||
|
for (size_t j = 0u; j < cityArea.NodeCount(); ++j) {
|
||||||
|
sout << cityArea.GetNodeAt(j) << " ";
|
||||||
|
}
|
||||||
|
sout << "} ";
|
||||||
|
}
|
||||||
|
sout << "\nGenerated " << DcelParser->RoadSegmentCount() << " road segments: ";
|
||||||
|
for (auto i = 0u; i < DcelParser->RoadSegmentCount(); ++i) {
|
||||||
|
sout << "{ ";
|
||||||
|
auto &roadSegment = DcelParser->GetRoadSegmentAt(i);
|
||||||
|
for (size_t j = 0u; j < roadSegment.Size(); ++j) {
|
||||||
|
sout << roadSegment[j] << " ";
|
||||||
|
}
|
||||||
|
sout << "} ";
|
||||||
|
}
|
||||||
|
UE_LOG(LogCarla, Log, TEXT("\n%s"), sout.str().c_str());
|
||||||
|
}
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACityMapGenerator::GenerateRoads() {
|
||||||
|
constexpr auto basicRoadTag = ECityMapMeshTag::RoadTwoLanes;
|
||||||
|
constexpr auto basicIntersectionTag = ECityMapMeshTag::RoadXIntersection;
|
||||||
|
|
||||||
|
// Rotation for vertical roads.
|
||||||
|
const FQuat rotation(FVector(0.0f, 0.0f, 1.0f), HALF_PI);
|
||||||
|
|
||||||
|
check(Dcel != nullptr);
|
||||||
|
using Graph = MapGen::DoublyConnectedEdgeList;
|
||||||
|
const Graph &graph = *Dcel;
|
||||||
|
|
||||||
|
const uint32 margin = CityMapMeshTag::GetRoadIntersectionSize() / 2u;
|
||||||
|
|
||||||
|
// For each edge add road segment.
|
||||||
|
for (auto &edge : graph.GetHalfEdges()) {
|
||||||
|
auto source = Graph::GetSource(edge).GetPosition();
|
||||||
|
auto target = Graph::GetTarget(edge).GetPosition();
|
||||||
|
|
||||||
|
if (source.x == target.x) {
|
||||||
|
// vertical
|
||||||
|
auto y = 1u + margin + std::min(source.y, target.y);
|
||||||
|
auto end = std::max(source.y, target.y) - margin;
|
||||||
|
for (; y < end; ++y) {
|
||||||
|
AddInstance(basicRoadTag, source.x, y, HALF_PI);
|
||||||
|
}
|
||||||
|
} else if (source.y == target.y) {
|
||||||
|
// horizontal
|
||||||
|
auto x = 1u + margin + std::min(source.x, target.x);
|
||||||
|
auto end = std::max(source.x, target.x) - margin;
|
||||||
|
for (; x < end; ++x) {
|
||||||
|
AddInstance(basicRoadTag, x, source.y);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UE_LOG(LogCarla, Warning, TEXT("Diagonal edge ignored"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each node add the intersection.
|
||||||
|
for (auto &node : graph.GetNodes()) {
|
||||||
|
const auto coords = node.GetPosition();
|
||||||
|
ECityMapMeshTag tag = basicIntersectionTag;
|
||||||
|
switch (node.IntersectionType) {
|
||||||
|
case MapGen::EIntersectionType::Turn90Deg:
|
||||||
|
tag = ECityMapMeshTag::Road90DegTurn;
|
||||||
|
break;
|
||||||
|
case MapGen::EIntersectionType::TIntersection:
|
||||||
|
tag = ECityMapMeshTag::RoadTIntersection;
|
||||||
|
break;
|
||||||
|
case MapGen::EIntersectionType::XIntersection:
|
||||||
|
tag = ECityMapMeshTag::RoadXIntersection;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UE_LOG(LogCarla, Warning, TEXT("Intersection type not implemented"));
|
||||||
|
}
|
||||||
|
FString tagStr = CityMapMeshTag::ToString(tag);
|
||||||
|
std::wstringstream sout;
|
||||||
|
for (float a : node.Rots)
|
||||||
|
sout << a << " ";
|
||||||
|
UE_LOG(
|
||||||
|
LogCarla,
|
||||||
|
Log,
|
||||||
|
TEXT("Add instance \"%s\" at {%d, %d} with rotation %f, { %s }"),
|
||||||
|
*tagStr, coords.x, coords.y, node.Rotation, sout.str().c_str());
|
||||||
|
AddInstance(tag, coords.x, coords.y, node.Rotation);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "MapGen/CityMapMeshHolder.h"
|
||||||
|
#include "MapGen/DoublyConnectedEdgeList.h"
|
||||||
|
#include "MapGen/GraphParser.h"
|
||||||
|
#include "CityMapGenerator.generated.h"
|
||||||
|
|
||||||
|
UCLASS(HideCategories=(Rendering, Input))
|
||||||
|
class CARLA_API ACityMapGenerator : public ACityMapMeshHolder
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
ACityMapGenerator(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
~ACityMapGenerator();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
/// Called after a property change in editor.
|
||||||
|
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Update the map based on the current settings.
|
||||||
|
void UpdateMap();
|
||||||
|
|
||||||
|
/// Update the random seeds. Generate random if no fixed seed is used.
|
||||||
|
void UpdateSeeds();
|
||||||
|
|
||||||
|
/// Regenerate the DCEL.
|
||||||
|
void GenerateGraph();
|
||||||
|
|
||||||
|
/// Add the road meshes to the scene based on the current DCEL.
|
||||||
|
void GenerateRoads();
|
||||||
|
|
||||||
|
/// @name Map Generation
|
||||||
|
/// @{
|
||||||
|
UPROPERTY(Category = "Map Generation", EditAnywhere, meta = (ClampMin = "10", ClampMax = "200"))
|
||||||
|
uint32 MapSizeX = 20u;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Map Generation", EditAnywhere, meta = (ClampMin = "10", ClampMax = "200"))
|
||||||
|
uint32 MapSizeY = 20u;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Map Generation", EditAnywhere)
|
||||||
|
bool bGenerateRoads = true;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Map Generation", EditAnywhere)
|
||||||
|
bool bUseFixedSeed = true;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Map Generation", EditAnywhere, meta = (EditCondition = bUseFixedSeed))
|
||||||
|
int32 Seed = 123456789;
|
||||||
|
/// @}
|
||||||
|
|
||||||
|
/// @name Map Generation - Advance Display
|
||||||
|
/// @{
|
||||||
|
UPROPERTY(Category = "Map Generation", EditAnywhere, AdvancedDisplay, meta = (EditCondition = bUseFixedSeed))
|
||||||
|
bool bUseMultipleFixedSeeds = false;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Map Generation", EditAnywhere, AdvancedDisplay, meta = (EditCondition = bUseMultipleFixedSeeds))
|
||||||
|
int32 RoadPlanningSeed;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Map Generation", EditAnywhere, AdvancedDisplay, meta = (EditCondition = bUseMultipleFixedSeeds))
|
||||||
|
int32 BuildingGenerationSeed;
|
||||||
|
/// @}
|
||||||
|
|
||||||
|
/// @name Other private members
|
||||||
|
/// @{
|
||||||
|
TUniquePtr<MapGen::DoublyConnectedEdgeList> Dcel;
|
||||||
|
|
||||||
|
TUniquePtr<MapGen::GraphParser> DcelParser;
|
||||||
|
/// @}
|
||||||
|
};
|
|
@ -0,0 +1,40 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GraphTypes.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace MapGen {
|
||||||
|
|
||||||
|
class CARLA_API CityAreaDescription : private NonCopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit CityAreaDescription(const GraphFace &Face) : _face(&Face) {}
|
||||||
|
|
||||||
|
void Add(const GraphNode &Node) {
|
||||||
|
_nodes.emplace_back(&Node);
|
||||||
|
}
|
||||||
|
|
||||||
|
const GraphFace &GetFace() const {
|
||||||
|
return *_face;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GraphNode &GetNodeAt(size_t i) const {
|
||||||
|
return *_nodes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t NodeCount() const {
|
||||||
|
return _nodes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
const GraphFace *_face;
|
||||||
|
|
||||||
|
std::vector<const GraphNode *> _nodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MapGen
|
|
@ -0,0 +1,13 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace MapGen {
|
||||||
|
|
||||||
|
enum class EIntersectionType {
|
||||||
|
Turn90Deg,
|
||||||
|
TIntersection,
|
||||||
|
XIntersection
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MapGen
|
|
@ -0,0 +1,127 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#include "Carla.h"
|
||||||
|
#include "CityMapMeshHolder.h"
|
||||||
|
|
||||||
|
#include "Components/InstancedStaticMeshComponent.h"
|
||||||
|
#include "Engine/StaticMesh.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using tag_size_t = std::underlying_type<ECityMapMeshTag>::type;
|
||||||
|
|
||||||
|
constexpr static tag_size_t NUMBER_OF_TAGS = CityMapMeshTag::GetNumberOfTags();
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// -- Constructor --------------------------------------------------------------
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
ACityMapMeshHolder::ACityMapMeshHolder(const FObjectInitializer& ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
PrimaryActorTick.bCanEverTick = false;
|
||||||
|
|
||||||
|
SceneRootComponent =
|
||||||
|
ObjectInitializer.CreateDefaultSubobject<USceneComponent>(this, TEXT("SceneComponent"));
|
||||||
|
SceneRootComponent->SetMobility(EComponentMobility::Static);
|
||||||
|
RootComponent = SceneRootComponent;
|
||||||
|
|
||||||
|
for (tag_size_t i = 0u; i < NUMBER_OF_TAGS; ++i) {
|
||||||
|
// Add static mesh holder.
|
||||||
|
StaticMeshes.Add(CityMapMeshTag::FromUInt(i));
|
||||||
|
// Create an instantiator for each mesh.
|
||||||
|
const FString name = CityMapMeshTag::ToString(i) + "Instantiator";
|
||||||
|
auto instantiator = CreateDefaultSubobject<UInstancedStaticMeshComponent>(*name);
|
||||||
|
instantiator->SetMobility(EComponentMobility::Static);
|
||||||
|
instantiator->SetupAttachment(SceneRootComponent);
|
||||||
|
MeshInstatiators.Add(instantiator);
|
||||||
|
instantiator->RegisterComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// -- Public methods -----------------------------------------------------------
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
FVector ACityMapMeshHolder::GetTileLocation(uint32 X, uint32 Y) const
|
||||||
|
{
|
||||||
|
return {X * MapScale, Y * MapScale, 0.0f};
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// -- Protected methods --------------------------------------------------------
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
void ACityMapMeshHolder::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
||||||
|
{
|
||||||
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
||||||
|
if (PropertyChangedEvent.Property) {
|
||||||
|
ResetInstantiators();
|
||||||
|
UpdateMapScale();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const UStaticMesh *ACityMapMeshHolder::GetStaticMesh(ECityMapMeshTag Tag) const
|
||||||
|
{
|
||||||
|
return StaticMeshes[Tag];
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACityMapMeshHolder::AddInstance(ECityMapMeshTag Tag, uint32 X, uint32 Y)
|
||||||
|
{
|
||||||
|
AddInstance(Tag, FTransform(GetTileLocation(X, Y)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACityMapMeshHolder::AddInstance(ECityMapMeshTag Tag, uint32 X, uint32 Y, float Angle)
|
||||||
|
{
|
||||||
|
const FQuat rotation(FVector(0.0f, 0.0f, 1.0f), Angle);
|
||||||
|
const FVector location = GetTileLocation(X, Y);
|
||||||
|
AddInstance(Tag, FTransform(rotation, location));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACityMapMeshHolder::AddInstance(ECityMapMeshTag Tag, FTransform Transform)
|
||||||
|
{
|
||||||
|
auto instantiator = MeshInstatiators[CityMapMeshTag::ToUInt(Tag)];
|
||||||
|
check(instantiator != nullptr);
|
||||||
|
instantiator->AddInstance(Transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// -- Private methods ----------------------------------------------------------
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
void ACityMapMeshHolder::ResetInstantiators()
|
||||||
|
{
|
||||||
|
for (tag_size_t i = 0u; i < NUMBER_OF_TAGS; ++i) {
|
||||||
|
UInstancedStaticMeshComponent *instantiator = MeshInstatiators[i];
|
||||||
|
check(instantiator != nullptr);
|
||||||
|
instantiator->ClearInstances();
|
||||||
|
instantiator->SetStaticMesh(StaticMeshes[CityMapMeshTag::FromUInt(i)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACityMapMeshHolder::UpdateMapScale()
|
||||||
|
{
|
||||||
|
auto Tag = CityMapMeshTag::GetBaseMeshTag();
|
||||||
|
auto *mesh = GetStaticMesh(Tag);
|
||||||
|
if (mesh == nullptr) {
|
||||||
|
UE_LOG(
|
||||||
|
LogCarla,
|
||||||
|
Error,
|
||||||
|
TEXT("Cannot find mesh \"%s\" for computing tile size"),
|
||||||
|
*CityMapMeshTag::ToString(Tag));
|
||||||
|
MapScale = 1.0f;
|
||||||
|
} else {
|
||||||
|
FVector size = mesh->GetBoundingBox().GetSize();
|
||||||
|
if (size.X != size.Y) {
|
||||||
|
UE_LOG(
|
||||||
|
LogCarla,
|
||||||
|
Warning,
|
||||||
|
TEXT("Base mesh \"%s\" is not squared"),
|
||||||
|
*CityMapMeshTag::ToString(Tag));
|
||||||
|
}
|
||||||
|
MapScale = size.X;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "CityMapMeshTag.h"
|
||||||
|
#include "CityMapMeshHolder.generated.h"
|
||||||
|
|
||||||
|
class UInstancedStaticMeshComponent;
|
||||||
|
|
||||||
|
/// Holds the static meshes and instances necessary for building the city map.
|
||||||
|
UCLASS(Abstract)
|
||||||
|
class CARLA_API ACityMapMeshHolder : public AActor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Sets default values for this actor's properties
|
||||||
|
ACityMapMeshHolder(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
/// Called after a property change in editor.
|
||||||
|
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Return the 3D world location (relative to this actor) of the given 2D
|
||||||
|
/// tile.
|
||||||
|
FVector GetTileLocation(uint32 X, uint32 Y) const;
|
||||||
|
|
||||||
|
/// Return the static mesh corresponding to @a Tag.
|
||||||
|
const UStaticMesh *GetStaticMesh(ECityMapMeshTag Tag) const;
|
||||||
|
|
||||||
|
/// Add an instance of a mesh with a given tile location.
|
||||||
|
/// @param Tag The mesh' tag
|
||||||
|
/// @param X Tile coordinate X
|
||||||
|
/// @param Y Tile coordinate Y
|
||||||
|
void AddInstance(ECityMapMeshTag Tag, uint32 X, uint32 Y);
|
||||||
|
|
||||||
|
/// Add an instance of a mesh with a given tile location and rotation.
|
||||||
|
/// @param Tag The mesh' tag
|
||||||
|
/// @param X Tile coordinate X
|
||||||
|
/// @param Y Tile coordinate Y
|
||||||
|
/// @param Angle Rotation around Z axis
|
||||||
|
void AddInstance(ECityMapMeshTag Tag, uint32 X, uint32 Y, float Angle);
|
||||||
|
|
||||||
|
/// Add an instance of a mesh with a given transform.
|
||||||
|
/// @param Tag The mesh' tag
|
||||||
|
/// @param Transform Transform that will be applied to the mesh
|
||||||
|
void AddInstance(ECityMapMeshTag Tag, FTransform Transform);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/// Clear all instances in the instantiators and update the static meshes.
|
||||||
|
void ResetInstantiators();
|
||||||
|
|
||||||
|
/// Set the scale to the dimensions of the base mesh.
|
||||||
|
void UpdateMapScale();
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
USceneComponent *SceneRootComponent;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Meshes", EditAnywhere)
|
||||||
|
TMap<ECityMapMeshTag, UStaticMesh *> StaticMeshes;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Meshes|Debug", VisibleAnywhere)
|
||||||
|
float MapScale;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Meshes|Debug", VisibleAnywhere)
|
||||||
|
TArray<UInstancedStaticMeshComponent *> MeshInstatiators;
|
||||||
|
};
|
|
@ -0,0 +1,26 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#include "Carla.h"
|
||||||
|
#include "CityMapMeshTag.h"
|
||||||
|
|
||||||
|
#include "Package.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
ECityMapMeshTag CityMapMeshTag::GetBaseMeshTag()
|
||||||
|
{
|
||||||
|
return ECityMapMeshTag::RoadTwoLanes;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 CityMapMeshTag::GetRoadIntersectionSize()
|
||||||
|
{
|
||||||
|
return 5u;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString CityMapMeshTag::ToString(ECityMapMeshTag Tag)
|
||||||
|
{
|
||||||
|
const UEnum* ptr = FindObject<UEnum>(ANY_PACKAGE, TEXT("ECityMapMeshTag"), true);
|
||||||
|
if(!ptr)
|
||||||
|
return FString("Invalid");
|
||||||
|
return ptr->GetEnumName(static_cast<int32>(Tag));
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/// Tag to identify the meshes used by the ProceduralMapGenerator.
|
||||||
|
///
|
||||||
|
/// It will work as long as we have less than 255 meshes, currently blueprint
|
||||||
|
/// type enums support uint8 only.
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class ECityMapMeshTag : uint8
|
||||||
|
{
|
||||||
|
RoadTwoLanes UMETA(DisplayName = "Road: Two Lanes"),
|
||||||
|
RoadFourLanes UMETA(DisplayName = "Road: Four Lanes"),
|
||||||
|
Road90DegTurn UMETA(DisplayName = "Road: 90 Degree Turn"),
|
||||||
|
RoadTIntersection UMETA(DisplayName = "Road: T-Intersection"),
|
||||||
|
RoadXIntersection UMETA(DisplayName = "Road: X-Intersection"),
|
||||||
|
|
||||||
|
NUMBER_OF_TAGS UMETA(Hidden)
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Helper class for working with ECityMapMeshTag.
|
||||||
|
class CARLA_API CityMapMeshTag
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Return the number of tags.
|
||||||
|
static constexpr uint8 GetNumberOfTags() {
|
||||||
|
return ToUInt(ECityMapMeshTag::NUMBER_OF_TAGS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the base mesh. The base mesh defines the unit tile for map scaling.
|
||||||
|
static ECityMapMeshTag GetBaseMeshTag();
|
||||||
|
|
||||||
|
/// Get the size in tiles of a road intersection side. I.e., return N such NxN
|
||||||
|
/// is the size of a road intersection piece.
|
||||||
|
static uint32 GetRoadIntersectionSize();
|
||||||
|
|
||||||
|
/// @name Tag conversions
|
||||||
|
/// @{
|
||||||
|
|
||||||
|
/// Convert @a Tag to an unsigned integer type.
|
||||||
|
static constexpr uint8 ToUInt(ECityMapMeshTag Tag) {
|
||||||
|
return static_cast<uint8>(Tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert an unsigned integer to a ECityMapMeshTag.
|
||||||
|
static ECityMapMeshTag FromUInt(uint8 Value) {
|
||||||
|
check(Value < GetNumberOfTags());
|
||||||
|
return static_cast<ECityMapMeshTag>(Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get @a Tag name as FString.
|
||||||
|
static FString ToString(ECityMapMeshTag Tag);
|
||||||
|
|
||||||
|
/// Convert @a Value to ECityMapMeshTag and get its name as FString.
|
||||||
|
static FString ToString(uint8 Value) {
|
||||||
|
return ToString(FromUInt(Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @}
|
||||||
|
};
|
|
@ -0,0 +1,315 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#include "Carla.h"
|
||||||
|
#include "DoublyConnectedEdgeList.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#ifdef WITH_EDITOR
|
||||||
|
#include <sstream>
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
|
||||||
|
namespace MapGen {
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// -- Local static methods ---------------------------------------------------
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
#ifdef WITH_EDITOR
|
||||||
|
|
||||||
|
static void ResetIndices() {
|
||||||
|
GraphNode::ResetIndex();
|
||||||
|
GraphHalfEdge::ResetIndex();
|
||||||
|
GraphFace::ResetIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
|
||||||
|
/// Return the pair {prev, next}, where prev/next is the previous/next edge
|
||||||
|
/// counterclockwise around edge's source node. I.e., edge's position is in
|
||||||
|
/// between prev and next.
|
||||||
|
///
|
||||||
|
/// Note: Always returns the half-edge pointing out from node.
|
||||||
|
///
|
||||||
|
/// The time complexity is O(n*log(n)) where n is the number of edges of
|
||||||
|
/// edge's source.
|
||||||
|
static std::pair<DoublyConnectedEdgeList::HalfEdge *, DoublyConnectedEdgeList::HalfEdge *>
|
||||||
|
FindPositionInNode(DoublyConnectedEdgeList::HalfEdge &halfEdge)
|
||||||
|
{
|
||||||
|
using Dcel = DoublyConnectedEdgeList;
|
||||||
|
// from [-pi, pi] to [0, 1].
|
||||||
|
auto normalize = [](auto a) {
|
||||||
|
constexpr decltype(a) twoPi = 2.0 * 3.14159265359;
|
||||||
|
a /= twoPi;
|
||||||
|
while (a >= 1.0) a -= 1.0;
|
||||||
|
while (a < 0.0) a += 1.0;
|
||||||
|
return a;
|
||||||
|
};
|
||||||
|
auto angle = Dcel::GetAngle(halfEdge);
|
||||||
|
std::map<decltype(angle), Dcel::HalfEdge *> edgeMap;
|
||||||
|
// Iterate every half-edge in the source node.
|
||||||
|
auto &firstHalfEdge = Dcel::GetLeavingHalfEdge(Dcel::GetSource(halfEdge));
|
||||||
|
auto *edge = &firstHalfEdge;
|
||||||
|
do {
|
||||||
|
if (edge != &halfEdge) {
|
||||||
|
auto alpha = DoublyConnectedEdgeList::GetAngle(*edge);
|
||||||
|
auto a = normalize(alpha - angle);
|
||||||
|
edgeMap.insert({a, edge});
|
||||||
|
}
|
||||||
|
edge = &Dcel::GetNextInNode(*edge);
|
||||||
|
} while (edge != &firstHalfEdge);
|
||||||
|
check(!edgeMap.empty());
|
||||||
|
return {edgeMap.rbegin()->second, edgeMap.begin()->second};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// -- Constructors and destructor --------------------------------------------
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
DoublyConnectedEdgeList::DoublyConnectedEdgeList(
|
||||||
|
const Position &Position0,
|
||||||
|
const Position &Position1) :
|
||||||
|
Nodes(),
|
||||||
|
HalfEdges(2u),
|
||||||
|
Faces(1u)
|
||||||
|
{
|
||||||
|
Nodes.emplace_back(Position0);
|
||||||
|
Nodes.emplace_back(Position1);
|
||||||
|
|
||||||
|
Faces.front().HalfEdge = &HalfEdges.front();
|
||||||
|
|
||||||
|
HalfEdges.front().Source = &Nodes.front();
|
||||||
|
HalfEdges.front().Target = &Nodes.back();
|
||||||
|
HalfEdges.front().Next = &HalfEdges.back();
|
||||||
|
HalfEdges.front().Pair = &HalfEdges.back();
|
||||||
|
HalfEdges.front().Face = &Faces.front();
|
||||||
|
|
||||||
|
HalfEdges.back().Source = &Nodes.back();
|
||||||
|
HalfEdges.back().Target = &Nodes.front();
|
||||||
|
HalfEdges.back().Next = &HalfEdges.front();
|
||||||
|
HalfEdges.back().Pair = &HalfEdges.front();
|
||||||
|
HalfEdges.back().Face = &Faces.front();
|
||||||
|
|
||||||
|
Nodes.front().LeavingHalfEdge = &HalfEdges.front();
|
||||||
|
Nodes.back().LeavingHalfEdge = &HalfEdges.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
DoublyConnectedEdgeList::~DoublyConnectedEdgeList()
|
||||||
|
{
|
||||||
|
#ifdef WITH_EDITOR
|
||||||
|
ResetIndices();
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// -- Adding elements to the graph -------------------------------------------
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
DoublyConnectedEdgeList::Node &DoublyConnectedEdgeList::AddNode(
|
||||||
|
const Position &NodePosition,
|
||||||
|
Node &OtherNode)
|
||||||
|
{
|
||||||
|
Nodes.emplace_back(NodePosition);
|
||||||
|
auto &newNode = Nodes.back();
|
||||||
|
HalfEdges.emplace_back();
|
||||||
|
auto &edge0 = HalfEdges.back();
|
||||||
|
HalfEdges.emplace_back();
|
||||||
|
auto &edge1 = HalfEdges.back();
|
||||||
|
|
||||||
|
edge0.Source = &newNode;
|
||||||
|
edge0.Target = &OtherNode;
|
||||||
|
edge0.Pair = &edge1;
|
||||||
|
|
||||||
|
edge1.Source = &OtherNode;
|
||||||
|
edge1.Target = &newNode;
|
||||||
|
edge1.Pair = &edge0;
|
||||||
|
|
||||||
|
HalfEdge *prev;
|
||||||
|
HalfEdge *next;
|
||||||
|
std::tie(prev, next) = FindPositionInNode(edge1);
|
||||||
|
|
||||||
|
edge0.Next = next;
|
||||||
|
edge0.Face = next->Face;
|
||||||
|
|
||||||
|
edge1.Next = &edge0;
|
||||||
|
edge1.Face = next->Face;
|
||||||
|
|
||||||
|
prev->Pair->Next = &edge1;
|
||||||
|
|
||||||
|
newNode.LeavingHalfEdge = &edge0;
|
||||||
|
return newNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
DoublyConnectedEdgeList::Node &DoublyConnectedEdgeList::SplitEdge(
|
||||||
|
const Position &Position,
|
||||||
|
HalfEdge &edge)
|
||||||
|
{
|
||||||
|
HalfEdges.emplace_back();
|
||||||
|
auto &edge0 = HalfEdges.back();
|
||||||
|
HalfEdges.emplace_back();
|
||||||
|
auto &edge1 = HalfEdges.back();
|
||||||
|
|
||||||
|
Nodes.emplace_back(Position);
|
||||||
|
auto &newNode = Nodes.back();
|
||||||
|
|
||||||
|
auto &node0 = *edge.Source;
|
||||||
|
|
||||||
|
// Opposite direction of edge.
|
||||||
|
edge0.Source = &newNode;
|
||||||
|
edge0.Target = &node0;
|
||||||
|
edge0.Pair = &edge1;
|
||||||
|
edge0.Face = edge.Pair->Face;
|
||||||
|
|
||||||
|
// Same direction as edge.
|
||||||
|
edge1.Source = &node0;
|
||||||
|
edge1.Target = &newNode;
|
||||||
|
edge1.Pair = &edge0;
|
||||||
|
edge1.Next = &edge;
|
||||||
|
edge1.Face = edge.Face;
|
||||||
|
|
||||||
|
// Fix connections to node0.
|
||||||
|
HalfEdge *prev;
|
||||||
|
HalfEdge *next;
|
||||||
|
std::tie(prev, next) = FindPositionInNode(edge);
|
||||||
|
edge0.Next = next;
|
||||||
|
prev->Pair->Next = &edge1;
|
||||||
|
|
||||||
|
// Fix the pair that was split.
|
||||||
|
edge.Source = &newNode;
|
||||||
|
edge.Pair->Target = &newNode;
|
||||||
|
edge.Pair->Next = &edge0;
|
||||||
|
|
||||||
|
// Fix the node's edges.
|
||||||
|
node0.LeavingHalfEdge = &edge1;
|
||||||
|
newNode.LeavingHalfEdge = &edge0;
|
||||||
|
|
||||||
|
return newNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
DoublyConnectedEdgeList::Face &DoublyConnectedEdgeList::ConnectNodes(
|
||||||
|
Node &Node0,
|
||||||
|
Node &Node1)
|
||||||
|
{
|
||||||
|
Faces.emplace_back();
|
||||||
|
auto &newFace = Faces.back();
|
||||||
|
HalfEdges.emplace_back();
|
||||||
|
auto &edge0 = HalfEdges.back();
|
||||||
|
HalfEdges.emplace_back();
|
||||||
|
auto &edge1 = HalfEdges.back();
|
||||||
|
|
||||||
|
edge0.Source = &Node0;
|
||||||
|
edge0.Target = &Node1;
|
||||||
|
edge0.Pair = &edge1;
|
||||||
|
edge1.Source = &Node1;
|
||||||
|
edge1.Target = &Node0;
|
||||||
|
edge1.Pair = &edge0;
|
||||||
|
|
||||||
|
// Connect edges to node0.
|
||||||
|
HalfEdge *prev0;
|
||||||
|
HalfEdge *next0;
|
||||||
|
std::tie(prev0, next0) = FindPositionInNode(edge0);
|
||||||
|
edge1.Next = next0;
|
||||||
|
prev0->Pair->Next = &edge0;
|
||||||
|
|
||||||
|
// Connect edges to node1.
|
||||||
|
HalfEdge *prev1;
|
||||||
|
HalfEdge *next1;
|
||||||
|
std::tie(prev1, next1) = FindPositionInNode(edge1);
|
||||||
|
edge0.Next = next1;
|
||||||
|
prev1->Pair->Next = &edge1;
|
||||||
|
|
||||||
|
// Attach faces to the newly created edges.
|
||||||
|
auto &oldFace = *next1->Face;
|
||||||
|
oldFace.HalfEdge = &edge0;
|
||||||
|
newFace.HalfEdge = &edge1;
|
||||||
|
|
||||||
|
// Iterate over the edges of each face and correct pointers.
|
||||||
|
auto fixFace = [](Face &face) {
|
||||||
|
auto &firstEdge = GetHalfEdge(face);
|
||||||
|
auto *edge = &firstEdge;
|
||||||
|
do {
|
||||||
|
edge->Face = &face;
|
||||||
|
edge = &GetNextInFace(*edge);
|
||||||
|
} while (edge != &firstEdge);
|
||||||
|
};
|
||||||
|
fixFace(oldFace);
|
||||||
|
fixFace(newFace);
|
||||||
|
|
||||||
|
return newFace;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// -- Other member functions -------------------------------------------------
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
float DoublyConnectedEdgeList::GetAngle(const HalfEdge &halfEdge) {
|
||||||
|
auto src = GetSource(halfEdge).GetPosition();
|
||||||
|
auto trg = GetTarget(halfEdge).GetPosition();
|
||||||
|
auto dir = trg - src; // @todo normalize?
|
||||||
|
return std::atan2(static_cast<double>(dir.y), static_cast<double>(dir.x));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_EDITOR
|
||||||
|
|
||||||
|
void DoublyConnectedEdgeList::PrintToLog() const
|
||||||
|
{
|
||||||
|
std::wstringstream sout;
|
||||||
|
{
|
||||||
|
sout << "iterate all nodes: ";
|
||||||
|
for (auto &node : GetNodes()) {
|
||||||
|
auto p = node.GetPosition();
|
||||||
|
sout << node << "{" << p.x << "," << p.y << "} ";
|
||||||
|
}
|
||||||
|
sout << "\n";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
sout << "iterate all faces: ";
|
||||||
|
for (auto &face : GetFaces()) {
|
||||||
|
sout << face << " ";
|
||||||
|
}
|
||||||
|
sout << "\n";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
sout << "iterate all edges: ";
|
||||||
|
for (auto &edge : GetHalfEdges()) {
|
||||||
|
auto &src = GetSource(edge);
|
||||||
|
auto &trg = GetTarget(edge);
|
||||||
|
auto &face = GetFace(edge);
|
||||||
|
sout << edge << "{" << src << "->" << trg << "," << face << "} ";
|
||||||
|
}
|
||||||
|
sout << "\n";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
sout << "iterate nodes in face: ";
|
||||||
|
for (auto &face : GetFaces()) {
|
||||||
|
sout << face << "{ ";
|
||||||
|
auto &firstEdge = GetHalfEdge(face);
|
||||||
|
const auto *edge = &firstEdge;
|
||||||
|
do {
|
||||||
|
sout << GetSource(*edge) << " ";
|
||||||
|
edge = &GetNextInFace(*edge);
|
||||||
|
} while (edge != &firstEdge);
|
||||||
|
sout << "} ";
|
||||||
|
}
|
||||||
|
sout << "\n";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
sout << "iterate edges in node: ";
|
||||||
|
for (auto &node : GetNodes()) {
|
||||||
|
sout << node << "{ ";
|
||||||
|
auto &firstEdge = GetLeavingHalfEdge(node);
|
||||||
|
const auto *edge = &firstEdge;
|
||||||
|
do {
|
||||||
|
sout << GetTarget(*edge) << " ";
|
||||||
|
edge = &GetNextInNode(*edge);
|
||||||
|
} while (edge != &firstEdge);
|
||||||
|
sout << "} ";
|
||||||
|
}
|
||||||
|
sout << "\n";
|
||||||
|
}
|
||||||
|
UE_LOG(LogCarla, Log, TEXT("\n%s"), sout.str().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
|
||||||
|
} // namespace MapGen
|
|
@ -0,0 +1,332 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GraphTypes.h"
|
||||||
|
#include "ListView.h"
|
||||||
|
#include "Position.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
namespace MapGen {
|
||||||
|
|
||||||
|
/// Simple doubly-connected edge list structure. It only allows adding
|
||||||
|
/// elements, not removing them.
|
||||||
|
class CARLA_API DoublyConnectedEdgeList : private NonCopyable
|
||||||
|
{
|
||||||
|
// =========================================================================
|
||||||
|
// -- DCEL types -----------------------------------------------------------
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
using Position = MapGen::Position<int32>;
|
||||||
|
|
||||||
|
struct Node;
|
||||||
|
struct HalfEdge;
|
||||||
|
struct Face;
|
||||||
|
|
||||||
|
struct Node : public GraphNode
|
||||||
|
{
|
||||||
|
friend DoublyConnectedEdgeList;
|
||||||
|
|
||||||
|
Node(const Position &Pos) : Position(Pos) {}
|
||||||
|
|
||||||
|
Node &operator=(const Node &) = delete;
|
||||||
|
|
||||||
|
const DoublyConnectedEdgeList::Position &GetPosition() const
|
||||||
|
{
|
||||||
|
return Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DoublyConnectedEdgeList::Position Position;
|
||||||
|
HalfEdge *LeavingHalfEdge = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HalfEdge : public GraphHalfEdge
|
||||||
|
{
|
||||||
|
friend DoublyConnectedEdgeList;
|
||||||
|
|
||||||
|
HalfEdge &operator=(const HalfEdge &) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Node *Source = nullptr;
|
||||||
|
Node *Target = nullptr;
|
||||||
|
HalfEdge *Next = nullptr;
|
||||||
|
HalfEdge *Pair = nullptr;
|
||||||
|
Face *Face = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Face : public GraphFace
|
||||||
|
{
|
||||||
|
friend DoublyConnectedEdgeList;
|
||||||
|
|
||||||
|
Face &operator=(const Face &) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
HalfEdge *HalfEdge = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
using NodeContainer = std::list<Node>;
|
||||||
|
using NodeIterator = typename NodeContainer::iterator;
|
||||||
|
using ConstNodeIterator = typename NodeContainer::const_iterator;
|
||||||
|
|
||||||
|
using HalfEdgeContainer = std::list<HalfEdge>;
|
||||||
|
using HalfEdgeIterator = typename HalfEdgeContainer::iterator;
|
||||||
|
using ConstHalfEdgeIterator = typename HalfEdgeContainer::const_iterator;
|
||||||
|
|
||||||
|
using FaceContainer = std::list<Face>;
|
||||||
|
using FaceIterator = typename FaceContainer::iterator;
|
||||||
|
using ConstFaceIterator = typename FaceContainer::const_iterator;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// -- Constructors and destructor ------------------------------------------
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Create a DoublyConnectedEdgeList with two nodes, two edges and one face.
|
||||||
|
explicit DoublyConnectedEdgeList(const Position &Position0, const Position &Position1);
|
||||||
|
|
||||||
|
/// Create a DoublyConnectedEdgeList consisting of a cycle of N nodes.
|
||||||
|
template <size_t N>
|
||||||
|
explicit DoublyConnectedEdgeList(const std::array<Position, N> &Cycle)
|
||||||
|
: DoublyConnectedEdgeList(Cycle[0u], Cycle[1u])
|
||||||
|
{
|
||||||
|
static_assert(N > 2u, "Not enough nodes to make a cycle!");
|
||||||
|
for (auto i = 2u; i < Cycle.size(); ++i) {
|
||||||
|
AddNode(Cycle[i], Nodes.back());
|
||||||
|
}
|
||||||
|
ConnectNodes(Nodes.front(), Nodes.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
~DoublyConnectedEdgeList();
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
/// @name Adding elements to the graph -------------------------------------
|
||||||
|
// =========================================================================
|
||||||
|
/// {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Add a node at @a NodePosition and attach it to @a OtherNode.
|
||||||
|
///
|
||||||
|
/// The time complexity is O(n*log(n)) where n is the number of edges
|
||||||
|
/// leaving @a OtherNode.
|
||||||
|
///
|
||||||
|
/// @return The newly generated node.
|
||||||
|
Node &AddNode(const Position &NodePosition, Node &OtherNode);
|
||||||
|
|
||||||
|
/// Split @a HalfEdge (and its pair) at @a Position.
|
||||||
|
///
|
||||||
|
/// The time complexity is O(n*log(n)) where n is the number of edges
|
||||||
|
/// leaving @a HalfEdge's source.
|
||||||
|
///
|
||||||
|
/// @return The newly generated node.
|
||||||
|
Node &SplitEdge(const Position &Position, HalfEdge &HalfEdge);
|
||||||
|
|
||||||
|
/// Connect two nodes by a pair of edges.
|
||||||
|
///
|
||||||
|
/// It is assumed that both nodes are connected by the same face.
|
||||||
|
///
|
||||||
|
/// The time complexity is O(n0*log(n0) + n1*log(n1) + nf) where n0 and n1
|
||||||
|
/// are the number of edges leaving @a Node0 and @a Node1 respectively, and
|
||||||
|
/// nf is the number of edges in the face containing both nodes.
|
||||||
|
///
|
||||||
|
/// @return The newly generated face.
|
||||||
|
Face &ConnectNodes(Node &Node0, Node &Node1);
|
||||||
|
|
||||||
|
/// @}
|
||||||
|
// =========================================================================
|
||||||
|
/// @name Counting graph elements ------------------------------------------
|
||||||
|
// =========================================================================
|
||||||
|
/// @{
|
||||||
|
public:
|
||||||
|
|
||||||
|
size_t CountNodes() const
|
||||||
|
{
|
||||||
|
return Nodes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t CountHalfEdges() const
|
||||||
|
{
|
||||||
|
return HalfEdges.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t CountFaces() const
|
||||||
|
{
|
||||||
|
return Faces.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @}
|
||||||
|
// =========================================================================
|
||||||
|
/// @name Accessing graph elements -----------------------------------------
|
||||||
|
// =========================================================================
|
||||||
|
/// @{
|
||||||
|
public:
|
||||||
|
|
||||||
|
ListView<NodeIterator> GetNodes()
|
||||||
|
{
|
||||||
|
return ListView<NodeIterator>(Nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView<ConstNodeIterator> GetNodes() const
|
||||||
|
{
|
||||||
|
return ListView<ConstNodeIterator>(Nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView<HalfEdgeIterator> GetHalfEdges()
|
||||||
|
{
|
||||||
|
return ListView<HalfEdgeIterator>(HalfEdges);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView<ConstHalfEdgeIterator> GetHalfEdges() const
|
||||||
|
{
|
||||||
|
return ListView<ConstHalfEdgeIterator>(HalfEdges);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView<FaceIterator> GetFaces()
|
||||||
|
{
|
||||||
|
return ListView<FaceIterator>(Faces);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView<ConstFaceIterator> GetFaces() const
|
||||||
|
{
|
||||||
|
return ListView<ConstFaceIterator>(Faces);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @}
|
||||||
|
// =========================================================================
|
||||||
|
/// @name Accessing graph pointers -----------------------------------------
|
||||||
|
// =========================================================================
|
||||||
|
/// @{
|
||||||
|
public:
|
||||||
|
|
||||||
|
// -- Primary pointers -----------------------------------------------------
|
||||||
|
|
||||||
|
static Node &GetSource(HalfEdge &halfEdge)
|
||||||
|
{
|
||||||
|
check(halfEdge.Source != nullptr);
|
||||||
|
return *halfEdge.Source;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Node &GetSource(const HalfEdge &halfEdge)
|
||||||
|
{
|
||||||
|
check(halfEdge.Source != nullptr);
|
||||||
|
return *halfEdge.Source;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Node &GetTarget(HalfEdge &halfEdge)
|
||||||
|
{
|
||||||
|
check(halfEdge.Target != nullptr);
|
||||||
|
return *halfEdge.Target;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Node &GetTarget(const HalfEdge &halfEdge)
|
||||||
|
{
|
||||||
|
check(halfEdge.Target != nullptr);
|
||||||
|
return *halfEdge.Target;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HalfEdge &GetPair(HalfEdge &halfEdge)
|
||||||
|
{
|
||||||
|
check(halfEdge.Pair != nullptr);
|
||||||
|
return *halfEdge.Pair;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const HalfEdge &GetPair(const HalfEdge &halfEdge)
|
||||||
|
{
|
||||||
|
check(halfEdge.Pair != nullptr);
|
||||||
|
return *halfEdge.Pair;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Face &GetFace(HalfEdge &halfEdge)
|
||||||
|
{
|
||||||
|
check(halfEdge.Face != nullptr);
|
||||||
|
return *halfEdge.Face;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Face &GetFace(const HalfEdge &halfEdge)
|
||||||
|
{
|
||||||
|
check(halfEdge.Face != nullptr);
|
||||||
|
return *halfEdge.Face;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HalfEdge &GetLeavingHalfEdge(Node &node)
|
||||||
|
{
|
||||||
|
check(node.LeavingHalfEdge != nullptr);
|
||||||
|
return *node.LeavingHalfEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const HalfEdge &GetLeavingHalfEdge(const Node &node)
|
||||||
|
{
|
||||||
|
check(node.LeavingHalfEdge != nullptr);
|
||||||
|
return *node.LeavingHalfEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HalfEdge &GetHalfEdge(Face &face)
|
||||||
|
{
|
||||||
|
check(face.HalfEdge != nullptr);
|
||||||
|
return *face.HalfEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const HalfEdge &GetHalfEdge(const Face &face)
|
||||||
|
{
|
||||||
|
check(face.HalfEdge != nullptr);
|
||||||
|
return *face.HalfEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Secondary pointers ---------------------------------------------------
|
||||||
|
|
||||||
|
static HalfEdge &GetNextInFace(HalfEdge &halfEdge)
|
||||||
|
{
|
||||||
|
check(halfEdge.Next != nullptr);
|
||||||
|
return *halfEdge.Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const HalfEdge &GetNextInFace(const HalfEdge &halfEdge)
|
||||||
|
{
|
||||||
|
check(halfEdge.Next != nullptr);
|
||||||
|
return *halfEdge.Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HalfEdge &GetNextInNode(HalfEdge &halfEdge)
|
||||||
|
{
|
||||||
|
return GetNextInFace(GetPair(halfEdge));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const HalfEdge &GetNextInNode(const HalfEdge &halfEdge)
|
||||||
|
{
|
||||||
|
return GetNextInFace(GetPair(halfEdge));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @}
|
||||||
|
// =========================================================================
|
||||||
|
/// @name Other member functions -------------------------------------------
|
||||||
|
// =========================================================================
|
||||||
|
/// @{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Return the angle [-pi, pi] of the half-edge.
|
||||||
|
static float GetAngle(const HalfEdge &halfEdge);
|
||||||
|
|
||||||
|
#ifdef WITH_EDITOR
|
||||||
|
void PrintToLog() const;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// @}
|
||||||
|
// =========================================================================
|
||||||
|
// -- Private members ------------------------------------------------------
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
NodeContainer Nodes;
|
||||||
|
|
||||||
|
HalfEdgeContainer HalfEdges;
|
||||||
|
|
||||||
|
FaceContainer Faces;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MapGen
|
|
@ -0,0 +1,109 @@
|
||||||
|
//// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
//
|
||||||
|
#include "Carla.h"
|
||||||
|
#include "GraphGenerator.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace MapGen {
|
||||||
|
|
||||||
|
using Graph = DoublyConnectedEdgeList;
|
||||||
|
|
||||||
|
constexpr static int32 MARGIN = 6;
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// -- Static local methods ---------------------------------------------------
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
static int32 signOf(int32 val) {
|
||||||
|
return (0 < val) - (val < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Graph::Position &getSourcePosition(const Graph::HalfEdge &edge) {
|
||||||
|
return Graph::GetSource(edge).GetPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Graph::Position &getTargetPosition(const Graph::HalfEdge &edge) {
|
||||||
|
return Graph::GetTarget(edge).GetPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Graph::Position getDirection(const Graph::HalfEdge &edge) {
|
||||||
|
return getTargetPosition(edge) - getSourcePosition(edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::pair<Graph::HalfEdge *, Graph::HalfEdge *> getRandomOpposingEdges(
|
||||||
|
Graph::Face &face,
|
||||||
|
FRandomStream &random) {
|
||||||
|
// Get all the edges in the face.
|
||||||
|
std::vector<Graph::HalfEdge *> edges;
|
||||||
|
edges.reserve(4u);
|
||||||
|
auto &firstEdge = Graph::GetHalfEdge(face);
|
||||||
|
auto *edge = &firstEdge;
|
||||||
|
do {
|
||||||
|
edges.emplace_back(edge);
|
||||||
|
edge = &Graph::GetNextInFace(*edge);
|
||||||
|
} while (edge != &firstEdge);
|
||||||
|
check(edges.size() == 4u);
|
||||||
|
auto randomIndex = random.RandRange(0, edges.size() - 1);
|
||||||
|
return {edges[randomIndex], edges[(randomIndex + 2u) % edges.size()]};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Graph::Face *splitFace(Graph &graph, Graph::Face &face, FRandomStream &random) {
|
||||||
|
auto edgePair = getRandomOpposingEdges(face, random);
|
||||||
|
auto dir = getDirection(*edgePair.first);
|
||||||
|
// Assumes both edges are opposing faces on a rectangle.
|
||||||
|
auto otherDir = getDirection(*edgePair.second);
|
||||||
|
check((dir.x == -1 * otherDir.x) && (dir.y == -1 * otherDir.y));
|
||||||
|
// If the rectangle is not big enough do not split it.
|
||||||
|
if ((std::abs(dir.x) < 2*MARGIN+1) && (std::abs(dir.y) < 2*MARGIN+1))
|
||||||
|
return nullptr;
|
||||||
|
// Get a random point along the edges.
|
||||||
|
auto randX = (dir.x != 0 ? signOf(dir.x) * random.RandRange(MARGIN, std::abs(dir.x) - MARGIN) : 0);
|
||||||
|
auto randY = (dir.y != 0 ? signOf(dir.y) * random.RandRange(MARGIN, std::abs(dir.y) - MARGIN) : 0);
|
||||||
|
auto position0 = getSourcePosition(*edgePair.first) + Graph::Position{randX, randY};
|
||||||
|
auto position1 = getTargetPosition(*edgePair.second) + Graph::Position{randX, randY};
|
||||||
|
// Split the edges and connect.
|
||||||
|
Graph::Node &node0 = graph.SplitEdge(position0, *edgePair.first);
|
||||||
|
Graph::Node &node1 = graph.SplitEdge(position1, *edgePair.second);
|
||||||
|
return &graph.ConnectNodes(node0, node1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void randomize(Graph &graph, const int32 seed)
|
||||||
|
{
|
||||||
|
check(graph.CountNodes() == 4u);
|
||||||
|
check(graph.CountHalfEdges() == 8u);
|
||||||
|
check(graph.CountFaces() == 2u);
|
||||||
|
FRandomStream random(seed);
|
||||||
|
/// @todo We skip first face because is the surrounding face. But this won't
|
||||||
|
/// be always the case, if the graph is generated differently it might be a
|
||||||
|
/// different one.
|
||||||
|
Graph::Face *face = &*(++graph.GetFaces().begin());
|
||||||
|
do {
|
||||||
|
face = splitFace(graph, *face, random);
|
||||||
|
#ifdef WITH_EDITOR
|
||||||
|
graph.PrintToLog();
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
} while (face != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// -- GraphGenerator -----------------------------------------------------------
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
TUniquePtr<DoublyConnectedEdgeList> GraphGenerator::Generate(
|
||||||
|
const uint32 SizeX,
|
||||||
|
const uint32 SizeY,
|
||||||
|
const int32 Seed)
|
||||||
|
{
|
||||||
|
using Position = typename DoublyConnectedEdgeList::Position;
|
||||||
|
std::array<Position, 4u> box = {
|
||||||
|
Position(0, 0),
|
||||||
|
Position(0, SizeY),
|
||||||
|
Position(SizeX, SizeY),
|
||||||
|
Position(SizeX, 0)};
|
||||||
|
auto Dcel = MakeUnique<DoublyConnectedEdgeList>(box);
|
||||||
|
randomize(*Dcel, Seed);
|
||||||
|
return Dcel;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MapGen
|
|
@ -0,0 +1,19 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "DoublyConnectedEdgeList.h"
|
||||||
|
|
||||||
|
namespace MapGen {
|
||||||
|
|
||||||
|
/// Random DoublyConnectedEdgeList generator.
|
||||||
|
class CARLA_API GraphGenerator : private NonCopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Create a squared DoublyConnectedEdgeList of size @a SizeX times @a SizeY
|
||||||
|
/// and generate random connections inside using fixed @a Seed.
|
||||||
|
static TUniquePtr<DoublyConnectedEdgeList> Generate(uint32 SizeX, uint32 SizeY, int32 Seed);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MapGen
|
|
@ -0,0 +1,168 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#include "Carla.h"
|
||||||
|
#include "GraphParser.h"
|
||||||
|
|
||||||
|
#include "DoublyConnectedEdgeList.h"
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace MapGen {
|
||||||
|
|
||||||
|
using Graph = DoublyConnectedEdgeList;
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// -- Local static methods ---------------------------------------------------
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
static int getQuadrant(float angle) {
|
||||||
|
return static_cast<int>(std::round(angle/HALF_PI));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumes angles are separated by half pi approx.
|
||||||
|
static float getRotation(float angle0, float angle1) {
|
||||||
|
const int min = getQuadrant(std::min(angle0, angle1));
|
||||||
|
const int max = getQuadrant(std::max(angle0, angle1));
|
||||||
|
return HALF_PI * std::min(min, min * max);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumes angles are separated by half pi approx.
|
||||||
|
static float getRotation(float angle0, float angle1, float angle2) {
|
||||||
|
/// @todo There has to be a better way.
|
||||||
|
switch (getQuadrant(angle0) + getQuadrant(angle1) + getQuadrant(angle2)) {
|
||||||
|
case 0:
|
||||||
|
return HALF_PI;
|
||||||
|
case 1:
|
||||||
|
return 0.0;
|
||||||
|
case 2:
|
||||||
|
return -1.0 * HALF_PI;
|
||||||
|
case 3:
|
||||||
|
return PI;
|
||||||
|
default:
|
||||||
|
UE_LOG(LogCarla, Error, TEXT("Wrong quadrants"));
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @todo This can probably be done at graph creation.
|
||||||
|
static void fixGraphData(Graph &graph) {
|
||||||
|
// Set the edge count for each node in the graph.
|
||||||
|
for (auto &node : graph.GetNodes()) {
|
||||||
|
std::vector<float> angles;
|
||||||
|
angles.reserve(4u);
|
||||||
|
// Iterate every half-edge in this node.
|
||||||
|
auto &firstEdge = Graph::GetLeavingHalfEdge(node);
|
||||||
|
auto *edge = &firstEdge;
|
||||||
|
do {
|
||||||
|
edge->Angle = Graph::GetAngle(*edge);
|
||||||
|
angles.emplace_back(edge->Angle);
|
||||||
|
edge = &Graph::GetNextInNode(*edge);
|
||||||
|
} while (edge != &firstEdge);
|
||||||
|
check(!angles.empty());
|
||||||
|
node.EdgeCount = angles.size();
|
||||||
|
node.bIsIntersection = true;
|
||||||
|
switch (node.EdgeCount) {
|
||||||
|
case 2:
|
||||||
|
node.Rotation = getRotation(angles[0u], angles[1u]);
|
||||||
|
node.IntersectionType = EIntersectionType::Turn90Deg;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
node.Rotation = getRotation(angles[0u], angles[1u], angles[2u]);
|
||||||
|
node.IntersectionType = EIntersectionType::TIntersection;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
default:
|
||||||
|
node.Rotation = 0.0;
|
||||||
|
node.IntersectionType = EIntersectionType::XIntersection;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
node.Rots.swap(angles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// -- RoadSegmentBuilder -----------------------------------------------------
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
class RoadSegmentBuilder {
|
||||||
|
public:
|
||||||
|
|
||||||
|
std::vector<TUniquePtr<RoadSegmentDescription>> Segments;
|
||||||
|
|
||||||
|
explicit RoadSegmentBuilder(const Graph &graph) : _graph(graph) {}
|
||||||
|
|
||||||
|
void Add(Graph::HalfEdge &edge) {
|
||||||
|
if (!insert(edge))
|
||||||
|
return;
|
||||||
|
if (Graph::GetSource(edge).bIsIntersection) {
|
||||||
|
Segments.emplace_back(MakeUnique<RoadSegmentDescription>());
|
||||||
|
_handlingInitial = false;
|
||||||
|
}
|
||||||
|
if (_handlingInitial) {
|
||||||
|
_initial.emplace_back(&edge);
|
||||||
|
} else {
|
||||||
|
Segments.back()->Add(edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Close() {
|
||||||
|
for (auto edge : _initial) {
|
||||||
|
Segments.back()->Add(*edge);
|
||||||
|
}
|
||||||
|
_handlingInitial = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/// Insert both half-edges only if they haven't been visited yet.
|
||||||
|
bool insert(Graph::HalfEdge &edge) {
|
||||||
|
return _visitedEdges.insert(&edge).second &&
|
||||||
|
_visitedEdges.insert(&Graph::GetPair(edge)).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Graph &_graph;
|
||||||
|
|
||||||
|
std::unordered_set<const Graph::HalfEdge *> _visitedEdges;
|
||||||
|
|
||||||
|
bool _handlingInitial = true;
|
||||||
|
|
||||||
|
std::vector<const Graph::HalfEdge *> _initial;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// -- GraphParser ------------------------------------------------------------
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
GraphParser::GraphParser(DoublyConnectedEdgeList &graph) {
|
||||||
|
check(graph.CountNodes() >= 4u);
|
||||||
|
check(graph.CountHalfEdges() >= 8u);
|
||||||
|
check(graph.CountFaces() >= 2u);
|
||||||
|
|
||||||
|
fixGraphData(graph);
|
||||||
|
|
||||||
|
CityAreas.reserve(graph.CountFaces() - 1);
|
||||||
|
|
||||||
|
RoadSegmentBuilder rsb(graph);
|
||||||
|
|
||||||
|
auto faceList = graph.GetFaces();
|
||||||
|
auto it = faceList.begin();
|
||||||
|
++it; // Ignore first face (unbounded).
|
||||||
|
for (; it != faceList.end(); ++it) {
|
||||||
|
CityAreas.emplace_back(MakeUnique<CityAreaDescription>(*it));
|
||||||
|
CityAreaDescription &cityArea = *CityAreas.back();
|
||||||
|
// Iterate every half-edge in this face.
|
||||||
|
auto &firstEdge = Graph::GetHalfEdge(*it);
|
||||||
|
for (auto *edge = &Graph::GetNextInFace(firstEdge);
|
||||||
|
edge != &firstEdge;
|
||||||
|
edge = &Graph::GetNextInFace(*edge)) {
|
||||||
|
cityArea.Add(Graph::GetSource(*edge));
|
||||||
|
rsb.Add(*edge);
|
||||||
|
}
|
||||||
|
rsb.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
RoadSegments.swap(rsb.Segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MapGen
|
|
@ -0,0 +1,68 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CityAreaDescription.h"
|
||||||
|
#include "GraphTypes.h"
|
||||||
|
#include "RoadSegmentDescription.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace MapGen {
|
||||||
|
|
||||||
|
class DoublyConnectedEdgeList;
|
||||||
|
|
||||||
|
class CARLA_API GraphParser : private NonCopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit GraphParser(DoublyConnectedEdgeList &Dcel);
|
||||||
|
|
||||||
|
bool HasRoadSegments() const {
|
||||||
|
return !RoadSegments.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasCityAreas() const {
|
||||||
|
return !CityAreas.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t RoadSegmentCount() const {
|
||||||
|
return RoadSegments.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t CityAreaCount() const {
|
||||||
|
return CityAreas.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const RoadSegmentDescription &GetRoadSegmentAt(size_t i) const {
|
||||||
|
return *RoadSegments[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const CityAreaDescription &GetCityAreaAt(size_t i) const {
|
||||||
|
return *CityAreas[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
TUniquePtr<RoadSegmentDescription> PopRoadSegment() {
|
||||||
|
TUniquePtr<RoadSegmentDescription> ptr{RoadSegments.back().Release()};
|
||||||
|
RoadSegments.pop_back();
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TUniquePtr<CityAreaDescription> PopCityArea() {
|
||||||
|
TUniquePtr<CityAreaDescription> ptr{CityAreas.back().Release()};
|
||||||
|
CityAreas.pop_back();
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
using RoadSegmentList = std::vector<TUniquePtr<RoadSegmentDescription>>;
|
||||||
|
|
||||||
|
using CityAreaList = std::vector<TUniquePtr<CityAreaDescription>>;
|
||||||
|
|
||||||
|
RoadSegmentList RoadSegments;
|
||||||
|
|
||||||
|
CityAreaList CityAreas;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MapGen
|
|
@ -0,0 +1,16 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#include "Carla.h"
|
||||||
|
#include "GraphTypes.h"
|
||||||
|
|
||||||
|
namespace MapGen {
|
||||||
|
|
||||||
|
#ifdef WITH_EDITOR
|
||||||
|
|
||||||
|
template <> uint32 DataIndex<'n'>::NEXT_INDEX = 0u;
|
||||||
|
template <> uint32 DataIndex<'e'>::NEXT_INDEX = 0u;
|
||||||
|
template <> uint32 DataIndex<'f'>::NEXT_INDEX = 0u;
|
||||||
|
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
|
||||||
|
} // namespace MapGen
|
|
@ -0,0 +1,62 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CityMapDefinitions.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace MapGen {
|
||||||
|
|
||||||
|
#ifdef WITH_EDITOR
|
||||||
|
|
||||||
|
/// For debug only purposes.
|
||||||
|
template <char C>
|
||||||
|
struct DataIndex : private NonCopyable
|
||||||
|
{
|
||||||
|
|
||||||
|
DataIndex() : index(++NEXT_INDEX) {}
|
||||||
|
|
||||||
|
static void ResetIndex() {
|
||||||
|
NEXT_INDEX = 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OSTREAM>
|
||||||
|
friend OSTREAM &operator<<(OSTREAM &os, const DataIndex &d) {
|
||||||
|
os << C << d.index;
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
// private:
|
||||||
|
|
||||||
|
uint32 index = 0u;
|
||||||
|
|
||||||
|
static uint32 NEXT_INDEX;
|
||||||
|
};
|
||||||
|
|
||||||
|
# define INHERIT_GRAPH_TYPE_BASE_CLASS(c) : public DataIndex<c>
|
||||||
|
#else
|
||||||
|
# define INHERIT_GRAPH_TYPE_BASE_CLASS(c) : private NonCopyable
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
|
||||||
|
struct GraphNode INHERIT_GRAPH_TYPE_BASE_CLASS('n')
|
||||||
|
{
|
||||||
|
uint32 EdgeCount;
|
||||||
|
bool bIsIntersection = true; // at this point every node is an intersection.
|
||||||
|
EIntersectionType IntersectionType;
|
||||||
|
float Rotation;
|
||||||
|
std::vector<float> Rots;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GraphHalfEdge INHERIT_GRAPH_TYPE_BASE_CLASS('e')
|
||||||
|
{
|
||||||
|
float Angle;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GraphFace INHERIT_GRAPH_TYPE_BASE_CLASS('f')
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
#undef INHERIT_GRAPH_TYPE_BASE_CLASS
|
||||||
|
|
||||||
|
} // namespace MapGen
|
|
@ -0,0 +1,43 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace MapGen {
|
||||||
|
|
||||||
|
template<typename IT>
|
||||||
|
class CARLA_API ListView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using iterator = IT;
|
||||||
|
|
||||||
|
explicit ListView(iterator begin, iterator end) : Begin(begin), End(end) {}
|
||||||
|
|
||||||
|
template <typename STL_CONTAINER>
|
||||||
|
explicit ListView(STL_CONTAINER &StlContainer) :
|
||||||
|
Begin(iterator(StlContainer.begin())),
|
||||||
|
End(iterator(StlContainer.end())) {}
|
||||||
|
|
||||||
|
ListView(const ListView &) = default;
|
||||||
|
ListView &operator=(const ListView &) = delete;
|
||||||
|
|
||||||
|
iterator begin() const {
|
||||||
|
return Begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator end() const {
|
||||||
|
return End;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const {
|
||||||
|
return Begin == End;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
const iterator Begin;
|
||||||
|
|
||||||
|
const iterator End;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MapGen
|
|
@ -0,0 +1,55 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace MapGen {
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class CARLA_API Position {
|
||||||
|
public:
|
||||||
|
using number_type = T;
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
std::is_arithmetic<number_type>::value &&
|
||||||
|
!std::is_same<number_type, bool>::value, "not a valid number type");
|
||||||
|
|
||||||
|
number_type x;
|
||||||
|
|
||||||
|
number_type y;
|
||||||
|
|
||||||
|
constexpr Position(T X, T Y) : x(X), y(Y) {}
|
||||||
|
|
||||||
|
constexpr bool operator==(const Position &rhs) const {
|
||||||
|
return (x == rhs.x) && (y == rhs.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator!=(const Position &rhs) const {
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Position &operator+=(const Position &rhs) {
|
||||||
|
x += rhs.x;
|
||||||
|
y += rhs.y;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend Position operator+(Position lhs, const Position &rhs) {
|
||||||
|
lhs += rhs;
|
||||||
|
return lhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
Position &operator-=(const Position &rhs) {
|
||||||
|
x -= rhs.x;
|
||||||
|
y -= rhs.y;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend Position operator-(Position lhs, const Position &rhs) {
|
||||||
|
lhs -= rhs;
|
||||||
|
return lhs;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MapGen
|
|
@ -0,0 +1,48 @@
|
||||||
|
// CARLA, Copyright (C) 2017 Computer Vision Center (CVC)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GraphTypes.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace MapGen {
|
||||||
|
|
||||||
|
class CARLA_API RoadSegmentDescription : private NonCopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
void Add(const GraphHalfEdge &Edge) {
|
||||||
|
if (Angle == nullptr) {
|
||||||
|
Angle = MakeUnique<float>(Edge.Angle);
|
||||||
|
} else if (*Angle != Edge.Angle) { /// @todo Use a scale.
|
||||||
|
Angle = nullptr;
|
||||||
|
}
|
||||||
|
_vect.emplace_back(&Edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
const GraphHalfEdge &operator[](size_t i) const {
|
||||||
|
return *_vect[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Size() const {
|
||||||
|
return _vect.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsStraight() const {
|
||||||
|
return Angle.IsValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return nullptr if the road segment is not straight.
|
||||||
|
const float *GetAngle() const {
|
||||||
|
return Angle.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
TUniquePtr<float> Angle = nullptr;
|
||||||
|
|
||||||
|
std::vector<const GraphHalfEdge *> _vect;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MapGen
|
Loading…
Reference in New Issue