Manta Interactive Ray Tracer Development Mailing List

Text archives Help


[MANTA] r1061 - in trunk: . Engine/Control Model Model/Materials Model/Primitives Model/Readers scenes


Chronological Thread 
  • From: cgribble@sci.utah.edu
  • To: manta@sci.utah.edu
  • Subject: [MANTA] r1061 - in trunk: . Engine/Control Model Model/Materials Model/Primitives Model/Readers scenes
  • Date: Mon, 15 May 2006 23:25:27 -0600 (MDT)

Author: cgribble
Date: Mon May 15 23:25:20 2006
New Revision: 1061

Added:
   trunk/Engine/Control/DynPLTWorker.cc
   trunk/Engine/Control/DynPLTWorker.h
   trunk/Model/Materials/DynPLTMaterial.cc
   trunk/Model/Materials/DynPLTMaterial.h
   trunk/Model/Primitives/DynPLTParticle.h
   trunk/scenes/dynplt.cc
Modified:
   trunk/CMakeLists.txt
   trunk/Engine/Control/CMakeLists.txt
   trunk/Model/CMakeLists.txt
   trunk/Model/Materials/CMakeLists.txt
   trunk/Model/Primitives/CMakeLists.txt
   trunk/Model/Primitives/Sphere.h
   trunk/Model/Readers/CMakeLists.txt
   trunk/scenes/CMakeLists.txt
Log:
Model/Materials/DynPLTMaterial.h
Model/Materials/DynPLTMaterial.cc
Model/Materials/CMakeLists.txt
  Added first cut at a shader to lazily evaluate PLTs.  If a particle's 
texture
    is valid, currently shades the particle a flat red.  Otherwise, requests 
that
    the texture be generated and uses Lambertian shading until it is ready.

Model/Primitives/DynPLTParticle.h
Model/Primitives/Sphere.h
Model/Primitives/CMakeLists.txt
  Added first cut at simple primitive for lazily evaluated PLTs.  Contains 
flags
    indicating:  (1) if the texture is valid; and (2) if texture generation 
has
    been requested.

Engine/Control/DynPLTWorker.cc
Engine/Control/DynPLTWorker.h
Engine/Control/CMakeLists.txt
  Added first cut at workers for PLT generation.  Currently, the workers just
    sleep for a random amount of time (0-5 seconds) before setting the 
particle's
    valid flag to true.

scenes/dynplt.cc
  Added first cut at a scene using new lazily evaluated PLT functionality.
    Creates the texture generation mailbox and workers, reads in a NRRD 
particle
    dataset, and off it goes.

CMakeLists.txt
  Added new CMake variables for controlling build options:
    - BUILD_NRRDPARTICLES adds NRRD particle reader functionality and the 
nrrdp
      scene
    - BUILD_DYNPLT add lazily evaluated PLT support
  Best to leave these off unless you're helping me work on the particles.  :)

scenes/CMakeLists.txt
  Slight modification to the way nrrdp scene is built (now depends on
    BUILD_NRRDPARTICLES declared in Manta/src/CMakeLists.txt)
  Added commands for building dynplt scene

Model/CMakeLists.txt
  Conditionally add teem library to link command if building with lazily
    evaluated PLT support

Model/Readers/CMakeLists.txt
  Slight modification to the way ParticleNRRD reader is built (now depends on
    BUILD_NRRDPARTICLES declared in Manta/src/CMakeLists.txt)


Modified: trunk/CMakeLists.txt
==============================================================================
--- trunk/CMakeLists.txt        (original)
+++ trunk/CMakeLists.txt        Mon May 15 23:25:20 2006
@@ -267,6 +267,9 @@
   SUBDIRS(fox)
 ENDIF(BUILD_FOX)
 
+SET(BUILD_NRRDPARTICLES 0 CACHE BOOL "Build NRRD particle data reader/scene")
+SET(BUILD_DYNPLT 0 CACHE BOOL "Lazily evaluated PLTs for NRRD particle 
datasets")
+
 ##################################################################
 # Look for Teem http://teem.sourceforge.net/
 

Modified: trunk/Engine/Control/CMakeLists.txt
==============================================================================
--- trunk/Engine/Control/CMakeLists.txt (original)
+++ trunk/Engine/Control/CMakeLists.txt Mon May 15 23:25:20 2006
@@ -3,3 +3,12 @@
      Control/RTRT.h
      Control/RTRT.cc
      )
+
+# Conditionally compile DynPLTWorker code
+IF(BUILD_DYNPLT)
+   SET(Manta_Control_SRCS
+       ${Manta_Control_SRCS}
+       Control/DynPLTWorker.h
+       Control/DynPLTWorker.cc
+   )
+ENDIF(BUILD_DYNPLT)

Added: trunk/Engine/Control/DynPLTWorker.cc
==============================================================================
--- (empty file)
+++ trunk/Engine/Control/DynPLTWorker.cc        Mon May 15 23:25:20 2006
@@ -0,0 +1,48 @@
+#include <Engine/Control/DynPLTWorker.h>
+#include <Model/Primitives/DynPLTParticle.h>
+
+// XXX:  temporary
+#include <Core/Math/MT_RNG.h>
+
+#include <iostream>
+#include <unistd.h>
+// end XXX
+
+using namespace Manta;
+
+DynPLTWorker::DynPLTWorker(unsigned int id,
+                           SCIRun::Mailbox<const DynPLTParticle*>* queue) :
+  id(id), queue(queue)
+{
+  // Do nothing
+}
+
+DynPLTWorker::~DynPLTWorker(void)
+{
+  // Do nothing
+}
+
+void DynPLTWorker::run(void)
+{
+  // XXX:  temporary
+  MT_RNG rng;
+  rng.seed_rng(id + 100);
+  // end XXX
+
+  const DynPLTParticle* particle;
+  while (particle=queue->receive()) {
+    if (particle->valid)
+      continue;
+
+    // render particle's texture
+
+    // XXX:  temporary
+    unsigned int time=static_cast<unsigned int>(5*rng.gendrand());
+    // std::cout<<"DynPLTWorker["<<id<<"]::run - sleeping for "<<time<<" 
seconds\n";
+    sleep(time);
+    // std::cout<<"DynPLTWorker["<<id<<"]::run - set particle->valid = 
true!\n";
+    // end XXX
+
+    particle->valid=true;
+  }
+}

Added: trunk/Engine/Control/DynPLTWorker.h
==============================================================================
--- (empty file)
+++ trunk/Engine/Control/DynPLTWorker.h Mon May 15 23:25:20 2006
@@ -0,0 +1,26 @@
+
+#ifndef Manta_Engine_DynPLTWorker_h
+#define Manta_Engine_DynPLTWorker_h
+
+#include <SCIRun/Core/Thread/Mailbox.h>
+#include <SCIRun/Core/Thread/Runnable.h>
+
+namespace Manta
+{
+  class DynPLTParticle;
+
+  class DynPLTWorker : public SCIRun::Runnable
+  {
+  public:
+    DynPLTWorker(unsigned int id, SCIRun::Mailbox<const DynPLTParticle*>* 
queue);
+    virtual ~DynPLTWorker(void);
+    
+    virtual void run(void);
+
+  private:
+    unsigned int id;
+    SCIRun::Mailbox<const DynPLTParticle*>* queue;
+  };
+}
+
+#endif

Modified: trunk/Model/CMakeLists.txt
==============================================================================
--- trunk/Model/CMakeLists.txt  (original)
+++ trunk/Model/CMakeLists.txt  Mon May 15 23:25:20 2006
@@ -35,13 +35,11 @@
              ${Manta_Textures_SRCS}
              )
 
-IF(MODEL_PARTICLENRRD)
+TARGET_LINK_LIBRARIES(Manta_Model Manta_Interface Manta_Core)
+
+IF(BUILD_DYNPLT)
    TARGET_LINK_LIBRARIES(Manta_Model
-                         Manta_Interface
-                        Manta_Core
                         ${FOUND_TEEM_LIB})
-ELSE(MODEL_PARTICLENRRD)
-   TARGET_LINK_LIBRARIES(Manta_Model Manta_Interface Manta_Core)
-ENDIF(MODEL_PARTICLENRRD)
+ENDIF(BUILD_DYNPLT)
 
 

Modified: trunk/Model/Materials/CMakeLists.txt
==============================================================================
--- trunk/Model/Materials/CMakeLists.txt        (original)
+++ trunk/Model/Materials/CMakeLists.txt        Mon May 15 23:25:20 2006
@@ -23,3 +23,11 @@
      Materials/Transparent.cc
      Materials/Transparent.h
      )
+
+IF(BUILD_DYNPLT)
+   SET(Manta_Materials_SRCS
+       ${Manta_Materials_SRCS}
+       Materials/DynPLTMaterial.h
+       Materials/DynPLTMaterial.cc
+   )
+ENDIF(BUILD_DYNPLT)

Added: trunk/Model/Materials/DynPLTMaterial.cc
==============================================================================
--- (empty file)
+++ trunk/Model/Materials/DynPLTMaterial.cc     Mon May 15 23:25:20 2006
@@ -0,0 +1,138 @@
+
+#include <Core/Exceptions/InternalError.h>
+#include <Model/Materials/DynPLTMaterial.h>
+#include <Model/Primitives/DynPLTParticle.h>
+#include <Model/Textures/Constant.h>
+#include <Interface/Light.h>
+#include <Interface/LightSet.h>
+#include <Interface/Primitive.h>
+#include <Interface/RayPacket.h>
+#include <Interface/AmbientLight.h>
+#include <Interface/Context.h>
+#include <Interface/ShadowAlgorithm.h>
+
+using namespace Manta;
+
+DynPLTMaterial::DynPLTMaterial(SCIRun::Mailbox<const DynPLTParticle*>* queue,
+                               const Color& color) :
+  queue(queue)
+{
+  colortex=new Constant<Color>(color);
+}
+
+DynPLTMaterial::DynPLTMaterial(SCIRun::Mailbox<const DynPLTParticle*>* queue,
+                               const Texture<Color>* colorfn) :
+  queue(queue), colortex(colorfn)
+{
+  // Do nothing
+}
+
+DynPLTMaterial::~DynPLTMaterial(void)
+{
+  // Do nothing
+}
+
+void DynPLTMaterial::shade(const RenderContext& context, RayPacket& rays) 
const
+{
+  for (int i=rays.begin(); i<rays.end(); ++i) {
+    // Group rays that hit the same particle to shade with a single texture
+    // status check
+    const Primitive* hit_prim=rays.getHitPrimitive(i);
+    int end=i+1;
+    while (end < rays.end() && rays.getHitPrimitive(end) == hit_prim)
+      end++;
+    RayPacket subPacket(rays, i, end);
+
+    // Shade the subpacket
+    const DynPLTParticle* particle=dynamic_cast<const 
DynPLTParticle*>(hit_prim);
+    if (!particle)
+      throw SCIRun::InternalError("Only DynPLTParticle primitives are 
supported",
+                                  __FILE__, __LINE__);
+
+    if (particle->valid) {
+      // Particle's texture is valid
+      // const DynPLT* plt=particle->getDynPLT();
+      for (int j=subPacket.begin(); j<subPacket.end(); ++j) {
+        // query appropriate texel values in plt
+        // bi-linearly interpolate texel values
+        // set resulting color
+        rays.setColor(j, Color(RGB(1, 0, 0)));
+      }
+    } else {
+      // Particle's texture is invalid
+      if (!particle->requested) {
+        particle->requested=true;
+        queue->send(particle);
+      }
+
+      // Use Lambertian shading while texture is invalid
+      lambertianShade(context, subPacket);
+    }
+    
+    i=end;
+  }
+}
+
+void DynPLTMaterial::lambertianShade(const RenderContext& context,
+                                     RayPacket& rays) const
+{
+  // Shade a bunch of rays that have intersected the same particle
+
+  // Compute normals
+  rays.computeNormals(context);
+
+  // Compute colors
+  Packet<Color> diffuse;
+  colortex->mapValues(diffuse, context, rays);
+
+  // Compute ambient contributions for all rays
+  ColorArray totalLight;
+  activeLights->getAmbientLight()->computeAmbient(context, rays, totalLight);
+
+  // Normalize directions for proper dot product computation
+  rays.normalizeDirections();
+
+  ShadowAlgorithm::StateBuffer stateBuffer;
+  bool firstTime=true;
+  bool done;
+  int count=0;
+  do {
+    RayPacketData shadowData;
+    RayPacket shadowRays(shadowData, RayPacket::UnknownShape, 0, 0,
+                         rays.getDepth(), 0);
+
+    // Call the shadow algorithm (SA) to generate shadow rays.  We may not be
+    // able to compute all of them, so we pass along a buffer for the SA
+    // object to store it's state.  The firstTime flag tells the SA to fill
+    // in the state rather than using anything in the state buffer.  Most
+    // SAs will only need to store an int or two in the statebuffer.
+    done=context.shadowAlgorithm->computeShadows(context, activeLights,
+                                                 rays, shadowRays,
+                                                 firstTime, stateBuffer);
+
+    // Normalize directions for proper dot product computation
+    shadowRays.normalizeDirections();
+
+    for (int i=shadowRays.begin(); i < shadowRays.end(); ++i) {
+      if (!shadowRays.wasHit(i)) {
+        // Not in shadow, so compute the direct and specular contributions
+        Vector normal=rays.getNormal(i);
+        Vector shadowdir=shadowRays.getDirection(i);
+        ColorComponent cos_theta=Dot(shadowdir, normal);
+        Color light=shadowRays.getColor(i);
+        for (int j=0; j < Color::NumComponents; ++j)
+          totalLight[j][i] += light[j]*cos_theta;
+      }
+    }
+
+    firstTime=false;
+  } while(!done);
+
+  // Sum up diffuse/specular contributions
+  for (int i=rays.begin(); i < rays.end(); ++i) {
+    Color result;
+    for (int j=0;j<Color::NumComponents; ++j)
+      result[j]=totalLight[j][i] * diffuse.colordata[j][i];
+    rays.setColor(i, result);
+  }
+}

Added: trunk/Model/Materials/DynPLTMaterial.h
==============================================================================
--- (empty file)
+++ trunk/Model/Materials/DynPLTMaterial.h      Mon May 15 23:25:20 2006
@@ -0,0 +1,32 @@
+
+#ifndef Manta_Model_DynPLTMaterial_h
+#define Manta_Model_DynPLTMaterial_h
+
+#include <Core/Color/Color.h>
+#include <Interface/Texture.h>
+#include <Model/Materials/LitMaterial.h>
+#include <Model/Primitives/DynPLTParticle.h>
+#include <SCIRun/Core/Thread/Mailbox.h>
+
+namespace Manta 
+{
+  class DynPLTMaterial : public LitMaterial
+  {
+  public:
+    DynPLTMaterial(SCIRun::Mailbox<const DynPLTParticle*>* queue,
+                   const Color& color);
+    DynPLTMaterial(SCIRun::Mailbox<const DynPLTParticle*>* queue,
+                   const Texture<Color>* colorfn);
+    virtual ~DynPLTMaterial(void);
+    
+    virtual void shade(const RenderContext& context, RayPacket& rays) const;
+    
+  private:
+    SCIRun::Mailbox<const DynPLTParticle*>* queue;
+    const Texture<Color>* colortex;
+
+    void lambertianShade(const RenderContext& context, RayPacket& rays) 
const;
+  };
+}
+
+#endif

Modified: trunk/Model/Primitives/CMakeLists.txt
==============================================================================
--- trunk/Model/Primitives/CMakeLists.txt       (original)
+++ trunk/Model/Primitives/CMakeLists.txt       Mon May 15 23:25:20 2006
@@ -40,3 +40,9 @@
      Primitives/WaldTriangle.cc
 )
 
+IF(BUILD_DYNPLT)
+   SET(Manta_Primitives_SRCS
+       ${Manta_Primitives_SRCS}
+       Primitives/DynPLTParticle.h
+   )
+ENDIF(BUILD_DYNPLT)

Added: trunk/Model/Primitives/DynPLTParticle.h
==============================================================================
--- (empty file)
+++ trunk/Model/Primitives/DynPLTParticle.h     Mon May 15 23:25:20 2006
@@ -0,0 +1,51 @@
+
+#ifndef Manta_Model_DynPLTParticle_h
+#define Manta_Model_DynPLTParticle_h
+
+// #include <Core/Color/GrayColor.h>
+#include <Model/Primitives/Sphere.h>
+// #include <Model/Textures/ImageTexture.h>
+
+namespace Manta {
+  // XXX:  where does this go?
+  class DynPLT
+  {
+  public:
+    DynPLT(unsigned int res=16);
+
+  private:
+    // ImageTexture<GrayColor> texture;
+    // ImageTexture<GrayColor> inside;
+
+    void dilate(void);
+  };
+  // end XXX
+
+  class DynPLTParticle : public Sphere
+  {
+  public:
+    DynPLTParticle(Material* material, const Vector& center, Real radius) :
+      Sphere(material, center, radius), valid(false), requested(false)
+    {
+      // Do nothing
+    }
+
+    virtual ~DynPLTParticle(void)
+    {
+      // Do nothing
+    }
+
+    const DynPLT* getDynPLT(void) const
+    {
+      return plt;
+    }
+
+    mutable bool valid;
+    mutable bool requested;
+
+  private:
+    DynPLT* plt;
+  };
+}
+
+#endif

Modified: trunk/Model/Primitives/Sphere.h
==============================================================================
--- trunk/Model/Primitives/Sphere.h     (original)
+++ trunk/Model/Primitives/Sphere.h     Mon May 15 23:25:20 2006
@@ -20,7 +20,7 @@
                                   RayPacket& rays) const;
     virtual void computeTexCoords3(const RenderContext& context,
                                   RayPacket& rays) const;
-  private:
+  protected:
     Vector center;
     Real radius;
     Real inv_radius;

Modified: trunk/Model/Readers/CMakeLists.txt
==============================================================================
--- trunk/Model/Readers/CMakeLists.txt  (original)
+++ trunk/Model/Readers/CMakeLists.txt  Mon May 15 23:25:20 2006
@@ -20,12 +20,11 @@
 ENDIF (APPLE)
 
 # Reader for NRRD particle data
-SET(MODEL_PARTICLENRRD 0 CACHE BOOL "NRRD particle data reader")
-IF(MODEL_PARTICLENRRD)
+IF(BUILD_NRRDPARTICLES)
    SET(Manta_Readers_SRCS
        ${Manta_Readers_SRCS}
        Readers/ParticleNRRD.h
        Readers/ParticleNRRD.cc
    )
    INCLUDE_DIRECTORIES(${FOUND_TEEM_INCLUDE})
-ENDIF(MODEL_PARTICLENRRD)
+ENDIF(BUILD_NRRDPARTICLES)

Modified: trunk/scenes/CMakeLists.txt
==============================================================================
--- trunk/scenes/CMakeLists.txt (original)
+++ trunk/scenes/CMakeLists.txt Mon May 15 23:25:20 2006
@@ -85,8 +85,13 @@
 ENDIF(SCENE_PERF)
 
 # Viewer for NRRD particle datasets
-SET(SCENE_PARTICLEREADER 0 CACHE BOOL "NRRD particle data viewer")
-IF(SCENE_PARTICLEREADER)
+IF(BUILD_NRRDPARTICLES)
    ADD_LIBRARY(scene_pnrrd pnrrd.cc)
    TARGET_LINK_LIBRARIES(scene_pnrrd ${MANTA_SCENE_LINK})
-ENDIF(SCENE_PARTICLEREADER)
+ENDIF(BUILD_NRRDPARTICLES)
+
+# Lazily evaluated PLTs for NRRD particle datasets
+IF(BUILD_DYNPLT)
+   ADD_LIBRARY(scene_dynplt dynplt.cc)
+   TARGET_LINK_LIBRARIES(scene_dynplt ${MANTA_SCENE_LINK})
+ENDIF(BUILD_DYNPLT)

Added: trunk/scenes/dynplt.cc
==============================================================================
--- (empty file)
+++ trunk/scenes/dynplt.cc      Mon May 15 23:25:20 2006
@@ -0,0 +1,114 @@
+#include <Core/Exceptions/IllegalArgument.h>
+#include <Core/Geometry/Vector.h>
+#include <Core/Util/Args.h>
+#include <Engine/Control/DynPLTWorker.h>
+#include <Interface/Context.h>
+#include <Interface/LightSet.h>
+#include <Interface/MantaInterface.h>
+#include <Interface/Scene.h>
+#include <Model/AmbientLights/ConstantAmbient.h>
+#include <Model/Backgrounds/ConstantBackground.h>
+#include <Model/Groups/Group.h>
+#include <Model/Lights/PointLight.h>
+#include <Model/Materials/DynPLTMaterial.h>
+#include <Model/Primitives/DynPLTParticle.h>
+#include <Model/Readers/ParticleNRRD.h>
+#include <SCIRun/Core/Thread/Mailbox.h>
+#include <SCIRun/Core/Thread/Thread.h>
+
+#include <sgi_stl_warnings_off.h>
+#include <iostream>
+#include <sgi_stl_warnings_on.h>
+
+using namespace Manta;
+using namespace SCIRun;
+using namespace std;
+
+extern "C"
+Scene* make_scene(ReadContext const& context, vector<string> const& args)
+{
+  Group* world=0;
+  string fname="";
+  int nthreads=1;
+  double radius=1.;
+  int ridx=-1;
+
+  int argc=static_cast<int>(args.size());
+  for(int i=0; i<argc; ++i) {
+    string arg=args[i];
+    if (arg=="-bv") {
+      string s;
+      if (!getStringArg(i, args, s))
+        throw IllegalArgument("scene dynplt -bv", i, args);
+      world=context.manta_interface->makeGroup(s);
+    } else if (arg=="-i") {
+      if (!getStringArg(i, args, fname))
+        throw IllegalArgument("scene dynplt -i", i, args);
+    } else if (arg=="-nthreads") {
+      if (!getIntArg(i, args, nthreads))
+        throw IllegalArgument("scene dynplt -nthreads", i, args);
+    } else if (arg=="-radius") {
+      if (!getDoubleArg(i, args, radius))
+        throw IllegalArgument("scene dynplt -radius", i, args);
+    } else if (arg=="-ridx") {
+      if (!getIntArg(i, args, ridx))
+        throw IllegalArgument("scene dynplt -ridx", i, args);
+    } else {
+      cerr<<"Valid options for scene dynplt:\n";
+      cerr<<"  -bv <string>      bounding volume {bvh|grid|group}\n";
+      cerr<<"  -i <string>       particle data filename\n";
+      cerr<<"  -nthreads <int>   number of dynplt workers\n";
+      cerr<<"  -radius <float>   particle radius\n";
+      cerr<<"  -ridx <int>       radius index\n";
+      throw IllegalArgument("scene dynplt", i, args);
+    }
+  }
+
+  if (!world)
+    world=new Group();
+
+  // Create DynPLT work queue
+  unsigned int size=100; // XXX:  this should be a cmdln parameter
+  Mailbox<const DynPLTParticle*>* queue=new Mailbox<const 
DynPLTParticle*>("DynPLT Work Queue", size);
+  
+  // Create DynPLTWorker threads
+  // workers.resize(nthreads);
+  for (unsigned int i=0; i<nthreads; ++i) {
+    ostringstream name;
+    name<<"DynPLTWorker "<<i;
+    Thread* thread=new Thread(new DynPLTWorker(i, queue), 
name.str().c_str(), 0,
+                              Thread::NotActivated);
+    // thread->setStackSize(RENDER_THREAD_STACKSIZE);
+    thread->activate(false);
+    // workers[i]=thread;
+  }
+
+  // Load particle data, add particles to group
+  ParticleNRRD pnrrd(fname);
+  float* pdata=pnrrd.getParticleData();
+  for (unsigned int i=0; i<pnrrd.getNParticles(); ++i) {
+    unsigned int idx=i*pnrrd.getNVars();
+    Real x=pdata[idx];
+    Real y=pdata[idx + 1];
+    Real z=pdata[idx + 2];
+    
+    if (ridx>=0)
+      radius=pdata[idx + ridx];
+    
+    Material* dynplt=new DynPLTMaterial(queue, Color(RGB(1, 1, 1)));
+    world->add(new DynPLTParticle(dynplt, Vector(x, y, z), radius));
+  }
+
+  // Create scene
+  Scene* scene=new Scene();
+  scene->setBackground(new ConstantBackground(Color(RGB(0, 0, 0))));
+  scene->setObject(world);
+
+  // Add lights
+  LightSet* lights=new LightSet();
+  lights->add(new PointLight(Vector(1, 1, 1), Color(RGB(1, 1, 1))));
+  lights->setAmbientLight(new ConstantAmbient(Color(RGB(0.4, 0.4, 0.4))));
+  scene->setLights(lights);
+
+  return scene;
+}




  • [MANTA] r1061 - in trunk: . Engine/Control Model Model/Materials Model/Primitives Model/Readers scenes, cgribble, 05/15/2006

Archive powered by MHonArc 2.6.16.

Top of page