Port code for road generation
This commit is contained in:
parent
a79a62a354
commit
778d5cda20
|
@ -4,6 +4,8 @@
|
|||
|
||||
#define LOCTEXT_NAMESPACE "FCarlaModule"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogCarla);
|
||||
|
||||
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
|
||||
|
@ -16,5 +18,5 @@ void FCarlaModule::ShutdownModule()
|
|||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FCarlaModule, Carla)
|
||||
|
||||
IMPLEMENT_MODULE(FCarlaModule, Carla)
|
|
@ -1,9 +1,15 @@
|
|||
// 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
|
||||
|
||||
#include "ModuleManager.h"
|
||||
|
||||
#include "../Common/NonCopyable.h"
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogCarla, Log, All);
|
||||
|
||||
class FCarlaModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
|
@ -11,4 +17,4 @@ public:
|
|||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() 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