Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: orxonox.OLD/orxonox/trunk/src/lib/coord/p_node.cc @ 4992

Last change on this file since 4992 was 4992, checked in by bensch, 19 years ago

orxonox/trunk: optimizations

File size: 21.2 KB
Line 
1
2
3/*
4   orxonox - the future of 3D-vertical-scrollers
5
6   Copyright (C) 2004 orx
7
8   This program is free software; you can redistribute it and/or modify
9   it under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2, or (at your option)
11   any later version.
12
13   ### File Specific:
14   main-programmer: Patrick Boenzli
15   co-programmer:
16
17   @todo Smooth-Parent: delay, speed
18*/
19
20#define DEBUG_SPECIAL_MODULE DEBUG_MODULE_PNODE
21
22#include "p_node.h"
23#include "null_parent.h"
24
25#include "load_param.h"
26#include "class_list.h"
27
28#include "stdincl.h"
29#include "compiler.h"
30#include "error.h"
31#include "debug.h"
32#include "list.h"
33#include "vector.h"
34
35//#include "vector.h"
36//#include "quaternion.h"
37
38using namespace std;
39
40
41/**
42 *  standard constructor
43*/
44PNode::PNode ()
45{
46  init(NULL);
47
48  NullParent::getInstance()->addChild(this);
49}
50
51/**
52 * @param root the load-Element for the PNode
53*/
54PNode::PNode(const TiXmlElement* root)
55{
56  this->init(NULL);
57  this->loadParams(root);
58
59  NullParent::getInstance()->addChild(this);
60}
61
62
63/**
64 *  constructor with coodinates
65 * @param absCoordinate the Absolute coordinate of the Object
66 * @param parent The parent-node of this node.
67*/
68PNode::PNode (const Vector& absCoordinate, PNode* parent )
69{
70  this->init(parent);
71
72  this->absCoordinate = absCoordinate;
73
74  if (likely(parent != NULL))
75  {
76    this->relCoordinate = this->absCoordinate - parent->getAbsCoor();
77    parent->addChild (this);
78  }
79}
80
81/**
82 *  standard deconstructor
83*/
84PNode::~PNode ()
85{
86  tIterator<PNode>* iterator = this->children->getIterator();
87  PNode* pn = iterator->nextElement();
88  while( pn != NULL)
89    {
90      delete pn;
91      pn = iterator->nextElement();
92    }
93  delete iterator;
94  /* this deletes all children in the list */
95  delete this->children;
96  if (this->parent)
97    this->parent->removeChild(this);
98}
99
100/**
101 *  initializes a PNode
102 * @param parent the parent for this PNode
103*/
104void PNode::init(PNode* parent)
105{
106  this->setClassID(CL_PARENT_NODE, "PNode");
107  this->children = new tList<PNode>();
108  this->bRelCoorChanged = true;
109  this->bAbsCoorChanged = false;
110  this->bRelDirChanged = true;
111  this->bAbsDirChanged = false;
112  this->parent = parent;
113
114  this->toPosition = NULL;
115  this->toDirection = NULL;
116  this->bias = 1.0;
117}
118
119/**
120 *  loads parameters of the PNode
121 * @param root the XML-element to load the properties of
122*/
123void PNode::loadParams(const TiXmlElement* root)
124{
125  static_cast<BaseObject*>(this)->loadParams(root);
126
127  LoadParam<PNode>(root, "rel-coor", this, &PNode::setRelCoor)
128      .describe("Sets The relative position of the Node to its parent.");
129
130  LoadParam<PNode>(root, "abs-coor", this, &PNode::setAbsCoor)
131      .describe("Sets The absolute Position of the Node.");
132
133  LoadParam<PNode>(root, "rel-dir", this, &PNode::setRelDir)
134      .describe("Sets The relative rotation of the Node to its parent.");
135
136  LoadParam<PNode>(root, "abs-dir", this, &PNode::setAbsDir)
137      .describe("Sets The absolute rotation of the Node.");
138
139  LoadParam<PNode>(root, "parent", this, &PNode::setParent)
140      .describe("the Name of the Parent of this PNode");
141
142  LoadParam<PNode>(root, "parent-mode", this, &PNode::setParentMode)
143      .describe("the mode to connect this node to its parent ()");
144
145  // cycling properties
146  if (root != NULL)
147  {
148    const TiXmlElement* element = root->FirstChildElement();
149    while (element != NULL)
150    {
151      LoadParam<PNode>(root, "parent", this, &PNode::addChild, true)
152          .describe("adds a new Child to the current Node.");
153
154      element = element->NextSiblingElement();
155    }
156  }
157}
158
159/**
160 *  set relative coordinates
161 * @param relCoord relative coordinates to its parent
162
163   it is very importand, that you use this function, if you want to update the
164   relCoordinates. If you don't use this, the PNode won't recognize, that something
165   has changed and won't update the children Nodes.
166*/
167void PNode::setRelCoor (const Vector& relCoord)
168{
169  this->bRelCoorChanged = true;
170  this->relCoordinate = relCoord;
171}
172
173/**
174 *  set relative coordinates
175 * @param x x-relative coordinates to its parent
176 * @param y y-relative coordinates to its parent
177 * @param z z-relative coordinates to its parent
178   \see  void PNode::setRelCoor (const Vector& relCoord)
179*/
180void PNode::setRelCoor (float x, float y, float z)
181{
182  this->setRelCoor(Vector(x, y, z));
183}
184
185/**
186 * sets a new relative position smoothely
187 * @param relCoordSoft the new Position to iterate to
188 * @param bias how fast to iterate to this position
189 */
190void PNode::setRelCoorSoft(const Vector& relCoordSoft, float bias)
191{
192  if (likely(this->toPosition == NULL))
193    this->toPosition = new Vector();
194
195  *this->toPosition = relCoordSoft;
196  this->bias = bias;
197}
198
199
200/**
201 *  set relative coordinates smoothely
202 * @param x x-relative coordinates to its parent
203 * @param y y-relative coordinates to its parent
204 * @param z z-relative coordinates to its parent
205   \see  void PNode::setRelCoorSoft (const Vector&, float)
206 */
207void PNode::setRelCoorSoft (float x, float y, float z, float bias)
208{
209  this->setRelCoorSoft(Vector(x, y, z), bias);
210}
211
212/**
213 * @param absCoord set absolute coordinate
214
215   it is very importand, that you use this function, if you want to update the
216   absCoordinates. If you don't use this, the PNode won't recognize, that something
217   has changed and won't update the children Nodes.
218*/
219void PNode::setAbsCoor (const Vector& absCoord)
220{
221  this->bAbsCoorChanged = true;
222  this->absCoordinate = absCoord;
223}
224
225/**
226 * @param x x-coordinate.
227 * @param y y-coordinate.
228 * @param z z-coordinate.
229 * @see void PNode::setAbsCoor (const Vector& absCoord)
230 */
231void PNode::setAbsCoor(float x, float y, float z)
232{
233  this->setAbsCoor(Vector(x, y, z));
234}
235
236/**
237 *  shift coordinate (abs and rel)
238 * @param shift shift vector
239
240   this function shifts the current coordinates about the vector shift. this is
241   usefull because from some place else you can:
242   PNode* someNode = ...;
243   Vector objectMovement = calculateShift();
244   someNode->shiftCoor(objectMovement);
245
246   elsewhere you would have to:
247   PNode* someNode = ...;
248   Vector objectMovement = calculateShift();
249   Vector currentCoor = someNode->getRelCoor();
250   Vector newCoor = currentCoor + objectMovement;
251   someNode->setRelCoor(newCoor);
252
253   yea right... shorter...
254 *
255 * @todo this is ambiguous, from the outside one does not know it absCoor has been changed
256 * this might lead to strange artefacts !!
257
258*/
259void PNode::shiftCoor (const Vector& shift)
260{
261
262  if( unlikely(this->bAbsCoorChanged))
263    {
264      this->absCoordinate += shift;
265    }
266  else
267    {
268      this->relCoordinate += shift;
269      this->bRelCoorChanged = true;
270    }
271}
272
273/**
274 *  set relative direction
275 * @param relDir to its parent
276
277   it is very importand, that you use this function, if you want to update the
278   relDirection. If you don't use this, the PNode won't recognize, that something
279   has changed and won't update the children Nodes.
280*/
281void PNode::setRelDir (const Quaternion& relDir)
282{
283  this->bRelCoorChanged = true;
284  this->relDirection = relDir;
285}
286
287/**
288 * @see void PNode::setRelDir (const Quaternion& relDir)
289 * @param x the x direction
290 * @param y the y direction
291 * @param z the z direction
292 *
293 * main difference is, that here you give a directional vector, that will be translated into a Quaternion
294 */
295void PNode::setRelDir (float x, float y, float z)
296{
297  this->setRelDir(Quaternion(Vector(x,y,z), Vector(0,1,0)));
298}
299
300
301/**
302 * sets the Relative Direction of this node to its parent in a Smoothed way
303 * @param relDirSoft the direction to iterate to smoothely.
304 * @param bias how fast to iterate to the new Direction
305 */
306void PNode::setRelDirSoft(const Quaternion& relDirSoft, float bias)
307{
308  if (likely(this->toDirection == NULL))
309    this->toDirection = new Quaternion();
310
311  *this->toDirection = relDirSoft;
312  this->bias = bias;
313}
314
315/**
316 * @see void PNode::setRelDirSoft (const Quaternion& relDir)
317 * @param x the x direction
318 * @param y the y direction
319 * @param z the z direction
320 *
321 * main difference is, that here you give a directional vector, that will be translated into a Quaternion
322 */
323void PNode::setRelDirSoft(float x, float y, float z, float bias)
324{
325  this->setRelDirSoft(Quaternion(Vector(x,y,z), Vector(0,1,0)), bias);
326}
327
328
329/**
330 *  sets the absolute direction (0,0,1)
331 * @param absDir absolute coordinates
332
333   it is very importand, that you use this function, if you want to update the
334   absDirection. If you don't use this, the PNode won't recognize, that something
335   has changed and won't update the children Nodes.
336*/
337void PNode::setAbsDir (const Quaternion& absDir)
338{
339  this->bAbsDirChanged = true;
340  this->absDirection = absDir;
341}
342
343/**
344 * @see void PNode::setAbsDir (const Quaternion& relDir)
345 * @param x the x direction
346 * @param y the y direction
347 * @param z the z direction
348 *
349 * main difference is, that here you give a directional vector, that will be translated into a Quaternion
350 */
351void PNode::setAbsDir (float x, float y, float z)
352{
353  this->setAbsDir(Quaternion(Vector(x,y,z), Vector(0,1,0)));
354}
355
356/**
357 *  shift coordinate (abs and rel)
358 * @param shift vector
359
360   this function shifts the current coordinates about the vector shift. this is
361   usefull because from some place else you can:
362   PNode* someNode = ...;
363   Quaternion objectMovement = calculateShift();
364   someNode->shiftCoor(objectMovement);
365
366   elsewhere you would have to:
367   PNode* someNode = ...;
368   Quaternion objectMovement = calculateShift();
369   Quaternion currentCoor = someNode->getRelCoor();
370   Quaternion newCoor = currentCoor + objectMovement;
371   someNode->setRelCoor(newCoor);
372
373   yea right... shorter...
374
375   @todo implement this
376*/
377void PNode::shiftDir (const Quaternion& shift)
378{
379  this->bRelDirChanged = true;
380  this->relDirection = this->relDirection * shift;
381}
382
383/**
384 *  adds a child and makes this node to a parent
385 * @param pNode child reference
386 * @param parentMode on which changes the child should also change ist state
387
388   use this to add a child to this node.
389*/
390void PNode::addChild (PNode* pNode, int parentMode)
391{
392  if( likely(pNode->parent != NULL))
393    {
394      PRINTF(4)("PNode::addChild() - reparenting node: removing it and adding it again\n");
395      pNode->parent->children->remove(pNode);
396    }
397  pNode->parentMode = parentMode;
398  pNode->parent = this;
399  this->children->add(pNode);
400}
401
402/**
403 * @see PNode::addChild(PNode* parent);
404 * @param childName the name of the child to add to this PNode
405 */
406void PNode::addChild (const char* childName)
407{
408  PNode* childNode = dynamic_cast<PNode*>(ClassList::getObject(childName, CL_PARENT_NODE));
409  if (childNode != NULL)
410    this->addChild(childNode);
411}
412
413
414/**
415 *  removes a child from the node
416 * @param pNode the child to remove from this pNode.
417
418   Children from pNode will not be lost, they are referenced to NullPointer
419*/
420void PNode::removeChild (PNode* pNode)
421{
422  pNode->remove();
423  this->children->remove (pNode);
424  pNode->parent = NULL;
425}
426
427
428/**
429 *  remove this pnode from the tree and adds all following to NullParent
430
431   this can be the case, if an entity in the world is been destroyed.
432*/
433void PNode::remove()
434{
435  NullParent* nullParent = NullParent::getInstance();
436
437  tIterator<PNode>* iterator = this->children->getIterator();
438  PNode* pn = iterator->nextElement();
439
440  while( pn != NULL)
441    {
442      nullParent->addChild(pn, pn->getParentMode());
443      pn = iterator->nextElement();
444    }
445  delete iterator;
446  this->parent->children->remove(this);
447}
448
449
450/**
451 *  sets the parent of this PNode
452 * @param parent the Parent to set
453*/
454void PNode::setParent (PNode* parent)
455{
456  parent->addChild(this);
457}
458
459/**
460 * @see PNode::setParent(PNode* parent);
461 * @param parentName the name of the Parent to set to this PNode
462 */
463void PNode::setParent (const char* parentName)
464{
465  PNode* parentNode = dynamic_cast<PNode*>(ClassList::getObject(parentName, CL_PARENT_NODE));
466  if (parentNode != NULL)
467    parentNode->addChild(this);
468}
469
470/**
471 * does the reparenting in a very smooth way
472 * @param parentNode the new Node to connect this node to.
473 */
474void PNode::softReparent(PNode* parentNode, float bias)
475{
476  if (this->parent == parentNode)
477    return;
478
479  //this->setRelCoorSoft(this->getRelCoor());
480  if (likely(this->toPosition == NULL))
481  {
482    this->toPosition = new Vector();
483    *this->toPosition = this->getRelCoor();
484  }
485  if (likely(this->toDirection == NULL))
486  {
487    this->toDirection = new Quaternion();
488    *this->toDirection = this->getRelDir();
489  }
490  this->bias = bias;
491
492
493  Vector tmpV = this->getAbsCoor();
494  Quaternion tmpQ = this->getAbsDir();
495
496  parentNode->addChild(this);
497
498/*  if (this->parentMode & PNODE_ROTATE_MOVEMENT)
499  this->setRelCoor(parent->getRelDir().apply(tmpV - parent->getAbsCoor()));
500  else*/
501  this->setRelCoor(tmpV - parentNode->getAbsCoor());
502
503  this->setRelDir(tmpQ - parentNode->getAbsDir());
504}
505
506void PNode::softReparent(const char* parentName, float bias)
507{
508  PNode* parentNode = dynamic_cast<PNode*>(ClassList::getObject(parentName, CL_PARENT_NODE));
509  if (parentNode != NULL)
510    this->softReparent(parentNode, bias);
511}
512
513
514/**
515 *  set the mode of this parent manualy
516 * @param parentMode the mode of the bind-type.
517*/
518void PNode::setParentMode (PARENT_MODE parentMode)
519{
520  this->parentMode = parentMode;
521}
522
523/**
524 *  sets the mode of this parent manually
525 * @param parentMode a String representing this parentingMode
526 */
527void PNode::setParentMode (const char* parentingMode)
528{
529  if (!strcmp(parentingMode, "local-rotate"))
530    this->setParentMode(PNODE_LOCAL_ROTATE);
531  else  if (!strcmp(parentingMode, "rotate-movement"))
532    this->setParentMode(PNODE_ROTATE_MOVEMENT);
533  else  if (!strcmp(parentingMode, "movement"))
534    this->setParentMode(PNODE_MOVEMENT);
535  else  if (!strcmp(parentingMode, "all"))
536    this->setParentMode(PNODE_ALL);
537  else  if (!strcmp(parentingMode, "rotate-and-move"))
538    this->setParentMode(PNODE_ROTATE_AND_MOVE);
539}
540
541
542/**
543 *  has to be called, if the parent coordinate has changed
544
545   normaly this will be done by the parent itself automaticaly. If you call this, you
546   will force an update of the coordinated of the node.
547*/
548/*
549void PNode::parentCoorChanged ()
550{
551  this->bRelCoorChanged = true;
552}
553*/
554
555/**
556 *  updates the absCoordinate/absDirection
557 * @param dt The time passed since the last update
558
559   this is used to go through the parent-tree to update all the absolute coordinates
560   and directions. this update should be done by the engine, so you don't have to
561   worry, normaly...
562*/
563void PNode::update (float dt)
564{
565  if( likely(this->parent != NULL))
566    {
567      // movement for nodes with smoothMove enabled
568      if (unlikely(this->toPosition != NULL))
569      {
570        Vector moveVect = (*this->toPosition - this->getRelCoor()) *dt*bias;
571
572        if (likely(moveVect.len() >= .001))
573        {
574          this->shiftCoor(moveVect);
575        }
576        else
577        {
578          delete this->toPosition;
579          this->toPosition = NULL;
580          PRINTF(5)("SmoothMove of %s finished\n", this->getName());
581        }
582      }
583      if (unlikely(this->toDirection != NULL))
584      {
585        Quaternion rotQuat = (*this->toDirection - this->getRelDir()) *dt*bias;
586
587//        if (likely(rotQuat.len() >= .001))
588        {
589          this->shiftDir(rotQuat);
590        }
591/*        else
592        {
593          delete this->toPosition;
594          this->toPosition = NULL;
595          PRINTF(5)("SmoothMove of %s finished\n", this->getName());
596        }*/
597      }
598
599      this->lastAbsCoordinate = this->absCoordinate;
600
601      PRINTF(5)("PNode::update - %s - (%f, %f, %f)\n", this->getName(), this->absCoordinate.x, this->absCoordinate.y, this->absCoordinate.z);
602
603      if( likely(this->parentMode & PNODE_MOVEMENT))
604        {
605          if( unlikely(this->bAbsCoorChanged))
606            {
607              /* if you have set the absolute coordinates this overrides all other changes */
608              this->relCoordinate = this->absCoordinate - parent->getAbsCoor ();
609            }
610          if( likely(this->bRelCoorChanged))
611            {
612              /* update the current absCoordinate */
613              this->absCoordinate = parent->getAbsCoor() + this->relCoordinate;
614            }
615        }
616
617      if( this->parentMode & PNODE_LOCAL_ROTATE)
618        {
619          if( unlikely(this->bAbsDirChanged))
620            {
621              /* if you have set the absolute coordinates this overrides all other changes */
622              this->relDirection = this->absDirection - parent->getAbsDir();
623            }
624          else if( likely(this->bRelDirChanged))
625            {
626              /* update the current absDirection - remember * means rotation around sth.*/
627              this->absDirection = parent->getAbsDir() * this->relDirection;
628            }
629        }
630
631      if( this->parentMode & PNODE_ROTATE_MOVEMENT)
632        {
633          if( unlikely(this->bAbsCoorChanged))
634            {
635              /* if you have set the absolute coordinates this overrides all other changes */
636              this->relCoordinate = this->absCoordinate - parent->getAbsCoor ();
637            }
638          else if( likely(this->bRelCoorChanged))
639            {
640              /* update the current absCoordinate */
641              this->absCoordinate = parent->getAbsCoor() + parent->getAbsDir().apply(this->relCoordinate);
642            }
643        }
644
645
646      tIterator<PNode>* iterator = this->children->getIterator();
647      PNode* pn = iterator->nextElement();
648      while( pn != NULL)
649        {
650          /* if this node has changed, make sure, that all children are updated also */
651          if( likely(this->bRelCoorChanged || this->bAbsCoorChanged))
652            pn->parentCoorChanged ();
653          if( likely(this->bRelDirChanged || this->bAbsDirChanged))
654            pn->parentDirChanged ();
655
656          pn->update(dt);
657          //pn = this->children->nextElement();
658          pn = iterator->nextElement();
659        }
660      delete iterator;
661
662      this->velocity = (this->absCoordinate - this->lastAbsCoordinate) / dt;
663      this->bRelCoorChanged = false;
664      this->bAbsCoorChanged = false;
665      this->bRelDirChanged = false;
666      this->bAbsDirChanged = false;
667    }
668  else
669    {
670      PRINTF(4)("NullParent::update - (%f, %f, %f)\n", this->absCoordinate.x, this->absCoordinate.y, this->absCoordinate.z);
671      this->absCoordinate = this->relCoordinate;
672      this->absDirection = this->getAbsDir () * this->relDirection;
673
674      tIterator<PNode>* iterator = this->children->getIterator();
675      //PNode* pn = this->children->enumerate ();
676      PNode* pn = iterator->nextElement();
677      while( pn != NULL)
678      {
679        /* if this node has changed, make sure, that all children are updated also */
680          if( this->bRelCoorChanged || this->bAbsCoorChanged)
681            pn->parentCoorChanged ();
682          if( this->bRelDirChanged || this->bAbsDirChanged)
683            pn->parentDirChanged ();
684          pn->update (dt);
685          //pn = this->children->nextElement ();
686          pn = iterator->nextElement();
687        }
688      delete iterator;
689      this->bRelCoorChanged = false;
690      this->bAbsCoorChanged = false;
691      this->bRelDirChanged = false;
692      this->bAbsDirChanged = false;
693    }
694}
695
696/**
697 *  displays some information about this pNode
698 * @param depth The deph into which to debug the children of this PNode to.
699  (0: all children will be debugged, 1: only this PNode, 2: this and direct children...)
700 * @param level The n-th level of the Node we draw (this is internal and only for nice output)
701*/
702void PNode::debug(unsigned int depth, unsigned int level) const
703{
704  for (unsigned int i = 0; i < level; i++)
705    PRINT(0)(" |");
706  if (this->children->getSize() > 0)
707    PRINT(0)(" +");
708  else
709    PRINT(0)(" -");
710  PRINT(0)("PNode(%s::%s) - absCoord: (%0.2f, %0.2f, %0.2f), relCoord(%0.2f, %0.2f, %0.2f)\n",
711           this->getClassName(),
712           this->getName(),
713           this->absCoordinate.x,
714           this->absCoordinate.y,
715           this->absCoordinate.z,
716           this->relCoordinate.x,
717           this->relCoordinate.y,
718           this->relCoordinate.z );
719  if (depth >= 2 || depth == 0)
720  {
721    tIterator<PNode>* iterator = this->children->getIterator();
722      //PNode* pn = this->children->enumerate ();
723    PNode* pn = iterator->nextElement();
724    while( pn != NULL)
725    {
726      if (depth == 0)
727        pn->debug(0, level + 1);
728      else
729        pn->debug(depth - 1, level +1);
730      pn = iterator->nextElement();
731    }
732    delete iterator;
733  }
734}
735
736/**
737   displays the PNode at its position with its rotation as a cube.
738*/
739void PNode::debugDraw(float size) const
740{
741  glMatrixMode(GL_MODELVIEW);
742  glPushMatrix();
743  float matrix[4][4];
744
745  /* translate */
746  glTranslatef (this->getAbsCoor ().x,
747                this->getAbsCoor ().y,
748                this->getAbsCoor ().z);
749  /* rotate */
750  this->getAbsDir ().matrix (matrix);
751  glMultMatrixf((float*)matrix);
752  {
753    glBegin(GL_LINE_STRIP);
754    glVertex3f( .5*size,  .5*size, -.5*size);
755    glVertex3f( .5*size,  .5*size,  .5*size);
756    glVertex3f(-.5*size,  .5*size,  .5*size);
757    glVertex3f(-.5*size, -.5*size,  .5*size);
758    glVertex3f( .5*size,  .5*size, -.5*size);
759    glVertex3f( .5*size, -.5*size, -.5*size);
760    glVertex3f(-.5*size, -.5*size, -.5*size);
761    glVertex3f(-.5*size,  .5*size, -.5*size);
762    glEnd();
763    glBegin(GL_LINE_STRIP);
764    glVertex3f(-.5*size, -.5*size, -.5*size);
765    glVertex3f(-.5*size, -.5*size,  .5*size);
766    glVertex3f( .5*size, -.5*size,  .5*size);
767    glVertex3f( .5*size, -.5*size, -.5*size);
768    glEnd();
769    glBegin(GL_LINES);
770    glVertex3f( .5*size, -.5*size,  .5*size);
771    glVertex3f( .5*size,  .5*size,  .5*size);
772    glVertex3f(-.5*size, -.5*size,  .5*size);
773    glVertex3f(-.5*size,  .5*size,  .5*size);
774    glEnd();
775  }
776
777  glPopMatrix();
778}
Note: See TracBrowser for help on using the repository browser.