Sep 7, 2013

Discover & Fix operators

Here is a beginners tutorial to start to learn how to modify or add werkkzeug operators.
This tutorial assumes you have a good knowledge of C++ and you know how to use Visual Studio.

For this first lesson, I chose to study a concrete case and l'll start by jumping right into the code to fix an operator that do not work as expected.

Target : I noticed that the Print operator (wz4Render type, yellow operators) does not take care of its scale, rotation and translate parameters.

So let's go to fix that.


The first thing to do is to find the code of this operator. This is not so easy in the beginning and the best way to do that is to use the search engine of visual studio and specify to search only in *.ops files.

ops file is written in a scripting language, specific to werkkzeug, used to declare all werkkzeug operators. It contains the operators descriptions, their parameters and a C++ code part. This facilitate the development because from this script, the compilation will generate plenty of real C++ code boring to write concerning definition of all variables/functions needed to bind operators to the werkkzeug engine and GUI. With this system, add a new operator is very easy, you just need to write its description in script language and the rest is done automatically.

In visual studio, use "Search in files" and in the search options specify only *.ops files. Entering operator name (here "print") is a good keyword to search, but as you know print is very common. So there is better, using the regular expression (check the option) and search for the next string :

^operator.*print.*$
 
It will returns 2 results :

C:\github\werkkzeug4\wz4\wz4frlib\tron_ops.ops(586):operator Wz4Mesh TronPrint(?*GenBitmap)  
C:\github\werkkzeug4\wz4\wz4frlib\chaosfx_ops.ops(52):operator Wz4Render Print(ChaosFont)

Here we are looking for an operator of the Wz4Render type (yellow operators) so this is the second line.

Tip : to know the type of an operator, the simplest method is to select the operator in wekkzeug and click the wiki button in menu bar in the top right window (parameters window). A new window appears and the "Output Type" information provide the type. These are the same types that are declared in the ops files.

Double-click on the seach result and you obtain the Print operator code in scripting language :

operator Wz4Render Print(ChaosFont)
{
  column = 1;
  parameter
  {
    int Renderpass(-127..127);
    anim float31 Scale:1 (-1024..1024 step 0.01) = 1;
    anim float30 Rot (-16..16 step 0.01) = 0;
    anim float31 Trans (-0x10000..0x10000 step 0.01) = { 0,0,0 } ;
    anim color Color("rgba")=0xffffffff;
    group "Text";
    overbox overlabel lines 5 string Text:1 = "hund."; 
    group "Animation Script"; overbox overlabel linenumber lines 5 string Script:0;
  }
  code
  {
    RNPrint *node = new RNPrint();
    node->ParaBase = node->Para = *para;
    node->Font = in0; in0->AddRef();
    node->Text = cmd->Strings[1];
    node->Font->PreparePrint();
    out->RootNode = node;
    out->AddCode(cmd,para->Renderpass);
  }
}

Description : this define an Wz4Render operator named "Print" which takes as input another operator of ChaosFont type (the operator you need to connect above it). Column keyword is used to specify the column placement in the add operator dialog. The parameter block is where are defined all its parameters (same you see in the parameter window). And the code block, is where is placed C++ used to instanciate the operator and add it to the engine. It is called once when adding the operator to the page or called when you change any parameter in GUI.

What interests us particularly here is the RNPrint object (RN = RenderNode). We are going to do a new search in visual studio on the RNPrint keyword (this time, search in all files, so remove the *.ops filter and regular exprssion option). We obtain this :

C:\github\werkkzeug4\wz4\wz4frlib\chaosfx_ops.cpp(279):RNPrint *node = new RNPrint();
C:\github\werkkzeug4\wz4\wz4frlib\chaosfx.cpp(166):RNPrint::RNPrint()
C:\github\werkkzeug4\wz4\wz4frlib\chaosfx.cpp(172):RNPrint::~RNPrint()
C:\github\werkkzeug4\wz4\wz4frlib\chaosfx.cpp(177):void RNPrint::Simulate(Wz4RenderContext *ctx)
C:\github\werkkzeug4\wz4\wz4frlib\chaosfx.cpp(185):void RNPrint::Prepare(Wz4RenderContext *ctx)
C:\github\werkkzeug4\wz4\wz4frlib\chaosfx.cpp(194):void RNPrint::Render(Wz4RenderContext *ctx)
C:\github\werkkzeug4\wz4\wz4frlib\chaosfx.hpp(43):class RNPrint : public Wz4RenderNode
C:\github\werkkzeug4\wz4\wz4frlib\chaosfx.hpp(47):RNPrint();
C:\github\werkkzeug4\wz4\wz4frlib\chaosfx.hpp(48):~RNPrint();
C:\github\werkkzeug4\wz4\wz4frlib\chaosfx_ops.ops(68):RNPrint *node = new RNPrint();

This class is written in chaosfx.hpp and chaosfx.cpp.
Let's go to this class declaration in chaosfx.hpp :

class RNPrint : public Wz4RenderNode
{
...
};

As you can see, this class is derived from the Wz4RenderNode base class, let's see that :

class Wz4RenderNode : public Wz4BaseNode
{
public:
  Wz4RenderNode();
  ~Wz4RenderNode();

  virtual void Simulate(Wz4RenderContext *ctx);             // perform calculations
  virtual void Transform(Wz4RenderContext *ctx,const sMatrix34 &); // build list of model matrices with GRAPH!
  virtual void Prepare(Wz4RenderContext *ctx);              // prepare rendering
  virtual void Render(Wz4RenderContext *ctx);               // render z only

  ScriptSymbol *AddSymbol(const sChar *);                   // add symbol for animation
  void ClearRecFlagsR();                                    // prepare recursions
  void ClearMatricesR(); 
  void SimulateCalc(Wz4RenderContext *ctx);                 // call the script
  void SimulateChilds(Wz4RenderContext *ctx);               // recurse to childs
  void TransformChilds(Wz4RenderContext *ctx,const sMatrix34 &); // recurse to childs
  void PrepareChilds(Wz4RenderContext *ctx);                // recurse to childs
  void RenderChilds(Wz4RenderContext *ctx);                 // recurse to childs

  void SimulateChild(Wz4RenderContext *ctx,Wz4RenderNode *c); // just one child...
  void SetTimeAndSimulate(Wz4RenderContext *ctx,sF32 newtime,sF32 localoffset);

  sArray<Wz4RenderNode *> Childs; // tree
  ScriptCode *Code;               // code for animation
  wOp *Op;                        // (insecure) backlink to op, for error messages
  sInt Renderpass;                // used for sorting nodes 
  sInt RenderpassSort;            // used for sorting nodes 
  sBool MayCollapse;              // childs in this node may be collapsed into another node, for better renderpass sorting.
  sInt RecFlags;                  // recursion flags: prevent double calls of Simulate & Prepare
  sArray<sMatrix34CM> Matrices;   // Liste von Matrix für's zeichnen. 
  sInt TimelineStart;             // timeline range
  sInt TimelineEnd;
  sF32 ClipRandDuration;          // hack for the cliprand operator
};

Tip : to see an useful example on how to use Wz4RenderNode objects, take a look at RNCubeExample class (at top of chaosfx.cpp and chaosfx.hpp). There is many comments and it is used as template for all other Wz4RenderNode objects.

 Back to the RNPrint code and its parameters problem (highlighted lines are places where I added or modified code to fix the problem) :

chaosfx.hpp :

class RNPrint : public Wz4RenderNode
{
  sMatrix34 Matrix;
public:
  RNPrint();
  ~RNPrint();
  
  void Simulate(Wz4RenderContext *ctx);
  void Prepare(Wz4RenderContext *ctx);
  void Render(Wz4RenderContext *ctx);

  Wz4RenderParaPrint Para,ParaBase;
  Wz4RenderAnimPrint Anim;
  class ChaosFont *Font;
  sPoolString Text;
}; 

chaosfx.cpp:

/****************************************************************************/
/***                                                                      ***/
/***   Print with font                                                    ***/
/***                                                                      ***/
/****************************************************************************/

RNPrint::RNPrint()
{
  Font = 0;
  Anim.Init(Wz4RenderType->Script);
}

RNPrint::~RNPrint()
{
  Font->Release();
}

void RNPrint::Simulate(Wz4RenderContext *ctx)
{
  Para = ParaBase;

  Anim.Bind(ctx->Script,&Para);
  SimulateCalc(ctx);
}

void RNPrint::Prepare(Wz4RenderContext *ctx)
{
  sSRT srt;
  srt.Scale = Para.Scale;
  srt.Rotate = Para.Rot*sPI2F;
  srt.Translate = Para.Trans;
  srt.MakeMatrix(Matrix);
}

void RNPrint::Render(Wz4RenderContext *ctx)
{
  if((ctx->RenderMode & sRF_TARGET_MASK)==sRF_TARGET_MAIN)
  {    
    sMatrix34CM *model;
    sViewport view = ctx->View;
    sFORALL(Matrices,model)
    {
      view.Model = sMatrix34(*model) * Matrix;
      view.Prepare();
      Font->Print(view,Text,Para.Color,1.0f/Font->Height);
    } 
  }  
}


Before I modify this code, there wasn't any reference to the scale, rot and trans parameters and that is why they do not work. So, the solution here is to compute a transformation matrix using scale, rot and trans vectors and to multiply the view.Model matrix with this new matrix.

To compute the matrix, the RNCubeExample says that the virtual prepare function is a good place to do some maths. So I overided this "prepare" function (defined in the base class Wz4RenderNode) and put inside the matrix calculation :

  sSRT srt;
  srt.Scale = Para.Scale;
  srt.Rotate = Para.Rot*sPI2F;
  srt.Translate = Para.Trans;
  srt.MakeMatrix(Matrix);

This is the regular way in werkkzeug to do that. We simply use the 3 vectors (scale, rot, trans parameters) to compute a matrix.

It remains to declare a new matrix variable and the prepare function prototype in the RNPrint class declaration, add the multiplication of this new matrix in the ::Render function and it's done, we can now use transformation parameters of the "Print" operator to transform the text in the 3D space.

No comments:

Post a Comment