00001
00002 #include "imesh_fileio.h"
00003
00004
00005
00006 #include <vcl_fstream.h>
00007 #include <vcl_sstream.h>
00008 #include <vcl_limits.h>
00009 #include <vul/vul_file.h>
00010 #include <vgl/vgl_point_2d.h>
00011
00012
00013
00014 bool imesh_read(const vcl_string& filename, imesh_mesh& mesh)
00015 {
00016 vcl_string ext = vul_file::extension(filename);
00017 if (ext == ".ply2")
00018 return imesh_read_ply2(filename,mesh);
00019 if (ext == ".ply")
00020 return imesh_read_ply(filename,mesh);
00021 else if (ext == ".obj")
00022 return imesh_read_obj(filename,mesh);
00023
00024 return false;
00025 }
00026
00027
00028
00029 bool imesh_read_ply2(const vcl_string& filename, imesh_mesh& mesh)
00030 {
00031 vcl_ifstream fh(filename.c_str());
00032 bool retval = imesh_read_ply2(fh,mesh);
00033 fh.close();
00034 return retval;
00035 }
00036
00037
00038
00039 bool imesh_read_ply2(vcl_istream& is, imesh_mesh& mesh)
00040 {
00041 unsigned int num_verts, num_faces;
00042 is >> num_verts >> num_faces;
00043 vcl_auto_ptr<imesh_vertex_array<3> > verts(new imesh_vertex_array<3>(num_verts));
00044 vcl_auto_ptr<imesh_face_array > faces(new imesh_face_array(num_faces));
00045 for (unsigned int v=0; v<num_verts; ++v) {
00046 imesh_vertex<3>& vert = (*verts)[v];
00047 is >> vert[0] >> vert[1] >> vert[2];
00048 }
00049 for (unsigned int f=0; f<num_faces; ++f) {
00050 vcl_vector<unsigned int>& face = (*faces)[f];
00051 unsigned int cnt;
00052 is >> cnt;
00053 face.resize(cnt,0);
00054 for (unsigned int v=0; v<cnt; ++v)
00055 is >> face[v];
00056 }
00057
00058 mesh.set_vertices(vcl_auto_ptr<imesh_vertex_array_base>(verts));
00059 mesh.set_faces(vcl_auto_ptr<imesh_face_array_base>(faces));
00060 return true;
00061 }
00062
00063
00064 bool imesh_read_ply(const vcl_string& filename, imesh_mesh& mesh)
00065 {
00066 vcl_ifstream fh(filename.c_str());
00067 bool retval = imesh_read_ply(fh,mesh);
00068 fh.close();
00069 return retval;
00070 }
00071
00072
00073
00074 bool imesh_read_ply(vcl_istream& is, imesh_mesh& mesh)
00075 {
00076 unsigned int num_verts, num_faces;
00077 vcl_string str;
00078 is >> str;
00079 bool done=false;
00080 while (!done) {
00081 is >> str;
00082 if (str.compare("element")==0) {
00083 is >> str;
00084 if (str.compare("vertex")==0) {
00085 is >> num_verts;
00086 }
00087 else if (str.compare("face")==0) {
00088 is >> num_faces;
00089 }
00090 }
00091 else if (str.compare("end_header")==0) {
00092 done = true;
00093 }
00094 }
00095 vcl_auto_ptr<imesh_vertex_array<3> > verts(new imesh_vertex_array<3>(num_verts));
00096 vcl_auto_ptr<imesh_face_array > faces(new imesh_face_array(num_faces));
00097 for (unsigned int v=0; v<num_verts; ++v) {
00098 imesh_vertex<3>& vert = (*verts)[v];
00099 is >> vert[0] >> vert[1] >> vert[2];
00100 }
00101 for (unsigned int f=0; f<num_faces; ++f) {
00102 vcl_vector<unsigned int>& face = (*faces)[f];
00103 unsigned int cnt;
00104 is >> cnt;
00105 face.resize(cnt,0);
00106 for (unsigned int v=0; v<cnt; ++v)
00107 is >> face[v];
00108 }
00109
00110 mesh.set_vertices(vcl_auto_ptr<imesh_vertex_array_base>(verts));
00111 mesh.set_faces(vcl_auto_ptr<imesh_face_array_base>(faces));
00112 return true;
00113 }
00114
00115
00116 void imesh_write_ply2(const vcl_string& filename, const imesh_mesh& mesh)
00117 {
00118 vcl_ofstream fh(filename.c_str());
00119 imesh_write_ply2(fh,mesh);
00120 fh.close();
00121 }
00122
00123
00124
00125 void imesh_write_ply2(vcl_ostream& os, const imesh_mesh& mesh)
00126 {
00127 os << mesh.num_verts() <<'\n'<< mesh.num_faces() <<'\n';
00128 const imesh_vertex_array_base& verts = mesh.vertices();
00129 for (unsigned int v=0; v<verts.size(); ++v) {
00130 os << verts(v,0) << ' '
00131 << verts(v,1) << ' '
00132 << verts(v,2) << '\n';
00133 }
00134
00135 const imesh_face_array_base& faces = mesh.faces();
00136 for (unsigned int f=0; f<faces.size(); ++f) {
00137 os << faces.num_verts(f);
00138 for (unsigned int v=0; v<faces.num_verts(f); ++v)
00139 os << ' ' << faces(f,v);
00140 os << '\n';
00141 }
00142 }
00143
00144
00145
00146 bool imesh_read_uv2(const vcl_string& filename, imesh_mesh& mesh)
00147 {
00148 vcl_ifstream fh(filename.c_str());
00149 bool retval = imesh_read_uv2(fh,mesh);
00150 fh.close();
00151 return retval;
00152 }
00153
00154
00155
00156 bool imesh_read_uv2(vcl_istream& is, imesh_mesh& mesh)
00157 {
00158 vcl_vector<vgl_point_2d<double> > uv;
00159 unsigned int num_verts, num_faces;
00160 is >> num_verts >> num_faces;
00161 if (num_verts != mesh.num_verts() && num_verts != mesh.num_edges()*2)
00162 return false;
00163
00164 for (unsigned int i=0; i<num_verts; ++i) {
00165 double u,v;
00166 is >> u >> v;
00167 uv.push_back(vgl_point_2d<double>(u,v));
00168 }
00169 mesh.set_tex_coords(uv);
00170 return true;
00171 }
00172
00173
00174
00175 bool imesh_read_obj(const vcl_string& filename, imesh_mesh& mesh)
00176 {
00177 vcl_ifstream fh(filename.c_str());
00178 bool retval = imesh_read_obj(fh,mesh);
00179 fh.close();
00180 return retval;
00181 }
00182
00183
00184
00185 bool imesh_read_obj(vcl_istream& is, imesh_mesh& mesh)
00186 {
00187 vcl_auto_ptr<imesh_vertex_array<3> > verts(new imesh_vertex_array<3>);
00188 vcl_auto_ptr<imesh_face_array> faces(new imesh_face_array);
00189 vcl_vector<vgl_vector_3d<double> > normals;
00190 vcl_vector<vgl_point_2d<double> > tex;
00191 vcl_string last_group = "ungrouped";
00192 char c;
00193 while (is >> c)
00194 {
00195 switch (c)
00196 {
00197 case 'v':
00198 {
00199 char c2 = (char)is.peek();
00200 switch (c2)
00201 {
00202 case 'n':
00203 {
00204 is.ignore();
00205 double x,y,z;
00206 is >> x >> y >> z;
00207 normals.push_back(vgl_vector_3d<double>(x,y,z));
00208 break;
00209 }
00210 case 't':
00211 {
00212 is.ignore();
00213 double x,y;
00214 is >> x >> y;
00215 is.ignore(256,'\n');
00216 tex.push_back(vgl_point_2d<double>(x,y));
00217 break;
00218 }
00219 default:
00220 {
00221 double x,y,z;
00222 is >> x >> y >> z;
00223 verts->push_back(imesh_vertex<3>(x,y,z));
00224 break;
00225 }
00226 }
00227 break;
00228 }
00229 case 'f':
00230 {
00231 vcl_string line;
00232 vcl_getline(is,line);
00233 vcl_vector<unsigned int> vi, ti, ni;
00234 unsigned int v;
00235 vcl_stringstream ss(line);
00236 while (ss >> v) {
00237 vi.push_back(v-1);
00238 if (ss.peek() == '/') {
00239 ss.ignore();
00240 if (ss.peek() != '/') {
00241 ss >> v;
00242 ti.push_back(v-1);
00243 if (ss.peek() == '/') {
00244 ss.ignore();
00245 if (ss.peek() >= '0' && ss.peek() <= '9') {
00246 ss >> v;
00247 ni.push_back(v-1);
00248 }
00249 else {
00250 vcl_cerr << "improperly formed face line in OBJ: "<<line<<'\n';
00251 return false;
00252 }
00253 }
00254 }
00255 else {
00256 ss.ignore();
00257 if (ss.peek() >= '0' && ss.peek() <= '9') {
00258 ss >> v;
00259 ni.push_back(v-1);
00260 }
00261 else {
00262 vcl_cerr << "improperly formed face line in OBJ: "<<line<<'\n';
00263 return false;
00264 }
00265 }
00266 }
00267 }
00268 faces->push_back(vi);
00269 break;
00270 }
00271 case 'g':
00272 {
00273 faces->make_group(last_group);
00274 is.ignore();
00275 vcl_getline(is,last_group);
00276 break;
00277 }
00278 default:
00279 is.ignore(vcl_numeric_limits<vcl_streamsize>::max(),'\n');
00280 break;
00281 }
00282 }
00283
00284
00285 if (faces->has_groups())
00286 faces->make_group(last_group);
00287
00288 if (normals.size() == verts->size())
00289 verts->set_normals(normals);
00290
00291 mesh.set_vertices(vcl_auto_ptr<imesh_vertex_array_base>(verts));
00292 mesh.set_faces(vcl_auto_ptr<imesh_face_array_base>(faces));
00293 mesh.set_tex_coords(tex);
00294
00295 return true;
00296 }
00297
00298
00299
00300 void imesh_write_obj(const vcl_string& filename, const imesh_mesh& mesh)
00301 {
00302 vcl_ofstream fh(filename.c_str());
00303 imesh_write_obj(fh,mesh);
00304 fh.close();
00305 }
00306
00307
00308
00309
00310 void imesh_write_obj(vcl_ostream& os, const imesh_mesh& mesh)
00311 {
00312 const imesh_vertex_array_base& verts = mesh.vertices();
00313 for (unsigned int v=0; v<verts.size(); ++v) {
00314 os << "v "
00315 << verts(v,0) << ' '
00316 << verts(v,1) << ' '
00317 << verts(v,2) << '\n';
00318 }
00319
00320 if (verts.has_normals()) {
00321 for (unsigned int n=0; n<verts.size(); ++n) {
00322 const vgl_vector_3d<double>& v = verts.normal(n);
00323 os << "vn "
00324 << v.x() << ' '
00325 << v.y() << ' '
00326 << v.z() << '\n';
00327 }
00328 }
00329
00330 if (mesh.has_tex_coords()) {
00331 const vcl_vector<vgl_point_2d<double> >& tex = mesh.tex_coords();
00332 for (unsigned int t=0; t<tex.size(); ++t) {
00333 os << "vt " << tex[t].x() << ' ' << tex[t].y() << '\n';
00334 }
00335 }
00336
00337 const imesh_face_array_base& faces = mesh.faces();
00338 const vcl_vector<vcl_pair<vcl_string,unsigned int> >& groups = faces.groups();
00339
00340 bool write_extra = mesh.has_tex_coords() || verts.has_normals();
00341
00342 if (!groups.empty())
00343 os << "g " << groups[0].first << '\n';
00344 unsigned int g=0;
00345 unsigned int e=0;
00346 for (unsigned int f=0; f<faces.size(); ++f)
00347 {
00348 while (g < groups.size() && groups[g].second <= f) {
00349 ++g;
00350 if (g < groups.size())
00351 os << "g " << groups[g].first << '\n';
00352 else {
00353 os << "g ungrouped\n";
00354 }
00355 }
00356 os << 'f';
00357 for (unsigned int v=0; v<faces.num_verts(f); ++v) {
00358 os << ' ' << faces(f,v)+1;
00359 if (write_extra) {
00360 os << '/';
00361 if (mesh.has_tex_coords() == imesh_mesh::TEX_COORD_ON_CORNER) {
00362 os << ++e;
00363 }
00364 if (mesh.has_tex_coords() == imesh_mesh::TEX_COORD_ON_VERT)
00365 os << faces(f,v)+1;
00366 if (verts.has_normals())
00367 os << '/' << faces(f,v)+1;
00368 }
00369 }
00370 os << '\n';
00371 }
00372 }
00373
00374 void imesh_write_kml(vcl_ostream& os, const imesh_mesh& mesh)
00375 {
00376 const imesh_face_array_base& faces = mesh.faces();
00377 const imesh_vertex_array_base& verts = mesh.vertices();
00378
00379 if (faces.size() <= 1) {
00380
00381 return;
00382 }
00383
00384 os.precision(12);
00385 for (unsigned int f=0; f<faces.size(); ++f)
00386 {
00387
00388 os << " <Polygon id=\"building_face" << f << "\">\n"
00389 << " <extrude>0</extrude>\n"
00390 << " <tessellate>0</tessellate>\n"
00391 << " <altitudeMode>absolute</altitudeMode>\n"
00392 << " <outerBoundaryIs>\n"
00393 << " <LinearRing>\n"
00394 << " <coordinates>" << vcl_endl;
00395
00396 for (unsigned int v=0; v<faces.num_verts(f); ++v) {
00397 unsigned int idx = faces(f,v);
00398 double x = verts(idx, 0);
00399 double y = verts(idx, 1);
00400 double z = verts(idx, 2);
00401 os << " " << x << ", " << y << ", " << z << vcl_endl;
00402 }
00403
00404
00405 unsigned int idx = faces(f,0);
00406 double x = verts(idx, 0);
00407 double y = verts(idx, 1);
00408 double z = verts(idx, 2);
00409 os << " " << x << ", " << y << ", " << z << vcl_endl
00410 << " </coordinates>\n"
00411 << " </LinearRing>\n"
00412 << " </outerBoundaryIs>\n"
00413 << " </Polygon>" << vcl_endl;
00414 }
00415 }
00416
00417 void imesh_write_kml_collada(vcl_ostream& os, const imesh_mesh& mesh)
00418 {
00419
00420 if (mesh.faces().regularity() != 3)
00421 {
00422 vcl_cerr << "ERROR! only triangle meshes are supported.\n";
00423 return;
00424 }
00425 const imesh_regular_face_array<3>& tris =
00426 static_cast<const imesh_regular_face_array<3>&>(mesh.faces());
00427 const imesh_vertex_array<3>& verts = mesh.vertices<3>();
00428 const unsigned int nverts = verts.size();
00429 const unsigned int nfaces = tris.size();
00430
00431 vcl_string geometry_id = "geometry";
00432 vcl_string geometry_position_id = "geometry_position";
00433 vcl_string geometry_position_array_id = "geometry_position_array";
00434 vcl_string geometry_uv_id = "geometry_uv";
00435 vcl_string geometry_uv_array_id = "geometry_uv_array";
00436 vcl_string geometry_vertex_id = "geometry_vertex";
00437 vcl_string geometry_normal_id = "geometry_normal";
00438
00439
00440 os <<"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
00441 << "<COLLADA xmlns=\"http://www.collada.org/2005/11/COLLADASchema\" version=\"1.4.1\">\n"
00442
00443 << " <asset>\n"
00444 << " <contributor>\n"
00445 << " <authoring_tool>VXL imesh library</authoring_tool>\n"
00446 << " </contributor>\n"
00447 #if 0 // When we figure out how to get a date string we can write this
00448 << " <created>2008-04-08T13:07:52-08:00</created>\n"
00449 << " <modified>2008-04-08T13:07:52-08:00</modified>\n"
00450 #endif
00451 << " <unit name=\"meters\" meter=\"1\"/>\n"
00452 << " <up_axis>Z_UP</up_axis>\n"
00453 << " </asset>\n";
00454
00455
00456 os << " <library_materials>" << vcl_endl
00457 << " <material id=\"GreyID\" name=\"Grey\">"<< vcl_endl
00458 << " <instance_effect url=\"#Grey-effect\"/>"<< vcl_endl
00459 << " </material>"<< vcl_endl
00460 << " </library_materials>"<< vcl_endl
00461
00462 << " <library_effects>"<< vcl_endl
00463 << " <effect id=\"Grey-effect\" name=\"Grey-effect\">\n"
00464 << " <profile_COMMON>\n"
00465 << " <technique sid=\"COMMON\">\n"
00466 << " <phong>\n"
00467 << " <diffuse>\n"
00468 << " <color>1.000000 1.000000 1.000000 1</color>\n"
00469 << " </diffuse>\n"
00470 << " </phong>\n"
00471 << " </technique>\n"
00472 << " <extra>\n"
00473 << " <technique profile=\"GOOGLEEARTH\">\n"
00474 << " <double_sided>1</double_sided>\n"
00475 << " </technique>\n"
00476 << " </extra>\n"
00477 << " </profile_COMMON>\n"
00478 << " </effect>\n"
00479 << " </library_effects>\n";
00480
00481
00482 os << " <library_geometries>\n"
00483 <<" <geometry id=\"" << geometry_id << "\" name=\"" << geometry_id << "\">\n"
00484 <<" <mesh>\n"
00485 <<" <source id=\"" << geometry_position_id << "\">\n"
00486 <<" <float_array id=\"" << geometry_position_array_id << "\" count=\"" << nverts*3 << "\">\n";
00487
00488 for (unsigned int v=0; v<nverts; ++v)
00489 os << " "<< verts[v][0] << ' ' << verts[v][1] << ' ' << verts[v][2] << '\n';
00490
00491 os <<"\n </float_array>\n"
00492 <<" <technique_common>\n"
00493 <<" <accessor source=\"#" << geometry_position_array_id << "\" count=\"" << nverts << "\" stride=\"3\">\n"
00494 <<" <param name=\"X\" type=\"float\"/>\n"
00495 <<" <param name=\"Y\" type=\"float\"/>\n"
00496 <<" <param name=\"Z\" type=\"float\"/>\n"
00497 <<" </accessor>\n"
00498 <<" </technique_common>\n"
00499 <<" </source>\n"
00500 <<" <source id=\"" << "geometry_normal" << "\">\n"
00501
00502 <<" <float_array id=\"" << "geometry_normal_array" << "\" count=\"" << nfaces*3 << "\">\n";
00503 for (unsigned int f=0; f<nfaces; ++f) {
00504 const vgl_vector_3d<double>& n = tris.normal(f);
00505 os << " " << n.x() << ' ' << n.y() << ' ' << n.z() << '\n';
00506 }
00507
00508 os <<"\n </float_array>\n"
00509 <<" <technique_common>\n"
00510 <<" <accessor source=\"#" << "geometry_normal_array" << "\" count=\"" << nfaces << "\" stride=\"3\">\n"
00511 <<" <param name=\"X\" type=\"float\"/>\n"
00512 <<" <param name=\"Y\" type=\"float\"/>\n"
00513 <<" <param name=\"Z\" type=\"float\"/>\n"
00514 <<" </accessor>\n"
00515 <<" </technique_common>\n"
00516 <<" </source>\n"
00517
00518 <<" <vertices id=\"" <<geometry_vertex_id << "\">\n"
00519 <<" <input semantic=\"POSITION\" source=\"#" << geometry_position_id << "\"/>\n"
00520 <<" </vertices>\n"
00521 <<" <triangles material=\"Grey\" count=\"" << nfaces << "\">\n"
00522 <<" <input semantic=\"VERTEX\" source=\"#" << geometry_vertex_id << "\" offset=\"0\"/>\n"
00523 <<" <input semantic=\"NORMAL\" source=\"#" << "geometry_normal" << "\" offset=\"1\"/>\n"
00524
00525 <<" <p>\n";
00526
00527 for (unsigned int f=0; f<nfaces; ++f) {
00528 os << " "
00529 << tris[f][0] << ' ' << f <<" "
00530 << tris[f][1] << ' ' << f <<" "
00531 << tris[f][2] << ' ' << f <<'\n';
00532 }
00533 os << " </p>\n"
00534 << " </triangles>\n"
00535 << " </mesh>\n"
00536 << " </geometry>\n"
00537 << " </library_geometries>\n";
00538
00539
00540 os << " <library_visual_scenes>\n"
00541 << " <visual_scene id=\"vis_scene\">\n"
00542 << " <node id=\"Model\" name=\"Model\">\n"
00543 << " <node id=\"mesh\" name=\"mesh\">\n"
00544 << " <instance_geometry url=\"#geometry\">\n"
00545 << " <bind_material>\n"
00546 << " <technique_common>\n"
00547 << " <instance_material symbol=\"Grey\" target=\"#GreyID\"/>\n"
00548 << " </technique_common>\n"
00549 << " </bind_material>\n"
00550 << " </instance_geometry>\n"
00551 << " </node>"
00552 << " </node>"
00553 << " </visual_scene>\n"
00554 << " </library_visual_scenes>\n"
00555 << " <scene>\n"
00556 << " <instance_visual_scene url=\"#vis_scene\"/>\n"
00557 << " </scene>\n"
00558 << "</COLLADA>\n";
00559 }
00560
00561 void imesh_write_vrml(vcl_ostream& os, const imesh_mesh& mesh)
00562 {
00563
00564 if (mesh.faces().regularity() != 3)
00565 {
00566 vcl_cerr << "ERROR! only triangle meshes are supported.\n";
00567 return;
00568 }
00569 const imesh_regular_face_array<3>& tris =
00570 static_cast<const imesh_regular_face_array<3>&>(mesh.faces());
00571 const imesh_vertex_array_base& vertsb = mesh.vertices();
00572 unsigned d = vertsb.dim();
00573 if (d!=2&&d!=3)
00574 {
00575 vcl_cerr << "vertex dimension must be 2 or 3.\n";
00576 return;
00577 }
00578 const unsigned int nfaces = tris.size();
00579 os << "#VRML V2.0 utf8\n\n"
00580 << "Transform {\n"
00581 << " translation 0 0 0\n"
00582 << " children\n"
00583 << " Shape {\n";
00584
00585
00586 os << " appearance Appearance {\n"
00587 << " material Material{}\n"
00588 << " texture ImageTexture {\n"
00589 << " url \""<< mesh.tex_source() <<"\"\n"
00590 << " }\n"
00591 << " }\n";
00592
00593
00594 os << " geometry IndexedFaceSet\n"
00595 << " {\n"
00596 << " coord Coordinate{\n"
00597 << " point [\n";
00598 if (d == 2) {
00599 const imesh_vertex_array<2>& verts2= mesh.vertices<2>();
00600 for (unsigned i=0;i<verts2.size();++i)
00601 os << " " << verts2[i][0] << ' ' << verts2[i][1] << ' ' << 0.0 << '\n';
00602 }
00603 else {
00604 const imesh_vertex_array<3>& verts3= mesh.vertices<3>();
00605 for (unsigned i=0;i<verts3.size();++i)
00606 os << " " << verts3[i][0] << ' ' << verts3[i][1] << ' ' << verts3[i][2] << '\n';
00607 }
00608 os << " ]}\n";
00609
00610
00611 os << " coordIndex [\n";
00612 for (unsigned i=0;i<nfaces;++i) {
00613 os << " "
00614 << tris[i][0] << ' '
00615 << tris[i][1] << ' '
00616 << tris[i][2] << ' '
00617 << -1 << '\n';
00618 }
00619 os << " ]\n";
00620
00621
00622 if (mesh.has_tex_coords() == imesh_mesh::TEX_COORD_ON_VERT) {
00623 os << " texCoord TextureCoordinate {\n"
00624 << " point [\n";
00625
00626
00627 const vcl_vector<vgl_point_2d<double> >& tc = mesh.tex_coords();
00628 for (unsigned int i=0; i<tc.size(); ++i)
00629 os << " " << tc[i].x() << ' ' << tc[i].y() << ",\n";
00630
00631
00632 os << " ]}\n";
00633
00634
00635 os << " texCoordIndex [\n";
00636 for (unsigned i=0;i<nfaces;++i) {
00637 os << " "
00638 << tris[i][0] << ' '
00639 << tris[i][1] << ' '
00640 << tris[i][2] << ' '
00641 << -1 << '\n';
00642 }
00643 os << " ]\n";
00644 }
00645
00646
00647 os << "solid TRUE\n"
00648 << "convex FALSE\n"
00649 << "creaseAngle 0\n";
00650
00651 #if 0
00652 os << " colorPerVertex FALSE\n"
00653 << " color Color {\n"
00654 << " color [ ]\n"
00655 << " }\n"
00656 << " colorIndex [ ]\n";
00657 #endif
00658
00659
00660 os << " }\n";
00661
00662
00663 os << "}\n";
00664
00665
00666 os << "}\n";
00667 }