Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 101 additions & 13 deletions Gems/Pointcloud/Code/Source/Tools/Builders/PointcloudBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,98 @@
#include <Atom/RPI.Reflect/Buffer/BufferAsset.h>
#include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
#include <AzCore/Math/Color.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <Pointcloud/PointcloudAsset.h>
#include <Pointcloud/PointcloudFeatureProcessorInterface.h>
#include <fstream>
#include <unordered_set>

namespace Pointcloud
{
namespace
{
//! True if the PLY has correct header syntax and all required properties.
//! Position is always required
//! If colorsRequired is set to true also "red", "green" and "blue" must be defined
bool ShouldProcessPointcloud(const char* fullPath, bool colorsRequired)
{
std::ifstream file(fullPath, std::ios::binary);
if (!file.is_open())
{
return false;
}

// Custom parsing to avoid reading all vertex data which happly always attempts.
std::unordered_set<std::string> properties;
std::string line;
bool inVertexElement = false;
while (std::getline(file, line))
{
if (!line.empty() && line.back() == '\r')
{
line.pop_back();
}

if (line.rfind("element ", 0) == 0)
{
inVertexElement = line.rfind("element vertex ", 0) == 0;
}
else if (inVertexElement && line.rfind("property ", 0) == 0)
{
const size_t lastSpace = line.find_last_of(' ');
if (lastSpace != std::string::npos)
{
properties.insert(line.substr(lastSpace + 1));
}
}
else if (line == "end_header")
{
break;
}
}

AZStd::vector<const char*> requiredProperties = { "x", "y", "z" };
if (colorsRequired)
{
requiredProperties.push_back("red");
requiredProperties.push_back("green");
requiredProperties.push_back("blue");
}
for (const char* required : requiredProperties)
{
if (properties.find(required) == properties.end())
{
return false;
}
}
return true;
}
} // namespace

constexpr const char* PointcloudBuilderJobKey = "PointcloudBuilder";

//! When true, only .ply files whose vertex element has x/y/z/red/green/blue properties are processed. When false
//! (default), every .ply is processed and points without color data are white.
constexpr AZStd::string_view PointcloudBuilderSkipPlyWithoutColorKey = "/O3DE/Pointcloud/Builder/SkipPlyWithoutColor";

PointcloudBuilder::PointcloudBuilder()
{
if (auto* registry = AZ::SettingsRegistry::Get())
{
registry->Get(m_skipPlyWithoutColor, PointcloudBuilderSkipPlyWithoutColorKey);
}

AssetBuilderSDK::AssetBuilderDesc pointcloudAssetBuilderDescriptor;
pointcloudAssetBuilderDescriptor.m_name = PointcloudBuilderJobKey;
pointcloudAssetBuilderDescriptor.m_version = 1;
pointcloudAssetBuilderDescriptor.m_version = 2;
pointcloudAssetBuilderDescriptor.m_busId = azrtti_typeid<PointcloudBuilder>();
// Includes the filter setting so toggling it re-runs CreateJobs on every .ply
pointcloudAssetBuilderDescriptor.m_analysisFingerprint = AZStd::string::format(
"%u,%lu,%lu",
"%u,%lu,%lu,%d",
PointcloudAsset::PointcloudMagicNumber,
sizeof(PointcloudAsset::CloudHeader),
sizeof(PointcloudAsset::CloudVertex));
sizeof(PointcloudAsset::CloudVertex),
m_skipPlyWithoutColor ? 1 : 0);
pointcloudAssetBuilderDescriptor.m_patterns.push_back(
AssetBuilderSDK::AssetBuilderPattern("*.ply", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));

Expand Down Expand Up @@ -63,6 +137,13 @@ namespace Pointcloud
{
const auto fullSourcePath = AZ::IO::Path(request.m_watchFolder) / AZ::IO::Path(request.m_sourceFile);

if (!ShouldProcessPointcloud(fullSourcePath.String().c_str(), m_skipPlyWithoutColor))
{
// Not a point-cloud .ply (missing x/y/z/red/green/blue vertex properties)
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}

// Create an output job for each platform
for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
{
Expand All @@ -81,22 +162,29 @@ namespace Pointcloud
const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
if (!ShouldProcessPointcloud(request.m_fullPath.c_str(), m_skipPlyWithoutColor))
{
AZ_TracePrintf(
AssetBuilderSDK::ErrorWindow,
"Error: %s is not a point-cloud .ply (missing at least one required property x/y/z/red/green/blue).\n",
request.m_fullPath.c_str());
return;
}

happly::PLYData plyIn(request.m_fullPath.c_str());
const auto elementNames = plyIn.getElementNames();
auto vertices = plyIn.getVertexPositions();
const auto vertices = plyIn.getVertexPositions();
auto& vertexElement = plyIn.getElement("vertex");
bool hasRed = vertexElement.hasProperty("red");
bool hasGreen = vertexElement.hasProperty("green");
bool hasBlue = vertexElement.hasProperty("blue");
std::vector<std::array<unsigned char, 3>> colors;
if (hasRed && hasGreen && hasBlue)
if (vertexElement.hasProperty("red") && vertexElement.hasProperty("green") && vertexElement.hasProperty("blue"))
{
colors = plyIn.getVertexColors();
}
else
{
AZ_TracePrintf(AssetBuilderSDK::WarningWindow, "Warning: The ply file does not contain red, green, and blue color data.\n");
colors.resize(vertices.size(), { 255, 255, 255 });
AZ_TracePrintf(
AssetBuilderSDK::WarningWindow,
"Warning: %s does not contain red, green and blue color data, points will be white.\n",
request.m_fullPath.c_str());
}

AZ::Data::Asset<PointcloudAsset> pointcloudAsset;
Expand All @@ -105,7 +193,7 @@ namespace Pointcloud
for (int i = 0; i < vertices.size(); i++)
{
const auto& vertex = vertices[i];
AZ::Color color = AZ::Colors::GreenYellow;
AZ::Color color = AZ::Colors::White;
if (i < colors.size())
{
const auto& colorData = colors[i];
Expand All @@ -115,7 +203,7 @@ namespace Pointcloud
static_cast<float>(colorData[2]) / 255.0f,
1.0f);
}
PointcloudAsset::CloudVertex cloudVertex;
PointcloudAsset::CloudVertex cloudVertex{};
cloudVertex.m_position = { static_cast<float>(vertex[0]), static_cast<float>(vertex[1]), static_cast<float>(vertex[2]) };

cloudVertex.m_color = color.ToU32();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ namespace Pointcloud
void ShutDown() override
{
}

private:
bool m_skipPlyWithoutColor = false;
};

} // namespace Pointcloud
2 changes: 1 addition & 1 deletion Gems/Pointcloud/gem.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"gem_name": "Pointcloud",
"version": "4.0.0",
"version": "4.1.0",
"display_name": "Pointcloud",
"license": "Apache-2.0",
"license_url": "https://opensource.org/licenses/Apache-2.0",
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,20 @@ It offers:

At this moment it accepts [PLY](<https://en.wikipedia.org/wiki/PLY_(file_format)>) as a source asset.

By default, every `.ply` file is processed into a point cloud asset; points without color data are white. To process only `.ply` files whose `vertex` element has `x`, `y`, `z`, `red`, `green` and `blue` properties (e.g. when other builders handle `.ply` files too), enable the `SkipPlyWithoutColor` option in a `.setreg` file:

```json
{
"O3DE": {
"Pointcloud": {
"Builder": {
"SkipPlyWithoutColor": true
}
}
}
}
```

![](doc/Pointcloud.png)\
Pointcloud asset was obtained from [potree](https://github.com/potree/potree).

Expand Down
Loading