Virtools Building Blocks in Visual Studio net 2003
/ Virtools Building Blocks in Visual Studio net 2003This tutorial a way to get Virtools building blocks to build using Visual Studio .net 2003. Apparently they still use Visual C++ 6.0 at Virtools (given the price of VS.net 2003 i cant really blame them). My problem is that the only VS available at MSDNAA (microsoft developer network academic alliance) so i needed to make it work. By the way i use Virtools Dev 3.0 - so i don't know if it is backwards compatible - please Colophon write me if you get this to work on something other than 3.0.
There is still much of this i haven't figured out yet, but at least i can get a box to fly aimlessly out the screen.
In the tutorial please substitute $VIRTOOLS with your installation of Virtools Dev.
Setting up the project in VS.net 2003
Start a new project and select Visual C++ Projects->MFC->MFC DLL and select a name for the project. VS will put up a wizard of some sorts, just press Finish.
You can safely delete stdafx.h and stdafx.cpp we will only use the Virtools header files.
Breaking news: You should only delete stdafx.h or else VS.net will bitch about some error. stdafx.cpp should be completely wiped, and replaced by #include "CKAll.h". Don't ask why, cause i don't know.
You can now return to the originally scheduled show ;-)
First thing to do is to set up the properties of the project, click Project-><project name> Properties.
The first thing to do is to tell VS to use Virtools's header files and libraries.
Enter Configuration Properties->C/C++->General and edit the Additional Include Directories value, and add $VIRTOOLS/Sdk/Includes to the list of directories.
Set Configuration Properties->C/C++->Precompiled Headers set Create/Use PCH Through File to CKAll.h.
Now to configure the linker.
In Configuration Properties->Linker->General you should set Additional Library Directories to $VIRTOOLS/Sdk/Lib.
To avoid copying the DLL file from the output dir to $VIRTOOLS/BuildingBlocks after each build, it is usable to set Output File to $VIRTOOLS/BuildingBlocks/<project name>.dll, but be warned that if your DLL fixes f*ck up, Virtools will be unable to start - but that is easily fixed by just removing the DLL file.
Now go to Configuration Properties->Linker->Input and in Additional Dependencies you should add ck2.lib and vxmath.lib.
Defining the exported symbols
Double-click on the .def file in the "solution explorer", this is where you define which symbols are "publicly" viewable in the DLL file you are going to create.
Virtools building blocks exports 3 symbols/functions, these are:
CKGetPluginInfo CKGetPluginInfoCount RegisterBehaviorDeclarations
The first two functions are used to retrieve the plugins in this DLL file, yes, each DLL can hold several plugins.
We will get back to those functions later. == #includes and exported functions
Before starting any coding it is usable to just flush the entire <project name>.cpp and <project name>.h files, and start from scratch.
The first statement is to include the Virtools SDK main header:
#include "CKAll.h"
You might think that it would be cleaner to #include in the <project name>.h file, but then you end up with some can't find precombiled header errors, so just include it in each file (only one file for now).
Another fix: You should leave the #include "resource.h" line in <project name>.h file. At least, that works on my setup.
We can now define the three exported functions, for now we will assume that we only have one plugin in this DLL (you can still have several building blocks in the same plugin).
First up is to define the function for retrieving the number of plugins:
PLUGIN_EXPORT int CKGetPluginInfoCount() {
return 1;
}
PLUGIN_EXPORT is apparently needed to export the symbol, rather than the C standard extern keyword. The function simply returns an integer.
Now we need a function to return a CKPluginInfo object and return one for each plugin. The DLL is responsible for managing the memory of these returned object, so the easiest thing to do is to just define a CKPluginInfo globally and return a pointer to that when CKGetPluginInfo is called.
CKPluginInfo g_PluginInfo;
And now we are ready to define the function.
PLUGIN_EXPORT CKPluginInfo* CKGetPluginInfo(int Index) {
switch (Index) {
case 0:
g_PluginInfo.m_Author="halfdan";
g_PluginInfo.m_Description="Test Building Blocks";
g_PluginInfo.m_Extension="";
g_PluginInfo.m_Type=CKPLUGIN_BEHAVIOR_DLL;
g_PluginInfo.m_Version=0x000001;
g_PluginInfo.m_InitInstanceFct = 0;
g_PluginInfo.m_ExitInstanceFct = 0;
g_PluginInfo.m_GUID=PLUGIN_GUID;
g_PluginInfo.m_Summary="Building blocks that are quite likely _not_ usable.";
break;
}
return &g_PluginInfo;
}
So far i have only explored the CKPLUGIN_BEHAVIOR_DLL type, but i am sure that others exist.
The m_InitIstanceFct and m_ExitIstanceFct are set to 0 here, but can be used to create "manager" objects, e.g. the collision manager. The signature of the methods are:
CKERROR InitInstance(CKContext* context); CKERROR ExitInstance(CKContext* context);
And they return CK_OK on success.
The last exported function defines the building blocks contained in the plugin. Our initial implementation of this function is just to do nothing, since we have not defined any building blocks yet.
void RegisterBehaviorDeclarations(XObjectDeclarationArray *reg) {
}
Later on, when we define building blocks we use RegisterBehavior ( reg, FillBehaviorBuildingBlock ); calls inside this function.
Only one thing left to do now, before compiling the first version of the building block. You need to define GUIDs for your plugin, a GUID is a unique 2 32-bit number tuple that distinguish your "stuff" from Virtools "stuff", in the $VIRTOOLS/Sdk/Utils dir, there is a program called CKGuidGen you can use it to create your GUIDs.
The easiest way to work with GUIDs is to just define them using #define, you should start by creating three GUIDs, one for your plugin, one for you (as author of a building block) and one for the building block you will create shortly.
#define PLUGIN_GUID CKGUID(0x534d2902,0x5b700c48) #define AUTHOR_GUID CKGUID(0x46655a9b,0x24f3799a) #define BUILDING_BLOCK_GUID CKGUID(0x442019ec,0x5e9e1f9a)
I suggest defining these in the <project name>.h header file because they will be used by all building blocks you create in this DLL.
The PLUGIN_GUID is used in the CKGetPluginInfo function previously defined. The two other GUIDs will be used later when you create your first building block.
The first compile and test
You should now have enough code to try and compile your project press Ctrl-Shift-B and start building.
Hopefully the DLL is compiled and you should be able to start Virtools and see the plugin inside the Options->Installed Plugins dialog box.
Ok, now on to making the first building block. We will create a building block that just moves an 3d entity along a vector.
Filling a CKObjectDeclaration
First we need to define the building block's author, guid, version, category, etc. This is done in the RegisterBehaviorDeclarations function we defined earlier.
For each building block we need to create a method that returns a CKObjectDeclaration object, and register that in RegisterBahaviorDeclarations using RegisterBehavior. I suggest putting each building block in a separate .cpp file, then you just need to define the needed functions in the header file of the project.
First the function.
CKObjectDeclaration *FillBehaviorTestBehavior () {
CKObjectDeclaration *od = CreateCKObjectDeclaration ( "Test Behavior" );
od->SetDescription ( "Translate an 3d entity using a vector." );
od->SetCategory ( "3D Transformations/Constraint" );
od->SetType ( CKDLL_BEHAVIORPROTOTYPE );
od->SetGuid ( BUILDING_BLOCK_GUID );
od->SetAuthorGuid ( AUTHOR_GUID );
od->SetAuthorName ( "halfdan" );
od->SetVersion ( 0x00000001 );
od->SetCreationFunction ( CreateTestBehaviorProto );
od->SetCompatibleClassId ( CKCID_3DENTITY );
return od;
}
Most of it is self explanatory, first define a name, then a description, then the category where the building block should be located in Virtools' building block browser. Then you define the type, i have only used CKDLL_BEHAVIORPROTOTYPE so i don't know what other there are. You set the GUID of the building block, using the #define'ed value from the header file. Set the author GUID, the author name, the version (i haven't discovered how that value is interpreted yet, there probably are a CKVERSION(x,y,z) macro).
The defined creation function is the function that should be called to get the "protocol" or "interface" for the building block, that is, what parameters the building block takes. I'm not sure what SetCompatibleClassID does, but i'm guessing it defines the objects types that can hold this building block in their script, CKCID_3DENTITY seems to cover every 3d entity in Virtools.
Now you should define this function in the header file of the project, you might as well just define the two other functions needed to get the building block to work completely.
CKObjectDeclaration *FillBehaviorTestBehavior (); CKERROR CreateTestBehaviorProto ( CKBehaviorPrototype **pproto); int TestBehavior(const CKBehaviorContext& behcontext);
We haven't mentioned the last function before, it is the function that does the actual "work" of the building block, transforming, rotating or whatever else is needed.
Defining the protocol
Now it is time to define the protocol for the building block, this is done in the following function.
CKERROR CreateTestBehaviorProto ( CKBehaviorPrototype **pproto) {
CKBehaviorPrototype *proto = CreateCKBehaviorPrototype("Test Behavior");
if(!proto) return CKERR_OUTOFMEMORY;
proto->DeclareInput("In");
proto->DeclareOutput("Out");
proto->DeclareInParameter("Object", CKPGUID_3DENTITY );
proto->DeclareInParameter("Movement Vector", CKPGUID_VECTOR );
proto->SetFlags(CK_BEHAVIORPROTOTYPE_NORMAL);
proto->SetFunction(TestBehavior);
proto->SetBehaviorFlags(CKBEHAVIOR_TARGETABLE);
*pproto = proto;
return CK_OK;
}
First we create the Behavior prototype (or protocol) object.
The first thing is to define the In and Out points, this is where the building block is called by other building blocks, and where it transfers control on to other building blocks. I think it is Virtools policy to name these In and Out.
We also need to declare the parameters for the building block, since we want our building block to move an object, we define one parameter as an CKPGUID_3DENTITY, this means that this parameter can only hold 3D entities. We also define a vector to move the object along, as input parameter.
Some flags are set, i have no idea what this means, but CK_BEHAVIORPROTOTYPE_NORMAL sounds ok, so i stick with that.
We also set the behavior flag CKBEHAVIOR_TARGETABLE which means that this building block can target another object, than the one who's scipt it is attached to.
The last thing to do is define the function to call when the building block is invoked.
Making sure everything works so far
It is always nice to see that what you have so far is working, so just define the "invoke" function as simple as possible and return CBKR_OK to indicate that the building block was succesfully invoked.
int TestBehavior(const CKBehaviorContext& behcontext) {
return CKBR_OK;
}
Now it's time to try and build the building block. Ctrl-Shift-B to build, and fire up Virtools.
If everything worked out fine you should see your building block in the category you defined.
Now you can create an 3d entity and drag your script onto the entity, or into the entity's script/schematic area.
You can also double-click on the building block in the schematics view and get the parameters editor dialog. Notice that the parameters defined in the "protocol" function are available for editing in the dialog.
Implementing the "invoke" function
While this is not a tutorial on writing the actual Virtools building block, it is nice to get a little example to get a feel of how the Virtools SDK works. All this code goes in the int TestBehavior(const CKBehaviorContext& behcontext); function.
The first thing to do is to retrieve the parameters from the behavior context.
CKBehavior* beh = behcontext.Behavior;
// Set IO states
beh->ActivateInput(0,FALSE);
beh->ActivateOutput(0);
// Get the Owner Object
CK3dEntity *Ent1 = (CK3dEntity *) beh->GetTarget();
if( !Ent1 ) return CKBR_OWNERERROR;
// Get the Object
CK3dEntity *Ent2 = (CK3dEntity *) beh->GetInputParameterObject(0);
if(!Ent2) return CKBR_OWNERERROR;
VxVector transvec;
beh->GetInputParameterValue(1, &transvec);
We retrieve the behavior frm the behavior context, we set the io states (no, i don't know what this does) - we get the owner object (target object), the object in which this building block resides.
Then we start retrieving the parameters, parameters are indexed using an integer rather than their name, so keep the parameter order in mind!
All that is left now is to call the function on the object to be translated, we translate it according to itself along the movement_vector and the third parameter tells Translate to also translate the children of this object.
obj->Translate ( &movement_vector, obj, TRUE );
Time to compile/test again
After yet another compile you should set up a little scene with a camera, an object and your own building block in the camera script. Edit the parameters of the building block and set the object to the object in the scene, and the vector to something like (0, 0, -1) to have the object moving towards the camera. Remember to set initial conditions on the object to be able to go back to ... the initial condition after the object has been moved out of camera view.
If you try and run the script, nothing happens! Hmm. Well this is because you need to call the building-block constantly so the translation keeps occuring each "tick" - so construct a loop and try again. Calling the building block again and again
And there you have it folks! Your own little building block! Joins us next week when we try something even more fun, like shaving our testicles using a meat cleaver!