Search code examples
c++openinventorcoin3d

How to add an arbitrary number of engine outputs using Coin3d/OpenInventor


I am writing an application that visualises a large data set using the Coin3d library (which is based on the same code base as OpenInventor). I have been wrestling with this problem for a while and I have never found a satisfactory solution.

The data comes in a variable number of 'strips' and I have created a SoEngine that collects data to be visualised, sends it to a number of outputs, which is then connected to an SoQuadMesh for each strip for rendering.

The reason why I am using an engine here is that the data is fetched from the data source and the visualisation updated as the user navigates around it. That is, as the user zooms in and out, the resolution of the image is changed (as per google maps). The data is retrieved in a background thread (it takes a second or two) and then used to update the engine outputs.

The problem is that there does not seem to be a way to create an arbitrary number of SoEngineOutputs - they all have to be declared in the class definition before adding to the engine with the SO_ENGINE_ADD_OUTPUT macro.

By analysing the Coin source code, I have tried to workaround this by implementing the code behind the SO_ENGINE_ADD_OUTPUT macro in a slightly modified form, but ultimately I failed (or lost my nerve) because the SoEngine::outputdata is a static field which should be created only once; I did not want to risk the consequences of re-initialising it, without knowing the details of the whole implementation under-the-hood.

The solution I have working now is to declare all the outputs up to a possible maximum value, as in the header:

class Engine : public SoEngine
{
    SO_ENGINE_HEADER(Engine);

public:

    // The output: vector of points, edges, colours and indices
    // A set of these is needed for each strip in the visualisation
    SoEngineOutputList dataPoints;
    SoEngineOutputList edgePoints;
    SoEngineOutputList dataColours;
    SoEngineOutputList edgeColours;
    SoEngineOutputList numSamples;
    SoEngineOutputList numDepths;

// Macro to simplify and shorten the code for adding multiple engine outputs
#define ENGINE_DECLARE_OUTPUTS(N) \
    SoEngineOutput dataPoints_##N;  /*SoMFVec3f*/ \
    SoEngineOutput edgePoints_##N;  /*SoMFVec3f*/ \
    SoEngineOutput dataColours_##N; /*SoMFColor*/ \
    SoEngineOutput edgeColours_##N; /*SoMFColor*/ \
    SoEngineOutput numSamples_##N;  /*SoSFInt32 */ \
    SoEngineOutput numDepths_##N;   /*SoSFInt32 */

    // Declare all the outputs from the engine. Note that they have to be added
    // individually because it uses the macro above.
    ENGINE_DECLARE_OUTPUTS(0);
    ENGINE_DECLARE_OUTPUTS(1);
    ENGINE_DECLARE_OUTPUTS(2);
    ENGINE_DECLARE_OUTPUTS(3);
    // etc. all the way to a constant MAX_NUM_SAMPLE_SETS

Then in the Engine constructor, add each output to the engines output lists:

#define ENGINE_ADD_OUTPUTS(N) \
        SO_ENGINE_ADD_OUTPUT(dataPoints_##N, SoMFVec3f); \
        SO_ENGINE_ADD_OUTPUT(edgePoints_##N, SoMFVec3f); \
        SO_ENGINE_ADD_OUTPUT(dataColours_##N, SoMFColor); \
        SO_ENGINE_ADD_OUTPUT(edgeColours_##N, SoMFColor); \
        SO_ENGINE_ADD_OUTPUT(numSamples_##N, SoSFInt32); \
        SO_ENGINE_ADD_OUTPUT(numDepths_##N, SoSFInt32); \
        dataPoints.append(&dataPoints_##N); \
        edgePoints.append(&edgePoints_##N); \
        dataColours.append(&dataColours_##N); \
        edgeColours.append(&edgeColours_##N); \
        numSamples.append(&numSamples_##N); \
        numDepths.append(&numDepths_##N);

// Add all the outputs from the engine. Note that they have to be added
// individually because it uses the macro above.  The number added should match
// the number defined in MAX_NUM_SAMPLE_SETS
ENGINE_ADD_OUTPUTS(0);
ENGINE_ADD_OUTPUTS(1);
ENGINE_ADD_OUTPUTS(2);
ENGINE_ADD_OUTPUTS(3);
// etc. all the way to a constant MAX_NUM_SAMPLE_SETS

This works, but there is a performance hit when the Engine class in instantiated of about 20 seconds when MAX_NUM_SAMPLE_SETS is set to 100 - which means a declaration of 600 SoEngineOutputs. MAX_NUM_SAMPLE_SETS = 100 is the largest possible - most visualisations require a lot less than this (less than 10), so I want to be able to determine the number of outputs at run-time.

So my questions are:

  1. Is there a way to add an arbitrary number of SoEngineOutputs in Coin3d at runtime?
  2. Why is there such a performance hit with this number of declarations of `SoEngineOutput? (This is possibly a general C++ question for which I will create a separate question, or it is an issue with Coin3d)
  3. Is there a better way to implement a solution for this?

Solution

  • If I had the 50 reputation that would enable me to comment, I would ask why you want to use an SoEngine for this. The purpose of SoEngines is to enable computations and construct dependencies between various elements in a scene graph, that must be active during traversal, i.e., dynamically, and that can be written to an .iv file.

    I get the impression that what you do needs to be done once, when you load your data prior to presentation. In that case you can build your scene graph and fill your nodes directly based on the data set, without routing it through an SoEngine.

    Note that even if you have a stream of data at runtime for which the visualisation must be updated dynamically, you are still free to change the scene graph while it is in use, if you take care not to remove or add nodes while it is being traversed. There are various ways to accomplish this, but that is probably another question.

    Edit: Another question: Why, if you get the data in strips, would you convert it to an SoIndexedFaceSet and not SoTriangleStripSet in the first place?