LightWrap++
AC++wrapperfortheLightWave3DSDK
A more complex, complete plugin

This is the source code for a complete shading node.

Note:
This document covers LightWrap++ and not the LightWave SDK. A fairly thorough understanding of the LightWave SDK is required to understand the details of this sample. While lwpp wraps the LightWave 3D SDK it does not replace many of the concepts, it only expands upon them.
First you need to include the appropriate headers:
#include <lwpp/vparm.h>

Plugin class definition

The next step is to actually define the class.

Since this will be a Node with an XPanel GUI, it will be derived from lwpp::XPanelNodeHandler.

You only need to actually define and implement the member functions you need. However, the constructor is essential.

Note:
Don't worry about the MaterialComponent, this will be covered later

class MaterialMixer : public lwpp::XPanelNodeHandler
{
private:
lwpp::LWNodeOutput *outMaterial;
lwpp::LWNodeOutput *outDiffuse, *outSpecular, *outReflection, *outRefraction, *outTransparency;
lwpp::LWNodeInput *inBaseMaterial, *inTopMaterial;
lwpp::LWNodeInput *inBaseDiffuse, *inBaseSpecular, *inBaseReflection, *inBaseRefraction, *inBaseTransparency;
lwpp::LWNodeInput *inTopDiffuse, *inTopSpecular, *inTopReflection, *inTopRefraction, *inTopTransparency;
MaterialComponent Diffuse, Specular, Reflection, Refraction, Transparency;
public:
MaterialMixer (void *priv, void *context, LWError *err);
~MaterialMixer ();
virtual void Evaluate (LWNodalAccess *na, NodeOutputID outID, NodeValue value);
virtual int Interface (int version, LWInterface *local, void *serverdata);
virtual void *DataGet (unsigned int vid);
virtual LWXPRefreshCode DataSet (unsigned int vid, void *value);
virtual MaterialMixer &operator=(const MaterialMixer &from);
virtual LWError NewTime (LWFrame frame, LWTime time);
virtual void ChangeNotify (LWXPanelID panel, unsigned int cid, unsigned int vid, int event_type);
virtual LWError Load (const lwpp::LoadState &ls);
virtual LWError Save (const lwpp::SaveState &ss);
};

Registering the plugin

The next step is to register the plugin with lwpp. This will allow lwpp to create the plugin once the plugin file is loaded into LightWave. (For seasoned developers: This completely replaces the need to create a ServerDesc or _mod_descrip).

ServerTagInfo tags[] =
{
{"Material Blender",SRVTAG_USERNAME},
{"db&w/Materials",SRVTAG_NODEGROUP},
{ NULL }
};

Initializing the plugin, basic member functions

Let's have a look at the constructor of the plugin.

MaterialMixer::MaterialMixer (void *priv, void *context, LWError *err)
: lwpp::XPanelNodeHandler(priv, context, err)
{
You need to initilize the base classe lwpp::XPanelNodeHandler with the parameters to your plugin. These are the same for most kinds of plugins.

The following lines check the version of LW and will return an error message if LightWave is of a version earlier than 9.2 (since this is when Materials got introduced). The error message will be returned to LightWave and displayed, initializing the plugin will fail and the destructor will be called.

if ( (lwpp::LightWave::Major() < 9) ||
{
*err = "This plugin needs a newer version of LightWave 3D (9.2 as a minimum)";
return;
}
This is how inputs are added to the node. The variables are member variables.
inBaseMaterial = addMaterialInput("Fg Material");
inBaseDiffuse = addColourInput("Fg Diffuse");
inBaseSpecular = addColourInput("Fg Specular");
inBaseReflection = addColourInput("Fg Reflection");
inBaseRefraction = addColourInput("Fg Refraction");
inBaseTransparency = addScalarInput("Fg Transparency");
inTopMaterial = addMaterialInput("Bg Material");
inTopDiffuse = addColourInput("Bg Diffuse");
inTopSpecular = addColourInput("Bg Specular");
inTopReflection = addColourInput("Bg Reflection");
inTopRefraction = addColourInput("Bg Refraction");
inTopTransparency = addScalarInput("Bg Transparency");
Attaching outputs to the node is quite similar.
outMaterial = addMaterialOutput("Material");
outDiffuse = addColorOutput("Diffuse");
outSpecular = addColorOutput("Specular");
outReflection = addColorOutput("Reflection");
outRefraction = addColorOutput("Refraction");
outTransparency = addScalarOutput("Transparency");
Here we initialize our MaterialComponent classes - again, more on that later.
Diffuse.Initialize(*this, "Diffuse");
Specular.Initialize(*this, "Specular");
Reflection.Initialize(*this, "Reflection");
Refraction.Initialize(*this, "Refraction");
Transparency.Initialize(*this, "Transparency");
}
The Destructor does nothing in this case.
MaterialMixer::~MaterialMixer ()
{
;
}
Things are starting to heat up a little. lwpp uses operator=() to copy instances of the plugin. In this case you need to manually copy the MaterialComponent class.
MaterialMixer &MaterialMixer::operator=(const MaterialMixer &from)
{
if (&from != this)
{
Diffuse = from.Diffuse;
Specular = from.Specular;
Reflection = from.Reflection;
Refraction = from.Refraction;
Transparency = from.Transparency;
}
return *this;
}
NewTime is called once per pass when rendering, or whenever the current frame changes. In this case the NewTime member function of the MaterialComponent member variables are called.

This is usually a good place to update lwpp::VParm for example.

LWError MaterialMixer::NewTime (LWFrame frame, LWTime time)
{
Diffuse.NewTime(frame, time);
Specular.NewTime(frame, time);
Reflection.NewTime(frame, time);
Refraction.NewTime(frame, time);
Transparency.NewTime(frame, time);
return 0;
}
Evaluate is where the magic happens. In this case the current values of the inputs are retrieved and used for processing. setValue() (a member function of lwpp::Node, that lwpp::NodeHandler and thus lwpp::XPanelNodeHandler derive from) is used to set the output value for the current output that is evaluated.
void MaterialMixer::Evaluate (LWNodalAccess *na, NodeOutputID outID, NodeValue value)
{
LWNodalMaterial bgMat = {{0.0, 0.0, 0.0},{0.0, 0.0, 0.0},{0.0, 0.0, 0.0},{0.0, 0.0, 0.0},0};
LWNodalMaterial fgMat = {{0.0, 0.0, 0.0},{0.0, 0.0, 0.0},{0.0, 0.0, 0.0},{0.0, 0.0, 0.0},0};
LWNodalMaterial outMat= {{0.0, 0.0, 0.0},{0.0, 0.0, 0.0},{0.0, 0.0, 0.0},{0.0, 0.0, 0.0},0};
inBaseMaterial->evaluate(na, bgMat);
inTopMaterial->evaluate(na, fgMat);
if (outMaterial->isID(outID))
{
inBaseDiffuse->evaluate(na, bgMat.diffuse);
inBaseSpecular->evaluate(na, bgMat.specular);
inBaseReflection->evaluate(na, bgMat.reflection);
inBaseRefraction->evaluate(na, bgMat.refraction);
inBaseTransparency->evaluate(na, bgMat.transparency);
inTopDiffuse->evaluate(na, fgMat.diffuse);
inTopSpecular->evaluate(na, fgMat.specular);
inTopReflection->evaluate(na, fgMat.reflection);
inTopRefraction->evaluate(na, fgMat.refraction);
inTopTransparency->evaluate(na, fgMat.transparency);
Diffuse.evaluate(na, bgMat.diffuse, fgMat.diffuse, outMat.diffuse);
Specular.evaluate(na, bgMat.specular, fgMat.specular, outMat.specular);
Reflection.evaluate(na, bgMat.reflection, fgMat.reflection, outMat.reflection);
Refraction.evaluate(na, bgMat.refraction, fgMat.refraction, outMat.refraction);
Transparency.evaluate(na, bgMat.transparency, fgMat.transparency, outMat.transparency);
setValue(value, outMat);
}
else if (outDiffuse->isID(outID))
{
inBaseDiffuse->evaluate(na, bgMat.diffuse);
inTopDiffuse->evaluate(na, fgMat.diffuse);
Diffuse.evaluate(na, bgMat.diffuse, fgMat.diffuse, outMat.diffuse);
setValue(value, outMat.diffuse);
}
else if (outSpecular->isID(outID))
{
inBaseSpecular->evaluate(na, bgMat.specular);
inTopSpecular->evaluate(na, fgMat.specular);
Specular.evaluate(na, bgMat.specular, fgMat.specular, outMat.specular);
setValue(value, outMat.specular);
}
else if (outReflection->isID(outID))
{
inBaseReflection->evaluate(na, bgMat.reflection);
inTopReflection->evaluate(na, fgMat.reflection);
Reflection.evaluate(na, bgMat.reflection, fgMat.reflection, outMat.reflection);
setValue(value, outMat.reflection);
}
else if (outRefraction->isID(outID))
{
inBaseRefraction->evaluate(na, bgMat.refraction);
inTopRefraction->evaluate(na, fgMat.refraction);
Refraction.evaluate(na, bgMat.refraction, fgMat.refraction, outMat.refraction);
setValue(value, outMat.refraction);
}
else if (outTransparency->isID(outID))
{
inBaseTransparency->evaluate(na, bgMat.transparency);
inTopTransparency->evaluate(na, fgMat.transparency);
Transparency.evaluate(na, bgMat.transparency, fgMat.transparency, outMat.transparency);
setValue(value, outMat.transparency);
}
}
Next we'll define an enum for the IDs of the XPanel controls as well as a static array of strings for the pop-up defining the blend mode.
enum { BDiff = 0x9000, MDiff, BSpec, MSpec, BRefl, MRefl, BRefr, MRefr, BTran, MTran};
static char *blendModeS[] =
{
"Normal", "Additive", "Subtractive", "Multiply", "Screen", "Darken", "Lighten", "Difference", "Negative", "Colour Dodge", "Colour Burn", "Red", "Green", "Blue", 0
};

Creating an XPanels GUI

Now we're ready to construct our interface, this is done by overriding the Interface() method of the base plugin:

int MaterialMixer::Interface (int version, LWInterface *local, void *serverdata)
Now we define the LWXPanelControl and LWXPanelDataDesc arrays for XPanel as well as our hints for the GUI. This is identical to using the C SDK for XPanels.
{
static LWXPanelControl ctrl[] =
{
{MDiff, "Diffuse Mode", "iPopChoice"},
{BDiff, "Diffuse Opacity", "percent-env"},
{MSpec, "Specular Mode", "iPopChoice"},
{BSpec, "Specular Opacity", "percent-env"},
{MRefl, "Reflection Mode", "iPopChoice"},
{BRefl, "Reflection Opacity", "percent-env"},
{MRefr, "Refraction Mode", "iPopChoice"},
{BRefr, "Refraction Opacity", "percent-env"},
{MTran, "Transparency Mode", "iPopChoice"},
{BTran, "Transparency Opacity", "percent-env"},
0
};
static LWXPanelDataDesc desc[] =
{
{MDiff, "Diffuse Mode", "integer"},
{BDiff, "Diffuse Opacity", "float-env"},
{MSpec, "Specular Mode", "integer"},
{BSpec, "Specular Opacity", "float-env"},
{MRefl, "Reflection Mode", "integer"},
{BRefl, "Reflection Opacity", "float-env"},
{MRefr, "Refraction Mode", "integer"},
{BRefr, "Refraction Opacity", "float-env"},
{MTran, "Transparency Mode", "integer"},
{BTran, "Transparency Opacity", "float-env"},
};
static LWXPanelHint hints[] =
{
XpSTRLIST(MDiff, blendModeS),
XpSTRLIST(MSpec, blendModeS),
XpSTRLIST(MRefl, blendModeS),
XpSTRLIST(MRefr, blendModeS),
XpSTRLIST(MTran, blendModeS),
XpDIVADD(BDiff),
XpDIVADD(BSpec),
XpDIVADD(BRefl),
XpDIVADD(BRefr),
XpEND,
};
CreateViewXPanel() creates the panel and the ID is returned to LW so it can handle it if needed.
CreateViewXPanel (ctrl, desc, hints);
local->panel = LW_XPanel.getID();
return AFUNC_OK;
}
Now that we have created the XPanel GUI we need to manage the change of user interface controls. Again, this is done by overriding member functions of the base class.

DataGet() returns the value of a control to LW to display:

void *MaterialMixer::DataGet (unsigned int vid)
{
switch (vid)
{
case MDiff: return Diffuse.ModeGet(); break;
case MSpec: return Specular.ModeGet(); break;
case MRefl: return Reflection.ModeGet(); break;
case MRefr: return Refraction.ModeGet(); break;
case MTran: return Transparency.ModeGet(); break;
case BDiff: return Diffuse.OpacityGet(); break;
case BSpec: return Specular.OpacityGet(); break;
case BRefl: return Reflection.OpacityGet(); break;
case BRefr: return Refraction.OpacityGet(); break;
case BTran: return Transparency.OpacityGet(); break;
default:
break;
}
return 0;
}
DataSet() is LightWave telling the plugin that the value of a control changed. We store the changed value in our member variables.
LWXPRefreshCode MaterialMixer::DataSet (unsigned int vid, void *value)
{
int *i = static_cast<int *>(value);
switch (vid)
{
case MDiff: Diffuse.Mode = (BlendingMode)*i; break;
case MSpec: Specular.Mode = (BlendingMode)*i; break;
case MRefl: Reflection.Mode = (BlendingMode)*i; break;
case MRefr: Refraction.Mode = (BlendingMode)*i; break;
case MTran: Transparency.Mode = (BlendingMode)*i; break;
default:
break;
}
return LWXPRC_DFLT;
}
ChangeNotify() again is a member function override. It is set up automatically by lwpp.

In this case we update the node preview (yet another member function provided by lwpp for Nodes) if a value has changed while the user is changing a control. Once the user releases the control, we perform a full Update() (as you guessed, yet another member function provided by lwpp).

void MaterialMixer::ChangeNotify (LWXPanelID panel, unsigned int cid, unsigned int vid, int event_type)
{
if (event_type == LWXPEVENT_TRACK)
{
UpdateNodePreview();
}
else if (event_type == LWXPEVENT_VALUE)
{
Update();
}
}
This concludes the GUI code and we can proceed with loading and saving our variables.

Loading and saving settings

We start off by defining LWIDs and a LWBlockIdent struct for the LightWave i/o system.

// defines etc. for file i/o
#define IO_CDIF LWID_('C','D','I','F')
#define IO_CSPC LWID_('C','S','P','C')
#define IO_CRFL LWID_('C','R','F','L')
#define IO_CRFR LWID_('C','R','F','R')
#define IO_CTRN LWID_('C','T','R','N')
static LWBlockIdent idroot[] =
{
IO_CDIF, "Diffuse", // 0
IO_CSPC, "Specular", // 1
IO_CRFL, "Reflection", // 2
IO_CRFR, "Refrection", // 3
IO_CTRN, "Transparency", // 4
0
};
Loading is quite similar to using LWs native system. We advise to use nested blocks. All classes derived from lwpp::Storeable can be passed directly to a lwpp::LoadState or lwpp::SaveState for i/o.
{
LWError err = 0;
while(LWID id = ls.Find(idroot))
{
switch( id )
{
case IO_CDIF: ls.read(Diffuse); break;
case IO_CSPC: ls.read(Specular); break;
case IO_CRFL: ls.read(Reflection); break;
case IO_CRFR: ls.read(Refraction); break;
case IO_CTRN: ls.read(Transparency); break;
default: break;
}
ls.End();
}
return err;
}
Saving is even easier, especially since the MaterialComponent class used in this sample is derived from lwpp::Storeable
{
LWError err = 0;
ss.WriteItem(idroot[0], Diffuse);
ss.WriteItem(idroot[1], Specular);
ss.WriteItem(idroot[2], Reflection);
ss.WriteItem(idroot[3], Refraction);
ss.WriteItem(idroot[4], Transparency);
return 0;
}

The MaterialComponent class

This class is a helper class to manage the components used in the material blender and to reduce the amount of duplicate code:

#define IO_MODE LWID_('M','O','D','E')
#define IO_OPAC LWID_('O','P','A','C')
static LWBlockIdent idcomp[] =
{
IO_MODE, "Mode", // 0
IO_OPAC, "Opacity", // 1
0
};
class MaterialComponent : public lwpp::Storeable
{
public:
lwpp::LWNodeInput *inOpacity;
lwpp::VParm *Opacity;
BlendingMode Mode;
MaterialComponent()
: Mode(Blend_Normal),
inMode(0), inOpacity(0),
Opacity(0)
{
;
}
MaterialComponent &operator=(const MaterialComponent &from)
{
*Opacity = *from.Opacity;
Mode = from.Mode;
return *this;
}
void Initialize(lwpp::NodeHandler &node, std::string name)
{
inOpacity = node.addScalarInput(name + " Opacity");
inMode = node.addIntegerInput(name + " Mode");
Opacity = new lwpp::VParm(LWVP_PERCENT , name+"Opacity", pluginName, node.chanGrp());
}
void evaluate(LWNodalAccess *na, LWDVector bg, LWDVector fg, LWDVector &out)
{
double opacity = Opacity->GetValue();
inOpacity->evaluate(na, opacity);
int mode = (int)Mode;
inMode->evaluate(na, mode);
nodeUtil.Blend(out, bg, fg, opacity, (BlendingMode)lwpp::Clamp(mode, 0, 13));
}
void evaluate(LWNodalAccess *na, double bg, double fg, double &out)
{
double opacity = Opacity->GetValue();
inOpacity->evaluate(na, opacity);
int mode = (int)Mode;
inMode->evaluate(na, mode);
LWDVector f = {fg, 0.0, 0.0};
LWDVector b = {bg, 0.0, 0.0};
LWDVector o = {out, 0.0, 0.0};
nodeUtil.Blend(o, b, f, opacity, (BlendingMode)lwpp::Clamp(mode, 0, 13));
out = o[0];
}
void *ModeGet ()
{
if (!inMode->isConnected()) return &Mode;
return 0;
}
void *OpacityGet ()
{
if (!inOpacity->isConnected()) return Opacity->ID();
return 0;
}
LWError NewTime (LWFrame frame, LWTime time)
{
Opacity->NewTime(time);
return 0;
}
virtual LWError Load (const lwpp::LoadState &ls)
{
LWError err = 0;
while(LWID id = ls.Find(idcomp))
{
switch( id )
{
case IO_MODE: Mode = (BlendingMode)ls.readInt(); break;
case IO_OPAC: ls.read(*Opacity); break;
default: break;
}
ls.End();
}
return err;
}
virtual LWError Save (const lwpp::SaveState &ss)
{
LWError err = 0;
ss.WriteBlock(idcomp[0], (int)Mode);
ss.WriteItem(idcomp[1], *Opacity);
return 0;
}
};