[774] | 1 | /************************************************************************
|
---|
| 2 |
|
---|
| 3 | Adjacency model representation.
|
---|
| 4 | $Id: AdjModel.cxx,v 1.1 1997/06/25 15:24:52 garland Exp $
|
---|
| 5 |
|
---|
| 6 | Adapted from:
|
---|
| 7 | mlab: (Id: polymodel.cc,v 1.21 1997/02/06 16:34:14 garland Exp)
|
---|
| 8 | via
|
---|
[1025] | 9 | simplif: (Id: polymodel.cc,v 1.5 1997/02/25 20:36:36 garland Exp)
|
---|
[774] | 10 |
|
---|
| 11 | ************************************************************************/
|
---|
| 12 |
|
---|
| 13 | #include "AdjModel.h"
|
---|
| 14 |
|
---|
[1025] | 15 | using namespace simplif;
|
---|
[774] | 16 |
|
---|
| 17 | int Model::in_Vertex(const Vec3& p)
|
---|
| 18 | {
|
---|
| 19 | Vertex *v = newVertex(p[X], p[Y], p[Z]);
|
---|
| 20 | bounds.addPoint(p);
|
---|
| 21 | return vertCount() - 1;
|
---|
| 22 | }
|
---|
| 23 |
|
---|
| 24 | int Model::in_Normal(const Vec3& n)
|
---|
| 25 | {
|
---|
| 26 | normals.add(n);
|
---|
| 27 | return normCount()-1;
|
---|
| 28 | }
|
---|
| 29 |
|
---|
| 30 | int Model::in_TexCoord(const Vec2& t)
|
---|
| 31 | {
|
---|
| 32 | texcoords.add(t);
|
---|
| 33 | return texcoordCount()-1;
|
---|
| 34 | }
|
---|
| 35 |
|
---|
| 36 | int Model::in_Face(int a, int b, int c, int n1, int n2, int n3, int t1, int t2, int t3)
|
---|
| 37 | {
|
---|
| 38 | Vertex *v1 = vertices(a-1);
|
---|
| 39 | Vertex *v2 = vertices(b-1);
|
---|
| 40 | Vertex *v3 = vertices(c-1);
|
---|
| 41 |
|
---|
| 42 | Face *t = newFace(v1, v2, v3);
|
---|
| 43 |
|
---|
| 44 | t->normals[0] = n1;
|
---|
| 45 | t->normals[1] = n2;
|
---|
| 46 | t->normals[2] = n3;
|
---|
| 47 |
|
---|
| 48 | t->texcoords[0] = t1;
|
---|
| 49 | t->texcoords[1] = t2;
|
---|
| 50 | t->texcoords[2] = t3;
|
---|
| 51 |
|
---|
| 52 | return faceCount() - 1;
|
---|
| 53 | }
|
---|
| 54 |
|
---|
| 55 | int Model::miin_Face(int a, int b, int c)
|
---|
| 56 | {
|
---|
| 57 | Vertex *v1 = vertices(a-1);
|
---|
| 58 | Vertex *v2 = vertices(b-1);
|
---|
| 59 | Vertex *v3 = vertices(c-1);
|
---|
| 60 |
|
---|
| 61 | Face *t = newFace(v1, v2, v3);
|
---|
| 62 |
|
---|
| 63 | t->normals[0] = a;
|
---|
| 64 | t->normals[1] = b;
|
---|
| 65 | t->normals[2] = c;
|
---|
| 66 |
|
---|
| 67 | t->texcoords[0] = a;
|
---|
| 68 | t->texcoords[1] = b;
|
---|
| 69 | t->texcoords[2] = c;
|
---|
| 70 |
|
---|
| 71 | return faceCount() - 1;
|
---|
| 72 | }
|
---|
| 73 |
|
---|
| 74 | #ifdef SUPPORT_FCOLOR
|
---|
| 75 | int Model::in_FColor(const Vec3& c)
|
---|
| 76 | {
|
---|
| 77 | Face *f = faces(faces.length()-1);
|
---|
| 78 |
|
---|
| 79 | if( !f->props )
|
---|
| 80 | f->props = new FProp;
|
---|
| 81 |
|
---|
| 82 | f->props->color = c;
|
---|
| 83 | return 0;
|
---|
| 84 | }
|
---|
| 85 | #endif
|
---|
| 86 |
|
---|
| 87 | #ifdef SUPPORT_VCOLOR
|
---|
| 88 | int Model::in_VColor(const Vec3& c)
|
---|
| 89 | {
|
---|
| 90 | Vertex *v = vertices(vertices.length()-1);
|
---|
| 91 |
|
---|
| 92 | if( !v->props )
|
---|
| 93 | v->props = new VProp;
|
---|
| 94 |
|
---|
| 95 | v->props->color = c;
|
---|
| 96 | return 0;
|
---|
| 97 | }
|
---|
| 98 | #endif
|
---|
| 99 |
|
---|
| 100 | Vec3 Model::synthesizeNormal(Vertex *v)
|
---|
| 101 | {
|
---|
| 102 | Vec3 n(0,0,0);
|
---|
| 103 | int n_count = 0;
|
---|
| 104 |
|
---|
| 105 | edge_buffer& edges = v->edgeUses();
|
---|
| 106 | for(int i=0; i<edges.length(); i++)
|
---|
| 107 | {
|
---|
| 108 | face_buffer& faces = edges(i)->faceUses();
|
---|
| 109 |
|
---|
| 110 | for(int j=0; j<faces.length(); j++)
|
---|
| 111 | {
|
---|
| 112 | n += faces(j)->plane().normal();
|
---|
| 113 | n_count++;
|
---|
| 114 | }
|
---|
| 115 | }
|
---|
| 116 |
|
---|
| 117 | if( n_count )
|
---|
| 118 | n /= (real)n_count;
|
---|
| 119 | else
|
---|
| 120 | {
|
---|
| 121 | std::cerr << "Vertex with no normals!!: " << v->uniqID;
|
---|
| 122 | std::cerr << " / " << v->tempID << std::endl;
|
---|
| 123 | }
|
---|
| 124 | return n;
|
---|
| 125 | }
|
---|
| 126 |
|
---|
| 127 |
|
---|
| 128 | ////////////////////////////////////////////////////////////////////////
|
---|
| 129 | //
|
---|
| 130 | // Model Transmogrification:
|
---|
| 131 | //
|
---|
| 132 | // These routines are the basic model munging procedures.
|
---|
| 133 | // All model simplification is implemented in terms of these
|
---|
| 134 | // transmogrification primitives.
|
---|
| 135 | ////////////////////////////////////////////////////////////////////////
|
---|
| 136 |
|
---|
| 137 |
|
---|
| 138 | Vertex *Model::newVertex(real x, real y, real z)
|
---|
| 139 | {
|
---|
| 140 | Vertex *v = new Vertex(x, y, z);
|
---|
| 141 |
|
---|
| 142 | v->vID = v->uniqID = vertices.add(v);
|
---|
| 143 |
|
---|
| 144 | /* if( logfile && selected_output&OUTPUT_MODEL_DEFN )
|
---|
| 145 | *logfile << "v " << x << " " << y << " " << z << std::endl;
|
---|
| 146 | */
|
---|
| 147 | validVertCount++;
|
---|
| 148 |
|
---|
| 149 | return v;
|
---|
| 150 | }
|
---|
| 151 |
|
---|
| 152 | Edge *Model::newEdge(Vertex *a, Vertex *b)
|
---|
| 153 | {
|
---|
| 154 | Edge *e = new Edge(a, b);
|
---|
| 155 |
|
---|
| 156 | e->uniqID = edges.add(e);
|
---|
| 157 | e->sym()->uniqID = e->uniqID;
|
---|
| 158 |
|
---|
| 159 | validEdgeCount++;
|
---|
| 160 |
|
---|
| 161 | return e;
|
---|
| 162 | }
|
---|
| 163 |
|
---|
| 164 | static Edge *get_edge(Model *m, Vertex *org, Vertex *v)
|
---|
| 165 | {
|
---|
| 166 | edge_buffer& edge_uses = org->edgeUses();
|
---|
| 167 |
|
---|
| 168 | for(int i=0; i<edge_uses.length(); i++)
|
---|
| 169 | if( edge_uses(i)->dest() == v )
|
---|
| 170 | return edge_uses(i);
|
---|
| 171 |
|
---|
| 172 | Edge *e = m->newEdge(org, v);
|
---|
| 173 |
|
---|
| 174 | return e;
|
---|
| 175 | }
|
---|
| 176 |
|
---|
| 177 |
|
---|
| 178 | Face *Model::newFace(Vertex *v1, Vertex *v2, Vertex *v3)
|
---|
| 179 | {
|
---|
| 180 | Edge *e0 = get_edge(this, v1, v2); // v1->edgeTo(m, v2);
|
---|
| 181 | Edge *e1 = get_edge(this, v2, v3); // v2->edgeTo(m, v3);
|
---|
| 182 | Edge *e2 = get_edge(this, v3, v1); // v3->edgeTo(m, v1);
|
---|
| 183 |
|
---|
| 184 | Face *t = new Face(e0, e1, e2);
|
---|
| 185 |
|
---|
| 186 | t->uniqID = faces.add(t);
|
---|
| 187 |
|
---|
| 188 | /* if( logfile && selected_output&OUTPUT_MODEL_DEFN )
|
---|
| 189 | *logfile << "f " << v1->uniqID+1 << " "
|
---|
| 190 | << v2->uniqID+1 << " " << v3->uniqID+1 << std::endl;
|
---|
| 191 | */
|
---|
| 192 | validFaceCount++;
|
---|
| 193 |
|
---|
| 194 | return t;
|
---|
| 195 | }
|
---|
| 196 |
|
---|
| 197 |
|
---|
| 198 | void Model::killVertex(Vertex *v)
|
---|
| 199 | {
|
---|
| 200 | if( v->isValid() )
|
---|
| 201 | {
|
---|
| 202 | /* if( logfile && selected_output&OUTPUT_CONTRACTIONS )
|
---|
| 203 | *logfile << "v- " << v->uniqID+1 << std::endl;
|
---|
| 204 | */
|
---|
| 205 | v->kill();
|
---|
| 206 | validVertCount--;
|
---|
| 207 | }
|
---|
| 208 | }
|
---|
| 209 |
|
---|
| 210 | void Model::killEdge(Edge *e)
|
---|
| 211 | {
|
---|
| 212 | if( e->isValid() )
|
---|
| 213 | {
|
---|
| 214 | e->kill();
|
---|
| 215 | validEdgeCount--;
|
---|
| 216 | }
|
---|
| 217 | }
|
---|
| 218 |
|
---|
| 219 | void Model::killFace(Face *f)
|
---|
| 220 | {
|
---|
| 221 | if( f->isValid() )
|
---|
| 222 | {
|
---|
| 223 | /* if( logfile && selected_output&OUTPUT_CONTRACTIONS )
|
---|
| 224 | *logfile << "f- " << f->uniqID+1 << std::endl;
|
---|
| 225 | */
|
---|
| 226 | f->kill();
|
---|
| 227 | validFaceCount--;
|
---|
| 228 | }
|
---|
| 229 | }
|
---|
| 230 |
|
---|
| 231 | void Model::reshapeVertex(Vertex *v, real x, real y, real z)
|
---|
| 232 | {
|
---|
| 233 | v->set(x, y, z);
|
---|
| 234 |
|
---|
| 235 | #ifdef LOG_LOWLEVEL_OPS
|
---|
| 236 | if( logfile )
|
---|
| 237 | *logfile << "v! " << v->uniqID+1 << " "
|
---|
| 238 | << x << " " << y << " " << z << endl;
|
---|
| 239 | #endif
|
---|
| 240 | }
|
---|
| 241 |
|
---|
| 242 | void Model::remapVertex(Vertex *from, Vertex *to)
|
---|
| 243 | {
|
---|
| 244 | from->remapTo(to);
|
---|
| 245 |
|
---|
| 246 | #ifdef LOG_LOWLEVEL_OPS
|
---|
| 247 | if( logfile )
|
---|
| 248 | *logfile << "v> " << from->uniqID+1 << " " << to->uniqID+1 << endl;
|
---|
| 249 | #endif
|
---|
| 250 | }
|
---|
| 251 |
|
---|
| 252 |
|
---|
| 253 |
|
---|
| 254 | void Model::contract(Vertex *v1,
|
---|
| 255 | const vert_buffer& rest,
|
---|
| 256 | const Vec3& to,
|
---|
| 257 | face_buffer& changed)
|
---|
| 258 | {
|
---|
| 259 | int i;
|
---|
| 260 |
|
---|
| 261 | /* if( logfile && selected_output&OUTPUT_CONTRACTIONS )
|
---|
| 262 | {
|
---|
| 263 | *logfile << "v% (" << v1->uniqID+1;
|
---|
| 264 | for(i=0; i<rest.length(); i++)
|
---|
| 265 | *logfile << " " << rest(i)->uniqID+1;
|
---|
| 266 |
|
---|
| 267 | *logfile << ") " << to[X] << " " << to[Y] << " " << to[Z] << std::endl;
|
---|
| 268 | }
|
---|
| 269 | */
|
---|
| 270 |
|
---|
| 271 | //
|
---|
| 272 | // Collect all the faces that are going to be changed
|
---|
| 273 | //
|
---|
| 274 | contractionRegion(v1, rest, changed);
|
---|
| 275 |
|
---|
| 276 | reshapeVertex(v1, to[X], to[Y], to[Z]);
|
---|
| 277 |
|
---|
| 278 | for(i=0; i<rest.length(); i++)
|
---|
| 279 | rest(i)->remapTo(v1);
|
---|
| 280 |
|
---|
| 281 | removeDegeneracy(changed);
|
---|
| 282 | }
|
---|
| 283 |
|
---|
| 284 | void Model::maybeFixFace(Face *F)
|
---|
| 285 | {
|
---|
| 286 | //
|
---|
| 287 | // Invalid faces do not need to be fixed.
|
---|
| 288 | #ifdef SAFETY
|
---|
| 289 | assert( F->isValid() );
|
---|
| 290 | #endif
|
---|
| 291 |
|
---|
| 292 | Vertex *v0=F->vertex(0); Vertex *v1=F->vertex(1); Vertex *v2=F->vertex(2);
|
---|
| 293 | Edge *e0 = F->edge(0); Edge *e1 = F->edge(1); Edge *e2 = F->edge(2);
|
---|
| 294 |
|
---|
| 295 | bool a=(v0 == v1), b=(v0 == v2), c=(v1 == v2);
|
---|
| 296 |
|
---|
| 297 | if( a && c )
|
---|
| 298 | {
|
---|
| 299 | // This triangle has been reduced to a point
|
---|
| 300 | killEdge(e0);
|
---|
| 301 | killEdge(e1);
|
---|
| 302 | killEdge(e2);
|
---|
| 303 |
|
---|
| 304 | killFace(F);
|
---|
| 305 | }
|
---|
| 306 | //
|
---|
| 307 | // In the following 3 cases, the triangle has become an edge
|
---|
| 308 | else if( a )
|
---|
| 309 | {
|
---|
| 310 | killEdge(e0);
|
---|
| 311 | e1->remapTo(e2->sym());
|
---|
| 312 | killFace(F);
|
---|
| 313 | }
|
---|
| 314 | else if( b )
|
---|
| 315 | {
|
---|
| 316 | killEdge(e2);
|
---|
| 317 | e0->remapTo(e1->sym());
|
---|
| 318 | killFace(F);
|
---|
| 319 | }
|
---|
| 320 | else if( c )
|
---|
| 321 | {
|
---|
| 322 | killEdge(e1);
|
---|
| 323 | e0->remapTo(e2->sym());
|
---|
| 324 | killFace(F);
|
---|
| 325 | }
|
---|
| 326 | else
|
---|
| 327 | {
|
---|
| 328 | // This triangle remains non-degenerate
|
---|
| 329 | /* // SUS: recalcular la normal en los triangulos cambiados no degenerados
|
---|
| 330 |
|
---|
[1025] | 331 | simplif::Vertex * auxv0 = F->vertex(0);
|
---|
| 332 | simplif::Vertex * auxv1 = F->vertex(1);
|
---|
| 333 | simplif::Vertex * auxv2 = F->vertex(2);
|
---|
| 334 | simplif::Vec3 vd1(auxv1->operator[](simplif::X) - auxv0->operator[](simplif::X),
|
---|
| 335 | auxv1->operator[](simplif::Y) - auxv0->operator[](simplif::Y),
|
---|
| 336 | auxv1->operator[](simplif::Z) - auxv0->operator[](simplif::Z));
|
---|
| 337 | simplif::Vec3 vd2(auxv2->operator[](simplif::X) - auxv0->operator[](simplif::X),
|
---|
| 338 | auxv2->operator[](simplif::Y) - auxv0->operator[](simplif::Y),
|
---|
| 339 | auxv2->operator[](simplif::Z) - auxv0->operator[](simplif::Z));
|
---|
[774] | 340 |
|
---|
[1025] | 341 | simplif::unitize(vd1);
|
---|
| 342 | simplif::unitize(vd2);
|
---|
[774] | 343 |
|
---|
[1025] | 344 | simplif::Vec3 vn = vd1 ^ vd2;
|
---|
| 345 | simplif::unitize(vn);
|
---|
[774] | 346 |
|
---|
| 347 | //auxf->vertex_normals[0] = vn;
|
---|
| 348 | //auxf->vertex_normals[1] = vn;
|
---|
| 349 | //auxf->vertex_normals[2] = vn;
|
---|
[1025] | 350 | F->vertex_normals[0][simplif::X] = vn[simplif::X];
|
---|
| 351 | F->vertex_normals[0][simplif::Y] = vn[simplif::Y];
|
---|
| 352 | F->vertex_normals[0][simplif::Z] = vn[simplif::Z];
|
---|
| 353 | F->vertex_normals[1][simplif::X] = vn[simplif::X];
|
---|
| 354 | F->vertex_normals[1][simplif::Y] = vn[simplif::Y];
|
---|
| 355 | F->vertex_normals[1][simplif::Z] = vn[simplif::Z];
|
---|
| 356 | F->vertex_normals[2][simplif::X] = vn[simplif::X];
|
---|
| 357 | F->vertex_normals[2][simplif::Y] = vn[simplif::Y];
|
---|
| 358 | F->vertex_normals[2][simplif::Z] = vn[simplif::Z];*/
|
---|
[774] | 359 |
|
---|
| 360 | }
|
---|
| 361 | }
|
---|
| 362 |
|
---|
| 363 | void Model::removeDegeneracy(face_buffer& changed)
|
---|
| 364 | {
|
---|
| 365 | for(int i=0; i<changed.length(); i++)
|
---|
| 366 | maybeFixFace(changed(i));
|
---|
| 367 | }
|
---|
| 368 |
|
---|
| 369 | void Model::contractionRegion(Vertex *v1,
|
---|
| 370 | const vert_buffer& vertices,
|
---|
| 371 | face_buffer& changed)
|
---|
| 372 | {
|
---|
| 373 | changed.reset();
|
---|
| 374 |
|
---|
| 375 | //
|
---|
| 376 | // First, reset the marks on all reachable vertices
|
---|
| 377 | int i;
|
---|
| 378 |
|
---|
| 379 | untagFaceLoop(v1);
|
---|
| 380 | for(i=0; i<vertices.length(); i++)
|
---|
| 381 | untagFaceLoop(vertices(i));
|
---|
| 382 |
|
---|
| 383 | //
|
---|
| 384 | // Now, pick out all the unique reachable faces
|
---|
| 385 | collectFaceLoop(v1, changed);
|
---|
| 386 | for(i=0; i<vertices.length(); i++)
|
---|
| 387 | collectFaceLoop(vertices(i), changed);
|
---|
| 388 | }
|
---|
| 389 |
|
---|
| 390 |
|
---|
| 391 | void Model::contractionRegion(Vertex *v1, Vertex *v2, face_buffer& changed)
|
---|
| 392 | {
|
---|
| 393 | changed.reset();
|
---|
| 394 |
|
---|
| 395 | //
|
---|
| 396 | // Clear marks on all reachable faces
|
---|
| 397 | untagFaceLoop(v1);
|
---|
| 398 | untagFaceLoop(v2);
|
---|
| 399 |
|
---|
| 400 | //
|
---|
| 401 | // Collect all the unique reachable faces
|
---|
| 402 | collectFaceLoop(v1, changed);
|
---|
| 403 | collectFaceLoop(v2, changed);
|
---|
| 404 | }
|
---|
| 405 |
|
---|
| 406 | void Model::contract(Vertex *v1, Vertex *v2, const Vec3& to,
|
---|
| 407 | face_buffer& changed)
|
---|
| 408 | {
|
---|
| 409 | #ifdef SAFETY
|
---|
| 410 | assert( v1 != v2);
|
---|
| 411 | #endif
|
---|
| 412 |
|
---|
| 413 | /* if( logfile && selected_output&OUTPUT_CONTRACTIONS )
|
---|
| 414 | *logfile << "v% (" << v1->uniqID+1 << " " << v2->uniqID+1 << ") "
|
---|
| 415 | << to[X] << " "
|
---|
| 416 | << to[Y] << " "
|
---|
| 417 | << to[Z] << std::endl;
|
---|
| 418 | */
|
---|
| 419 | //
|
---|
| 420 | // Collect all the faces that are going to be changed
|
---|
| 421 | //
|
---|
| 422 | contractionRegion(v1, v2, changed);
|
---|
| 423 |
|
---|
| 424 | reshapeVertex(v1, to[X], to[Y], to[Z]);
|
---|
| 425 |
|
---|
| 426 | //
|
---|
| 427 | // Map v2 ---> v1. This accomplishes the topological change that we want.
|
---|
| 428 | v2->remapTo(v1);
|
---|
| 429 |
|
---|
| 430 | removeDegeneracy(changed);
|
---|
| 431 | }
|
---|
| 432 |
|
---|
| 433 |
|
---|
| 434 | /*
|
---|
| 435 | ostream& operator<<(ostream& out, Model& M)
|
---|
| 436 | {
|
---|
| 437 | int i;
|
---|
| 438 | int uniqVerts = 0;
|
---|
| 439 |
|
---|
| 440 | out << "#$SMF 1.0" << endl;
|
---|
| 441 |
|
---|
| 442 | out_annotate(out, M);
|
---|
| 443 |
|
---|
| 444 | for(i=0; i<M.vertCount(); i++)
|
---|
| 445 | {
|
---|
| 446 | if( M.vertex(i)->isValid() )
|
---|
| 447 | {
|
---|
| 448 | M.vertex(i)->tempID = ++uniqVerts;
|
---|
| 449 |
|
---|
| 450 | out_annotate(out, M.vertex(i));
|
---|
| 451 | const Vertex& v = *M.vertex(i);
|
---|
| 452 | out << "v " << v[X] << " " << v[Y] << " " << v[Z] << endl;
|
---|
| 453 |
|
---|
| 454 | #ifdef SUPPORT_VCOLOR
|
---|
| 455 | if( v.props )
|
---|
| 456 | out << ":vc "
|
---|
| 457 | << v.props->color[X] << " "
|
---|
| 458 | << v.props->color[Y] << " "
|
---|
| 459 | << v.props->color[Z] << endl;
|
---|
| 460 | #endif
|
---|
| 461 | }
|
---|
| 462 | else
|
---|
| 463 | M.vertex(i)->tempID = -1;
|
---|
| 464 |
|
---|
| 465 | }
|
---|
| 466 |
|
---|
| 467 | for(i=0; i<M.faceCount(); i++)
|
---|
| 468 | {
|
---|
| 469 | if( M.face(i)->isValid() )
|
---|
| 470 | {
|
---|
| 471 | Face *f = M.face(i);
|
---|
| 472 | out_annotate(out, f);
|
---|
| 473 | out << "f ";
|
---|
| 474 | Vertex *v0 = (Vertex *)f->vertex(0);
|
---|
| 475 | Vertex *v1 = (Vertex *)f->vertex(1);
|
---|
| 476 | Vertex *v2 = (Vertex *)f->vertex(2);
|
---|
| 477 | #ifdef SAFETY
|
---|
| 478 | assert( v0->tempID >= 0 );
|
---|
| 479 | assert( v1->tempID >= 0 );
|
---|
| 480 | assert( v2->tempID >= 0 );
|
---|
| 481 | #endif
|
---|
| 482 | out<<v0->tempID << " " <<v1->tempID<< " " <<v2->tempID<< endl;
|
---|
| 483 | #ifdef SUPPORT_FCOLOR
|
---|
| 484 | if( f->props )
|
---|
| 485 | out << ":fc "
|
---|
| 486 | << f->props->color[X] << " "
|
---|
| 487 | << f->props->color[Y] << " "
|
---|
| 488 | << f->props->color[Z] << endl;
|
---|
| 489 | #endif
|
---|
| 490 | }
|
---|
| 491 | }
|
---|
| 492 |
|
---|
| 493 | return out;
|
---|
| 494 | }
|
---|
| 495 | */ |
---|