/**
   * Merges all geometries in the collection into the output mesh. Creates a new material using the
   * TextureAtlas.
   *
   * @param geometries
   * @param outMesh
   */
  public static void mergeGeometries(Collection<Geometry> geometries, Mesh outMesh) {
    int[] compsForBuf = new int[VertexBuffer.Type.values().length];
    Format[] formatForBuf = new Format[compsForBuf.length];

    int totalVerts = 0;
    int totalTris = 0;
    int totalLodLevels = 0;
    int maxWeights = -1;

    Mode mode = null;
    for (Geometry geom : geometries) {
      totalVerts += geom.getVertexCount();
      totalTris += geom.getTriangleCount();
      totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels());

      Mode listMode;
      int components;
      switch (geom.getMesh().getMode()) {
        case Points:
          listMode = Mode.Points;
          components = 1;
          break;
        case LineLoop:
        case LineStrip:
        case Lines:
          listMode = Mode.Lines;
          components = 2;
          break;
        case TriangleFan:
        case TriangleStrip:
        case Triangles:
          listMode = Mode.Triangles;
          components = 3;
          break;
        default:
          throw new UnsupportedOperationException();
      }

      for (VertexBuffer vb : geom.getMesh().getBufferList().getArray()) {
        int currentCompsForBuf = compsForBuf[vb.getBufferType().ordinal()];
        if (vb.getBufferType() != Type.Index
            && currentCompsForBuf != 0
            && currentCompsForBuf != vb.getNumComponents()) {
          throw new UnsupportedOperationException(
              "The geometry "
                  + geom
                  + " buffer "
                  + vb.getBufferType()
                  + " has different number of components than the rest of the meshes "
                  + "(this: "
                  + vb.getNumComponents()
                  + ", expected: "
                  + currentCompsForBuf
                  + ")");
        }
        compsForBuf[vb.getBufferType().ordinal()] = vb.getNumComponents();
        formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat();
      }

      maxWeights = Math.max(maxWeights, geom.getMesh().getMaxNumWeights());

      if (mode != null && mode != listMode) {
        throw new UnsupportedOperationException(
            "Cannot combine different" + " primitive types: " + mode + " != " + listMode);
      }
      mode = listMode;
      compsForBuf[Type.Index.ordinal()] = components;
    }

    outMesh.setMaxNumWeights(maxWeights);
    outMesh.setMode(mode);
    if (totalVerts >= 65536) {
      // make sure we create an UnsignedInt buffer so
      // we can fit all of the meshes
      formatForBuf[Type.Index.ordinal()] = Format.UnsignedInt;
    } else {
      formatForBuf[Type.Index.ordinal()] = Format.UnsignedShort;
    }

    // generate output buffers based on retrieved info
    for (int i = 0; i < compsForBuf.length; i++) {
      if (compsForBuf[i] == 0) {
        continue;
      }

      Buffer data;
      if (i == Type.Index.ordinal()) {
        data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris);
      } else {
        data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts);
      }

      VertexBuffer vb = new VertexBuffer(Type.values()[i]);
      vb.setupData(Usage.Static, compsForBuf[i], formatForBuf[i], data);
      outMesh.setBuffer(vb);
    }

    int globalVertIndex = 0;
    int globalTriIndex = 0;

    for (Geometry geom : geometries) {
      Mesh inMesh = geom.getMesh();
      geom.computeWorldMatrix();
      Matrix4f worldMatrix = geom.getWorldMatrix();

      int geomVertCount = inMesh.getVertexCount();
      int geomTriCount = inMesh.getTriangleCount();

      for (int bufType = 0; bufType < compsForBuf.length; bufType++) {
        VertexBuffer inBuf = inMesh.getBuffer(Type.values()[bufType]);
        VertexBuffer outBuf = outMesh.getBuffer(Type.values()[bufType]);

        if (inBuf == null || outBuf == null) {
          continue;
        }

        if (Type.Index.ordinal() == bufType) {
          int components = compsForBuf[bufType];

          IndexBuffer inIdx = inMesh.getIndicesAsList();
          IndexBuffer outIdx = outMesh.getIndexBuffer();

          for (int tri = 0; tri < geomTriCount; tri++) {
            for (int comp = 0; comp < components; comp++) {
              int idx = inIdx.get(tri * components + comp) + globalVertIndex;
              outIdx.put((globalTriIndex + tri) * components + comp, idx);
            }
          }
        } else if (Type.Position.ordinal() == bufType) {
          FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly();
          FloatBuffer outPos = (FloatBuffer) outBuf.getData();
          doTransformVerts(inPos, globalVertIndex, outPos, worldMatrix);
        } else if (Type.Normal.ordinal() == bufType) {
          FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly();
          FloatBuffer outPos = (FloatBuffer) outBuf.getData();
          doTransformNorms(inPos, globalVertIndex, outPos, worldMatrix);
        } else if (Type.Tangent.ordinal() == bufType) {
          FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly();
          FloatBuffer outPos = (FloatBuffer) outBuf.getData();
          int components = inBuf.getNumComponents();
          doTransformTangents(inPos, globalVertIndex, components, outPos, worldMatrix);
        } else {
          inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount);
        }
      }

      globalVertIndex += geomVertCount;
      globalTriIndex += geomTriCount;
    }
  }
  // Don't remove splitmirorred boolean,It's not used right now, but i intend to
  // make this method also split vertice with rotated tangent space and I'll
  // add another splitRotated boolean
  private static List<VertexData> splitVertices(
      Mesh mesh, List<VertexData> vertexData, boolean splitMirorred) {
    int nbVertices = mesh.getBuffer(Type.Position).getNumElements();
    List<VertexData> newVertices = new ArrayList<VertexData>();
    Map<Integer, Integer> indiceMap = new HashMap<Integer, Integer>();
    FloatBuffer normalBuffer = mesh.getFloatBuffer(Type.Normal);

    for (int i = 0; i < vertexData.size(); i++) {
      ArrayList<TriangleData> triangles = vertexData.get(i).triangles;
      Vector3f givenNormal = new Vector3f();
      populateFromBuffer(givenNormal, normalBuffer, i);

      ArrayList<TriangleData> trianglesUp = new ArrayList<TriangleData>();
      ArrayList<TriangleData> trianglesDown = new ArrayList<TriangleData>();
      for (int j = 0; j < triangles.size(); j++) {
        TriangleData triangleData = triangles.get(j);
        if (parity(givenNormal, triangleData.normal) > 0) {
          trianglesUp.add(triangleData);
        } else {
          trianglesDown.add(triangleData);
        }
      }

      // if the vertex has triangles with opposite parity it has to be split
      if (!trianglesUp.isEmpty() && !trianglesDown.isEmpty()) {
        log.log(Level.FINE, "Splitting vertex {0}", i);
        // assigning triangle with the same parity to the original vertex
        vertexData.get(i).triangles.clear();
        vertexData.get(i).triangles.addAll(trianglesUp);

        // creating a new vertex
        VertexData newVert = new VertexData();
        // assigning triangles with opposite parity to it
        newVert.triangles.addAll(trianglesDown);

        newVertices.add(newVert);
        // keep vertex index to fix the index buffers later
        indiceMap.put(nbVertices, i);
        for (TriangleData tri : newVert.triangles) {
          for (int j = 0; j < tri.index.length; j++) {
            if (tri.index[j] == i) {
              tri.index[j] = nbVertices;
            }
          }
        }
        nbVertices++;
      }
    }

    if (!newVertices.isEmpty()) {

      // we have new vertices, we need to update the mesh's buffers.
      for (Type type : VertexBuffer.Type.values()) {
        // skip tangent buffer as we're gonna overwrite it later
        if (type == Type.Tangent || type == Type.BindPoseTangent) {
          continue;
        }
        VertexBuffer vb = mesh.getBuffer(type);
        // Some buffer (hardware skinning ones) can be there but not
        // initialized, they must be skipped.
        // They'll be initialized when Hardware Skinning is engaged
        if (vb == null || vb.getNumComponents() == 0) {
          continue;
        }

        Buffer buffer = vb.getData();
        // IndexBuffer has special treatement, only swapping the vertex indices is needed
        if (type == Type.Index) {
          boolean isShortBuffer = vb.getFormat() == VertexBuffer.Format.UnsignedShort;
          for (VertexData vertex : newVertices) {
            for (TriangleData tri : vertex.triangles) {
              for (int i = 0; i < tri.index.length; i++) {
                if (isShortBuffer) {
                  ((ShortBuffer) buffer).put(tri.triangleOffset + i, (short) tri.index[i]);
                } else {
                  ((IntBuffer) buffer).put(tri.triangleOffset + i, tri.index[i]);
                }
              }
            }
          }
          vb.setUpdateNeeded();
        } else {
          // copy the buffer in a bigger one and append nex vertices to the end
          Buffer newVerts =
              VertexBuffer.createBuffer(vb.getFormat(), vb.getNumComponents(), nbVertices);
          if (buffer != null) {
            buffer.rewind();
            bulkPut(vb.getFormat(), newVerts, buffer);

            int index = vertexData.size();
            newVerts.position(vertexData.size() * vb.getNumComponents());
            for (int j = 0; j < newVertices.size(); j++) {
              int oldInd = indiceMap.get(index);
              for (int i = 0; i < vb.getNumComponents(); i++) {
                putValue(vb.getFormat(), newVerts, buffer, oldInd * vb.getNumComponents() + i);
              }
              index++;
            }
            vb.updateData(newVerts);
            // destroy previous buffer as it's no longer needed
            destroyDirectBuffer(buffer);
          }
        }
      }
      vertexData.addAll(newVertices);

      mesh.updateCounts();
    }

    return vertexData;
  }