Use a tube factory

From JReality Wiki
Jump to: navigation, search

JavaDoc: TubeFactory JavaDoc: PolygonalTubeFactory


The jReality tube factories put tubes around curves. TubeFactory itself is an interface; the simplest implementing class is PolygonalTubeFactory.


Simple example

Source file: TubeFactory01


Run as Java webstart


Here's a simple example showing how to use a PolygonalTubeFactory to put a tube around a curve.

  • The curve is provided by the method Primitives.discreteTorusKnot(). You can use any IndexedLineSet as input to the constructor.
  • The second argument to the constructor is an integer identifying which edge in the IndexedLineSet should be tubed.


If the tubes look familiar, it's because the default line shader uses the same method to generate tubes when you specify that edges should be drawn with tubes. But notice that the line shader always disables display of points and lines when it displays such tubes. (Otherwise it would fall into an infinite regress, drawing tubes around the edges of the tubes around the edges around the tubes ...).


 
...
public class TubeFactory01 {
 
  public static void main(String[] args)  {
    SceneGraphComponent torussgc = SceneGraphUtility.createFullSceneGraphComponent("torus knot");
    DefaultGeometryShader dgs = (DefaultGeometryShader) 
           ShaderUtility.createDefaultGeometryShader(torussgc.getAppearance(), true);
    dgs.setShowLines(true);
    dgs.setShowPoints(false);
    DefaultPolygonShader dps = (DefaultPolygonShader) dgs.createPolygonShader("default");
    dps.setDiffuseColor(Color.white);
    DefaultLineShader dls = (DefaultLineShader) dgs.createLineShader("default");
    dls.setDiffuseColor(Color.yellow);
    dls.setTubeRadius(.01);
    IndexedLineSet torus1 = Primitives.discreteTorusKnot(1, .25, 2, 9, 250);
    PolygonalTubeFactory ptf = new PolygonalTubeFactory(torus1, 0);
    ptf.setClosed(true);
    ptf.setRadius(.1);
    ptf.setGenerateEdges(true);
    ptf.update();
    IndexedFaceSet torus1Tubes = ptf.getTube();
    torussgc.setGeometry(torus1Tubes);
    JRViewer.display(torussgc);
  }
}
 

Advanced example

Source file: TubeFactory02


Run as Java webstart


This second example shows a more sophisticated usage.

  • The default cross section is replaced by a customized, star-shaped one.
  • The vertex colors of the tube are inherited from the vertex colors of the line set.
  • The tube is twisted, an effect which is only noticeable since the cross section is no longer circular.


And, for those who like to change parameters interactively, the example shows how to add a ShrinkPanel plugin to a ViewerApp, in this case, one that allows you to control the three different radii which determine the geometry of the example.


...
	torussgc = SceneGraphUtility
				.createFullSceneGraphComponent("torus knot");
	dgs = (DefaultGeometryShader) ShaderUtility
				.createDefaultGeometryShader(torussgc.getAppearance(), true);
	dgs.setShowLines(drawEdges);
	dgs.setShowPoints(false);
	dps = (DefaultPolygonShader) dgs.createPolygonShader("default");
	dps.setSmoothShading(isSmooth);
	updateGeometry();
	JRViewer v = new JRViewer();
	v.addBasicUI();
	v.addContentSupport(ContentType.Raw);
	v.registerPlugin(new ContentAppearance());
	v.setContent(torussgc);
	SimpleController c = v.getController();
	Plugin p = createShrinkPanel(getInspector(), "inspector");
	c.registerPlugin(p);
	v.startup();
	Viewer viewer =  v.getPlugin(View.class).getViewer();
	CameraUtility.encompass(viewer);
	addKeyListener(viewer);  
    }
 
  private static void updateGeometry() {
    IndexedFaceSet tubedTorusKnot = tubedTorusKnot(R, r, tubeRadius); 
    torussgc.setGeometry(tubedTorusKnot);
  }
 
  private static IndexedFaceSet tubedTorusKnot(double R, double r, double tubeRadius) {
     IndexedLineSet torus1 = Primitives.discreteTorusKnot(R,r, 2, 9, 250);//       double[][] verts = new double[250][3];
     colorVertices(torus1, new double[] {1,0,0}, new double[] {0,1,0});
     // create a non-circular cross section for the tube
     int size = 16;
     double scale = 1;
     double[][] mysection = new double[size][3];
     for (int i = 0; i<size; ++i)  {
         double angle = (i/(size-1.0)) * Math.PI * 2;
         mysection[i][0] = scale * Math.cos(angle)  *(1.5+Math.cos(4*angle));
         mysection[i][1] = scale *  Math.sin(angle)  *(1.5+Math.cos(4*angle));
         mysection[i][2] = 0.0;
     }
     PolygonalTubeFactory ptf = new PolygonalTubeFactory(torus1, 0);
     ptf.setClosed(true);
     ptf.setVertexColorsEnabled(true);
     ptf.setRadius(tubeRadius);
     ptf.setCrossSection(mysection);
     ptf.setTwists(6);
     double[][] vcolors = torus1.getVertexAttributes(Attribute.COLORS).toDoubleArrayArray(null);
     ptf.setVertexColors(vcolors);
     ptf.setGenerateEdges(true);
     ptf.update();
     IndexedFaceSet torus1Tubes = ptf.getTube();
     return torus1Tubes;
  }
  
  public static void colorVertices(IndexedLineSet ils, double[] color1, double[] color2)  {
    int nPts = ils.getNumPoints();
    double[][] colors = new double[nPts][3];
    double[][] vertices = ils.getVertexAttributes(Attribute.COORDINATES).toDoubleArrayArray(null);
    for (int i = 1; i<nPts-1; ++i)  {
      double[] v1 = Rn.subtract(null, vertices[i], vertices[i-1]);
      double t = 10 * Math.sqrt(Math.abs( v1[0]*v1[0] + v1[1] * v1[1]));
//      t = 10 * Rn.euclideanDistance(vertices[i], vertices[i-1]);
      t = t  - ((int) t);
      Rn.linearCombination(colors[i],t, color1, 1-t, color2);
    }
    System.arraycopy(colors[1], 0, colors[0], 0, 3);
    System.arraycopy(colors[nPts-2], 0, colors[nPts-1], 0, 3);
    ils.setVertexAttributes(Attribute.COLORS, StorageModel.DOUBLE_ARRAY.array(3).createReadOnly(colors));
  }
 
  private static Component getInspector() {
    Box container = Box.createVerticalBox();
    final TextSlider RSlider = new TextSlider.Double("R",  SwingConstants.HORIZONTAL, 0.0, 2, R);
    RSlider.addActionListener(new ActionListener()  {
      public void actionPerformed(ActionEvent e)  {
        R = RSlider.getValue().doubleValue();
        updateGeometry();
      }
    });
    container.add(RSlider);
    final TextSlider rSlider = new TextSlider.Double("r",  SwingConstants.HORIZONTAL, 0.0, 1, r);
    rSlider.addActionListener(new ActionListener()  {
      public void actionPerformed(ActionEvent e)  {
        r = rSlider.getValue().doubleValue();
        updateGeometry();
      }
    });
    container.add(rSlider);
    final TextSlider rtSlider = new TextSlider.Double("tube radius",  SwingConstants.HORIZONTAL, 0.0, 1, tubeRadius);
    rtSlider.addActionListener(new ActionListener()  {
      public void actionPerformed(ActionEvent e)  {
        tubeRadius = rtSlider.getValue().doubleValue();
        updateGeometry();
      }
    });
    container.add(rtSlider);
    container.setName("Parameters");
    return container;
  }
 
}
 

The shrink panel is created in the following code:

	public Plugin createShrinkPanel( final Component c, final String title) {
	ShrinkPanelPlugin p = new ShrinkPanelPlugin() {
			
	{
	GridLayout gl = new GridLayout();
        gl.setRows(1);
	setInitialPosition(SHRINKER_RIGHT);
	shrinkPanel.setName(title);
	shrinkPanel.setLayout(gl);
	shrinkPanel.add(c);
	}
			
	@Override
	public Class<? extends SideContainerPerspective> getPerspectivePluginClass() {
		return View.class;
	}
 
	@Override
	public PluginInfo getPluginInfo() {
		return new PluginInfo(title);
	}
	};
	return p;
 
    }
Previous: Parametrized Surfaces Developer Tutorial: Contents Next: Use a ball and stick factory