Difference between revisions of "Mesh Operation - Example"

From The Foundry MODO SDK wiki
Jump to: navigation, search
(Configs)
Line 353: Line 353:
  
 
===Configs===
 
===Configs===
 +
<source lang="xml">
 +
<?xml version="1.0" encoding="UTF-8"?>
 +
<configuration>
 +
 +
<!--
 +
The CommandHelp block is used to define the item and channel usernames.
 +
-->
 +
 +
<atom type="CommandHelp">
 +
<hash type="Item" key="sample.bevel.item@en_US">
 +
<atom type="UserName">Bevel Sample</atom>
 +
<hash type="Channel" key="enable">
 +
<atom type="UserName">Enable</atom>
 +
</hash>
 +
<hash type="Channel" key="offset">
 +
<atom type="UserName">Offset</atom>
 +
</hash>
 +
</hash>
 +
</atom>
 +
 +
<!--
 +
The properties form displays a single property to control the offset.
 +
The common mesh operation sheet is also included to add the enable
 +
checkbox to the top of the form.
 +
-->
 +
 +
<atom type="Attributes">
 +
<hash type="Sheet" key="sample.bevel.item:sheet">
 +
<atom type="Label">Bevel Sample</atom>
 +
<atom type="Filter">sample.bevel.item:filterPreset</atom>
 +
<hash type="InCategory" key="itemprops:general#head">
 +
<atom type="Ordinal">110</atom>
 +
</hash>
 +
<list type="Control" val="ref item-common:sheet">
 +
<atom type="StartCollapsed">0</atom>
 +
<atom type="Hash">#0</atom>
 +
</list>
 +
<list type="Control" val="ref meshoperation:sheet">
 +
<atom type="StartCollapsed">0</atom>
 +
<atom type="Hash">#1</atom>
 +
</list>
 +
<list type="Control" val="cmd item.channel sample.bevel.item$offset ?">
 +
<atom type="Label">Offset</atom>
 +
<atom type="StartCollapsed">0</atom>
 +
<atom type="Hash">sample.bevel.item.offset.ctrl:control</atom>
 +
</list>
 +
</hash>
 +
</atom>
 +
 +
<!--
 +
A filter determines when the properties form should be displayed. In this
 +
case the properties form should be displayed when the sample.bevel.item
 +
item type is selected.
 +
-->
 +
 +
<atom type="Filters">
 +
<hash type="Preset" key="sample.bevel.item:filterPreset">
 +
<atom type="Name">Bevel Sample</atom>
 +
<atom type="Description"></atom>
 +
<atom type="Category">pmodel:filterCat</atom>
 +
<atom type="Enable">1</atom>
 +
<list type="Node">1 .group 0 &quot;&quot;</list>
 +
<list type="Node">1 itemtype 0 1 &quot;sample.bevel.item&quot;</list>
 +
<list type="Node">-1 .endgroup </list>
 +
</hash>
 +
</atom>
 +
 +
<!--
 +
The categories define which sub-category the mesh operation will appear
 +
in. Two categories have to be set, one that shows all Mesh Operations
 +
and another which is filtered to show only operations that support a
 +
specific selection type.
 +
-->
 +
 +
<atom type="Categories">
 +
<hash type="Category" key="MeshOperations">
 +
<hash type="C" key="sample.bevel.item">polygon</hash>
 +
</hash>
 +
 +
<hash type="Category" key="MeshOperationsPolygons">
 +
<hash type="C" key="sample.bevel.item">polygon</hash>
 +
</hash>
 +
</atom>
 +
 +
</configuration>
 +
</source>

Revision as of 12:10, 29 June 2016

Overview

This sample plugin will demonstrate how to create a very simple mesh operation that bevels some selected polygons. The evaluation function will handle the creation of the geometry, and the re-evaluation function will handle subsequent evaluations that simply offset the beveled geometry along the normal.

Mesh Operation

Constructor

Evaluation

Re-Evaluation

User Interface

The Code

Plugin

#include <lxsdk/lxidef.h>
#include <lxsdk/lx_mesh.hpp>
#include <lxsdk/lx_pmodel.hpp>
#include <lxsdk/lxu_attributes.hpp>
#include <lxsdk/lxu_math.hpp>
#include <vector>
 
#define SERVER_NAME	"sample.bevel"
 
/*
 *	The PointData structure is used to cache the incremental data for each point.
 *	It stores the element pointer, the non-offset position, as well as the normal.
 */
 
struct PointData
{
	LXtPointID		 id;
	LXtVector		 position;
	LXtVector		 normal;
};
 
/*
 *	The polygon visitor performs the bevel operation for a single polygon. It
 *	creates new points at the same position as the points on the polygon it is
 *	beveling. The polygon is then updated to use the new points and "wall" polygons
 *	are added bridging the old and new points. The new point information is cached
 *	to that it can work with the incremental re-evaluation.
 */
 
class PolygonVisitor : public CLxVisitor
{
	public:
		PolygonVisitor () : _cache (NULL) { }
 
			void
		evaluate ()			LXx_OVERRIDE
		{
			std::vector <LXtPointID> newPoints;
			std::vector <LXtPointID> oldPoints;
			LXtPolygonID		 polygon = NULL;
			LXtID4			 polygonType = 0;
			unsigned int		 pointCount = 0;
 
			polygon = _polygon.ID ();
 
			/*
			 *	For each point on the polygon, create a new point that
			 *	matches it's position. Add the point information to
			 *	the cache.
			 */
 
			_polygon.VertexCount (&pointCount);
 
			for (unsigned int i = 0; i < pointCount; i++)
			{
				PointData		 pointData;
				LXtPointID		 oldPoint = NULL;
				LXtFVector		 position;
 
				if (LXx_OK (_polygon.VertexByIndex (i, &oldPoint)))
				{
					_point.Select (oldPoint);
 
					/*
					 *	Get the normal and position of the
					 *	original point.
					 */
 
					_point.Normal (polygon, pointData.normal);
					_point.Pos (position);
					LXx_VCPY (pointData.position, position);
 
					/*
					 *	Copy the new point and store it in the
					 *	cache.
					 */
 
					_point.Copy (&pointData.id);
					_cache->push_back (pointData);
 
					/*
					 *	Add the new and old points to a list
					 *	so they can be enumerated to create
					 *	the wall polygons.
					 */
 
					newPoints.push_back (pointData.id);
					oldPoints.push_back (oldPoint);
				}
			}
 
			/*
			 *	Update the polygon to use the new points.
			 */
 
			_polygon.SetVertexList (&newPoints[0], newPoints.size (), 0);
 
			/*
			 *	Create "wall" polygons linking the old and new points.
			 */
 
			_polygon.Type (&polygonType);
 
			if (oldPoints.size () == newPoints.size ())
			{
				pointCount = oldPoints.size ();
 
				for (unsigned int i = 0; i < pointCount; i++)
				{
					LXtPointID		 wallPoints[4] = {NULL};
					LXtPolygonID		 wallPolygon = NULL;
 
					wallPoints[0] = oldPoints[i];
					wallPoints[1] = newPoints[i];
					wallPoints[2] = newPoints[(i+1)%pointCount];
					wallPoints[3] = oldPoints[(i+1)%pointCount];
 
					_polygon.NewProto (polygonType, wallPoints, 4, 1, &wallPolygon);
				}
			}
		}
 
		CLxUser_Polygon		 _polygon;
		CLxUser_Point		 _point;
		std::vector <PointData>	*_cache;
};
 
class MeshOperation : public CLxImpl_MeshOperation, public CLxDynamicAttributes
{
	public:
		MeshOperation ()
		{
			/*
			 *	A single attribute is added to the mesh operation to
			 *	control the bevel offset. This attribute is converted
			 *	to a channel on the automatically generated item.
			 */
 
			dyna_Add ("offset", LXsTYPE_DISTANCE);
			attr_SetFlt (0, 0.0);
		}
 
			LxResult
		mop_Evaluate (
			ILxUnknownID		 mesh_obj,
			LXtID4			 type,
			LXtMarkMode		 mode)			LXx_OVERRIDE
		{
			CLxUser_Mesh		 mesh (mesh_obj);
			PolygonVisitor		 visitor;
			LxResult		 result = LXe_FAILED;
 
			/*
			 *	If the offset amount is 0.0, then we want to do nothing.
			 */
 
			_offset = dyna_Float (0);
 
			if (lx::Compare (_offset, 0.0) == LXi_EQUAL_TO)
				return LXe_OK;
 
			if (LXx_OK (mesh.BeginEditBatch ()))
			{
				/*
				 *	Enumerate over the polygons and bevel each one.
				 */
 
				visitor._polygon.fromMesh (mesh);
				visitor._point.fromMesh (mesh);
				visitor._cache = &_points;
 
				visitor._polygon.Enumerate (mode, visitor, NULL);
 
				/*
				 *	Apply an offset to the beveled polygons.
				 */
 
				OffsetPositions (mesh);
 
				mesh.SetMeshEdits (LXf_MESHEDIT_GEOMETRY);
				mesh.EndEditBatch ();
 
				result = LXe_OK;
			}
 
			return result;
		}
 
			int
		mop_Compare (
			ILxUnknownID		 other_obj)		LXx_OVERRIDE
		{
			MeshOperation		*other = NULL;
 
			_offset = dyna_Float (0);
 
			/*
			 *	Cast the other interface into our implementation, and
			 *	then compare the offset attribute.
			 */
 
			lx::CastServer (SERVER_NAME, other_obj, other);
 
			if (other)
			{
				/*
				 *	If the offset is identical, we don't want to
				 *	do anything.
				 */
 
				if (lx::Compare (other->_offset, _offset) == LXi_EQUAL_TO)
					return LXiMESHOP_IDENTICAL;
 
				/*
				 *	As long as neither offset is 0.0, the previous
				 *	operation is compatible.
				 */
 
				if (lx::Compare (other->_offset, 0.0) != LXi_EQUAL_TO && lx::Compare (_offset, 0.0) != LXi_EQUAL_TO)
					return LXiMESHOP_COMPATIBLE;
			}
 
			return LXiMESHOP_DIFFERENT;
		}
 
			LxResult
		mop_Convert (
			ILxUnknownID		 other_obj)		LXx_OVERRIDE
		{
			MeshOperation		*other = NULL;
 
			_offset = dyna_Float (0);
 
			/*
			 *	Cast the other interface into our implementation, and
			 *	then copy the cached points that want to offset.
			 */
 
			lx::CastServer (SERVER_NAME, other_obj, other);
 
			if (other)
			{
				_points.clear ();
 
				for (unsigned int i = 0; i < other->_points.size (); i++)
				{
					_points.push_back (other->_points[i]);
				}
 
				return LXe_OK;
			}
 
			return LXe_FAILED;
		}
 
			LxResult
		mop_ReEvaluate (
			ILxUnknownID		 mesh_obj,
			LXtID4			 type)			LXx_OVERRIDE
		{
			CLxUser_Mesh		 mesh (mesh_obj);
 
			_offset = dyna_Float (0);
 
			/*
			 *	Deform the cached points by reapplying the offset.
			 */
 
			if (LXx_OK (OffsetPositions (mesh)))
				return mesh.SetMeshEdits (LXf_MESHEDIT_POSITION);
 
			return LXe_FAILED;
		}
 
			LxResult
		OffsetPositions (
			CLxUser_Mesh		&mesh)
		{
			CLxUser_Point		 point;
			LxResult		 result = LXe_FAILED;
			int			 i = 0;
 
			if (mesh.test ())
			{
				point.fromMesh (mesh);
 
				for (i = 0; i < _points.size (); i++)
				{
					PointData		*pointData = NULL;
					LXtVector		 pos;
 
					pointData = &_points[i];
 
					if (LXx_OK (point.Select (pointData->id)))
					{
						LXx_VCPY (pos, pointData->normal);
						LXx_VSCL (pos, _offset);
						LXx_VADD (pos, pointData->position);
 
						point.SetPos (pos);
					}
				}
 
				result = LXe_OK;
			}
 
			return result;
		}
 
		double			 _offset;
		std::vector <PointData>	 _points;
 
		static LXtTagInfoDesc	 descInfo[];
};
 
LXtTagInfoDesc MeshOperation::descInfo[] =
{
	{ LXsMESHOP_PMODEL,		"." },
	{ LXsPMODEL_SELECTIONTYPES,	LXsSELOP_TYPE_POLYGON },
	{ 0 }
};
 
void initialize ()
{
	CLxGenericPolymorph	*srv = NULL;
 
	srv = new CLxPolymorph						<MeshOperation>;
	srv->AddInterface		(new CLxIfc_MeshOperation	<MeshOperation>);
	srv->AddInterface		(new CLxIfc_Attributes		<MeshOperation>);
	srv->AddInterface		(new CLxIfc_StaticDesc		<MeshOperation>);
 
	lx::AddServer (SERVER_NAME, srv);
}

Configs

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
 
	<!--
		The CommandHelp block is used to define the item and channel usernames.
	-->
 
	<atom type="CommandHelp">
		<hash type="Item" key="sample.bevel.item@en_US">
			<atom type="UserName">Bevel Sample</atom>
			<hash type="Channel" key="enable">
				<atom type="UserName">Enable</atom>
			</hash>
			<hash type="Channel" key="offset">
				<atom type="UserName">Offset</atom>
			</hash>
		</hash>
	</atom>
 
	<!--
		The properties form displays a single property to control the offset.
		The common mesh operation sheet is also included to add the enable
		checkbox to the top of the form.
	-->
 
	<atom type="Attributes">
		<hash type="Sheet" key="sample.bevel.item:sheet">
			<atom type="Label">Bevel Sample</atom>
			<atom type="Filter">sample.bevel.item:filterPreset</atom>
			<hash type="InCategory" key="itemprops:general#head">
				<atom type="Ordinal">110</atom>
			</hash>
			<list type="Control" val="ref item-common:sheet">
				<atom type="StartCollapsed">0</atom>
				<atom type="Hash">#0</atom>
			</list>
			<list type="Control" val="ref meshoperation:sheet">
				<atom type="StartCollapsed">0</atom>
				<atom type="Hash">#1</atom>
			</list>
			<list type="Control" val="cmd item.channel sample.bevel.item$offset ?">
				<atom type="Label">Offset</atom>
				<atom type="StartCollapsed">0</atom>
				<atom type="Hash">sample.bevel.item.offset.ctrl:control</atom>
			</list>
		</hash>
	</atom>
 
	<!--
		A filter determines when the properties form should be displayed. In this
		case the properties form should be displayed when the sample.bevel.item
		item type is selected.
	-->
 
	<atom type="Filters">
		<hash type="Preset" key="sample.bevel.item:filterPreset">
			<atom type="Name">Bevel Sample</atom>
			<atom type="Description"></atom>
			<atom type="Category">pmodel:filterCat</atom>
			<atom type="Enable">1</atom>
			<list type="Node">1 .group 0 &quot;&quot;</list>
			<list type="Node">1 itemtype 0 1 &quot;sample.bevel.item&quot;</list>
			<list type="Node">-1 .endgroup </list>
		</hash>
	</atom>
 
	<!--
		The categories define which sub-category the mesh operation will appear
		in. Two categories have to be set, one that shows all Mesh Operations
		and another which is filtered to show only operations that support a
		specific selection type.
	-->
 
	<atom type="Categories">
		<hash type="Category" key="MeshOperations">
			<hash type="C" key="sample.bevel.item">polygon</hash>		
		</hash>
 
		<hash type="Category" key="MeshOperationsPolygons">
			<hash type="C" key="sample.bevel.item">polygon</hash>
		</hash>
	</atom>
 
</configuration>