mirror of
https://git.mirrors.martin98.com/https://github.com/google/draco
synced 2025-09-13 04:03:15 +08:00
Merging latest changes from the private repo.
1. Added support for loading sub-objects in .OBJ into a generic attribute. 2. Added missing mesh_are_equivlent classes needed for certain unit tests. 3. Updated javascript decoder reflecting our latest changes + added support for GeometryAttribute::custom_id() method. 4. Added checks for valid quantization bits to our draco_encoder app.
This commit is contained in:
parent
17cdf150cb
commit
ef7ad0fda3
@ -326,6 +326,8 @@ set(draco_mesh_sources
|
|||||||
"${draco_root}/mesh/edgebreaker_traverser.h"
|
"${draco_root}/mesh/edgebreaker_traverser.h"
|
||||||
"${draco_root}/mesh/mesh.cc"
|
"${draco_root}/mesh/mesh.cc"
|
||||||
"${draco_root}/mesh/mesh.h"
|
"${draco_root}/mesh/mesh.h"
|
||||||
|
"${draco_root}/mesh/mesh_are_equivalent.cc"
|
||||||
|
"${draco_root}/mesh/mesh_are_equivalent.h"
|
||||||
"${draco_root}/mesh/mesh_attribute_corner_table.cc"
|
"${draco_root}/mesh/mesh_attribute_corner_table.cc"
|
||||||
"${draco_root}/mesh/mesh_attribute_corner_table.h"
|
"${draco_root}/mesh/mesh_attribute_corner_table.h"
|
||||||
"${draco_root}/mesh/mesh_cleanup.cc"
|
"${draco_root}/mesh/mesh_cleanup.cc"
|
||||||
@ -389,6 +391,7 @@ set(draco_test_sources
|
|||||||
"${draco_root}/io/ply_decoder_test.cc"
|
"${draco_root}/io/ply_decoder_test.cc"
|
||||||
"${draco_root}/io/ply_reader_test.cc"
|
"${draco_root}/io/ply_reader_test.cc"
|
||||||
"${draco_root}/io/point_cloud_io_test.cc"
|
"${draco_root}/io/point_cloud_io_test.cc"
|
||||||
|
"${draco_root}/mesh/mesh_are_equivalent_test.cc"
|
||||||
"${draco_root}/mesh/mesh_cleanup_test.cc"
|
"${draco_root}/mesh/mesh_cleanup_test.cc"
|
||||||
"${draco_root}/mesh/mesh_test.cc"
|
"${draco_root}/mesh/mesh_test.cc"
|
||||||
"${draco_root}/mesh/triangle_soup_mesh_builder_test.cc"
|
"${draco_root}/mesh/triangle_soup_mesh_builder_test.cc"
|
||||||
|
@ -78,6 +78,4 @@ bool FloatPointsKdTreeDecoder::DecodePointCloud(DecoderBuffer *buffer,
|
|||||||
|
|
||||||
} // namespace draco
|
} // namespace draco
|
||||||
|
|
||||||
#undef BIT_DECODER_TYPE
|
|
||||||
|
|
||||||
#endif // DRACO_COMPRESSION_POINT_CLOUD_ALGORITHMS_FLOAT_POINTS_KD_TREE_DECODER_H_
|
#endif // DRACO_COMPRESSION_POINT_CLOUD_ALGORITHMS_FLOAT_POINTS_KD_TREE_DECODER_H_
|
||||||
|
@ -104,6 +104,4 @@ bool FloatPointsKdTreeEncoder::EncodePointCloud(InputIteratorT points_begin,
|
|||||||
|
|
||||||
} // namespace draco
|
} // namespace draco
|
||||||
|
|
||||||
#undef BIT_ENCODER_TYPE
|
|
||||||
|
|
||||||
#endif // DRACO_COMPRESSION_POINT_CLOUD_ALGORITHMS_FLOAT_POINTS_KD_TREE_ENCODER_H_
|
#endif // DRACO_COMPRESSION_POINT_CLOUD_ALGORITHMS_FLOAT_POINTS_KD_TREE_ENCODER_H_
|
||||||
|
@ -28,10 +28,12 @@ ObjDecoder::ObjDecoder()
|
|||||||
num_positions_(0),
|
num_positions_(0),
|
||||||
num_tex_coords_(0),
|
num_tex_coords_(0),
|
||||||
num_normals_(0),
|
num_normals_(0),
|
||||||
|
num_sub_objects_(0),
|
||||||
pos_att_id_(-1),
|
pos_att_id_(-1),
|
||||||
tex_att_id_(-1),
|
tex_att_id_(-1),
|
||||||
norm_att_id_(-1),
|
norm_att_id_(-1),
|
||||||
material_att_id_(-1),
|
material_att_id_(-1),
|
||||||
|
sub_obj_att_id_(-1),
|
||||||
deduplicate_input_values_(true),
|
deduplicate_input_values_(true),
|
||||||
last_material_id_(0),
|
last_material_id_(0),
|
||||||
open_material_file_(false),
|
open_material_file_(false),
|
||||||
@ -87,6 +89,7 @@ bool ObjDecoder::DecodeInternal() {
|
|||||||
counting_mode_ = true;
|
counting_mode_ = true;
|
||||||
ResetCounters();
|
ResetCounters();
|
||||||
material_name_to_id_.clear();
|
material_name_to_id_.clear();
|
||||||
|
num_sub_objects_ = 0;
|
||||||
// Parse all lines.
|
// Parse all lines.
|
||||||
bool error = false;
|
bool error = false;
|
||||||
while (ParseDefinition(&error) && !error) {
|
while (ParseDefinition(&error) && !error) {
|
||||||
@ -137,6 +140,26 @@ bool ObjDecoder::DecodeInternal() {
|
|||||||
for (AttributeValueIndex i(0); i < material_name_to_id_.size(); ++i) {
|
for (AttributeValueIndex i(0); i < material_name_to_id_.size(); ++i) {
|
||||||
out_point_cloud_->attribute(material_att_id_)->SetAttributeValue(i, &i);
|
out_point_cloud_->attribute(material_att_id_)->SetAttributeValue(i, &i);
|
||||||
}
|
}
|
||||||
|
// Set custom id 0 for the material attribute.
|
||||||
|
out_point_cloud_->attribute(material_att_id_)->set_custom_id(0);
|
||||||
|
}
|
||||||
|
if (num_sub_objects_ > 1) {
|
||||||
|
GeometryAttribute va;
|
||||||
|
if (num_sub_objects_ < 256) {
|
||||||
|
va.Init(GeometryAttribute::GENERIC, nullptr, 1, DT_UINT8, false, 1, 0);
|
||||||
|
} else if (num_sub_objects_ < (1 << 16)) {
|
||||||
|
va.Init(GeometryAttribute::GENERIC, nullptr, 1, DT_UINT16, false, 2, 0);
|
||||||
|
} else {
|
||||||
|
va.Init(GeometryAttribute::GENERIC, nullptr, 1, DT_UINT32, false, 4, 0);
|
||||||
|
}
|
||||||
|
sub_obj_att_id_ =
|
||||||
|
out_point_cloud_->AddAttribute(va, false, num_sub_objects_);
|
||||||
|
// Fill the sub object id entries.
|
||||||
|
for (AttributeValueIndex i(0); i < num_sub_objects_; ++i) {
|
||||||
|
out_point_cloud_->attribute(sub_obj_att_id_)->SetAttributeValue(i, &i);
|
||||||
|
}
|
||||||
|
// Set custom id 1 for the sub object attribute.
|
||||||
|
out_point_cloud_->attribute(sub_obj_att_id_)->set_custom_id(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform a second iteration of parsing and fill all the data.
|
// Perform a second iteration of parsing and fill all the data.
|
||||||
@ -171,6 +194,7 @@ void ObjDecoder::ResetCounters() {
|
|||||||
num_tex_coords_ = 0;
|
num_tex_coords_ = 0;
|
||||||
num_normals_ = 0;
|
num_normals_ = 0;
|
||||||
last_material_id_ = 0;
|
last_material_id_ = 0;
|
||||||
|
num_sub_objects_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ObjDecoder::ParseDefinition(bool *error) {
|
bool ObjDecoder::ParseDefinition(bool *error) {
|
||||||
@ -197,6 +221,8 @@ bool ObjDecoder::ParseDefinition(bool *error) {
|
|||||||
return true;
|
return true;
|
||||||
if (ParseMaterialLib(error))
|
if (ParseMaterialLib(error))
|
||||||
return true;
|
return true;
|
||||||
|
if (ParseObject(error))
|
||||||
|
return true;
|
||||||
// No known definition was found. Ignore the line.
|
// No known definition was found. Ignore the line.
|
||||||
parser::SkipLine(buffer());
|
parser::SkipLine(buffer());
|
||||||
return true;
|
return true;
|
||||||
@ -347,6 +373,12 @@ bool ObjDecoder::ParseFace(bool *error) {
|
|||||||
out_point_cloud_->attribute(material_att_id_)
|
out_point_cloud_->attribute(material_att_id_)
|
||||||
->SetPointMapEntry(vert_id, AttributeValueIndex(last_material_id_));
|
->SetPointMapEntry(vert_id, AttributeValueIndex(last_material_id_));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sub_obj_att_id_ >= 0) {
|
||||||
|
const int sub_obj_id = num_sub_objects_ > 0 ? num_sub_objects_ - 1 : 0;
|
||||||
|
out_point_cloud_->attribute(sub_obj_att_id_)
|
||||||
|
->SetPointMapEntry(vert_id, AttributeValueIndex(sub_obj_id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
++num_obj_faces_;
|
++num_obj_faces_;
|
||||||
@ -410,6 +442,22 @@ bool ObjDecoder::ParseMaterial(bool * /* error */) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ObjDecoder::ParseObject(bool *error) {
|
||||||
|
std::array<char, 2> c;
|
||||||
|
if (!buffer()->Peek(&c)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (std::memcmp(&c[0], "o ", 2) != 0)
|
||||||
|
return false;
|
||||||
|
buffer()->Advance(1);
|
||||||
|
parser::SkipWhitespace(buffer());
|
||||||
|
std::string obj_name;
|
||||||
|
if (!parser::ParseString(buffer(), &obj_name))
|
||||||
|
return false;
|
||||||
|
num_sub_objects_++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool ObjDecoder::ParseVertexIndices(std::array<int32_t, 3> *out_indices) {
|
bool ObjDecoder::ParseVertexIndices(std::array<int32_t, 3> *out_indices) {
|
||||||
// Parsed attribute indices can be in format:
|
// Parsed attribute indices can be in format:
|
||||||
// 1. POS_INDEX
|
// 1. POS_INDEX
|
||||||
|
@ -68,6 +68,7 @@ class ObjDecoder {
|
|||||||
bool ParseFace(bool *error);
|
bool ParseFace(bool *error);
|
||||||
bool ParseMaterialLib(bool *error);
|
bool ParseMaterialLib(bool *error);
|
||||||
bool ParseMaterial(bool *error);
|
bool ParseMaterial(bool *error);
|
||||||
|
bool ParseObject(bool *error);
|
||||||
|
|
||||||
// Parses triplet of position, tex coords and normal indices.
|
// Parses triplet of position, tex coords and normal indices.
|
||||||
// Returns false on error.
|
// Returns false on error.
|
||||||
@ -84,11 +85,13 @@ class ObjDecoder {
|
|||||||
int num_positions_;
|
int num_positions_;
|
||||||
int num_tex_coords_;
|
int num_tex_coords_;
|
||||||
int num_normals_;
|
int num_normals_;
|
||||||
|
int num_sub_objects_;
|
||||||
|
|
||||||
int pos_att_id_;
|
int pos_att_id_;
|
||||||
int tex_att_id_;
|
int tex_att_id_;
|
||||||
int norm_att_id_;
|
int norm_att_id_;
|
||||||
int material_att_id_;
|
int material_att_id_;
|
||||||
|
int sub_obj_att_id_; // Attribute id for storing sub-objects.
|
||||||
|
|
||||||
bool deduplicate_input_values_;
|
bool deduplicate_input_values_;
|
||||||
|
|
||||||
|
@ -54,4 +54,21 @@ TEST_F(ObjDecoderTest, ParialAttributesOBJ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_F(ObjDecoderTest, SubObjects) {
|
||||||
|
// Tests loading an Obj with sub objects.
|
||||||
|
const std::string file_name = "cube_att_sub_o.obj";
|
||||||
|
const std::unique_ptr<Mesh> mesh(DecodeObj<Mesh>(file_name));
|
||||||
|
ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name;
|
||||||
|
ASSERT_GT(mesh->num_faces(), 0);
|
||||||
|
|
||||||
|
// A sub object attribute should be the fourth attribute of the mesh (in this
|
||||||
|
// case).
|
||||||
|
ASSERT_EQ(mesh->num_attributes(), 4);
|
||||||
|
ASSERT_EQ(mesh->attribute(3)->attribute_type(), GeometryAttribute::GENERIC);
|
||||||
|
// There should be 3 different sub objects used in the model.
|
||||||
|
ASSERT_EQ(mesh->attribute(3)->size(), 3);
|
||||||
|
// Verify that the sub object attribute has custom id == 1.
|
||||||
|
ASSERT_EQ(mesh->attribute(3)->custom_id(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace draco
|
} // namespace draco
|
||||||
|
File diff suppressed because one or more lines are too long
@ -38,6 +38,7 @@ interface PointAttribute {
|
|||||||
boolean normalized();
|
boolean normalized();
|
||||||
long byte_stride();
|
long byte_stride();
|
||||||
long byte_offset();
|
long byte_offset();
|
||||||
|
long custom_id();
|
||||||
};
|
};
|
||||||
|
|
||||||
[Prefix="draco::"]
|
[Prefix="draco::"]
|
||||||
|
188
mesh/mesh_are_equivalent.cc
Normal file
188
mesh/mesh_are_equivalent.cc
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
// Copyright 2016 The Draco Authors.
|
||||||
|
//
|
||||||
|
// 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 "mesh/mesh_are_equivalent.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace draco {
|
||||||
|
|
||||||
|
void MeshAreEquivalent::PrintPostion(const Mesh &mesh, FaceIndex f, int32_t c) {
|
||||||
|
fprintf(stderr, "Printing position for (%i,%i)\n", f.value(), c);
|
||||||
|
const auto pos_att = mesh.GetNamedAttribute(GeometryAttribute::POSITION);
|
||||||
|
const PointIndex ver_index = mesh.face(f)[c];
|
||||||
|
const AttributeValueIndex pos_index = pos_att->mapped_index(ver_index);
|
||||||
|
const auto pos = pos_att->GetValue<float, 3>(pos_index);
|
||||||
|
fprintf(stderr, "Position (%f,%f,%f)\n", pos[0], pos[1], pos[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f MeshAreEquivalent::GetPostion(const Mesh &mesh, FaceIndex f,
|
||||||
|
int32_t c) {
|
||||||
|
const auto pos_att = mesh.GetNamedAttribute(GeometryAttribute::POSITION);
|
||||||
|
const PointIndex ver_index = mesh.face(f)[c];
|
||||||
|
const AttributeValueIndex pos_index = pos_att->mapped_index(ver_index);
|
||||||
|
const auto pos = pos_att->GetValue<float, 3>(pos_index);
|
||||||
|
return Vector3f(pos[0], pos[1], pos[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshAreEquivalent::InitCornerIndexOfSmallestPointXYZ() {
|
||||||
|
DCHECK_EQ(mesh_infos_[0].corner_index_of_smallest_vertex.size(), 0);
|
||||||
|
DCHECK_EQ(mesh_infos_[1].corner_index_of_smallest_vertex.size(), 0);
|
||||||
|
for (int i = 0; i < 2; ++i) {
|
||||||
|
mesh_infos_[i].corner_index_of_smallest_vertex.reserve(num_faces_);
|
||||||
|
for (FaceIndex f(0); f < num_faces_; ++f) {
|
||||||
|
mesh_infos_[i].corner_index_of_smallest_vertex.push_back(
|
||||||
|
ComputeCornerIndexOfSmallestPointXYZ(mesh_infos_[i].mesh, f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DCHECK_EQ(mesh_infos_[0].corner_index_of_smallest_vertex.size(), num_faces_);
|
||||||
|
DCHECK_EQ(mesh_infos_[1].corner_index_of_smallest_vertex.size(), num_faces_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshAreEquivalent::InitOrderedFaceIndex() {
|
||||||
|
DCHECK_EQ(mesh_infos_[0].ordered_index_of_face.size(), 0);
|
||||||
|
DCHECK_EQ(mesh_infos_[1].ordered_index_of_face.size(), 0);
|
||||||
|
for (int32_t i = 0; i < 2; ++i) {
|
||||||
|
mesh_infos_[i].ordered_index_of_face.reserve(num_faces_);
|
||||||
|
for (FaceIndex j(0); j < num_faces_; ++j) {
|
||||||
|
mesh_infos_[i].ordered_index_of_face.push_back(j);
|
||||||
|
}
|
||||||
|
const FaceIndexLess less(mesh_infos_[i]);
|
||||||
|
std::sort(mesh_infos_[i].ordered_index_of_face.begin(),
|
||||||
|
mesh_infos_[i].ordered_index_of_face.end(), less);
|
||||||
|
|
||||||
|
DCHECK_EQ(mesh_infos_[i].ordered_index_of_face.size(), num_faces_);
|
||||||
|
DCHECK(std::is_sorted(mesh_infos_[i].ordered_index_of_face.begin(),
|
||||||
|
mesh_infos_[i].ordered_index_of_face.end(), less));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t MeshAreEquivalent::ComputeCornerIndexOfSmallestPointXYZ(
|
||||||
|
const Mesh &mesh, FaceIndex f) {
|
||||||
|
Vector3f pos[3]; // For the three corners.
|
||||||
|
for (int32_t i = 0; i < 3; ++i) {
|
||||||
|
pos[i] = GetPostion(mesh, f, i);
|
||||||
|
}
|
||||||
|
const auto min_it = std::min_element(pos, pos + 3);
|
||||||
|
return min_it - pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshAreEquivalent::Init(const Mesh &mesh0, const Mesh &mesh1) {
|
||||||
|
mesh_infos_.clear();
|
||||||
|
DCHECK_EQ(mesh_infos_.size(), 0);
|
||||||
|
|
||||||
|
num_faces_ = mesh1.num_faces();
|
||||||
|
mesh_infos_.push_back(MeshInfo(mesh0));
|
||||||
|
mesh_infos_.push_back(MeshInfo(mesh1));
|
||||||
|
|
||||||
|
DCHECK_EQ(mesh_infos_.size(), 2);
|
||||||
|
DCHECK_EQ(mesh_infos_[0].corner_index_of_smallest_vertex.size(), 0);
|
||||||
|
DCHECK_EQ(mesh_infos_[1].corner_index_of_smallest_vertex.size(), 0);
|
||||||
|
DCHECK_EQ(mesh_infos_[0].ordered_index_of_face.size(), 0);
|
||||||
|
DCHECK_EQ(mesh_infos_[1].ordered_index_of_face.size(), 0);
|
||||||
|
|
||||||
|
InitCornerIndexOfSmallestPointXYZ();
|
||||||
|
InitOrderedFaceIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MeshAreEquivalent::operator()(const Mesh &mesh0, const Mesh &mesh1) {
|
||||||
|
if (mesh0.num_faces() != mesh1.num_faces())
|
||||||
|
return false;
|
||||||
|
if (mesh0.num_attributes() != mesh1.num_attributes())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// The following function inits mesh info, i.e., computes the order of
|
||||||
|
// faces with respect to the lex order. This way one can then compare the
|
||||||
|
// the two meshes face by face. It also determines the first corner of each
|
||||||
|
// face with respect to lex order.
|
||||||
|
Init(mesh0, mesh1);
|
||||||
|
|
||||||
|
// Check for every attribute that is valid that every corner is identical.
|
||||||
|
typedef GeometryAttribute::Type AttributeType;
|
||||||
|
const int att_max = AttributeType::NAMED_ATTRIBUTES_COUNT;
|
||||||
|
for (int att_id = 0; att_id < att_max; ++att_id) {
|
||||||
|
// First check for existence of the attribute in both meshes.
|
||||||
|
const PointAttribute *const att0 =
|
||||||
|
mesh0.GetNamedAttribute(AttributeType(att_id));
|
||||||
|
const PointAttribute *const att1 =
|
||||||
|
mesh1.GetNamedAttribute(AttributeType(att_id));
|
||||||
|
if (att0 == nullptr && att1 == nullptr)
|
||||||
|
continue;
|
||||||
|
if (att0 == nullptr)
|
||||||
|
return false;
|
||||||
|
if (att1 == nullptr)
|
||||||
|
return false;
|
||||||
|
if (att0->data_type() != att1->data_type())
|
||||||
|
return false;
|
||||||
|
if (att0->components_count() != att1->components_count())
|
||||||
|
return false;
|
||||||
|
if (att0->normalized() != att1->normalized())
|
||||||
|
return false;
|
||||||
|
if (att0->byte_stride() != att1->byte_stride())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DCHECK(att0->IsValid());
|
||||||
|
DCHECK(att1->IsValid());
|
||||||
|
|
||||||
|
// Prepare blocks of memomry to hold data of corners for this attribute.
|
||||||
|
std::unique_ptr<uint8[]> data0(new uint8[att0->byte_stride()]);
|
||||||
|
std::unique_ptr<uint8[]> data1(new uint8[att0->byte_stride()]);
|
||||||
|
|
||||||
|
// Check every corner of every face.
|
||||||
|
for (int i = 0; i < num_faces_; ++i) {
|
||||||
|
const FaceIndex f0 = mesh_infos_[0].ordered_index_of_face[i];
|
||||||
|
const FaceIndex f1 = mesh_infos_[1].ordered_index_of_face[i];
|
||||||
|
const int c0_off = mesh_infos_[0].corner_index_of_smallest_vertex[f0];
|
||||||
|
const int c1_off = mesh_infos_[1].corner_index_of_smallest_vertex[f1];
|
||||||
|
|
||||||
|
for (int c = 0; c < 3; ++c) {
|
||||||
|
// Get the index of each corner.
|
||||||
|
const PointIndex corner0 = mesh0.face(f0)[(c0_off + c) % 3];
|
||||||
|
const PointIndex corner1 = mesh1.face(f1)[(c1_off + c) % 3];
|
||||||
|
// Map it to the right index for that attribute.
|
||||||
|
const AttributeValueIndex index0 = att0->mapped_index(corner0);
|
||||||
|
const AttributeValueIndex index1 = att1->mapped_index(corner1);
|
||||||
|
|
||||||
|
// Obtaining the data.
|
||||||
|
att0->GetValue(index0, data0.get());
|
||||||
|
att1->GetValue(index1, data1.get());
|
||||||
|
// Compare the data as is in memory.
|
||||||
|
if (memcmp(data0.get(), data1.get(), att0->byte_stride()) != 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MeshAreEquivalent::FaceIndexLess::operator()(FaceIndex f0,
|
||||||
|
FaceIndex f1) const {
|
||||||
|
if (f0 == f1)
|
||||||
|
return false;
|
||||||
|
const int c0 = mesh_info.corner_index_of_smallest_vertex[f0];
|
||||||
|
const int c1 = mesh_info.corner_index_of_smallest_vertex[f1];
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
const Vector3f vf0 = GetPostion(mesh_info.mesh, f0, (c0 + i) % 3);
|
||||||
|
const Vector3f vf1 = GetPostion(mesh_info.mesh, f1, (c1 + i) % 3);
|
||||||
|
if (vf0 < vf1)
|
||||||
|
return true;
|
||||||
|
if (vf1 < vf0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// In case the two faces are equivalent.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace draco
|
70
mesh/mesh_are_equivalent.h
Normal file
70
mesh/mesh_are_equivalent.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2016 The Draco Authors.
|
||||||
|
//
|
||||||
|
// 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_MESH_MESH_ARE_EQUIVALENT_H_
|
||||||
|
#define DRACO_MESH_MESH_ARE_EQUIVALENT_H_
|
||||||
|
|
||||||
|
#include "core/vector_d.h"
|
||||||
|
#include "mesh/mesh.h"
|
||||||
|
|
||||||
|
// This file defines a functor to compare two meshes for equivalency up
|
||||||
|
// to permutation of the vertices.
|
||||||
|
namespace draco {
|
||||||
|
|
||||||
|
// A functor to compare two meshes for equivalency up to permutation of the
|
||||||
|
// vertices.
|
||||||
|
class MeshAreEquivalent {
|
||||||
|
public:
|
||||||
|
// Returns true if both meshes are equivalent up to permutation of
|
||||||
|
// the internal order of vertices. This includes all attributes.
|
||||||
|
bool operator()(const Mesh &mesh0, const Mesh &mesh1);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Internal type to keep overview.
|
||||||
|
struct MeshInfo {
|
||||||
|
explicit MeshInfo(const Mesh &in_mesh) : mesh(in_mesh) {}
|
||||||
|
const Mesh &mesh;
|
||||||
|
std::vector<FaceIndex> ordered_index_of_face;
|
||||||
|
IndexTypeVector<FaceIndex, int> corner_index_of_smallest_vertex;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prepare functor for actual comparision.
|
||||||
|
void Init(const Mesh &mesh0, const Mesh &mesh1);
|
||||||
|
|
||||||
|
// Get position as Vector3f of corner c of face f.
|
||||||
|
static Vector3f GetPostion(const Mesh &mesh, FaceIndex f, int32_t c);
|
||||||
|
// Internal helper function mostly for debugging.
|
||||||
|
void PrintPostion(const Mesh &mesh, FaceIndex f, int32_t c);
|
||||||
|
// Get the corner index of the lex smallest vertex of face f.
|
||||||
|
static int32_t ComputeCornerIndexOfSmallestPointXYZ(const Mesh &mesh,
|
||||||
|
FaceIndex f);
|
||||||
|
|
||||||
|
// Less compare functor for two faces (represented by their indices)
|
||||||
|
// with respect to their lex order.
|
||||||
|
struct FaceIndexLess {
|
||||||
|
FaceIndexLess(const MeshInfo &in_mesh_info) : mesh_info(in_mesh_info) {}
|
||||||
|
bool operator()(FaceIndex f0, FaceIndex f1) const;
|
||||||
|
const MeshInfo &mesh_info;
|
||||||
|
};
|
||||||
|
|
||||||
|
void InitCornerIndexOfSmallestPointXYZ();
|
||||||
|
void InitOrderedFaceIndex();
|
||||||
|
|
||||||
|
std::vector<MeshInfo> mesh_infos_;
|
||||||
|
int32_t num_faces_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace draco
|
||||||
|
|
||||||
|
#endif // DRACO_MESH_MESH_ARE_EQUIVALENT_H_
|
109
mesh/mesh_are_equivalent_test.cc
Normal file
109
mesh/mesh_are_equivalent_test.cc
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright 2016 The Draco Authors.
|
||||||
|
//
|
||||||
|
// 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 "mesh/mesh_are_equivalent.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "core/draco_test_base.h"
|
||||||
|
#include "core/draco_test_utils.h"
|
||||||
|
#include "io/mesh_io.h"
|
||||||
|
#include "io/obj_decoder.h"
|
||||||
|
#include "mesh/mesh.h"
|
||||||
|
|
||||||
|
namespace draco {
|
||||||
|
|
||||||
|
class MeshAreEquivalentTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<Mesh> DecodeObj(const std::string &file_name) const {
|
||||||
|
const std::string path = GetTestFileFullPath(file_name);
|
||||||
|
std::unique_ptr<Mesh> mesh(new Mesh());
|
||||||
|
ObjDecoder decoder;
|
||||||
|
if (!decoder.DecodeFromFile(path, mesh.get()))
|
||||||
|
return nullptr;
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(MeshAreEquivalentTest, TestOnIndenticalMesh) {
|
||||||
|
const std::string file_name = "test_nm.obj";
|
||||||
|
const std::unique_ptr<Mesh> mesh(DecodeObj(file_name));
|
||||||
|
ASSERT_NE(mesh, nullptr) << "Failed to load test model." << file_name;
|
||||||
|
MeshAreEquivalent equiv;
|
||||||
|
ASSERT_TRUE(equiv(*mesh, *mesh));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MeshAreEquivalentTest, TestPermutedOneFace) {
|
||||||
|
const std::string file_name_0 = "one_face_123.obj";
|
||||||
|
const std::string file_name_1 = "one_face_312.obj";
|
||||||
|
const std::string file_name_2 = "one_face_321.obj";
|
||||||
|
const std::unique_ptr<Mesh> mesh_0(DecodeObj(file_name_0));
|
||||||
|
const std::unique_ptr<Mesh> mesh_1(DecodeObj(file_name_1));
|
||||||
|
const std::unique_ptr<Mesh> mesh_2(DecodeObj(file_name_2));
|
||||||
|
ASSERT_NE(mesh_0, nullptr) << "Failed to load test model." << file_name_0;
|
||||||
|
ASSERT_NE(mesh_1, nullptr) << "Failed to load test model." << file_name_1;
|
||||||
|
ASSERT_NE(mesh_2, nullptr) << "Failed to load test model." << file_name_2;
|
||||||
|
MeshAreEquivalent equiv;
|
||||||
|
ASSERT_TRUE(equiv(*mesh_0, *mesh_0));
|
||||||
|
ASSERT_TRUE(equiv(*mesh_0, *mesh_1)); // Face rotated.
|
||||||
|
ASSERT_FALSE(equiv(*mesh_0, *mesh_2)); // Face inverted.
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MeshAreEquivalentTest, TestPermutedTwoFaces) {
|
||||||
|
const std::string file_name_0 = "two_faces_123.obj";
|
||||||
|
const std::string file_name_1 = "two_faces_312.obj";
|
||||||
|
const std::unique_ptr<Mesh> mesh_0(DecodeObj(file_name_0));
|
||||||
|
const std::unique_ptr<Mesh> mesh_1(DecodeObj(file_name_1));
|
||||||
|
ASSERT_NE(mesh_0, nullptr) << "Failed to load test model." << file_name_0;
|
||||||
|
ASSERT_NE(mesh_1, nullptr) << "Failed to load test model." << file_name_1;
|
||||||
|
MeshAreEquivalent equiv;
|
||||||
|
ASSERT_TRUE(equiv(*mesh_0, *mesh_0));
|
||||||
|
ASSERT_TRUE(equiv(*mesh_1, *mesh_1));
|
||||||
|
ASSERT_TRUE(equiv(*mesh_0, *mesh_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
TEST_F(MeshAreEquivalentTest, TestPermutedThreeFaces) {
|
||||||
|
const std::string file_name_0 = "three_faces_123.obj";
|
||||||
|
const std::string file_name_1 = "three_faces_312.obj";
|
||||||
|
const std::unique_ptr<Mesh> mesh_0(DecodeObj(file_name_0));
|
||||||
|
const std::unique_ptr<Mesh> mesh_1(DecodeObj(file_name_1));
|
||||||
|
ASSERT_NE(mesh_0, nullptr) << "Failed to load test model." << file_name_0;
|
||||||
|
ASSERT_NE(mesh_1, nullptr) << "Failed to load test model." << file_name_1;
|
||||||
|
MeshAreEquivalent equiv;
|
||||||
|
ASSERT_TRUE(equiv(*mesh_0, *mesh_0));
|
||||||
|
ASSERT_TRUE(equiv(*mesh_1, *mesh_1));
|
||||||
|
ASSERT_TRUE(equiv(*mesh_0, *mesh_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test checks that the edgebreaker algorithm does not change the mesh up
|
||||||
|
// to the order of faces and vertices.
|
||||||
|
TEST_F(MeshAreEquivalentTest, TestOnBigMesh) {
|
||||||
|
const std::string file_name = "test_nm.obj";
|
||||||
|
const std::unique_ptr<Mesh> mesh0(DecodeObj(file_name));
|
||||||
|
ASSERT_NE(mesh0, nullptr) << "Failed to load test model." << file_name;
|
||||||
|
|
||||||
|
std::unique_ptr<Mesh> mesh1;
|
||||||
|
std::stringstream ss;
|
||||||
|
WriteMeshIntoStream(mesh0.get(), ss, MESH_EDGEBREAKER_ENCODING);
|
||||||
|
ReadMeshFromStream(&mesh1, ss);
|
||||||
|
ASSERT_TRUE(ss.good()) << "Mesh IO failed.";
|
||||||
|
|
||||||
|
MeshAreEquivalent equiv;
|
||||||
|
ASSERT_TRUE(equiv(*mesh0, *mesh0));
|
||||||
|
ASSERT_TRUE(equiv(*mesh1, *mesh1));
|
||||||
|
ASSERT_TRUE(equiv(*mesh0, *mesh1));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace draco
|
@ -169,10 +169,28 @@ int main(int argc, char **argv) {
|
|||||||
options.is_point_cloud = true;
|
options.is_point_cloud = true;
|
||||||
} else if (!strcmp("-qp", argv[i]) && i < argc_check) {
|
} else if (!strcmp("-qp", argv[i]) && i < argc_check) {
|
||||||
options.pos_quantization_bits = StringToInt(argv[++i]);
|
options.pos_quantization_bits = StringToInt(argv[++i]);
|
||||||
|
if (options.pos_quantization_bits > 31) {
|
||||||
|
printf(
|
||||||
|
"Error: The maximum number of quantization bits for the position "
|
||||||
|
"attribute is 31.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
} else if (!strcmp("-qt", argv[i]) && i < argc_check) {
|
} else if (!strcmp("-qt", argv[i]) && i < argc_check) {
|
||||||
options.tex_coords_quantization_bits = StringToInt(argv[++i]);
|
options.tex_coords_quantization_bits = StringToInt(argv[++i]);
|
||||||
|
if (options.tex_coords_quantization_bits > 31) {
|
||||||
|
printf(
|
||||||
|
"Error: The maximum number of quantization bits for the texture "
|
||||||
|
"coordinate attribute is 31.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
} else if (!strcmp("-qn", argv[i]) && i < argc_check) {
|
} else if (!strcmp("-qn", argv[i]) && i < argc_check) {
|
||||||
options.normals_quantization_bits = StringToInt(argv[++i]);
|
options.normals_quantization_bits = StringToInt(argv[++i]);
|
||||||
|
if (options.normals_quantization_bits > 31) {
|
||||||
|
printf(
|
||||||
|
"Error: The maximum number of quantization bits for the normal "
|
||||||
|
"attribute is 31.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
} else if (!strcmp("-cl", argv[i]) && i < argc_check) {
|
} else if (!strcmp("-cl", argv[i]) && i < argc_check) {
|
||||||
options.compression_level = StringToInt(argv[++i]);
|
options.compression_level = StringToInt(argv[++i]);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user