Chapter 3. Creating a Field

If the set of field classes supported by Inventor does not suit your needs when defining a node or engine class, you may have to create your own field subclass. This chapter describes creating single-value and multiple-value fields. If you create a new field and you need automatic type conversion so that it can be connected to a field of a different type, you can build an engine that is a subclass of SoFieldConverter. Creating new field converters is explained in Chapter 6.

The chapter examples show creating two field classes:

Overview

The file SoSubField.h contains the macros for defining new field classes. If you do create a field class, you will probably need to use only a few of these macros. The more commonly used ones are documented in this chapter.

Creating a new field requires these steps:

  1. Select a name for the new field class, determine whether it is derived from SoSField or SoMField (or a subclass), and determine the type of the field value (for example, float, SbVec3f).

  2. Define an initClass() method to initialize type information. This step is required for both single-value and multiple-value fields (see “Initializing the Field Class”).

  3. Implement the reading and writing methods for the field.

    a. For single-value fields, implement readValue() and writeValue()
    methods (see “Creating a Single-Value Field”).

    b. For multiple-value fields, implement read1Value() and
    write1Value() methods. You may also want to implement a
    getNumValuesPerLine() method for a multiple-value field. This
    method is used by the ASCII file format and tells how many values
    fit on one line when the field is written out
    (see “Creating a Multiple-Value Field”).

  4. If it is useful, you can optionally define other methods for setting and accessing values in the field. For example, SoSFVec3f allows you to set the value from three floats or from an array of three floats, and so on.

Initializing the Field Class

The initClass() method in the source file sets up runtime type information for the field class. For single-value fields, use the SO_SFIELD_INIT() macro. For multiple-value fields, use SO_MFIELD_INIT(). See Chapter 1 for a brief description of runtime type-checking in Open Inventor.

Creating a Single-Value Field

This section shows how to create a single-value field class, SFDouble, that contains a double-precision real number.

The header file for this class uses the SO_SFIELD_HEADER() macro to declare all required member variables and functions for a single-value field. This macro declares the methods and variables for field classes that are derived from SoSField. The first argument to the macro is the name of the new field. The second argument is the type of the value stored in the field, and the third is the type that can be passed to or returned from a function. For double, a primitive type, these two arguments are identical. For more

complicated field types, this last type is typically a reference to the field value; for example,

const SbVec3f &

for a field holding an SbVec3f.

If your new field class is derived from another single-value field (rather than from an abstract class), use the SO_SFIELD_DERIVED_HEADER() macro.

Example 3-1 shows the header file for the SFDouble class.

Example 3-1. SFDouble.h


#include <Inventor/fields/SoSubField.h>

class SFDouble : public SoSField {

   // Declares all required member variables and functions for a
   // single-value field
   SO_SFIELD_HEADER(SFDouble, double, double);

 public:
   // Initializes field class, setting up runtime type info
   static void    initClass();
};

The source file for the SFDouble field class uses the SO_SFIELD_SOURCE() macro to define all required member variables and functions for the single-value field. It uses the SO_SFIELD_INIT() macro to initialize the field class and set up runtime type information.

SO_SFIELD_SOURCE() defines all methods that are declared in SO_SFIELD_HEADER(). SO_SFIELD_DERIVED_SOURCE() defines all methods that are declared in SO_SFIELD_DERIVED_HEADER(). (Even if you did not use the standard macros in your class header because you wanted to implement methods differently, you may still be able to use these macros in your source file. See SoSubField.h for details.)

Two methods for reading and writing the new field are also included. The SoInput class defines several read() methods that read a primitive type from the current input. One of these methods reads a number of type double, so we can use that method here to read into the value member variable defined in SO_SFIELD_HEADER(). The read() methods return FALSE on error, which is just what we want. Similarly, the SoOutput class has several write() methods. The new class uses the one that writes out a double.

Example 3-2 shows the source file for the SFDouble class.

Example 3-2. SFDouble.c++


#include "SFDouble.h"

// Defines all required member variables and functions for a
// single-value field
SO_SFIELD_SOURCE(SFDouble, double, double);

// Initializes the class, setting up runtime type info.

void
SFDouble::initClass()
{
   // This macro takes the name of the class and the name of the
   // parent class
   SO_SFIELD_INIT_CLASS(SFDouble, SoSField);
}


// This reads the value of a double-precision field from a
// file. It returns FALSE if the value could not be read
// successfully.


SbBool
SFDouble::readValue(SoInput *in)
{
   // Read a double from the input
   return in->read(value);
}


// This writes the value of a double-precision field to a
// file.

void
SFDouble::writeValue(SoOutput *out) const
{
   // Write a double
   out->write(value);
}

For more complex field-value types, you must be careful when writing values; you have to see if the output format is binary before writing spaces or other ASCII formatting. For example, the method to write the value of an SoSFVec3f field looks like this:

void
SoSFVec3f::writeValue(SoOutput *out) const
{
   // Write first component of vector
   out->write(value[0]);

   // If not writing binary format, output a space between
   // values
   if (! out->isBinary())
      out->write(' ');

   // Repeat for other components of vector
   out->write(value[1]);
   if (! out->isBinary())
      out->write(' ');
   out->write(value[2]);
}

Creating a Multiple-Value Field

This section shows how to create a multiple-value field class, MFDouble, that contains any number of double-precision real numbers.

Example 3-3 shows the header file for the MFDouble class. It uses the SO_MFIELD_HEADER() macro, which is typically the only macro you need to use in an SoMField subclass header, unless you wish to change value handling or construction/destruction.

Use the SO_MFIELD_DERIVED_HEADER() macro to declare the methods and variables for fields that are derived from other multiple-value fields (rather than from an abstract class).

Example 3-3. MFDouble.h


#include <Inventor/fields/SoSubField.h>

class MFDouble : public SoMField {

   // This macro is just like the one for single-value fields.
   SO_MFIELD_HEADER(MFDouble, double, double);

 public:
   static void    initClass();

 private:
   // This returns the number of ASCII values to write per
   // output line. It can be used to produce more compact
   // output files for fields containing small value types.
   virtual int    getNumValuesPerLine() const;
};

The SO_MFIELD_SOURCE() macro defines all methods that are declared in SO_MFIELD_HEADER(). This macro includes several other macros that you may find useful even if you don't want to use all of them.

SO_MFIELD_SOURCE_MALLOC(), used in Example 3-4, is an alternate version of SO_MFIELD_SOURCE(). The difference is that this macro implements value storage management using malloc(), realloc(), and free(), while SO_MFIELD_SOURCE() uses the new and delete operators. While SO_MFIELD_SOURCE_MALLOC() produces faster and more efficient code than the other source macro, it should be used only for field classes whose values have no constructors or destructors (since they would never be called). For example, the SoMFShort class uses this macro, since values of type short are not C++ instances, and therefore do not have any constructors or destructors. The MFDouble field contains a basic type (a double) that does not need a constructor, so we are free to use this alternate (and more efficient) macro.

SO_MFIELD_DERIVED_SOURCE() defines all methods that are declared in SO_MFIELD_DERIVED_HEADER(). Use these macros if you are deriving a field from another field.

Example 3-4 shows the source file for the MFDouble class.

Example 3-4. MFDouble.c++


#include "MFDouble.h"

// Defines all required member variables and functions for a
// multiple-value field. We use the version that allocates field
// value storage with malloc(), since there is no constructor to
// call for our values.
SO_MFIELD_SOURCE_MALLOC(MFDouble, double, double);

// Initializes the class, setting up runtime type info.

void
MFDouble::initClass()
{
   // This macro takes the name of the class and the name of the
   // parent class
   SO_MFIELD_INIT_CLASS(MFDouble, SoMField);
}

// This reads one value of the double-precision field from a
// file. It is passed the index of the value to read; we can
// assume that the field already contains enough room to hold
// this value. It returns FALSE if the value could not be read
// successfully.

SbBool
MFDouble::read1Value(SoInput *in, int index)
{
   // Read a double from the input
   return in->read(values[index]);
}

// This writes one value of a double-precision field to a
// file.

void
MFDouble::write1Value(SoOutput *out, int index) const
{
   // Write a double
   out->write(values[index]);
}

// Returns number of ASCII values to write out per line.

int
MFDouble::getNumValuesPerLine() const
{
   // We can probably fit 4 doubles per line pretty easily.
   return 4;
}