/* (non-Javadoc)
   * @see org.apache.batik.parser.PathHandler#curvetoQuadraticSmoothRel(float, float)
   */
  public void curvetoQuadraticSmoothRel(float x, float y) throws ParseException {
    if (verbose) System.out.println("curvetoQuadraticSmoothRel: " + x + "," + y);

    Vertex lastPoint = pathPoints.getLast();
    if (lastPoint instanceof BezierVertex
        && cubicBezVertTOQuadricControlPoint.get(lastPoint) != null) {
      Vertex lastEndPoint =
          new Vertex(
              pathPoints.getLast().getX(),
              pathPoints.getLast().getY(),
              pathPoints.getLast().getZ());

      // Get control point of last QuadTo
      Vertex lastQuadControlPoint = cubicBezVertTOQuadricControlPoint.get(lastPoint);
      cubicBezVertTOQuadricControlPoint.remove(lastPoint);

      // Rotate that controlpoint around the end point of the last QuadTo
      lastQuadControlPoint.rotateZ(lastEndPoint, 180);

      // Put in startPoint = last QuadTo Endpoint of this smoothQuadTo, the calculated control
      // point, and the endpoint of smoothQuadTo
      BezierVertex b5 =
          Tools3D.getCubicFromQuadraticCurve(
              lastEndPoint,
              lastQuadControlPoint,
              new Vertex(lastPoint.getX() + x, lastPoint.getY() + y, 0));

      // Save last quad control point
      cubicBezVertTOQuadricControlPoint.put(b5, lastQuadControlPoint);
      pathPoints.add(b5);
      currentSubPath.add(b5);
    } else {
      if (verbose)
        System.out.println(
            "couldnt get last control point at curvetoQuadraticSmoothRel - using last point as the control point");

      Vertex lastEndPoint = new Vertex(lastPoint.getX(), lastPoint.getY(), 0);
      Vertex quadControlPoint = new Vertex(lastPoint.getX(), lastPoint.getY(), 0);

      BezierVertex b5 =
          Tools3D.getCubicFromQuadraticCurve(
              lastEndPoint,
              quadControlPoint,
              new Vertex(lastPoint.getX() + x, lastPoint.getY() + y, 0));

      cubicBezVertTOQuadricControlPoint.put(b5, quadControlPoint);
      pathPoints.add(b5);
      currentSubPath.add(b5);
    }
  }
  /* (non-Javadoc)
   * @see org.apache.batik.parser.PathHandler#arcAbs(float, float, float, boolean, boolean, float, float)
   */
  public void arcAbs(
      float rx, float ry, float phi, boolean large_arc, boolean sweep, float x, float y)
      throws ParseException {
    if (verbose)
      System.out.println(
          "arcAbs: "
              + rx
              + " "
              + ry
              + " "
              + phi
              + " "
              + large_arc
              + " "
              + sweep
              + " "
              + x
              + " "
              + y);

    Vertex lastPoint = pathPoints.getLast();
    List<Vertex> arcVertices =
        Tools3D.arcTo(lastPoint.x, lastPoint.y, rx, ry, phi, large_arc, sweep, x, y, 40);

    // Prevent odd picking behavour, in which the normal is
    // not correctly computed, because the 2 points are the same
    if (!arcVertices.isEmpty() && lastPoint != null && arcVertices.get(0).equalsVector(lastPoint)) {
      arcVertices.remove(0);
    }
    pathPoints.addAll(arcVertices);
    currentSubPath.addAll(arcVertices);
  }
    /*
     * (non-Javadoc)
     *
     * @see
     * org.mt4j.input.inputProcessors.IGestureEventListener#processGestureEvent
     * (org.mt4j.input.inputProcessors.MTGestureEvent)
     */
    public boolean processGestureEvent(MTGestureEvent ge) {
      if (ge instanceof TapEvent) {
        TapEvent te = (TapEvent) ge;
        if (te.getTapID() == TapEvent.TAPPED) {
          Vector3D w =
              Tools3D.project(app, app.getCurrentScene().getSceneCam(), te.getLocationOnScreen());
          Vector3D x = suggestionBox.globalToLocal(w);
          float zero = suggestionBox.getVerticesLocal()[0].y;
          float heightPerLine =
              suggestionBox.getHeightXY(TransformSpace.LOCAL)
                  / (float) (suggestionBox.getLineCount() + 1);
          int line = (int) ((x.y - zero) / heightPerLine);

          if (currentSuggestions.size() > line) {

            try {
              setText(currentSuggestions.get(line));
            } catch (NullPointerException ex) {
              setText("Error in MTSuggestionTextArea");
            }
          }
        }
      }
      return false;
    }
  /* (non-Javadoc)
   * @see org.apache.batik.parser.PathHandler#curvetoQuadraticRel(float, float, float, float)
   */
  public void curvetoQuadraticRel(float x1, float y1, float x, float y) throws ParseException {
    if (verbose) System.out.println("curvetoQuadraticRel: " + x1 + "," + y1 + "  " + x + "," + y);

    if (!pathPoints.isEmpty() && pathPoints.getLast() != null) {
      Vertex lastPoint = pathPoints.getLast();

      Vertex lastEndPoint = new Vertex(lastPoint.getX(), lastPoint.getY(), lastPoint.getZ());
      Vertex quadControlPoint = new Vertex(lastPoint.getX() + x1, lastPoint.getY() + y1, 0);

      // Put in startPoint = last QuadTo Endpoint of this smoothQuadTo, the calculated control
      // point, and the endpoint of smoothQuadTo
      BezierVertex b5 =
          Tools3D.getCubicFromQuadraticCurve(
              lastEndPoint,
              quadControlPoint,
              new Vertex(lastPoint.getX() + x, lastPoint.getY() + y, 0));

      cubicBezVertTOQuadricControlPoint.put(b5, quadControlPoint);
      pathPoints.add(b5);
      currentSubPath.add(b5);
    } else {
      System.out.println("last point null at curvetoQuadraticRel");
    }
  }
  /**
   * Assigns the texture.
   *
   * @param pathToModel
   * @param modelFile
   * @param scene
   * @param m
   * @param sceneMaterialID
   * @param mesh
   */
  private void assignMaterial(
      String pathToModel,
      File modelFile,
      Scene3ds scene,
      Mesh3ds m,
      int sceneMaterialID,
      MTTriangleMesh mesh) {
    if (scene.materials() > 0) {
      if (m.faceMats() > 0) {
        // Just take the first material in the mesh, it could have more but we dont support more
        // than 1 material for a mesh
        // materialIndexForMesh = m.faceMat(0).matIndex();

        Material3ds mat = scene.material(sceneMaterialID);
        String materialName = mat.name();
        if (debug)
          logger.debug(
              "Material name for mesh \"" + mesh.getName() + ":-> \"" + materialName + "\"");
        materialName.trim();
        materialName.toLowerCase();

        // Try to load texture
        try {
          PImage cachedImage = textureCache.get(materialName);
          if (cachedImage != null) {
            mesh.setTexture(cachedImage);
            mesh.setTextureEnabled(true);
            if (debug) logger.debug("->Loaded texture from CACHE : \"" + materialName + "\"");
            return;
          }

          if (modelFile.exists()) { // If model is loaded from local file system
            String modelFolder =
                modelFile
                    .getParent(); // pathToModel.substring(),
                                  // pathToModel.lastIndexOf(File.pathSeparator)
            File modelFolderFile = new File(modelFolder);
            if (modelFolderFile.exists() && modelFolderFile.isDirectory())
              modelFolder = modelFolderFile.getAbsolutePath();
            else {
              modelFolder = "";
            }

            String[] suffix =
                new String[] {
                  "jpg", "JPG", "tga", "TGA", "bmp", "BMP", "png", "PNG", "tiff", "TIFF"
                };
            for (int j = 0; j < suffix.length; j++) {
              String suffixString = suffix[j];
              // Try to load and set texture to mesh
              String texturePath =
                  modelFolder + MTApplication.separator + materialName + "." + suffixString;
              File textureFile = new File(texturePath);
              if (textureFile.exists()) {
                boolean success = textureFile.renameTo(new File(texturePath));
                if (!success) {
                  // File was not successfully renamed
                  logger.debug("failed to RENAME file: " + textureFile.getAbsolutePath());
                }
                PImage texture = null;
                if (MT4jSettings.getInstance().isOpenGlMode()) { // TODO check if render thread
                  PImage img = pa.loadImage(texturePath);
                  if (Tools3D.isPowerOfTwoDimension(img)) {
                    texture =
                        new GLTexture(
                            pa,
                            img,
                            new GLTextureSettings(
                                TEXTURE_TARGET.TEXTURE_2D,
                                SHRINKAGE_FILTER.Trilinear,
                                EXPANSION_FILTER.Bilinear,
                                WRAP_MODE.REPEAT,
                                WRAP_MODE.REPEAT));
                  } else {
                    texture =
                        new GLTexture(
                            pa,
                            img,
                            new GLTextureSettings(
                                TEXTURE_TARGET.RECTANGULAR,
                                SHRINKAGE_FILTER.Trilinear,
                                EXPANSION_FILTER.Bilinear,
                                WRAP_MODE.REPEAT,
                                WRAP_MODE.REPEAT));
                    // ((GLTexture)texture).setFilter(SHRINKAGE_FILTER.BilinearNoMipMaps,
                    // EXPANSION_FILTER.Bilinear);
                  }
                } else {
                  texture = pa.loadImage(texturePath);
                }
                mesh.setTexture(texture);
                mesh.setTextureEnabled(true);

                textureCache.put(materialName, texture);
                if (debug) logger.debug("->Loaded material texture: \"" + materialName + "\"");
                break;
              }
              if (j + 1 == suffix.length) {
                logger.error("Couldnt load material texture: \"" + materialName + "\"");
              }
            }
          } else { // Probably loading from jar file
            PImage texture = null;
            String[] suffix =
                new String[] {
                  "jpg", "JPG", "tga", "TGA", "bmp", "BMP", "png", "PNG", "tiff", "TIFF"
                };
            for (String suffixString : suffix) {
              String modelFolder =
                  pathToModel.substring(0, pathToModel.lastIndexOf(MTApplication.separator));
              String texturePath =
                  modelFolder + MTApplication.separator + materialName + "." + suffixString;
              if (MT4jSettings.getInstance().isOpenGlMode()) {
                PImage img = pa.loadImage(texturePath);
                if (Tools3D.isPowerOfTwoDimension(img)) {
                  texture =
                      new GLTexture(
                          pa,
                          img,
                          new GLTextureSettings(
                              TEXTURE_TARGET.TEXTURE_2D,
                              SHRINKAGE_FILTER.Trilinear,
                              EXPANSION_FILTER.Bilinear,
                              WRAP_MODE.REPEAT,
                              WRAP_MODE.REPEAT));
                } else {
                  texture =
                      new GLTexture(
                          pa,
                          img,
                          new GLTextureSettings(
                              TEXTURE_TARGET.RECTANGULAR,
                              SHRINKAGE_FILTER.Trilinear,
                              EXPANSION_FILTER.Bilinear,
                              WRAP_MODE.REPEAT,
                              WRAP_MODE.REPEAT));
                  // ((GLTexture)texture).setFilter(SHRINKAGE_FILTER.BilinearNoMipMaps,
                  // EXPANSION_FILTER.Bilinear);
                }
              } else {
                texture = pa.loadImage(texturePath);
              }
              mesh.setTexture(texture);
              mesh.setTextureEnabled(true);

              textureCache.put(materialName, texture);
              if (debug) logger.debug("->Loaded material texture: \"" + materialName + "\"");
              break;
            }
          }
        } catch (Exception e) {
          logger.error(e.getMessage());
        }
      } // if (m.faceMats() > 0)
    } // if (scene.materials() > 0)
  }
 // FIXME TEST -> adapt tex coords for non fitting, NPOT gl texture
 private void adaptTexCoordsForNPOTUse() {
   PImage tex = this.getTexture();
   if (tex instanceof GLTexture) {
     Tools3D.adaptTextureCoordsNPOT(this, (GLTexture) tex);
   }
 }
  /**
   * Instantiates a new model display scene.
   *
   * @param mtApplication the mt application
   * @param name the name
   */
  public Space3DScene(MTApplication mtApplication, String name) {
    super(mtApplication, name);
    this.pa = mtApplication;

    if (!MT4jSettings.getInstance().isOpenGlMode()) {
      System.err.println("Scene only usable when using the OpenGL renderer! - See settings.txt");
      return;
    }

    this.setClearColor(new MTColor(200, 200, 200, 255));
    this.registerGlobalInputProcessor(new CursorTracer(pa, this));

    // Add a background image for the scene
    this.getCanvas()
        .addChild(new MTBackgroundImage(pa, pa.loadImage(imagesPath + "3040.jpg"), true));

    // Init light settings
    MTLight.enableLightningAndAmbient(pa, 150, 150, 150, 255);
    // Create a light source //I think GL_LIGHT0 is used by processing!
    //		MTLight light = new MTLight(pa, GL.GL_LIGHT3, new Vector3D(0,0,0));
    MTLight light = new MTLight(pa, GL.GL_LIGHT3, new Vector3D(pa.width / 5f, -pa.height / 10f, 0));

    // Set up a material to react to the light
    GLMaterial material = new GLMaterial(Tools3D.getGL(pa));
    material.setAmbient(new float[] {.1f, .1f, .1f, 1f});
    material.setDiffuse(new float[] {1.0f, 1.0f, 1.0f, 1f});
    material.setEmission(new float[] {.0f, .0f, .0f, 1f});
    material.setSpecular(new float[] {1.0f, 1.0f, 1.0f, 1f}); // almost white: very reflective
    material.setShininess(127); // 0=no shine,  127=max shine

    // Create the earth
    earth = new MTSphere(pa, "earth", 40, 40, 80, TextureMode.Projected); // TextureMode.Polar);
    earth.setLight(light);
    earth.setMaterial(material);
    earth.rotateX(earth.getCenterPointRelativeToParent(), -90);
    earth.setTexture(
        new GLTexture(
            pa,
            imagesPath + "worldMap.jpg",
            new GLTextureSettings(
                TEXTURE_TARGET.TEXTURE_2D,
                SHRINKAGE_FILTER.Trilinear,
                EXPANSION_FILTER.Bilinear,
                WRAP_MODE.CLAMP_TO_EDGE,
                WRAP_MODE.CLAMP_TO_EDGE)));
    earth.generateAndUseDisplayLists();
    earth.setPositionGlobal(
        new Vector3D(
            pa.width / 2f,
            pa.height / 2f,
            250)); // earth.setPositionGlobal(new Vector3D(200, 200, 250));
    // Animate earth rotation
    new Animation(
            "rotation animation", new MultiPurposeInterpolator(0, 360, 17000, 0, 1, -1), earth)
        .addAnimationListener(
            new IAnimationListener() {
              public void processAnimationEvent(AnimationEvent ae) {
                earth.rotateY(earth.getCenterPointLocal(), ae.getDelta(), TransformSpace.LOCAL);
              }
            })
        .start();

    // Put planets in a group that can be manipulated by gestures
    // so the rotation of the planets doesent get changed by the gestures
    MTComponent group = new MTComponent(mtApplication);
    group.setComposite(
        true); // This makes the group "consume" all picking and gestures of the children
    group.registerInputProcessor(new DragProcessor(mtApplication));
    group.addGestureListener(DragProcessor.class, new DefaultDragAction());
    group.addGestureListener(DragProcessor.class, new InertiaDragAction(80, 0.8f, 10));
    group.registerInputProcessor(new RotateProcessor(mtApplication));
    group.addGestureListener(RotateProcessor.class, new DefaultRotateAction());
    // Scale the earth from the center. Else it might get distorted
    group.registerInputProcessor(new ScaleProcessor(mtApplication));
    group.addGestureListener(
        ScaleProcessor.class,
        new IGestureEventListener() {
          public boolean processGestureEvent(MTGestureEvent ge) {
            ScaleEvent se = (ScaleEvent) ge;
            earth.scaleGlobal(
                se.getScaleFactorX(),
                se.getScaleFactorY(),
                se.getScaleFactorX(),
                earth.getCenterPointGlobal());
            return false;
          }
        });
    this.getCanvas().addChild(group);
    group.addChild(earth);

    // Create the moon
    final MTSphere moonSphere = new MTSphere(pa, "moon", 35, 35, 25, TextureMode.Polar);
    moonSphere.setMaterial(material);
    moonSphere.translate(new Vector3D(earth.getRadius() + moonSphere.getRadius() + 50, 0, 0));
    moonSphere.setTexture(
        new GLTexture(
            pa,
            imagesPath + "moonmap1k.jpg",
            new GLTextureSettings(
                TEXTURE_TARGET.RECTANGULAR,
                SHRINKAGE_FILTER.Trilinear,
                EXPANSION_FILTER.Bilinear,
                WRAP_MODE.CLAMP_TO_EDGE,
                WRAP_MODE.CLAMP_TO_EDGE)));
    moonSphere.generateAndUseDisplayLists();
    moonSphere.unregisterAllInputProcessors();
    // Rotate the moon around the earth
    new Animation(
            "moon animation", new MultiPurposeInterpolator(0, 360, 12000, 0, 1, -1), moonSphere)
        .addAnimationListener(
            new IAnimationListener() {
              public void processAnimationEvent(AnimationEvent ae) {
                moonSphere.rotateZ(
                    earth.getCenterPointLocal(), ae.getDelta(), TransformSpace.RELATIVE_TO_PARENT);
              }
            })
        .start();
    // Rotate the moon around ints own center
    new Animation(
            "moon animation around own axis",
            new MultiPurposeInterpolator(0, 360, 9000, 0, 1, -1),
            moonSphere)
        .addAnimationListener(
            new IAnimationListener() {
              public void processAnimationEvent(AnimationEvent ae) {
                moonSphere.rotateZ(
                    moonSphere.getCenterPointLocal(), -3 * ae.getDelta(), TransformSpace.LOCAL);
                moonSphere.rotateY(
                    moonSphere.getCenterPointLocal(), 0.5f * ae.getDelta(), TransformSpace.LOCAL);
              }
            })
        .start();
    earth.addChild(moonSphere);
  }