/**
  * Configure the light source used for shadow mapping
  *
  * @param lightNodeInstance The node containing the light instance
  * @param lightIndex The index of the light in light instances of the node
  */
 public void setLight(final Node lightNodeInstance, final int lightIndex) {
   // android.util.Log.d(TAG,"setLight("+lightNodeInstance.id+","+lightIndex+")");
   this.lightType = lightNodeInstance.lightInstances[lightIndex].type;
   switch (this.lightType) {
     case Light.DIRECTIONAL:
       this.lightModel[8] = this.lightModel[8] = this.lightModel[9] = this.lightModel[11] = 0f;
       this.lightModel[10] = -1f;
       MatrixUtils.multiplyMV(this.lightModel, 8, lightNodeInstance.model, 0, this.lightModel, 8);
       this.lightModel[0] = this.lightModel[1] = this.lightModel[2] = 0f;
       this.lightModel[3] = 1f;
       MatrixUtils.multiplyMV(this.lightModel, 0, lightNodeInstance.model, 0, this.lightModel, 0);
       final float length =
           Matrix.length(this.lightModel[0], this.lightModel[1], this.lightModel[2]);
       this.lightModel[8] *= length;
       this.lightModel[9] *= length;
       this.lightModel[10] *= length;
       break;
     case Light.POINT:
       this.lightModel[0] = this.lightModel[1] = this.lightModel[2] = 0f;
       this.lightModel[3] = 1f;
       MatrixUtils.multiplyMV(this.lightModel, 0, lightNodeInstance.model, 0, this.lightModel, 0);
       break;
     case Light.SPOT:
       this.lightModel[0] = this.lightModel[1] = this.lightModel[2] = 0f;
       this.lightModel[3] = 1f;
       MatrixUtils.multiplyMV(this.lightModel, 0, lightNodeInstance.model, 0, this.lightModel, 0);
       this.lightModel[8] = this.lightModel[8] = this.lightModel[9] = this.lightModel[11] = 0f;
       this.lightModel[10] = -1f;
       MatrixUtils.multiplyMV(this.lightModel, 8, lightNodeInstance.model, 0, this.lightModel, 8);
       this.lightModel[4] = this.lightModel[0] + this.lightModel[8];
       this.lightModel[5] = this.lightModel[1] + this.lightModel[9];
       this.lightModel[6] = this.lightModel[2] + this.lightModel[10];
       this.lightModel[7] = 1f;
       break;
   }
 }
  /**
   * Simple recursive method to draw a node and its subnodes
   *
   * @param nodeInstance The node instance
   */
  private void drawNode(Node nodeInstance) {
    MatrixUtils.multiplyMM(this.lightMvpCache, 48, this.lightMvpCache, 32, nodeInstance.model, 0);
    GLES20.glUniformMatrix4fv(this.u_lightMvpMatrixMat4Handle, 1, false, this.lightMvpCache, 48);

    if (nodeInstance.geometryInstances != null) {
      for (GeometryInstance geometryInstance : nodeInstance.geometryInstances) {
        for (Element element : geometryInstance.geometry.elements) {
          if (element.handle == GlBuffer.UNBIND_HANDLE) {
            GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, GlBuffer.UNBIND_HANDLE);
            final GlBuffer<float[]> elementBuffer = BufferUtils.elementToGlBuffer(element);
            elementBuffer.position(elementBuffer.chunks[0]);
            GLES20.glVertexAttribPointer(
                this.a_PositionVec4Handle,
                elementBuffer.chunks[0].components,
                elementBuffer.datatype,
                false,
                elementBuffer.stride,
                elementBuffer.data);
            GLES20.glDrawArrays(element.type, 0, element.count);
            elementBuffer.free();
          } else {
            GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, element.handle);
            GLES20Utils.glVertexAttribPointer(
                this.a_PositionVec4Handle,
                element.inputs[0][GlAssets.Geometry.Element.SIZE],
                GLES20.GL_FLOAT,
                false,
                element.stride,
                element.inputs[0][GlAssets.Geometry.Element.OFFSET]);
            GLES20.glDrawArrays(element.type, 0, element.count);
            GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, GlBuffer.UNBIND_HANDLE);
          }
        }
      }
    }
    if (nodeInstance.nodeInstances != null) {
      for (Node childNode : nodeInstance.nodeInstances) {
        this.drawNode(childNode);
      }
    }
  }
 /**
  * Fine settings used to set the light view and projection matrixes. This method is very important
  * as it allows to improve greatly quality without CPU additional charge. Try to set zFar and
  * zNear as best as you can. A good practice is to bind a camera instance to each node instance
  * used as root node for shadow map generation.
  *
  * @param cameraNodeInstance The node owning the camera
  * @param cameraIndex The indew of the camera in the node instance
  */
 @SuppressLint("NewApi")
 public void setCamera(final Node cameraNodeInstance, final int cameraIndex) {
   // android.util.Log.d(TAG,"setCamera("+cameraNodeInstance.id+","+cameraIndex+")");
   if (cameraNodeInstance.cameraInstances[cameraIndex].type == Camera.ORTHOGRAPHIC) {
     Matrix.orthoM(
         lightMvpCache,
         16,
         -cameraNodeInstance.cameraInstances[cameraIndex].settings[Camera.XMAG],
         cameraNodeInstance.cameraInstances[cameraIndex].settings[Camera.XMAG],
         -cameraNodeInstance.cameraInstances[cameraIndex].settings[Camera.YMAG],
         cameraNodeInstance.cameraInstances[cameraIndex].settings[Camera.YMAG],
         cameraNodeInstance.cameraInstances[cameraIndex].settings[Camera.ZNEAR],
         cameraNodeInstance.cameraInstances[cameraIndex].settings[Camera.ZFAR]);
   } else {
     MatrixUtils.perspectiveM(
         this.lightMvpCache,
         16,
         cameraNodeInstance.cameraInstances[cameraIndex].settings[Camera.YFOV],
         1,
         cameraNodeInstance.cameraInstances[cameraIndex].settings[Camera.ZNEAR],
         cameraNodeInstance.cameraInstances[cameraIndex].settings[Camera.ZFAR]);
   }
 }
  /* (non-Javadoc)
   * @see fr.kesk.gl.shader.GlShader#render(fr.kesk.gl.GlAssets.Node)
   */
  @Override
  public void render(Node nodeInstance) {
    // android.util.Log.d(TAG,"render("+nodeInstance.id+")");
    if (this.lightType == Light.DIRECTIONAL) {
      this.lightModel[4] = this.lightModel[5] = this.lightModel[6] = 0f;
      this.lightModel[7] = 1f;
      MatrixUtils.multiplyMV(this.lightModel, 4, nodeInstance.model, 0, this.lightModel, 4);
      this.lightModel[0] = this.lightModel[4] - this.lightModel[8];
      this.lightModel[1] = this.lightModel[5] - this.lightModel[9];
      this.lightModel[2] = this.lightModel[6] - this.lightModel[10];
      this.lightModel[3] = 1f;
      Matrix.setLookAtM(
          this.lightMvpCache,
          0,
          this.lightModel[0],
          this.lightModel[1],
          this.lightModel[2],
          this.lightModel[4],
          this.lightModel[5],
          this.lightModel[6],
          0f,
          1f,
          0f);
      MatrixUtils.multiplyMM(this.lightMvpCache, 32, this.lightMvpCache, 16, this.lightMvpCache, 0);
      MatrixUtils.multiplyMM(
          this.currentShadowMap.shadowMatrix,
          0,
          GlFastShadowMapShader.BIAS_MATRIX,
          0,
          this.lightMvpCache,
          32);
    } else if (this.lightType == Light.POINT) {
      this.lightModel[4] = this.lightModel[5] = this.lightModel[6] = 0f;
      this.lightModel[7] = 1f;
      MatrixUtils.multiplyMV(this.lightModel, 4, nodeInstance.model, 0, this.lightModel, 4);
      Matrix.setLookAtM(
          this.lightMvpCache,
          0,
          this.lightModel[0],
          this.lightModel[1],
          this.lightModel[2],
          this.lightModel[4],
          this.lightModel[5],
          this.lightModel[6],
          0f,
          1f,
          0f);
      MatrixUtils.multiplyMM(this.lightMvpCache, 32, this.lightMvpCache, 16, this.lightMvpCache, 0);
      MatrixUtils.multiplyMM(
          this.currentShadowMap.shadowMatrix,
          0,
          GlFastShadowMapShader.BIAS_MATRIX,
          0,
          this.lightMvpCache,
          32);
    }

    // Render the scene in the FBO
    this.fbo.bind();
    this.program.start();
    this.cullfaceMode = GlContext.glGetState(GLES20.GL_CULL_FACE_MODE);
    this.viewPort = GlContext.glGetState(GLES20.GL_VIEWPORT);
    GLES20.glCullFace(GLES20.GL_FRONT);
    GLES20.glViewport(0, 0, this.quality, this.quality);
    GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
    GLES20.glEnableVertexAttribArray(this.a_PositionVec4Handle);
    this.drawNode(nodeInstance);
    GLES20.glDisableVertexAttribArray(this.a_PositionVec4Handle);
    this.fbo.unbind();
    GLES20.glCullFace((int) this.cullfaceMode[0]);
    GLES20.glViewport(
        (int) this.viewPort[0],
        (int) this.viewPort[1],
        (int) this.viewPort[2],
        (int) this.viewPort[3]);
  }