Difference between revisions of "Hacky Sculpt Previewer"

From Second Life Wiki
Jump to navigation Jump to search
 
 
(5 intermediate revisions by 2 users not shown)
Line 1: Line 1:
<pre>
<pre>
/**
/**
  *  First attempt at a sculpt previewer
  *  First attempt at a sculpt previewer
  *  Adapted from Sun's ObjLoad
  *  Adapted from Sun's ObjLoad
  *  BSD license (inherited)
  *  BSD license (inherited)
* */
/**
* Original Yumi Murakami.
* 6-5-2007 Strife Onizuka:
*  Fixed centering of object and wrapping around when
*  downsampling.
* 6-5-2007 Yumi Murakami
*  Use system native UI, attempted to get shading to work.
*  Now generates surface normals and installs Materials, both
*  of which are required for shading - but shading still does
*  not work :(
  * */
  * */


Line 19: Line 30:
import javax.media.j3d.*;
import javax.media.j3d.*;
import javax.swing.JFileChooser;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.vecmath.*;
import javax.vecmath.*;
Line 27: Line 40:
import com.sun.j3d.loaders.objectfile.ObjectFile;
import com.sun.j3d.loaders.objectfile.ObjectFile;
import com.sun.j3d.utils.behaviors.vp.*;
import com.sun.j3d.utils.behaviors.vp.*;
import com.sun.j3d.utils.geometry.*;
import com.sun.j3d.utils.universe.*;
import com.sun.j3d.utils.universe.*;


public class SculptPreview extends javax.swing.JFrame {
public class SculptPreview extends javax.swing.JFrame {


private boolean left_right = true;
private boolean top_bottom = false;
     private boolean spin = false;
     private boolean spin = false;
     private boolean noTriangulate = false;
     private boolean noTriangulate = false;
Line 45: Line 61:
         int red = (RGB >> 16) & 255;
         int red = (RGB >> 16) & 255;
         double[] result = new double[3];
         double[] result = new double[3];
         result[0] = ((double)red) / 255.0;
         result[0] = (((double)red) / 255.0) - 0.5;
         result[1] = ((double)green) / 255.0;
         result[1] = (((double)green) / 255.0) - 0.5;
         result[2] = ((double)blue) / 255.0;
         result[2] = (((double)blue) / 255.0) - 0.5;
         return result;
         return result;
     }
     }
 
     public BranchGroup createSceneGraph() {
     public BranchGroup createSceneGraph() {
// Create the root of the branch graph
// Create the root of the branch graph
BranchGroup objRoot = new BranchGroup();
BranchGroup objRoot = new BranchGroup();


        // Create a Transformgroup to scale all objects so they
// Create a Transformgroup to scale all objects so they
        // appear in the scene.
// appear in the scene.
        TransformGroup objScale = new TransformGroup();
TransformGroup objScale = new TransformGroup();
        Transform3D t3d = new Transform3D();
Transform3D t3d = new Transform3D();
        t3d.setScale(0.7);
t3d.setScale(0.7);
        objScale.setTransform(t3d);
objScale.setTransform(t3d);
        objRoot.addChild(objScale);
objRoot.addChild(objScale);


// Create the transform group node and initialize it to the
// Create the transform group node and initialize it to the
// identity.  Enable the TRANSFORM_WRITE capability so that
// identity.  Enable the TRANSFORM_WRITE capability so that
// our behavior code can modify it at runtime.  Add it to the
// our behavior code can modify it at runtime.  Add it to the
// root of the subgraph.
// root of the subgraph.
TransformGroup objTrans = new TransformGroup();
TransformGroup objTrans = new TransformGroup();
objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
objScale.addChild(objTrans);
objScale.addChild(objTrans);


Shape3D s = new Shape3D();
Shape3D s = new Shape3D();
        BufferedImage theImage = null;
BufferedImage theImage = null;


        JFileChooser chooser = new JFileChooser();
JFileChooser chooser = new JFileChooser();
        FileNameExtensionFilter filter = new FileNameExtensionFilter("Supported Image Formats",ImageIO.getReaderFileSuffixes());
FileNameExtensionFilter filter = new FileNameExtensionFilter("Supported Image Formats",ImageIO.getReaderFileSuffixes());
        chooser.setFileFilter(filter);
                chooser.setFileFilter(filter);
        int chooserResult = chooser.showOpenDialog(this);
        if (chooserResult == JFileChooser.CANCEL_OPTION) {
            System.out.println("Cancelled");
            System.exit(0);
        }
     
        try {
            theImage = ImageIO.read(chooser.getSelectedFile());
        } catch (IOException e) {
            System.out.println("Error opening sculpt texture file");
            e.printStackTrace();
            System.exit(0);
        }
        int xStep, yStep;
        int width = theImage.getWidth();
        int height = theImage.getHeight();
        xStep = width / 64;
        yStep = height / 64;
        if (xStep < 1) xStep = 1;
        if (yStep < 1) xStep = 1;
        if ((xStep > 1) || (yStep > 1)) {
            System.out.println("Sculpt texture being downsampled, please limit to 64x64");
        }


        int vertices = (width / xStep) * (height / yStep);
                boolean ok = false;
        System.out.println(vertices);
                while (!ok) {
                    int chooserResult = chooser.showOpenDialog(this);
                    if (chooserResult == JFileChooser.CANCEL_OPTION) {
                            System.exit(0);
                    }
                    ok = true;
    try {
theImage = ImageIO.read(chooser.getSelectedFile());
                    } catch (IOException e) {
                        JOptionPane.showMessageDialog(this,"An error occured opening the texture file:\n" + e.getMessage(),"File error",JOptionPane.ERROR_MESSAGE);
System.out.println("Error opening sculpt texture file");
                        ok = false;
                    }
                }


        TriangleArray ta = new TriangleArray(vertices * 6,GeometryArray.COORDINATES);
int xStep, yStep;
int width = theImage.getWidth();
int height = theImage.getHeight();
xStep = width / 64;
yStep = height / 64;
if (xStep < 1)
xStep = 1;
if (yStep < 1)
yStep = 1;
if ((xStep > 1) || (yStep > 1)) {
                    JOptionPane.showMessageDialog(this,"Sculpt texture dimensions are " + width + "x" + height + ".\nMaximum detail level on Second Life to 64x64,\nso the extra pixel data is being wasted.","Texture Over Resolution",JOptionPane.WARNING_MESSAGE);
}
int xmax = width - (left_right?0:xStep);
int ymax = height - (top_bottom?0:yStep);
int vertices = (xmax/xStep) * (height/yStep);
System.out.println(vertices);


        int x, y, r;
                TriangleArray ta = new TriangleArray(vertices * 6,GeometryArray.COORDINATES);
        r=0;
int x, y, r;
        for (x=0; x<width; x+=xStep) {
r = 0;
            for (y=0; y<height; y+=yStep) {
                int topLeftRGB = theImage.getRGB(x,y);
double[] bottom = new double[3];
                int topRightRGB = theImage.getRGB((x+xStep) % width,y);
double[] top = new double[3];
                int bottomLeftRGB = theImage.getRGB(x,(y+yStep) % height);
                int bottomRightRGB = theImage.getRGB((x+xStep) % width,(y+yStep) % height);
if(!top_bottom)//find the top center and bottom averages
                // Top left triangle
{
                ta.setCoordinates(r,RGBtoVertex(topLeftRGB));
r = 0;
                ta.setCoordinates(r+1,RGBtoVertex(topRightRGB));
bottom[0] = bottom[1] = bottom[2] = top[0] = top[1] = top[2] = 0.0;
                ta.setCoordinates(r+2,RGBtoVertex(bottomLeftRGB));
for (x=0; x<width; x+=xStep) {
                // Bottom right triangle
double[] pb = RGBtoVertex(theImage.getRGB(x,0));
                ta.setCoordinates(r+3,RGBtoVertex(topRightRGB));
double[] pt = RGBtoVertex(theImage.getRGB(x,height - xStep));
                ta.setCoordinates(r+4,RGBtoVertex(bottomLeftRGB));
bottom[0] += pb[0];
                ta.setCoordinates(r+5,RGBtoVertex(bottomRightRGB));
bottom[1] += pb[1];
bottom[2] += pb[2];
top[0] += pt[0];
top[1] += pt[1];
top[2] += pt[2];
}
bottom[0] = (bottom[0] * xStep) / width;
bottom[1] = (bottom[1] * xStep) / width;
bottom[2] = (bottom[2] * xStep) / width;
top[0] = (top[0] * xStep) / width;
top[1] = (top[1] * xStep) / width;
top[2] = (top[2] * xStep) / width;
}
for (x=0; x<xmax; x+=xStep) {//link left to right
if(!top_bottom)
{
int BottomLeftRGB = theImage.getRGB(x,0);
int BottomRightRGB = theImage.getRGB((x+xStep) % width,0);
ta.setCoordinates(r,bottom);
                                ta.setCoordinates(r+1,RGBtoVertex(BottomRightRGB));
ta.setCoordinates(r+2,RGBtoVertex(BottomLeftRGB));
r+=3;
}
for (y=0; y<ymax; y+=yStep) {
int topLeftRGB = theImage.getRGB(x,y);
int topRightRGB = theImage.getRGB((x+xStep) % width,y);
int bottomLeftRGB = theImage.getRGB(x,(y+yStep) % height);
int bottomRightRGB = theImage.getRGB((x+xStep) % width,(y+yStep) % height);
// Top left triangle
ta.setCoordinates(r,RGBtoVertex(topLeftRGB));
ta.setCoordinates(r+1,RGBtoVertex(topRightRGB));
ta.setCoordinates(r+2,RGBtoVertex(bottomLeftRGB));
// Bottom right triangle
ta.setCoordinates(r+3,RGBtoVertex(topRightRGB));
ta.setCoordinates(r+4,RGBtoVertex(bottomRightRGB));
                                ta.setCoordinates(r+5,RGBtoVertex(bottomLeftRGB));
r+=6;
}
if(!top_bottom)
{
int TopLeftRGB = theImage.getRGB(x,ymax);
int TopRightRGB = theImage.getRGB((x+xStep) % width,ymax);
ta.setCoordinates(r,RGBtoVertex(TopLeftRGB));
ta.setCoordinates(r+1,RGBtoVertex(TopRightRGB));
ta.setCoordinates(r+2,top);
r+=3;
}
}


                 r+=6;
                 GeometryInfo info = new GeometryInfo(ta);
            }
                NormalGenerator gen = new NormalGenerator();
        }
                gen.generateNormals(info);
                s.setGeometry(info.getIndexedGeometryArray());


        s.setGeometry(ta);
                Appearance app = new Appearance();
        objTrans.addChild(s);
                Material mat = new Material( new Color3f((float)0.0,(float)0.0,(float)0.0), // Ambient
                                            new Color3f((float)1.0,(float)1.0,(float)1.0), // Emissive
                                            new Color3f((float)0.5,(float)0.5,(float)0.5), // Diffuse
                                            new Color3f((float)1.0,(float)1.0,(float)1.0), // Specular
                                            (float)10.0 // Shiny
                                            );                        // );
                app.setMaterial(mat);
                s.setAppearance(app);
objTrans.addChild(s);


BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);
BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);


        if (spin) {
if (spin) {
  Transform3D yAxis = new Transform3D();
  Transform3D yAxis = new Transform3D();
  Alpha rotationAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE,
  Alpha rotationAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE,
  0, 0,
  0, 0,
  4000, 0, 0,
  4000, 0, 0,
  0, 0, 0);
  0, 0, 0);


  RotationInterpolator rotator =
  RotationInterpolator rotator =
      new RotationInterpolator(rotationAlpha, objTrans, yAxis,
  new RotationInterpolator(rotationAlpha, objTrans, yAxis,
      0.0f, (float) Math.PI*2.0f);
  0.0f, (float) Math.PI*2.0f);
  rotator.setSchedulingBounds(bounds);
  rotator.setSchedulingBounds(bounds);
  objTrans.addChild(rotator);
  objTrans.addChild(rotator);
}  
}  


         // Set up the background
         // Set up the background
Line 158: Line 239:
     private Canvas3D createUniverse() {
     private Canvas3D createUniverse() {
// Get the preferred graphics configuration for the default screen
// Get the preferred graphics configuration for the default screen
GraphicsConfiguration config =
GraphicsConfiguration config =
    SimpleUniverse.getPreferredConfiguration();
SimpleUniverse.getPreferredConfiguration();


// Create a Canvas3D using the preferred configuration
// Create a Canvas3D using the preferred configuration
Canvas3D canvas3d = new Canvas3D(config);
Canvas3D canvas3d = new Canvas3D(config);


// Create simple universe with view branch
// Create simple universe with view branch
univ = new SimpleUniverse(canvas3d);
univ = new SimpleUniverse(canvas3d);
        BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);
BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);


// add mouse behaviors to the ViewingPlatform
// add mouse behaviors to the ViewingPlatform
ViewingPlatform viewingPlatform = univ.getViewingPlatform();
ViewingPlatform viewingPlatform = univ.getViewingPlatform();


PlatformGeometry pg = new PlatformGeometry();
PlatformGeometry pg = new PlatformGeometry();


// Set up the ambient light
// Set up the ambient light
Color3f ambientColor = new Color3f(0.1f, 0.1f, 0.1f);
Color3f ambientColor = new Color3f(0.1f, 0.1f, 0.1f);
AmbientLight ambientLightNode = new AmbientLight(ambientColor);
AmbientLight ambientLightNode = new AmbientLight(ambientColor);
ambientLightNode.setInfluencingBounds(bounds);
ambientLightNode.setInfluencingBounds(bounds);
pg.addChild(ambientLightNode);
pg.addChild(ambientLightNode);


// Set up the directional lights
// Set up the directional lights
Color3f light1Color = new Color3f(1.0f, 1.0f, 0.9f);
Color3f light1Color = new Color3f(1.0f, 1.0f, 0.9f);
Vector3f light1Direction  = new Vector3f(1.0f, 1.0f, 1.0f);
Vector3f light1Direction  = new Vector3f(1.0f, 1.0f, 1.0f);
Color3f light2Color = new Color3f(1.0f, 1.0f, 1.0f);
Color3f light2Color = new Color3f(1.0f, 1.0f, 1.0f);
Vector3f light2Direction  = new Vector3f(-1.0f, -1.0f, -1.0f);
Vector3f light2Direction  = new Vector3f(-1.0f, -1.0f, -1.0f);


DirectionalLight light1
DirectionalLight light1
    = new DirectionalLight(light1Color, light1Direction);
= new DirectionalLight(light1Color, light1Direction);
light1.setInfluencingBounds(bounds);
light1.setInfluencingBounds(bounds);
pg.addChild(light1);
pg.addChild(light1);


DirectionalLight light2
DirectionalLight light2
    = new DirectionalLight(light2Color, light2Direction);
= new DirectionalLight(light2Color, light2Direction);
light2.setInfluencingBounds(bounds);
light2.setInfluencingBounds(bounds);
pg.addChild(light2);
pg.addChild(light2);


viewingPlatform.setPlatformGeometry( pg );
viewingPlatform.setPlatformGeometry( pg );
     
 
// This will move the ViewPlatform back a bit so the
// This will move the ViewPlatform back a bit so the
// objects in the scene can be viewed.
// objects in the scene can be viewed.
viewingPlatform.setNominalViewingTransform();
viewingPlatform.setNominalViewingTransform();


if (!spin) {
if (!spin) {
            OrbitBehavior orbit = new OrbitBehavior(canvas3d,
OrbitBehavior orbit = new OrbitBehavior(canvas3d,
    OrbitBehavior.REVERSE_ALL);
OrbitBehavior.REVERSE_ALL);
            orbit.setSchedulingBounds(bounds);
orbit.setSchedulingBounds(bounds);
            viewingPlatform.setViewPlatformBehavior(orbit);      
viewingPlatform.setViewPlatformBehavior(orbit);      
}         
}         
       
        // Ensure at least 5 msec per frame (i.e., < 200Hz)
// Ensure at least 5 msec per frame (i.e., < 200Hz)
univ.getViewer().getView().setMinimumFrameCycleTime(5);
univ.getViewer().getView().setMinimumFrameCycleTime(5);


return canvas3d;
return canvas3d;
     }
     }
    private void usage() {
        System.out.println(
                "Usage: java SculptPreview [-s] [-n] [-t] [-c degrees] <.obj file>");
        System.out.println("  -s Spin (no user interaction)");
        System.out.println("  -n No triangulation");
        System.out.println("  -t No stripification");
        System.out.println(
                "  -c Set crease angle for normal generation (default is 60 without");
        System.out.println(
                "    smoothing group info, otherwise 180 within smoothing groups)");
        System.exit(0);
    } // End of usage


     /**
     /**
Line 231: Line 299:
     */
     */
     public SculptPreview(String args[]) {
     public SculptPreview(String args[]) {
         if (args.length != 0) {
         // Initialize the GUI components
            for (int i = 0 ; i < args.length ; i++) {
                if (args[i].startsWith("-")) {
                    if (args[i].equals("-s")) {
                        spin = true;
                    } else if (args[i].equals("-n")) {
                        noTriangulate = true;
                    } else if (args[i].equals("-t")) {
                        noStripify = true;
                    } else if (args[i].equals("-c")) {
                        if (i < args.length - 1) {
                            creaseAngle = (new Double(args[++i])).doubleValue();
                        } else usage();
                    } else {
                        usage();
                    }
                } else {
                    try {
                        if ((args[i].indexOf("file:") == 0) ||
                                (args[i].indexOf("http") == 0)) {
                            filename = new URL(args[i]);
                        } else if (args[i].charAt(0) != '/') {
                            filename = new URL("file:./" + args[i]);
                        } else {
                            filename = new URL("file:" + args[i]);
                        }
                    } catch (MalformedURLException e) {
                        System.err.println(e);
                        System.exit(1);
                    }
                }
            }     
        }
           
     
// Initialize the GUI components
initComponents();
initComponents();


Line 287: Line 320:
     // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
     // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
     private void initComponents() {
     private void initComponents() {
        try  {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            System.out.println("Error setting UI!");
        }
         drawingPanel = new javax.swing.JPanel();
         drawingPanel = new javax.swing.JPanel();


Line 305: Line 344:
         java.awt.EventQueue.invokeLater(new Runnable() {
         java.awt.EventQueue.invokeLater(new Runnable() {
             public void run() {
             public void run() {
                 ObjLoad objLoad = new ObjLoad(args);
                 SculptPreview sp = new SculptPreview(args);
                 objLoad.setVisible(true);
                 sp.setVisible(true);
             }
             }
         });
         });
Line 315: Line 354:
     // End of variables declaration//GEN-END:variables
     // End of variables declaration//GEN-END:variables
      
      
}
}</pre>
</pre>

Latest revision as of 20:22, 5 May 2007

/**
 *  First attempt at a sculpt previewer
 *  Adapted from Sun's ObjLoad
 *  BSD license (inherited)
 * */

/**
 * Original Yumi Murakami.
 * 6-5-2007 Strife Onizuka:
 *   Fixed centering of object and wrapping around when
 *   downsampling.
 * 6-5-2007 Yumi Murakami 
 *   Use system native UI, attempted to get shading to work.
 *   Now generates surface normals and installs Materials, both
 *   of which are required for shading - but shading still does
 *   not work :(
 * */

import java.awt.GraphicsConfiguration;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.imageio.*;
import javax.imageio.stream.ImageInputStream;
import javax.media.j3d.*;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.vecmath.*;

import com.sun.j3d.loaders.IncorrectFormatException;
import com.sun.j3d.loaders.ParsingErrorException;
import com.sun.j3d.loaders.Scene;
import com.sun.j3d.loaders.objectfile.ObjectFile;
import com.sun.j3d.utils.behaviors.vp.*;
import com.sun.j3d.utils.geometry.*;
import com.sun.j3d.utils.universe.*;

public class SculptPreview extends javax.swing.JFrame {

	private boolean left_right = true;
	private boolean top_bottom = false;
    private boolean spin = false;
    private boolean noTriangulate = false;
    private boolean noStripify = false;
    private double creaseAngle = 60.0;
    private URL filename = null;

    private SimpleUniverse univ = null;
    private BranchGroup scene = null;

    double[] RGBtoVertex(int RGB) {
        int blue = RGB & 255;
        int green = (RGB >> 8) & 255;
        int red = (RGB >> 16) & 255;
        double[] result = new double[3];
        result[0] = (((double)red) / 255.0) - 0.5;
        result[1] = (((double)green) / 255.0) - 0.5;
        result[2] = (((double)blue) / 255.0) - 0.5;
        return result;
    }
	
    public BranchGroup createSceneGraph() {
		// Create the root of the branch graph
		BranchGroup objRoot = new BranchGroup();

			// Create a Transformgroup to scale all objects so they
			// appear in the scene.
			TransformGroup objScale = new TransformGroup();
			Transform3D t3d = new Transform3D();
			t3d.setScale(0.7);
			objScale.setTransform(t3d);
			objRoot.addChild(objScale);

		// Create the transform group node and initialize it to the
		// identity.  Enable the TRANSFORM_WRITE capability so that
		// our behavior code can modify it at runtime.  Add it to the
		// root of the subgraph.
		TransformGroup objTrans = new TransformGroup();
		objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
		objScale.addChild(objTrans);

		Shape3D s = new Shape3D();
		BufferedImage theImage = null;

		JFileChooser chooser = new JFileChooser();
		FileNameExtensionFilter filter = new FileNameExtensionFilter("Supported Image Formats",ImageIO.getReaderFileSuffixes());
                chooser.setFileFilter(filter);

                boolean ok = false;
                while (!ok) {
                    int chooserResult = chooser.showOpenDialog(this);
                    if (chooserResult == JFileChooser.CANCEL_OPTION) {
                            System.exit(0);
                    }
                    ok = true;
		    try {
			theImage = ImageIO.read(chooser.getSelectedFile());
                    } catch (IOException e) {
                        JOptionPane.showMessageDialog(this,"An error occured opening the texture file:\n" + e.getMessage(),"File error",JOptionPane.ERROR_MESSAGE);
			System.out.println("Error opening sculpt texture file");
                        ok = false;
                    }
                }

		int xStep, yStep;
		int width = theImage.getWidth();
		int height = theImage.getHeight();
		xStep = width / 64;
		yStep = height / 64;
		if (xStep < 1)
			xStep = 1;
		if (yStep < 1)
			yStep = 1;
		if ((xStep > 1) || (yStep > 1)) {
                    JOptionPane.showMessageDialog(this,"Sculpt texture dimensions are " + width + "x" + height + ".\nMaximum detail level on Second Life to 64x64,\nso the extra pixel data is being wasted.","Texture Over Resolution",JOptionPane.WARNING_MESSAGE);
		}
		int xmax = width - (left_right?0:xStep);
		int ymax = height - (top_bottom?0:yStep);
		
		int vertices = (xmax/xStep) * (height/yStep);
		System.out.println(vertices);

                TriangleArray ta = new TriangleArray(vertices * 6,GeometryArray.COORDINATES);
		int x, y, r;
		r = 0;
		
		double[] bottom = new double[3];
		double[] top = new double[3];
		
		if(!top_bottom)//find the top center and bottom averages
		{
			r = 0;
			bottom[0] = bottom[1] = bottom[2] = top[0] = top[1] = top[2] = 0.0;
			for (x=0; x<width; x+=xStep) {
				double[] pb = RGBtoVertex(theImage.getRGB(x,0));
				double[] pt = RGBtoVertex(theImage.getRGB(x,height - xStep));
				bottom[0] += pb[0];
				bottom[1] += pb[1];
				bottom[2] += pb[2];
				top[0] += pt[0];
				top[1] += pt[1];
				top[2] += pt[2];
			}
			bottom[0] = (bottom[0] * xStep) / width;
			bottom[1] = (bottom[1] * xStep) / width;
			bottom[2] = (bottom[2] * xStep) / width;
			top[0] = (top[0] * xStep) / width;
			top[1] = (top[1] * xStep) / width;
			top[2] = (top[2] * xStep) / width;
		}
		
		for (x=0; x<xmax; x+=xStep) {//link left to right
			if(!top_bottom)
			{
				int BottomLeftRGB = theImage.getRGB(x,0);
				int BottomRightRGB = theImage.getRGB((x+xStep) % width,0);
				ta.setCoordinates(r,bottom);
                                ta.setCoordinates(r+1,RGBtoVertex(BottomRightRGB));
				ta.setCoordinates(r+2,RGBtoVertex(BottomLeftRGB));
				
				r+=3;
			}
			for (y=0; y<ymax; y+=yStep) {
				int topLeftRGB = theImage.getRGB(x,y);
				int topRightRGB = theImage.getRGB((x+xStep) % width,y);
				int bottomLeftRGB = theImage.getRGB(x,(y+yStep) % height);
				int bottomRightRGB = theImage.getRGB((x+xStep) % width,(y+yStep) % height);
				// Top left triangle
				ta.setCoordinates(r,RGBtoVertex(topLeftRGB));
				ta.setCoordinates(r+1,RGBtoVertex(topRightRGB));
				ta.setCoordinates(r+2,RGBtoVertex(bottomLeftRGB));
				// Bottom right triangle
				ta.setCoordinates(r+3,RGBtoVertex(topRightRGB));
				ta.setCoordinates(r+4,RGBtoVertex(bottomRightRGB));
                                ta.setCoordinates(r+5,RGBtoVertex(bottomLeftRGB));
				r+=6;
			}
			if(!top_bottom)
			{
				int TopLeftRGB = theImage.getRGB(x,ymax);
				int TopRightRGB = theImage.getRGB((x+xStep) % width,ymax);
				ta.setCoordinates(r,RGBtoVertex(TopLeftRGB));
				ta.setCoordinates(r+1,RGBtoVertex(TopRightRGB));
				ta.setCoordinates(r+2,top);
				
				r+=3;
			}
		}

                GeometryInfo info = new GeometryInfo(ta);
                NormalGenerator gen = new NormalGenerator();
                gen.generateNormals(info);
                s.setGeometry(info.getIndexedGeometryArray());

                Appearance app = new Appearance();
                Material mat = new Material( new Color3f((float)0.0,(float)0.0,(float)0.0), // Ambient 
                                             new Color3f((float)1.0,(float)1.0,(float)1.0), // Emissive
                                             new Color3f((float)0.5,(float)0.5,(float)0.5), // Diffuse
                                             new Color3f((float)1.0,(float)1.0,(float)1.0), // Specular
                                             (float)10.0 // Shiny
                                            );                        // );
                app.setMaterial(mat);
                s.setAppearance(app);
		objTrans.addChild(s);

		BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);

		if (spin) {
		  Transform3D yAxis = new Transform3D();
		  Alpha rotationAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE,
						  0, 0,
						  4000, 0, 0,
						  0, 0, 0);

		  RotationInterpolator rotator =
			  new RotationInterpolator(rotationAlpha, objTrans, yAxis,
						   0.0f, (float) Math.PI*2.0f);
		  rotator.setSchedulingBounds(bounds);
		  objTrans.addChild(rotator);
		} 

        // Set up the background
        Color3f bgColor = new Color3f(0.05f, 0.05f, 0.5f);
        Background bgNode = new Background(bgColor);
        bgNode.setApplicationBounds(bounds);
        objRoot.addChild(bgNode);

	return objRoot;
    }
    
    private Canvas3D createUniverse() {
	// Get the preferred graphics configuration for the default screen
		GraphicsConfiguration config =
			SimpleUniverse.getPreferredConfiguration();

		// Create a Canvas3D using the preferred configuration
		Canvas3D canvas3d = new Canvas3D(config);

		// Create simple universe with view branch
		univ = new SimpleUniverse(canvas3d);
		BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);

		// add mouse behaviors to the ViewingPlatform
		ViewingPlatform viewingPlatform = univ.getViewingPlatform();

		PlatformGeometry pg = new PlatformGeometry();

		// Set up the ambient light
		Color3f ambientColor = new Color3f(0.1f, 0.1f, 0.1f);
		AmbientLight ambientLightNode = new AmbientLight(ambientColor);
		ambientLightNode.setInfluencingBounds(bounds);
		pg.addChild(ambientLightNode);

		// Set up the directional lights
		Color3f light1Color = new Color3f(1.0f, 1.0f, 0.9f);
		Vector3f light1Direction  = new Vector3f(1.0f, 1.0f, 1.0f);
		Color3f light2Color = new Color3f(1.0f, 1.0f, 1.0f);
		Vector3f light2Direction  = new Vector3f(-1.0f, -1.0f, -1.0f);

		DirectionalLight light1
			= new DirectionalLight(light1Color, light1Direction);
		light1.setInfluencingBounds(bounds);
		pg.addChild(light1);

		DirectionalLight light2
			= new DirectionalLight(light2Color, light2Direction);
		light2.setInfluencingBounds(bounds);
		pg.addChild(light2);

		viewingPlatform.setPlatformGeometry( pg );
		  
		// This will move the ViewPlatform back a bit so the
		// objects in the scene can be viewed.
		viewingPlatform.setNominalViewingTransform();

		if (!spin) {
				OrbitBehavior orbit = new OrbitBehavior(canvas3d,
								OrbitBehavior.REVERSE_ALL);
				orbit.setSchedulingBounds(bounds);
				viewingPlatform.setViewPlatformBehavior(orbit);	    
		}        
			
			// Ensure at least 5 msec per frame (i.e., < 200Hz)
		univ.getViewer().getView().setMinimumFrameCycleTime(5);

		return canvas3d;
    }

    /**
     * Creates new form 
     */
    public SculptPreview(String args[]) {
        // Initialize the GUI components
	initComponents();

	// Create Canvas3D and SimpleUniverse; add canvas to drawing panel
	Canvas3D c = createUniverse();
	drawingPanel.add(c, java.awt.BorderLayout.CENTER);

	// Create the content branch and add it to the universe
	scene = createSceneGraph();
	univ.addBranchGraph(scene);
    }

    // ----------------------------------------------------------------
    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
    private void initComponents() {
        try  {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            System.out.println("Error setting UI!");
        }

        drawingPanel = new javax.swing.JPanel();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("SculptPreview");
        drawingPanel.setLayout(new java.awt.BorderLayout());

        drawingPanel.setPreferredSize(new java.awt.Dimension(700, 700));
        getContentPane().add(drawingPanel, java.awt.BorderLayout.CENTER);

        pack();
    }// </editor-fold>//GEN-END:initComponents
    
    /**
     * @param args the command line arguments
     */
    public static void main(final String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                SculptPreview sp = new SculptPreview(args);
                sp.setVisible(true);
            }
        });
    }
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JPanel drawingPanel;
    // End of variables declaration//GEN-END:variables
    
}