An application programmers interface exists to allow the construction of user written collaborative modules. These modules can then operate within the COVISA framework in the same way as any other collaborative module. All of the collaborative data sharing modules supplied with this release use this library, and their source code can be found in $EXPLORERHOME/src, where $EXPLORERHOME is the root directory of the installed IRIS Explorer tree. Some annotated source code is available, see Section 3.2.
To compile a collaborative module, you must select Collaborative under Module->Options... within Module Builder. You must write your collaborative code in C++; this may of course be combined with other code written in C or Fortran.
cxCollab – collaboration class library.
#include <cx/cxCollab.h>
cxCollab(void); ~cxCollab(void);
int portRegister(int numPorts, char **names, int *portTypes, int *dataTypes); int portRegister(char *name, int portType, int dataType);
void checkWidgets(void); int isConnected(void); int newData(char *name);
int setData(char *name, void *ptr); int sendData(int numPorts, char **names); int sendData(char *name); void clearData(char *name);
cxLattice * getLattice(char *name); cxParameter * getParameter(char *name); cxPyramid * getPyramid(char *name); cxPick * getPick(char *name); cxGeo * getGeometry(char *name);
const char * getOwnUserName(void); const char * getUName(char *name);
This class is used to make IRIS Explorer modules collaboratively aware. It allows them to share any of the internal datatypes of IRIS Explorer between separate instances of the visualization system.
Creates an instance of cxCollab. It should be created as a static variable so that state parameters within the class are not lost between firings of an IRIS Explorer module.
Registers a name to be associated with a data object that is to be shared. It requires a name, a port type and a datatype. Port types are either INPUT or INTERNAL. An INPUT type associates the given name with a module input port. Sending data of type INPUT takes the current data object on that named port and passes it into the collaborative session. An INTERNAL type takes a reference to an IRIS Explorer data object created within the module during its execution and passes it into the collaborative session. Data types are either PARAMETER, LATTICE, PYRAMID or GEOMETRY and correspond directly to the IRIS Explorer datatype of that name.
Registers a number of ports at one time to be associated with a data object that is to be shared. See above for a description of the parameters.
A number of reserved widgets are required to make this system work. The reserved names are:
This function checks the state of these widgets and takes appropriate action. It should be called once on each firing.Initiate, Connection_State, Join, ID, Name, Application.
Checks whether the module is currently connected to the collaborative session. Returns 1 (TRUE) if it is connected, else 0 (FALSE).
Checks whether there is any new data associated with a registered name. Returns 1 (TRUE) if it is a valid registered name and new data has arrived else 0 (FALSE).
For port types of type INTERNAL, the pointer to the data object to be sent must be set before calling sendData. After sendData has been called, the internal pointer is reset to NULL. Returns 1 (TRUE) if successful, else 0 (FALSE).
Send the data associated with a given registered port name. If the port type is INPUT, then the module input port of that name will be opened and the current data sent. If the port type is INTERNAL then the data associated with that name by a previous call to setData will be sent. Returns 1 (TRUE) if successful, else 0 (FALSE). Failure will occur if the name given does not match one of the registered port names.
As above but sends multiple data objects in a single message. This minimises the number of firings of the attached modules in the session. Returns 1 (TRUE) if successful, else 0 (FALSE). Failure will occur if the name given does not match one of the registered port names.
To be more memory efficient, the incoming data object held by the named port may be deleted once it has been used. If not deleted, it remains until a new data object is sent from the session, this has the advantage that it may be re-referenced.
Get the incoming data object, which should be a lattice, from the named port. Returns NULL on error.
Get the incoming data object, which should be a parameter, from the named port. Returns NULL on error.
Get the incoming data object, which should be a pyramid, from the named port. Returns NULL on error.
Get the incoming data object, which should be a pick, from the named port. Returns NULL on error.
Get the incoming data object, which should be a geometry, from the named port. The geometry is returned as a piece of scene graph under the returned geometry node. It should be instanced using a call to cxGeoInventorDefine. Returns NULL on error.
Get the string representing the username of the user within the collaborative session.
All objects passed into the collaborative session are tagged with the username of the person who created them. This function returns the string containing the username of the user who created the object associated with the named object.
A number of port names are reserved for use by the collaborative class and should not be used by users' code. Instances of these ports must be included in any user written collaborative module. The port names are as follows:
Initiate
Join
ID
Name
Connection_State
Application
Additionally, to facilitate the automatic launching and connection of collaborative modules, none of its ports should be set to Required.
Some example source code is shown here, the collaborative code is highlighted in blue and the comments are in green. The modules shown here are MShareLat, a completely new module, and MShareGraph3D which is based on the Graph3D module but allows for collaborative work. The lines of code of MShareGraph3D shown in black are the original module code of Graph3D. This demonstrates how little additional code was required to convert it to be a collaborative module.
All of the source code shown here, plus code for all of the other collaborative modules, is available in $EXPLORERHOME/src/
Example 3-1 User Function for the MShareLat Module
// Collab Includes
#include <cx/cxCollab.h>
// Explorer Includes
#include <cx/Typedefs.h>
#include <cx/Info.h>
#include <cx/cxLattice.api.h>
#include <cx/DataAccess.h>
#include <cx/PortAccess.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* User Function
*/
void ShareLat(long pass,cxLattice **OutLat)
{
static cxCollab collab_info;// Create a static instance of the
// collaboration class so that
// connection state and other info
// is not lost between each
// invocation of this function.
static int firsttime = 1;
if (firsttime == 1) {
firsttime = 0;
//Register a single collaborative port: name, source type, datatype
collab_info.portRegister("In_Lat",INPUT,LATTICE);
return;
}
// Check reserved widgets looking for connect/disconnect instructions
collab_info.checkWidgets();
// If connected then can do things
if (collab_info.isConnected()) {
// Check for data to read
if (collab_info.newData("In_Lat")) {
*OutLat = collab_info.getLattice("In_Lat");
cxDataRefInc((void *)*OutLat);
}
}
// Do stuff in here if need to, sing, dance etc . . .
// If connected then can do things
if (collab_info.isConnected()) {
// Check for data to send
int port = cxInputPortOpen("In_Lat");
if (cxInputDataChanged(port))
collab_info.sendData("In_Lat");
}
if ((pass) && (cxInputDataChanged(cxInputPortOpen("In_Lat")))) {
*OutLat = (cxLattice *)cxInputDataGet(cxInputPortOpen("In_Lat"));
}
} // end User function
#ifdef __cplusplus
}
#endif
Example 3-2 User Function for the MShareGraph3D Module
// Collab Includes
#include <cx/cxCollab.h>
/* Inventor Includes */
#include <Inventor/nodes/SoGroup.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/fields/SoSFVec3f.h>
#include <Inventor/nodes/SoComplexity.h>
#include <Inventor/nodes/SoShapeHints.h>
/* Explorer Includes */
#ifdef WIN32
#include <float.h>
#else
#include <values.h>
#endif
#include <cx/DataAccess.h>
#include <cx/DataTypes.h>
#include <cx/Geometry.h>
#include <cx/Lookup.h>
#include <cx/PortAccess.h>
#include <cx/UI.h>
// A convenient way to list all of the ports
// that we are going to register for collaboration,
// along with their port types and data types
char *port_names[] = {"Graph Type","Color By What","Height Scale",
"Surface Scale","Treat Negatives As ?"};
int port_dataTypes[] = {PARAMETER, PARAMETER, PARAMETER,
PARAMETER, PARAMETER};
int port_types[] = {INPUT,INPUT,INPUT,INPUT,INPUT};
int num_ports = 5;
#ifdef __STDC__
extern "C" void graph3d(cxGeometry **geom)
#else
extern "C" void graph3d(geom)
cxGeometry **geom;
#endif
{
static cxCollab collab_info; // class instance
// for collaborative elements
static int firsttime = 1;
cxCoord *coord;
cxCoordType coordtype;
cxData *data_struct;
cxErrorCode err;
cxLookup *clut;
cxPrimType primType;
double smin, smax;
float color_array[4], color_value, *coordvals, cyl_point[6], *data,
delx, dely, dx, dy, max_val, min_val, point[3], radius, scalefactor,
size, s_min = 0.01, s_max = 1.0, xmin, ymin;
int port;
long *dims, flag, i, j, nDataVar, nDim;
int cmap_flag;
int ngtve;
double val;
// Ports handled internally
cxLattice *lat, *cmap;
long color_by_what, graph_type, n_vals;
double h_scale, s_scale;
cxParameter *Scale_Param;
lat = (cxLattice *)cxInputDataGet(cxInputPortOpen("Data In"));
cmap = (cxLattice *)cxInputDataGet(cxInputPortOpen("ColorMap"));
// Check to see if we are connected. If we are then check to see if any
// new data has arrived on any of the ports that we registered as being
// collaborative. If we find new data (or the "sync values" button has
// been pressed) compile a list of port names for which data should be
// sent. Then send the data.
if (collab_info.isConnected()) {
char **portNames = new char * [num_ports];
int port,numPorts=0;
int sendAll = FALSE;
port = cxInputPortOpen("Sync Values");
if (cxInputDataChanged(port))
sendAll = TRUE;
// Compile list of port names for which data is new
for (int i = 0; i < num_ports; i++ ) {
port = cxInputPortOpen(port_names[i]);
if ((cxInputDataChanged(port)) || (sendAll)) {
int len = strlen(port_names[i])+1;
portNames[numPorts] = new char [len];
bzero(portNames[numPorts],len);
strcpy(portNames[numPorts],port_names[i]);
numPorts++;
}
}
// If some new data, then send to collaborators
if (numPorts > 0)
collab_info.sendData(numPorts,portNames);
//collab_info.sendData(portNames[0]);
// Tidy Up
for (i = 0; i < numPorts; i++ )
free(portNames[i]);
free(portNames);
// If we were just sync(ing) values, then stop having sent the
// data values.
if (sendAll)
return;
}
long dummy = cxParamLongGet((cxParameter*)cxInputDataGet(
cxInputPortOpen("Sync Values")));
graph_type = cxParamLongGet((cxParameter*)cxInputDataGet(
cxInputPortOpen("Graph Type")));
color_by_what = cxParamLongGet((cxParameter*)cxInputDataGet(
cxInputPortOpen("Color By What")));
n_vals = cxParamLongGet((cxParameter*)cxInputDataGet(
cxInputPortOpen("Treat Negatives As ?")));
h_scale = cxParamDblGet((cxParameter*)cxInputDataGet(
cxInputPortOpen("Height Scale")));
s_scale = cxParamDblGet((cxParameter*)cxInputDataGet(
cxInputPortOpen("Surface Scale")));
if (firsttime == 1) {
firsttime = 0;
// Register (once only) the names of ports that we want to be
// collaborative
collab_info.portRegister(num_ports,port_names, port_types,port_dataTypes);
// consume this value
long dummy = cxParamLongGet((cxParameter*)cxInputDataGet(
cxInputPortOpen("Sync Values")));
return;
}
// Check reserved widgets looking for connect/disconnect/re-connect
// instruction
collab_info.checkWidgets();
// If connected then can do things
if (collab_info.isConnected()) {
// Turn on Sync Values button
cxInWdgtShow("Sync Values");
// Check for data to be read from the collaborative session
// associated with port names that we have registered.
// Update the control panel if new data is found.
if (collab_info.newData("Graph Type")) {
graph_type = cxParamLongGet(collab_info.getParameter("Graph Type"));
cxInWdgtLongSet("Graph Type",graph_type);
}
if (collab_info.newData("Color By What")) {
color_by_what = cxParamLongGet(collab_info.getParameter("Color By What"));
cxInWdgtLongSet("Color By What",color_by_what);
}
if (collab_info.newData("Treat Negatives As ?")) {
n_vals = cxParamLongGet(collab_info.getParameter("Treat Negatives As ?"));
cxInWdgtLongSet("Treat Negatives As ?",n_vals);
}
if (collab_info.newData("Height Scale")) {
h_scale = cxParamDblGet(collab_info.getParameter("Height Scale"));
cxInWdgtDblSet("Height Scale",h_scale);
}
if (collab_info.newData("Surface Scale")) {
s_scale = cxParamDblGet(collab_info.getParameter("Surface Scale"));
cxInWdgtDblSet("Surface Scale",s_scale);
}
}
// If no data then finish
if (lat == NULL)
return;
/* Get the input lattice and determine its data type */
err = cxLatDescGet(lat, &nDim, &dims, NULL, &nDataVar, &primType,
NULL, NULL, &coordtype);
if (err == cx_err_error) {
cxModAlert("No memory to get lattice description, return control");
return;
}
/* Get the data structure and data */
cxLatPtrGet(lat, &data_struct, (void **) (&data), &coord,
(void **) (&coordvals));
if (err == cx_err_error) {
cxModAlert("Invalid lattice pointer used, return control");
return;
}
/* Get the maximum size of the ground area for the blocks
* This code assumes that a uniform lattice is connected
*/
if (coordtype != cx_coord_uniform) {
cxModAlert
("Unexpected lattice type connected, return control");
return;
}
delx = (coordvals[1] - coordvals[0])/((float) dims[0] - 1.0);
dely = (coordvals[3] - coordvals[2])/((float) dims[1] - 1.0);
dx = 0.5 * delx;
dy = 0.5 * dely;
xmin = coordvals[0];
ymin = coordvals[2];
/* If a colour map is attached, generate its lookup table */
if (cmap != NULL) {
clut = cxLookupCreate(cmap, cx_lookup_nearest);
cmap_flag = 1;
}
else
cmap_flag = 0;
/* Find minimum and maximum values in input lattice */
#ifdef WIN32
min_val = FLT_MAX;
max_val = FLT_MIN;
#else
min_val = MAXFLOAT;
max_val = MINFLOAT;
#endif
for (j = 0; j < dims[1]; j++) {
for (i = 0; i < dims[0]; i++) {
val = extract_value(data, primType, (j*dims[0]+i));
if (min_val > val)
min_val = val;
if (max_val < val)
max_val = val;
}
}
/* Scale the height according to the minimum and maximum values
* allowing for a user-specified multiplication factor h_scale
*/
if (max_val == min_val)
scalefactor = 0.08 * (float) h_scale;
else
scalefactor = 8.0 * (float) h_scale / (max_val - min_val);
/* Set external Scale Factor */
Scale_Param = cxParamDoubleNew((double)scalefactor);
cxOutputDataSet(cxOutputPortOpen("Scale"),(void *)Scale_Param);
/* Initialise the geometry structure */
*geom = cxGeoNew();
cxGeoBufferSelect(*geom);
/* New code to use shared memory geometry transcription */
port = cxOutputPortOpen("Geometry");
cxGeoBufferPortSet(port);
/* Create the geometry */
cxGeoRoot();
cxGeoDelete();
/* If a colour map is attached get a colour value
* else use a fixed value 0f 0.8
*/
if (!cmap_flag) {
color_array[0] = 0.8;
color_array[1] = 0.8;
color_array[2] = 0.8;
}
/* The distance between blocks is determined by the
* user-specified scale factor s_scale
*/
if (s_scale < s_min)
{
/* If the size would be too small then it is clamped
* and the widget must be examined for correct values
* before setting it to the clamped value
*/
size = s_min;
cxInWdgtMinMaxGet("Surface Scale", &smin, &smax);
flag = 0;
if (smin > size)
{
smin = (double) size;
flag = 1;
}
if (smax < smin)
{
smax = smin;
flag = 1;
}
if (flag == 1)
cxInWdgtDblMinMaxSet("Surface Scale", smin, smax);
cxInWdgtDblSet("Surface Scale", (double) size);
}
else if (s_scale > s_max)
{
/* If the size would exceed the available space then it is
* clamped and the widget must be examined for correct
* values before setting it to the clamped value
*/
size = s_max;
cxInWdgtMinMaxGet("Surface Scale", &smin, &smax);
flag = 0;
if (smin > size)
{
smin = (double) size;
flag = 1;
}
if (smax < smin)
{
smax = smin;
flag = 1;
}
if (flag == 1)
cxInWdgtDblMinMaxSet("Surface Scale", smin, smax);
cxInWdgtDblSet("Surface Scale", (double) size);
}
else
size = (float) s_scale;
/* Build up the geometry by inspecting each node */
for (j = 0; j < dims[1]; j++)
{
for (i = 0; i < dims[0]; i++)
{
val = extract_value(data, primType,(j*dims[0]+i));
/* The colour used for the node */
if (cmap_flag) {
switch (color_by_what)
{
case 0:
color_value = (float) i / (float) (dims[0]-1);
break;
case 1:
color_value = (float) j / (float) (dims[1]-1);
break;
case 2:
color_value = (float) val;
cxLookupInterp(clut, &color_value, (void *)color_array);
break;
}
cxLookupInterp(clut, &color_value, (void *)color_array);
}
/* See if negative */
if (val < 0.0)
ngtve = 1; /*True*/
else
ngtve = 0; /* False */
/* Make Positive */
if ((n_vals == 1) && (ngtve)) {
val = -1 * val;
ngtve = 0; /* False */
}
/* Select the type of graph */
switch (graph_type)
{
case 0:
/* Blocks */
if ((n_vals != 2) || (!ngtve)) {
/* Create a group */
SoGroup *Group = new SoGroup ;
Group->ref();
/* Add Color */
SoMaterial *mtl = new SoMaterial;
Group->addChild(mtl);
mtl->diffuseColor.setValue(color_array[0],color_array[1],color_array[2]);
/* Create Transform */
SoTransform *Trans = new SoTransform;
Group->addChild(Trans);
point[0] = (float) xmin + i * delx ;
point[1] = (float) ymin + j * dely ;
point[2] = (float) (scalefactor * val)/2 ;
Trans->translation.setValue(point);
SoComplexity *complex = new SoComplexity;
Group->addChild(complex);
complex->value.setValue(0.2);
/* Create cube */
point[0] = (float) (xmin + i * delx + dx * size) -
(xmin + i * delx - dx * size);
point[1] = (float) (ymin + j * dely + dy * size) -
(ymin + j * dely - dy * size);
if (ngtve)
point[2] = (float) scalefactor * val * -1;
else
point[2] = (float) scalefactor * val ;
SoCube *cube = new SoCube;
Group->addChild((SoNode *)cube);
cube->width.setValue(point[0]);
cube->height.setValue(point[1]);
cube->depth.setValue(point[2]);
/* Add to Explorer Geom */
cxGeoInventorDefine(Group);
Group->unref();
}
break;
case 1:
/* Cylinders */
/* A cylinder is defined by 2 centres and a radius
* A cone is defined by a centre, a top and a base radius
*/
if ((n_vals != 2) || (!ngtve)) {
cyl_point[0] = cyl_point[3] = xmin + i * delx;
cyl_point[1] = cyl_point[4] = ymin + j * dely;
cyl_point[2] = 0.0;
cyl_point[5] = scalefactor * val;
if (dx < dy)
radius = dx * size;
else
radius = dy * size;
cxGeoCylindersDefine(1, &cyl_point[0], &cyl_point[3],
&radius);
cxGeoComplexityAdd(0.2);
cxGeoColorAdd(1, color_array, CX_GEO_PER_OBJECT);
}
break;
case 2:
/* Cones */
/* A cylinder is defined by 2 centres and a radius
* A cone is defined by a centre, a top and a base radius
*/
if ((n_vals != 2) || (!ngtve)) {
cyl_point[0] = cyl_point[3] = xmin + i * delx;
cyl_point[1] = cyl_point[4] = ymin + j * dely;
cyl_point[2] = 0.0;
cyl_point[5] = scalefactor * val;
if (dx < dy)
radius = dx * size;
else
radius = dy * size;
cxGeoConesDefine(1, &cyl_point[0], &cyl_point[3],
&radius);
cxGeoComplexityAdd(0.2);
cxGeoColorAdd(1, color_array, CX_GEO_PER_OBJECT);
}
break;
default:
/* Unexpected type */
cxModAlert
("unexpected graph type requested, return control");
return;
}
}
}
if (cmap_flag) {
cxLookupDestroy(clut);
}
cxGeoBufferClose(*geom);
}