Agrarsense
PointcloudUtilities.cpp
Go to the documentation of this file.
1// Copyright (c) 2023 FrostBit Software Lab at the Lapland University of Applied Sciences
2//
3// This work is licensed under the terms of the MIT license.
4// For a copy, see <https://opensource.org/licenses/MIT>.
5
7
8#include "CoreMinimal.h"
9#include "Misc/Paths.h"
10#include "Misc/FileHelper.h"
11#include "Async/Async.h"
12#include "HAL/PlatformFileManager.h"
13
14#include <fstream>
15#include <vector>
16#include <string>
17#include <functional>
18
19void UPointcloudUtilities::SaveTArrayAsPlyAsync(FString FullFileName, const TArray<FVector> points)
20{
21 if (points.Num() == 0)
22 {
23 return;
24 }
25
26 if (!FullFileName.EndsWith(TEXT(".ply")))
27 {
28 UE_LOG(LogTemp, Warning, TEXT("PointcloudUtilities.cpp: Invalid file format. Expecting filename to end with '.ply'!"));
29 return;
30 }
31
32 FString Directory = FPaths::GetPath(FullFileName);
33 IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
34 if (!PlatformFile.DirectoryExists(*Directory))
35 {
36 if (!PlatformFile.CreateDirectoryTree(*Directory))
37 {
38 return;
39 }
40 }
41
42 AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [FullFileName, points]()
43 {
44 // Create PLY header in binary format
45 std::string header = "ply\nformat binary_little_endian 1.0\n"
46 "element vertex " + std::to_string(points.Num()) + "\n"
47 "property double x\nproperty double y\nproperty double z\n"
48 "end_header\n";
49
50 // Calculate the size of the buffer
51 size_t numPoints = points.Num();
52 size_t bufferSize = numPoints * sizeof(FVector);
53
54 // Open file
55 std::ofstream plyFile(TCHAR_TO_UTF8(*FullFileName), std::ios::out | std::ios::binary);
56 if (!plyFile.is_open())
57 {
58 return;
59 }
60
61 // Write header
62 plyFile.write(header.c_str(), header.size());
63
64 // Write point cloud buffer to the file
65 plyFile.write(reinterpret_cast<const char*>(points.GetData()), bufferSize);
66
67 // Close file
68 plyFile.close();
69 });
70}
71
72void UPointcloudUtilities::SaveVectorArrayAsPlyAsync(FString FullFileName, const std::vector<FPointData> points)
73{
74 if (points.empty())
75 {
76 return;
77 }
78
79 if (!FullFileName.EndsWith(TEXT(".ply")))
80 {
81 UE_LOG(LogTemp, Warning, TEXT("PointcloudUtilities.cpp: Invalid file format. Expecting filename to end with '.ply'!"));
82 return;
83 }
84
85 FString Directory = FPaths::GetPath(FullFileName);
86 IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
87 if (!PlatformFile.DirectoryExists(*Directory))
88 {
89 if (!PlatformFile.CreateDirectoryTree(*Directory))
90 {
91 return;
92 }
93 }
94
95 AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [FullFileName, points]()
96 {
97 // Create PLY header in binary format
98 std::string header = "ply\nformat binary_little_endian 1.0\n"
99 "element vertex " + std::to_string(points.size()) + "\n"
100 "property float x\nproperty float y\nproperty float z\n"
101 "property uchar red\nproperty uchar green\nproperty uchar blue\n"
102 "end_header\n";
103
104 // Prepare the buffer size
105 size_t numPoints = points.size();
106 size_t bufferSize = numPoints * (sizeof(float) * 3 + sizeof(uint8_t) * 3);
107 std::vector<char> buffer;
108 buffer.reserve(bufferSize);
109
110 // Prepare point data
111 for (const auto& point : points)
112 {
113 // Write coordinates (X, Y, Z) as binary floats
114 buffer.insert(buffer.end(), reinterpret_cast<const char*>(&point.X), reinterpret_cast<const char*>(&point.X) + sizeof(float));
115 buffer.insert(buffer.end(), reinterpret_cast<const char*>(&point.Y), reinterpret_cast<const char*>(&point.Y) + sizeof(float));
116 buffer.insert(buffer.end(), reinterpret_cast<const char*>(&point.Z), reinterpret_cast<const char*>(&point.Z) + sizeof(float));
117
118 // Extract RGB components and write them as unsigned chars
119 uint8_t red = (point.RGB >> 16) & 0xFF;
120 uint8_t green = (point.RGB >> 8) & 0xFF;
121 uint8_t blue = point.RGB & 0xFF;
122 buffer.insert(buffer.end(), reinterpret_cast<const char*>(&red), reinterpret_cast<const char*>(&red) + sizeof(uint8_t));
123 buffer.insert(buffer.end(), reinterpret_cast<const char*>(&green), reinterpret_cast<const char*>(&green) + sizeof(uint8_t));
124 buffer.insert(buffer.end(), reinterpret_cast<const char*>(&blue), reinterpret_cast<const char*>(&blue) + sizeof(uint8_t));
125 }
126
127 // Open file
128 std::ofstream plyFile(TCHAR_TO_UTF8(*FullFileName), std::ios::out | std::ios::binary);
129 if (!plyFile.is_open())
130 {
131 UE_LOG(LogTemp, Warning, TEXT("Failed to open file for writing: %s"), *FullFileName);
132 return;
133 }
134
135 // Write header
136 plyFile.write(header.c_str(), header.size());
137
138 // Write point cloud buffer to the file
139 plyFile.write(buffer.data(), buffer.size());
140
141 // Close file
142 plyFile.close();
143 });
144}
static void SaveTArrayAsPlyAsync(FString FullFileName, const TArray< FVector > points)
static void SaveVectorArrayAsPlyAsync(FString FullFileName, const std::vector< FPointData > points)