import com.jme.animation.SpatialTransformer;

import com.jme.bounding.BoundingSphere;
import com.jme.app.SimpleGame;
import com.jme.scene.Node;
import com.jme.scene.TriMesh;
import com.jme.scene.TriMesh.Mode;
import com.jme.scene.Line;
import com.jme.scene.Point;
import com.jme.scene.shape.Quad;
import com.jme.scene.Text;
import com.jme.scene.TexCoords;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.CullState;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.MaterialState.ColorMaterial;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.BlendState;
import com.jme.scene.Spatial;
import com.jme.scene.SharedNode;
import com.jme.scene.BillboardNode;
import com.jme.scene.lod.DiscreteLodNode;
import com.jme.scene.DistanceSwitchModel;

import com.jme.system.DisplaySystem;

import com.jme.renderer.Renderer;
import com.jme.renderer.ColorRGBA;

import com.jme.light.DirectionalLight;

import com.jme.image.Image;
import com.jme.image.Texture;
import com.jme.image.Texture2D;
import com.jme.image.Texture.MinificationFilter;
import com.jme.image.Texture.MagnificationFilter;

import com.jme.util.TextureManager;

import java.net.MalformedURLException;
import java.net.URL;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.RenderingHints;
import java.awt.Toolkit;

import com.jmex.font3d.Text3D;
import com.jmex.font3d.Font3D;

import com.jme.util.geom.NormalGenerator;
import com.jme.util.geom.BufferUtils;

import com.jme.math.Vector3f;
import com.jme.math.Matrix4f;
import com.jme.math.TransformMatrix;
import com.jme.math.Quaternion;
 
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import com.jme.scene.state.BlendState.TestFunction;

// draws a X3DV file with IndexedTriangleSet and TriangleSet nodes

class AppearanceToJme {
    static X3dAppearance getAppearance(X3dNode node) {
        if (((X3dShape)node.m_parent) == null)
            return null;
         return (X3dAppearance)((X3dShape)node.m_parent).appearance;
    }
}

class MaterialToJme {
   static X3dMaterial getMaterial(X3dNode node) {
      X3dAppearance appearance = AppearanceToJme.getAppearance(node);
      if (appearance != null) {
          X3dMaterial material = (X3dMaterial)((X3dAppearance)appearance).material;
          return material;
      }
      return null;
   }

   static void setMaterial(MaterialState materialState, X3dNode node) {
      X3dMaterial material = getMaterial(node);
      if (material == null)
          return;
      materialState.setAmbient(new ColorRGBA(material.diffuseColor[0],
                                             material.diffuseColor[1],  
                                             material.diffuseColor[2],  
                                             1 - material.transparency)); 

      materialState.setDiffuse(new ColorRGBA(material.diffuseColor[0],
                                             material.diffuseColor[1],
                                             material.diffuseColor[2],
                                             1 - material.transparency));

      materialState.setEmissive(new ColorRGBA(material.emissiveColor[0],
                                              material.emissiveColor[1],
                                              material.emissiveColor[2],
                                              1 - material.transparency));

      materialState.setSpecular(new ColorRGBA(material.specularColor[0],
                                              material.specularColor[1],
                                              material.specularColor[2],
                                              1 - material.transparency));

      materialState.setShininess(material.shininess * 128.0f);

      materialState.setEnabled(true);
   }

   static boolean isTransparentMaterial(X3dNode node) {
      X3dMaterial material = getMaterial(node);
      if (material != null)
          return material.transparency > 0.0f;
       return true;
   }

   static MaterialState makeMaterial(X3dNode node) {
      MaterialState materialState = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
      setMaterial(materialState, node);
      return materialState;
   }
}

class TextureToJme {
   static TextureState makeTexture(X3dNode node) {
      X3dAppearance appearance = AppearanceToJme.getAppearance(node);
      if (appearance != null) {
          if ((appearance.texture != null) && 
              (appearance.texture.getClass().getName().equals("X3dImageTexture"))
             ) {
              float scale[] = new float[2];
              scale[0] = 1.0f;
              scale[1] = 1.0f;
              float center[] = new float[2];
              center[0] = 1.0f;
              center[1] = 1.0f;
              float rotation = 0.0f;
              float translation[] = new float[2];
              translation[0] = 1.0f;
              translation[1] = 1.0f;
              if ((appearance.textureTransform != null) && 
                  (appearance.textureTransform.getClass().getName().equals(
                   "X3dTextureTransform"))) {
                   X3dTextureTransform textureTransformNode = (X3dTextureTransform)appearance.textureTransform;
                   scale[0] = textureTransformNode.scale[0]; 
                   scale[1] = textureTransformNode.scale[1]; 
                   center[0] = textureTransformNode.center[0]; 
                   center[1] = textureTransformNode.center[1]; 
                   rotation = textureTransformNode.rotation; 
                   translation[0] = textureTransformNode.translation[0]; 
                   translation[1] = textureTransformNode.translation[1]; 
              }
              X3dImageTexture textureNode = (X3dImageTexture)appearance.texture;
              if (textureNode.url.length > 0)
                  for (int i = 0; i < textureNode.url.length; i++) {
                      Texture texture = TextureManager.loadTexture(textureNode.url[i], 
                           Texture.MinificationFilter.NearestNeighborNoMipMaps,
                           Texture.MagnificationFilter.NearestNeighbor,
                           1.0f,
                           true);
                      if (texture == null)
                          continue;
                      Matrix4f transformMatrix = new Matrix4f();
                      transformMatrix.setIdentity();
                      Matrix4f multMatrix = new Matrix4f();

                      multMatrix.setIdentity();
                      multMatrix.setTranslation(-center[0], -center[1], 0.0f);
                      transformMatrix = transformMatrix.mult(multMatrix);

                      multMatrix.setIdentity();
                      multMatrix.scale(new Vector3f(scale[0], scale[1], 0));
                      transformMatrix = transformMatrix.mult(multMatrix);
 
                      multMatrix.setIdentity();
                      multMatrix.fromAngleAxis(rotation, 
                                               new Vector3f(0.0f, 0.0f, 1.0f));
                      transformMatrix = transformMatrix.mult(multMatrix);

                      multMatrix.setIdentity();
                      multMatrix.setTranslation(center[0], center[1], 0.0f);
                      transformMatrix = transformMatrix.mult(multMatrix);

                      multMatrix.setIdentity();
                      multMatrix.setTranslation(translation[0], translation[1], 0.0f);
                      transformMatrix = transformMatrix.mult(multMatrix);

                      texture.setMatrix(transformMatrix);
                      texture.setWrap(Texture.WrapAxis.S, textureNode.repeatS ? Texture.WrapMode.Repeat : Texture.WrapMode.Clamp);
                      texture.setWrap(Texture.WrapAxis.T, textureNode.repeatT ? Texture.WrapMode.Repeat : Texture.WrapMode.Clamp);
                      TextureState textureState = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
                      textureState.setTexture(texture, 0);
                      return textureState;
                  }
          }          
      }
      return null;
   }

   static boolean isTransparentTexture(X3dNode node) {
      X3dAppearance appearance = AppearanceToJme.getAppearance(node);
      if (appearance != null) {
          if (appearance.texture != null) {
              X3dImageTexture textureNode = (X3dImageTexture)appearance.texture;
              if (textureNode.url.length > 0)
                  for (int i = 0; i < textureNode.url.length; i++) {
                      URL textureUrl = null;
                      try {
                          String prefix = "";
                          if (textureNode.url[i].charAt(0) == '/')
                              prefix = "//"; 
                          textureUrl = new URL("file", "", prefix + textureNode.url[i]);
                      } catch (MalformedURLException ex) {
                          if (i == textureNode.url.length - 1)
                              return false;
                          else
                              continue;
                      }
                      java.awt.Image img = Toolkit.getDefaultToolkit().getImage(textureUrl);
                      return TextureManager.hasAlpha(img);
                  }
              }
          }
          return false;
      }
}

class AlphaToJme {
    static BlendState makeAlpha() {
        BlendState blendState = DisplaySystem.getDisplaySystem().getRenderer().createBlendState();
        blendState.setTestEnabled(true);
	blendState.setBlendEnabled(true);
	blendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
	blendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusDestinationAlpha);
	blendState.setTestFunction(BlendState.TestFunction.GreaterThan);

        return blendState;
    }
}

class ColorToJme {
   static FloatBuffer makeColor(X3dNode node, int needed_length) {

      int numDefinedColors = 0;
      float r = 0.8f;
      float b = 0.8f;
      float g = 0.8f;
      float a = 1;
      float color[] = new float[needed_length * 4];
      if (node != null) {
         if (node.getClass().getName().equals("X3dColor")) {
            X3dColor colors = (X3dColor) node;
            int colorLength = colors.color.length / 3 > needed_length ? 
                              needed_length : colors.color.length / 3;
            if (colors.color.length == 3) {
                r = colors.color[0];       
                b = colors.color[1];       
                g = colors.color[2];       
            } else {      
               for (int i = 0; i < colorLength; i++) {
                  for (int j = 0; j < 3; j++)
                     color[i * 4 + j] = colors.color[i * 3 + j];
                  color[i * 4 + 3] = 1;
                  numDefinedColors++;
               }
            }           
         } else if (node.getClass().getName().equals("X3dColorRGBA")) {
             X3dColorRGBA colors = (X3dColorRGBA) node;
             for (int i = 0; i < colors.color.length; i++) {
                color[i] = colors.color[i];
                numDefinedColors++;
             }
         }
      }
      for (int i = 0; i < needed_length; i++)
         color[i] = 0.8f;
      FloatBuffer colorsBuffer = BufferUtils.createFloatBuffer(color.length);
      colorsBuffer.put(color); 
      return colorsBuffer;
   }
}

class MyTriangleSetCallback extends X3dTriangleSetDoWithDataCallback {

   public void doWithData(X3dNode node) {

      X3dTriangleSet triangles = (X3dTriangleSet) node; 

      float vertices[] = ((X3dCoordinate)triangles.coord).point;
      FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
      verticesBuffer.put(vertices);

      FloatBuffer normalsBuffer = null;
      if (((X3dNormal)triangles.normal) != null) {
         float normals[] = ((X3dNormal)triangles.normal).vector;
         if (normals != null) {
             normalsBuffer = BufferUtils.createFloatBuffer(normals.length);
             normalsBuffer.put(normals);
         }
      }

      FloatBuffer colorsBuffer = ColorToJme.makeColor(triangles.color, vertices.length / 3);

      FloatBuffer texCoordsBuffer = null;
      if (triangles.texCoord != null) {
         float texCoords[] = ((X3dTextureCoordinate)triangles.texCoord).point;

         if (texCoords != null) {
            texCoordsBuffer = BufferUtils.createFloatBuffer(texCoords.length);
            texCoordsBuffer.put(texCoords);
         }
      }
      int indices[] = new int[vertices.length / 3];
      for (int i = 0; i < indices.length; i++)
          indices[i] = i;
      IntBuffer indicesBuffer = BufferUtils.createIntBuffer(indices.length);
      indicesBuffer.put(indices);

      TriMesh tri = new TriMesh("TriangleSet", verticesBuffer, 
                                               normalsBuffer,
                                               colorsBuffer, 
                                               texCoordsBuffer == null ? null :
                                               new TexCoords(texCoordsBuffer),
                                               indicesBuffer);

      TextureState texture = TextureToJme.makeTexture(triangles);
      if (texture != null)
          tri.setRenderState(texture);
      if (TextureToJme.isTransparentTexture(triangles) || 
          MaterialToJme.isTransparentMaterial(triangles)) {
          tri.setRenderState(AlphaToJme.makeAlpha());
          tri.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
      }

      if (triangles.color != null)
         tri.setLightCombineMode(Spatial.LightCombineMode.Off);
      MaterialState materialState = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
      CullState cullState = DisplaySystem.getDisplaySystem().getRenderer().createCullState();
      materialState.setMaterialFace(MaterialState.MaterialFace.FrontAndBack);
      if (triangles.solid)
          if (triangles.ccw)
              cullState.setCullFace(CullState.Face.Back);
          else
              cullState.setCullFace(CullState.Face.Front);         
      if (texture == null)
          MaterialToJme.setMaterial(materialState, triangles);
      tri.clearRenderState(RenderState.StateType.Material);
      tri.setRenderState(materialState);
      tri.setRenderState(cullState);

      node.m_data = tri;             
   }
}

class MyIndexedTriangleSetCallback extends X3dIndexedTriangleSetDoWithDataCallback {

   public void doWithData(X3dNode node) {

      X3dIndexedTriangleSet triangles = (X3dIndexedTriangleSet) node; 

      float vertices[] = ((X3dCoordinate)triangles.coord).point;
      FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
      verticesBuffer.put(vertices);

      FloatBuffer normalsBuffer = null;
      if (((X3dNormal)triangles.normal) != null) {
         float normals[] = ((X3dNormal)triangles.normal).vector;
         if (normals != null) {
             normalsBuffer = BufferUtils.createFloatBuffer(normals.length);
             normalsBuffer.put(normals);
         }
      }

      FloatBuffer colorsBuffer = ColorToJme.makeColor(triangles.color, vertices.length / 3);

      FloatBuffer texCoordsBuffer = null;
      if (triangles.texCoord != null) {
         float texCoords[] = ((X3dTextureCoordinate)triangles.texCoord).point;

         if (texCoords != null) {
            texCoordsBuffer = BufferUtils.createFloatBuffer(texCoords.length);
            texCoordsBuffer.put(texCoords);
         }
      }
      int indices[] = triangles.index;
      IntBuffer indicesBuffer = BufferUtils.createIntBuffer(indices.length);
      indicesBuffer.put(indices);

      TriMesh tri = new TriMesh("IndexedTriangleSet", 
                                verticesBuffer, 
                                normalsBuffer,
                                colorsBuffer, 
                                texCoordsBuffer == null ? null :
                                new TexCoords(texCoordsBuffer),
                                indicesBuffer);
      TextureState texture = TextureToJme.makeTexture(triangles);
      if (texture != null)
          tri.setRenderState(texture);
      if (TextureToJme.isTransparentTexture(triangles) || 
          MaterialToJme.isTransparentMaterial(triangles)) {
          tri.setRenderState(AlphaToJme.makeAlpha());
          tri.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
      }
      if (triangles.color != null)
         tri.setLightCombineMode(Spatial.LightCombineMode.Off);
      MaterialState materialState = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
      CullState cullState = DisplaySystem.getDisplaySystem().getRenderer().createCullState();
      materialState.setMaterialFace(MaterialState.MaterialFace.FrontAndBack);
      if (triangles.solid)
          if (triangles.ccw)
              cullState.setCullFace(CullState.Face.Back);
          else
              cullState.setCullFace(CullState.Face.Front);         
      MaterialToJme.setMaterial(materialState, triangles);
      tri.clearRenderState(RenderState.StateType.Material);
      tri.setRenderState(materialState);
      tri.setRenderState(cullState);

      node.m_data = tri;             
   }
}

class MyIndexedLineSetCallback extends X3dIndexedLineSetDoWithDataCallback {

   public void doWithData(X3dNode node) {

      X3dIndexedLineSet lines = (X3dIndexedLineSet) node; 

      if (lines.coord == null)
          return;

      int indices[] = lines.coordIndex;
      float vertices[] = ((X3dCoordinate)lines.coord).point;
      float growingVertices[] = new float[indices.length * 2 * 3];
      int growingVerticesLen = 0;
      for (int i = 1; i < indices.length; i++) {
          if ((indices[i] > -1) && (indices[i - 1] > -1))  {
             growingVertices[growingVerticesLen++] = vertices[3 * indices[i - 1]];
             growingVertices[growingVerticesLen++] = vertices[3 * indices[i - 1] + 1];
             growingVertices[growingVerticesLen++] = vertices[3 * indices[i - 1] + 2];
             growingVertices[growingVerticesLen++] = vertices[3 * indices[i]];
             growingVertices[growingVerticesLen++] = vertices[3 * indices[i] + 1];
             growingVertices[growingVerticesLen++] = vertices[3 * indices[i] + 2];
          } 
      }
      float lineVertices[] = new float[growingVerticesLen];
      for (int i = 0; i < growingVerticesLen; i++)
         lineVertices[i] = growingVertices[i];      
      FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(lineVertices.length);
      verticesBuffer.put(lineVertices);

      FloatBuffer colorsBuffer = ColorToJme.makeColor(lines.color, lineVertices.length);

      FloatBuffer normalsBuffer = null;
      TexCoords texCoords = new TexCoords(null);
      Line line = new Line("IndexedLineSet", verticesBuffer, 
                                             normalsBuffer,
                                             colorsBuffer, 
                                             texCoords);
      line.setRenderState(MaterialToJme.makeMaterial(lines));
      TextureState texture = TextureToJme.makeTexture(lines);
      if (texture != null)
          line.setRenderState(texture);
      line.setRenderState(AlphaToJme.makeAlpha());
      if (lines.color != null)
         line.setLightCombineMode(Spatial.LightCombineMode.Off);
       
      node.m_data = line;             
   }
}

class MyPointSetCallback extends X3dPointSetDoWithDataCallback {

   public void doWithData(X3dNode node) {

      X3dPointSet points = (X3dPointSet) node; 

      if (points.coord == null)
          return;

      float vertices[] = ((X3dCoordinate)points.coord).point;
      FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
      verticesBuffer.put(vertices);

      FloatBuffer colorsBuffer = ColorToJme.makeColor(points.color, vertices.length);

      FloatBuffer normalsBuffer = null;
      TexCoords texCoords = new TexCoords(null);
      Point point = new Point("PointSet", verticesBuffer, 
                                             normalsBuffer,
                                             colorsBuffer, 
                                             texCoords);
      point.setRenderState(MaterialToJme.makeMaterial(points));
      TextureState texture = TextureToJme.makeTexture(points);
      if (texture != null)
          point.setRenderState(texture);
      point.setRenderState(AlphaToJme.makeAlpha());
      if (points.color != null)
         point.setLightCombineMode(Spatial.LightCombineMode.Off);
       
      node.m_data = point;             
   }
}

class MyTextCallback extends X3dTextDoWithDataCallback {

   final int textRes = 256;
   final int textScale = textRes - 1;
   final float scaleFactor = 0.45f;

   Spatial makeText(String strings[], Font font, Color color) {

       Quad txt = null;
       int len = 0;
       for (int i = 0; i < strings.length; i++)
           if (strings[i].length() > len)
               len = strings[i].length();
       if (len == 0)
           return null;
       BufferedImage img = new BufferedImage(textRes * len, 
                                             textRes * strings.length, 
                                             BufferedImage.TYPE_INT_ARGB);
       Graphics2D g2d = (Graphics2D) img.getGraphics();
       g2d.setFont(font);
       g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                            RenderingHints.VALUE_ANTIALIAS_ON);
       g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 
                            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    
       for (int i = 0; i < strings.length; i++) {

           g2d.setColor(color);
           g2d.drawString(strings[i], 0, 
                          textRes * (i + scaleFactor + (1 - scaleFactor) / 2));
       }

       txt = new Quad("Text", len * 2, strings.length * 2);
       txt.setLocalTranslation(len, -strings.length + 1.5f, 0);

       Texture tex = TextureManager.loadTexture(img, 
                           MinificationFilter.BilinearNoMipMaps, 
                           MagnificationFilter.Bilinear, true);

       TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
       ts.setEnabled(true);
       ts.setTexture(tex);
       txt.setRenderState(ts);
   
       txt.setRenderState(AlphaToJme.makeAlpha());
       return txt;
   }

   public void doWithData(X3dNode node) {

      X3dText text = (X3dText) node; 

      if ((text.string != null) && (text.string.length > 0)) {

          X3dFontStyle fontStyle = (X3dFontStyle)text.fontStyle;
          int size = 1;
          String family = "Serif";
          int style = Font.PLAIN;
    
          Font font = null;
          if (fontStyle != null) {
              size = fontStyle.size < 1 ? 1 : (int)fontStyle.size;
              size = size * (int)((float)textScale * scaleFactor);
              if (fontStyle.style != null) {
                  if (fontStyle.style.equals("PLAIN"))
                      style = Font.PLAIN;
                  else if (fontStyle.style.equals("BOLD"))
                      style = Font.BOLD;
                  else if (fontStyle.style.equals("ITALIC"))
                      style = Font.ITALIC;
                  else if (fontStyle.style.equals("BOLDITALIC"))
                      style = Font.ITALIC | Font.BOLD;
                  else
                      style = Font.PLAIN;
              }
              if (fontStyle.family != null) {
                  for (int i = 0; i < fontStyle.family.length; i++) {
                      if (fontStyle.family[i].equals("SERIF")) {
                          family = "Serif";
                      } else if (fontStyle.family[i].equals("SANS")) {
                          family = "SansSerif";
                      } else if (fontStyle.family[i].equals("TYPEWRITER")) {
                          family = "Monospaced";
                      } else {
                          font = new Font(fontStyle.family[i], style, size);
                          if (font.getFamily().equals(fontStyle.family[i]))
                              break;
                          else
                              continue;
                      }
                      font = new Font(family, style, size);
                      break;
                  }
              } else
                  font = new Font(family, style, size);              
          } else
              font = new Font(family, style, size);              

      if (((X3dShape)node.m_parent) == null)
          return;

      Color color = new Color(0.8f, 0.8f, 0.8f);
      X3dAppearance appearance = (X3dAppearance)((X3dShape)node.m_parent).appearance;
      if (appearance != null) {
          X3dMaterial material = (X3dMaterial)((X3dAppearance)appearance).material;

          if (material != null)
              color = new Color(material.diffuseColor[0],
                                material.diffuseColor[1],
                                material.diffuseColor[2]);
     }

          node.m_data = makeText(text.string, font, color);
      }
   }
}

class MyShapeCallback extends X3dShapeDoWithDataCallback {

   public void doWithData(X3dNode node) {
      X3dShape shape = (X3dShape) node;
      Node jmeShape = new Node("Shape");
      if (shape.geometry != null)
         if (shape.geometry.m_data != null)
            jmeShape.attachChild(((Spatial)shape.geometry.m_data));
      node.m_data = jmeShape;       
   }
}

class MyTransformCallback extends X3dTransformDoWithDataCallback {

   public void doWithData(X3dNode node) {
      X3dTransform transform = (X3dTransform) node;

      Node jmeTransform = new Node("Transform");
      TransformMatrix matrix = new TransformMatrix();
      TransformMatrix multMatrix = new TransformMatrix();
      Vector3f tempStore = new Vector3f();
      Quaternion quat = new Quaternion();

      multMatrix.setTranslation(transform.translation[0], 
                                transform.translation[1],
                                transform.translation[2]);
      matrix.multLocal(multMatrix, tempStore);

      multMatrix.loadIdentity();
      multMatrix.setTranslation(transform.center[0], 
                                transform.center[1],
                                transform.center[2]);
      matrix.multLocal(multMatrix, tempStore);

      multMatrix.loadIdentity();
      quat.fromAngleAxis(transform.rotation[3], 
                         new Vector3f(transform.rotation[0],
                                      transform.rotation[1],
                                      transform.rotation[2]));
      multMatrix.setRotationQuaternion(quat);
      matrix.multLocal(multMatrix, tempStore);

      // scaleOrientation (shearing ?) not supported by TransformMatrix ?
      multMatrix.loadIdentity();
      quat.loadIdentity(); 
      quat.fromAngleAxis(transform.scaleOrientation[3], 
                         new Vector3f(transform.scaleOrientation[0],
                                      transform.scaleOrientation[1],
                                      transform.scaleOrientation[2]));
      multMatrix.setRotationQuaternion(quat);
      matrix.multLocal(multMatrix, tempStore);

      multMatrix.loadIdentity();
      multMatrix.setScale(new Vector3f(transform.scale[0], 
                                       transform.scale[1],
                                       transform.scale[2]));
      matrix.multLocal(multMatrix, tempStore);

      // scaleOrientation (shearing ?) not supported by TransformMatrix ?
      multMatrix.loadIdentity();
      quat.loadIdentity(); 
      quat.fromAngleAxis(-transform.scaleOrientation[3], 
                         new Vector3f(transform.scaleOrientation[0],
                                      transform.scaleOrientation[1],
                                      transform.scaleOrientation[2]));
      multMatrix.setRotationQuaternion(quat);
      matrix.multLocal(multMatrix, tempStore);

      multMatrix.loadIdentity();
      multMatrix.setTranslation(-transform.center[0], 
                                -transform.center[1],
                                -transform.center[2]);
      matrix.multLocal(multMatrix, tempStore);

      matrix.applyToSpatial(jmeTransform);
      if (transform.children != null) {
         for (int i = 0; i < transform.children.length; i++) {
            if (transform.children[i] == null)
               continue;
            if (transform.children[i].m_data == null)
               continue;
            jmeTransform.attachChild(((Node)transform.children[i].m_data));
         }
      }
      node.m_data = jmeTransform;       
   }
}

class MyGroupCallback extends X3dGroupDoWithDataCallback {

   public void doWithData(X3dNode node) {
      X3dGroup group = (X3dGroup)node;
      Node jmeGroup = new Node("Group");
      if (node.m_parent != null) // not root node
         node.m_data = jmeGroup;       
      if (group.children != null) {
         for (int i = 0; i < group.children.length; i++) {
            if (group.children[i] == null)
               continue;
            if (group.children[i].m_data == null)
               continue;
            ((Node)node.m_data).attachChild(((Node)group.children[i].m_data));
         }
      }
   }
}

class MyAnchorCallback extends X3dAnchorDoWithDataCallback {

   public void doWithData(X3dNode node) {
      X3dAnchor anchor = (X3dAnchor)node;
      Node jmeAnchor = new Node("Anchor");
      if (anchor.children != null) {
         for (int i = 0; i < anchor.children.length; i++) {
            if (anchor.children[i] == null)
               continue;
            if (anchor.children[i].m_data == null)
               continue;
            jmeAnchor.attachChild(((Node)anchor.children[i].m_data));
         }
      }
      node.m_data = jmeAnchor;       
   }
}

class MySwitchCallback extends X3dSwitchDoWithDataCallback {

   public void doWithData(X3dNode node) {
      X3dSwitch swith = (X3dSwitch)node;
      if ((swith.whichChoice < 0) || 
          (swith.whichChoice >= swith.children.length))
         return;
      Node jmeSwitch = new Node("Switch");
      if ((swith.children[swith.whichChoice] != null) && 
          (swith.children[swith.whichChoice].m_data != null))
         jmeSwitch.attachChild(((Node)swith.children[swith.whichChoice].m_data));
      node.m_data = jmeSwitch;       
   }
}

class MyCollisionCallback extends X3dCollisionDoWithDataCallback {

   public void doWithData(X3dNode node) {
      X3dCollision collision = (X3dCollision)node;
      Node jmeCollision = new Node("Collision");
      if (collision.children != null) {
         for (int i = 0; i < collision.children.length; i++) {
            if (collision.children[i] == null)
               continue;
            if (collision.children[i].m_data == null)
               continue;
            jmeCollision.attachChild(((Node)collision.children[i].m_data));
         }
      }
      node.m_data = jmeCollision;       
   }
}

class MyBillboardCallback extends X3dBillboardDoWithDataCallback {

   public void doWithData(X3dNode node) {
      X3dBillboard billboard = (X3dBillboard)node;
      boolean valid = false;
      int axis = -1;
      if (billboard.axisOfRotation[0] == 0)
         if (billboard.axisOfRotation[1] == 0)
            if (billboard.axisOfRotation[2] == 0) {
                valid = true;
                axis = BillboardNode.CAMERA_ALIGNED;
            } else {
                valid = true;
                axis = BillboardNode.AXIAL_Z;
            }
         else   
            if (billboard.axisOfRotation[2] == 0) {
                valid = true;
                axis = BillboardNode.AXIAL_Y;
            }
            
       BillboardNode jmeBillboard = new BillboardNode(valid ? "Billboard" : 
                                                      "unsupported Billboard");
      if (valid)
          jmeBillboard.setAlignment(axis);
      node.m_data = jmeBillboard;       
      if (billboard.children != null) {
         for (int i = 0; i < billboard.children.length; i++) {
            if (billboard.children[i] == null)
               continue;
            if (billboard.children[i].m_data == null)
               continue;
            jmeBillboard.attachChild(((Node)billboard.children[i].m_data));
         }
      }
   }
}

class MyLODCallback extends X3dLODDoWithDataCallback {

   public void doWithData(X3dNode node) {
      X3dLOD lod = (X3dLOD)node;
      DistanceSwitchModel switchModel = new DistanceSwitchModel();            
      DiscreteLodNode jmeLOD = new DiscreteLodNode("LOD", switchModel);
      node.m_data = jmeLOD;       
      if (lod.children != null) {
         for (int i = 0; i < lod.children.length; i++) {
            if (lod.children[i] == null)
               continue;
            if (lod.children[i].m_data == null)
               continue;
            jmeLOD.attachChild(((Node)lod.children[i].m_data));
            if (i < lod.children.length) {
                float minRange = 0;
                if (i > 0)
                    minRange = lod.range[i - 1];
                float maxRange = java.lang.Float.MAX_VALUE;
                if (i < lod.range.length)
                    maxRange = lod.range[i];
                switchModel.setModelDistance(i, minRange, maxRange);
            }
         }
      }
   }
}

class MyCADPartCallback extends X3dCADPartDoWithDataCallback {

   public void doWithData(X3dNode node) {
      X3dCADPart cadPart = (X3dCADPart)node;
      Node jmeCADPart = new Node("CADPart");

      TransformMatrix matrix = new TransformMatrix();
      TransformMatrix multMatrix = new TransformMatrix();
      Vector3f tempStore = new Vector3f();
      Quaternion quat = new Quaternion();

      multMatrix.setTranslation(cadPart.translation[0], 
                                cadPart.translation[1],
                                cadPart.translation[2]);
      matrix.multLocal(multMatrix, tempStore);

      multMatrix.loadIdentity();
      multMatrix.setTranslation(cadPart.center[0], 
                                cadPart.center[1],
                                cadPart.center[2]);
      matrix.multLocal(multMatrix, tempStore);

      multMatrix.loadIdentity();
      quat.fromAngleAxis(cadPart.rotation[3], 
                         new Vector3f(cadPart.rotation[0],
                                      cadPart.rotation[1],
                                      cadPart.rotation[2]));
      multMatrix.setRotationQuaternion(quat);
      matrix.multLocal(multMatrix, tempStore);

      // scaleOrientation (shearing ?) not supported by TransformMatrix ?
      multMatrix.loadIdentity();
      quat.loadIdentity(); 
      quat.fromAngleAxis(cadPart.scaleOrientation[3], 
                         new Vector3f(cadPart.scaleOrientation[0],
                                      cadPart.scaleOrientation[1],
                                      cadPart.scaleOrientation[2]));
      multMatrix.setRotationQuaternion(quat);
      matrix.multLocal(multMatrix, tempStore);

      multMatrix.loadIdentity();
      multMatrix.setScale(new Vector3f(cadPart.scale[0], 
                                       cadPart.scale[1],
                                       cadPart.scale[2]));
      matrix.multLocal(multMatrix, tempStore);

      // scaleOrientation (shearing ?) not supported by TransformMatrix ?
      multMatrix.loadIdentity();
      quat.loadIdentity(); 
      quat.fromAngleAxis(-cadPart.scaleOrientation[3], 
                         new Vector3f(cadPart.scaleOrientation[0],
                                      cadPart.scaleOrientation[1],
                                      cadPart.scaleOrientation[2]));
      multMatrix.setRotationQuaternion(quat);
      matrix.multLocal(multMatrix, tempStore);

      multMatrix.loadIdentity();
      multMatrix.setTranslation(-cadPart.center[0], 
                                -cadPart.center[1],
                                -cadPart.center[2]);
      matrix.multLocal(multMatrix, tempStore);

      matrix.applyToSpatial(jmeCADPart);
      if (cadPart.children != null) {
         for (int i = 0; i < cadPart.children.length; i++) {
            if (cadPart.children[i] == null)
               continue;
            if (cadPart.children[i].m_data == null)
               continue;
            jmeCADPart.attachChild(((Node)cadPart.children[i].m_data));
         }
      }
      node.m_data = jmeCADPart;       
   }
}

class MyCADAssemblyCallback extends X3dCADAssemblyDoWithDataCallback {

   public void doWithData(X3dNode node) {
      X3dCADAssembly cadAssembly = (X3dCADAssembly)node;
      Node jmeCADAssembly = new Node("CADAssembly");
      if (cadAssembly.children != null) {
         for (int i = 0; i < cadAssembly.children.length; i++) {
            if (cadAssembly.children[i] == null)
               continue;
            if (cadAssembly.children[i].m_data == null)
               continue;
            jmeCADAssembly.attachChild(((Node)cadAssembly.children[i].m_data));
         }
      }
      node.m_data = jmeCADAssembly;       
   }
}

class MyCADLayerCallback extends X3dCADLayerDoWithDataCallback {

   public void doWithData(X3dNode node) {
      X3dCADLayer cadLayer = (X3dCADLayer)node;
      Node jmeCADLayer = new Node("CADLayer");
      node.m_data = jmeCADLayer;       
      if (cadLayer.children == null)
          return;
      for (int i = 0; i < cadLayer.children.length; i++) {
         if (cadLayer.children[i] == null)
            continue;
         if (cadLayer.children[i].m_data == null)
            continue;
         if ((cadLayer.visible == null) || (i >= cadLayer.visible.length))
            ((Node)node.m_data).attachChild(((Node)cadLayer.children[i].m_data));
         else if (cadLayer.visible[i])
            ((Node)node.m_data).attachChild(((Node)cadLayer.children[i].m_data));
      }
   }
}

class MyCADFaceCallback extends X3dCADFaceDoWithDataCallback {

   public void doWithData(X3dNode node) {
      X3dCADFace cadFace = (X3dCADFace)node;
      Node jmeCADFace = new Node("CADFace");
      node.m_data = jmeCADFace;       
      ((Node)node.m_data).attachChild(((Node)cadFace.shape.m_data));
   }
}

class MyboxCallback extends X3dboxDoWithDataCallback {

   public void doWithData(X3dNode node) {
      X3dbox box = (X3dbox) node;
      Node jmeBox = new Node("box");
      if (box.m_protoRoot != null) {
         if (box.m_protoRoot.m_data != null)
             jmeBox.attachChild((Spatial)box.m_protoRoot.m_data);             
      }
      node.m_data = jmeBox;       
   }
}

public class main extends SimpleGame{

   public static void main(String args[]) {
      main app = new main();
//      app.setConfigShowMode(ConfigShowMode.AlwaysShow);
      app.start();
   }                                             
   protected static MyTriangleSetCallback myTriangleSet = new MyTriangleSetCallback();
   protected static MyIndexedTriangleSetCallback myIndexedTriangleSet = new MyIndexedTriangleSetCallback();
   protected static MyTextCallback myText = new MyTextCallback();
   protected static MyIndexedLineSetCallback myIndexedLineSet = new MyIndexedLineSetCallback();
   protected static MyPointSetCallback myPointSet = new MyPointSetCallback();
   protected static MyShapeCallback myShape = new MyShapeCallback();
   protected static MyTransformCallback myTransform = new MyTransformCallback();
   protected static MyGroupCallback myGroup = new MyGroupCallback();
   protected static MyAnchorCallback myAnchor = new MyAnchorCallback();
   protected static MySwitchCallback mySwitch = new MySwitchCallback();
   protected static MyCollisionCallback myCollision = new MyCollisionCallback();
   protected static MyBillboardCallback myBillboard = new MyBillboardCallback();
   protected static MyLODCallback myLOD = new MyLODCallback();
   protected static MyCADPartCallback myCADPart = new MyCADPartCallback();
   protected static MyCADAssemblyCallback myCADAssembly = new MyCADAssemblyCallback();
   protected static MyCADLayerCallback myCADLayer = new MyCADLayerCallback();
   protected static MyCADFaceCallback myCADFace = new MyCADFaceCallback();
   protected static MyboxCallback mybox = new MyboxCallback();

   protected static void setCallbacks() {
      X3dTriangleSet.setX3dTriangleSetDoWithDataCallback(myTriangleSet);
      X3dIndexedTriangleSet.setX3dIndexedTriangleSetDoWithDataCallback(myIndexedTriangleSet);
      X3dText.setX3dTextDoWithDataCallback(myText);
      X3dIndexedLineSet.setX3dIndexedLineSetDoWithDataCallback(myIndexedLineSet);
      X3dPointSet.setX3dPointSetDoWithDataCallback(myPointSet);
      X3dShape.setX3dShapeDoWithDataCallback(myShape);
      X3dTransform.setX3dTransformDoWithDataCallback(myTransform);
      X3dGroup.setX3dGroupDoWithDataCallback(myGroup);
      X3dAnchor.setX3dAnchorDoWithDataCallback(myAnchor);
      X3dSwitch.setX3dSwitchDoWithDataCallback(mySwitch);
      X3dCollision.setX3dCollisionDoWithDataCallback(myCollision);
      X3dBillboard.setX3dBillboardDoWithDataCallback(myBillboard);
      X3dLOD.setX3dLODDoWithDataCallback(myLOD);
      X3dCADPart.setX3dCADPartDoWithDataCallback(myCADPart);
      X3dCADAssembly.setX3dCADAssemblyDoWithDataCallback(myCADAssembly);
      X3dCADLayer.setX3dCADLayerDoWithDataCallback(myCADLayer);
      X3dCADFace.setX3dCADFaceDoWithDataCallback(myCADFace);
      X3dbox.setX3dboxDoWithDataCallback(mybox);
   }
                                                          
   protected void simpleInitGame(){              
      X3dSceneGraph sceneGraph = new X3dSceneGraph();
      setCallbacks();
// code for -manyclasses
      X3dSceneGraph.root.m_data = rootNode;
      X3dSceneGraph.doWithData();
// else
//      sceneGraph.root.m_data = rootNode;
//      sceneGraph.doWithData();

      SpatialTransformer st = new SpatialTransformer(1);
      st.setObject((Node)X3dSceneGraph.A.m_data, 0, -1);
      st.setPosition(0, 0, new Vector3f(0, 0, 0));
      st.setPosition(0, 20, new Vector3f(2, 0, 0));
      st.interpolateMissing();
      ((Node)X3dSceneGraph.A.m_data).addController(st);

      LightState lightState = DisplaySystem.getDisplaySystem().getRenderer().createLightState();

      lightState.detachAll();

      DirectionalLight light = new DirectionalLight();
      light.setDirection(new Vector3f(0.0f, 0.0f, -1.0f));
      light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      light.setAmbient(new ColorRGBA(0.1f, 0.1f, 0.1f, 1.0f));
      light.setConstant(100);
      light.setEnabled(true);
      lightState.attach(light);

      DirectionalLight light2 = new DirectionalLight();
      light2.setDirection(new Vector3f(0.0f, 0.0f, 1.0f));
      light2.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      light2.setAmbient(new ColorRGBA(0.1f, 0.1f, 0.1f, 1.0f));
      light2.setConstant(100);
      light2.setEnabled(true);
      lightState.attach(light2);

      rootNode.setRenderState(lightState);
   }
};
