1. 程式人生 > >三維圖形 ---- 三角形與直線碰撞原理(包含 JavaScript 程式碼)

三維圖形 ---- 三角形與直線碰撞原理(包含 JavaScript 程式碼)

//碰撞的三角形
var triangleVertices = new Float32Array([-1.9, 0.1, -0.1, -0.4, 1.9, 0.5, 0.6, 0.1, 4.7]);
//碰撞的直線
var lineVertices = new Float32Array([-3.0, 0.7, 2.0, 2.0, 0.3, 2.0]);
var collisionDetecion = new Object();
//碰撞點在此計算出來

function initVertexBuffersForCollisionDetecionTriangle(gl){
	var colors = new Float32Array([0.0, 0.0, 0.8, 0.0, 0.0, 1.8 ,0.0, 0.2, 0.5]);//三角形顏色
	var indices = new Uint8Array([0, 1, 2]);
	var vertexBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, triangleVertices, gl.STATIC_DRAW);//繫結三角形頂點座標
	vertexBuffer.num = 3;
	vertexBuffer.type = gl.FLOAT;
	collisionDetecion.vertexBuffer = vertexBuffer;
		
	var colorBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);//繫結三角形定點顏色
	colorBuffer.num = 3;
	colorBuffer.type = gl.FLOAT;
	collisionDetecion.colorBuffer = colorBuffer;
	
	var indexBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
	indexBuffer.type = gl.UNSIGNED_BYTE;
	collisionDetecion.indexBuffer = indexBuffer;
	collisionDetecion.numIndices = indices.length;
	if(!collisionDetecion.vertexBuffer || !collisionDetecion.colorBuffer || !collisionDetecion.indexBuffer){
		return null;
	}
	//*******************************************//
	//射線顏色
	var lineColors = new Float32Array([0.0, 0.9, 0.0, 0.5, 0.0, 0.0]);
	var lineIndices = new Uint8Array([0, 1]);
	var lineVertexBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, lineVertexBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, lineVertices, gl.STATIC_DRAW);
	lineVertexBuffer.num = 3;
	lineVertexBuffer.type = gl.FLOAT;
	collisionDetecion.lineVertexBuffer = lineVertexBuffer;//繫結射線座標點
	
	var lineColorBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, lineColorBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, lineColors, gl.STATIC_DRAW);
	lineColorBuffer.num = 3;
	lineColorBuffer.type = gl.FLOAT;
	collisionDetecion.lineColorBuffer = lineColorBuffer;//繫結射線顏色
	
	var lineIndexBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, lineIndexBuffer);
	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, lineIndices, gl.STATIC_DRAW);//繫結射線座標點索引
	indexBuffer.type = gl.UNSIGNED_BYTE;
	collisionDetecion.lineIndexBuffer = lineIndexBuffer;
	collisionDetecion.lineNumberIndices = lineIndices.length;
	if(!collisionDetecion.lineVertexBuffer || !collisionDetecion.lineColorBuffer || !collisionDetecion.lineIndexBuffer){
		return null;
	}
	//***********************************************//
	var ax = triangleVertices[0], ay = triangleVertices[1], az = triangleVertices[2];
	var bx = triangleVertices[3], by = triangleVertices[3 + 1], bz = triangleVertices[3 + 2];
	var cx = triangleVertices[3*2], cy = triangleVertices[3*2 + 1], cz = triangleVertices[3*2 + 2];
	//射線向量
	var dx = lineVertices[0] - lineVertices[3]; 
	var dy = lineVertices[1] - lineVertices[3 + 1];
	var dz = lineVertices[2] - lineVertices[3 + 2];
	//起始點
	var ox = lineVertices[0], oy = lineVertices[1], oz = lineVertices[2];
	var a = ax - bx, b = ax - cx, c = dx, d = ax - ox;
	var e = ay - by, f = ay - cy, g = dy, h = ay - oy;
	var i = az - bz, j = az - cz, k = dz, l = az - oz;
	
	var param = 1.0 / (a*(f*k - g*j) + b*(g*i -e*k) + c*(e*j - f*i));
	var Timeparam = a*(f*l - h*j) + b*(h*i - e*l) + d*(e*j - f*i);
	//方向向量的時間倍數
	var time = Timeparam * param;
	//起點 + 時間 * 方向向量 = 碰撞點
	var resultX = ox + time*dx;
	var resultY = oy + time*dy;
	var resultZ = oz + time*dz;
	var collisionDetecionPointer = new Float32Array([resultX, resultY, resultZ]);
	var collisionDetecionColors = new Float32Array([1.0,0.0,0.0]);
	var collisionDetecionIndex = new Uint8Array(0);
	var pointerVertexBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, pointerVertexBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, collisionDetecionPointer, gl.STATIC_DRAW);
	pointerVertexBuffer.num = 3;
	pointerVertexBuffer.type = gl.FLOAT;
	collisionDetecion.pointerVertexBuffer = pointerVertexBuffer;//繫結碰撞點座標
	
	var pointerColorBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, pointerColorBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, collisionDetecionColors, gl.STATIC_DRAW);//繫結碰撞點顏色
	pointerColorBuffer.num = 3;
	pointerColorBuffer.type = gl.FLOAT;
	collisionDetecion.pointerColorBuffer = pointerColorBuffer;
	
	var pointerIndexBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, pointerIndexBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, collisionDetecionIndex, gl.STATIC_DRAW);//繫結碰撞點索引
	pointerIndexBuffer.type = gl.UNSIGNED_BYTE;
	collisionDetecion.pointerIndexBuffer = pointerIndexBuffer;
	collisionDetecion.pointerNumIndices = collisionDetecionIndex.length;
	if(!collisionDetecion.pointerVertexBuffer ||
	   !collisionDetecion.pointerColorBuffer ||
	   !collisionDetecion.pointerIndexBuffer){
	   	console.log("failed to create pointer ...");
		return null;
	}
	
	
	gl.bindBuffer(gl.ARRAY_BUFFER,null);
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
	return collisionDetecion;
}


var Offset = 200;
function keyhitkeydownCollisionDetecion(ev, gl, canvas, fbo, shadowProgram,
	triangle, plane, normalProgram, floorProgram,  collisionDetecionTriangle) {
	var codeChar = String.fromCharCode(event.keyCode);
	var DEGREE_SPAN = (3.0 / 180.0 * Math.PI); //攝像機每次轉動的角度
	if(event.keyCode == 39) { // The right arrow key was pressed
		//向前
		cx = cx - Math.sin(direction) * 1.0;
		cz = cz - Math.cos(direction) * 1.0;
	} else
	if(event.keyCode == 37) { // The left arrow key was pressed
		//向後
		cx = cx + Math.sin(direction) * 1.0;
		cz = cz + Math.cos(direction) * 1.0;
	} else if(event.keyCode == 38) {
		direction = direction + DEGREE_SPAN;
	} else if(event.keyCode == 40) {
		direction = direction - DEGREE_SPAN;
	} else {
		return;
	}
	//設定新的觀察目標點XZ座標
	tx = (cx - Math.sin(direction) * Offset); //觀察目標點x座標 
	tz = (cz - Math.cos(direction) * Offset); //觀察目標點z座標	

	var viewProjMatrix = new Matrix4(); // Prepare a view projection matrix for regular drawing
	viewProjMatrix.setPerspective(45, canvas.width / canvas.height, 1.0, 100.0);
	viewProjMatrix.lookAt(cx, 1.5, cz, tx, 0, tz, 0, 1, 0);

	var currentAngle = 0.0; // Current rotation angle (degrees)
	
	var tick = function() {

		
		gl.viewport(0, 0, canvas.width, canvas.height);
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear color and depth buffer
		gl.useProgram(normalProgram); // Set the shader for regular drawing
		// Draw the triangle and plane ( for regular drawing)
		drawTriangle(gl, normalProgram, triangle, currentAngle, viewProjMatrix, triangle, plane);
		drawPlane(gl, normalProgram, plane, viewProjMatrix);
		gl.useProgram(floorProgram);
		drawCollisionDetecionTriangle(gl, floorProgram, collisionDetecionTriangle, viewProjMatrix, canvas);
		drawCollisionDetecionLine(gl, floorProgram, collisionDetecionTriangle, viewProjMatrix, canvas);
		ComputeCollisionDetecionLine(gl, floorProgram, collisionDetecionTriangle, viewProjMatrix, canvas);
		window.requestAnimationFrame(tick, canvas);
	};
	tick();
}
//畫三角形
function drawCollisionDetecionTriangle(gl, floorProgram, collisionDetecionTriangle, viewProjMatrix, canvas){
	gl.bindBuffer(gl.ARRAY_BUFFER, collisionDetecionTriangle.vertexBuffer);
	gl.vertexAttribPointer(floorProgram.a_Position, collisionDetecionTriangle.vertexBuffer.num, 
		collisionDetecionTriangle.vertexBuffer.type, false, 0, 0);
	gl.enableVertexAttribArray(floorProgram.a_Position);
	if(floorProgram.a_Color != undefined){
		gl.bindBuffer(gl.ARRAY_BUFFER, collisionDetecionTriangle.colorBuffer);
		gl.vertexAttribPointer(floorProgram.a_Color, collisionDetecionTriangle.colorBuffer.num, 
			collisionDetecionTriangle.colorBuffer.type,false,0,0);
		gl.enableVertexAttribArray(floorProgram.a_Color);
	}
	g_mvpMatrix.set(viewProjMatrix);
	g_mvpMatrix.translate(0.2,0.0,0.0);
	g_mvpMatrix.rotate(angle,0.0,1.0,0.0);
	g_mvpMatrix.scale(1.5, 1.5, 1.5);
	g_mvpMatrix.multiply(g_modelMatrix);
	gl.uniformMatrix4fv(floorProgram.u_MvpMatrix, false, g_mvpMatrix.elements);
	gl.drawElements(gl.TRIANGLE_FAN, 3, gl.UNSIGNED_BYTE, 0);

	//gl.drawArrays(gl.TRIANGLES, 0, 3);
}
//畫直線
function  drawCollisionDetecionLine(gl, floorProgram, collisionDetecionTriangle, viewProjMatrix, canvas){
	gl.bindBuffer(gl.ARRAY_BUFFER, collisionDetecionTriangle.lineVertexBuffer);
	gl.vertexAttribPointer(floorProgram.a_Position, collisionDetecionTriangle.lineVertexBuffer.num, 
		collisionDetecionTriangle.lineVertexBuffer.type,false,0,0);
	gl.enableVertexAttribArray(floorProgram.a_Position);
	if(floorProgram.a_Color != null){
		gl.bindBuffer(gl.ARRAY_BUFFER, collisionDetecionTriangle.lineColorBuffer);
		gl.vertexAttribPointer(floorProgram.a_Color, collisionDetecionTriangle.lineColorBuffer.num,
						collisionDetecionTriangle.colorBuffer.type, false, 0, 0);
		gl.enableVertexAttribArray(floorProgram.a_Color);
	}
	g_mvpMatrix.set(viewProjMatrix);
	g_mvpMatrix.translate(0.2,0.0,0.0);
	g_mvpMatrix.rotate(angle,0.0,1.0,0.0);
	g_mvpMatrix.scale(1.5, 1.5, 1.5);
	g_mvpMatrix.multiply(g_modelMatrix);
	gl.uniformMatrix4fv(floorProgram.u_MvpMatrix, false, g_mvpMatrix.elements);
	gl.drawElements(gl.LINES,collisionDetecionTriangle.lineNumberIndices,gl.UNSIGNED_BYTE,0);
}
//畫碰撞點
function ComputeCollisionDetecionLine(gl, floorProgram, collisionDetecionTriangle, viewProjMatrix, canvas){
	gl.bindBuffer(gl.ARRAY_BUFFER, collisionDetecionTriangle.pointerVertexBuffer);
	gl.vertexAttribPointer(floorProgram.a_Position, collisionDetecionTriangle.pointerVertexBuffer.num, 
		collisionDetecionTriangle.pointerVertexBuffer.type,false,0,0);
	gl.enableVertexAttribArray(floorProgram.a_Position);
	if(floorProgram.a_Color != null){
		gl.bindBuffer(gl.ARRAY_BUFFER, collisionDetecionTriangle.pointerColorBuffer);
		gl.vertexAttribPointer(floorProgram.a_Color, collisionDetecionTriangle.pointerColorBuffer.num,
						collisionDetecionTriangle.pointerColorBuffer.type, false, 0, 0);
		gl.enableVertexAttribArray(floorProgram.a_Color);
	}
	g_mvpMatrix.set(viewProjMatrix);
	g_mvpMatrix.translate(0.2,0.0,0.0);
	g_mvpMatrix.rotate(angle,0.0,1.0,0.0);
	g_mvpMatrix.scale(1.5, 1.5, 1.5);
	g_mvpMatrix.multiply(g_modelMatrix);
	gl.uniformMatrix4fv(floorProgram.u_MvpMatrix, false, g_mvpMatrix.elements);
	gl.drawElements(gl.POINTS,1,gl.UNSIGNED_BYTE,0);
	
}

/**
 *  48到57 0到9
 *  65到90 a到z
 *  38 up
 *  40 down
 *  112到135 F1到F24
 */

var r = 1;
var speed = 0.5;
var cita = 0.3;

var direction = 0; //視線方向
var cx = 0; //攝像機x座標 
var cz = 20; //攝像機z座標

var tx = 0; //觀察目標點x座標
var tz = 0; //觀察目標點z座標  

var keyhitkeydown = function(ev, gl, canvas, fbo, shadowProgram, triangle, 
	triangle2, plane, normalProgram, floorProgram, triangle3, triangle2ForLine) {
	var codeChar = String.fromCharCode(event.keyCode);

	var DEGREE_SPAN = (3.0 / 180.0 * Math.PI); //攝像機每次轉動的角度
	if(event.keyCode == 39) { // The right arrow key was pressed
		//向前
		cx = cx - Math.sin(direction) * 1.0;
		cz = cz - Math.cos(direction) * 1.0;
	} else
	if(event.keyCode == 37) { // The left arrow key was pressed
		//向後
		cx = cx + Math.sin(direction) * 1.0;
		cz = cz + Math.cos(direction) * 1.0;
	} else if(event.keyCode == 38) {
		direction = direction + DEGREE_SPAN;
	} else if(event.keyCode == 40) {
		direction = direction - DEGREE_SPAN;
	} else {
		return;
	}
	//設定新的觀察目標點XZ座標
	tx = (cx - Math.sin(direction) * Offset); //觀察目標點x座標 
	tz = (cz - Math.cos(direction) * Offset); //觀察目標點z座標
			
	var viewProjMatrixFromLight = new Matrix4(); // Prepare a view projection matrix for generating a shadow map
	viewProjMatrixFromLight.setPerspective(70.0, OFFSCREEN_WIDTH / OFFSCREEN_HEIGHT, 1.0, 100.0);
	viewProjMatrixFromLight.lookAt(LIGHT_X, LIGHT_Y, LIGHT_Z, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
	
	var viewProjMatrix = new Matrix4(); // Prepare a view projection matrix for regular drawing
	viewProjMatrix.setPerspective(45, canvas.width / canvas.height, 1.0, 100.0);
	viewProjMatrix.lookAt(cx, 1.5, cz, tx, 0, tz, 0, 1, 0);

	var currentAngle2 = 0.0; // Current rotation angle (degrees)
	var mvpMatrixFromLight_t = new Matrix4(); // A model view projection matrix from light source (for triangle)
	var mvpMatrixFromLight_p = new Matrix4(); // A model view projection matrix from light source (for plane)
	var tick = function() {
		
		
		currentAngle2 = animate(currentAngle2);
		gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); // Change the drawing destination to FBO
		gl.viewport(0, 0, OFFSCREEN_HEIGHT, OFFSCREEN_HEIGHT); // Set view port for FBO
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear FBO    
		gl.useProgram(shadowProgram); // Set shaders for generating a shadow map
		// Draw the triangle and the plane (for generating a shadow map)
		drawTriangle(gl, shadowProgram, triangle, currentAngle2, viewProjMatrixFromLight);
		mvpMatrixFromLight_t.set(g_mvpMatrix); // Used later
		drawPlane(gl, shadowProgram, plane, viewProjMatrixFromLight);
		mvpMatrixFromLight_p.set(g_mvpMatrix); // Used later

		gl.bindFramebuffer(gl.FRAMEBUFFER, null); // Change the drawing destination to color buffer
		gl.viewport(0, 0, canvas.width, canvas.height);
		
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear color and depth buffer
		gl.useProgram(normalProgram); // Set the shader for regular drawing
		gl.uniform1i(normalProgram.u_ShadowMap, 0); // Pass 0 because gl.TEXTURE0 is enabledする
		// Draw the triangle and plane ( for regular drawing)
		gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_t.elements);
		drawTriangle(gl, normalProgram, triangle, currentAngle2, viewProjMatrix);
		gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_p.elements);
		drawPlane(gl, normalProgram, plane, viewProjMatrix);
		gl.useProgram(floorProgram);
    	
		window.requestAnimationFrame(tick, canvas);
	};
	tick();

}
var moveDistance = -5.0;
var isStop = true;
function Stop() {
	if(isStop) {
		isStop = false;
		return;
	}
	if(!isStop) {
		isStop = true;
	}
}

var angle = 0.0;
function Rotate() {
	angle += 4;
}


var FLOOR_VSHADER =
	'attribute vec4 a_Position;\n' +
	'attribute vec4 a_Color;\n' +
	'uniform mat4 u_MvpMatrix;\n' +
	'varying vec4 v_Color;\n' +
	'void main() {\n' +
	//'  gl_Position = u_MvpMatrix * a_Position;\n' +
		'  gl_Position = u_MvpMatrix * a_Position;\n' +

	'  gl_PointSize = 6.0;\n' +
	'  v_Color = a_Color;\n' +
	'}\n';

var FLOOR_FSHADER =
	'#ifdef GL_ES\n' +
	'precision mediump float;\n' +
	'#endif\n' +
	'varying vec4 v_Color;\n' +
	'void main() {\n' +
	//'  gl_FragColor = vec4(gl_FragCoord.z, 0.6, 0.0, 0.0);\n' + // Write the z-value in R
	'  gl_FragColor = vec4(v_Color.rgb,1.0);\n' +
	'}\n';



function getFloorProgram(gl, FLOOR_VSHADER, FLOOR_FSHADER) {
	var floorProgram = createProgram(gl, FLOOR_VSHADER, FLOOR_FSHADER);
	return floorProgram;
}

// Shadow.js (c) 2012 matsuda and tanaka
// Vertex shader program for generating a shadow map
var SHADOW_VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'uniform mat4 u_MvpMatrix;\n' +
  'void main() {\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\n' +
  '}\n';

// Fragment shader program for generating a shadow map
var SHADOW_FSHADER_SOURCE =
  '#ifdef GL_ES\n' +
  'precision mediump float;\n' +
  '#endif\n' +
  'void main() {\n' +
  '  gl_FragColor = vec4(gl_FragCoord.z, 0.0, 0.0, 0.0);\n' + // Write the z-value in R
  '}\n';

// Vertex shader program for regular drawing
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute vec4 a_Color;\n' +
  'uniform mat4 u_MvpMatrix;\n' +
  'uniform mat4 u_MvpMatrixFromLight;\n' +
  'varying vec4 v_PositionFromLight;\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\n' + 
  '  v_PositionFromLight = u_MvpMatrixFromLight * a_Position;\n' +
  '  v_Color = a_Color;\n' +
  '}\n';

// Fragment shader program for regular drawing
var FSHADER_SOURCE =
  '#ifdef GL_ES\n' +
  'precision mediump float;\n' +
  '#endif\n' +
  'uniform sampler2D u_ShadowMap;\n' +
  'varying vec4 v_PositionFromLight;\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;\n' +
  '  vec4 rgbaDepth = texture2D(u_ShadowMap, shadowCoord.xy);\n' +
  '  float depth = rgbaDepth.r;\n' + // Retrieve the z-value from R
  '  float visibility = (shadowCoord.z > depth + 0.005) ? 0.7 : 1.0;\n' +
  //'  gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);\n' +
    '  gl_FragColor = vec4(v_Color.rgb,1.0);\n' +
  '}\n';

var OFFSCREEN_WIDTH = 2048, OFFSCREEN_HEIGHT = 2048;
var LIGHT_X = 0, LIGHT_Y = 7, LIGHT_Z = 2; // Position of the light source


function main() {
  // Retrieve <canvas> element
  var canvas = document.getElementById('webgl');
  // Get the rendering context for WebGL
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }
	
  // Initialize shaders for generating a shadow map
  var shadowProgram = createProgram(gl, SHADOW_VSHADER_SOURCE, SHADOW_FSHADER_SOURCE);
  shadowProgram.a_Position = gl.getAttribLocation(shadowProgram, 'a_Position');
  shadowProgram.u_MvpMatrix = gl.getUniformLocation(shadowProgram, 'u_MvpMatrix');
  if (shadowProgram.a_Position < 0 || !shadowProgram.u_MvpMatrix) {
    console.log('Failed to get the storage location of attribute or uniform variable from shadowProgram'); 
    return;
  }

  // Initialize shaders for regular drawing
  var normalProgram = createProgram(gl, VSHADER_SOURCE, FSHADER_SOURCE);
  normalProgram.a_Position = gl.getAttribLocation(normalProgram, 'a_Position');
  normalProgram.a_Color = gl.getAttribLocation(normalProgram, 'a_Color');
  normalProgram.u_MvpMatrix = gl.getUniformLocation(normalProgram, 'u_MvpMatrix');
  normalProgram.u_MvpMatrixFromLight = gl.getUniformLocation(normalProgram, 'u_MvpMatrixFromLight');
  normalProgram.u_ShadowMap = gl.getUniformLocation(normalProgram, 'u_ShadowMap');
  if (normalProgram.a_Position < 0 || normalProgram.a_Color < 0 || !normalProgram.u_MvpMatrix ||
      !normalProgram.u_MvpMatrixFromLight || !normalProgram.u_ShadowMap) {
    console.log('Failed to get the storage location of attribute or uniform variable from normalProgram'); 
    return;
  }
	
	var floorProgram = getFloorProgram(gl, FLOOR_VSHADER, FLOOR_FSHADER);
	floorProgram.a_Position = gl.getAttribLocation(floorProgram, 'a_Position');
  floorProgram.a_Color = gl.getAttribLocation(floorProgram, 'a_Color');
  floorProgram.u_MvpMatrix = gl.getUniformLocation(floorProgram, 'u_MvpMatrix');
  if(floorProgram.a_Position < 0 || floorProgram.a_Color < 0 || !floorProgram.u_MvpMatrix) {
 		console.log('Failed to get the storage location of attribute or uniform variable from shadowProgram');
 		return;
	}
  
  //檢測到的碰撞點
  var collisionDetecionTriangle = initVertexBuffersForCollisionDetecionTriangle(gl);
	//***********************//
  // Set the vertex information
  var triangle = initVertexBuffersForTriangle(gl);
  var plane = initVertexBuffersForPlane(gl);
  if (!triangle || !plane) {
    console.log('Failed to set the vertex information');
    return;
  }
	
  // Initialize framebuffer object (FBO)  
  var fbo = initFramebufferObject(gl);
  if (!fbo) {
    console.log('Failed to initialize frame buffer object');
    return;
  }
  gl.activeTexture(gl.TEXTURE0); // Set a texture object to the texture unit
  gl.bindTexture(gl.TEXTURE_2D, fbo.texture);

  // Set the clear color and enable the depth test
  gl.clearColor(0, 0, 0, 1);
  gl.enable(gl.DEPTH_TEST);
  
 
  document.onkeydown = function(ev){ keyhitkeydownCollisionDetecion(ev, gl, canvas, fbo, shadowProgram, 
  	triangle, plane, normalProgram, floorProgram, collisionDetecionTriangle); };
  var viewProjMatrixFromLight = new Matrix4(); // Prepare a view projection matrix for generating a shadow map
  viewProjMatrixFromLight.setPerspective(70.0, OFFSCREEN_WIDTH/OFFSCREEN_HEIGHT, 1.0, 100.0);
  viewProjMatrixFromLight.lookAt(LIGHT_X, LIGHT_Y, LIGHT_Z, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

  var viewProjMatrix = new Matrix4();          // Prepare a view projection matrix for regular drawing
  viewProjMatrix.setPerspective(45, canvas.width/canvas.height, 1.0, 100.0);

  viewProjMatrix.lookAt(cx, 1.5, cz, tx, 0, tz, 0, 1, 0);


  var currentAngle = 0.0; // Current rotation angle (degrees)
  var mvpMatrixFromLight_t = new Matrix4(); // A model view projection matrix from light source (for triangle)
  var mvpMatrixFromLight_p = new Matrix4(); // A model view projection matrix from light source (for plane)
  var tick = function() {
    currentAngle = animate(currentAngle);

		
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);               // Change the drawing destination to color buffer
    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);    // Clear color and depth buffer
		
    gl.useProgram(normalProgram); // Set the shader for regular drawing
    gl.uniform1i(normalProgram.u_ShadowMap, 0);  // Pass 0 because gl.TEXTURE0 is enabledする
    // Draw the triangle and plane ( for regular drawing)
    gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_t.elements);
    drawTriangle(gl, normalProgram, triangle, currentAngle, viewProjMatrix, triangle, plane);
    gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_p.elements);
    drawPlane(gl, normalProgram, plane, viewProjMatrix);
		gl.useProgram(floorProgram);
	  drawCollisionDetecionTriangle(gl, floorProgram, collisionDetecionTriangle, viewProjMatrix, canvas);
    drawCollisionDetecionLine(gl, floorProgram, collisionDetecionTriangle, viewProjMatrix, canvas);
    ComputeCollisionDetecionLine(gl, floorProgram, collisionDetecionTriangle, viewProjMatrix, canvas);
    window.requestAnimationFrame(tick, canvas);
  };
  tick(); 
}

// Coordinate transformation matrix
var g_modelMatrix = new Matrix4();
var g_mvpMatrix = new Matrix4();
function drawTriangle(gl, program, triangle, angle, viewProjMatrix) {
  // Set rotate angle to model matrix and draw triangle
  g_modelMatrix.setRotate(angle, 0, 1, 0);
  draw(gl, program, triangle, viewProjMatrix);
}

function drawPlane(gl, program, plane, viewProjMatrix) {
  // Set rotate angle to model matrix and draw plane
  g_modelMatrix.setRotate(0, 0, 1, 1);
  draw(gl, program, plane, viewProjMatrix);
}

function draw(gl, program, o, viewProjMatrix) {
  initAttributeVariable(gl, program.a_Position, o.vertexBuffer);
  if (program.a_Color != undefined) // If a_Color is defined to attribute
    initAttributeVariable(gl, program.a_Color, o.colorBuffer);

  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, o.indexBuffer);

  // Calculate the model view project matrix and pass it to u_MvpMatrix
  g_mvpMatrix.set(viewProjMatrix);
  g_mvpMatrix.multiply(g_modelMatrix);
  gl.uniformMatrix4fv(program.u_MvpMatrix, false, g_mvpMatrix.elements);

  gl.drawElements(gl.TRIANGLES, o.numIndices, gl.UNSIGNED_BYTE, 0);
}

// Assign the buffer objects and enable the assignment
function initAttributeVariable(gl, a_attribute, buffer) {
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.vertexAttribPointer(a_attribute, buffer.num, buffer.type, false, 0, 0);
  gl.enableVertexAttribArray(a_attribute);
}
/**
 *  建立地面
 * @param {Object} gl
 */
function initVertexBuffersForPlane(gl) {
  // Create a plane
  //  v1------v0
  //  |        | 
  //  |        |
  //  |        |
  //  v2------v3

  // Vertex coordinates
  var vertices = new Float32Array([
    8.0, 0.0, 8.5,  -8.0, 0.0, 8.5,  -8.0, 0.0, -8.5,   8.0, 0.0, -8.5    // v0-v1-v2-v3
  ]);

  // Colors
  var colors = new Float32Array([
    1.0, 0.0, 1.0,    1.0, 0.0, 0.5,  1.0, 0.0, 1.0,   1.0, 0.0, 1.0
  ]);

  // Indices of the vertices
  var indices = new Uint8Array([0, 1, 2,   0, 2, 3]);

  var o = new Object(); // Utilize Object object to return multiple buffer objects together

  // Write vertex information to buffer object
  o.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);
  o.colorBuffer = initArrayBufferForLaterUse(gl, colors, 3, gl.FLOAT);
  o.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);
  if (!o.vertexBuffer || !o.colorBuffer || !o.indexBuffer) return null; 
  o.numIndices = indices.length;

  // Unbind the buffer object
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

  return o;
}

/**
 * 頂上的三角形
 * @param {Object} gl
 */
function initVertexBuffersForTriangle(gl) {
  // Create a triangle
  //       v2
  //      / | 
  //     /  |
  //    /   |
  //  v0----v1

  // Vertex coordinates
  var vertices = new Float32Array([-0.8, 3.5, 0.0,  0.8, 3.5, 0.0,  0.0, 3.5, 1.8]);
  // Colors
  var colors = new Float32Array([1.0, 0.5, 0.0,  1.0, 0.5, 0.0,  1.0, 0.0, 0.0]);    
  // Indices of the vertices
  var indices = new Uint8Array([0, 1, 2]);

  var o = new Object();  // Utilize Object object to return multiple buffer objects together

  // Write vertex information to buffer object
  o.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);
  o.colorBuffer = initArrayBufferForLaterUse(gl, colors, 3, gl.FLOAT);
  o.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);
  if (!o.vertexBuffer || !o.colorBuffer || !o.indexBuffer) return null; 

  o.numIndices = indices.length;
  // Unbind the buffer object
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

  return o;
}

function initArrayBufferForLaterUse(gl, data, num, type) {
  // Create a buffer object
  var buffer = gl.createBuffer();
  if (!buffer) {
    console.log('Failed to create the buffer object');
    return null;
  }
  // Write date into the buffer object
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);

  // Store the necessary information to assign the object to the attribute variable later
  buffer.num = num;
  buffer.type = type;

  return buffer;
}

function initElementArrayBufferForLaterUse(gl, data, type) {
  // Create a buffer object
  var buffer = gl.createBuffer();
  if (!buffer) {
    console.log('Failed to create the buffer object');
    return null;
  }
  // Write date into the buffer object
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW);

  buffer.type = type;

  return buffer;
}

function initFramebufferObject(gl) {
  var framebuffer, texture, depthBuffer;

  // Define the error handling function
  var error = function() {
    if (framebuffer) gl.deleteFramebuffer(framebuffer);
    if (texture) gl.deleteTexture(texture);
    if (depthBuffer) gl.deleteRenderbuffer(depthBuffer);
    return null;
  }

  // Create a framebuffer object (FBO)
  framebuffer = gl.createFramebuffer();
  if (!framebuffer) {
    console.log('Failed to create frame buffer object');
    return error();
  }

  // Create a texture object and set its size and parameters
  texture = gl.createTexture(); // Create a texture object
  if (!texture) {
    console.log('Failed to create texture object');
    return error();
  }
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

  // Create a renderbuffer object and Set its size and parameters
  depthBuffer = gl.createRenderbuffer(); // Create a renderbuffer object
  if (!depthBuffer) {
    console.log('Failed to create renderbuffer object');
    return error();
  }
  gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
  gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT);

  // Attach the texture and the renderbuffer object to the FBO
  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);

  // Check if FBO is configured correctly
  var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
  if (gl.FRAMEBUFFER_COMPLETE !== e) {
    console.log('Frame buffer object is incomplete: ' + e.toString());
    return error();
  }

  framebuffer.texture = texture; // keep the required object

  // Unbind the buffer object
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.bindTexture(gl.TEXTURE_2D, null);
  gl.bindRenderbuffer(gl.RENDERBUFFER, null);

  return framebuffer;
}

var ANGLE_STEP = 40;   // The increments of rotation angle (degrees)

var last = Date.now(); // Last time that this function was called
function animate(angle) {
  var now = Date.now();   // Calculate the elapsed time
  var elapsed = now - last;
  last = now;
  // Update the current rotation angle (adjusted by the elapsed time)
  var newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
  return newAngle % 360;
}