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:
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
{
private:
lwpp::LWNodeOutput *outDiffuse, *outSpecular, *outReflection, *outRefraction, *outTransparency;
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 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);
};
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).
{
{"Material Blender",SRVTAG_USERNAME},
{"db&w/Materials",SRVTAG_NODEGROUP},
};
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.
{
*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.
{
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.
{
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.
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:
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),
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:
{
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.
{
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).
{
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.
#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[] =
{
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 )
{
default: break;
}
}
return err;
}
Saving is even easier, especially since the MaterialComponent class used in this sample is derived from
lwpp::Storeable
{
LWError err = 0;
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[] =
{
0
};
{
public:
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 evaluate(
LWNodalAccess *na, LWDVector bg, LWDVector fg, LWDVector &out)
{
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;
}
{
LWError err = 0;
while(LWID
id = ls.
Find(idcomp))
{
switch( id )
{
default: break;
}
}
return err;
}
{
LWError err = 0;
return 0;
}
};