Manta Interactive Ray Tracer Development Mailing List

Text archives Help


[MANTA] r1342 - in trunk: Interface Model/Groups Model/Primitives scenes


Chronological Thread 
  • From: thiago@sci.utah.edu
  • To: manta@sci.utah.edu
  • Subject: [MANTA] r1342 - in trunk: Interface Model/Groups Model/Primitives scenes
  • Date: Thu, 12 Apr 2007 18:47:48 -0600 (MDT)

Author: thiago
Date: Thu Apr 12 18:47:46 2007
New Revision: 1342

Modified:
   trunk/Interface/AccelerationStructure.h
   trunk/Model/Groups/DynBVH.cc
   trunk/Model/Groups/DynBVH.h
   trunk/Model/Primitives/Sphere.cc
   trunk/Model/Primitives/Sphere.h
   trunk/scenes/iwviewer.cc
   trunk/scenes/objviewer.cc
   trunk/scenes/vorpal.cc
Log:
Model/Groups/DynBVH.h
Model/Groups/DynBVH.cc :
   Modified to use new AccelerationStructure interface. It is also now
   able to do updates and full rebuilds when paired with the
   KeyFrameAnimation class.

scenes/objviewer.cc
scenes/iwviewer.cc: 
   Modified to use new DynBVH interface. objviewer appears to work,
   haven't tested iwviewer though.

scenes/vorpal.cc: 
   The entire scene is now animated. Right now the particles are just
   randomly being moved around since I only have one timestep.

Model/Primitives/Sphere.h
Model/Primitives/Sphere.cc: 
   Added clone and interpolate. Had to remove inheritance from
   TexCoordMapper so that clone would work (otherwise a compiler error
   is given. In any case, the TexCoordMapper inheritance wasn't being
   used anywhere.

Modified: trunk/Interface/AccelerationStructure.h
==============================================================================
--- trunk/Interface/AccelerationStructure.h     (original)
+++ trunk/Interface/AccelerationStructure.h     Thu Apr 12 18:47:46 2007
@@ -11,7 +11,7 @@
     //TODO: decide whether we want a seperate rebuild and update
     //function or let the acceleration structure decide which to use.
 
-    virtual void rebuild(Group *group, int proc, int numProcs) = 0;
+    virtual void rebuild(Group *group, int proc=0, int numProcs=1) = 0;
     //virtual void update() = 0;
 
   };

Modified: trunk/Model/Groups/DynBVH.cc
==============================================================================
--- trunk/Model/Groups/DynBVH.cc        (original)
+++ trunk/Model/Groups/DynBVH.cc        Thu Apr 12 18:47:46 2007
@@ -5,6 +5,7 @@
 #include <limits>
 #include <iostream>
 #include <SCIRun/Core/Thread/Time.h>
+#include <Interface/Context.h>
 
 using namespace Manta;
 using std::cerr;
@@ -76,7 +77,7 @@
             for (int i = 0; i < root_node.children; i++ )
             {
                 const int object_id = object_ids[root_node.child+i];
-                this->get(object_id)->intersect(context,subpacket);
+                currGroup->get(object_id)->intersect(context,subpacket);
             }
         } else {
             RayPacket subpacket(rays, firstActive, rays.end());
@@ -116,7 +117,7 @@
                 for (int i = 0; i < node->children; i++ )
                 {
                     const int object_id = object_ids[node->child+i];
-                    this->get(object_id)->intersect(context,subpacket);
+                    currGroup->get(object_id)->intersect(context,subpacket);
                 }
             } else {
                 RayPacket subpacket(rays, firstActive[which], rays.end());
@@ -151,7 +152,7 @@
             {
                 const int object_id = object_ids[node.child+i];
 
-                this->get(object_id)->intersect(context,subpacket);
+                currGroup->get(object_id)->intersect(context,subpacket);
             }
         }
         else
@@ -423,20 +424,43 @@
 
 void DynBVH::preprocess(const PreprocessContext& context)
 {
-  cerr << "\nDynBVH::preprocess START\n";
-  double start = SCIRun::Time::currentSeconds();
-    Group::preprocess(context);
+  if (currGroup)
+    currGroup->preprocess(context);
+}
+
+void DynBVH::rebuild(Group *group, int proc, int numProcs)
+{
+  if (proc > 0) return; //TODO: make this parallel (at least some of it).
+
+  //for now we rebuild if the number of primitives is different and
+  //update otherwise. This of course isn't always best, so if someone
+  //wants to improve on that, go for it.
+  PreprocessContext context;
+
+  if(currGroup && currGroup->getSize() == group->getSize()) {
+    currGroup = group;
+    updateBounds(context, 0);
+  }
+  else {
+
+    cerr << "\nDynBVH::preprocess START\n";
+    double start = SCIRun::Time::currentSeconds();
     double group_preprocess_end = SCIRun::Time::currentSeconds();
 
-    nodes = new BVHNode[2*this->getSize()];
-    object_ids = new int[this->getSize()];
-    for ( int i = 0; i < this->getSize(); i++ )
-        object_ids[i] = i;
+    if(group->getSize() > object_ids.size()) {
+      nodes.resize(2*group->getSize());
+      object_ids.resize(group->getSize());
+    }
+
+    currGroup = group;    
+
+    for ( int i = 0; i < currGroup->getSize(); i++ )
+      object_ids[i] = i;
 
     num_nodes = 1; // root node
     int nextFree = 1;
     double build_start = SCIRun::Time::currentSeconds();
-    build(context, 0, 0, this->getSize(), nextFree);
+    build(context, 0, 0, currGroup->getSize(), nextFree);
     double updateBound_start = SCIRun::Time::currentSeconds();
     updateBounds(context, 0);
     double end = SCIRun::Time::currentSeconds();
@@ -445,6 +469,7 @@
          << "object_ids initialization 
("<<build_start-group_preprocess_end<<")\n"
          << "build ("<<updateBound_start-build_start<<")\n"
          << "updateBounds ("<<end-updateBound_start<<")\n\n";
+  }
 }
 
 void DynBVH::build(const PreprocessContext& context,
@@ -468,7 +493,7 @@
         // make leaf
         node.makeLeaf(objectBegin, objectEnd-objectBegin);
 
-        std::sort(object_ids+objectBegin,object_ids+objectEnd);
+        
std::sort(object_ids.begin()+objectBegin,object_ids.begin()+objectEnd);
 
         num_nodes++;
     }
@@ -501,7 +526,7 @@
         {
             BBox temp;
             int obj_id = object_ids[node.child + i];
-            this->get(obj_id)->computeBounds(context,temp);
+            currGroup->get(obj_id)->computeBounds(context,temp);
 
             node.bounds.extendByBox(temp);
         }
@@ -559,7 +584,7 @@
         {
             BVHSAHEvent new_event;
             BBox tri_box;
-            this->get(object_ids[i])->computeBounds(context, tri_box);
+            currGroup->get(object_ids[i])->computeBounds(context, tri_box);
             new_event.position = tri_box.center()[output_axis];
             new_event.obj_id   = object_ids[i];
             events.push_back(new_event);
@@ -606,7 +631,7 @@
     {
         BVHSAHEvent new_event;
         BBox tri_box;
-        this->get(object_ids[i])->computeBounds(context, tri_box);
+        currGroup->get(object_ids[i])->computeBounds(context, tri_box);
         new_event.position = tri_box.center()[axis];
         new_event.obj_id   = object_ids[i];
         events.push_back(new_event);
@@ -629,7 +654,7 @@
         events[i].left_area = left_box.computeArea();
 
         BBox tri_box;
-        this->get(events[i].obj_id)->computeBounds(context, tri_box);
+        currGroup->get(events[i].obj_id)->computeBounds(context, tri_box);
         left_box.extendByBox(tri_box);
 
         num_left++;
@@ -644,7 +669,7 @@
     for ( int i = num_events - 1; i >= 0; i-- )
     {
         BBox tri_box;
-        this->get(events[i].obj_id)->computeBounds(context, tri_box);
+        currGroup->get(events[i].obj_id)->computeBounds(context, tri_box);
 
         right_box.extendByBox(tri_box);
 
@@ -695,7 +720,7 @@
     packet.getDirection(0, 2) > 0 ? 0 : 1
   };
 
-  const BVHNode* const nodes = this->nodes;
+  const BVHNode* const nodes = &this->nodes[0];
   __m128 min_rcp[3];
   __m128 max_rcp[3];
 
@@ -746,7 +771,7 @@
 
           for (int i = 0; i < thisNode.children; i++ ) {
             const int object_id = object_ids[thisNode.child+i];
-            this->get(object_id)->intersect(context,subpacket);
+            currGroup->get(object_id)->intersect(context,subpacket);
           }
         }
       }
@@ -829,7 +854,7 @@
 
         for (int i = 0; i < thisNode.children; i++ ) {
           const int object_id = object_ids[thisNode.child+i];
-          this->get(object_id)->intersect(context,subpacket);
+          currGroup->get(object_id)->intersect(context,subpacket);
         }
       }
     }

Modified: trunk/Model/Groups/DynBVH.h
==============================================================================
--- trunk/Model/Groups/DynBVH.h (original)
+++ trunk/Model/Groups/DynBVH.h Thu Apr 12 18:47:46 2007
@@ -4,10 +4,11 @@
 #include <Model/Groups/Group.h>
 #include <Core/Geometry/BBox.h>
 #include <Interface/RayPacket.h>
+#include <Interface/AccelerationStructure.h>
 
 namespace Manta
 {
-    class DynBVH : public Group
+    class DynBVH : public AccelerationStructure
     {
     public:
         struct IAData
@@ -53,11 +54,12 @@
 
         };
 
-        BVHNode* nodes;
-        int* object_ids;
+        vector<BVHNode> nodes;
+        vector<int> object_ids;
         int num_nodes;
+        Group* currGroup;
 
-        DynBVH() : nodes(NULL), num_nodes(0)
+        DynBVH() : currGroup(NULL)
         {}
 
         void preprocess(const PreprocessContext&);
@@ -77,10 +79,12 @@
             bbox.extendByBox(nodes[0].bounds);
         }
 
-        static Group* create(const vector<string>&)
-        {
-            return new DynBVH();
-        }
+      //Is this used by anyone?
+//         static Group* create(const vector<string>&)
+//         {
+//             return new DynBVH();
+//         }
+        void rebuild(Group *group, int proc=0, int numProcs=1);
 
         void build(const PreprocessContext& context,
                    int nodeID, int objectBegin, int objectEnd,

Modified: trunk/Model/Primitives/Sphere.cc
==============================================================================
--- trunk/Model/Primitives/Sphere.cc    (original)
+++ trunk/Model/Primitives/Sphere.cc    Thu Apr 12 18:47:46 2007
@@ -13,7 +13,7 @@
 using namespace std;
 
 Sphere::Sphere(Material* material, const Vector& center, Real radius)
-  : PrimitiveCommon(material, this), center(center), radius(radius)
+  : PrimitiveCommon(material), center(center), radius(radius)
 {
   inv_radius = 1/radius;
 }
@@ -22,6 +22,21 @@
 {
 }
 
+Sphere* Sphere::clone(CloneDepth depth, Clonable* incoming)
+{
+  Sphere *copy;
+  if (incoming)
+    copy = static_cast<Sphere*>(incoming);
+  else
+    copy = new Sphere();
+
+  copy->PrimitiveCommon::clone(depth, copy);
+  copy->center = center;
+  copy->radius = radius;
+  copy->inv_radius = inv_radius;
+  return copy;
+}
+
 void Sphere::computeBounds(const PreprocessContext&, BBox& bbox) const
 {
   bbox.extendBySphere(center, radius);
@@ -372,4 +387,24 @@
     rays.setTexCoords(i, Vector(x, y, 0));
   }
   rays.setFlag(RayPacket::HaveTexture2|RayPacket::HaveTexture3);
+}
+
+Interpolable::InterpErr Sphere::interpolate(const std::vector<keyframe_t> 
&keyframes)
+{
+  PrimitiveCommon::interpolate(keyframes);
+  center = Vector(0,0,0);
+  radius = 0.0f;
+
+  for (int i=0; i < keyframes.size(); ++i) {
+    Interpolable::keyframe_t kt = keyframes[i];
+    Sphere *sphere = dynamic_cast<Sphere*>(keyframes[i].keyframe);
+    if (sphere == NULL)
+      return notInterpolable;
+    radius += sphere->radius * keyframes[i].t;
+    center += sphere->center * keyframes[i].t;
+  }
+
+  inv_radius = 1/radius;
+
+  return success;
 }

Modified: trunk/Model/Primitives/Sphere.h
==============================================================================
--- trunk/Model/Primitives/Sphere.h     (original)
+++ trunk/Model/Primitives/Sphere.h     Thu Apr 12 18:47:46 2007
@@ -7,11 +7,13 @@
 #include <Core/Geometry/Vector.h>
 
 namespace Manta {
-  class Sphere : public PrimitiveCommon, public TexCoordMapper {
+  class Sphere : public PrimitiveCommon {
   public:
     Sphere(Material* material, const Vector& center, Real radius);
     virtual ~Sphere();
 
+    virtual Sphere* clone(CloneDepth depth, Clonable* incoming);
+
     virtual void computeBounds(const PreprocessContext& context,
                                BBox& bbox) const;
     virtual void intersect(const RenderContext& context, RayPacket& rays) 
const;
@@ -21,6 +23,8 @@
     virtual void computeTexCoords3(const RenderContext& context,
                                   RayPacket& rays) const;
 
+    virtual InterpErr interpolate(const std::vector<keyframe_t> &keyframes);
+
     Vector getCenter(void) const
     {
       return center;
@@ -32,6 +36,7 @@
     }
     
   private:
+    Sphere(){};
     Vector center;
     Real radius;
     Real inv_radius;

Modified: trunk/scenes/iwviewer.cc
==============================================================================
--- trunk/scenes/iwviewer.cc    (original)
+++ trunk/scenes/iwviewer.cc    Thu Apr 12 18:47:46 2007
@@ -170,16 +170,7 @@
   }
   Material* default_material = new Lambertian(Color(RGBColor(0.5,0.6,0.5)));
   
-  Group* bvh_group = 0;
-
-  switch (build_type) {
-  case DynBVH_Build:
-    bvh_group = new DynBVH();
-    break;
-  case None_Build:
-    bvh_group = new Group();
-    break;
-  }
+  Group* bvh_group = new Group();
 
   // Iterate over all the triangles and make them
   switch (tri_type) {
@@ -208,6 +199,17 @@
       else
         bvh_group->add(new Triangle(matl, iw->va(i), iw->vc(i), iw->vb(i)));
     }
+    break;
+  }
+
+  switch (build_type) {
+  case DynBVH_Build:
+    DynBVH *bvh = new DynBVH;
+    bvh->rebuild(bvh_group);
+    return bvh;
+    break;
+  case None_Build:
+    return bvh_group;
     break;
   }
 

Modified: trunk/scenes/objviewer.cc
==============================================================================
--- trunk/scenes/objviewer.cc   (original)
+++ trunk/scenes/objviewer.cc   Thu Apr 12 18:47:46 2007
@@ -452,7 +452,7 @@
         triangle_array = new Object *[ total_triangles ];
         break;
     case DynBVH_Build:
-        bvh_group = new DynBVH();
+        bvh_group = new Group;
         break;
     case BVH_Build:
         bvh_group = new BVH(build_args);
@@ -633,6 +633,12 @@
     }
     break;
     case DynBVH_Build:
+    {
+      DynBVH *dynbvh = new DynBVH;
+      dynbvh->rebuild(bvh_group);
+      bvh = dynbvh;
+      break;
+    }
     case BVH_Build:
     case GriddedGroup_Build:
     case LinearGroup_Build:

Modified: trunk/scenes/vorpal.cc
==============================================================================
--- trunk/scenes/vorpal.cc      (original)
+++ trunk/scenes/vorpal.cc      Thu Apr 12 18:47:46 2007
@@ -5,11 +5,14 @@
 #include <Interface/Scene.h>
 #include <Model/Textures/HeightColorMap.h>
 #include <Model/Materials/Lambertian.h>
+#include <Model/Materials/Phong.h>
 #include <Model/AmbientLights/ArcAmbient.h>
+#include <Model/AmbientLights/ConstantAmbient.h>
 #include <Model/Backgrounds/LinearBackground.h>
 #include <Model/Groups/Group.h>
 #include <Model/Groups/DynBVH.h>
 #include <Model/Lights/PointLight.h>
+#include <Model/MiscObjects/KeyFrameAnimation.h>
 #include <Model/Primitives/Sphere.h>
 #include <Model/Primitives/Heightfield.h>
 #include <Core/Math/MinMax.h>
@@ -22,6 +25,50 @@
 using namespace Manta;
 using namespace std;
 
+
+Group* readParticles(const string& particles_filename, Vector& maxBound)
+{
+  ifstream in(particles_filename.c_str());
+  if(!in){
+    cerr << "Error opening " << particles_filename << "\n";
+    exit(1);
+  }
+
+  int nparticles;
+  float min_x, min_y, max_x, max_y;
+  in >> nparticles >> min_x >> min_y >> max_x >> max_y;
+  in.get();
+  float *data = new float[nparticles*3];
+  in.read(reinterpret_cast<char*>(data), sizeof(float)*(nparticles*3));
+  if(!in){
+    cerr << "Error reading data from " << particles_filename << "\n";
+    exit(1);
+  }
+
+  float max_dim = std::max(max_x-min_x, max_y-min_y)/8;
+  maxBound = Vector( (max_x-min_x)/max_dim, (max_y-min_y)/max_dim, 1);
+
+  Group *particles = new Group;
+//   Material *particle_matl = new Lambertian(Color(RGB(.2,.6,.2)));
+
+  Material *particle_matl =new Phong(Color(RGB(0xCD/255.0, 
0x7f/255.0,0x32/255.0)), 
+                                     Color(RGB(.7,.7,.7)),
+                                     64, 0);
+//   Material *particle_matl =new Phong(Color(RGB(0xCD/255.0, 
0x7f/255.0,0x32/255.0)), 
+//                                      Color(RGB(.7,.7,.7)),
+//                                      64,  (ColorComponent) 0.4);
+
+  //Note, we add some randomness just to test the dynnamic scenes and 
interpolation...
+  for (int i = 0; i < nparticles; ++i) {
+    Vector center( (data[i*3 + 0]-min_x)/max_dim*(drand48()*.2+.9), 
+                   (data[i*3 + 1]-min_y)/max_dim*(drand48()*.2+.9), 
+                   data[i*3 + 2]*2*(drand48()*.2+.9));
+    particles->add(new Sphere(particle_matl, center, .03));
+  }
+
+  return particles;
+}
+
 extern "C"
 Scene* make_scene(const ReadContext&, const vector<string>& args)
 {
@@ -48,49 +95,62 @@
     }
   }
 
-  Group* group = new Group();
+  Group *group = new Group;
 
-  ifstream in(particles_filename.c_str());
-  if(!in){
-    cerr << "Error opening " << particles_filename << "\n";
-    exit(1);
-  }
-  int nparticles;
-  float min_x, min_y, max_x, max_y;
-  in >> nparticles >> min_x >> min_y >> max_x >> max_y;
-  in.get();
-  float *data = new float[nparticles*3];
-  in.read(reinterpret_cast<char*>(data), sizeof(float)*(nparticles*3));
-  if(!in){
-    cerr << "Error reading data from " << particles_filename << "\n";
-    exit(1);
-  }
-    
-  float max_dim = std::max(max_x-min_x, max_y-min_y)/8;
+  Vector minBound(0, 0, 0), maxBound;
+  Group *particles1 = readParticles(particles_filename, maxBound);
+
+  string particles2_filename = 
"/home/sci/thiago/work/Fusion/vorpal/code/cropped_particles_5e10_intel";
+  Group *particles2 = readParticles(particles_filename, maxBound);
 
-  Vector minBound(0, 0, 0);
-  Vector maxBound( (max_x-min_x)/max_dim, (max_y-min_y)/max_dim, 1);
+  string particles3_filename = 
"/home/sci/thiago/work/Fusion/vorpal/code/cropped_particles_1e11_intel";
+  Group *particles3 = readParticles(particles_filename, maxBound);
+    
+  KeyFrameAnimation *particle_animation = new 
KeyFrameAnimation(KeyFrameAnimation::linear);
+  particle_animation->startAnimation();
+  particle_animation->push_back(particles1);
+  particle_animation->push_back(particles2);
+  particle_animation->push_back(particles3);
+  particle_animation->setDuration(10);
+  particle_animation->useAccelerationStructure(new DynBVH());
 
   Heightfield* heightfield = new Heightfield(NULL, 
heightfield_filename.c_str(), minBound, maxBound);
   BBox bbox;
   PreprocessContext preprocessContext;
   heightfield->computeBounds(preprocessContext, bbox);
   heightfield->setMaterial(new Lambertian(new 
HeightColorMap(bbox.getMin().z(), bbox.getMax().z())));
+
+  string heightfield2_filename = 
"/home/sci/thiago/work/Fusion/vorpal/code/vorpal_10k_rand1.hf";
+  Heightfield* heightfield2 = new Heightfield(NULL, 
heightfield2_filename.c_str(), minBound, maxBound);
+  heightfield2->setMaterial(new Lambertian(new 
HeightColorMap(bbox.getMin().z(), bbox.getMax().z())));
+
+  string heightfield3_filename = 
"/home/sci/thiago/work/Fusion/vorpal/code/vorpal_10k_rand2.hf";
+  Heightfield* heightfield3 = new Heightfield(NULL, 
heightfield3_filename.c_str(), minBound, maxBound);
+  heightfield3->setMaterial(new Lambertian(new 
HeightColorMap(bbox.getMin().z(), bbox.getMax().z())));
+
 //   if ( mapr )
 //     prim->setTexCoordMapper( mapr );
   
-  Group *particles = new DynBVH;
-  Material *particle_matl = new Lambertian(Color(RGB(.2,.6,.2)));
-  for (int i = 0; i < nparticles; ++i) {
-    Vector center( (data[i*3 + 0]-min_x)/max_dim, 
-                   (data[i*3 + 1]-min_y)/max_dim, 
-                   data[i*3 + 2]*4);
-    particles->add(new Sphere(particle_matl, center, .03));
-  }
-  group->add(particles);  
-  group->add(heightfield);
 
 
+  KeyFrameAnimation *animation = new 
KeyFrameAnimation(KeyFrameAnimation::linear);
+  animation->startAnimation();
+  Group *frame1 = new Group();
+  Group *frame2 = new Group();
+  Group *frame3 = new Group();
+  frame1->add(heightfield);
+  frame2->add(heightfield2);
+  frame3->add(heightfield3);
+  animation->push_back(frame1);
+  animation->push_back(frame2);
+  animation->push_back(frame3);
+  animation->setDuration(10);
+  
+//   AccelerationStructure *as = new DynBVH;
+//   as->rebuild(particles);
+  group->add(particle_animation);  
+  group->add(animation);
+
   Scene* scene = new Scene();
   scene->setBackground(new LinearBackground(Color(RGB(0.2, 0.4, 0.9)),
                                             Color(RGB(0.0,0.0,0.0)),
@@ -98,11 +158,24 @@
   scene->setObject(group);
 
   LightSet* lights = new LightSet();
-  lights->add(new PointLight(Vector(0,0,1000), Color(RGB(1,1,1))*2));
-  Color cup(RGB(0.1, 0.3, 0.8));
-  Color cdown(RGB(0.82, 0.62, 0.62));
+
+  group->computeBounds(preprocessContext, bbox);
+  Vector lightCenter =(maxBound + minBound)/2 + 
Vector(0,0,bbox.getMax().z()*4);
+
+  const int NUM_LIGHT_SAMPLES_ROOT = 1;
+  for (float i=0; i < NUM_LIGHT_SAMPLES_ROOT; ++i) {
+    for (float j=0; j < NUM_LIGHT_SAMPLES_ROOT; ++j) {
+      lights->add(new 
PointLight(lightCenter+1*Vector(i/NUM_LIGHT_SAMPLES_ROOT,
+                                                      
j/NUM_LIGHT_SAMPLES_ROOT,0),
+                                 
Color(RGB(1,1,1))*1/(NUM_LIGHT_SAMPLES_ROOT*NUM_LIGHT_SAMPLES_ROOT)));
+    }
+  }
+  Color cup(RGB(0.3, 0.3, 0.3));
+  Color cdown(RGB(0.1, 0.1, 0.1));
+  Color grey(RGB(0.1, 0.1, 0.1));
   Vector up(0,1,0);
-  lights->setAmbientLight(new ArcAmbient(cup, cdown, up));
+   lights->setAmbientLight(new ArcAmbient(cup, cdown, up));
+//   lights->setAmbientLight(new ConstantAmbient(grey));
   scene->setLights(lights);
   return scene;
 }




  • [MANTA] r1342 - in trunk: Interface Model/Groups Model/Primitives scenes, thiago, 04/12/2007

Archive powered by MHonArc 2.6.16.

Top of page