8.10. Modify Geometry

Bevel Action

Figure 8.7. Bevel action class



Open Inventor allows you to bevel all Open Inventor shapes except NURBS and sphere. The beveling is performed using an SoBevelAction SoBevelAction SoBevelAction , which traverses a scene graph looking for shapes to bevel.

Bevel properties

Figure 8.8. Bevel property node class



Shapes can be selected by adding SoBevelProperty SoBevelProperty SoBevelProperty and/or SoEdgeFlag SoEdgeFlag SoEdgeFlag and/or SoVertexFlag SoVertexFlag SoVertexFlag nodes in the scene graph before the shapes to be beveled.

Five kinds of parameters control the beveling:

The methods setAngle(), setRadius(), and enableAbsoluteRadius() are available in SoBevelAction SoBevelAction SoBevelAction as well as SoBevelProperty SoBevelProperty SoBevelProperty . The SoBevelAction SoBevelAction SoBevelAction methods set the global default bevel properties that will apply to beveled shapes. However, if bevel property values have also been set by the SoBevelProperty SoBevelProperty SoBevelProperty node, they are used to override the global properties locally.

Different effects are possible by combining these parameters different ways:

  • A rounded corner: The vertex is marked, but none of the adjacent edges are marked.

  • Rounded edges: There are two types of rounded edges:

    • If both vertices of the edge are unmarked, the rounded edge has a cylindrical cross section.

    • If only one of the two vertices of the edge is marked, the rounded edge looks like a portion of a cone, where the cone vertex is the marked vertex.

Other combinations of parameters are useful for marking vertices and edges easily. For example, we can mark all edges with adjacent face normals making an angle greater than 33 degrees, except the first and the second edge.

Furthermore, some tests can be applied in order to “clean up” the shape before it is beveled. This is the purpose of the methods enableCoplanarTest(), enableDuplicateTest(), and enableOrderingTest() of SoBevelAction SoBevelAction SoBevelAction , and the bitmask field testsBeforeBevel of SoBevelProperty SoBevelProperty SoBevelProperty . The coplanar test, which eliminates coplanar faces, and the duplicate test, which eliminates duplicated faces, are time consuming but are necessary for shapes with coplanar faces or duplicated faces because the bevel action does not work properly with such cases.

Applying the action

The action is applied in the same manner as any other action and generates a scene graph containing beveled shapes.

Obtaining the results

As a result of the beveling, another scene graph is created, and can be obtained with the method getSceneGraph(). Each selected shape is beveled, and appears as a scene graph containing VRML shapes or Inventor shapes (see setOutputVRML() of SoBevelAction SoBevelAction SoBevelAction or the vrmlOutput field of SoBevelProperty SoBevelProperty SoBevelProperty ).

The shapes are:

SoBevelAction SoBevelAction SoBevelAction does not take into consideration PER_FACE or PER_VERTEX materials. Therefore, the shapes to be beveled should have a uniform material (SoMaterialBinding::OVERALL).

Example 8.4. How to use SoBevelAction

This example, which bevels a cube with a bevel radius equal to 20% of the cube size, can be found in: $OIVHOME/src/Inventor/examples/Features/BevelAction/BevelAction.cxx.

void
main(int, char **argv)                      

// Initialize Inventor and Xt 
Widget myWindow = SoXt::init(argv[0]); 
if (myWindow == NULL) exit(1);
  {
    SoInput *input = new SoInput();

    if (!input->openFile("$OIVNETHOME/data/models/simple/cube.iv"))
    exit(0);

    SoSeparator *root = SoDB::readAll(input);
    root->ref();

    SoXtExaminerViewer *myViewer = new SoXtExaminerViewer(myWindow);
    myViewer->setBackgroundColor(SbColor(1.0, 1.0, 1.0));

    SoSeparator *result1 = new SoSeparator();
    result1->ref();
    SoComplexity *myComplexity = new SoComplexity();
    myComplexity->value = 1.f;
    result1->addChild(myComplexity);

    SoBevelProperty *prop = new SoBevelProperty();
    prop->radius.setValue(0.2f);
    result1->addChild(prop);
    result1->addChild(root);

    root->unref();

    SoBevelAction baction;
    baction.setOutputVRML(FALSE);
    baction.apply(result1);
    SoGroup *result2 = baction.getSceneGraph();
    result2->ref();

    myViewer->setSceneGraph(result2);
    myViewer->setTitle("BevelAction");
    myViewer->show();
    myViewer->viewAll();

    SoWriteAction myAction;
    myAction.getOutput()->openFile("./bevel.iv");
    myAction.apply(result2);
    myAction.getOutput()->closeFile();

    SoXt::show(myWindow);
    SoXt::mainLoop();
  }
   
private void CreateSample()
{
  SoInput input = new SoInput();

  input.OpenFile("$OIVNETHOME/src/Inventor/examples/data/BevelAction/cube.iv");

  SoSeparator root = SoDB.ReadAll(input);

  SoWinExaminerViewer myViewer = new SoWinExaminerViewer(this, "", true,
        SoWinFullViewer.BuildFlags.BUILD_ALL, SoWinViewer.Types.EDITOR);
  myViewer.SetBackgroundColor(new SbColor(1f, 1f, 1f));

  SoSeparator result1 = new SoSeparator();
  SoComplexity myComplexity = new SoComplexity();
  myComplexity.value.Value = 1.0f;
  result1.AddChild(myComplexity);

  SoBevelProperty prop = new SoBevelProperty();
  prop.radius.Value = 0.2f;
  result1.AddChild(prop);
  result1.AddChild(root);

  SoBevelAction baction = new SoBevelAction();
  baction.SetOutputVRML(false);
  baction.Apply(result1);
  SoGroup result2 = baction.GetSceneGraph();

  myViewer.SetSceneGraph(result2);
  myViewer.SetTitle("BevelAction");
  myViewer.Show();
  myViewer.ViewAll();

  SoWriteAction myAction = new SoWriteAction();
  myAction.GetOutput().OpenFile("./bevel.iv");
  myAction.Apply(result2);
  myAction.GetOutput().CloseFile();
}
   
public void start()
{
  super.start();
  setLayout(new BorderLayout());
  Panel panel = new Panel(new BorderLayout());

  myViewer = new SwSimpleViewer(SwSimpleViewer.EXAMINER);

  SoInput input = new SoInput();

  input.openFile("../../../../data/models/simple/cube.iv");

  SoSeparator root = SoDB.readAll(input);

  myViewer = new SwSimpleViewer(SwSimpleViewer.EXAMINER);
  myViewer.setBackgroundColor(new SbColor(1f, 1f, 1f));

  SoSeparator result1 = new SoSeparator();
  SoComplexity myComplexity = new SoComplexity();
  myComplexity.value.setValue(1f);
  result1.addChild(myComplexity);

  SoBevelProperty prop = new SoBevelProperty();
  prop.radius.setValue(0.2f);
  result1.addChild(prop);
  result1.addChild(root);

  SoBevelAction baction = new SoBevelAction();
  baction.setOutputVRML(false);
  baction.apply(result1);
  SoGroup result2 = baction.getSceneGraph();

  myViewer.setSceneGraph(result2);
  myViewer.viewAll();

  SoWriteAction myAction = new SoWriteAction();
  myAction.getOutput().openFile("./bevel.iv");
  myAction.apply(result2);
  myAction.getOutput().closeFile();

  panel.add(myViewer);
  add(panel);
}


Split Geometry Action

Figure 8.9. Split geometry action class



Open Inventor allows you to split any kind of SoIndexedFaceSet SoIndexedFaceSet SoIndexedFaceSet shape or SoIndexedTriangleStripSet SoIndexedTriangleStripSet SoIndexedTriangleStripSet shape, along one, two, or three dimensions. The splitting is performed using SoSplitGeometryAction SoSplitGeometryAction SoSplitGeometryAction , which traverses a scene graph looking for shapes to split. The shapes are split into the number of x, y, and z subdivisions that you request.

Split geometry action

Figure 8.10.  Split geometry action



Purpose

The question is: why is the split geometry action useful?

The Open Inventor SoSplitGeometryAction SoSplitGeometryAction SoSplitGeometryAction is most useful when you need to visualize large data sets where IndexedFaceSet and/or IndexedTriangleStripSet nodes define most of the geometry. In this case, when used in combination with an SoOctreeOrdering SoOctreeOrdering SoOctreeOrdering node (see the section called “SoOctreeOrdering Node” for more information), it may be possible to improve rendering performance significantly.

Figure 8.10, “ Split geometry action”, displays a large surface described by only one SoIndexedTriangleStripSet SoIndexedTriangleStripSet SoIndexedTriangleStripSet node. If you insert an octree at the top of your scene graph, the optimization offered by the octree will not be very efficient because there is only one object in the scene. This means that each render thread must still render the whole surface, which is a lot of redundant computation.

If you apply an SoSplitGeometryAction SoSplitGeometryAction SoSplitGeometryAction to this node to split it into 16 new SoIndexedTriangleStripSets as shown in the example (of course, you can split a node into as many pieces as your geometry allows), the Open Inventor rendering process can divide the work between threads and thus improve performance.

Indeed, now that we have the same geometry, but described by several objects, we can use the SoOctreeOrdering SoOctreeOrdering SoOctreeOrdering node effectively. Each render thread will only need to render the part of the surface present in the associated frustum (for further details, see the SoOctreeOrdering SoOctreeOrdering SoOctreeOrdering node).

Specify Options

The scene graph will be changed by this action. But you can specify how it will be changed.

If you don’t need to keep your original geometry, then the action can replace your original node with a separator containing all new nodes.

But if you want to keep your original geometry, use the setKeepOriginalGeometry() method. This will modify the behavior of the action and replace the original node with an SoSwitch SoSwitch SoSwitch node containing both the original and the new geometry. Each new switch node added to the scene graph is named “switchX” where X is the identifier of the object to split. For example, if this is the first object found by the action, the switch node will be called “switch0”. Use the root of your scene graph and the getByName() method to get the new switch node:

SoNode *mySwitch = myRoot->getByName("switch0");
        
SoNode mySwitch = SoNode.GetByName("switch0");
      
SoNode mySwitch = SoNode.getByName("switch0");

You can specify the desired number of subdivisions in the constructor. The first division value will be used to split the object along its x bounding box value, the second along y, and the third along z if you have requested splitting in three dimensions.

The above approach is suitable when you know the dimensions of your geometry in advance. However, if you have no prior knowledge of its dimensions, you can use the setSmartSplitting() method. A call to this method before applying the action will allow the action to split your geometry using the largest division value to split the object along its greatest bounding box value side, the next largest value to split it along the objects’s next largest bounding box value, and so on.

If your scene graph contains switch nodes, you can specify to the action to traverse and split all children or just the active children using the setTraverseAllSoSwitchChildren() method.

If you want to see the results of the action, a call to setDistinguishSplitParts() method will apply different materials to all each of the new objects.

To specifiy a pre-split callback function to call when a node of the given type is encountered during traversal, use setSplitGeometryActionPreCB(SoSplitGeometryActionCB *userCB, void *userData) . The pre-callback is called just before the node is traversed. For each node to split, you can customize how to split it by enabling or disabling options, changing the division values, etc.

SoSplitGeometryActionCB is a defined type:

typedef void SoSplitGeometryActionCB(const SoSplitGeometryAction *action,
                                    SoNode *objectToSplit,
                                    const int objectId,int divAlongX,
                                    int divAlongY,int divAlongZ,
                                    void *userData);
            

To specifiy a pre-split delegate to call when a node of the given type is encountered during traversal, use PreTraversal property. The delegate is invoked just before the node is traversed. For each node to split, you can customize how to split it by enabling or disabling options, changing the division values, etc.

public delegate void PreCallback(
	SoSplitGeometryAction action,
	SoNode objectToSplit,
	int objectId,
	int divAlongX,
	int divAlongY,
	int divAlongZ
)
          

The split geometry algorithm does not add, delete, or modify (e.g., split) individual triangles. It collects/sorts triangles into groups (subdivisions). If the geometry is very simple or the requested number of subdivisions is very large, it may not be possible to split the object into as many subdivisions as requested. If the requested number of subdivisions is greater than the maximum possible subdivisions, the object will be split into the maximum possible subdivisions.

A call to the setMaximumDivisionWarning() method enables Inventor warnings to inform you if you’ve requested more than the maximum possible number of subdivisions. Turning on warnings will slow traversal because, to determine if the subdivision value is valid, the average length of every primitive is calculated. This particular Inventor warning is Off by default.

All these options must be set before applying the action.

Applying the Action

The action is applied in the same manner as any other action and generates a scene graph containing split shapes.

You can split your geometry along two dimensions, which is best for large flat surface visualizations, or along three dimensions, in which case you must be careful not to include flat indexed shapes in your scene graph or your application will generate an Open Inventor error. The number of divisions – two values to divide along two dimensions, three values to divide along three dimensions – can be specified in the constructor.

Example 8.5.  How to use SoSplitGeometryAction

This example splits a model into four pieces using SoSplitGeometryAction SoSplitGeometryAction SoSplitGeometryAction , then runs the MP viewer using an SoOctreeOrdering SoOctreeOrdering SoOctreeOrdering node:

int
main(int argc, char **argv)
{
 //Create the window
 HWND myWindow = SoWin::threadInit(argv[0]);
 if (myWindow == NULL) return;
 
 //Create the root :
 SoSeparator *myRoot = new SoSeparator;
 myRoot->ref();
 
 //Create the octree :
 SoOctreeOrdering *myOctree = new SoOctreeOrdering;
 myOctree->ref();
 
 //Read a file containing a big SoIndexedFaceSet :
 SoSeparator *myScene = new SoSeparator;
 char *myFile = "crater.iv";
 if (myFile)
 {
   myScene = readFile(myFile);
   myScene->ref();
 }
 
 //create scene graph :
 myRoot->addChild(myOctree);
 myOctree->addChild(myScene);
 
 //apply the SoSplitGeometryAction to divide the model in 4 pieces:
 SoSplitGeometryAction mySplitAction(2,2);
 mySplitAction.setSmartSplitting();
 mySplitAction.setDistinguishSplitParts();
 mySplitAction.apply(myRoot);
 
 //create an MP viewer :
 SoXtMPExaminerViewer *myViewer = new
 SoXtMPExaminerViewer(myWindow,"ivView", TRUE,
 SoXtMPExaminerViewer::BUILD_ALL,
 SoXtMPExaminerViewer::BROWSER, 1);
 
 myViewer->setSceneGraph(myRoot);
 
 SoXt::show(myWindow);
 SoXt::mainLoop();
 return 0;
}
            
public void CreateSample()
{
  //Create the root :
  SoSeparator myRoot = new SoSeparator();
  
  //read the file
  SoSeparator myScene;
  
  myScene = readFile("$OIVNETHOME/src/Inventor/examples/data/crater.iv");
  
  //create the action... :
  SoSplitGeometryAction mySplitAction = new SoSplitGeometryAction(2, 2);
  mySplitAction.SetSmartSplitting(true);
  mySplitAction.SetDistinguishSplitParts(true);
  
  //... and split the scene :
  mySplitAction.Apply(myScene);
  
  //create the octree :
  SoOctreeOrdering myOctree = new SoOctreeOrdering();
  myOctree.dropCulling.Value = true;
  myOctree.adjustDecimation.Value = false;
  myOctree.dropScreenArea.Value = 1;
  
  //create scene graph :
  myRoot.AddChild(myOctree);
  myOctree.AddChild(myScene);
  
  //create an Examiner Viewer :
  SoWinExaminerViewer myViewer = new SoWinExaminerViewer(this, "", true,
  SoWinFullViewer.BuildFlags.BUILD_ALL, SoWinViewer.Types.BROWSER);
  
  myViewer.SetSceneGraph(myRoot);
  myViewer.SetTitle("OctreeAndSplitAction");
  myViewer.Show();
}
          
public void start()
{
  super.start();
  setLayout(new BorderLayout());
  Panel panel = new Panel(new BorderLayout());

  // Create the root :
  SoSeparator myRoot = new SoSeparator();

  // read the file
  SoSeparator myScene;

  myScene = readFile("../../../../data/models/wheel.iv");

  // create the action... :
  SoSplitGeometryAction mySplitAction = new SoSplitGeometryAction(2, 2);
  mySplitAction.setSmartSplitting(true);
  mySplitAction.setDistinguishSplitParts(true);

  // ... and split the scene :
  mySplitAction.apply(myScene);

  // create the octree :
  SoOctreeOrdering myOctree = new SoOctreeOrdering();
  myOctree.dropCulling.setValue(true);
  myOctree.adjustDecimation.setValue(false);
  myOctree.dropScreenArea.setValue(1);

  // create scene graph :
  myRoot.addChild(myOctree);
  myOctree.addChild(myScene);

  // create an Examiner Viewer :
  myViewer = new SwSimpleViewer(SwSimpleViewer.EXAMINER);
  myViewer.setSceneGraph(myRoot);

  panel.add(myViewer);
  add(panel);
}


Remarks

Split geometry action on the Harley model

Figure 8.11. Split geometry action on the Harley model



SoSplitGeometryAction SoSplitGeometryAction SoSplitGeometryAction splits every SoIndexedShape SoIndexedShape SoIndexedShape it finds during traversal of the scene graph. This means that if you apply the action to a scene graph containing several SoIndexedShape SoIndexedShape SoIndexedShape nodes, each of the indexed shapes will be divided into the requested number of subdivisions (see figure above).

Currently this action affects only SoIndexedFaceSet SoIndexedFaceSet SoIndexedFaceSet and SoIndexedTriangleStripSet SoIndexedTriangleStripSet SoIndexedTriangleStripSet nodes. The new object is the same type of node as the original.

Texture Mapping: If the original geometry has explicit texture coordinates, the results will be correct. If texture coordinates are computed by Open Inventor, the texture will be applied separately to each new object.

Simplify Action

Geometry Simplification

The first LMV (Large Model Visualization) algorithm Open Inventor includes is geometry simplification. The purpose of this algorithm is to represent the geometry with a smaller number of triangles that approximate the original geometry. After simplification, we can choose between the original geometry and one or more simplified versions of the geometry. Therefore, we have some control over the rendering cost.

Open Inventor integrates mesh decimation through two components: a simplifier and a simplify action. The simplify action can be applied to a scene graph, or some part of it, to produce a simpler representation that will be used for subsequent redraws. A new node, SoLevelOfSimplification SoLevelOfSimplification SoLevelOfSimplification , has been added to group these representations and to keep track of their complexity levels, represented by a percentage of the original number of primitives.

Simplifiers

Objects derived from the virtual class SoSimplifier SoSimplifier SoSimplifier receive a set of triangles and return an Open Inventor node representing a simplified version of the original geometry. Thus one can ask for a simplified version of a triangle list containing 50% of the original number of triangles.

Open Inventor contains an SoDecimator SoDecimator SoDecimator object derived from SoSimplifier SoSimplifier SoSimplifier , which implements a geometry simplification algorithm. This algorithm uses iterative contractions of vertex pairs to simplify models. Simplifiers are used internally by simplify actions but can also be used by any application to generate a simplified node from a given list of triangles not stored in an Open Inventor node. The programmer can implement new simplification methods by deriving from SoSimplifier SoSimplifier SoSimplifier .

Simplifier schema

Figure 8.12. Simplifier schema



Simplify Actions

Open Inventor has several simplify actions. Each action can be applied to a specific scene graph to create a simplified version of its geometry.

Each action’s constructor receives an SoSimplifier SoSimplifier SoSimplifier which is used for simplifying the geometry. This simplifier can be an SoDecimator SoDecimator SoDecimator or any class derived from SoSimplifier SoSimplifier SoSimplifier by the application.

Several traversal methods are available:

  • SoGlobalSimplifyAction SoGlobalSimplifyAction SoGlobalSimplifyAction

    SoGlobalSimplifyAction SoGlobalSimplifyAction SoGlobalSimplifyAction traverses the scene graph and collects all the triangles in a single list. This list is then simplified and the result is stored in a new scene graph.

    The SoGlobalSimplifyAction SoGlobalSimplifyAction SoGlobalSimplifyAction provides two simplification strategies:

    • SIMPLIFY_GLOBALLY: All the triangles found in the scene graph are collected in a single object. Then this object is simplified.

    • SIMPLIFY_BY_SUBGROUP: Triangles are collected until a material change or a separator is found.

      The generateNormals flag can be used to force per-vertex normal computation during an SoGlobalSimplifyAction SoGlobalSimplifyAction SoGlobalSimplifyAction . When not set, objects may appear faceted rather than smooth. Setting the addShapeHintsNode flag to TRUE tells Open Inventor to build normals at run-time.

      By default the SoGlobalSimplifyAction SoGlobalSimplifyAction SoGlobalSimplifyAction collects all the triangles even if they are part of a simple shape (Cube, Cone, Sphere, Text3, etc.). Setting the setCatchAllShapesFlag flag to FALSE tells the action to collect triangles only if they belong to a complex shape.

      The getSimplifiedSceneGraph() method returns the root of the new scene graph after applying the SoGlobalSimplifyAction SoGlobalSimplifyAction SoGlobalSimplifyAction

      SoGlobalSimplifyAction schema

      Figure 8.13. SoGlobalSimplifyAction schema



  • SoShapeSimplifyAction SoShapeSimplifyAction SoShapeSimplifyAction

    SoShapeSimplifyAction SoShapeSimplifyAction SoShapeSimplifyAction traverses the scene graph and replaces every complex shape with its simplified version.

    // the Simplifier object: uses geometric simplification
    SoDecimator *decimer = new SoDecimator;
    
    // the simplification action: uses the global simplification.
    SoShapeSimplifyAction simplify(decimer);
    
    int numLevels = 3;
    static float levels[5] = {1.0f, 0.3f, 0.1f};
    
    simplify.setSimplificationLevels(numLevels,levels);
    simplify.setMinTriangles(20);
    
    simplify.apply(selection);
    
    delete decimer;
                    
    // the Simplifier object: uses geometric simplification
    SoDecimator decimer = new SoDecimator();
    
    // the simplification action: uses the global simplification.
    SoShapeSimplifyAction simplify = new SoShapeSimplifyAction(decimer);
    
    int numLevels = 3;
    float[] levels = new float[] {1.0f, 0.3f, 0.1f};
    
    simplify.SetSimplificationLevels(levels);
    simplify.SetMinTriangles(20);
    
    simplify.Apply(selection);
                  
    // the Simplifier object: uses geometric simplification
    SoDecimator decimer = new SoDecimator();
    
    // the simplification action: uses the global simplification.
    SoShapeSimplifyAction simplify = new SoShapeSimplifyAction(decimer);
    
    int numLevels = 3;
    float[] levels = new float[] {1.0f, 0.3f, 0.1f};
    
    simplify.setSimplificationLevels(levels);
    simplify.setMinTriangles(20);
    
    simplify.apply(selection);
    
  • SoReorganizeAction SoReorganizeAction SoReorganizeAction

    SoReorganizeAction SoReorganizeAction SoReorganizeAction reorganizes the scene graph by grouping shapes with common properties, then groups these shapes into a single shape and runs the simplifier on it. If a simplifier is not provided, it creates an IndexedTriangleStripSet from this shape. This action is similar to the ivfix utility program.

    // Replace scene with reorganized scene graph:
    int numLevels = 3;
    static float levels[5] = {1.0f, 0.3f, 0.1f};
    SoReorganizeAction *simplify;
    
    if (doSimplify)
    {
      SoDecimator *decimer = new SoDecimator;
      simplify = new SoReorganizeAction(decimer);
      simplify->setSimplificationLevels(numLevels,levels);
      simplify->setSizeFactor(4.);
      simplify->setMinTriangles(20);
    }
    else
    {
      simplify = new SoReorganizeAction;
      simplify->generateNormals(TRUE);
      simplify->generateTriangleStrips(TRUE);
    }
    
    simplify->apply(sep);
    
    sep->unref();
    
    if (simplify->getSimplifiedSceneGraph() != NULL)
    {
      SoNode *result = simplify->getSimplifiedSceneGraph();
      ...
    }
    
    delete simplify;
                    
    // Replace scene with reorganized scene graph:
    int numLevels = 3;
    float[] levels = new float[] {1.0f, 0.3f, 0.1f};
    SoReorganizeAction simplify;
    
    if (doSimplify)
    {
      SoDecimator decimer = new SoDecimator();
      simplify = new SoReorganizeAction(decimer);
      simplify.SetSimplificationLevels(levels);
      simplify.SetSizeFactor(4f);
      simplify.SetMinTriangles(20);
    }
    else
    {
      simplify = new SoReorganizeAction();
      simplify.GenerateNormals(true);
      simplify.GenerateTriangleStrips(true);
    }
    
    simplify.Apply(sep);
    
    if (simplify.GetSimplifiedSceneGraph() != null)
    {
      SoNode result = simplify.GetSimplifiedSceneGraph();
      ...
    }
                  
    // Replace scene with reorganized scene graph:
    int numLevels = 3;
    float[] levels = new float[] {1.0f, 0.3f, 0.1f};
    SoReorganizeAction simplify;
    
    if (doSimplify)
    {
      SoDecimator decimer = new SoDecimator();
      simplify = new SoReorganizeAction(decimer);
      simplify.setSimplificationLevels(levels);
      simplify.setSizeFactor(4f);
      simplify.setMinTriangles(20);
    }
    else
    {
      simplify = new SoReorganizeAction();
      simplify.generateNormals(true);
      simplify.generateTriangleStrips(true);
    }
    
    simplify.apply(sep);
    
    if (simplify.getSimplifiedSceneGraph() != null)
    {
      SoNode result = simplify.getSimplifiedSceneGraph();
      ...
    }
    

    Each simplified sub-level can be stored in regular separators or in WWWInline nodes. In this case, the urlName field specifies the full url to use for each WWWInline. For instance if urlName = “dir/file”, the action will generate “dir/file_1.wrl”, ”dir/file_2.wrl”, and so on.

When a simplify action generates SoLevelOfSimplification SoLevelOfSimplification SoLevelOfSimplification nodes, the range field of each level of simplification node can be given globally in the range field of the simplify action. If the simplify action range field is not set, the simplify action computes it automatically for each level of simplification node. In this case, the sizeFactor field can be used to customize the range computation.

A minimum number of triangles can be set to stop the simplification when the object becomes too small.

Specifying 0% for the last level inserts an empty node in the scene graph. This allows you to completely remove objects that are too small or too far from the viewer.