1. Added support for loading quad meshes from .obj files

2. Fixed bug in javascript decoders that caused them to fail when
loading large geometries
This commit is contained in:
Ondrej Stava 2017-03-21 16:06:58 -07:00
parent 10a25ba708
commit c7a5e903e5
11 changed files with 245 additions and 79 deletions

View File

@ -322,66 +322,65 @@ bool ObjDecoder::ParseFace(bool *error) {
// Face definition found! // Face definition found!
buffer()->Advance(1); buffer()->Advance(1);
if (!counting_mode_) { if (!counting_mode_) {
// Parse face indices. std::array<int32_t, 3> indices[4];
for (int i = 0; i < 3; ++i) { // Parse face indices (we try to look for up to four to support quads).
const PointIndex vert_id(3 * num_obj_faces_ + i); int num_valid_indices = 0;
parser::SkipWhitespace(buffer()); for (int i = 0; i < 4; ++i) {
std::array<int32_t, 3> indices; if (!ParseVertexIndices(&indices[i])) {
if (!ParseVertexIndices(&indices)) { if (i == 3) {
break; // It's ok if there is no fourth vertex index.
}
*error = true; *error = true;
return true; return true;
} }
// Use face entries to store mapping between vertex and attribute indices. ++num_valid_indices;
if (indices[0] > 0) { }
out_point_cloud_->attribute(pos_att_id_) // Process the first face.
->SetPointMapEntry(vert_id, AttributeValueIndex(indices[0] - 1)); for (int i = 0; i < 3; ++i) {
} else if (indices[0] < 0) { const PointIndex vert_id(3 * num_obj_faces_ + i);
out_point_cloud_->attribute(pos_att_id_) MapPointToVertexIndices(vert_id, indices[i]);
->SetPointMapEntry( }
vert_id, AttributeValueIndex(num_positions_ + indices[0])); ++num_obj_faces_;
} if (num_valid_indices == 4) {
// Add an additional triangle for the quad.
if (indices[1] > 0) { //
out_point_cloud_->attribute(tex_att_id_) // 3----2
->SetPointMapEntry(vert_id, AttributeValueIndex(indices[1] - 1)); // | / |
} else if (indices[1] < 0) { // | / |
out_point_cloud_->attribute(tex_att_id_) // 0----1
->SetPointMapEntry( //
vert_id, AttributeValueIndex(num_tex_coords_ + indices[1])); const PointIndex vert_id(3 * num_obj_faces_);
} else if (tex_att_id_ >= 0) { MapPointToVertexIndices(vert_id, indices[0]);
// Texture index not provided but expected. Insert 0 entry as the MapPointToVertexIndices(vert_id + 1, indices[2]);
// default value. MapPointToVertexIndices(vert_id + 2, indices[3]);
out_point_cloud_->attribute(tex_att_id_) ++num_obj_faces_;
->SetPointMapEntry(vert_id, AttributeValueIndex(0)); }
} } else {
// We are in the couting mode.
if (indices[2] > 0) { // We need to determine how many triangles are in the obj face.
out_point_cloud_->attribute(norm_att_id_) // Go over the line and check how many gaps there are between non-empty
->SetPointMapEntry(vert_id, AttributeValueIndex(indices[2] - 1)); // sub-strings.
} else if (indices[2] < 0) { parser::SkipWhitespace(buffer());
out_point_cloud_->attribute(norm_att_id_) int num_indices = 0;
->SetPointMapEntry(vert_id, bool is_end = false;
AttributeValueIndex(num_normals_ + indices[2])); while (buffer()->Peek(&c) && c != '\n') {
} else if (norm_att_id_ >= 0) { if (parser::PeekWhitespace(buffer(), &is_end)) {
// Normal index not provided but expected. Insert 0 entry as the default buffer()->Advance(1);
// value. } else {
out_point_cloud_->attribute(norm_att_id_) // Non-whitespace reached.. assume it's index declaration, skip it.
->SetPointMapEntry(vert_id, AttributeValueIndex(0)); num_indices++;
} while (!parser::PeekWhitespace(buffer(), &is_end)) {
buffer()->Advance(1);
if (material_att_id_ >= 0) { }
out_point_cloud_->attribute(material_att_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));
} }
} }
if (is_end || num_indices < 3 || num_indices > 4) {
*error = true;
return false;
}
// Either one or two new triangles.
num_obj_faces_ += num_indices - 2;
} }
++num_obj_faces_;
parser::SkipLine(buffer()); parser::SkipLine(buffer());
return true; return true;
} }
@ -464,7 +463,7 @@ bool ObjDecoder::ParseVertexIndices(std::array<int32_t, 3> *out_indices) {
// 2. POS_INDEX/TEX_COORD_INDEX // 2. POS_INDEX/TEX_COORD_INDEX
// 3. POS_INDEX/TEX_COORD_INDEX/NORMAL_INDEX // 3. POS_INDEX/TEX_COORD_INDEX/NORMAL_INDEX
// 4. POS_INDEX//NORMAL_INDEX // 4. POS_INDEX//NORMAL_INDEX
parser::SkipWhitespace(buffer()); parser::SkipCharacters(buffer(), " \t");
if (!parser::ParseSignedInt(buffer(), &(*out_indices)[0]) || if (!parser::ParseSignedInt(buffer(), &(*out_indices)[0]) ||
(*out_indices)[0] == 0) (*out_indices)[0] == 0)
return false; // Position index must be present and valid. return false; // Position index must be present and valid.
@ -496,6 +495,65 @@ bool ObjDecoder::ParseVertexIndices(std::array<int32_t, 3> *out_indices) {
return true; return true;
} }
void ObjDecoder::MapPointToVertexIndices(
PointIndex vert_id, const std::array<int32_t, 3> &indices) {
// Use face entries to store mapping between vertex and attribute indices
// (positions, texture coordinates and normal indices).
// Any given index is used when indices[x] != 0. For positive values, the
// point is mapped directly to the specified attribute index. Negative input
// indices indicate addressing from the last element (e.g. -1 is the last
// attribute value of a given type, -2 the second last, etc.).
if (indices[0] > 0) {
out_point_cloud_->attribute(pos_att_id_)
->SetPointMapEntry(vert_id, AttributeValueIndex(indices[0] - 1));
} else if (indices[0] < 0) {
out_point_cloud_->attribute(pos_att_id_)
->SetPointMapEntry(vert_id,
AttributeValueIndex(num_positions_ + indices[0]));
}
if (indices[1] > 0) {
out_point_cloud_->attribute(tex_att_id_)
->SetPointMapEntry(vert_id, AttributeValueIndex(indices[1] - 1));
} else if (indices[1] < 0) {
out_point_cloud_->attribute(tex_att_id_)
->SetPointMapEntry(vert_id,
AttributeValueIndex(num_tex_coords_ + indices[1]));
} else if (tex_att_id_ >= 0) {
// Texture index not provided but expected. Insert 0 entry as the
// default value.
out_point_cloud_->attribute(tex_att_id_)
->SetPointMapEntry(vert_id, AttributeValueIndex(0));
}
if (indices[2] > 0) {
out_point_cloud_->attribute(norm_att_id_)
->SetPointMapEntry(vert_id, AttributeValueIndex(indices[2] - 1));
} else if (indices[2] < 0) {
out_point_cloud_->attribute(norm_att_id_)
->SetPointMapEntry(vert_id,
AttributeValueIndex(num_normals_ + indices[2]));
} else if (norm_att_id_ >= 0) {
// Normal index not provided but expected. Insert 0 entry as the default
// value.
out_point_cloud_->attribute(norm_att_id_)
->SetPointMapEntry(vert_id, AttributeValueIndex(0));
}
// Assign material index to the point if it is available.
if (material_att_id_ >= 0) {
out_point_cloud_->attribute(material_att_id_)
->SetPointMapEntry(vert_id, AttributeValueIndex(last_material_id_));
}
// Assign sub-object index to the point if it is available.
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));
}
}
bool ObjDecoder::ParseMaterialFile(const std::string &file_name, bool *error) { bool ObjDecoder::ParseMaterialFile(const std::string &file_name, bool *error) {
// Get the correct path to the |file_name| using the folder from // Get the correct path to the |file_name| using the folder from
// |input_file_name_| as the root folder. // |input_file_name_| as the root folder.

View File

@ -74,6 +74,11 @@ class ObjDecoder {
// Returns false on error. // Returns false on error.
bool ParseVertexIndices(std::array<int32_t, 3> *out_indices); bool ParseVertexIndices(std::array<int32_t, 3> *out_indices);
// Maps specified point index to the parsed vertex indices (triplet of
// position, texture coordinate, and normal indices) .
void MapPointToVertexIndices(PointIndex pi,
const std::array<int32_t, 3> &indices);
// Parses material file definitions from a separate file. // Parses material file definitions from a separate file.
bool ParseMaterialFile(const std::string &file_name, bool *error); bool ParseMaterialFile(const std::string &file_name, bool *error);
bool ParseMaterialFileDefinition(bool *error); bool ParseMaterialFileDefinition(bool *error);

View File

@ -71,4 +71,22 @@ TEST_F(ObjDecoderTest, SubObjects) {
ASSERT_EQ(mesh->attribute(3)->custom_id(), 1); ASSERT_EQ(mesh->attribute(3)->custom_id(), 1);
} }
TEST_F(ObjDecoderTest, QuadOBJ) {
// Tests loading an Obj with quad faces.
const std::string file_name = "cube_quads.obj";
const std::unique_ptr<Mesh> mesh(DecodeObj<Mesh>(file_name));
ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name;
ASSERT_EQ(mesh->num_faces(), 12);
ASSERT_EQ(mesh->num_attributes(), 3);
ASSERT_EQ(mesh->num_points(), 4 * 6); // Four points per quad face.
}
TEST_F(ObjDecoderTest, ComplexPolyOBJ) {
// Tests that we fail to load an obj with complex polygon (expected failure).
const std::string file_name = "complex_poly.obj";
const std::unique_ptr<Mesh> mesh(DecodeObj<Mesh>(file_name));
ASSERT_EQ(mesh, nullptr);
}
} // namespace draco } // namespace draco

View File

@ -22,6 +22,26 @@
namespace draco { namespace draco {
namespace parser { namespace parser {
void SkipCharacters(DecoderBuffer *buffer, const char *skip_chars) {
if (skip_chars == nullptr)
return;
const int num_skip_chars = strlen(skip_chars);
char c;
while (buffer->Peek(&c)) {
// Check all characters in the pattern.
bool skip = false;
for (int i = 0; i < num_skip_chars; ++i) {
if (c == skip_chars[i]) {
skip = true;
break;
}
}
if (!skip)
return;
buffer->Advance(1);
}
}
void SkipWhitespace(DecoderBuffer *buffer) { void SkipWhitespace(DecoderBuffer *buffer) {
bool end_reached = false; bool end_reached = false;
while (PeekWhitespace(buffer, &end_reached) && !end_reached) { while (PeekWhitespace(buffer, &end_reached) && !end_reached) {

View File

@ -20,6 +20,9 @@
namespace draco { namespace draco {
namespace parser { namespace parser {
// Skips to first character not included in |skip_chars|.
void SkipCharacters(DecoderBuffer *buffer, const char *skip_chars);
// Skips any whitespace until a regular character is reached. // Skips any whitespace until a regular character is reached.
void SkipWhitespace(DecoderBuffer *buffer); void SkipWhitespace(DecoderBuffer *buffer);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,27 @@
// 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.
// Returns true if the specified Draco version is supported by this decoder.
function isVersionSupported(versionString) {
if (typeof versionString !== 'string')
return false;
const version = versionString.split('.');
if (version.length < 2 || version.length > 3)
return false; // Unexpected version string.
if (version[0] > 0 || version[1] > 9)
return false;
return true;
}
Module['isVersionSupported'] = isVersionSupported;

11
testdata/complex_poly.obj vendored Normal file
View File

@ -0,0 +1,11 @@
v 0.0 0.0 0.0
v 0.0 0.0 1.0
v 0.0 1.0 0.0
v 0.0 1.0 1.0
v 1.0 0.0 0.0
v 1.0 0.0 1.0
v 1.0 1.0 0.0
v 1.0 1.0 1.0
f 1 2 3 4 5 6 7 8

27
testdata/cube_quads.obj vendored Normal file
View File

@ -0,0 +1,27 @@
v 0.0 0.0 0.0
v 0.0 0.0 1.0
v 0.0 1.0 0.0
v 0.0 1.0 1.0
v 1.0 0.0 0.0
v 1.0 0.0 1.0
v 1.0 1.0 0.0
v 1.0 1.0 1.0
vn 0.0 0.0 1.0
vn 0.0 0.0 -1.0
vn 0.0 1.0 0.0
vn 0.0 -1.0 0.0
vn 1.0 0.0 0.0
vn -1.0 0.0 0.0
vt 0.0 0.0
vt 0.0 1.0
vt 1.0 0.0
vt 1.0 1.0
f 1/4/2 3/3/2 7/1/2 5/2/2
f 1/2/6 2/4/6 4/3/6 3/1/6
f 3/1/3 4/2/3 8/4/3 7/3/3
f 5/4/5 7/3/5 8/1/5 6/2/5
f 1/2/4 5/4/4 6/3/4 2/1/4
f 2/2/1 6/4/1 8/3/1 4/1/1