/**
   * Load model.
   *
   * @param pa the pa
   * @param pathToModel the path to model
   * @param creaseAngle the crease angle
   * @param flipTextureY flip texture y
   * @param flipTextureX flip texture x
   * @return the MT triangle meshes[]
   * @throws FileNotFoundException the file not found exception
   */
  public MTTriangleMesh[] loadModelImpl(
      PApplet pa, String pathToModel, float creaseAngle, boolean flipTextureY, boolean flipTextureX)
      throws FileNotFoundException {
    long timeA = System.currentTimeMillis();
    this.pa = pa;

    ArrayList<MTTriangleMesh> returnMeshList = new ArrayList<MTTriangleMesh>();

    TriangleNormalGenerator normalGenerator = new TriangleNormalGenerator();
    //		normalGenerator.setDebug(debug);

    HashMap<Integer, Group> materialIdToGroup = new HashMap<Integer, Group>();

    // TODO implement
    if (textureCache != null) textureCache.clear();
    textureCache = new WeakHashMap<String, PImage>();

    Scene3ds scene = null;

    try {
      TextDecode3ds decode = new TextDecode3ds();
      //			int level = Scene3ds.DECODE_USED_PARAMS_AND_CHUNKS; //Scene3ds.DECODE_ALL;
      // DECODE_USED_PARAMS, DECODE_USED_PARAMS_AND_CHUNKS
      int level = Scene3ds.DECODE_ALL;

      // LOAD all meshes from file into scene object
      File file = new File(pathToModel);
      if (file.exists()) {
        scene = new Scene3ds(file, decode, level);
      } else {
        InputStream in =
            Thread.currentThread().getContextClassLoader().getResourceAsStream(pathToModel);
        if (in == null) {
          in = pa.getClass().getResourceAsStream(pathToModel);
        }
        if (in != null) {
          scene = new Scene3ds(in, decode, level);
        } else {
          throw new FileNotFoundException("File not found: " + file.getAbsolutePath());
        }
      }

      if (debug) logger.debug("\n-> Loading model: " + file.getName() + " <-");

      if (debug) {
        // Go through all MATERIALS
        logger.debug("\nNum Scene Materials: " + scene.materials());
        for (int m = 0; m < scene.materials(); m++) {
          Material3ds mat = scene.material(m);
          logger.debug("  Material " + m + ": \" " + mat.name() + "\"");
        }
        logger.debug("");
      }

      ///////// Go through all MESHES //////////////////////////
      for (int i = 0; i < scene.meshes(); i++) {
        Mesh3ds m = scene.mesh(i);

        if (debug) {
          int texMapType = m.texMapType();
          logger.debug("Texture coordinates provided: " + m.texCoords());
          logger.debug("Texture mapping type: " + texMapType);
          logger.debug("Mesh:" + m.name() + " Pivot:" + m.pivot());
        }

        /*
        XYZTrack3ds pos 		= m.positionTrack();
        RotationTrack3ds rot 	= m.rotationTrack();
        XYZTrack3ds sc 			= m.scaleTrack();
        //FIXME .track and key(i) werden nicht zur verfügung gestellt?!? aber in javadoc
        */

        if (debug) {
          logger.debug(
              "->Processing mesh: " + i + " of " + scene.meshes() + " Name: \"" + m.name() + "\"");
          logger.debug("  Num Faces: " + m.faces());
          logger.debug("  Num Vertices: " + m.vertices());
          logger.debug("  Num TextureCoordinates: " + m.texCoords());
          logger.debug("");
        }

        // Create the arrays needed for the cruncher
        Vertex[] vertices = new Vertex[m.vertices()];
        int[] indices = new int[m.faces() * 3];

        int[] texCoordIndices = new int[m.faces() * 3];
        float[][] textureCoords = new float[m.texCoords()][2];

        // Fill Vertices array
        for (int j = 0; j < m.vertices(); j++) {
          Vertex3ds vert = m.vertex(j);

          if (this.flipY) {
            vertices[j] = new Vertex(vert.X, -vert.Y, vert.Z, -1, -1);
          } else {
            vertices[j] = new Vertex(vert.X, vert.Y, vert.Z, -1, -1);
          }

          if (m.texCoords() > j) textureCoords[j] = new float[] {m.texCoord(j).U, m.texCoord(j).V};
        }

        // Fill texcoords array
        for (int j = 0; j < m.texCoords(); j++) {
          TexCoord3ds tex = m.texCoord(j);

          float[] texCoord = new float[2];
          texCoord[0] = tex.U;
          texCoord[1] = tex.V;

          textureCoords[j] = texCoord;
        }

        // TODO so werden gleiche materials in verschiedenen meshes nicht zu einem mesh gemacht!
        // also müsste also ohne clear machen und dafür vertices + texcoords in einen grossen
        // array, dafür müssten indices aber per offset angepasst werden dass die wieder stimmen!
        materialIdToGroup.clear();

        if (m.faceMats() > 0) {
          // List face Materials  //TODO USE LOGGERS!!
          logger.debug("  Num Face-Materials: " + m.faceMats());
          for (int n = 0; n < m.faceMats(); n++) {
            FaceMat3ds fmat = m.faceMat(n);
            logger.debug("    FaceMat ID: " + fmat.matIndex());
            logger.debug("    FaceMat indices: " + fmat.faceIndexes());

            int[] faceIndicesForMaterial = fmat.faceIndex();
            if (faceIndicesForMaterial.length <= 0) {
              continue;
            }

            // Check if there already is a group with the same material
            Group group = materialIdToGroup.get(fmat.matIndex());

            // If not, create a new group and save it in map
            if (group == null) {
              group = new Group(new Integer(fmat.matIndex()).toString());
              materialIdToGroup.put(fmat.matIndex(), group);
            }

            // Go through all pointers to the faces for this material
            // and get the corresponding face
            for (int j = 0; j < faceIndicesForMaterial.length; j++) {
              int k = faceIndicesForMaterial[j];
              Face3ds face = m.face(k);

              AFace aFace = new AFace();
              aFace.p0 = face.P0;
              aFace.p1 = face.P1;
              aFace.p2 = face.P2;

              aFace.t0 = face.P0;
              aFace.t1 = face.P1;
              aFace.t2 = face.P2;

              group.addFace(aFace);
            }
          }

          Iterator<Integer> it = materialIdToGroup.keySet().iterator();
          logger.debug("Mesh: " + m.name() + " Anzahl Groups:" + materialIdToGroup.keySet().size());
          while (it.hasNext()) {
            int currentGroupName = it.next();
            Group currentGroup = materialIdToGroup.get(currentGroupName);
            logger.debug("Current group: " + currentGroupName);

            currentGroup.compileItsOwnLists(vertices, textureCoords);

            // Get the new arrays
            Vertex[] newVertices = currentGroup.getGroupVertices();
            int[] newIndices = currentGroup.getIndexArray();
            float[][] newTextureCoords = currentGroup.getGroupTexCoords();
            int[] newTexIndices = currentGroup.getTexCoordIndices();

            logger.debug(
                "\nGroup: \""
                    + currentGroup.name
                    + "\" ->Vertices: "
                    + currentGroup.verticesForGroup.size()
                    + " ->TextureCoords: "
                    + currentGroup.texCoordsForGroup.size()
                    + " ->Indices: "
                    + currentGroup.indexArray.length
                    + " ->Texcoord Indices: "
                    + currentGroup.texCoordIndexArray.length);
            logger.debug("");

            if (vertices.length > 2) {
              GeometryInfo geometry = null;
              // Load as all vertex normals smoothed if creaseAngle == 180;
              if (creaseAngle == 180) {
                geometry =
                    normalGenerator.generateSmoothNormals(
                        pa,
                        newVertices,
                        newIndices,
                        newTextureCoords,
                        newTexIndices,
                        creaseAngle,
                        flipTextureY,
                        flipTextureX);
              } else {
                geometry =
                    normalGenerator.generateCreaseAngleNormals(
                        pa,
                        newVertices,
                        newIndices,
                        newTextureCoords,
                        newTexIndices,
                        creaseAngle,
                        flipTextureY,
                        flipTextureX);
              }

              MTTriangleMesh mesh = new MTTriangleMesh(pa, geometry);

              if (mesh != null) {
                mesh.setName(m.name() + " material: " + new Integer(currentGroupName).toString());
                // Assign texture
                this.assignMaterial(pathToModel, file, scene, m, currentGroupName, mesh);

                if (mesh.getTexture() != null) {
                  mesh.setTextureEnabled(true);
                } else {
                  logger.debug("No texture could be assigned to mesh.");
                }
                returnMeshList.add(mesh);
              }
            }
          }
        } else {
          // If there are no materials for this mesh dont split mesh into
          // groups by material
          // Fill indices array and texcoords array (Here: vertex index = texcoord index)
          for (int faceIndex = 0; faceIndex < m.faces(); faceIndex++) {
            Face3ds f = m.face(faceIndex);

            indices[faceIndex * 3] = f.P0;
            indices[faceIndex * 3 + 1] = f.P1;
            indices[faceIndex * 3 + 2] = f.P2;

            texCoordIndices[faceIndex * 3] = f.P0;
            texCoordIndices[faceIndex * 3 + 1] = f.P1;
            texCoordIndices[faceIndex * 3 + 2] = f.P2;
          } // for faces

          // Create the Mesh and set a texture
          if (vertices.length > 2) {
            // Create normals for the mesh and duplicate vertices for faces that share the same
            // Vertex, but with different texture coordinates or different normals
            GeometryInfo geometry = null;
            // Generate normals and denormalize vertices with more than 1 texture coordinate
            if (creaseAngle == 180) {
              geometry =
                  normalGenerator.generateSmoothNormals(
                      pa,
                      vertices,
                      indices,
                      textureCoords,
                      texCoordIndices,
                      creaseAngle,
                      flipTextureY,
                      flipTextureX);
            } else {
              geometry =
                  normalGenerator.generateCreaseAngleNormals(
                      pa,
                      vertices,
                      indices,
                      textureCoords,
                      texCoordIndices,
                      creaseAngle,
                      flipTextureY,
                      flipTextureX);
            }
            MTTriangleMesh mesh = new MTTriangleMesh(pa, geometry);
            mesh.setName(m.name());
            //						this.assignMaterial(file, scene, m, sceneMaterialID, mesh);
            returnMeshList.add(mesh);
          } // end if vertices.lentgh > 2
        }
      } // for meshes

      //			logger.debug(decode.text());
    } catch (Exception e) {
      e.printStackTrace();
    }

    materialIdToGroup.clear();

    long timeB = System.currentTimeMillis();
    long delta = timeB - timeA;
    logger.debug("Loaded model in: " + delta + " ms");
    return (MTTriangleMesh[]) returnMeshList.toArray(new MTTriangleMesh[returnMeshList.size()]);
  }
  /**
   * 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)
  }