Skip to content

Commit

Permalink
1. Added support for loading quad meshes from .obj files
Browse files Browse the repository at this point in the history
2. Fixed bug in javascript decoders that caused them to fail when
loading large geometries
  • Loading branch information
ondys committed Mar 21, 2017
1 parent 10a25ba commit c7a5e90
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 79 deletions.
168 changes: 113 additions & 55 deletions io/obj_decoder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -322,66 +322,65 @@ bool ObjDecoder::ParseFace(bool *error) {
// Face definition found!
buffer()->Advance(1);
if (!counting_mode_) {
// Parse face indices.
for (int i = 0; i < 3; ++i) {
const PointIndex vert_id(3 * num_obj_faces_ + i);
parser::SkipWhitespace(buffer());
std::array<int32_t, 3> indices;
if (!ParseVertexIndices(&indices)) {
std::array<int32_t, 3> indices[4];
// Parse face indices (we try to look for up to four to support quads).
int num_valid_indices = 0;
for (int i = 0; i < 4; ++i) {
if (!ParseVertexIndices(&indices[i])) {
if (i == 3) {
break; // It's ok if there is no fourth vertex index.
}
*error = true;
return true;
}
// Use face entries to store mapping between vertex and attribute indices.
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));
}

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));
++num_valid_indices;
}
// Process the first face.
for (int i = 0; i < 3; ++i) {
const PointIndex vert_id(3 * num_obj_faces_ + i);
MapPointToVertexIndices(vert_id, indices[i]);
}
++num_obj_faces_;
if (num_valid_indices == 4) {
// Add an additional triangle for the quad.
//
// 3----2
// | / |
// | / |
// 0----1
//
const PointIndex vert_id(3 * num_obj_faces_);
MapPointToVertexIndices(vert_id, indices[0]);
MapPointToVertexIndices(vert_id + 1, indices[2]);
MapPointToVertexIndices(vert_id + 2, indices[3]);
++num_obj_faces_;
}
} else {
// We are in the couting mode.
// We need to determine how many triangles are in the obj face.
// Go over the line and check how many gaps there are between non-empty
// sub-strings.
parser::SkipWhitespace(buffer());
int num_indices = 0;
bool is_end = false;
while (buffer()->Peek(&c) && c != '\n') {
if (parser::PeekWhitespace(buffer(), &is_end)) {
buffer()->Advance(1);
} else {
// Non-whitespace reached.. assume it's index declaration, skip it.
num_indices++;
while (!parser::PeekWhitespace(buffer(), &is_end)) {
buffer()->Advance(1);
}
}
}
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());
return true;
}
Expand Down Expand Up @@ -464,7 +463,7 @@ bool ObjDecoder::ParseVertexIndices(std::array<int32_t, 3> *out_indices) {
// 2. POS_INDEX/TEX_COORD_INDEX
// 3. POS_INDEX/TEX_COORD_INDEX/NORMAL_INDEX
// 4. POS_INDEX//NORMAL_INDEX
parser::SkipWhitespace(buffer());
parser::SkipCharacters(buffer(), " \t");
if (!parser::ParseSignedInt(buffer(), &(*out_indices)[0]) ||
(*out_indices)[0] == 0)
return false; // Position index must be present and valid.
Expand Down Expand Up @@ -496,6 +495,65 @@ bool ObjDecoder::ParseVertexIndices(std::array<int32_t, 3> *out_indices) {
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) {
// Get the correct path to the |file_name| using the folder from
// |input_file_name_| as the root folder.
Expand Down
5 changes: 5 additions & 0 deletions io/obj_decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ class ObjDecoder {
// Returns false on error.
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.
bool ParseMaterialFile(const std::string &file_name, bool *error);
bool ParseMaterialFileDefinition(bool *error);
Expand Down
18 changes: 18 additions & 0 deletions io/obj_decoder_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,22 @@ TEST_F(ObjDecoderTest, SubObjects) {
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
20 changes: 20 additions & 0 deletions io/parser_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@
namespace draco {
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) {
bool end_reached = false;
while (PeekWhitespace(buffer, &end_reached) && !end_reached) {
Expand Down
3 changes: 3 additions & 0 deletions io/parser_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
namespace draco {
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.
void SkipWhitespace(DecoderBuffer *buffer);

Expand Down
16 changes: 7 additions & 9 deletions javascript/draco_decoder.js

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions javascript/draco_mesh_decoder.js

Large diffs are not rendered by default.

15 changes: 7 additions & 8 deletions javascript/draco_point_cloud_decoder.js

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions javascript/emscripten/version.js
Original file line number Diff line number Diff line change
@@ -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 changes: 11 additions & 0 deletions testdata/complex_poly.obj
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions testdata/cube_quads.obj
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit c7a5e90

Please sign in to comment.