├── Config ├── DefaultEditor.ini ├── DefaultGame.ini └── DefaultEngine.ini ├── assets ├── add_class.PNG ├── point_cloud.PNG ├── point_cloud_las.PNG └── point_cloud_generator.PNG ├── Plugins └── PointCloudGeneratorPlugin │ ├── Resources │ └── Icon128.png │ ├── Source │ ├── PointCloudGeneratorPlugin │ │ ├── Public │ │ │ ├── PointCloudGeneratorPlugin.h │ │ │ └── PointCloudGenerator.h │ │ ├── Private │ │ │ ├── PointCloudGeneratorPlugin.cpp │ │ │ └── PointCloudGenerator.cpp │ │ └── PointCloudGeneratorPlugin.Build.cs │ └── ThirdParty │ │ └── npy.h │ └── PointCloudGeneratorPlugin.uplugin ├── Source ├── PointCloudPlugin │ ├── PointCloudPlugin.h │ ├── PointCloudPluginGameModeBase.cpp │ ├── PointCloudPlugin.cpp │ ├── PointCloudPluginGameModeBase.h │ └── PointCloudPlugin.Build.cs ├── PointCloudPlugin.Target.cs └── PointCloudPluginEditor.Target.cs ├── PointCloudPlugin.uproject ├── .gitignore └── README.md /Config/DefaultEditor.ini: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/add_class.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/PointCloudGeneratorUE4/HEAD/assets/add_class.PNG -------------------------------------------------------------------------------- /assets/point_cloud.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/PointCloudGeneratorUE4/HEAD/assets/point_cloud.PNG -------------------------------------------------------------------------------- /assets/point_cloud_las.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/PointCloudGeneratorUE4/HEAD/assets/point_cloud_las.PNG -------------------------------------------------------------------------------- /assets/point_cloud_generator.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/PointCloudGeneratorUE4/HEAD/assets/point_cloud_generator.PNG -------------------------------------------------------------------------------- /Plugins/PointCloudGeneratorPlugin/Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/PointCloudGeneratorUE4/HEAD/Plugins/PointCloudGeneratorPlugin/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/PointCloudPlugin/PointCloudPlugin.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | -------------------------------------------------------------------------------- /Source/PointCloudPlugin/PointCloudPluginGameModeBase.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "PointCloudPluginGameModeBase.h" 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Config/DefaultGame.ini: -------------------------------------------------------------------------------- 1 | [/Script/EngineSettings.GeneralProjectSettings] 2 | ProjectID=9EA8B7E6447760C93D69C99D4BD9363B 3 | 4 | [StartupActions] 5 | bAddPacks=True 6 | InsertPack=(PackSource="StarterContent.upack,PackName="StarterContent") 7 | -------------------------------------------------------------------------------- /Source/PointCloudPlugin/PointCloudPlugin.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "PointCloudPlugin.h" 4 | #include "Modules/ModuleManager.h" 5 | 6 | IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, PointCloudPlugin, "PointCloudPlugin" ); 7 | -------------------------------------------------------------------------------- /PointCloudPlugin.uproject: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "EngineAssociation": "4.20", 4 | "Category": "", 5 | "Description": "", 6 | "Modules": [ 7 | { 8 | "Name": "PointCloudPlugin", 9 | "Type": "Runtime", 10 | "LoadingPhase": "Default" 11 | } 12 | ], 13 | "Plugins": [ 14 | { 15 | "Name": "PointCloudGeneratorPlugin", 16 | "Enabled": true 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /Source/PointCloudPlugin.Target.cs: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class PointCloudPluginTarget : TargetRules 7 | { 8 | public PointCloudPluginTarget(TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Game; 11 | 12 | ExtraModuleNames.AddRange( new string[] { "PointCloudPlugin" } ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Source/PointCloudPluginEditor.Target.cs: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class PointCloudPluginEditorTarget : TargetRules 7 | { 8 | public PointCloudPluginEditorTarget(TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Editor; 11 | 12 | ExtraModuleNames.AddRange( new string[] { "PointCloudPlugin" } ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Source/PointCloudPlugin/PointCloudPluginGameModeBase.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "GameFramework/GameModeBase.h" 7 | #include "PointCloudPluginGameModeBase.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS() 13 | class POINTCLOUDPLUGIN_API APointCloudPluginGameModeBase : public AGameModeBase 14 | { 15 | GENERATED_BODY() 16 | 17 | 18 | 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /Plugins/PointCloudGeneratorPlugin/Source/PointCloudGeneratorPlugin/Public/PointCloudGeneratorPlugin.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | class FPointCloudGeneratorPluginModule : public IModuleInterface 9 | { 10 | public: 11 | 12 | /** IModuleInterface implementation */ 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | }; 16 | -------------------------------------------------------------------------------- /Config/DefaultEngine.ini: -------------------------------------------------------------------------------- 1 | [URL] 2 | [/Script/EngineSettings.GameMapsSettings] 3 | EditorStartupMap=/Game/StarterContent/Maps/Minimal_Default 4 | GameDefaultMap=/Game/StarterContent/Maps/Minimal_Default 5 | GlobalDefaultGameMode="/Script/PointCloudPlugin.PointCloudPluginGameMode" 6 | 7 | [/Script/HardwareTargeting.HardwareTargetingSettings] 8 | TargetedHardwareClass=Desktop 9 | AppliedTargetedHardwareClass=Desktop 10 | DefaultGraphicsPerformance=Maximum 11 | AppliedDefaultGraphicsPerformance=Maximum 12 | 13 | 14 | -------------------------------------------------------------------------------- /Plugins/PointCloudGeneratorPlugin/PointCloudGeneratorPlugin.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "PointCloudGeneratorPlugin", 6 | "Description": "Point Cloud Generation", 7 | "Category": "Other", 8 | "CreatedBy": "Jeremy Castagno", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "Installed": false, 16 | "Modules": [ 17 | { 18 | "Name": "PointCloudGeneratorPlugin", 19 | "Type": "Developer", 20 | "LoadingPhase": "Default" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /Plugins/PointCloudGeneratorPlugin/Source/PointCloudGeneratorPlugin/Private/PointCloudGeneratorPlugin.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "PointCloudGeneratorPlugin.h" 4 | 5 | #define LOCTEXT_NAMESPACE "FPointCloudGeneratorPluginModule" 6 | 7 | void FPointCloudGeneratorPluginModule::StartupModule() 8 | { 9 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 10 | } 11 | 12 | void FPointCloudGeneratorPluginModule::ShutdownModule() 13 | { 14 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 15 | // we call this function before unloading the module. 16 | } 17 | 18 | #undef LOCTEXT_NAMESPACE 19 | 20 | IMPLEMENT_MODULE(FPointCloudGeneratorPluginModule, PointCloudGeneratorPlugin) -------------------------------------------------------------------------------- /Source/PointCloudPlugin/PointCloudPlugin.Build.cs: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class PointCloudPlugin : ModuleRules 6 | { 7 | public PointCloudPlugin(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); 12 | 13 | PrivateDependencyModuleNames.AddRange(new string[] { }); 14 | 15 | // Uncomment if you are using Slate UI 16 | // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); 17 | 18 | // Uncomment if you are using online features 19 | // PrivateDependencyModuleNames.Add("OnlineSubsystem"); 20 | 21 | // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | *.npy 5 | *.json 6 | 7 | Content/StarterContent 8 | 9 | # Compiled Object files 10 | *.slo 11 | *.lo 12 | *.o 13 | *.obj 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Compiled Dynamic libraries 20 | *.so 21 | *.dylib 22 | *.dll 23 | 24 | # Fortran module files 25 | *.mod 26 | 27 | # Compiled Static libraries 28 | *.lai 29 | *.la 30 | *.a 31 | *.lib 32 | 33 | # Executables 34 | *.exe 35 | *.out 36 | *.app 37 | *.ipa 38 | 39 | # These project files can be generated by the engine 40 | *.xcodeproj 41 | *.xcworkspace 42 | *.sln 43 | *.suo 44 | *.opensdf 45 | *.sdf 46 | *.VC.db 47 | *.VC.opendb 48 | 49 | # Precompiled Assets 50 | SourceArt/**/*.png 51 | SourceArt/**/*.tga 52 | 53 | # Binary Files 54 | Binaries/* 55 | Plugins/*/Binaries/* 56 | 57 | # Builds 58 | Build/* 59 | 60 | # Whitelist PakBlacklist-.txt files 61 | !Build/*/ 62 | Build/*/** 63 | !Build/*/PakBlacklist*.txt 64 | 65 | # Don't ignore icon files in Build 66 | !Build/**/*.ico 67 | 68 | # Built data for maps 69 | *_BuiltData.uasset 70 | 71 | # Configuration files generated by the Editor 72 | Saved/* 73 | 74 | # Compiled source files for the engine to use 75 | Intermediate/* 76 | Plugins/*/Intermediate/* 77 | 78 | # Cache files for the editor to use 79 | DerivedDataCache/* 80 | -------------------------------------------------------------------------------- /Plugins/PointCloudGeneratorPlugin/Source/PointCloudGeneratorPlugin/PointCloudGeneratorPlugin.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.IO; ////for the Path 5 | 6 | public class PointCloudGeneratorPlugin : ModuleRules 7 | { 8 | ///useful automations 9 | public string ModulePath 10 | { 11 | get { return ModuleDirectory; } 12 | } 13 | 14 | public string ThirdPartyPath 15 | { 16 | get { return Path.GetFullPath(Path.Combine(ModulePath, "../ThirdParty/")); } 17 | } 18 | public PointCloudGeneratorPlugin(ReadOnlyTargetRules Target) : base(Target) 19 | { 20 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 21 | 22 | 23 | PublicIncludePaths.AddRange( 24 | new string[] { 25 | ThirdPartyPath 26 | } 27 | ); 28 | 29 | 30 | PrivateIncludePaths.AddRange( 31 | new string[] { 32 | ThirdPartyPath 33 | } 34 | ); 35 | 36 | 37 | PublicDependencyModuleNames.AddRange( 38 | new string[] 39 | { 40 | "Core", 41 | // ... add other public dependencies that you statically link with here ... 42 | } 43 | ); 44 | 45 | 46 | PrivateDependencyModuleNames.AddRange( 47 | new string[] 48 | { 49 | "CoreUObject", 50 | "Engine", 51 | "Slate", 52 | "SlateCore", 53 | // ... add private dependencies that you statically link with here ... 54 | } 55 | ); 56 | 57 | 58 | DynamicallyLoadedModuleNames.AddRange( 59 | new string[] 60 | { 61 | // ... add any modules that your module loads dynamically here ... 62 | } 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Plugins/PointCloudGeneratorPlugin/Source/PointCloudGeneratorPlugin/Public/PointCloudGenerator.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "CoreMinimal.h" 9 | #include "Engine/TriggerVolume.h" 10 | #include "EngineUtils.h" 11 | #include "Engine.h" 12 | #include "GameFramework/Actor.h" 13 | #include "Engine/World.h" 14 | #include "npy.h" 15 | #include "PointCloudGenerator.generated.h" 16 | 17 | 18 | struct PointCloud 19 | { 20 | // this map will hold the class mapping values (string label of class -> interger value) 21 | std::vector points; 22 | // this holds the points received from the point cloud 23 | std::unordered_map class_mapping; 24 | }; 25 | 26 | /** 27 | * 28 | */ 29 | UCLASS() 30 | class POINTCLOUDGENERATORPLUGIN_API APointCloudGenerator : public ATriggerVolume 31 | { 32 | 33 | GENERATED_BODY() 34 | public: 35 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Point Cloud Generation") 36 | float resolution = 100.0; 37 | 38 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Point Cloud Generation", META = (Name = "Show Trace")) 39 | bool showTrace = false; 40 | 41 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Point Cloud Generation", META = (Name = "Record Classes")) 42 | bool recordClasses = true; 43 | 44 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Point Cloud Generation", META = (Name = "Save Directory")) 45 | FString saveDirectory = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()); 46 | 47 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Point Cloud Generation", META = (Name = "Enabled")) 48 | bool enabled = true; 49 | 50 | 51 | 52 | protected: 53 | // Called when the game starts or when spawned 54 | virtual void BeginPlay() override; 55 | void GatherPoints(FVector start, FVector range, PointCloud &pc, float res, bool trace, bool recordClasses); 56 | void SaveFile(FString saveDirectory, PointCloud &pc); 57 | void TimerElapsed(); 58 | 59 | 60 | }; 61 | 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Installation 4 | 5 | When adding this plugin to your project perform the following steps: 6 | 7 | * Copy `Plugins/PointCloudGeneratorPlugin` to **your** projects `Plugins` folder 8 | * Delete the `Binaries` and `Intermediate` folders of **your** project folder 9 | * Edit the text in `YOUR_PROJECT_NAME.uproject` to add this newly copied plugin. i.e. 10 | ```json 11 | "Plugins": [ 12 | { 13 | "Name": "PointCloudGeneratorPlugin", 14 | "Enabled": true 15 | } 16 | ] 17 | ``` 18 | * Right Click `YOUR_PROJECT_NAME.uproject` and select `Generate Visual Studio project files` 19 | * Open up your project in Visual Studio, i.e. double click `YOUR_PROJECT_NAME.sln`. 20 | * Rebuild the project. I set the solution platform as "Development Editor" 21 | * Now open up the project in unreal editor. ie double click `YOUR_PROJECT_NAME.uproject`. 22 | 23 | 24 | ## How to use 25 | 26 | After adding the plugin you should be able to see the PointCloudGeneratorPlugin C++ Classes available in the File Browser near the bottom of the UnrealEngine Editor. Simply drag in PointCloudGenerator class (should look like a triggered volume icon) into the world. 27 | 28 | ![Screenshot](assets/add_class.PNG) 29 | 30 | This volumetric actor is just a volume that you can size up with the brush settings. Just expand as you would any volume and fill the space that you want the point cloud to be captured. 31 | 32 | There are additional settings that you can change as well: 33 | 34 | 1. Resolution (cm, unreal units) - How far apart should every point in the point cloud be in the X and Y directions. 35 | 2. Show Trace - Show the debug line trace just to make sure its working. Default is off. 36 | 3. Record Classes - Record the classes that are hit? Every ray trace can give us information about *what** object was hit, not just position. This can be recorded and saved as a 4'th dimension in the point cloud. 37 | 4. Save Directory - What directory should we save to. Defaults to project directory. 38 | 39 | ![Screenshot](assets/point_cloud_generator.PNG) 40 | 41 | 42 | Once setting are configured just press the Play button on the editor. The line trace (ray cast) will begin one second after the play button is pressed and generate the point cloud. Data will be saved in a 2 dimensional numpy array in the save directory of choice. A class mapping file is provided as well if Record Classes is checked. 43 | 44 | Example plot in matplotlib: 45 | 46 | ![Screenshot](assets/point_cloud.PNG) 47 | 48 | Example plot for large point clouds using plas.io: 49 | 50 | ![Screenshot](assets/point_cloud_las.PNG) 51 | 52 | 53 | ## Logging or Errors 54 | 55 | Check the console output for any errors or information. 56 | 57 | 58 | ## Visualization 59 | 60 | You can visualize the data in python using standard matplotlib data (if the point cloud is not to big. i.e. < 10,000 points). If its pretty large you will need a GPU accelerated visualizer, e.g. WebGL. 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Plugins/PointCloudGeneratorPlugin/Source/PointCloudGeneratorPlugin/Private/PointCloudGenerator.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | // Fill out your copyright notice in the Description page of Project Settings. 4 | 5 | #include "PointCloudGenerator.h" 6 | #include "DrawDebugHelpers.h" 7 | 8 | 9 | // Called when the game starts or when spawned 10 | void APointCloudGenerator::BeginPlay() 11 | { 12 | Super::BeginPlay(); 13 | 14 | FTimerHandle UnusedHandle; 15 | // Timer put in because of paranio.... 16 | if (enabled) 17 | { 18 | GetWorldTimerManager().SetTimer( 19 | UnusedHandle, this, &APointCloudGenerator::TimerElapsed, 1, false); 20 | } 21 | else 22 | { 23 | UE_LOG(LogTemp, Display, TEXT("PointCloudGenerator disabled.")); 24 | } 25 | } 26 | 27 | 28 | void APointCloudGenerator::TimerElapsed() 29 | { 30 | auto bounds = this->GetBounds(); 31 | 32 | auto start_x = bounds.Origin.X - bounds.BoxExtent.X; 33 | auto start_y = bounds.Origin.Y - bounds.BoxExtent.Y; 34 | auto start_z = bounds.Origin.Z + bounds.BoxExtent.Z; 35 | 36 | auto range_x = bounds.BoxExtent.X * 2; 37 | auto range_y = bounds.BoxExtent.Y * 2; 38 | auto range_z = bounds.BoxExtent.Z * 2; 39 | 40 | FVector start(start_x, start_y, start_z); 41 | FVector range(range_x, range_y, range_z); 42 | UE_LOG(LogTemp, Display, TEXT("Resolution: %f; Trace: %d; Record Classes: %d"), resolution, showTrace, recordClasses); 43 | 44 | // Build Point Cloud 45 | PointCloud pc; 46 | GatherPoints(start, range, pc, resolution, showTrace, recordClasses); 47 | 48 | // Save data 49 | SaveFile(saveDirectory, pc); 50 | } 51 | 52 | 53 | void APointCloudGenerator::GatherPoints(FVector start, FVector range, PointCloud &pc, float res = 25.0, bool trace = false, bool recordClasses = true) 54 | { 55 | // Collision parameters and settings 56 | FCollisionQueryParams trace_params; 57 | FHitResult hit_result = FHitResult(ForceInit); 58 | trace_params.AddIgnoredActor(this); 59 | 60 | int numClasses = 0; 61 | 62 | for (float x = start.X; x < start.X + range.X; x += res) 63 | { 64 | for (float y = start.Y; y < start.Y + range.Y; y += res) 65 | { 66 | hit_result = FHitResult(ForceInit); 67 | FVector trace_start(x, y, start.Z); 68 | FVector trace_end(x, y, start.Z - range.Z); 69 | auto hit = GetWorld()->LineTraceSingleByChannel(hit_result, trace_start, trace_end, ECC_Visibility, trace_params); 70 | if (hit) 71 | { 72 | pc.points.emplace_back(hit_result.ImpactPoint.X); 73 | pc.points.emplace_back(hit_result.ImpactPoint.Y); 74 | pc.points.emplace_back(hit_result.ImpactPoint.Z); 75 | 76 | if (recordClasses) 77 | { 78 | auto display_name = hit_result.GetActor()->GetActorLabel(); 79 | std::string display_name_str(TCHAR_TO_UTF8(*display_name)); 80 | // Check if the key exists 81 | if (pc.class_mapping.find(display_name_str) != pc.class_mapping.end()) 82 | { 83 | // convert integer class mapping to float and add to point vector 84 | pc.points.emplace_back(static_cast(pc.class_mapping[display_name_str])); 85 | } 86 | else 87 | { 88 | // else create new mapping and increment mapping count 89 | pc.class_mapping[display_name_str] = numClasses; 90 | pc.points.emplace_back(static_cast(numClasses)); 91 | numClasses += 1; 92 | } 93 | //UE_LOG(LogTemp, Warning, TEXT("PointCloudGenerator: Hit Component %s, at (%f, %f)"), *display_name, x, y); 94 | } 95 | 96 | 97 | if (trace) 98 | { 99 | DrawDebugLine(GetWorld(), trace_start, hit_result.ImpactPoint, FColor::Green, true, 1, 0, 1); 100 | } 101 | } 102 | else 103 | { 104 | //UE_LOG(LogTemp, Warning, TEXT("PointCloudGenerator: No object hit in raycast (%f, %f)"), x, y); 105 | if (trace) 106 | { 107 | DrawDebugLine(GetWorld(), trace_start, trace_end, FColor::Red, true, 1, 0, 1); 108 | } 109 | } 110 | 111 | } 112 | 113 | } 114 | 115 | } 116 | 117 | int Map2JSON(std::string fname, std::unordered_map &m) { 118 | int count = 0; 119 | if (m.empty()) 120 | return 0; 121 | 122 | // Now we want to transfer our unorderem_map into an ordered map 123 | // unordered map was faster, but now we care about human readable output 124 | std::map ordered_map; 125 | for (auto it = m.begin(); it != m.end(); it++) { 126 | ordered_map[it->second] = it->first; 127 | } 128 | 129 | FILE *fp = fopen(fname.c_str(), "w"); 130 | if (!fp) 131 | return -errno; 132 | fprintf(fp, "{\n"); 133 | fprintf(fp, " \"classes\": [\n"); 134 | for (auto it = ordered_map.begin(); it != --ordered_map.end(); it++) { 135 | fprintf(fp, " \"%s\",\n", it->second.c_str()); 136 | count++; 137 | } 138 | fprintf(fp, " \"%s\"\n", (--ordered_map.end())->second.c_str()); 139 | fprintf(fp, " ]\n"); 140 | fprintf(fp, "}\n"); 141 | 142 | fclose(fp); 143 | return count; 144 | } 145 | 146 | void APointCloudGenerator::SaveFile(FString saveDir, PointCloud &pc) 147 | { 148 | if (FPaths::DirectoryExists(saveDir)) 149 | { 150 | const long unsigned cols = recordClasses ? 4 : 3; 151 | const long unsigned rows = pc.points.size() / cols; 152 | const long unsigned leshape[] = { rows, cols }; 153 | 154 | auto numpyPathF = FPaths::Combine(saveDir, FString("point_cloud.npy")); 155 | UE_LOG(LogTemp, Display, TEXT("Saving file: %s"), *numpyPathF); 156 | npy::SaveArrayAsNumpy(TCHAR_TO_UTF8(*numpyPathF), false, 2, leshape, pc.points); 157 | if (recordClasses) 158 | { 159 | auto classesPathF = FPaths::Combine(saveDir, FString("point_cloud_classes.json")); 160 | UE_LOG(LogTemp, Display, TEXT("Saving file: %s"), *classesPathF); 161 | Map2JSON(TCHAR_TO_UTF8(*classesPathF), pc.class_mapping); 162 | } 163 | } 164 | else 165 | { 166 | UE_LOG(LogTemp, Error, TEXT("Error! Directory does not exist: %s"), *saveDir); 167 | 168 | } 169 | 170 | } 171 | 172 | 173 | -------------------------------------------------------------------------------- /Plugins/PointCloudGeneratorPlugin/Source/ThirdParty/npy.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Leon Merten Lohse 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in 10 | all copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | */ 19 | 20 | #ifndef NPY_H 21 | #define NPY_H 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | 37 | namespace npy { 38 | 39 | /* Compile-time test for byte order. 40 | If your compiler does not define these per default, you may want to define 41 | one of these constants manually. 42 | Defaults to little endian order. */ 43 | #if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \ 44 | defined(__BIG_ENDIAN__) || \ 45 | defined(__ARMEB__) || \ 46 | defined(__THUMBEB__) || \ 47 | defined(__AARCH64EB__) || \ 48 | defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) 49 | const bool big_endian = true; 50 | #else 51 | const bool big_endian = false; 52 | #endif 53 | 54 | 55 | const char magic_string[] = "\x93NUMPY"; 56 | const size_t magic_string_length = 6; 57 | 58 | const char little_endian_char = '<'; 59 | const char big_endian_char = '>'; 60 | const char no_endian_char = '|'; 61 | 62 | constexpr char host_endian_char = (big_endian ? 63 | big_endian_char : 64 | little_endian_char); 65 | 66 | /* npy array length */ 67 | typedef unsigned long int ndarray_len_t; 68 | 69 | inline void write_magic(std::ostream& ostream, unsigned char v_major = 1, unsigned char v_minor = 0) { 70 | ostream.write(magic_string, magic_string_length); 71 | ostream.put(v_major); 72 | ostream.put(v_minor); 73 | } 74 | 75 | inline void read_magic(std::istream& istream, unsigned char& v_major, unsigned char& v_minor) { 76 | char buf[magic_string_length + 2]; 77 | istream.read(buf, magic_string_length + 2); 78 | 79 | if (!istream) { 80 | throw std::runtime_error("io error: failed reading file"); 81 | } 82 | 83 | if (0 != std::memcmp(buf, magic_string, magic_string_length)) 84 | throw std::runtime_error("this file does not have a valid npy format."); 85 | 86 | v_major = buf[magic_string_length]; 87 | v_minor = buf[magic_string_length + 1]; 88 | } 89 | 90 | // typestring magic 91 | struct Typestring { 92 | private: 93 | char c_endian; 94 | char c_type; 95 | int len; 96 | 97 | public: 98 | inline std::string str() { 99 | const size_t max_buflen = 16; 100 | char buf[max_buflen]; 101 | std::sprintf(buf, "%c%c%u", c_endian, c_type, len); 102 | return std::string(buf); 103 | } 104 | 105 | Typestring(const std::vector& v) 106 | :c_endian{ host_endian_char }, c_type{ 'f' }, len{ sizeof(float) } {} 107 | Typestring(const std::vector& v) 108 | :c_endian{ host_endian_char }, c_type{ 'f' }, len{ sizeof(double) } {} 109 | Typestring(const std::vector& v) 110 | :c_endian{ host_endian_char }, c_type{ 'f' }, len{ sizeof(long double) } {} 111 | 112 | Typestring(const std::vector& v) 113 | :c_endian{ no_endian_char }, c_type{ 'i' }, len{ sizeof(char) } {} 114 | Typestring(const std::vector& v) 115 | :c_endian{ host_endian_char }, c_type{ 'i' }, len{ sizeof(short) } {} 116 | Typestring(const std::vector& v) 117 | :c_endian{ host_endian_char }, c_type{ 'i' }, len{ sizeof(int) } {} 118 | Typestring(const std::vector& v) 119 | :c_endian{ host_endian_char }, c_type{ 'i' }, len{ sizeof(long) } {} 120 | Typestring(const std::vector& v) :c_endian{ host_endian_char }, c_type{ 'i' }, len{ sizeof(long long) } {} 121 | 122 | Typestring(const std::vector& v) 123 | :c_endian{ no_endian_char }, c_type{ 'u' }, len{ sizeof(unsigned char) } {} 124 | Typestring(const std::vector& v) 125 | :c_endian{ host_endian_char }, c_type{ 'u' }, len{ sizeof(unsigned short) } {} 126 | Typestring(const std::vector& v) 127 | :c_endian{ host_endian_char }, c_type{ 'u' }, len{ sizeof(unsigned int) } {} 128 | Typestring(const std::vector& v) 129 | :c_endian{ host_endian_char }, c_type{ 'u' }, len{ sizeof(unsigned long) } {} 130 | Typestring(const std::vector& v) 131 | :c_endian{ host_endian_char }, c_type{ 'u' }, len{ sizeof(unsigned long long) } {} 132 | 133 | Typestring(const std::vector>& v) 134 | :c_endian{ host_endian_char }, c_type{ 'c' }, len{ sizeof(std::complex) } {} 135 | Typestring(const std::vector>& v) 136 | :c_endian{ host_endian_char }, c_type{ 'c' }, len{ sizeof(std::complex) } {} 137 | Typestring(const std::vector>& v) 138 | :c_endian{ host_endian_char }, c_type{ 'c' }, len{ sizeof(std::complex) } {} 139 | }; 140 | 141 | inline void parse_typestring(std::string typestring) { 142 | std::regex re("'([<>|])([ifuc])(\\d+)'"); 143 | std::smatch sm; 144 | 145 | std::regex_match(typestring, sm, re); 146 | 147 | if (sm.size() != 4) { 148 | throw std::runtime_error("invalid typestring"); 149 | } 150 | } 151 | 152 | namespace pyparse { 153 | 154 | /** 155 | Removes leading and trailing whitespaces 156 | */ 157 | inline std::string trim(const std::string& str) { 158 | const std::string whitespace = " \t"; 159 | auto begin = str.find_first_not_of(whitespace); 160 | 161 | if (begin == std::string::npos) 162 | return ""; 163 | 164 | auto end = str.find_last_not_of(whitespace); 165 | 166 | return str.substr(begin, end - begin + 1); 167 | } 168 | 169 | 170 | inline std::string get_value_from_map(const std::string& mapstr) { 171 | size_t sep_pos = mapstr.find_first_of(":"); 172 | if (sep_pos == std::string::npos) 173 | return ""; 174 | 175 | std::string tmp = mapstr.substr(sep_pos + 1); 176 | return trim(tmp); 177 | } 178 | 179 | /** 180 | Parses the string representation of a Python dict 181 | The keys need to be known and may not appear anywhere else in the data. 182 | */ 183 | inline std::unordered_map parse_dict(std::string in, std::vector& keys) { 184 | 185 | std::unordered_map map; 186 | 187 | if (keys.size() == 0) 188 | return map; 189 | 190 | in = trim(in); 191 | 192 | // unwrap dictionary 193 | if ((in.front() == '{') && (in.back() == '}')) 194 | in = in.substr(1, in.length() - 2); 195 | else 196 | throw std::runtime_error("Not a Python dictionary."); 197 | 198 | std::vector> positions; 199 | 200 | for (auto const& value : keys) { 201 | size_t pos = in.find("'" + value + "'"); 202 | 203 | if (pos == std::string::npos) 204 | throw std::runtime_error("Missing '" + value + "' key."); 205 | 206 | std::pair position_pair{ pos, value }; 207 | positions.push_back(position_pair); 208 | } 209 | 210 | // sort by position in dict 211 | std::sort(positions.begin(), positions.end()); 212 | 213 | for (size_t i = 0; i < positions.size(); ++i) { 214 | std::string raw_value; 215 | size_t begin{ positions[i].first }; 216 | size_t end{ std::string::npos }; 217 | 218 | std::string key = positions[i].second; 219 | 220 | if (i + 1 < positions.size()) 221 | end = positions[i + 1].first; 222 | 223 | raw_value = in.substr(begin, end - begin); 224 | 225 | raw_value = trim(raw_value); 226 | 227 | if (raw_value.back() == ',') 228 | raw_value.pop_back(); 229 | 230 | map[key] = get_value_from_map(raw_value); 231 | } 232 | 233 | return map; 234 | } 235 | 236 | /** 237 | Parses the string representation of a Python boolean 238 | */ 239 | inline bool parse_bool(const std::string& in) { 240 | if (in == "True") 241 | return true; 242 | if (in == "False") 243 | return false; 244 | 245 | throw std::runtime_error("Invalid python boolan."); 246 | } 247 | 248 | /** 249 | Parses the string representation of a Python str 250 | */ 251 | inline std::string parse_str(const std::string& in) { 252 | if ((in.front() == '\'') && (in.back() == '\'')) 253 | return in.substr(1, in.length() - 2); 254 | 255 | throw std::runtime_error("Invalid python string."); 256 | } 257 | 258 | /** 259 | Parses the string represenatation of a Python tuple into a vector of its items 260 | */ 261 | inline std::vector parse_tuple(std::string in) { 262 | std::vector v; 263 | const char seperator = ','; 264 | 265 | in = trim(in); 266 | 267 | if ((in.front() == '(') && (in.back() == ')')) 268 | in = in.substr(1, in.length() - 2); 269 | else 270 | throw std::runtime_error("Invalid Python tuple."); 271 | 272 | std::istringstream iss(in); 273 | 274 | for (std::string token; std::getline(iss, token, seperator);) { 275 | v.push_back(token); 276 | } 277 | 278 | return v; 279 | } 280 | 281 | template 282 | inline std::string write_tuple(const std::vector& v) { 283 | if (v.size() == 0) 284 | return ""; 285 | 286 | std::ostringstream ss; 287 | 288 | if (v.size() == 1) { 289 | ss << "(" << v.front() << ",)"; 290 | } 291 | else { 292 | const std::string delimiter = ", "; 293 | // v.size() > 1 294 | ss << "("; 295 | std::copy(v.begin(), v.end() - 1, std::ostream_iterator(ss, delimiter.c_str())); 296 | ss << v.back(); 297 | ss << ")"; 298 | } 299 | 300 | return ss.str(); 301 | } 302 | 303 | inline std::string write_boolean(bool b) { 304 | if (b) 305 | return "True"; 306 | else 307 | return "False"; 308 | } 309 | 310 | } // namespace pyparse 311 | 312 | 313 | inline void parse_header(std::string header, std::string& descr, bool& fortran_order, std::vector& shape) { 314 | /* 315 | The first 6 bytes are a magic string: exactly "x93NUMPY". 316 | The next 1 byte is an unsigned byte: the major version number of the file format, e.g. x01. 317 | The next 1 byte is an unsigned byte: the minor version number of the file format, e.g. x00. Note: the version of the file format is not tied to the version of the numpy package. 318 | The next 2 bytes form a little-endian unsigned short int: the length of the header data HEADER_LEN. 319 | The next HEADER_LEN bytes form the header data describing the array's format. It is an ASCII string which contains a Python literal expression of a dictionary. It is terminated by a newline ('n') and padded with spaces ('x20') to make the total length of the magic string + 4 + HEADER_LEN be evenly divisible by 16 for alignment purposes. 320 | The dictionary contains three keys: 321 | "descr" : dtype.descr 322 | An object that can be passed as an argument to the numpy.dtype() constructor to create the array's dtype. 323 | "fortran_order" : bool 324 | Whether the array data is Fortran-contiguous or not. Since Fortran-contiguous arrays are a common form of non-C-contiguity, we allow them to be written directly to disk for efficiency. 325 | "shape" : tuple of int 326 | The shape of the array. 327 | For repeatability and readability, this dictionary is formatted using pprint.pformat() so the keys are in alphabetic order. 328 | */ 329 | 330 | // remove trailing newline 331 | if (header.back() != '\n') 332 | throw std::runtime_error("invalid header"); 333 | header.pop_back(); 334 | 335 | // parse the dictionary 336 | std::vector keys{ "descr", "fortran_order", "shape" }; 337 | auto dict_map = npy::pyparse::parse_dict(header, keys); 338 | 339 | if (dict_map.size() == 0) 340 | throw std::runtime_error("invalid dictionary in header"); 341 | 342 | std::string descr_s = dict_map["descr"]; 343 | std::string fortran_s = dict_map["fortran_order"]; 344 | std::string shape_s = dict_map["shape"]; 345 | 346 | // TODO: extract info from typestring 347 | parse_typestring(descr_s); 348 | // remove 349 | descr = npy::pyparse::parse_str(descr_s); 350 | 351 | // convert literal Python bool to C++ bool 352 | fortran_order = npy::pyparse::parse_bool(fortran_s); 353 | 354 | // parse the shape tuple 355 | auto shape_v = npy::pyparse::parse_tuple(shape_s); 356 | if (shape_v.size() == 0) 357 | throw std::runtime_error("invalid shape tuple in header"); 358 | 359 | for (auto item : shape_v) { 360 | ndarray_len_t dim = static_cast(std::stoul(item)); 361 | shape.push_back(dim); 362 | } 363 | } 364 | 365 | 366 | inline std::string write_header_dict(const std::string& descr, bool fortran_order, const std::vector& shape) { 367 | std::string s_fortran_order = npy::pyparse::write_boolean(fortran_order); 368 | std::string shape_s = npy::pyparse::write_tuple(shape); 369 | 370 | return "{'descr': '" + descr + "', 'fortran_order': " + s_fortran_order + ", 'shape': " + shape_s + ", }"; 371 | } 372 | 373 | inline void write_header(std::ostream& out, const std::string& descr, bool fortran_order, const std::vector& shape_v) 374 | { 375 | std::string header_dict = write_header_dict(descr, fortran_order, shape_v); 376 | 377 | size_t length = magic_string_length + 2 + 2 + header_dict.length() + 1; 378 | 379 | unsigned char version[2] = { 1, 0 }; 380 | if (length >= 255 * 255) { 381 | length = magic_string_length + 2 + 4 + header_dict.length() + 1; 382 | version[0] = 2; 383 | version[1] = 0; 384 | } 385 | size_t padding_len = 16 - length % 16; 386 | std::string padding(padding_len, ' '); 387 | 388 | // write magic 389 | write_magic(out, version[0], version[1]); 390 | 391 | // write header length 392 | if (version[0] == 1 && version[1] == 0) { 393 | char header_len_le16[2]; 394 | uint16_t header_len = header_dict.length() + padding.length() + 1; 395 | 396 | header_len_le16[0] = (header_len >> 0) & 0xff; 397 | header_len_le16[1] = (header_len >> 8) & 0xff; 398 | out.write(reinterpret_cast(header_len_le16), 2); 399 | } 400 | else { 401 | char header_len_le32[4]; 402 | uint32_t header_len = header_dict.length() + padding.length() + 1; 403 | 404 | header_len_le32[0] = (header_len >> 0) & 0xff; 405 | header_len_le32[1] = (header_len >> 8) & 0xff; 406 | header_len_le32[2] = (header_len >> 16) & 0xff; 407 | header_len_le32[3] = (header_len >> 24) & 0xff; 408 | out.write(reinterpret_cast(header_len_le32), 4); 409 | } 410 | 411 | out << header_dict << padding << '\n'; 412 | } 413 | 414 | inline std::string read_header(std::istream& istream) { 415 | // check magic bytes an version number 416 | unsigned char v_major, v_minor; 417 | read_magic(istream, v_major, v_minor); 418 | 419 | uint32_t header_length; 420 | if (v_major == 1 && v_minor == 0) { 421 | 422 | char header_len_le16[2]; 423 | istream.read(header_len_le16, 2); 424 | header_length = (header_len_le16[0] << 0) | (header_len_le16[1] << 8); 425 | 426 | if ((magic_string_length + 2 + 2 + header_length) % 16 != 0) { 427 | // TODO: display warning 428 | } 429 | } 430 | else if (v_major == 2 && v_minor == 0) { 431 | char header_len_le32[4]; 432 | istream.read(header_len_le32, 4); 433 | 434 | header_length = (header_len_le32[0] << 0) | (header_len_le32[1] << 8) 435 | | (header_len_le32[2] << 16) | (header_len_le32[3] << 24); 436 | 437 | if ((magic_string_length + 2 + 4 + header_length) % 16 != 0) { 438 | // TODO: display warning 439 | } 440 | } 441 | else { 442 | throw std::runtime_error("unsupported file format version"); 443 | } 444 | 445 | auto buf_v = std::vector(); 446 | buf_v.reserve(header_length); 447 | istream.read(buf_v.data(), header_length); 448 | std::string header(buf_v.data(), header_length); 449 | 450 | return header; 451 | } 452 | 453 | inline ndarray_len_t comp_size(const std::vector& shape) { 454 | ndarray_len_t size = 1; 455 | for (ndarray_len_t i : shape) 456 | size *= i; 457 | 458 | return size; 459 | } 460 | 461 | template 462 | inline void SaveArrayAsNumpy(const std::string& filename, bool fortran_order, unsigned int n_dims, const unsigned long shape[], const std::vector& data) 463 | { 464 | Typestring typestring_o(data); 465 | std::string typestring = typestring_o.str(); 466 | 467 | std::ofstream stream(filename, std::ofstream::binary); 468 | if (!stream) { 469 | throw std::runtime_error("io error: failed to open a file."); 470 | } 471 | 472 | std::vector shape_v(shape, shape + n_dims); 473 | write_header(stream, typestring, fortran_order, shape_v); 474 | 475 | auto size = static_cast(comp_size(shape_v)); 476 | 477 | stream.write(reinterpret_cast(data.data()), sizeof(Scalar) * size); 478 | } 479 | 480 | 481 | template 482 | inline void LoadArrayFromNumpy(const std::string& filename, std::vector& shape, std::vector& data) 483 | { 484 | std::ifstream stream(filename, std::ifstream::binary); 485 | if (!stream) { 486 | throw std::runtime_error("io error: failed to open a file."); 487 | } 488 | 489 | std::string header = read_header(stream); 490 | 491 | // parse header 492 | bool fortran_order; 493 | std::string typestr; 494 | 495 | parse_header(header, typestr, fortran_order, shape); 496 | 497 | // check if the typestring matches the given one 498 | Typestring typestring_o{ data }; 499 | std::string expect_typestr = typestring_o.str(); 500 | if (typestr != expect_typestr) { 501 | throw std::runtime_error("formatting error: typestrings not matching"); 502 | } 503 | 504 | 505 | // compute the data size based on the shape 506 | auto size = static_cast(comp_size(shape)); 507 | data.resize(size); 508 | 509 | // read the data 510 | stream.read(reinterpret_cast(data.data()), sizeof(Scalar)*size); 511 | } 512 | 513 | } // namespace npy 514 | 515 | #endif // NPY_H 516 | --------------------------------------------------------------------------------