From 0b3b8f380ab4172dcdf6700c18dedf01c9d85737 Mon Sep 17 00:00:00 2001 From: Patrick Trollmann Date: Fri, 7 Mar 2025 19:18:34 +0100 Subject: [PATCH 1/4] add SPLAT decoder to be able to encode .splat files to .drc files --- src/draco/io/point_cloud_io.cc | 9 +++- src/draco/io/splat_decoder.cc | 78 ++++++++++++++++++++++++++++++++++ src/draco/io/splat_decoder.h | 44 +++++++++++++++++++ 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/draco/io/splat_decoder.cc create mode 100644 src/draco/io/splat_decoder.h diff --git a/src/draco/io/point_cloud_io.cc b/src/draco/io/point_cloud_io.cc index 643820bd..2470f044 100644 --- a/src/draco/io/point_cloud_io.cc +++ b/src/draco/io/point_cloud_io.cc @@ -18,6 +18,7 @@ #include "draco/io/obj_decoder.h" #include "draco/io/parser_utils.h" #include "draco/io/ply_decoder.h" +#include "draco/io/splat_decoder.cc" namespace draco { @@ -26,7 +27,7 @@ StatusOr> ReadPointCloudFromFile( std::unique_ptr pc(new PointCloud()); // Analyze file extension. const std::string extension = parser::ToLower( - file_name.size() >= 4 ? file_name.substr(file_name.size() - 4) + file_name.size() >= 5 ? file_name.substr(file_name.find_last_of('.')) : file_name); if (extension == ".obj") { // Wavefront OBJ file format. @@ -43,6 +44,12 @@ StatusOr> ReadPointCloudFromFile( DRACO_RETURN_IF_ERROR(ply_decoder.DecodeFromFile(file_name, pc.get())); return std::move(pc); } + if (extension == ".splat") { + // SPLAT file format. + SplatDecoder splat_decoder; + DRACO_RETURN_IF_ERROR(splat_decoder.ReadSplatFile(file_name, pc.get())); + return std::move(pc); + } std::vector buffer; if (!ReadFileToBuffer(file_name, &buffer)) { diff --git a/src/draco/io/splat_decoder.cc b/src/draco/io/splat_decoder.cc new file mode 100644 index 00000000..0bc4db61 --- /dev/null +++ b/src/draco/io/splat_decoder.cc @@ -0,0 +1,78 @@ +// Copyright 2025 Patrick Trollmann. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/splat_decoder.h" + +namespace draco { +SplatDecoder::SplatDecoder() {} + +Status SplatDecoder::ReadSplatFile(const std::string &filename, + PointCloud *out_point_cloud) { + std::ifstream file(filename, std::ios::binary); + if (!file) { + return Status(Status::DRACO_ERROR, "Unable to open input file."); + } + + struct Gaussian { + float position[3]; + float scale[3]; + uint8_t color[4]; + uint8_t rotation[4]; + }; + + std::vector gaussians; + Gaussian gaussian; + while (file.read(reinterpret_cast(&gaussian), sizeof(Gaussian))) { + gaussians.push_back(gaussian); + } + const PointIndex::ValueType num_vertices = gaussians.size(); + out_point_cloud->set_num_points(num_vertices); + + GeometryAttribute vapos; + vapos.Init(GeometryAttribute::POSITION, nullptr, 6, DT_FLOAT32, false, + sizeof(float) * 6, 0); + const int att_id_position = + out_point_cloud->AddAttribute(vapos, true, num_vertices); + + GeometryAttribute vacol; + vacol.Init(GeometryAttribute::COLOR, nullptr, 8, DT_UINT8, true, + sizeof(uint8_t) * 8, 0); + const int att_id_color = + out_point_cloud->AddAttribute(vacol, true, num_vertices); + + for (PointIndex::ValueType i = 0; i < num_vertices; ++i) { + const auto &g = gaussians[i]; + std::array valpos; + valpos[0] = g.position[0]; + valpos[1] = g.position[1]; + valpos[2] = g.position[2]; + valpos[3] = g.scale[0]; + valpos[4] = g.scale[1]; + valpos[5] = g.scale[2]; + out_point_cloud->attribute(att_id_position) + ->SetAttributeValue(AttributeValueIndex(i), &valpos[0]); + + std::array valcol; + for (int j = 0; j < 4; j++) { + valcol[j] = g.color[j]; + } + for (int j = 4; j < 8; j++) { + valcol[j] = g.rotation[j-4]; + } + out_point_cloud->attribute(att_id_color) + ->SetAttributeValue(AttributeValueIndex(i), &valcol[0]); + } + return OkStatus(); +} +} // namespace draco \ No newline at end of file diff --git a/src/draco/io/splat_decoder.h b/src/draco/io/splat_decoder.h new file mode 100644 index 00000000..25e70f86 --- /dev/null +++ b/src/draco/io/splat_decoder.h @@ -0,0 +1,44 @@ +// Copyright 2025 Patrick Trollmann. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_SPLAT_DECODER_H_ +#define DRACO_IO_SPLAT_DECODER_H_ + +#include +#include + +#include "draco/point_cloud/point_cloud.h" +#include "draco/core/status.h" + +namespace draco { + +// Decodes a SPLAT file into draco::PointCloud. +// The current implementation assumes the properties: POSITION, SCALE, COLOR, ROTATION. +// POSITION and SCALE are stored together as GeometryAttribute.POSITION. COLOR and ROATION +// are stored together as GeometryAttribute.COLOR. Currently, it is not possible to use +// custom GeometryAttribute types or the GENERIC type. This types cannot be decoded in +// the WebGL version of Draco. +class SplatDecoder { + public: + SplatDecoder(); + + // Decodes a splat file stored in the input file. + Status ReadSplatFile(const std::string &filename, + PointCloud *out_point_cloud); + +}; + +} // namespace draco + +#endif // DRACO_IO_SPLAT_DECODER_H_ From 5a5858cc637077d9f3e80399451ee6d02666ef73 Mon Sep 17 00:00:00 2001 From: Patrick Trollmann Date: Fri, 7 Mar 2025 19:18:34 +0100 Subject: [PATCH 2/4] add SPLAT decoder to be able to encode .splat files to .drc files --- src/draco/io/point_cloud_io.cc | 9 +++- src/draco/io/splat_decoder.cc | 78 ++++++++++++++++++++++++++++++++++ src/draco/io/splat_decoder.h | 44 +++++++++++++++++++ 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/draco/io/splat_decoder.cc create mode 100644 src/draco/io/splat_decoder.h diff --git a/src/draco/io/point_cloud_io.cc b/src/draco/io/point_cloud_io.cc index 643820bd..2470f044 100644 --- a/src/draco/io/point_cloud_io.cc +++ b/src/draco/io/point_cloud_io.cc @@ -18,6 +18,7 @@ #include "draco/io/obj_decoder.h" #include "draco/io/parser_utils.h" #include "draco/io/ply_decoder.h" +#include "draco/io/splat_decoder.cc" namespace draco { @@ -26,7 +27,7 @@ StatusOr> ReadPointCloudFromFile( std::unique_ptr pc(new PointCloud()); // Analyze file extension. const std::string extension = parser::ToLower( - file_name.size() >= 4 ? file_name.substr(file_name.size() - 4) + file_name.size() >= 5 ? file_name.substr(file_name.find_last_of('.')) : file_name); if (extension == ".obj") { // Wavefront OBJ file format. @@ -43,6 +44,12 @@ StatusOr> ReadPointCloudFromFile( DRACO_RETURN_IF_ERROR(ply_decoder.DecodeFromFile(file_name, pc.get())); return std::move(pc); } + if (extension == ".splat") { + // SPLAT file format. + SplatDecoder splat_decoder; + DRACO_RETURN_IF_ERROR(splat_decoder.ReadSplatFile(file_name, pc.get())); + return std::move(pc); + } std::vector buffer; if (!ReadFileToBuffer(file_name, &buffer)) { diff --git a/src/draco/io/splat_decoder.cc b/src/draco/io/splat_decoder.cc new file mode 100644 index 00000000..0bc4db61 --- /dev/null +++ b/src/draco/io/splat_decoder.cc @@ -0,0 +1,78 @@ +// Copyright 2025 Patrick Trollmann. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/splat_decoder.h" + +namespace draco { +SplatDecoder::SplatDecoder() {} + +Status SplatDecoder::ReadSplatFile(const std::string &filename, + PointCloud *out_point_cloud) { + std::ifstream file(filename, std::ios::binary); + if (!file) { + return Status(Status::DRACO_ERROR, "Unable to open input file."); + } + + struct Gaussian { + float position[3]; + float scale[3]; + uint8_t color[4]; + uint8_t rotation[4]; + }; + + std::vector gaussians; + Gaussian gaussian; + while (file.read(reinterpret_cast(&gaussian), sizeof(Gaussian))) { + gaussians.push_back(gaussian); + } + const PointIndex::ValueType num_vertices = gaussians.size(); + out_point_cloud->set_num_points(num_vertices); + + GeometryAttribute vapos; + vapos.Init(GeometryAttribute::POSITION, nullptr, 6, DT_FLOAT32, false, + sizeof(float) * 6, 0); + const int att_id_position = + out_point_cloud->AddAttribute(vapos, true, num_vertices); + + GeometryAttribute vacol; + vacol.Init(GeometryAttribute::COLOR, nullptr, 8, DT_UINT8, true, + sizeof(uint8_t) * 8, 0); + const int att_id_color = + out_point_cloud->AddAttribute(vacol, true, num_vertices); + + for (PointIndex::ValueType i = 0; i < num_vertices; ++i) { + const auto &g = gaussians[i]; + std::array valpos; + valpos[0] = g.position[0]; + valpos[1] = g.position[1]; + valpos[2] = g.position[2]; + valpos[3] = g.scale[0]; + valpos[4] = g.scale[1]; + valpos[5] = g.scale[2]; + out_point_cloud->attribute(att_id_position) + ->SetAttributeValue(AttributeValueIndex(i), &valpos[0]); + + std::array valcol; + for (int j = 0; j < 4; j++) { + valcol[j] = g.color[j]; + } + for (int j = 4; j < 8; j++) { + valcol[j] = g.rotation[j-4]; + } + out_point_cloud->attribute(att_id_color) + ->SetAttributeValue(AttributeValueIndex(i), &valcol[0]); + } + return OkStatus(); +} +} // namespace draco \ No newline at end of file diff --git a/src/draco/io/splat_decoder.h b/src/draco/io/splat_decoder.h new file mode 100644 index 00000000..25e70f86 --- /dev/null +++ b/src/draco/io/splat_decoder.h @@ -0,0 +1,44 @@ +// Copyright 2025 Patrick Trollmann. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_SPLAT_DECODER_H_ +#define DRACO_IO_SPLAT_DECODER_H_ + +#include +#include + +#include "draco/point_cloud/point_cloud.h" +#include "draco/core/status.h" + +namespace draco { + +// Decodes a SPLAT file into draco::PointCloud. +// The current implementation assumes the properties: POSITION, SCALE, COLOR, ROTATION. +// POSITION and SCALE are stored together as GeometryAttribute.POSITION. COLOR and ROATION +// are stored together as GeometryAttribute.COLOR. Currently, it is not possible to use +// custom GeometryAttribute types or the GENERIC type. This types cannot be decoded in +// the WebGL version of Draco. +class SplatDecoder { + public: + SplatDecoder(); + + // Decodes a splat file stored in the input file. + Status ReadSplatFile(const std::string &filename, + PointCloud *out_point_cloud); + +}; + +} // namespace draco + +#endif // DRACO_IO_SPLAT_DECODER_H_ From 073c42c79ae797764bd3076868977b0eb2a5ca69 Mon Sep 17 00:00:00 2001 From: patrikant <57221183+patrikant@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:46:54 +0200 Subject: [PATCH 3/4] resolve typo --- src/draco/io/splat_decoder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/draco/io/splat_decoder.h b/src/draco/io/splat_decoder.h index 25e70f86..27ae9815 100644 --- a/src/draco/io/splat_decoder.h +++ b/src/draco/io/splat_decoder.h @@ -25,7 +25,7 @@ namespace draco { // Decodes a SPLAT file into draco::PointCloud. // The current implementation assumes the properties: POSITION, SCALE, COLOR, ROTATION. -// POSITION and SCALE are stored together as GeometryAttribute.POSITION. COLOR and ROATION +// POSITION and SCALE are stored together as GeometryAttribute.POSITION. COLOR and ROTATION // are stored together as GeometryAttribute.COLOR. Currently, it is not possible to use // custom GeometryAttribute types or the GENERIC type. This types cannot be decoded in // the WebGL version of Draco. From 0b8d16140f7927c202b4af9d4a6014ab53893c36 Mon Sep 17 00:00:00 2001 From: Patrick Trollmann Date: Wed, 24 Sep 2025 14:18:04 +0200 Subject: [PATCH 4/4] check if file extension exists (fix failing test) --- src/draco/io/point_cloud_io.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/draco/io/point_cloud_io.cc b/src/draco/io/point_cloud_io.cc index 2470f044..4679d925 100644 --- a/src/draco/io/point_cloud_io.cc +++ b/src/draco/io/point_cloud_io.cc @@ -26,9 +26,13 @@ StatusOr> ReadPointCloudFromFile( const std::string &file_name) { std::unique_ptr pc(new PointCloud()); // Analyze file extension. - const std::string extension = parser::ToLower( - file_name.size() >= 5 ? file_name.substr(file_name.find_last_of('.')) + const auto pos = file_name.find_last_of('.'); + const std::string extension = parser::ToLower(file_name.size() >= 5 + ? ((pos != std::string::npos) ? file_name.substr(pos) : "") : file_name); + if (extension.empty()) { + return Status(Status::DRACO_ERROR, "Unable to read input file: no file extension."); + } if (extension == ".obj") { // Wavefront OBJ file format. ObjDecoder obj_decoder;