draco icon indicating copy to clipboard operation
draco copied to clipboard

SEQUENTIAL_ENCODING does not preserve order

Open huonghld opened this issue 3 years ago • 4 comments

Hi! I am trying to use SEQUENTIAL_ENCODING, but the sequential encoding does not preserve order for my case. I add encoder.SetEncodingMethod(draco::MESH_SEQUENTIAL_ENCODING); to my encoder. Below is the entire code for the encoder:


#include <cinttypes>
#include <cstdlib>

#include "draco/compression/encode.h"
#include "draco/core/cycle_timer.h"
#include "draco/io/file_utils.h"
#include "draco/io/mesh_io.h"
#include "draco/io/point_cloud_io.h"

namespace {

struct Options {
Options();

bool is_point_cloud;
int pos_quantization_bits;
int tex_coords_quantization_bits;
bool tex_coords_deleted;
int normals_quantization_bits;
bool normals_deleted;
int generic_quantization_bits;
bool generic_deleted;
int compression_level;
bool use_metadata;
std::string input;
std::string output;
};

Options::Options()
: is_point_cloud(false),
pos_quantization_bits(11),
tex_coords_quantization_bits(10),
tex_coords_deleted(false),
normals_quantization_bits(8),
normals_deleted(false),
generic_quantization_bits(8),
generic_deleted(false),
compression_level(7),
use_metadata(false) {}

void Usage() {
printf("Usage: draco_encoder [options] -i input\n");
printf("\n");
printf("Main options:\n");
printf(" -h | -? show help.\n");
printf(" -i input file name.\n");
printf(" -o output file name.\n");
printf(
" -point_cloud forces the input to be encoded as a point "
"cloud.\n");
printf(
" -qp quantization bits for the position "
"attribute, default=11.\n");
printf(
" -qt quantization bits for the texture coordinate "
"attribute, default=10.\n");
printf(
" -qn quantization bits for the normal vector "
"attribute, default=8.\n");
printf(
" -qg quantization bits for any generic attribute, "
"default=8.\n");
printf(
" -cl compression level [0-10], most=10, least=0, "
"default=7.\n");
printf(
" --skip ATTRIBUTE_NAME skip a given attribute (NORMAL, TEX_COORD, "
"GENERIC)\n");
printf(
" --metadata use metadata to encode extra information in "
"mesh files.\n");
printf(
"\nUse negative quantization values to skip the specified attribute\n");
}

int StringToInt(const std::string &s) {
char *end;
return strtol(s.c_str(), &end, 10); // NOLINT
}

void PrintOptions(const draco::PointCloud &pc, const Options &options) {
printf("Encoder options:\n");
printf(" Compression level = %d\n", options.compression_level);
if (options.pos_quantization_bits == 0) {
printf(" Positions: No quantization\n");
} else {
printf(" Positions: Quantization = %d bits\n",
options.pos_quantization_bits);
}

if (pc.GetNamedAttributeId(draco::GeometryAttribute::TEX_COORD) >= 0) {
if (options.tex_coords_quantization_bits == 0) {
printf(" Texture coordinates: No quantization\n");
} else {
printf(" Texture coordinates: Quantization = %d bits\n",
options.tex_coords_quantization_bits);
}
} else if (options.tex_coords_deleted) {
printf(" Texture coordinates: Skipped\n");
}

if (pc.GetNamedAttributeId(draco::GeometryAttribute::NORMAL) >= 0) {
if (options.normals_quantization_bits == 0) {
printf(" Normals: No quantization\n");
} else {
printf(" Normals: Quantization = %d bits\n",
options.normals_quantization_bits);
}
} else if (options.normals_deleted) {
printf(" Normals: Skipped\n");
}

if (pc.GetNamedAttributeId(draco::GeometryAttribute::GENERIC) >= 0) {
if (options.generic_quantization_bits == 0) {
printf(" Generic: No quantization\n");
} else {
printf(" Generic: Quantization = %d bits\n",
options.generic_quantization_bits);
}
} else if (options.generic_deleted) {
printf(" Generic: Skipped\n");
}
printf("\n");
}

int EncodePointCloudToFile(const draco::PointCloud &pc, const std::string &file,
draco::Encoder *encoder) {
draco::CycleTimer timer;
// Encode the geometry.
draco::EncoderBuffer buffer;
timer.Start();
const draco::Status status = encoder->EncodePointCloudToBuffer(pc, &buffer);
if (!status.ok()) {
printf("Failed to encode the point cloud.\n");
printf("%s\n", status.error_msg());
return -1;
}
timer.Stop();
// Save the encoded geometry into a file.
if (!draco::WriteBufferToFile(buffer.data(), buffer.size(), file)) {
printf("Failed to write the output file.\n");
return -1;
}
printf("Encoded point cloud saved to %s (%" PRId64 " ms to encode).\n",
file.c_str(), timer.GetInMs());
printf("\nEncoded size = %zu bytes\n\n", buffer.size());
return 0;
}

int EncodeMeshToFile(const draco::Mesh &mesh, const std::string &file,
draco::Encoder *encoder) {
draco::CycleTimer timer;
// Encode the geometry.
draco::EncoderBuffer buffer;
timer.Start();
const draco::Status status = encoder->EncodeMeshToBuffer(mesh, &buffer);
if (!status.ok()) {
printf("Failed to encode the mesh.\n");
printf("%s\n", status.error_msg());
return -1;
}
timer.Stop();
// Save the encoded geometry into a file.
if (!draco::WriteBufferToFile(buffer.data(), buffer.size(), file)) {
printf("Failed to create the output file.\n");
return -1;
}
printf("Encoded mesh saved to %s (%" PRId64 " ms to encode).\n", file.c_str(),
timer.GetInMs());
printf("\nEncoded size = %zu bytes\n\n", buffer.size());
return 0;
}

} // anonymous namespace

int main(int argc, char **argv) {
Options options;
const int argc_check = argc - 1;

for (int i = 1; i < argc; ++i) {
if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i])) {
Usage();
return 0;
} else if (!strcmp("-i", argv[i]) && i < argc_check) {
options.input = argv[++i];
} else if (!strcmp("-o", argv[i]) && i < argc_check) {
options.output = argv[++i];
} else if (!strcmp("-point_cloud", argv[i])) {
options.is_point_cloud = true;
} else if (!strcmp("-qp", argv[i]) && i < argc_check) {
options.pos_quantization_bits = StringToInt(argv[++i]);
if (options.pos_quantization_bits > 30) {
printf(
"Error: The maximum number of quantization bits for the position "
"attribute is 30.\n");
return -1;
}
} else if (!strcmp("-qt", argv[i]) && i < argc_check) {
options.tex_coords_quantization_bits = StringToInt(argv[++i]);
if (options.tex_coords_quantization_bits > 30) {
printf(
"Error: The maximum number of quantization bits for the texture "
"coordinate attribute is 30.\n");
return -1;
}
} else if (!strcmp("-qn", argv[i]) && i < argc_check) {
options.normals_quantization_bits = StringToInt(argv[++i]);
if (options.normals_quantization_bits > 30) {
printf(
"Error: The maximum number of quantization bits for the normal "
"attribute is 30.\n");
return -1;
}
} else if (!strcmp("-qg", argv[i]) && i < argc_check) {
options.generic_quantization_bits = StringToInt(argv[++i]);
if (options.generic_quantization_bits > 30) {
printf(
"Error: The maximum number of quantization bits for generic "
"attributes is 30.\n");
return -1;
}
} else if (!strcmp("-cl", argv[i]) && i < argc_check) {
options.compression_level = StringToInt(argv[++i]);
} else if (!strcmp("--skip", argv[i]) && i < argc_check) {
if (!strcmp("NORMAL", argv[i + 1])) {
options.normals_quantization_bits = -1;
} else if (!strcmp("TEX_COORD", argv[i + 1])) {
options.tex_coords_quantization_bits = -1;
} else if (!strcmp("GENERIC", argv[i + 1])) {
options.generic_quantization_bits = -1;
} else {
printf("Error: Invalid attribute name after --skip\n");
return -1;
}
++i;
} else if (!strcmp("--metadata", argv[i])) {
options.use_metadata = true;
}
}
if (argc < 3 || options.input.empty()) {
Usage();
return -1;
}

std::unique_ptrdraco::PointCloud pc;
draco::Mesh *mesh = nullptr;
if (!options.is_point_cloud) {
auto maybe_mesh =
draco::ReadMeshFromFile(options.input, options.use_metadata);
if (!maybe_mesh.ok()) {
printf("Failed loading the input mesh: %s.\n",
maybe_mesh.status().error_msg());
return -1;
}
mesh = maybe_mesh.value().get();
pc = std::move(maybe_mesh).value();
} else {
auto maybe_pc = draco::ReadPointCloudFromFile(options.input);
if (!maybe_pc.ok()) {
printf("Failed loading the input point cloud: %s.\n",
maybe_pc.status().error_msg());
return -1;
}
pc = std::move(maybe_pc).value();
}

if (options.pos_quantization_bits < 0) {
printf("Error: Position attribute cannot be skipped.\n");
return -1;
}

// Delete attributes if needed. This needs to happen before we set any
// quantization settings.
if (options.tex_coords_quantization_bits < 0) {
if (pc->NumNamedAttributes(draco::GeometryAttribute::TEX_COORD) > 0) {
options.tex_coords_deleted = true;
}
while (pc->NumNamedAttributes(draco::GeometryAttribute::TEX_COORD) > 0) {
pc->DeleteAttribute(
pc->GetNamedAttributeId(draco::GeometryAttribute::TEX_COORD, 0));
}
}
if (options.normals_quantization_bits < 0) {
if (pc->NumNamedAttributes(draco::GeometryAttribute::NORMAL) > 0) {
options.normals_deleted = true;
}
while (pc->NumNamedAttributes(draco::GeometryAttribute::NORMAL) > 0) {
pc->DeleteAttribute(
pc->GetNamedAttributeId(draco::GeometryAttribute::NORMAL, 0));
}
}
if (options.generic_quantization_bits < 0) {
if (pc->NumNamedAttributes(draco::GeometryAttribute::GENERIC) > 0) {
options.generic_deleted = true;
}
while (pc->NumNamedAttributes(draco::GeometryAttribute::GENERIC) > 0) {
pc->DeleteAttribute(
pc->GetNamedAttributeId(draco::GeometryAttribute::GENERIC, 0));
}
}
#ifdef DRACO_ATTRIBUTE_INDICES_DEDUPLICATION_SUPPORTED
// If any attribute has been deleted, run deduplication of point indices again
// as some points can be possibly combined.
if (options.tex_coords_deleted || options.normals_deleted ||
options.generic_deleted) {
pc->DeduplicatePointIds();
}
#endif

// Convert compression level to speed (that 0 = slowest, 10 = fastest).
const int speed = 10 - options.compression_level;

draco::Encoder encoder;

// Setup encoder options.
if (options.pos_quantization_bits > 0) {
encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION,
options.pos_quantization_bits);
}
if (options.tex_coords_quantization_bits > 0) {
encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD,
options.tex_coords_quantization_bits);
}
if (options.normals_quantization_bits > 0) {
encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL,
options.normals_quantization_bits);
}
if (options.generic_quantization_bits > 0) {
encoder.SetAttributeQuantization(draco::GeometryAttribute::GENERIC,
options.generic_quantization_bits);
}
encoder.SetSpeedOptions(speed, speed);
encoder.SetEncodingMethod(draco::MESH_SEQUENTIAL_ENCODING);

if (options.output.empty()) {
// Create a default output file by attaching .drc to the input file name.
options.output = options.input + ".drc";
}

PrintOptions(*pc, options);

int ret = -1;
const bool input_is_mesh = mesh && mesh->num_faces() > 0;
if (input_is_mesh) {
ret = EncodeMeshToFile(*mesh, options.output, &encoder);
} else {
ret = EncodePointCloudToFile(*pc, options.output, &encoder);
}

if (ret != -1 && options.compression_level < 10) {
printf(
"For better compression, increase the compression level up to '-cl 10' "
".\n\n");
}

return ret;
}

Could you please check on this? Thanks!

huonghld avatar Apr 11 '22 03:04 huonghld

What is the input format? Sequential encoding should preserve order but it is possible some points got removed during file loading (e.g. when loading and .obj we can deduplicate values).

ondys avatar Apr 27 '22 19:04 ondys

I used the .obj format of the "bouncing" MIT meshes dataset. The dataset is available here: https://people.csail.mit.edu/drdaniel/mesh_animation/#data

huonghld avatar Apr 29 '22 20:04 huonghld

Draco performs deduplication when loading .obj files which may alter order of vertices. Please try turning of the deduplication and see if that helps.

ondys avatar Sep 16 '22 19:09 ondys