Text archives Help
- From: cgribble@sci.utah.edu
- To: manta@sci.utah.edu
- Subject: [MANTA] r1201 - in trunk: . Core Core/Util Engine/Control Engine/Renderers Interface StandAlone include
- Date: Tue, 3 Oct 2006 12:25:08 -0600 (MDT)
Author: cgribble
Date: Tue Oct 3 12:25:04 2006
New Revision: 1201
Added:
trunk/Core/Util/RayDump.cc
trunk/Core/Util/RayDump.h
trunk/include/UseRayDump.h.CMakeTemplate
Modified:
trunk/CMakeLists.txt
trunk/Core/CMakeLists.txt
trunk/Engine/Control/RTRT.cc
trunk/Engine/Control/RTRT.h
trunk/Engine/Renderers/Raytracer.cc
trunk/Interface/Context.h
trunk/Interface/MantaInterface.h
trunk/StandAlone/manta.cc
trunk/include/CMakeLists.txt
Log:
Added preliminary code to dump ray trees using Solomon's PABST format.
Configure
with CMake and turn the USE_RAYDUMP option on. Then invoke as follows:
bin/manta -np 1 -raydump <filename> [other options here]
Note that the RayDump data structures are not yet thread-safe, so you'll need
to
run with a single thread for now. (It'll check/reset the number of threads if
you forget.)
Only primary rays from the Raytracer renderer are dumped for the moment. It's
probably going to be non-trivial to dump a properly structured tree with
unique
ray IDs across packets, proper parent IDs, etc., but this code is a start.
If it
turns out not to work like I think it should (unfortunately Solomon's PABST
example code is even more trivial than what I've implemented here), it should
be
relatively straight-forward to rip out the new functionality.
Modified: trunk/CMakeLists.txt
==============================================================================
--- trunk/CMakeLists.txt (original)
+++ trunk/CMakeLists.txt Tue Oct 3 12:25:04 2006
@@ -48,6 +48,7 @@
FORCE_ADD_CXX_FLAGS("-DSCI_NOPERSISTENT")
SET(USE_STATS_COLLECTOR 0 CACHE BOOL "Enable stats collection")
+SET(USE_RAYDUMP 0 CACHE BOOL "Enable ray dump")
###############################################################################
###############################################################################
Modified: trunk/Core/CMakeLists.txt
==============================================================================
--- trunk/Core/CMakeLists.txt (original)
+++ trunk/Core/CMakeLists.txt Tue Oct 3 12:25:04 2006
@@ -62,6 +62,11 @@
Util/ThreadStorage.h
Util/ThreadStorage.cc)
+IF(${USE_RAYDUMP_DEF})
+ SET(CORE_SOURCES ${CORE_SOURCES}
+ Util/RayDump.h
+ Util/RayDump.cc)
+ENDIF(${USE_RAYDUMP_DEF})
ADD_LIBRARY (Manta_Core ${CORE_SOURCES})
Added: trunk/Core/Util/RayDump.cc
==============================================================================
--- (empty file)
+++ trunk/Core/Util/RayDump.cc Tue Oct 3 12:25:04 2006
@@ -0,0 +1,143 @@
+#include <Core/Util/RayDump.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+// TODO: replace the fwrite, fread with EndianSafe versions
+
+RayForest::~RayForest()
+{
+ // printf("RayForest::~RayForest - deleting trees\n");
+ for (size_t i = 0; i < trees.size(); i++ )
+ delete trees[i];
+}
+
+void RayForest::addChild(RayTree* t)
+{
+ trees.push_back(t);
+}
+
+bool RayForest::writeToFile(FILE* output) const
+{
+ // printf("RayForest::writeToFile() -- %d Children\n", int(trees.size()));
+ // write out the number of trees then
+ // call writeToFile recursively
+ size_t num_trees = trees.size();
+ size_t result = fwrite((const void*)&num_trees, sizeof(size_t), (size_t)1,
output);
+ if (result != 1)
+ {
+ return false;
+ }
+
+ for (size_t i = 0; i < num_trees; i++ )
+ {
+ // printf("RayForest::writeToFile() -- Writing Tree #%d\n", int(i));
+ if (!trees[i]->writeToFile(output))
+ return false;
+ }
+ return true;
+}
+
+bool RayForest::readFromFile(FILE* input)
+{
+ size_t num_trees = 0;
+ size_t result = fread((void*)&num_trees, sizeof(size_t), (size_t)1, input);
+ if (result != 1)
+ return false;
+
+ trees.resize(num_trees);
+ for (size_t i = 0; i < num_trees; i++ )
+ {
+ trees[i] = new RayTree;
+ if (!trees[i]->readFromFile(input))
+ return false;
+ }
+ return true;
+}
+
+RayTree::~RayTree()
+{
+ // printf("RayTree::~RayTree - deleting children\n");
+ for (size_t i = 0; i < children.size(); i++ )
+ delete children[i];
+}
+
+bool RayTree::writeToFile(FILE* output) const
+{
+ // printf("RayTree::writeToFile() -- %d Children\n", int(children.size()));
+ node.writeToFile(output);
+ // write out the number of children then
+ // call writeToFile recursively
+ size_t num_children = children.size();
+ size_t result = fwrite((const void*)&num_children, sizeof(size_t),
(size_t)1, output);
+ if (result != 1)
+ {
+ fprintf(stderr, "Failed to write number of children\n");
+ return false;
+ }
+
+ for (size_t i = 0; i < num_children; i++)
+ {
+ // printf("RayTree::writeToFile() -- Writing Child %d\n", int(i));
+ if (!children[i]->writeToFile(output))
+ return false;
+ }
+
+ return true;
+}
+
+bool RayTree::readFromFile(FILE* input)
+{
+ node.readFromFile(input);
+
+ size_t num_children = 0;
+ size_t result = fread((void*)&num_children, sizeof(size_t), (size_t)1,
input);
+ if (result != 1)
+ {
+ fprintf(stderr, "Failed to read number of children\n");
+ return false;
+ }
+
+ children.resize(num_children);
+
+ for (size_t i = 0; i < num_children; i++ )
+ {
+ children[i] = new RayTree;
+ if (!children[i]->readFromFile(input))
+ return false;
+ }
+ return true;
+}
+
+void RayTree::addChild(RayTree* t)
+{
+ children.push_back(t);
+}
+
+bool RayInfo::writeToFile(FILE* output) const
+{
+ // printf("RayInfo::writeToFile() -- Writing 4 Byte Values\n");
+ size_t result = fwrite((const void*)origin, (size_t)4,
(size_t)RayInfo::Num4Byte, output);
+ if (result != Num4Byte)
+ return false;
+
+ // printf("RayInfo::writeToFile() -- Writing %d Byte Values\n",
int(sizeof(long long int)));
+ result = fwrite((const void*)&ray_id, (size_t)8,
(size_t)RayInfo::Num8Byte, output);
+ if (result != Num8Byte)
+ return false;
+
+ return true;
+}
+
+bool RayInfo::readFromFile(FILE* input)
+{
+ size_t result = fread((void*)origin, (size_t)4, (size_t)RayInfo::Num4Byte,
input);
+ if (result != Num4Byte)
+ return false;
+
+ result = fread((void*)&ray_id, sizeof(long long int),
(size_t)RayInfo::Num8Byte, input);
+ if (result != Num8Byte)
+ return false;
+
+ return true;
+}
Added: trunk/Core/Util/RayDump.h
==============================================================================
--- (empty file)
+++ trunk/Core/Util/RayDump.h Tue Oct 3 12:25:04 2006
@@ -0,0 +1,133 @@
+#ifndef Manta_Core_RayDump_h
+#define Manta_Core_RayDump_h
+
+#include <stdio.h>
+#include <vector>
+
+class RayInfo
+{
+public:
+ RayInfo(){}
+
+ enum RayType
+ {
+ PrimaryRay = 0,
+ ShadowRay,
+ ReflectionRay,
+ RefractionRay,
+ DiffuseRay
+ };
+
+ // 4 byte entries
+ float origin[3]; // ray origin
+ float direction[3]; // ray direction
+ float time; // motion blur time [0,1)
+ float HitParameter; // ray t_value at hitpoint
+ int object_id; // object you hit, -1 for background
+ int material_id; // material of object, -1 for background
+ float s,t; // surface parameterization
+ // normal? ONB?
+ int depth; // bounce depth for reconstructing trees
+ RayType type;
+
+ // 8 byte entries
+ long long int ray_id; // unique ray identifier
+ long long int parent_id; // -1 for root
+
+ static const int Num4Byte = 14;
+ static const int Num8Byte = 2;
+
+ bool writeToFile(FILE* output) const;
+ bool readFromFile(FILE* input);
+
+ void setOrigin(float x, float y, float z)
+ {
+ origin[0] = x;
+ origin[1] = y;
+ origin[2] = z;
+ }
+
+ void setDirection(float x, float y, float z)
+ {
+ direction[0] = x;
+ direction[1] = y;
+ direction[2] = z;
+ }
+
+ void setTime(float t)
+ {
+ time = t;
+ }
+
+ void setHit(float t)
+ {
+ HitParameter = t;
+ }
+
+ void setObjectID(int i)
+ {
+ object_id = i;
+ }
+
+ void setMaterialID(int i)
+ {
+ material_id = i;
+ }
+
+ void setSurfaceParams(float u, float v)
+ {
+ s = u;
+ t = v;
+ }
+
+ void setDepth(int d)
+ {
+ depth = d;
+ }
+
+ void setType(RayType t)
+ {
+ type = t;
+ }
+
+ void setRayID(long long int id)
+ {
+ ray_id = id;
+ }
+
+ void setParentID(long long int id)
+ {
+ parent_id = id;
+ }
+};
+
+class RayTree
+{
+public:
+ RayTree() {}
+ RayTree(const RayInfo& r) : node(r) {}
+ ~RayTree();
+
+ void addChild(RayTree* t);
+ bool writeToFile(FILE* output) const;
+ bool readFromFile(FILE* input);
+
+ RayInfo node;
+ std::vector<RayTree*> children;
+};
+
+// a forest is a collection of Trees ;)
+class RayForest
+{
+public:
+ RayForest() {}
+ ~RayForest();
+
+ void addChild(RayTree* t);
+ bool writeToFile(FILE* output=0) const;
+ bool readFromFile(FILE* input=0);
+
+ std::vector<RayTree*> trees;
+};
+
+#endif // Manta_Core_RayDump_h
Modified: trunk/Engine/Control/RTRT.cc
==============================================================================
--- trunk/Engine/Control/RTRT.cc (original)
+++ trunk/Engine/Control/RTRT.cc Tue Oct 3 12:25:04 2006
@@ -458,11 +458,19 @@
if(!firstFrame){
for(int index = 0;index < static_cast<int>(channels.size());index++){
Channel* channel = channels[index];
+#ifdef USE_RAYDUMP
+ if (!forest)
+ cerr<<"1 Setting forest too late!\n";
+#endif
RenderContext myContext(this, index, proc, workersAnimAndImage,
&animFrameState,
currentLoadBalancer, currentPixelSampler,
currentRenderer, currentShadowAlgorithm,
- channel->camera, scene, thread_storage );
+ channel->camera, scene, thread_storage
+#ifdef USE_RAYDUMP
+ , forest
+#endif
+ );
currentImageTraverser->setupFrame(myContext);
}
}
@@ -546,6 +554,10 @@
for(int index = 0;index < static_cast<int>(channels.size());index++){
Channel* channel = channels[index];
+#ifdef USE_RAYDUMP
+ if (!forest)
+ cerr<<"2 Setting forest too late!\n";
+#endif
RenderContext myContext(this, index, proc, workersAnimAndImage,
&animFrameState,
currentLoadBalancer,
@@ -554,7 +566,11 @@
currentShadowAlgorithm,
channel->camera,
scene,
- thread_storage );
+ thread_storage
+#ifdef USE_RAYDUMP
+ , forest
+#endif
+ );
currentImageTraverser->setupFrame(myContext);
}
@@ -615,10 +631,18 @@
Channel* channel = channels[index];
long renderFrame =
renderFrameState.frameNumber%channel->pipelineDepth;
Image* image = channel->images[renderFrame];
+#ifdef USE_RAYDUMP
+ if (!forest)
+ cerr<<"3 Setting forest too late!\n";
+#endif
RenderContext myContext(this, index, proc, workersRendering,
&renderFrameState,
currentLoadBalancer, currentPixelSampler,
currentRenderer, currentShadowAlgorithm,
- channel->camera, scene, thread_storage );
+ channel->camera, scene, thread_storage
+#ifdef USE_RAYDUMP
+ , forest
+#endif
+ );
currentImageTraverser->renderImage(myContext, image);
}
}
@@ -1564,7 +1588,11 @@
RenderContext render_context(this, channel_index, workersRendering, 0, 0,
currentLoadBalancer, currentPixelSampler,
currentRenderer, currentShadowAlgorithm,
- channel->camera, scene, thread_storage );
+ channel->camera, scene, thread_storage
+#ifdef USE_RAYDUMP
+ , forest
+#endif
+ );
// Send this to the renderer.
currentRenderer->traceEyeRays( render_context, result_rays );
Modified: trunk/Engine/Control/RTRT.h
==============================================================================
--- trunk/Engine/Control/RTRT.h (original)
+++ trunk/Engine/Control/RTRT.h Tue Oct 3 12:25:04 2006
@@ -12,6 +12,7 @@
#include <Core/Thread/Semaphore.h>
#include <Core/Thread/Runnable.h>
#include <Core/Util/ThreadStorage.h>
+#include <UseRayDump.h>
#include <map>
#include <set>
#include <vector>
@@ -149,8 +150,11 @@
// Transactions
virtual void addTransaction(TransactionBase* );
- // Debug:
- virtual void shootOneRay( Color &result_color, RayPacket
&result_rays, Real image_x, Real image_y, int channel_index );
+ // Debug
+#ifdef USE_RAYDUMP
+ void setRayForest(RayForest* forest_) { forest = forest_; }
+#endif
+ virtual void shootOneRay( Color &result_color, RayPacket &result_rays,
Real image_x, Real image_y, int channel_index );
protected:
@@ -322,6 +326,10 @@
Scene* readMOScene(const string& name, const vector<string>& args,
bool printErrors);
void readMOStack(const string& name, const vector<string>& args, bool
printErrors );
+
+#ifdef USE_RAYDUMP
+ RayForest* forest;
+#endif
};
}
Modified: trunk/Engine/Renderers/Raytracer.cc
==============================================================================
--- trunk/Engine/Renderers/Raytracer.cc (original)
+++ trunk/Engine/Renderers/Raytracer.cc Tue Oct 3 12:25:04 2006
@@ -8,6 +8,7 @@
#include <Interface/RayPacket.h>
#include <Interface/Scene.h>
#include <Core/Util/Assert.h>
+#include <UseRayDump.h>
#include <iostream>
using namespace std;
@@ -42,6 +43,23 @@
{
ASSERT(rays.getFlag(RayPacket::HaveImageCoordinates));
context.camera->makeRays(rays);
+#ifdef USE_RAYDUMP
+ int size=rays.end() - rays.begin();
+ for (int i = rays.begin(); i<rays.end(); ++i) {
+ Vector origin=rays.getOrigin(i);
+ Vector direction=rays.getDirection(i);
+ // How to get a unique id for each ray across packets?
+ long long int id=context.forest->trees.size();
+
+ RayInfo ray;
+ ray.setOrigin(origin.x(), origin.y(), origin.z());
+ ray.setDirection(direction.x(), direction.y(), direction.z());
+ ray.setRayID(id);
+ ray.setParentID(static_cast<long long int>(-1));
+ ray.type = RayInfo::PrimaryRay;
+ context.forest->addChild(new RayTree(ray));
+ }
+#endif
rays.initializeImportance();
traceRays(context, rays);
}
Modified: trunk/Interface/Context.h
==============================================================================
--- trunk/Interface/Context.h (original)
+++ trunk/Interface/Context.h Tue Oct 3 12:25:04 2006
@@ -2,6 +2,8 @@
#ifndef Manta_Interface_Context_h
#define Manta_Interface_Context_h
+#include <UseRayDump.h>
+
namespace Manta {
class Camera;
class FrameState;
@@ -154,7 +156,11 @@
LoadBalancer* loadBalancer, PixelSampler* pixelSampler,
Renderer* renderer, ShadowAlgorithm* shadowAlgorithm,
const Camera* camera, const Scene* scene,
- ThreadStorage *storage_allocator_ )
+ ThreadStorage *storage_allocator_
+#ifdef USE_RAYDUMP
+ , RayForest* forest_ = 0
+#endif
+ )
: rtrt_int(rtrt_int), channelIndex(channelIndex),
proc(proc), numProcs(numProcs),
frameState(frameState),
@@ -162,6 +168,9 @@
renderer(renderer), shadowAlgorithm(shadowAlgorithm),
camera(camera), scene(scene),
storage_allocator( storage_allocator_ )
+#ifdef USE_RAYDUMP
+ , forest(forest_)
+#endif
{
}
MantaInterface* rtrt_int;
@@ -177,7 +186,11 @@
const Scene* scene;
mutable ThreadStorage *storage_allocator;
-
+
+#ifdef USE_RAYDUMP
+ RayForest* forest;
+#endif
+
private:
RenderContext(const RenderContext&);
RenderContext& operator=(const RenderContext&);
Modified: trunk/Interface/MantaInterface.h
==============================================================================
--- trunk/Interface/MantaInterface.h (original)
+++ trunk/Interface/MantaInterface.h Tue Oct 3 12:25:04 2006
@@ -6,6 +6,7 @@
#include <Interface/Transaction.h>
#include <SCIRun/Core/Exceptions/Exception.h>
#include <MantaTypes.h>
+#include <UseRayDump.h>
#include <sgi_stl_warnings_off.h>
#include <string>
@@ -184,9 +185,12 @@
}
// Others
+#ifdef USE_RAYDUMP
+ virtual void setRayForest(RayForest* /* forest */) {}
+#endif
// This should only be called from within a transaction called function.
- virtual void shootOneRay( Color &result_color, RayPacket
&result_rays, Real image_x, Real image_y, int channel_index ) = 0;
+ virtual void shootOneRay( Color &result_color, RayPacket &result_rays,
Real image_x, Real image_y, int channel_index ) = 0;
protected:
MantaInterface();
Modified: trunk/StandAlone/manta.cc
==============================================================================
--- trunk/StandAlone/manta.cc (original)
+++ trunk/StandAlone/manta.cc Tue Oct 3 12:25:04 2006
@@ -42,6 +42,7 @@
#include <Core/Exceptions/UnknownComponent.h>
#include <Core/Thread/Time.h>
#include <Core/Util/About.h>
+#include <UseRayDump.h>
// Default scene includes.
#include <Core/Color/ColorDB.h>
@@ -132,6 +133,9 @@
cerr << " -renderer S - Use renderer S, valid renderers are:\n";
printList(cerr, rtrt->listRenderers(), 2);
cerr << " -scene S - Render Scene S\n";
+#ifdef USE_RAYDUMP
+ cerr << " -raydump F - Dump ray trees to file F\n";
+#endif
Thread::exitAll(1);
}
@@ -173,6 +177,62 @@
delete this;
}
+#ifdef USE_RAYDUMP
+class RayDumpHelper {
+public:
+ RayDumpHelper(MantaInterface* rtrt, const string& fname);
+ void init(int, int);
+ void quit(int, int);
+
+private:
+ MantaInterface* rtrt;
+ FILE* raydump_fp;
+ RayForest* forest;
+};
+
+RayDumpHelper::RayDumpHelper(MantaInterface* rtrt, const string& fname)
+ : rtrt(rtrt)
+{
+ // cerr<<"raydump_file = "<<fname<<'\n';
+ raydump_fp = fopen(fname.c_str(), "wb");
+ if (!raydump_fp) {
+ cerr<<"Warning: failed to open \""<<fname<<"\" for writing\n";
+ raydump_fp = 0;
+ return;
+ }
+
+ cerr<<"Dumping ray trees to \""<<fname<<"\"\n";
+}
+
+void RayDumpHelper::init(int, int)
+{
+ if (raydump_fp) {
+ if (rtrt->numWorkers() > 1) {
+ cerr<<"Warning: dumping ray trees is not currently thread-safe\n";
+ cerr<<" changing number of workers to one\n";
+ rtrt->changeNumWorkers(1);
+ }
+
+ // cerr<<"creating a new forest\n";
+ forest = new RayForest();
+ rtrt->setRayForest(forest);
+ }
+}
+
+void RayDumpHelper::quit(int, int)
+{
+ if (forest && raydump_fp) {
+ // cerr<<"dumping ray trees and cleaning up\n";
+ forest->writeToFile(raydump_fp);
+ fclose(raydump_fp);
+ delete forest;
+ }
+
+ rtrt->finish();
+ delete this;
+}
+#endif
+
int
main(int argc, char* argv[])
{
@@ -348,6 +408,20 @@
if (!getStringArg(i,args,stack_file))
usage(rtrt);
}
+#ifdef USE_RAYDUMP
+ else if (arg == "-raydump") {
+ string raydump_file;
+ if (!getStringArg(i, args, raydump_file))
+ usage(rtrt);
+
+ // If dumping rays, run for exactly one frame
+ RayDumpHelper* r = new RayDumpHelper(rtrt, raydump_file);
+ rtrt->addOneShotCallback(MantaInterface::Absolute, 0,
+ Callback::create(r,
&RayDumpHelper::init));
+ rtrt->addOneShotCallback(MantaInterface::Absolute, 1,
+ Callback::create(r,
&RayDumpHelper::quit));
+ }
+#endif
}
}
catch (SCIRun::Exception& e) {
Modified: trunk/include/CMakeLists.txt
==============================================================================
--- trunk/include/CMakeLists.txt (original)
+++ trunk/include/CMakeLists.txt Tue Oct 3 12:25:04 2006
@@ -66,3 +66,17 @@
${CMAKE_CURRENT_SOURCE_DIR}/UseStatsCollector.h.CMakeTemplate
${CMAKE_BINARY_DIR}/include/UseStatsCollector.h
)
+
+###############################################################################
+## Configure UseRayDump.h
+
+IF(USE_RAYDUMP)
+ SET(USE_RAYDUMP_DEF "1" CACHE INTERNAL "Enable stats collection")
+ELSE(USE_RAYDUMP)
+ SET(USE_RAYDUMP_DEF "0" CACHE INTERNAL "Disable stats collection")
+ENDIF(USE_RAYDUMP)
+
+CONFIGURE_FILE(
+ ${CMAKE_CURRENT_SOURCE_DIR}/UseRayDump.h.CMakeTemplate
+ ${CMAKE_BINARY_DIR}/include/UseRayDump.h
+ )
Added: trunk/include/UseRayDump.h.CMakeTemplate
==============================================================================
--- (empty file)
+++ trunk/include/UseRayDump.h.CMakeTemplate Tue Oct 3 12:25:04 2006
@@ -0,0 +1,38 @@
+/*
+ For more information, please see:
http://software.sci.utah.edu
+
+ The MIT License
+
+ Copyright (c) 2005-2006
+ Scientific Computing and Imaging Institute, University of Utah
+
+ License for the specific language governing rights and limitations under
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef Manta_UseRayDump_h
+#define Manta_UseRayDump_h
+
+#if ${USE_RAYDUMP_DEF}
+#define USE_RAYDUMP 1
+
+#include <Core/Util/RayDump.h>
+#endif
+
+#endif
- [MANTA] r1201 - in trunk: . Core Core/Util Engine/Control Engine/Renderers Interface StandAlone include, cgribble, 10/03/2006
Archive powered by MHonArc 2.6.16.