CDR6275

Michio SHIRAISHI Official Site

JOGL 2.xでシェーダを使っているときにTextRenderで文字を描く方法

東邦大学理学部情報科学科 白石路雄
最終更新: 2012-09-10

 com.jogamp.graph.curve.opengl.TextRendererの使い方のサンプルが、joglのソースコードを落としてjogl-v2.0-rc10/src/test/com/jogamp/opengl/test/junit/graph/demosに入っているんですが、いやはや分かりにくいですね。で、なんとか簡単にしました。で、普通に画面描いてから、文字を出力しようと思ったら、当然glUseProgramでスイッチしないとだめです。ということで、力尽きたので、文字を描くのに関係するところにハイライトつけておきます。Matrixクラスがないと動かないのですが、Androidの android.opengl.Matrixクラスのソースコードに、nativeになっていたメソッドの中身を私が書き足したのものです。おまけに載せておきます。

import static javax.media.opengl.GL2ES2.*;

import java.io.IOException;
import java.nio.FloatBuffer;

import javax.media.opengl.*;

import com.jogamp.graph.curve.opengl.RenderState;
import com.jogamp.graph.curve.opengl.TextRenderer;
import com.jogamp.graph.font.Font;
import com.jogamp.graph.font.FontFactory;
import com.jogamp.graph.geom.opengl.SVertex;
import com.jogamp.newt.event.*;
import com.jogamp.newt.opengl.GLWindow;
import com.jogamp.opengl.util.FPSAnimator;
import com.jogamp.opengl.util.glsl.ShaderState;

public class FontTest implements GLEventListener, MouseListener{
	private boolean initialized = false;
	private int program;
	
	private int positionLocation;
	private FloatBuffer vertexPositionBuffer;

	private int colorLocation;
	private FloatBuffer vertexColorBuffer;

	private int projectionMatrixLocation;
	private FloatBuffer projectionMatrixBuffer;

	private int viewMatrixLocation;
	private FloatBuffer viewMatrixBuffer;
	
	private int modelMatrixLocation;
	private FloatBuffer modelMatrixBuffer;

	private int vertexShader;
	private String vertexShaderCode = 
			"#version 110\n" + 
			"attribute vec4 position;\n" + 
			"attribute vec4 color;\n" + 
			"uniform mat4 projectionMatrix;\n" + 
			"uniform mat4 viewMatrix;\n" + 
			"uniform mat4 modelMatrix;\n" + 
			"void main(void)\n" + 
			"{\n" + 
			"	gl_Position = projectionMatrix * viewMatrix * modelMatrix * position ;\n" + 
			"	gl_FrontColor = color;\n" + 
			"}\n";
	
	private int fragmentShader;
	private String fragmentShaderCode = 
			"#version 110\n" + 
			"void main(void)\n" + 
			"{\n" + 
			"	gl_FragColor = gl_Color;\n" + 
			"}\n";

	private Font font;
	private TextRenderer textRenderer;
	private ShaderState shaderState;


	private float[] cq = { 1.0f, 0.0f, 0.0f, 0.0f };
	private float[] tq = new float[4];
	private float[] rt = { 
			1.0f, 0.0f, 0.0f, 0.0f, 
			0.0f, 1.0f, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f, 0.0f,
			0.0f, 0.0f, 0.0f, 1.0f
	};
	private float[] vt = { 
			1.0f, 0.0f, 0.0f, 0.0f, 
			0.0f, 1.0f, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f, 0.0f,
			0.0f, 0.0f, 0.0f, 1.0f
	};
	private float cx = 0.0f;
	private float cy = 0.0f;
	private float sx = 1.0f;
	private float sy = 1.0f;

	private float[] vertexPositions= {  
			 1.0f,   1.0f,   1.0f, // 頂点0
			 1.0f,  -1.0f,  -1.0f, // 頂点3
			-1.0f,   1.0f,  -1.0f, // 頂点2
			-1.0f,  -1.0f,   1.0f, // 頂点1
			-1.0f,   1.0f,  -1.0f, // 頂点2
			 1.0f,   1.0f,   1.0f, // 頂点0
			-1.0f,   1.0f,  -1.0f, // 頂点2
			 1.0f,  -1.0f,  -1.0f, // 頂点3
			-1.0f,  -1.0f,   1.0f, // 頂点1
			 1.0f,   1.0f,   1.0f, // 頂点0
			-1.0f,  -1.0f,   1.0f, // 頂点1
			 1.0f,  -1.0f,  -1.0f, // 頂点3
	};

	private float[] vertexColors = {
			0.8f,   0.8f,   0.8f, 1.0f,
			0.8f,   0.8f,   0.8f, 1.0f,
			0.8f,   0.8f,   0.8f, 1.0f,
			0.8f,   0.3f,   0.3f, 1.0f,
			0.8f,   0.3f,   0.3f, 1.0f,
			0.8f,   0.3f,   0.3f, 1.0f,
			0.3f,   0.8f,   0.3f, 1.0f,
			0.3f,   0.8f,   0.3f, 1.0f,
			0.3f,   0.8f,   0.3f, 1.0f,	
			0.3f,   0.3f,   0.8f, 1.0f,
			0.3f,   0.3f,   0.8f, 1.0f,
			0.3f,   0.3f,   0.8f, 1.0f,
	};

	public static void main(String[] args){
		new FontTest(640, 640);
	}

	public FontTest(int width, int height){
		GLCapabilities caps = new GLCapabilities(GLProfile.get(GLProfile.GL2ES2));
		caps.setAlphaBits(4);
		caps.setSampleBuffers(true);
       	caps.setNumSamples(4);
		System.out.println("Requested: " + caps);
		
		GLWindow window = GLWindow.create(caps);
        window.setSize(width, height);
        window.setVisible(true);
        window.setTitle("First");

        window.addWindowListener(new WindowAdapter() {
            public void windowDestroyNotify(WindowEvent arg0) {
                System.exit(0);
            };
        });

        shaderState = new ShaderState();
		RenderState rs = RenderState.createRenderState(shaderState, SVertex.factory());
		this.textRenderer = TextRenderer.create(rs, 0);
		try {
			this.font = FontFactory.get(FontFactory.UBUNTU).getDefault();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
		
		window.addGLEventListener(this);
		window.addMouseListener(this);

		FPSAnimator animator = new FPSAnimator(window, 60);
		animator.add(window);
		animator.start();
	}

	@Override
	public void init(GLAutoDrawable drawable) {
		GL2ES2 gl = drawable.getGL().getGL2ES2();
		
		System.err.println("GL_VENDOR: "   + gl.glGetString(GL_VENDOR));
        System.err.println("GL_RENDERER: " + gl.glGetString(GL_RENDERER));
        System.err.println("GL_VERSION: "  + gl.glGetString(GL_VERSION));
        
		String[] codes = new String[1];
		int[] lengths = new int[1];
		int[] compiledStatus = new int[1];
		int[] logLength = new int[1];
		byte[] log;

		vertexShader = gl.glCreateShader(GL_VERTEX_SHADER);
		lengths[0] = vertexShaderCode.length();
		codes[0] = vertexShaderCode;
		gl.glShaderSource(vertexShader, codes.length, codes, lengths, 0);
		gl.glCompileShader(vertexShader);

		gl.glGetShaderiv(vertexShader, GL2ES2.GL_COMPILE_STATUS, compiledStatus, 0);
		if(compiledStatus[0]==0) {
			gl.glGetShaderiv(vertexShader, GL2ES2.GL_INFO_LOG_LENGTH, logLength, 0);
			log = new byte[logLength[0]];
			gl.glGetShaderInfoLog(vertexShader, logLength[0], (int[])null, 0, log, 0);
			System.err.println("Error compiling the vertex shader: " + new String(log));
			System.exit(1);
		}

		fragmentShader = gl.glCreateShader(GL_FRAGMENT_SHADER);
		lengths[0] = fragmentShaderCode.length();
		codes[0] = fragmentShaderCode;
		gl.glShaderSource(fragmentShader, codes.length, codes, lengths, 0);
		gl.glCompileShader(fragmentShader);

		gl.glGetShaderiv(fragmentShader, GL2ES2.GL_COMPILE_STATUS, compiledStatus, 0);
		if(compiledStatus[0]==0) {
			gl.glGetShaderiv(fragmentShader, GL2ES2.GL_INFO_LOG_LENGTH, logLength, 0);
			log = new byte[logLength[0]];
			gl.glGetShaderInfoLog(fragmentShader, logLength[0], (int[])null, 0, log, 0);
			System.err.println("Error copiling the fragment shader: " + new String(log));
			System.exit(1);
		}

		program = gl.glCreateProgram();
		gl.glAttachShader(program, vertexShader);
		gl.glAttachShader(program, fragmentShader);

		gl.glLinkProgram(program);

		positionLocation = gl.glGetAttribLocation(program, "position");
		colorLocation    = gl.glGetAttribLocation(program, "color"); 

		projectionMatrixLocation = gl.glGetUniformLocation(program, "projectionMatrix");
		viewMatrixLocation       = gl.glGetUniformLocation(program, "viewMatrix");
		modelMatrixLocation      = gl.glGetUniformLocation(program, "modelMatrix");

		vertexPositionBuffer = FloatBuffer.allocate(vertexPositions.length);
		vertexPositionBuffer.put(vertexPositions).rewind();

		vertexColorBuffer = FloatBuffer.allocate(vertexColors.length);
		vertexColorBuffer.put(vertexColors).rewind();

		viewMatrixBuffer = FloatBuffer.allocate(16);
		viewMatrixBuffer.put(rt).rewind();

		projectionMatrixBuffer = FloatBuffer.allocate(16);

		modelMatrixBuffer = FloatBuffer.allocate(16);
		float[] modelMatrix = new float[16];
		Matrix.setIdentityM(modelMatrix, 0);
		modelMatrixBuffer.put(modelMatrix).rewind();
		
		textRenderer.init(gl);

		gl.setSwapInterval(1);
		gl.glEnable(GL2ES2.GL_DEPTH_TEST);
		gl.glEnable(GL2ES2.GL_BLEND);
		textRenderer.setAlpha(gl, 1.0f);

		initialized = true;
	}

	@Override
	public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
		GL2ES2 gl = drawable.getGL().getGL2ES2();
		gl.glViewport(0, 0, width, height);	
		

		sx = 1.0f / (float)width;
		sy = 1.0f / (float)height;

		float[] projectionMatrix = new float[16];
		Matrix.setIdentityM(projectionMatrix, 0);
		Matrix.perspectiveM(projectionMatrix, 0, 30.0f, width/height, 0.1f, 100.0f);
		projectionMatrixBuffer.put(projectionMatrix).rewind();

		Matrix.setIdentityM(vt, 0);
		Matrix.setLookAtM(vt, 0, 0.0f, 0.0f, 10.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
		updateViewMatrix();
		
		
		textRenderer.reshapeOrtho(null, width, height, -1.0f, 1.0f);

	}

	@Override
	public void display(GLAutoDrawable drawable) {
		if(!initialized) return;
		GL2ES2 gl = drawable.getGL().getGL2ES2();
		gl.glEnable(GL_DEPTH_TEST);
		
		gl.glClearColor(0.55f, 0.55f, 0.55f, 1.0f);
		gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		
		gl.glUseProgram(program);

		gl.glUniformMatrix4fv(projectionMatrixLocation, 1, false, projectionMatrixBuffer);
		gl.glUniformMatrix4fv(viewMatrixLocation, 1, false, viewMatrixBuffer);
		gl.glUniformMatrix4fv(modelMatrixLocation, 1, false, modelMatrixBuffer);
		gl.glVertexAttribPointer(positionLocation, 3, GL_FLOAT, false, 0, vertexPositionBuffer);
		gl.glVertexAttribPointer(colorLocation, 4, GL_FLOAT, false, 0, vertexColorBuffer);
		
		gl.glEnableVertexAttribArray(positionLocation);
		gl.glEnableVertexAttribArray(colorLocation);
		gl.glDrawArrays(GL_TRIANGLES, 0, 12);
		gl.glDisableVertexAttribArray(colorLocation);	
		gl.glDisableVertexAttribArray(positionLocation);
		
		gl.glDisable(GL_DEPTH_TEST);
		
		gl.glUseProgram(shaderState.shaderProgram().program());
		
		String text = "The quick brown fox jumps over the lazy dog";
		float[] position = new float[] {0, 0, 0};
		int fontSize = 18;
		int[] texSize = new int[] { 0 }; 

		textRenderer.setColorStatic(gl, 0.0f, 0.0f, 0.0f);
		textRenderer.resetModelview(null);
		textRenderer.translate(gl, 10.0f, 10.0f, 0.0f);
		textRenderer.drawString3D(gl, font, text, position, fontSize, texSize);     
	}


	@Override
	public void dispose(GLAutoDrawable drawable){
		GL2ES2 gl = drawable.getGL().getGL2ES2();
		
		gl.glDetachShader(program, vertexShader);
		gl.glDeleteShader(vertexShader);

		gl.glDetachShader(program, fragmentShader);
		gl.glDeleteShader(fragmentShader);
	
		gl.glUseProgram(0);
		gl.glDeleteProgram(program);
	}

	@Override
	public void mouseClicked(MouseEvent e) {
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		int x = e.getX();
		int y = e.getY();
		float dx = (x - cx) * sx;
		float dy = (y - cy) * sy;
		float a = (float)Math.sqrt(dx * dx + dy * dy);
		if (a != 0.0) {
			float ar = a * (float)Math.PI;
			float as = (float)Math.sin(ar) / a;
			float dq[] = { (float)Math.cos(ar), dy * as, dx * as, 0.0f };

			tq[0] = dq[0] * cq[0] - dq[1] * cq[1] - dq[2] * cq[2] - dq[3] * cq[3];
			tq[1] = dq[0] * cq[1] + dq[1] * cq[0] + dq[2] * cq[3] - dq[3] * cq[2];
			tq[2] = dq[0] * cq[2] - dq[1] * cq[3] + dq[2] * cq[0] + dq[3] * cq[1];
			tq[3] = dq[0] * cq[3] + dq[1] * cq[2] - dq[2] * cq[1] + dq[3] * cq[0];

			rt[0] = 1.0f - tq[2] * tq[2] * 2.0f - tq[3] * tq[3] * 2.0f;
			rt[1] = (tq[1] * tq[2] + tq[3] * tq[0]) * 2.0f;
			rt[2] = (tq[3] * tq[1] - tq[2] * tq[0]) * 2.0f;
			rt[4] = (tq[1] * tq[2] - tq[3] * tq[0]) * 2.0f;
			rt[5] = 1.0f - tq[3] * tq[3] * 2.0f - tq[1] * tq[1] * 2.0f;
			rt[6] = (tq[2] * tq[3] + tq[1] * tq[0]) * 2.0f;
			rt[8] = (tq[3] * tq[1] + tq[2] * tq[0]) * 2.0f;
			rt[9] = (tq[2] * tq[3] - tq[1] * tq[0]) * 2.0f;
			rt[10] = 1.0f - tq[1] * tq[1] * 2.0f - tq[2] * tq[2] * 2.0f;
			updateViewMatrix();
		}
	}

	private void updateViewMatrix(){
		float[] matrix = new float[16];
		Matrix.multiplyMM(matrix, 0, vt, 0, rt, 0);
		viewMatrixBuffer.put(matrix).rewind();
	}

	@Override
	public void mouseEntered(MouseEvent e) {
	}

	@Override
	public void mouseExited(MouseEvent e) {
	}

	@Override
	public void mouseMoved(MouseEvent e) {
	}

	@Override
	public void mousePressed(MouseEvent e) {
		cx = e.getX();
		cy = e.getY();
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		cq[0] = tq[0];
		cq[1] = tq[1];
		cq[2] = tq[2];
		cq[3] = tq[3];
	}

	@Override
	public void mouseWheelMoved(MouseEvent e) {
	}
}
public class Matrix {
    private final static float[] sTemp = new float[32];
    public static void multiplyMM(float[] result, int resultOffset,
            float[] lhs, int lhsOffset, float[] rhs, int rhsOffset){
        if (result == null) {
            throw new IllegalArgumentException("result == null");
        }
        if (lhs == null) {
            throw new IllegalArgumentException("lhs == null");
        }
        if (rhs == null) {
            throw new IllegalArgumentException("rhs == null");
        }
        if (resultOffset + 16 > result.length) {
            throw new IllegalArgumentException("resultOffset + 16 > result.length");
        }
        if (lhsOffset + 16 > lhs.length) {
            throw new IllegalArgumentException("lhsOffset + 16 > lhs.length");
        }
        if (rhsOffset + 16 > rhs.length) {
            throw new IllegalArgumentException("rhsOffset + 16 > rhs.length");
        }
        
    	float[] matrix = new float[16];
    	float[] a = new float[16];
    	float[] b = new float[16];
		for(int i=0; i<16; i++){
			a[i] = lhs[i+lhsOffset];
			b[i] = rhs[i+rhsOffset];
		}
    	
		matrix[0]  = a[0] * b[0]  + a[4] * b[1]  + a[8] * b[2]   + a[12] * b[3];
		matrix[1]  = a[1] * b[0]  + a[5] * b[1]  + a[9] * b[2]   + a[13] * b[3];
		matrix[2]  = a[2] * b[0]  + a[6] * b[1]  + a[10] * b[2]  + a[14] * b[3];
		matrix[3]  = a[3] * b[0]  + a[7] * b[1]  + a[11] * b[2]  + a[15] * b[3];
		matrix[4]  = a[0] * b[4]  + a[4] * b[5]  + a[8] * b[6]   + a[12] * b[7];
		matrix[5]  = a[1] * b[4]  + a[5] * b[5]  + a[9] * b[6]   + a[13] * b[7];
		matrix[6]  = a[2] * b[4]  + a[6] * b[5]  + a[10] * b[6]  + a[14] * b[7];
		matrix[7]  = a[3] * b[4]  + a[7] * b[5]  + a[11] * b[6]  + a[15] * b[7];
		matrix[8]  = a[0] * b[8]  + a[4] * b[9]  + a[8] * b[10]  + a[12] * b[11];
		matrix[9]  = a[1] * b[8]  + a[5] * b[9]  + a[9] * b[10]  + a[13] * b[11];
		matrix[10] = a[2] * b[8]  + a[6] * b[9]  + a[10] * b[10] + a[14] * b[11];
		matrix[11] = a[3] * b[8]  + a[7] * b[9]  + a[11] * b[10] + a[15] * b[11];
		matrix[12] = a[0] * b[12] + a[4] * b[13] + a[8] * b[14]  + a[12] * b[15];
		matrix[13] = a[1] * b[12] + a[5] * b[13] + a[9] * b[14]  + a[13] * b[15];
		matrix[14] = a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15];
		matrix[15] = a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15];
		for(int i=0; i<16; i++){
			result[i+resultOffset] = matrix[i];
		}
    }

    public static void multiplyMV(float[] resultVec,
            int resultVecOffset, float[] lhsMat, int lhsMatOffset,
            float[] rhsVec, int rhsVecOffset){
        if (resultVec == null) {
            throw new IllegalArgumentException("resultVec == null");
        }
        if (lhsMat == null) {
            throw new IllegalArgumentException("lhsMat == null");
        }
        if (rhsVec == null) {
            throw new IllegalArgumentException("rhsVec == null");
        }
        if (resultVecOffset + 4 > resultVec.length) {
            throw new IllegalArgumentException("resultVecOffset + 16 > resultVec.length");
        }
        if (lhsMatOffset + 16 > lhsMat.length) {
            throw new IllegalArgumentException("lhsMatOffset + 16 > lhsMat.length");
        }
        if (rhsVecOffset + 4 > rhsVec.length) {
            throw new IllegalArgumentException("rhsVecOffset + 4 > rhsVec.length");
        }
 
       	float[] a = new float[16];
		for(int i=0; i<16; i++) a[i] = lhsMat[i+lhsMatOffset];
		float[] b = new float[4];
		for(int i=0; i<4; i++) b[i] = rhsVec[i+rhsVecOffset];
		resultVec[0+resultVecOffset] = a[0]*b[0] + a[4]*b[1] + a[ 8]*b[2] + a[12]*b[3] ;
		resultVec[1+resultVecOffset] = a[1]*b[0] + a[5]*b[1] + a[ 9]*b[2] + a[13]*b[3] ;
		resultVec[2+resultVecOffset] = a[2]*b[0] + a[6]*b[1] + a[10]*b[2] + a[14]*b[3] ;
		resultVec[3+resultVecOffset] = a[3]*b[0] + a[7]*b[1] + a[11]*b[2] + a[15]*b[3] ;
    }

    public static void transposeM(float[] mTrans, int mTransOffset, float[] m,
            int mOffset) {
        for (int i = 0; i < 4; i++) {
            int mBase = i * 4 + mOffset;
            mTrans[i + mTransOffset] = m[mBase];
            mTrans[i + 4 + mTransOffset] = m[mBase + 1];
            mTrans[i + 8 + mTransOffset] = m[mBase + 2];
            mTrans[i + 12 + mTransOffset] = m[mBase + 3];
        }
    }

    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 orthoM(float[] m, int mOffset,
        float left, float right, float bottom, float top,
        float near, float far) {
        if (left == right) {
            throw new IllegalArgumentException("left == right");
        }
        if (bottom == top) {
            throw new IllegalArgumentException("bottom == top");
        }
        if (near == far) {
            throw new IllegalArgumentException("near == far");
        }

        final float r_width  = 1.0f / (right - left);
        final float r_height = 1.0f / (top - bottom);
        final float r_depth  = 1.0f / (far - near);
        final float x =  2.0f * (r_width);
        final float y =  2.0f * (r_height);
        final float z = -2.0f * (r_depth);
        final float tx = -(right + left) * r_width;
        final float ty = -(top + bottom) * r_height;
        final float tz = -(far + near) * r_depth;
        m[mOffset + 0] = x;
        m[mOffset + 5] = y;
        m[mOffset +10] = z;
        m[mOffset +12] = tx;
        m[mOffset +13] = ty;
        m[mOffset +14] = tz;
        m[mOffset +15] = 1.0f;
        m[mOffset + 1] = 0.0f;
        m[mOffset + 2] = 0.0f;
        m[mOffset + 3] = 0.0f;
        m[mOffset + 4] = 0.0f;
        m[mOffset + 6] = 0.0f;
        m[mOffset + 7] = 0.0f;
        m[mOffset + 8] = 0.0f;
        m[mOffset + 9] = 0.0f;
        m[mOffset + 11] = 0.0f;
    }

    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 = 2.0f * ((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;
    }

    public static void perspectiveM(float[] m, int offset,
          float fovy, float aspect, float zNear, float zFar) {
        float f = 1.0f / (float) Math.tan(fovy * (Math.PI / 360.0));
        float rangeReciprocal = 1.0f / (zNear - zFar);

        m[offset + 0] = f / aspect;
        m[offset + 1] = 0.0f;
        m[offset + 2] = 0.0f;
        m[offset + 3] = 0.0f;

        m[offset + 4] = 0.0f;
        m[offset + 5] = f;
        m[offset + 6] = 0.0f;
        m[offset + 7] = 0.0f;

        m[offset + 8] = 0.0f;
        m[offset + 9] = 0.0f;
        m[offset + 10] = (zFar + zNear) * rangeReciprocal;
        m[offset + 11] = -1.0f;

        m[offset + 12] = 0.0f;
        m[offset + 13] = 0.0f;
        m[offset + 14] = 2.0f * zFar * zNear * rangeReciprocal;
        m[offset + 15] = 0.0f;
    }

    public static float length(float x, float y, float z) {
        return (float) Math.sqrt(x * x + y * y + z * z);
    }

    public static void setIdentityM(float[] sm, int smOffset) {
        for (int i=0 ; i<16 ; i++) {
            sm[smOffset + i] = 0;
        }
        for(int i = 0; i < 16; i += 5) {
            sm[smOffset + i] = 1.0f;
        }
    }

    public static void scaleM(float[] sm, int smOffset,
            float[] m, int mOffset,
            float x, float y, float z) {
        for (int i=0 ; i<4 ; i++) {
            int smi = smOffset + i;
            int mi = mOffset + i;
            sm[     smi] = m[     mi] * x;
            sm[ 4 + smi] = m[ 4 + mi] * y;
            sm[ 8 + smi] = m[ 8 + mi] * z;
            sm[12 + smi] = m[12 + mi];
        }
    }

    public static void scaleM(float[] m, int mOffset,
            float x, float y, float z) {
        for (int i=0 ; i<4 ; i++) {
            int mi = mOffset + i;
            m[     mi] *= x;
            m[ 4 + mi] *= y;
            m[ 8 + mi] *= z;
        }
    }

    public static void translateM(float[] tm, int tmOffset,
            float[] m, int mOffset,
            float x, float y, float z) {
        for (int i=0 ; i<12 ; i++) {
            tm[tmOffset + i] = m[mOffset + i];
        }
        for (int i=0 ; i<4 ; i++) {
            int tmi = tmOffset + i;
            int mi = mOffset + i;
            tm[12 + tmi] = m[mi] * x + m[4 + mi] * y + m[8 + mi] * z +
                m[12 + mi];
        }
    }

    public static void translateM(
            float[] m, int mOffset,
            float x, float y, float z) {
        for (int i=0 ; i<4 ; i++) {
            int mi = mOffset + i;
            m[12 + mi] += m[mi] * x + m[4 + mi] * y + m[8 + mi] * z;
        }
    }

    public static void rotateM(float[] rm, int rmOffset,
            float[] m, int mOffset,
            float a, float x, float y, float z) {
        synchronized(sTemp) {
            setRotateM(sTemp, 0, a, x, y, z);
            multiplyMM(rm, rmOffset, m, mOffset, sTemp, 0);
        }
    }

    public static void rotateM(float[] m, int mOffset,
            float a, float x, float y, float z) {
        synchronized(sTemp) {
            setRotateM(sTemp, 0, a, x, y, z);
            multiplyMM(sTemp, 16, m, mOffset, sTemp, 0);
            System.arraycopy(sTemp, 16, m, mOffset, 16);
        }
    }

    public static void setRotateM(float[] rm, int rmOffset,
            float a, float x, float y, float z) {
        rm[rmOffset + 3] = 0;
        rm[rmOffset + 7] = 0;
        rm[rmOffset + 11]= 0;
        rm[rmOffset + 12]= 0;
        rm[rmOffset + 13]= 0;
        rm[rmOffset + 14]= 0;
        rm[rmOffset + 15]= 1;
        a *= (float) (Math.PI / 180.0f);
        float s = (float) Math.sin(a);
        float c = (float) Math.cos(a);
        if (1.0f == x && 0.0f == y && 0.0f == z) {
            rm[rmOffset + 5] = c;   rm[rmOffset + 10]= c;
            rm[rmOffset + 6] = s;   rm[rmOffset + 9] = -s;
            rm[rmOffset + 1] = 0;   rm[rmOffset + 2] = 0;
            rm[rmOffset + 4] = 0;   rm[rmOffset + 8] = 0;
            rm[rmOffset + 0] = 1;
        } else if (0.0f == x && 1.0f == y && 0.0f == z) {
            rm[rmOffset + 0] = c;   rm[rmOffset + 10]= c;
            rm[rmOffset + 8] = s;   rm[rmOffset + 2] = -s;
            rm[rmOffset + 1] = 0;   rm[rmOffset + 4] = 0;
            rm[rmOffset + 6] = 0;   rm[rmOffset + 9] = 0;
            rm[rmOffset + 5] = 1;
        } else if (0.0f == x && 0.0f == y && 1.0f == z) {
            rm[rmOffset + 0] = c;   rm[rmOffset + 5] = c;
            rm[rmOffset + 1] = s;   rm[rmOffset + 4] = -s;
            rm[rmOffset + 2] = 0;   rm[rmOffset + 6] = 0;
            rm[rmOffset + 8] = 0;   rm[rmOffset + 9] = 0;
            rm[rmOffset + 10]= 1;
        } else {
            float len = length(x, y, z);
            if (1.0f != len) {
                float recipLen = 1.0f / len;
                x *= recipLen;
                y *= recipLen;
                z *= recipLen;
            }
            float nc = 1.0f - c;
            float xy = x * y;
            float yz = y * z;
            float zx = z * x;
            float xs = x * s;
            float ys = y * s;
            float zs = z * s;
            rm[rmOffset +  0] = x*x*nc +  c;
            rm[rmOffset +  4] =  xy*nc - zs;
            rm[rmOffset +  8] =  zx*nc + ys;
            rm[rmOffset +  1] =  xy*nc + zs;
            rm[rmOffset +  5] = y*y*nc +  c;
            rm[rmOffset +  9] =  yz*nc - xs;
            rm[rmOffset +  2] =  zx*nc - ys;
            rm[rmOffset +  6] =  yz*nc + xs;
            rm[rmOffset + 10] = z*z*nc +  c;
        }
    }

    public static void setRotateEulerM(float[] rm, int rmOffset,
            float x, float y, float z) {
        x *= (float) (Math.PI / 180.0f);
        y *= (float) (Math.PI / 180.0f);
        z *= (float) (Math.PI / 180.0f);
        float cx = (float) Math.cos(x);
        float sx = (float) Math.sin(x);
        float cy = (float) Math.cos(y);
        float sy = (float) Math.sin(y);
        float cz = (float) Math.cos(z);
        float sz = (float) Math.sin(z);
        float cxsy = cx * sy;
        float sxsy = sx * sy;

        rm[rmOffset + 0]  =   cy * cz;
        rm[rmOffset + 1]  =  -cy * sz;
        rm[rmOffset + 2]  =   sy;
        rm[rmOffset + 3]  =  0.0f;

        rm[rmOffset + 4]  =  cxsy * cz + cx * sz;
        rm[rmOffset + 5]  = -cxsy * sz + cx * cz;
        rm[rmOffset + 6]  =  -sx * cy;
        rm[rmOffset + 7]  =  0.0f;

        rm[rmOffset + 8]  = -sxsy * cz + sx * sz;
        rm[rmOffset + 9]  =  sxsy * sz + sx * cz;
        rm[rmOffset + 10] =  cx * cy;
        rm[rmOffset + 11] =  0.0f;

        rm[rmOffset + 12] =  0.0f;
        rm[rmOffset + 13] =  0.0f;
        rm[rmOffset + 14] =  0.0f;
        rm[rmOffset + 15] =  1.0f;
    }

    public static void setLookAtM(float[] rm, int rmOffset,
            float eyeX, float eyeY, float eyeZ,
            float centerX, float centerY, float centerZ, float upX, float upY,
            float upZ) {

        // See the OpenGL GLUT documentation for gluLookAt for a description
        // of the algorithm. We implement it in a straightforward way:

        float fx = centerX - eyeX;
        float fy = centerY - eyeY;
        float fz = centerZ - eyeZ;

        // Normalize f
        float rlf = 1.0f / Matrix.length(fx, fy, fz);
        fx *= rlf;
        fy *= rlf;
        fz *= rlf;

        // compute s = f x up (x means "cross product")
        float sx = fy * upZ - fz * upY;
        float sy = fz * upX - fx * upZ;
        float sz = fx * upY - fy * upX;

        // and normalize s
        float rls = 1.0f / Matrix.length(sx, sy, sz);
        sx *= rls;
        sy *= rls;
        sz *= rls;

        // compute u = s x f
        float ux = sy * fz - sz * fy;
        float uy = sz * fx - sx * fz;
        float uz = sx * fy - sy * fx;

        rm[rmOffset + 0] = sx;
        rm[rmOffset + 1] = ux;
        rm[rmOffset + 2] = -fx;
        rm[rmOffset + 3] = 0.0f;

        rm[rmOffset + 4] = sy;
        rm[rmOffset + 5] = uy;
        rm[rmOffset + 6] = -fy;
        rm[rmOffset + 7] = 0.0f;

        rm[rmOffset + 8] = sz;
        rm[rmOffset + 9] = uz;
        rm[rmOffset + 10] = -fz;
        rm[rmOffset + 11] = 0.0f;

        rm[rmOffset + 12] = 0.0f;
        rm[rmOffset + 13] = 0.0f;
        rm[rmOffset + 14] = 0.0f;
        rm[rmOffset + 15] = 1.0f;

        translateM(rm, rmOffset, -eyeX, -eyeY, -eyeZ);
    }
}