In this section we discuss the case where multiple data volumes are combined to render a single volume on the screen.
autotoc_md1040
SoDataCompositor
One or more data sets stored on disk can be composed together with a user-defined operator when loading from disk to main memory. The corresponding blocks of data (LDM tiles) are composed before the result is stored into main memory. Multiple data sets are present on disk but only the result of the composition is stored in main memory. The same result could be achieved by reading the input volumes and writing out a new composited volume. Using VolumeViz data composition has two advantages. First, we don’t need to keep the composited volume around on disk and second, we (potentially) don’t need to composite every voxel in the whole volume because LDM will only load tiles that are needed for rendering.
The SoDataCompositor node allows you to combine multiple data sets in memory instead of having to store the combined data sets on disk. For example, it can be used to visualize the result of the difference between two data sets. Some useful default operators are provided (add, subtract, and multiply) through the preDefCompositor field. Custom composition operators may be defined by deriving a subclass from SoDataCompositor and overriding the compose() method. To use the custom operator, the preDefCompositor field must be set to NONE.
The SoDataCompositor node cannot be used for unary operations (the number of data sets used with a data compositor must be greater than one). Unary operations can be performed using SoLDMDataTransform or SoVolumeTransform.
A number of rules apply to the use of SoDataCompositor :
Scene graph for CPU composition of two data sets
CPU composition of two data sets using the multiply operator
class MyCompositor : public SoDataCompositor
{
void compose( int numVolumeData, const SbVec3i32& tileDimension, int* vdid, SoBufferObject** inputBuffer, SoBufferObject* outputBuffer );
};
// Implement custom compose method
void
myCompositor::compose( int numVolumeData, const SbVec3i32& tileDimension, int* vdid, void** inputBuffer, void* outputBuffer )
{
// Get pointers to input and output buffers
// Note: Assume there are exactly two input volumes of type unsigned byte
SoRef<SoCpuBufferObject> inputBufferCpu0 = new SoCpuBufferObject();
inputBuffer[0]->map( inputBufferCpu0, SoBufferObject::READ_ONLY );
unsigned char* inputPtr0 = inputBufferCpu0->map( SoBufferObject::READ_ONLY );
SoRef<SoCpuBufferObject> inputBufferCpu1 = new SoCpuBufferObject();
inputBuffer[1]->map( inputBufferCpu1, SoBufferObject::READ_ONLY );
unsigned char* inputPtr1 = inputBufferCpu1->map( SoBufferObject::READ_ONLY );
SoRef<SoCpuBufferObject> outputBufferCpu = new SoCpuBufferObject();
outputBuffer->map( outputBufferCpu1, SoBufferObject::SET );
unsigned char* outputPtr = outputBufferCpu1->map( SoBufferObject::SET );
// Compute output volume similar to SoDataCompositor::MULTIPLY
int numVoxels = tileDimension[0] * tileDimension[1] * tileDimension[2];
for ( int i = 0; i < numVoxels; ++i )
{
*outputPtr++ = *inputPtr0++ * *inputPtr1++;
}
// Release mapping on input and output buffers
outputBufferCpu1->unmap();
outputBuffer->unmap( outputBufferCpu1 );
inputBufferCpu1->unmap();
inputBuffer->unmap( inputBufferCpu1 );
inputBufferCpu2->unmap();
inputBuffer->unmap( inputBufferCpu2 );
}
C# :
class MyCompositor : SoDataCompositor
{
// Automatically set preDefCompositor field to NONE
// (enabling use of our overridden compose function)
public MyCompositor() { preDefCompositor.SetValue( "NONE" ); }
public override void Compose( int numDataSet, SbVec3i32 tileDimension, int[] vdid, OIV.Inventor.Devices.SoBufferObject[] inputBuffer, OIV.Inventor.Devices.SoBufferObject outputBuffer )
{
SoCpuBufferObject bufIn1 = new SoCpuBufferObject();
SoCpuBufferObject bufIn2 = new SoCpuBufferObject();
SoCpuBufferObject bufOut = new SoCpuBufferObject();
ulong numVoxels = ( ulong )( tileDimension[0] * tileDimension[1] * tileDimension[2] );
inputBuffer[0].Map( bufIn1, SoBufferObject.AccessModes.READ_ONLY, 0, numVoxels );
inputBuffer[1].Map( bufIn2, SoBufferObject.AccessModes.READ_ONLY, 0, numVoxels );
outputBuffer.Map( bufOut, SoBufferObject.AccessModes.SET, 0, numVoxels );
SbNativeArray<byte> byteBuf1 = bufIn1.Map( SoBufferObject.AccessModes.READ_ONLY );
SbNativeArray<byte> byteBuf2 = bufIn2.Map( SoBufferObject.AccessModes.READ_ONLY );
SbNativeArray<byte> byteOut = bufOut.Map( SoBufferObject.AccessModes.SET );
for ( int i = 0; i < ( int )numVoxels; ++i )
{
byteOut[i] = ( byte )( byteBuf1[i] * byteBuf2[i] );
}
bufOut.Unmap();
outputBuffer.Unmap( bufOut );
bufIn1.Unmap();
inputBuffer[0].Unmap( bufIn1 );
bufIn2.Unmap();
inputBuffer[1].Unmap( bufIn2 );
}
}
Java :
SoDataCompositor dataCompositor = new SoDataCompositor(){
public void compose( SbVec3i32 tile_dimension, int[] volume_ids, Buffer[] input_buffer, int[] data_types, Buffer output_buffer ){
// compose by multiplying input buffers
byte b;
while ( output_buffer.hasRemaining() )
{
b = 0x01;
for ( int j = 0; j < input_buffer.length; j++ )
{
b *= ( ( ByteBuffer )input_buffer[j] ).get();
}
( ( ByteBuffer )output_buffer ).put( b );
}
}
}
;
dataCompositor.dataType.setValue( SoDataSet.DataTypes.UNSIGNED_BYTE );
dataCompositor.preDefCompositor.setValue( SoDataCompositor.PreDefCompositors.NONE );