Difference between revisions of "Tool spikey"

From The Foundry MODO SDK wiki
Jump to: navigation, search
Line 12: Line 12:
 
===Class Declarations===
 
===Class Declarations===
  
 +
We want this class to display structured data in the log. In this case it's used to show feedback on the current spikey factor. To that end, we inherit from [[Log_(lx-log.hpp)#ILxLogInfoBlock|CLxImpl_LogInfoBlock]] as we are going to want our data to be in the form of a log info block. The first method here indicates the name of the command, which in this case is test.spikey.  The next three walk the list of fields, setting the count as 1(there can only be one tool), the name of the data as 'factor', and the data type as percent. These are all put into arrays that we can later access.
 +
 +
<pre>
 
  class CSpikeyLogBlock :
 
  class CSpikeyLogBlock :
 
                 public CLxImpl_LogInfoBlock
 
                 public CLxImpl_LogInfoBlock
 
  {
 
  {
    public:
+
        public:
 
                 LxResult
 
                 LxResult
 
         lb_Name (
 
         lb_Name (
Line 23: Line 26:
 
                 return LXe_OK;
 
                 return LXe_OK;
 
         }
 
         }
+
 
 
                 LxResult
 
                 LxResult
 
         lb_FieldCount (
 
         lb_FieldCount (
Line 31: Line 34:
 
                 return LXe_OK;
 
                 return LXe_OK;
 
         }
 
         }
+
 
 
                 LxResult
 
                 LxResult
 
         lb_FieldName (
 
         lb_FieldName (
Line 40: Line 43:
 
                 return LXe_OK;
 
                 return LXe_OK;
 
         }
 
         }
+
 
 
                 LxResult
 
                 LxResult
 
         lb_FieldType (
 
         lb_FieldType (
Line 49: Line 52:
 
                 return LXe_OK;
 
                 return LXe_OK;
 
         }
 
         }
  };
+
  };</pre>
  
We want this class to display structured data in the log. In this case it's used to show feedback on the current spikey factor. To that end, we inherit from [[Log_(lx-log.hpp)#ILxLogInfoBlock|CLxImpl_LogInfoBlock]] as we are going to want our data to be in the form of a log info block.
+
The Spikey tool. In order to have our class create a tool, we inherit from [[Tool_(lx-tool.hpp)#Tool|CLxImpl_Tool]] and [[Vmodel_(lx-vmodel.hpp)|CLxImpl_ToolModel]]. Basic tool and tool model methods are defined. The attributes interface is inherited from the utility class; we inherit from it in order to be able to display the attributes of our tool.
  
 
  class CSpikeyTool :
 
  class CSpikeyTool :
Line 78: Line 81:
 
  };
 
  };
  
The Spikey tool. In order to have our class create a tool, we inherit from [[Tool_(lx-tool.hpp)|CLxImpl_Tool]] and [[Vmodel_(lx-vmodel.hpp)|CLxImpl_ToolModel]]. Basic tool and tool model methods are defined. The attributes interface is inherited from the utility class; we inherit from it in order to be able to display the attributes of our tool.
+
This class is a map visitor that collects the maps in vectors based on type. In order to create a map visitor, we are going to need to inherit from CLxImpl_AbstractVisitor and modify the methods so that it visits maps.  
  
 
  class CSpikeMapListVisitor : public CLxImpl_AbstractVisitor
 
  class CSpikeMapListVisitor : public CLxImpl_AbstractVisitor
Line 165: Line 168:
 
  };
 
  };
  
This class is a map visitor that collects the maps in vectors based on type. In order to create a map visitor, we are going to need to inherit from CLxImpl_AbstractVisitor and modify the methods so that it visits maps.  
+
This class does evaluation. It does so with a polygon visitor which holds the current state of the action.
  
 
  class CSpikePolygonVisitor : public CLxImpl_AbstractVisitor
 
  class CSpikePolygonVisitor : public CLxImpl_AbstractVisitor
Line 190: Line 193:
 
         bool edit_any;
 
         bool edit_any;
 
  };
 
  };
 
This class does evaluation. It does so with a polygon visitor which holds the current state of the action.
 
  
 
===[[Initialize_(index)|Initialize]]===
 
===[[Initialize_(index)|Initialize]]===
 +
 +
This initialize method exports two servers. The first is dependent on the CSpikeyTool class and includes the interface for all the classes that CSpikeyTool inherited from. The same is done for the second server, with the exception that the class in question here is CSpikeyLogBlock. Of note here is that the two servers are given the same name, so when the user runs the command "test.spikey" both are run.
  
 
         void
 
         void
Line 211: Line 214:
 
  }
 
  }
  
This initialize method exports two servers. The first is dependent on the CSpikeyTool class and includes the interface for all the classes that CSpikeyTool inherited from. The same is done for the second server, with the exception that the class in question here is CSpikeyLogBlock. Of note here is that the two servers are given the same name, so when the user runs the command "test.spikey" both are run.
 
  
Implementations
+
 
 +
===Implementations===
 +
 
 +
The CSpikeyTool constructor adds one tool attribute. It also allocates a vector type (which doesn't seem to need anything in it!), the falloff packet offset and the select mode mask.
  
 
  CSpikeyTool::CSpikeyTool ()
 
  CSpikeyTool::CSpikeyTool ()
Line 231: Line 236:
 
  }
 
  }
  
The CSpikeyTool constructor adds one tool attribute. It also allocates a vector type (which doesn't seem to need anything in it!), the falloff packet offset and the select mode mask.
+
Reset sets the attributes back to defaults.
  
 
         void
 
         void
Line 239: Line 244:
 
  }
 
  }
  
Reset sets the attributes back to defaults.
+
Boilerplate methods that identify this as an action (state altering) tool.  
 
   
 
   
 
         LXtObjectID
 
         LXtObjectID
Line 246: Line 251:
 
         return v_type.m_loc; // peek method; does not add-ref
 
         return v_type.m_loc; // peek method; does not add-ref
 
  }
 
  }
+
 
 
         const char *
 
         const char *
 
  CSpikeyTool::tool_Order ()
 
  CSpikeyTool::tool_Order ()
Line 259: Line 264:
 
  }
 
  }
  
Boilerplate methods that identify this as an action (state altering) tool.
+
We employ the simplest possible tool model -- default hauling. We indicate that we want to haul one attribute, we name the attribute, and we implement Initialize() which is what to do when the tool activates or re-activates. In this case set the factor back to zero.
 
+
  
 
         unsigned
 
         unsigned
Line 289: Line 293:
 
  }
 
  }
  
We employ the simplest possible tool model -- default hauling. We indicate that we want to haul one attribute, we name the attribute, and we implement Initialize() which is what to do when the tool activates or re-activates. In this case set the factor back to zero.
+
Get a vertex position. This gets the position of the given vertex of the polygon relative to the current map. This allows us to evaluate the shape of polygons in morphs.
  
 
         void
 
         void
Line 314: Line 318:
 
  }
 
  }
  
Get a vertex position. This gets the position of the given vertex of the polygon relative to the current map. This allows us to evaluate the shape of polygons in morphs.
+
Normalize a vector, if possible.
  
 
         bool
 
         bool
Line 332: Line 336:
 
  }
 
  }
  
Normalize a vector, if possible.
+
Get the normalized cross-product of edge vectors defined by the three points of the polygon.
  
 
         bool
 
         bool
Line 353: Line 357:
 
  }
 
  }
  
Get the normalized cross-product of edge vectors defined by the three points of the polygon.
+
Compute a good normal (one that's symmetry safe, which the basic polygon normal is not). This takes the cross product of the 0'th point, then adds in the rest flipping as needed. Final result is renormalized.
  
 
         bool
 
         bool
Line 376: Line 380:
 
  }
 
  }
  
Compute a good normal (one that's symmetry safe, which the basic polygon normal is not). This takes the cross product of the 0'th point, then adds in the rest flipping as needed. Final result is renormalized.
+
Compute the position of the spike for the current polygon in the current morph map. We compute the average position and the perimeter and normal of the polygon, and compute a position along the normal with an offset given by average edge length modulated by weight. Returns false if it cannot be computed.
  
 
         bool
 
         bool
Line 413: Line 417:
 
  }
 
  }
  
Compute the position of the spike for the current polygon in the current morph map. We compute the average position and the perimeter and normal of the polygon, and compute a position along the normal with an offset given by average edge length modulated by weight. Returns false if it cannot be computed.
+
Evaluation replaces the polygon with a fan of triangles. The central point is given a spike position in all morphs, and other maps are interpolated.
  
 
         LxResult
 
         LxResult
Line 554: Line 558:
 
  }
 
  }
  
Evaluation replaces the polygon with a fan of triangles. The central point is given a spike position in all morphs, and other maps are interpolated.
+
Tool evaluation uses layer scan interface to walk through all the active meshes and visit all the selected polygons.
  
 
         void
 
         void
Line 618: Line 622:
 
         scan.Apply ();
 
         scan.Apply ();
 
  }
 
  }
 
Tool evaluation uses layer scan interface to walk through all the active meshes and visit all the selected polygons.
 

Revision as of 18:44, 10 September 2013

Tool_spikey is a basic example plugin. This wiki page is intended as a walkthrough of the code in order to help you better understand the SDK.

This plugin adds the spikey tool to the suite of tools modo has. The tool takes surfaces and makes them more "spikey", as illustrated in screenshots below

Sphereb4spikey.png Sphereafterspikey.png

Code Walkthrough

Class Declarations

We want this class to display structured data in the log. In this case it's used to show feedback on the current spikey factor. To that end, we inherit from CLxImpl_LogInfoBlock as we are going to want our data to be in the form of a log info block. The first method here indicates the name of the command, which in this case is test.spikey. The next three walk the list of fields, setting the count as 1(there can only be one tool), the name of the data as 'factor', and the data type as percent. These are all put into arrays that we can later access.

 class CSpikeyLogBlock :
                public CLxImpl_LogInfoBlock
 {
        public:
                LxResult
        lb_Name (
                const char	       **name)		LXx_OVERRIDE
        {
                name[0] = "test.spikey";
                return LXe_OK;
        }

                LxResult
        lb_FieldCount (
                unsigned int		*count)		LXx_OVERRIDE
        {
                count[0] = 1;
                return LXe_OK;
        }

                LxResult
        lb_FieldName (
                unsigned int		 index,
                const char	       **name)		LXx_OVERRIDE
        {
                name[0] = "factor";
                return LXe_OK;
        }

                LxResult
        lb_FieldType (
                unsigned int		 index,
                const char	       **type)		LXx_OVERRIDE
        {
                type[0] = LXsTYPE_PERCENT;
                return LXe_OK;
        }
 };

The Spikey tool. In order to have our class create a tool, we inherit from CLxImpl_Tool and CLxImpl_ToolModel. Basic tool and tool model methods are defined. The attributes interface is inherited from the utility class; we inherit from it in order to be able to display the attributes of our tool.

class CSpikeyTool :
               public CLxImpl_Tool,
               public CLxImpl_ToolModel,
               public CLxDynamicAttributes
{
   public:
                       CSpikeyTool ();

       void		tool_Reset      () LXx_OVERRIDE;
       LXtObjectID	tool_VectorType () LXx_OVERRIDE;
       const char *	tool_Order      () LXx_OVERRIDE;
       LXtID4		tool_Task       () LXx_OVERRIDE;
       void		tool_Evaluate   (ILxUnknownID vts) LXx_OVERRIDE;

       unsigned	tmod_Flags      () LXx_OVERRIDE;
       void		tmod_Initialize (ILxUnknownID vts, ILxUnknownID adjust, unsigned flags) LXx_OVERRIDE;
       const char *	tmod_Haul       (unsigned index) LXx_OVERRIDE;

       CLxUser_LogService	 s_log;
       CLxUser_LayerService	 s_layer;
       CLxUser_VectorType	 v_type;
       unsigned		 offset_falloff, offset_view;
       unsigned		 mode_select;
};

This class is a map visitor that collects the maps in vectors based on type. In order to create a map visitor, we are going to need to inherit from CLxImpl_AbstractVisitor and modify the methods so that it visits maps.

class CSpikeMapListVisitor : public CLxImpl_AbstractVisitor
{
   public:
       CLxUser_MeshMap			 map;
       std::vector<LXtMeshMapID>	 morph;
       std::vector<LXtMeshMapID>	 spot;
       std::vector<LXtMeshMapID>	 other;
       LXtMeshMapID			 opos;
       float				*buf, *acc;
       unsigned			 len, max;

        CSpikeMapListVisitor ()
       {
               len = 0;
       }

       ~CSpikeMapListVisitor ()
       {
               FreeArrays ();
       }

               void
       FreeArrays ()
       {
               if (len) {
                       delete [] buf;
                       delete [] acc;
                       len = 0;
               }
       }

               void
       fromMeshObj (CLxLoc_Mesh &mesh)
       {
               map.fromMeshObj (mesh);
       }

               LxResult
       Evaluate ()
       {
               LXtID4			 type;
               unsigned		 dim;

               map.Type (&type);
               map.Dimension (&dim);
               if (dim == 0)
                       return LXe_OK;

               if (dim > max)
                       max = dim;

               if (type == LXi_VMAP_OBJECTPOS)
                       opos = map.ID ();

               else if (type == LXi_VMAP_MORPH)
                       morph.push_back (map.ID ());

               else if (type == LXi_VMAP_SPOT)
                       spot .push_back (map.ID ());

               else if (map.IsEdgeMap () != LXe_TRUE)
                       other.push_back (map.ID ());

               return LXe_OK;
       }

               void
       Collect ()
       {
               morph.clear ();
               spot .clear ();
               other.clear ();

               max = 0;
               map.Enum (this, 0);

               if (max > len) {
                       FreeArrays ();
                       buf = new float[max];
                       acc = new float[max];
                       len = max;
               }
       }
};

This class does evaluation. It does so with a polygon visitor which holds the current state of the action.

class CSpikePolygonVisitor : public CLxImpl_AbstractVisitor
{
   public:
       LxResult		 Evaluate ();
       bool			 Normalize (LXtVector);
       void			 VertexPos (unsigned index, LXtFVector pos);
       bool			 VertexCross (unsigned i0, unsigned i1, unsigned i2, LXtVector dir);
       bool			 GoodNormal (LXtVector dir);
       bool			 GetSpike (LXtVector pos);

       LXtMeshMapID		 map_id;
       LXtID4			 map_type;
       unsigned		 vrt_count;
       double			 pol_weight;

       CSpikeMapListVisitor	 e_maps;
       CLxUser_Mesh		 e_mesh;
       CLxUser_Polygon		 e_poly, e_dest;
       CLxUser_Point		 e_vert;
       CLxUser_FalloffPacket	 e_falloff;
       double			 e_factor;
       bool			 edit_any;
};

Initialize

This initialize method exports two servers. The first is dependent on the CSpikeyTool class and includes the interface for all the classes that CSpikeyTool inherited from. The same is done for the second server, with the exception that the class in question here is CSpikeyLogBlock. Of note here is that the two servers are given the same name, so when the user runs the command "test.spikey" both are run.

       void
initialize ()
{
       CLxGenericPolymorph		*srv;

       srv = new CLxPolymorph<CSpikeyTool>;
       srv->AddInterface (new CLxIfc_Tool      <CSpikeyTool>);
       srv->AddInterface (new CLxIfc_ToolModel <CSpikeyTool>);
       srv->AddInterface (new CLxIfc_Attributes<CSpikeyTool>);
       thisModule.AddServer ("test.spikey", srv);

       srv = new CLxPolymorph<CSpikeyLogBlock>;
       srv->AddInterface (new CLxIfc_LogInfoBlock<CSpikeyLogBlock>);
       thisModule.AddServer ("test.spikey", srv);
}


Implementations

The CSpikeyTool constructor adds one tool attribute. It also allocates a vector type (which doesn't seem to need anything in it!), the falloff packet offset and the select mode mask.

CSpikeyTool::CSpikeyTool ()
{
       CLxUser_PacketService	 sPkt;
       CLxUser_MeshService	 sMesh;

       dyna_Add (ATTRs_FACTOR, LXsTYPE_PERCENT);

       sPkt.NewVectorType (LXsCATEGORY_TOOL, v_type);
       sPkt.AddPacket (v_type, LXsP_TOOL_FALLOFF,    LXfVT_GET);
       sPkt.AddPacket (v_type, LXsP_TOOL_VIEW_EVENT, LXfVT_GET);

       offset_falloff = sPkt.GetOffset (LXsCATEGORY_TOOL, LXsP_TOOL_FALLOFF);
       offset_view    = sPkt.GetOffset (LXsCATEGORY_TOOL, LXsP_TOOL_VIEW_EVENT);
       mode_select    = sMesh.SetMode ("select");
}

Reset sets the attributes back to defaults.

       void
CSpikeyTool::tool_Reset ()
{
        attr_SetFlt (0, 0.0);
}

Boilerplate methods that identify this as an action (state altering) tool.

       LXtObjectID
CSpikeyTool::tool_VectorType ()
{
       return v_type.m_loc;	// peek method; does not add-ref
}
       const char *
CSpikeyTool::tool_Order ()
{
       return LXs_ORD_ACTR;
}

        LXtID4
CSpikeyTool::tool_Task ()
{
       return LXi_TASK_ACTR;
}

We employ the simplest possible tool model -- default hauling. We indicate that we want to haul one attribute, we name the attribute, and we implement Initialize() which is what to do when the tool activates or re-activates. In this case set the factor back to zero.

       unsigned
CSpikeyTool::tmod_Flags ()
{
       return LXfTMOD_I0_ATTRHAUL;
}

       void
CSpikeyTool::tmod_Initialize (
       ILxUnknownID		 vts,
       ILxUnknownID		 adjust,
       unsigned int		 flags)
{
       CLxUser_AdjustTool	 at (adjust);

       at.SetFlt (0, 0.0);
}

       const char *
CSpikeyTool::tmod_Haul (
       unsigned		 index)
{
       if (index == 0)
               return ATTRs_FACTOR;
       else
               return 0;
}

Get a vertex position. This gets the position of the given vertex of the polygon relative to the current map. This allows us to evaluate the shape of polygons in morphs.

       void
CSpikePolygonVisitor::VertexPos (
       unsigned		 index,
       LXtFVector		 pos)
{
       e_vert.SelectPolygonVertex (e_poly.ID (), index);

       if (map_type == LXi_VMAP_OBJECTPOS)
               e_vert.Pos (pos);

       else if (map_type == LXi_VMAP_SPOT) {
               if (e_vert.MapValue (map_id, pos) != LXe_OK)
                       e_vert.Pos (pos);

       } else {
               LXtFVector	 delta;

               e_vert.Pos (pos);
               e_vert.MapEvaluate (map_id, delta);
               LXx_VADD (pos, delta);
       }
}

Normalize a vector, if possible.

       bool
CSpikePolygonVisitor::Normalize (
       LXtVector		 v)
{
       double			 len;

       len = LXx_VLEN (v);
       if (len) {
               LXx_VSCL (v, 1.0 / len);
               return true;
       } else {
               LXx_VCLR (v);
               return false;
       }
}

Get the normalized cross-product of edge vectors defined by the three points of the polygon.

       bool
CSpikePolygonVisitor::VertexCross (
       unsigned		 i0,
       unsigned		 i1,
       unsigned		 i2,
       LXtVector		 dir)
{
       LXtFVector		 p0, p1, p2;
       LXtFVector		 e0, e1;

       VertexPos (i0, p0);
       VertexPos (i1, p1);
       VertexPos (i2, p2);
       LXx_VSUB3 (e0, p1, p0);
       LXx_VSUB3 (e1, p2, p1);
       LXx_VCROSS (dir, e0, e1);
       return Normalize (dir);
}

Compute a good normal (one that's symmetry safe, which the basic polygon normal is not). This takes the cross product of the 0'th point, then adds in the rest flipping as needed. Final result is renormalized.

       bool
CSpikePolygonVisitor::GoodNormal (
       LXtVector		 dir)
{
       LXtVector		 base, norm;
       unsigned		 i;

       if (!VertexCross (vrt_count - 1, 0, 1, base))
               return false;

       LXx_VCPY (dir, base);
       for (i = 1; i < vrt_count; i++)
               if (VertexCross (i - 1, i, (i + 1) % vrt_count, norm))
                       if (LXx_VDOT (base, norm) < 0.0)
                               LXx_VSUB (dir, norm);
                       else
                               LXx_VADD (dir, norm);

       return Normalize (dir);
}

Compute the position of the spike for the current polygon in the current morph map. We compute the average position and the perimeter and normal of the polygon, and compute a position along the normal with an offset given by average edge length modulated by weight. Returns false if it cannot be computed.

       bool
CSpikePolygonVisitor::GetSpike (
       LXtVector		 pos)
{
       LXtVector		 dir;
       LXtFVector		 p0, p1;
       double			 perimeter;
       unsigned		 i;

       LXx_VCLR (pos);
       perimeter = 0.0;

       VertexPos (vrt_count - 1, p0);

       for (i = 0; i < vrt_count; i++) {
               VertexPos (i, p1);

               LXx_VSUB3 (dir, p0, p1);
               perimeter += LXx_VLEN (dir);

               LXx_VADD (pos, p1);
               LXx_VCPY (p0, p1);
       }

       if (perimeter <= 0.0)
               return false;

       if (!GoodNormal (dir))
               return false;

       LXx_VSCL  (pos, 1.0 / vrt_count);
       LXx_VADDS (pos, dir, pol_weight * e_factor * perimeter / vrt_count);
       return true;
}

Evaluation replaces the polygon with a fan of triangles. The central point is given a spike position in all morphs, and other maps are interpolated.

       LxResult
CSpikePolygonVisitor::Evaluate ()
{
       LXtPointID		 vrt, vl[3];
       LXtPolygonID		 pol;
       LXtFVector		 fvec;
       LXtVector		 tip, tip1;
       unsigned		 i;

       std::vector<LXtMeshMapID>::iterator
                                mit;

       e_poly.VertexCount (&vrt_count);
       if (vrt_count < 3)
               return LXe_OK;

       /*
        * Compute average weight of all vertices, rejecting zero-weight polys.
        */
       pol_weight = 0.0;
       for (i = 0; i < vrt_count; i++) {
               e_poly.VertexByIndex (i, &vrt);
               e_vert.Pos (fvec);
               pol_weight += e_falloff.Evaluate (fvec, vrt);
       }

       if (pol_weight <= 0.0)
               return LXe_OK;

       pol_weight /= vrt_count;

       /*
        * Create vertex using the basic spike position, if any.
        */
       map_id   = e_maps.opos;
       map_type = LXi_VMAP_OBJECTPOS;
       if (!GetSpike (tip))
               return LXe_OK;

       e_vert.New (tip, &vl[2]);

       /*
        * Compute morph positions for the spike tip. MORF maps are deltas from
        * the base position and can be unset if close to zero.
        */
       map_type = LXi_VMAP_MORPH;
       for (mit = e_maps.morph.begin(); mit < e_maps.morph.end(); mit++) {
               map_id = *mit;
               if (GetSpike (tip1)) {
                       LXx_VSUB3 (fvec, tip1, tip);
                       if (LXx_VLEN (fvec) > 1.0e-5) {
                               e_vert.Select (vl[2]);
                               e_vert.SetMapValue (map_id, fvec);
                       }
               }
       }

       /*
        * Compute morph positions for the spike tip. SPOT maps are absolute
        * positions and can be unset if the same as the base position.
        */
       map_type = LXi_VMAP_SPOT;
       for (mit = e_maps.spot.begin(); mit < e_maps.spot.end(); mit++) {
               map_id = *mit;
               if (GetSpike (tip1)) {
                       LXx_VSUB3 (fvec, tip1, tip);
                       if (LXx_VLEN (fvec) > 1.0e-5) {
                               LXx_VCPY (fvec, tip1);
                               e_vert.Select (vl[2]);
                               e_vert.SetMapValue (map_id, fvec);
                       }
               }
       }

       /*
        * All other maps are set as averages of the vectors from the vertices.
        * This uses the allocated buffers because arbitrary vertex map types
        * can have any dimension.
        */
       for (mit = e_maps.other.begin(); mit < e_maps.other.end(); mit++) {
               unsigned	 k, dim;

               e_maps.map.Select (*mit);
               e_maps.map.Dimension (&dim);

               for (k = 0; k < dim; k++)
                       e_maps.acc[k] = 0.0;

               for (i = 0; i < vrt_count; i++) {
                       e_poly.VertexByIndex (i, &vrt);
                       if (e_poly.MapEvaluate (*mit, vrt, e_maps.buf) != LXe_OK)
                               break;

                       for (k = 0; k < dim; k++)
                               e_maps.acc[k] += e_maps.buf[k];
               }

               if (i < vrt_count)
                       continue;

               for (k = 0; k < dim; k++)
                       e_maps.acc[k] /= vrt_count;

               e_vert.Select (vl[2]);
               e_vert.SetMapValue (*mit, e_maps.acc);
       }

       /*
        * Finally replace the original polygon with a fan of triangles. We also
        * have to copy discontinuous vertex maps (like UV maps) from the original
        * polygon to the replacements.
        */
       e_poly.VertexByIndex (0, &vl[0]);
       for (i = 0; i < vrt_count; i++) {
               e_poly.VertexByIndex ((i + 1) % vrt_count, &vl[1]);
               e_poly.NewProto (0, vl, 3, 0, &pol);

               for (mit = e_maps.other.begin(); mit < e_maps.other.end(); mit++) {
                       unsigned	 k;

                       e_maps.map.Select (*mit);
                       if (e_maps.map.IsContinuous () == LXe_TRUE)
                               continue;

                       e_dest.Select (pol);
                       for (k = 0; k < 2; k++)
                               if (e_poly.MapEvaluate (*mit, vl[k], e_maps.buf) == LXe_OK)
                                       e_dest.SetMapValue (vl[k], *mit, e_maps.buf);
               }

               vl[0] = vl[1];
       }

       e_poly.Remove ();
       edit_any = true;

       return LXe_OK;
}

Tool evaluation uses layer scan interface to walk through all the active meshes and visit all the selected polygons.

       void
CSpikeyTool::tool_Evaluate (
       ILxUnknownID		 vts)
{
       CLxUser_VectorStack	 vec (vts);
       LXpToolViewEvent	*view;

       view = (LXpToolViewEvent *) vec.Read (offset_view);
       if (!view || view->type != LXi_VIEWTYPE_3D)
               return;

       /*
        * Display current spikey factor using our log block.
        */
       CLxUser_LogEntry	 entry;

       s_log.NewBlockEntry (LXe_INFO, "test.spikey", entry);
       entry.SetValue (0, 0, dyna_Value (0));

       /*
        * Read the falloff and start the scan in edit-poly mode.
        */
       CLxUser_LayerScan	 scan;
       CSpikePolygonVisitor	 vis;

       dyna_Value (0) . GetFlt (&vis.e_factor);
       vec.ReadObject (offset_falloff, vis.e_falloff);

       s_layer.BeginScan (LXf_LAYERSCAN_EDIT_POLYS, scan);

#if 0
       // NOTE: this is for marking new polygons for selection, if any are already
       //
       tool->mark = SelNumElements (ID_POLY) ? MARK_NEWSEL : MODE_NIL;
#endif

       /*
        * Enumerate all selected polygons in all mesh layers. If a mesh is
        * edited we set the "geometry" change flag indicating that points
        * and polygons have both changed.
        */
       unsigned		 i, n;

       n = scan.NumLayers ();
       for (i = 0; i < n; i++) {
               scan.EditMeshByIndex (i, vis.e_mesh);
               vis.e_vert.fromMeshObj (vis.e_mesh);
               vis.e_poly.fromMeshObj (vis.e_mesh);
               vis.e_dest.fromMeshObj (vis.e_mesh);
               vis.e_maps.fromMeshObj (vis.e_mesh);

               vis.e_maps.Collect ();
               vis.edit_any = false;

               vis.e_poly.Enum (&vis, mode_select);

               if (vis.edit_any)
                       scan.SetMeshChange (i, LXf_MESHEDIT_GEOMETRY);
       }

       scan.Apply ();
}