diff --git a/README.md b/README.md index 24bd121..1c0fc22 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Tiny but poweful single file wavefront obj loader written in C++. No dependency What's new ---------- +* Jan 29, 2016 : Support n-polygon and OpenSubdiv crease tag! Thanks dboogert! * Nov 26, 2015 : Now single-header only!. * Nov 08, 2015 : Improved API. * Jun 23, 2015 : Various fixes and added more projects using tinyobjloader. Thanks many contributors! @@ -67,16 +68,13 @@ Features * Normal * Material * Unknown material attributes are returned as key-value(value is string) map. +* Crease tag('t'). This is OpenSubdiv specific(not in wavefront .obj specification) -Notes ------ - -Polygon is converted into triangle. TODO ---- -- [ ] Support quad polygon and some tags for OpenSubdiv http://graphics.pixar.com/opensubdiv/ +* [ ] Support different indices for vertex/normal/texcoord License ------- @@ -148,4 +146,43 @@ Usage } printf("\n"); } + + +Reading .obj without triangulation. Use `num_vertices[i]` to iterate over faces(indices). `num_vertices[i]` stores the number of vertices for ith face. + + #define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc + #include "tiny_obj_loader.h" + + std::string inputfile = "cornell_box.obj"; + std::vector shapes; + std::vector materials; + std::string err; + bool triangulate = false; + bool ret = tinyobj::LoadObj(shapes, materials, err, inputfile.c_str(), triangulate); + + if (!err.empty()) { // `err` may contain warning message. + std::cerr << err << std::endl; + } + + if (!ret) { + exit(1); + } + + for (size_t i = 0; i < shapes.size(); i++) { + + size_t indexOffset = 0; + for (size_t n = 0; n < shapes[i].mesh.num_vertices.size(); n++) { + int ngon = shapes[i].mesh.num_vertices[n]; + for (size_t f = 0; f < ngon; f++) { + size_t v = shapes[i].mesh.indices[indexOffset + f]; + printf(" face[%ld] v[%ld] = (%f, %f, %f)\n", n, + shapes[i].mesh.positions[3*v+0], + shapes[i].mesh.positions[3*v+1], + shapes[i].mesh.positions[3*v+2]); + + } + indexOffset += ngon; + } + + } diff --git a/catmark_torus_creases0.obj b/catmark_torus_creases0.obj new file mode 100644 index 0000000..bf18f15 --- /dev/null +++ b/catmark_torus_creases0.obj @@ -0,0 +1,101 @@ +# +# Copyright 2013 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +# This file uses centimeters as units for non-parametric coordinates. + +v 1.25052 0.517982 0.353553 +v 0.597239 0.247384 0.353553 +v 0.597239 0.247384 -0.353553 +v 1.25052 0.517982 -0.353553 +v 0.517982 1.25052 0.353553 +v 0.247384 0.597239 0.353553 +v 0.247384 0.597239 -0.353553 +v 0.517982 1.25052 -0.353553 +v -0.517982 1.25052 0.353553 +v -0.247384 0.597239 0.353553 +v -0.247384 0.597239 -0.353553 +v -0.517982 1.25052 -0.353553 +v -1.25052 0.517982 0.353553 +v -0.597239 0.247384 0.353553 +v -0.597239 0.247384 -0.353553 +v -1.25052 0.517982 -0.353553 +v -1.25052 -0.517982 0.353553 +v -0.597239 -0.247384 0.353553 +v -0.597239 -0.247384 -0.353553 +v -1.25052 -0.517982 -0.353553 +v -0.517982 -1.25052 0.353553 +v -0.247384 -0.597239 0.353553 +v -0.247384 -0.597239 -0.353553 +v -0.517982 -1.25052 -0.353553 +v 0.517982 -1.25052 0.353553 +v 0.247384 -0.597239 0.353553 +v 0.247384 -0.597239 -0.353553 +v 0.517982 -1.25052 -0.353553 +v 1.25052 -0.517982 0.353553 +v 0.597239 -0.247384 0.353553 +v 0.597239 -0.247384 -0.353553 +v 1.25052 -0.517982 -0.353553 +vt 0 0 +vt 1 0 +vt 1 1 +vt 0 1 +f 5/1/1 6/2/2 2/3/3 1/4/4 +f 6/1/5 7/2/6 3/3/7 2/4/8 +f 7/1/9 8/2/10 4/3/11 3/4/12 +f 8/1/13 5/2/14 1/3/15 4/4/16 +f 9/1/17 10/2/18 6/3/19 5/4/20 +f 10/1/21 11/2/22 7/3/23 6/4/24 +f 11/1/25 12/2/26 8/3/27 7/4/28 +f 12/1/29 9/2/30 5/3/31 8/4/32 +f 13/1/33 14/2/34 10/3/35 9/4/36 +f 14/1/37 15/2/38 11/3/39 10/4/40 +f 15/1/41 16/2/42 12/3/43 11/4/44 +f 16/1/45 13/2/46 9/3/47 12/4/48 +f 17/1/49 18/2/50 14/3/51 13/4/52 +f 18/1/53 19/2/54 15/3/55 14/4/56 +f 19/1/57 20/2/58 16/3/59 15/4/60 +f 20/1/61 17/2/62 13/3/63 16/4/64 +f 21/1/65 22/2/66 18/3/67 17/4/68 +f 22/1/69 23/2/70 19/3/71 18/4/72 +f 23/1/73 24/2/74 20/3/75 19/4/76 +f 24/1/77 21/2/78 17/3/79 20/4/80 +f 25/1/81 26/2/82 22/3/83 21/4/84 +f 26/1/85 27/2/86 23/3/87 22/4/88 +f 27/1/89 28/2/90 24/3/91 23/4/92 +f 28/1/93 25/2/94 21/3/95 24/4/96 +f 29/1/97 30/2/98 26/3/99 25/4/100 +f 30/1/101 31/2/102 27/3/103 26/4/104 +f 31/1/105 32/2/106 28/3/107 27/4/108 +f 32/1/109 29/2/110 25/3/111 28/4/112 +f 1/1/113 2/2/114 30/3/115 29/4/116 +f 2/1/117 3/2/118 31/3/119 30/4/120 +f 3/1/121 4/2/122 32/3/123 31/4/124 +f 4/1/125 1/2/126 29/3/127 32/4/128 +t crease 2/1/0 1 5 4.7 +t crease 2/1/0 5 9 4.7 +t crease 2/1/0 9 13 4.7 +t crease 2/1/0 13 17 4.7 +t crease 2/1/0 17 21 4.7 +t crease 2/1/0 21 25 4.7 +t crease 2/1/0 25 29 4.7 +t crease 2/1/0 29 1 4.7 diff --git a/test.cc b/test.cc index f120c69..c2047a9 100644 --- a/test.cc +++ b/test.cc @@ -8,7 +8,7 @@ #include #include -static void PrintInfo(const std::vector& shapes, const std::vector& materials) +static void PrintInfo(const std::vector& shapes, const std::vector& materials, bool triangulate = true) { std::cout << "# of shapes : " << shapes.size() << std::endl; std::cout << "# of materials : " << materials.size() << std::endl; @@ -16,10 +16,32 @@ static void PrintInfo(const std::vector& shapes, const std::ve for (size_t i = 0; i < shapes.size(); i++) { printf("shape[%ld].name = %s\n", i, shapes[i].name.c_str()); printf("Size of shape[%ld].indices: %ld\n", i, shapes[i].mesh.indices.size()); - printf("Size of shape[%ld].material_ids: %ld\n", i, shapes[i].mesh.material_ids.size()); - assert((shapes[i].mesh.indices.size() % 3) == 0); - for (size_t f = 0; f < shapes[i].mesh.indices.size() / 3; f++) { - printf(" idx[%ld] = %d, %d, %d. mat_id = %d\n", f, shapes[i].mesh.indices[3*f+0], shapes[i].mesh.indices[3*f+1], shapes[i].mesh.indices[3*f+2], shapes[i].mesh.material_ids[f]); + + if (triangulate) + { + printf("Size of shape[%ld].material_ids: %ld\n", i, shapes[i].mesh.material_ids.size()); + assert((shapes[i].mesh.indices.size() % 3) == 0); + for (size_t f = 0; f < shapes[i].mesh.indices.size() / 3; f++) { + printf(" idx[%ld] = %d, %d, %d. mat_id = %d\n", f, shapes[i].mesh.indices[3*f+0], shapes[i].mesh.indices[3*f+1], shapes[i].mesh.indices[3*f+2], shapes[i].mesh.material_ids[f]); + } + } else { + for (size_t f = 0; f < shapes[i].mesh.indices.size(); f++) { + printf(" idx[%ld] = %d\n", f, shapes[i].mesh.indices[f]); + } + + printf("Size of shape[%ld].material_ids: %ld\n", i, shapes[i].mesh.material_ids.size()); + assert(shapes[i].mesh.material_ids.size() == shapes[i].mesh.num_vertices.size()); + for (size_t m = 0; m < shapes[i].mesh.material_ids.size(); m++) { + printf(" material_id[%ld] = %d\n", m, + shapes[i].mesh.material_ids[m]); + } + + } + + printf("shape[%ld].num_faces: %ld\n", i, shapes[i].mesh.num_vertices.size()); + for (size_t v = 0; v < shapes[i].mesh.num_vertices.size(); v++) { + printf(" num_vertices[%ld] = %ld\n", v, + static_cast(shapes[i].mesh.num_vertices[v])); } printf("shape[%ld].vertices: %ld\n", i, shapes[i].mesh.positions.size()); @@ -30,6 +52,44 @@ static void PrintInfo(const std::vector& shapes, const std::ve shapes[i].mesh.positions[3*v+1], shapes[i].mesh.positions[3*v+2]); } + + printf("shape[%ld].num_tags: %ld\n", i, shapes[i].mesh.tags.size()); + for (size_t t = 0; t < shapes[i].mesh.tags.size(); t++) { + printf(" tag[%ld] = %s ", t, shapes[i].mesh.tags[t].name.c_str()); + printf(" ints: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j) + { + printf("%ld", static_cast(shapes[i].mesh.tags[t].intValues[j])); + if (j < (shapes[i].mesh.tags[t].intValues.size()-1)) + { + printf(", "); + } + } + printf("]"); + + printf(" floats: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j) + { + printf("%f", shapes[i].mesh.tags[t].floatValues[j]); + if (j < (shapes[i].mesh.tags[t].floatValues.size()-1)) + { + printf(", "); + } + } + printf("]"); + + printf(" strings: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j) + { + printf("%s", shapes[i].mesh.tags[t].stringValues[j].c_str()); + if (j < (shapes[i].mesh.tags[t].stringValues.size()-1)) + { + printf(", "); + } + } + printf("]"); + printf("\n"); + } } for (size_t i = 0; i < materials.size(); i++) { @@ -52,6 +112,7 @@ static void PrintInfo(const std::vector& shapes, const std::ve printf(" material.disp = %s\n", materials[i].displacement_texname.c_str()); std::map::const_iterator it(materials[i].unknown_parameter.begin()); std::map::const_iterator itEnd(materials[i].unknown_parameter.end()); + for (; it != itEnd; it++) { printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); } @@ -62,7 +123,8 @@ static void PrintInfo(const std::vector& shapes, const std::ve static bool TestLoadObj( const char* filename, - const char* basepath = NULL) + const char* basepath = NULL, + bool triangulate = true) { std::cout << "Loading " << filename << std::endl; @@ -70,7 +132,7 @@ TestLoadObj( std::vector materials; std::string err; - bool ret = tinyobj::LoadObj(shapes, materials, err, filename, basepath); + bool ret = tinyobj::LoadObj(shapes, materials, err, filename, basepath, triangulate); if (!err.empty()) { std::cerr << err << std::endl; @@ -80,7 +142,7 @@ TestLoadObj( return false; } - PrintInfo(shapes, materials); + PrintInfo(shapes, materials, triangulate); return true; } @@ -92,7 +154,7 @@ TestStreamLoadObj() std::cout << "Stream Loading " << std::endl; std::stringstream objStream; - objStream + objStream << "mtllib cube.mtl\n" "\n" "v 0.000000 2.000000 2.000000\n" @@ -125,7 +187,7 @@ TestStreamLoadObj() "f 2 6 7 3\n" "# 6 elements"; -std::string matStream( +std::string matStream( "newmtl white\n" "Ka 0 0 0\n" "Kd 1 1 1\n" @@ -172,7 +234,7 @@ std::string matStream( private: std::stringstream m_matSStream; - }; + }; MaterialStringStreamReader matSSReader(matStream); std::vector shapes; @@ -198,7 +260,6 @@ main( int argc, char **argv) { - if (argc > 1) { const char* basepath = NULL; if (argc > 2) { @@ -209,7 +270,8 @@ main( //assert(true == TestLoadObj("cornell_box.obj")); //assert(true == TestLoadObj("cube.obj")); assert(true == TestStreamLoadObj()); + assert(true == TestLoadObj("catmark_torus_creases0.obj", NULL, false)); } - + return 0; } diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 3f0e7b1..8e05778 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -5,6 +5,7 @@ // // +// version 0.9.17: Support n-polygon and crease tag(OpenSubdiv extension) // version 0.9.16: Make tinyobjloader header-only // version 0.9.15: Change API to handle no mtl file case correctly(#58) // version 0.9.14: Support specular highlight, bump, displacement and alpha map(#53) @@ -68,12 +69,24 @@ typedef struct { std::map unknown_parameter; } material_t; -typedef struct { - std::vector positions; - std::vector normals; - std::vector texcoords; - std::vector indices; - std::vector material_ids; // per-mesh material ID +typedef struct +{ + std::string name; + + std::vector intValues; + std::vector floatValues; + std::vector stringValues; +} tag_t; + +typedef struct +{ + std::vector positions; + std::vector normals; + std::vector texcoords; + std::vector indices; + std::vector num_vertices; // up to 255 faces + std::vector material_ids; // per-mesh material ID + std::vector tags; // SubD tag } mesh_t; typedef struct { @@ -112,10 +125,11 @@ private: /// Returns true when loading .obj become success. /// Returns warning and error message into `err` /// 'mtl_basepath' is optional, and used for base path for .mtl file. +/// 'triangulate' is optional, and used whether triangulate polygon face in .obj or not. bool LoadObj(std::vector &shapes, // [output] std::vector &materials, // [output] std::string& err, // [output] - const char *filename, const char *mtl_basepath = NULL); + const char *filename, const char *mtl_basepath = NULL, bool triangulate = true); /// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve /// std::istream for materials. @@ -124,7 +138,7 @@ bool LoadObj(std::vector &shapes, // [output] bool LoadObj(std::vector &shapes, // [output] std::vector &materials, // [output] std::string& err, // [output] - std::istream &inStream, MaterialReader &readMatFn); + std::istream &inStream, MaterialReader &readMatFn, bool triangulate = true); /// Loads materials into std::map void LoadMtl(std::map &material_map, // [output] @@ -161,6 +175,14 @@ struct vertex_index { vertex_index(int vidx, int vtidx, int vnidx) : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx){} }; + +struct tag_sizes { + tag_sizes() : num_ints(0), num_floats(0), num_strings(0) {} + int num_ints; + int num_floats; + int num_strings; +}; + // for std::map static inline bool operator<(const vertex_index &a, const vertex_index &b) { if (a.v_idx != b.v_idx) @@ -379,6 +401,30 @@ static inline void parseFloat3(float &x, float &y, float &z, z = parseFloat(token); } +static tag_sizes parseTagTriple(const char* & token) +{ + tag_sizes ts; + + ts.num_ints = atoi(token); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return ts; + } + token++; + + ts.num_floats = atoi(token); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return ts; + } + token++; + + ts.num_strings = atoi(token); + token += strcspn(token, "/ \t\r") + 1; + + return ts; +} + // Parse triples: i, i/j/k, i//k, i/j static vertex_index parseTriple(const char *&token, int vsize, int vnsize, int vtsize) { @@ -433,13 +479,13 @@ updateVertex(std::map &vertexCache, positions.push_back(in_positions[3 * static_cast(i.v_idx) + 1]); positions.push_back(in_positions[3 * static_cast(i.v_idx) + 2]); - if (i.vn_idx >= 0) { + if ((i.vn_idx >= 0) && (static_cast(i.vn_idx * 3 + 2) < in_normals.size())) { normals.push_back(in_normals[3 * static_cast(i.vn_idx) + 0]); normals.push_back(in_normals[3 * static_cast(i.vn_idx) + 1]); normals.push_back(in_normals[3 * static_cast(i.vn_idx) + 2]); } - if (i.vt_idx >= 0) { + if ((i.vt_idx >= 0) && (static_cast(i.vt_idx * 2 + 1) < in_texcoords.size())) { texcoords.push_back(in_texcoords[2 * static_cast(i.vt_idx) + 0]); texcoords.push_back(in_texcoords[2 * static_cast(i.vt_idx) + 1]); } @@ -479,7 +525,8 @@ static bool exportFaceGroupToShape( const std::vector &in_normals, const std::vector &in_texcoords, const std::vector > &faceGroup, - const int material_id, const std::string &name, bool clearCache) { + std::vector &tags, + const int material_id, const std::string &name, bool clearCache, bool triangulate) { if (faceGroup.empty()) { return false; } @@ -494,30 +541,50 @@ static bool exportFaceGroupToShape( size_t npolys = face.size(); - // Polygon -> triangle fan conversion - for (size_t k = 2; k < npolys; k++) { - i1 = i2; - i2 = face[k]; + if (triangulate) { - unsigned int v0 = updateVertex( - vertexCache, shape.mesh.positions, shape.mesh.normals, - shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i0); - unsigned int v1 = updateVertex( - vertexCache, shape.mesh.positions, shape.mesh.normals, - shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i1); - unsigned int v2 = updateVertex( - vertexCache, shape.mesh.positions, shape.mesh.normals, - shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i2); + // Polygon -> triangle fan conversion + for (size_t k = 2; k < npolys; k++) { + i1 = i2; + i2 = face[k]; - shape.mesh.indices.push_back(v0); - shape.mesh.indices.push_back(v1); - shape.mesh.indices.push_back(v2); + unsigned int v0 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i0); + unsigned int v1 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i1); + unsigned int v2 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i2); + + shape.mesh.indices.push_back(v0); + shape.mesh.indices.push_back(v1); + shape.mesh.indices.push_back(v2); + + shape.mesh.num_vertices.push_back(3); + shape.mesh.material_ids.push_back(material_id); + + } + } else { + + for (size_t k = 0; k < npolys; k++) { + unsigned int v = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, face[k]); + + shape.mesh.indices.push_back(v); + + } + + shape.mesh.num_vertices.push_back(static_cast(npolys)); + shape.mesh.material_ids.push_back(material_id); // per face - shape.mesh.material_ids.push_back(material_id); } } shape.name = name; + shape.mesh.tags.swap(tags); if (clearCache) vertexCache.clear(); @@ -779,7 +846,7 @@ bool MaterialFileReader::operator()(const std::string &matId, bool LoadObj(std::vector &shapes, // [output] std::vector &materials, // [output] std::string &err, - const char *filename, const char *mtl_basepath) { + const char *filename, const char *mtl_basepath, bool trianglulate) { shapes.clear(); @@ -798,18 +865,19 @@ bool LoadObj(std::vector &shapes, // [output] } MaterialFileReader matFileReader(basePath); - return LoadObj(shapes, materials, err, ifs, matFileReader); + return LoadObj(shapes, materials, err, ifs, matFileReader, trianglulate); } bool LoadObj(std::vector &shapes, // [output] std::vector &materials, // [output] std::string& err, - std::istream &inStream, MaterialReader &readMatFn) { + std::istream &inStream, MaterialReader &readMatFn, bool triangulate) { std::stringstream errss; std::vector v; std::vector vn; std::vector vt; + std::vector tags; std::vector > faceGroup; std::string name; @@ -917,7 +985,7 @@ bool LoadObj(std::vector &shapes, // [output] // Create face group per material. bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, - faceGroup, material, name, true); + faceGroup, tags, material, name, true, triangulate); if (ret) { shapes.push_back(shape); } @@ -961,7 +1029,7 @@ bool LoadObj(std::vector &shapes, // [output] // flush previous face group. bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, - faceGroup, material, name, true); + faceGroup, tags, material, name, true, triangulate); if (ret) { shapes.push_back(shape); } @@ -995,7 +1063,7 @@ bool LoadObj(std::vector &shapes, // [output] // flush previous face group. bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, - faceGroup, material, name, true); + faceGroup, tags, material, name, true, triangulate); if (ret) { shapes.push_back(shape); } @@ -1017,11 +1085,51 @@ bool LoadObj(std::vector &shapes, // [output] continue; } + if (token[0] == 't' && isSpace(token[1])) { + tag_t tag; + + char namebuf[4096]; + token += 2; + sscanf(token, "%s", namebuf); + tag.name = std::string(namebuf); + + token += tag.name.size() + 1; + + tag_sizes ts = parseTagTriple(token); + + tag.intValues.resize(static_cast(ts.num_ints)); + + for(size_t i = 0; i < static_cast(ts.num_ints); ++i) + { + tag.intValues[i] = atoi(token); + token += strcspn(token, "/ \t\r") + 1; + } + + tag.floatValues.resize(static_cast(ts.num_floats)); + for(size_t i = 0; i < static_cast(ts.num_floats); ++i) + { + tag.floatValues[i] = parseFloat(token); + token += strcspn(token, "/ \t\r") + 1; + } + + tag.stringValues.resize(static_cast(ts.num_strings)); + for(size_t i = 0; i < static_cast(ts.num_strings); ++i) + { + char stringValueBuffer[4096]; + + sscanf(token, "%s", stringValueBuffer); + tag.stringValues[i] = stringValueBuffer; + token += tag.stringValues[i].size() + 1; + } + + tags.push_back(tag); + } + // Ignore unknown command. } - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, - material, name, true); + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, + material, name, true, triangulate); if (ret) { shapes.push_back(shape); }