content: /*
   * Copyright 2013 Hannes Janetzek
   * Copyright 2019 schedul-xor
   *
   * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
   *
   * This program is free software: you can redistribute it and/or modify it under the
   * terms of the GNU Lesser General Public License as published by the Free Software
   * Foundation, either version 3 of the License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful, but WITHOUT ANY
   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
   * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public License along with
   * this program. If not, see <http://www.gnu.org/licenses/>.
   */
  package org.oscim.renderer;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.nio.FloatBuffer;
  
  import static org.oscim.backend.GLAdapter.gl;
  
  public class GLMatrix {
  
      static final Logger log = LoggerFactory.getLogger(GLMatrix.class);
      private static final boolean dbg = false;
  
      private final long pointer;
      private final FloatBuffer buffer;
  
      private static final String INVALID_INPUT = "Bad Array!";
  
      public GLMatrix() {
          pointer = alloc();
          buffer = (getBuffer(pointer)).order(ByteOrder.nativeOrder()).asFloatBuffer();
      }
  
      /**
       * Set the Matrix from float array
       *
       * @param m float array to copy
       */
      public void set(float[] m) {
          if (m == null || m.length != 16)
              throw new IllegalArgumentException(INVALID_INPUT);
  
          set(pointer, m);
      }
  
      /**
       * Get the Matrix as float array
       *
       * @param m float array to store Matrix
       */
      public void get(float[] m) {
          if (m == null || m.length != 16)
              throw new IllegalArgumentException(INVALID_INPUT);
  
          get(pointer, m);
      }
  
      /**
       * Copy values from mat
       *
       * @param mat Matrix to copy
       */
      public void copy(GLMatrix mat) {
          copy(pointer, mat.pointer);
      }
  
      /**
       * Project Vector with Matrix
       *
       * @param vec3 Vector to project
       */
      public void prj(float[] vec3) {
          if (vec3 == null || vec3.length < 3)
              throw new IllegalArgumentException(INVALID_INPUT);
  
          prj(pointer, vec3);
      }
  
      /**
       * Project Vectors with Matrix
       *
       * @param vec3 Vector to project
       */
      public void prj3D(float[] vec3, int offset, int length) {
          if (vec3 == null || vec3.length / (offset + length) < 1)
              throw new IllegalArgumentException(INVALID_INPUT);
  
          prj3D(pointer, vec3, offset, length);
      }
  
      /**
       * Project Vectors with Matrix
       *
       * @param vec2 Vector to project
       */
      public void prj2D(float[] vec2, int offset, int length) {
          if (vec2 == null || offset < 0 || (length + offset) * 2 > vec2.length)
              throw new IllegalArgumentException(INVALID_INPUT);
  
          prj2D(pointer, vec2, offset, length);
      }
  
      /**
       * Project Vectors with Matrix
       *
       * @param vec2 Vector to project
       */
      public void prj2D(float[] src, int src_offset, float[] dst, int dst_offset, int length) {
          if (src == null || src_offset < 0 || length + src_offset * 2 > src.length)
              throw new IllegalArgumentException(INVALID_INPUT);
  
          prj2D2(pointer, src, src_offset, dst, dst_offset, length);
      }
  
      /**
       * Multiply rhs onto Matrix.
       *
       * @param rhs right hand side
       */
      public void multiplyRhs(GLMatrix rhs) {
          smulrhs(pointer, rhs.pointer);
      }
  
      /**
       * Use this matrix as rhs, multiply it on lhs and store result.
       *
       * @param lhs right hand side
       */
      public void multiplyLhs(GLMatrix lhs) {
          smullhs(pointer, lhs.pointer);
      }
  
      /**
       * Multiply rhs onto lhs and store result in Matrix.
       * <p/>
       * This matrix MUST be different from lhs and rhs!
       * <p/>
       * when combining matrices for vector projection this
       * has the same effect first as applying rhs then lhs.
       *
       * @param lhs left hand side
       * @param rhs right hand side
       */
      public void multiplyMM(GLMatrix lhs, GLMatrix rhs) {
          smul(pointer, lhs.pointer, rhs.pointer);
      }
  
      /**
       * Transpose mat and store result in Matrix
       *
       * @param mat to transpose
       */
      public void transposeM(GLMatrix mat) {
          strans(pointer, mat.pointer);
      }
  
      /**
       * Set rotation
       *
       * @param a angle in degree
       * @param x around x-axis
       * @param y around y-axis
       * @param z around z-axis
       */
      public void setRotation(float a, float x, float y, float z) {
          setRotation(pointer, a, x, y, z);
      }
  
      /**
       * Set translation
       *
       * @param x along x-axis
       * @param y along y-axis
       * @param z along z-axis
       */
      public void setTranslation(float x, float y, float z) {
          setTranslation(pointer, x, y, z);
      }
  
      /**
       * Set scale factor
       *
       * @param x axis
       * @param y axis
       * @param z axis
       */
      public void setScale(float x, float y, float z) {
          setScale(pointer, x, y, z);
      }
  
      /**
       * Set translation and x,y scale
       *
       * @param tx    translate x
       * @param ty    translate y
       * @param scale factor x,y
       */
      public void setTransScale(float tx, float ty, float scale) {
          setTransScale(pointer, tx, ty, scale);
      }
  
      /**
       * Set Matrix with glUniformMatrix
       *
       * @param location GL location id
       */
      public void setAsUniform(int location) {
          gl.uniformMatrix4fv(location, 1, false, buffer);
          //setAsUniform(pointer, location);
      }
  
      /**
       * Set single value
       *
       * @param pos   at position
       * @param value value to set
       */
      public void setValue(int pos, float value) {
          setValueAt(pointer, pos, value);
      }
  
      /**
       * add some offset (similar to glDepthOffset)
       *
       * @param delta offset
       */
      public void addDepthOffset(int delta) {
          addDepthOffset(pointer, delta);
      }
  
      /**
       * Set identity matrix
       */
      public void setIdentity() {
          identity(pointer);
      }
  
      /**
       * Free native object
       */
      @Override
      public void finalize() {
          if (pointer != 0)
              delete(pointer);
      }
  
      private static native long alloc();
  
      private static native void delete(long self);
  
      private static native void set(long self, float[] m);
  
      private static native void copy(long self, long other);
  
      private static native void identity(long self);
  
      private static native void get(long self, float[] m);
  
      private static native void mul(long self, long lhs_ptr);
  
      private static native void smul(long self, long rhs_ptr, long lhs_ptr);
  
      private static native void smulrhs(long self, long rhs_ptr);
  
      private static native void smullhs(long self, long lhs_ptr);
  
      private static native void strans(long self, long rhs_ptr);
  
      private static native void prj(long self, float[] vec3);
  
      private static native void prj3D(long self, float[] vec3, int start, int cnt);
  
      private static native void prj2D(long self, float[] vec2, int start, int cnt);
  
      private static native void prj2D2(long self, float[] vec2, int src_offset,
                                        float[] dst_vec, int dst_offset, int length);
  
      private static native void setRotation(long self, float a, float x, float y, float z);
  
      private static native void setScale(long self, float x, float y, float z);
  
      private static native void setTranslation(long self, float x, float y, float z);
  
      private static native void setTransScale(long self, float tx, float ty, float scale);
  
      //private static native void setAsUniform(long self, int handle);
  
      private static native void setValueAt(long self, int pos, float value);
  
      private static native void addDepthOffset(long self, int delta);
  
      private static native ByteBuffer getBuffer(long self);
  
      /* Copyright (C) 2007 The Android Open Source Project
       *
       * Licensed under the Apache License, Version 2.0 (the "License");
       * you may not use this file except in compliance with the License.
       * You may obtain a copy of the License at
       *
       * http://www.apache.org/licenses/LICENSE-2.0
       *
       * Unless required by applicable law or agreed to in writing, software
       * distributed under the License is distributed on an "AS IS" BASIS,
       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       * See the License for the specific language governing permissions and
       * limitations under the License. */
  
      /**
       * Define a projection matrix in terms of six clip planes
       *
       * @param m      the float array that holds the perspective matrix
       * @param offset the offset into float array m where the perspective
       *               matrix data is written
       */
      public static void frustumM(float[] m, int offset,
                                  float left, float right, float bottom, float top,
                                  float near, float far) {
          if (left == right) {
              throw new IllegalArgumentException("left == right");
          }
          if (top == bottom) {
              throw new IllegalArgumentException("top == bottom");
          }
          if (near == far) {
              throw new IllegalArgumentException("near == far");
          }
          if (near <= 0.0f) {
              throw new IllegalArgumentException("near <= 0.0f");
          }
          if (far <= 0.0f) {
              throw new IllegalArgumentException("far <= 0.0f");
          }
          final float r_width = 1.0f / (right - left);
          final float r_height = 1.0f / (top - bottom);
          final float r_depth = 1.0f / (near - far);
          final float x = 2.0f * (near * r_width);
          final float y = 2.0f * (near * r_height);
          final float A = (right + left) * r_width;
          final float B = (top + bottom) * r_height;
          final float C = (far + near) * r_depth;
          final float D = 2.0f * (far * near * r_depth);
          m[offset + 0] = x;
          m[offset + 5] = y;
          m[offset + 8] = A;
          m[offset + 9] = B;
          m[offset + 10] = C;
          m[offset + 14] = D;
          m[offset + 11] = -1.0f;
          m[offset + 1] = 0.0f;
          m[offset + 2] = 0.0f;
          m[offset + 3] = 0.0f;
          m[offset + 4] = 0.0f;
          m[offset + 6] = 0.0f;
          m[offset + 7] = 0.0f;
          m[offset + 12] = 0.0f;
          m[offset + 13] = 0.0f;
          m[offset + 15] = 0.0f;
      }
  
      /**
       * Inverts a 4 x 4 matrix.
       *
       * @param mInv       the array that holds the output inverted matrix
       * @param mInvOffset an offset into mInv where the inverted matrix is
       *                   stored.
       * @param m          the input array
       * @param mOffset    an offset into m where the matrix is stored.
       * @return true if the matrix could be inverted, false if it could not.
       */
      public static boolean invertM(float[] mInv, int mInvOffset, float[] m,
                                    int mOffset) {
          // Invert a 4 x 4 matrix using Cramer's Rule
  
          // transpose matrix
          final float src0 = m[mOffset + 0];
          final float src4 = m[mOffset + 1];
          final float src8 = m[mOffset + 2];
          final float src12 = m[mOffset + 3];
  
          final float src1 = m[mOffset + 4];
          final float src5 = m[mOffset + 5];
          final float src9 = m[mOffset + 6];
          final float src13 = m[mOffset + 7];
  
          final float src2 = m[mOffset + 8];
          final float src6 = m[mOffset + 9];
          final float src10 = m[mOffset + 10];
          final float src14 = m[mOffset + 11];
  
          final float src3 = m[mOffset + 12];
          final float src7 = m[mOffset + 13];
          final float src11 = m[mOffset + 14];
          final float src15 = m[mOffset + 15];
  
          // calculate pairs for first 8 elements (cofactors)
          final float atmp0 = src10 * src15;
          final float atmp1 = src11 * src14;
          final float atmp2 = src9 * src15;
          final float atmp3 = src11 * src13;
          final float atmp4 = src9 * src14;
          final float atmp5 = src10 * src13;
          final float atmp6 = src8 * src15;
          final float atmp7 = src11 * src12;
          final float atmp8 = src8 * src14;
          final float atmp9 = src10 * src12;
          final float atmp10 = src8 * src13;
          final float atmp11 = src9 * src12;
  
          // calculate first 8 elements (cofactors)
          final float dst0 = (atmp0 * src5 + atmp3 * src6 + atmp4 * src7)
                  - (atmp1 * src5 + atmp2 * src6 + atmp5 * src7);
          final float dst1 = (atmp1 * src4 + atmp6 * src6 + atmp9 * src7)
                  - (atmp0 * src4 + atmp7 * src6 + atmp8 * src7);
          final float dst2 = (atmp2 * src4 + atmp7 * src5 + atmp10 * src7)
                  - (atmp3 * src4 + atmp6 * src5 + atmp11 * src7);
          final float dst3 = (atmp5 * src4 + atmp8 * src5 + atmp11 * src6)
                  - (atmp4 * src4 + atmp9 * src5 + atmp10 * src6);
          final float dst4 = (atmp1 * src1 + atmp2 * src2 + atmp5 * src3)
                  - (atmp0 * src1 + atmp3 * src2 + atmp4 * src3);
          final float dst5 = (atmp0 * src0 + atmp7 * src2 + atmp8 * src3)
                  - (atmp1 * src0 + atmp6 * src2 + atmp9 * src3);
          final float dst6 = (atmp3 * src0 + atmp6 * src1 + atmp11 * src3)
                  - (atmp2 * src0 + atmp7 * src1 + atmp10 * src3);
          final float dst7 = (atmp4 * src0 + atmp9 * src1 + atmp10 * src2)
                  - (atmp5 * src0 + atmp8 * src1 + atmp11 * src2);
  
          // calculate pairs for second 8 elements (cofactors)
          final float btmp0 = src2 * src7;
          final float btmp1 = src3 * src6;
          final float btmp2 = src1 * src7;
          final float btmp3 = src3 * src5;
          final float btmp4 = src1 * src6;
          final float btmp5 = src2 * src5;
          final float btmp6 = src0 * src7;
          final float btmp7 = src3 * src4;
          final float btmp8 = src0 * src6;
          final float btmp9 = src2 * src4;
          final float btmp10 = src0 * src5;
          final float btmp11 = src1 * src4;
  
          // calculate second 8 elements (cofactors)
          final float dst8 = (btmp0 * src13 + btmp3 * src14 + btmp4 * src15)
                  - (btmp1 * src13 + btmp2 * src14 + btmp5 * src15);
          final float dst9 = (btmp1 * src12 + btmp6 * src14 + btmp9 * src15)
                  - (btmp0 * src12 + btmp7 * src14 + btmp8 * src15);
          final float dst10 = (btmp2 * src12 + btmp7 * src13 + btmp10 * src15)
                  - (btmp3 * src12 + btmp6 * src13 + btmp11 * src15);
          final float dst11 = (btmp5 * src12 + btmp8 * src13 + btmp11 * src14)
                  - (btmp4 * src12 + btmp9 * src13 + btmp10 * src14);
          final float dst12 = (btmp2 * src10 + btmp5 * src11 + btmp1 * src9)
                  - (btmp4 * src11 + btmp0 * src9 + btmp3 * src10);
          final float dst13 = (btmp8 * src11 + btmp0 * src8 + btmp7 * src10)
                  - (btmp6 * src10 + btmp9 * src11 + btmp1 * src8);
          final float dst14 = (btmp6 * src9 + btmp11 * src11 + btmp3 * src8)
                  - (btmp10 * src11 + btmp2 * src8 + btmp7 * src9);
          final float dst15 = (btmp10 * src10 + btmp4 * src8 + btmp9 * src9)
                  - (btmp8 * src9 + btmp11 * src10 + btmp5 * src8);
  
          // calculate determinant
          final float det =
                  src0 * dst0 + src1 * dst1 + src2 * dst2 + src3 * dst3;
  
          if (det == 0.0f) {
              return false;
          }
  
          // calculate matrix inverse
          final float invdet = 1.0f / det;
          mInv[mInvOffset] = dst0 * invdet;
          mInv[1 + mInvOffset] = dst1 * invdet;
          mInv[2 + mInvOffset] = dst2 * invdet;
          mInv[3 + mInvOffset] = dst3 * invdet;
  
          mInv[4 + mInvOffset] = dst4 * invdet;
          mInv[5 + mInvOffset] = dst5 * invdet;
          mInv[6 + mInvOffset] = dst6 * invdet;
          mInv[7 + mInvOffset] = dst7 * invdet;
  
          mInv[8 + mInvOffset] = dst8 * invdet;
          mInv[9 + mInvOffset] = dst9 * invdet;
          mInv[10 + mInvOffset] = dst10 * invdet;
          mInv[11 + mInvOffset] = dst11 * invdet;
  
          mInv[12 + mInvOffset] = dst12 * invdet;
          mInv[13 + mInvOffset] = dst13 * invdet;
          mInv[14 + mInvOffset] = dst14 * invdet;
          mInv[15 + mInvOffset] = dst15 * invdet;
  
          return true;
      }
  
      public static void lookAt(float[] m, int offset,
                                float eyex, float eyey, float eyez,
                                float centerx, float centery, float centerz,
                                float upx, float upy, float upz) {
          float z0 = eyex - centerx;
          float z1 = eyey - centery;
          float z2 = eyez - centerz;
          double len = 1.0 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
          z0 *= len;
          z1 *= len;
          z2 *= len;
  
          float x0 = upy * z2 - upz * z1;
          float x1 = upz * z0 - upx * z2;
          float x2 = upx * z1 - upy * z0;
          len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
          if (len == 0) {
              x0 = x1 = x2 = 0;
          } else {
              len = 1.0 / len;
              x0 *= len;
              x1 *= len;
              x2 *= len;
          }
  
          float y0 = z1 * x2 - z2 * x1;
          float y1 = z2 * x0 - z0 * x2;
          float y2 = z0 * x1 - z1 * x0;
          len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
          if (len == 0) {
              y0 = y1 = y2 = 0;
          } else {
              len = 1.0 / len;
              y0 *= len;
              y1 *= len;
              y2 *= len;
          }
  
          m[offset + 0] = x0;
          m[offset + 1] = y0;
          m[offset + 2] = z0;
          m[offset + 3] = 0.0f;
          m[offset + 4] = x1;
          m[offset + 5] = y1;
          m[offset + 6] = z1;
          m[offset + 7] = 0.0f;
          m[offset + 8] = x2;
          m[offset + 9] = y2;
          m[offset + 10] = z2;
          m[offset + 11] = 0.0f;
          m[offset + 12] = -(x0 * eyex + x1 * eyey + x2 * eyez);
          m[offset + 13] = -(y0 * eyex + y1 * eyey + y2 * eyez);
          m[offset + 14] = -(z0 * eyex + z1 * eyey + z2 * eyez);
          m[offset + 15] = 1.0f;
      }
  
      public static void orthoM(float[] m, int offset,
                                float left, float right, float bottom, float top,
                                float near, float far) {
          final float r_width = 1.0f / (left - right);
          final float r_height = 1.0f / (bottom - top);
          final float r_depth = 1.0f / (near - far);
          m[offset + 0] = -2.0f * r_width;
          m[offset + 5] = -2.0f * r_height;
          m[offset + 10] = 2.0f * r_depth;
          m[offset + 12] = (left + right) * r_width;
          m[offset + 13] = (top + bottom) * r_height;
          m[offset + 14] = (far + near) * r_depth;
          m[offset + 1] = 0.0f;
          m[offset + 2] = 0.0f;
          m[offset + 3] = 0.0f;
          m[offset + 4] = 0.0f;
          m[offset + 6] = 0.0f;
          m[offset + 7] = 0.0f;
          m[offset + 8] = 0.0f;
          m[offset + 9] = 0.0f;
          m[offset + 11] = 0.0f;
          m[offset + 15] = 1.0f;
      }
  }
  