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;
    }
  }
  @Override
  public void updateGeometricState() {
    if ((refreshFlags & RF_LIGHTLIST) != 0) {
      updateWorldLightList();
    }

    if ((refreshFlags & RF_TRANSFORM) != 0) {
      // combine with parent transforms- same for all spatial
      // subclasses.
      updateWorldTransforms();
    }

    if (!children.isEmpty()) {
      // the important part- make sure child geometric state is refreshed
      // first before updating own world bound. This saves
      // a round-trip later on.
      // NOTE 9/19/09
      // Although it does save a round trip,

      for (Spatial child : children.getArray()) {
        child.updateGeometricState();
      }

      for (Batch batch : batches.getArray()) {
        if (batch.needMeshUpdate) {
          batch.geometry.updateModelBound();
          batch.geometry.updateWorldBound();
          batch.needMeshUpdate = false;
        }
      }
    }

    if ((refreshFlags & RF_BOUND) != 0) {
      updateWorldBound();
    }

    assert refreshFlags == 0;
  }
  protected void doBatch() {
    Map<Material, List<Geometry>> matMap = new HashMap<Material, List<Geometry>>();
    int nbGeoms = 0;

    gatherGeomerties(matMap, this, needsFullRebatch);
    if (needsFullRebatch) {
      for (Batch batch : batches.getArray()) {
        batch.geometry.removeFromParent();
      }
      batches.clear();
      batchesByGeom.clear();
    }
    // only reset maxVertCount if there is something new to batch
    if (matMap.size() > 0) {
      maxVertCount = 0;
    }

    for (Map.Entry<Material, List<Geometry>> entry : matMap.entrySet()) {
      Mesh m = new Mesh();
      Material material = entry.getKey();
      List<Geometry> list = entry.getValue();
      nbGeoms += list.size();
      String batchName = name + "-batch" + batches.size();
      Batch batch;
      if (!needsFullRebatch) {
        batch = findBatchByMaterial(material);
        if (batch != null) {
          list.add(0, batch.geometry);
          batchName = batch.geometry.getName();
          batch.geometry.removeFromParent();
        } else {
          batch = new Batch();
        }
      } else {
        batch = new Batch();
      }
      mergeGeometries(m, list);
      m.setDynamic();

      batch.updateGeomList(list);

      batch.geometry = new Geometry(batchName);
      batch.geometry.setMaterial(material);
      this.attachChild(batch.geometry);

      batch.geometry.setMesh(m);
      batch.geometry.getMesh().updateCounts();
      batch.geometry.getMesh().updateBound();
      batches.add(batch);
    }
    if (batches.size() > 0) {
      needsFullRebatch = false;
    }

    logger.log(
        Level.FINE,
        "Batched {0} geometries in {1} batches.",
        new Object[] {nbGeoms, batches.size()});

    // init the temp arrays if something has been batched only.
    if (matMap.size() > 0) {
      // TODO these arrays should be allocated by chunk instead to avoid recreating them each time
      // the batch is changed.
      // init temp float arrays
      tmpFloat = new float[maxVertCount * 3];
      tmpFloatN = new float[maxVertCount * 3];
      if (useTangents) {
        tmpFloatT = new float[maxVertCount * 4];
      }
    }
  }