protected void updateSubBatch(Geometry bg) {
    Batch batch = batchesByGeom.get(bg);
    if (batch != null) {
      Mesh mesh = batch.geometry.getMesh();
      Mesh origMesh = bg.getMesh();

      VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position);
      FloatBuffer posBuf = (FloatBuffer) pvb.getData();
      VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal);
      FloatBuffer normBuf = (FloatBuffer) nvb.getData();

      VertexBuffer opvb = origMesh.getBuffer(VertexBuffer.Type.Position);
      FloatBuffer oposBuf = (FloatBuffer) opvb.getData();
      VertexBuffer onvb = origMesh.getBuffer(VertexBuffer.Type.Normal);
      FloatBuffer onormBuf = (FloatBuffer) onvb.getData();
      Matrix4f transformMat = getTransformMatrix(bg);

      if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) {

        VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent);
        FloatBuffer tanBuf = (FloatBuffer) tvb.getData();
        VertexBuffer otvb = origMesh.getBuffer(VertexBuffer.Type.Tangent);
        FloatBuffer otanBuf = (FloatBuffer) otvb.getData();
        doTransformsTangents(
            oposBuf,
            onormBuf,
            otanBuf,
            posBuf,
            normBuf,
            tanBuf,
            bg.startIndex,
            bg.startIndex + bg.getVertexCount(),
            transformMat);
        tvb.updateData(tanBuf);
      } else {
        doTransforms(
            oposBuf,
            onormBuf,
            posBuf,
            normBuf,
            bg.startIndex,
            bg.startIndex + bg.getVertexCount(),
            transformMat);
      }
      pvb.updateData(posBuf);
      nvb.updateData(normBuf);

      batch.needMeshUpdate = true;
    }
  }
  void resetToBind() {
    for (Mesh mesh : targets) {
      if (isMeshAnimated(mesh)) {
        FloatBuffer bwBuff = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
        ByteBuffer biBuff = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
        if (!biBuff.hasArray() || !bwBuff.hasArray()) {
          mesh.prepareForAnim(true); // prepare for software animation
        }
        VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
        VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
        VertexBuffer pos = mesh.getBuffer(Type.Position);
        VertexBuffer norm = mesh.getBuffer(Type.Normal);
        FloatBuffer pb = (FloatBuffer) pos.getData();
        FloatBuffer nb = (FloatBuffer) norm.getData();
        FloatBuffer bpb = (FloatBuffer) bindPos.getData();
        FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
        pb.clear();
        nb.clear();
        bpb.clear();
        bnb.clear();

        // reseting bind tangents if there is a bind tangent buffer
        VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent);
        if (bindTangents != null) {
          VertexBuffer tangents = mesh.getBuffer(Type.Tangent);
          FloatBuffer tb = (FloatBuffer) tangents.getData();
          FloatBuffer btb = (FloatBuffer) bindTangents.getData();
          tb.clear();
          btb.clear();
          tb.put(btb).clear();
        }

        pb.put(bpb).clear();
        nb.put(bnb).clear();
      }
    }
  }
  /**
   * Merges all geometries in the collection into the output mesh. Does not take into account
   * materials.
   *
   * @param geometries
   * @param outMesh
   */
  private void mergeGeometries(Mesh outMesh, List<Geometry> geometries) {
    int[] compsForBuf = new int[VertexBuffer.Type.values().length];
    VertexBuffer.Format[] formatForBuf = new VertexBuffer.Format[compsForBuf.length];

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

    Mesh.Mode mode = null;
    for (Geometry geom : geometries) {
      totalVerts += geom.getVertexCount();
      totalTris += geom.getTriangleCount();
      totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels());
      if (maxVertCount < geom.getVertexCount()) {
        maxVertCount = geom.getVertexCount();
      }
      Mesh.Mode listMode;
      int components;
      switch (geom.getMesh().getMode()) {
        case Points:
          listMode = Mesh.Mode.Points;
          components = 1;
          break;
        case LineLoop:
        case LineStrip:
        case Lines:
          listMode = Mesh.Mode.Lines;
          components = 2;
          break;
        case TriangleFan:
        case TriangleStrip:
        case Triangles:
          listMode = Mesh.Mode.Triangles;
          components = 3;
          break;
        default:
          throw new UnsupportedOperationException();
      }

      for (VertexBuffer vb : geom.getMesh().getBufferList().getArray()) {
        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[VertexBuffer.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[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt;
    } else {
      formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.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 == VertexBuffer.Type.Index.ordinal()) {
        data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris);
      } else {
        data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts);
      }

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

    int globalVertIndex = 0;
    int globalTriIndex = 0;

    for (Geometry geom : geometries) {
      Mesh inMesh = geom.getMesh();
      if (!isBatch(geom)) {
        geom.batch(this, globalVertIndex);
      }

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

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

        VertexBuffer outBuf = outMesh.getBuffer(VertexBuffer.Type.values()[bufType]);

        if (outBuf == null) {
          continue;
        }

        if (VertexBuffer.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 (VertexBuffer.Type.Position.ordinal() == bufType) {
          FloatBuffer inPos = (FloatBuffer) inBuf.getData();
          FloatBuffer outPos = (FloatBuffer) outBuf.getData();
          doCopyBuffer(inPos, globalVertIndex, outPos, 3);
        } else if (VertexBuffer.Type.Normal.ordinal() == bufType
            || VertexBuffer.Type.Tangent.ordinal() == bufType) {
          FloatBuffer inPos = (FloatBuffer) inBuf.getData();
          FloatBuffer outPos = (FloatBuffer) outBuf.getData();
          doCopyBuffer(inPos, globalVertIndex, outPos, compsForBuf[bufType]);
          if (VertexBuffer.Type.Tangent.ordinal() == bufType) {
            useTangents = true;
          }
        } else {
          inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount);
          //                    for (int vert = 0; vert < geomVertCount; vert++) {
          //                        int curGlobalVertIndex = globalVertIndex + vert;
          //                        inBuf.copyElement(vert, outBuf, curGlobalVertIndex);
          //                    }
        }
      }

      globalVertIndex += geomVertCount;
      globalTriIndex += geomTriCount;
    }
  }
  /**
   * Specific method for skinning with tangents to avoid cluttering the classic skinning calculation
   * with null checks that would slow down the process even if tangents don't have to be computed.
   * Also the iteration has additional indexes since tangent has 4 components instead of 3 for pos
   * and norm
   *
   * @param maxWeightsPerVert maximum number of weights per vertex
   * @param mesh the mesh
   * @param offsetMatrices the offsetMaytrices to apply
   * @param tb the tangent vertexBuffer
   */
  private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) {
    int maxWeightsPerVert = mesh.getMaxNumWeights();

    if (maxWeightsPerVert <= 0) {
      throw new IllegalStateException("Max weights per vert is incorrectly set!");
    }

    int fourMinusMaxWeights = 4 - maxWeightsPerVert;

    // NOTE: This code assumes the vertex buffer is in bind pose
    // resetToBind() has been called this frame
    VertexBuffer vb = mesh.getBuffer(Type.Position);
    FloatBuffer fvb = (FloatBuffer) vb.getData();
    fvb.rewind();

    VertexBuffer nb = mesh.getBuffer(Type.Normal);

    FloatBuffer fnb = (FloatBuffer) nb.getData();
    fnb.rewind();

    FloatBuffer ftb = (FloatBuffer) tb.getData();
    ftb.rewind();

    // get boneIndexes and weights for mesh
    ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
    FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();

    ib.rewind();
    wb.rewind();

    float[] weights = wb.array();
    byte[] indices = ib.array();
    int idxWeights = 0;

    TempVars vars = TempVars.get();

    float[] posBuf = vars.skinPositions;
    float[] normBuf = vars.skinNormals;
    float[] tanBuf = vars.skinTangents;

    int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length));
    int bufLength = 0;
    int tanLength = 0;
    for (int i = iterations - 1; i >= 0; i--) {
      // read next set of positions and normals from native buffer
      bufLength = Math.min(posBuf.length, fvb.remaining());
      tanLength = Math.min(tanBuf.length, ftb.remaining());
      fvb.get(posBuf, 0, bufLength);
      fnb.get(normBuf, 0, bufLength);
      ftb.get(tanBuf, 0, tanLength);
      int verts = bufLength / 3;
      int idxPositions = 0;
      // tangents has their own index because of the 4 components
      int idxTangents = 0;

      // iterate vertices and apply skinning transform for each effecting bone
      for (int vert = verts - 1; vert >= 0; vert--) {
        float nmx = normBuf[idxPositions];
        float vtx = posBuf[idxPositions++];
        float nmy = normBuf[idxPositions];
        float vty = posBuf[idxPositions++];
        float nmz = normBuf[idxPositions];
        float vtz = posBuf[idxPositions++];

        float tnx = tanBuf[idxTangents++];
        float tny = tanBuf[idxTangents++];
        float tnz = tanBuf[idxTangents++];

        // skipping the 4th component of the tangent since it doesn't have to be transformed
        idxTangents++;

        float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0, rtx = 0, rty = 0, rtz = 0;

        for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
          float weight = weights[idxWeights];
          Matrix4f mat = offsetMatrices[indices[idxWeights++]];

          rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
          ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
          rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;

          rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
          rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
          rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;

          rtx += (tnx * mat.m00 + tny * mat.m01 + tnz * mat.m02) * weight;
          rty += (tnx * mat.m10 + tny * mat.m11 + tnz * mat.m12) * weight;
          rtz += (tnx * mat.m20 + tny * mat.m21 + tnz * mat.m22) * weight;
        }

        idxWeights += fourMinusMaxWeights;

        idxPositions -= 3;

        normBuf[idxPositions] = rnx;
        posBuf[idxPositions++] = rx;
        normBuf[idxPositions] = rny;
        posBuf[idxPositions++] = ry;
        normBuf[idxPositions] = rnz;
        posBuf[idxPositions++] = rz;

        idxTangents -= 4;

        tanBuf[idxTangents++] = rtx;
        tanBuf[idxTangents++] = rty;
        tanBuf[idxTangents++] = rtz;

        // once again skipping the 4th component of the tangent
        idxTangents++;
      }

      fvb.position(fvb.position() - bufLength);
      fvb.put(posBuf, 0, bufLength);
      fnb.position(fnb.position() - bufLength);
      fnb.put(normBuf, 0, bufLength);
      ftb.position(ftb.position() - tanLength);
      ftb.put(tanBuf, 0, tanLength);
    }

    vars.release();

    vb.updateData(fvb);
    nb.updateData(fnb);
    tb.updateData(ftb);
  }