var images = [];
var dl = null;
var canvas_elem = null;
var c3d = null;
var temp_mat0 = null;
var temp_mat1 = null;
var object_mat = null;
var camera_mat = null;
var proj_mat = null;
var timer_id = null;
var options = {
	draw_backfaces: true,
	whiteout_alpha: 1,
	wireframe: false,
	subdivide_factor: 10.0,
	nonadaptive_depth: 0
};
var mouse_x = 0;
var mouse_y = 0;
var mouse_grab_point = null;
var mouse_is_down = false;
var horizontal_fov_radians = Math.PI / 2;
var object_omega = {x: 2.6, y: 2.6, z: 0};
var target_distance = 1.50;
var zoom_in_pressed = false;
var zoom_out_pressed = false;
var last_spin_time = 0;
var draw_wireframe = false;

// called by flash file
function setAngle(e0, e4, e8, e12, e1, e5, e9, e13, e2, e6, e10, e14) {
	rotateObjectByMat(e0, e4, e8, e12, e1, e5, e9, e13, e2, e6, e10, e14);
}
 
function getTime() {
  return (new Date()).getTime();
}
 
var MIN_Z = 0.05;
 
// Return the point between two points, also bisect the texture coords.
function bisect(p0, p1) {
  var p = {x: (p0.x + p1.x) / 2,
           y: (p0.y + p1.y) / 2,
           z: (p0.z + p1.z) / 2,
           u: (p0.u + p1.u) / 2,
           v: (p0.v + p1.v) / 2};
  return p;
}
 
// for debugging
function drawPerspectiveTriUnclippedSubX(c3d, v0, tv0, v1, tv1, v2, tv2) {
  var ctx = c3d.canvas_ctx_;
  ctx.beginPath();
  ctx.moveTo(tv0.x, tv0.y);
  ctx.lineTo(tv1.x, tv1.y);
  ctx.lineTo(tv2.x, tv2.y);
  ctx.lineTo(tv0.x, tv0.y);
  ctx.stroke();
}
  
function drawPerspectiveTriUnclippedSub(c3d, v0, tv0, v1, tv1, v2, tv2, depth_count) {
  var edgelen01 =
    Math.abs(tv0.x - tv1.x) +
    Math.abs(tv0.y - tv1.y);
  var edgelen12 =
    Math.abs(tv1.x - tv2.x) +
    Math.abs(tv1.y - tv2.y);
  var edgelen20 =
    Math.abs(tv2.x - tv0.x) +
    Math.abs(tv2.y - tv0.y);
  var zdepth01 =
    Math.abs(v0.z - v1.z);
  var zdepth12 =
    Math.abs(v1.z - v2.z);
  var zdepth20 =
    Math.abs(v2.z - v0.z);
 
  var subdiv = ((edgelen01 * zdepth01 > options.subdivide_factor) ? 1 : 0) +
               ((edgelen12 * zdepth12 > options.subdivide_factor) ? 2 : 0) +
               ((edgelen20 * zdepth20 > options.subdivide_factor) ? 4 : 0);
 
  if (depth_count) {
    depth_count--;
    if (depth_count == 0) {
      subdiv = 0;
    } else {
      subdiv = 7;
    }
  }
 
  if (subdiv == 0) {
    if (draw_wireframe) {
      var ctx = c3d.canvas_ctx_;
      ctx.beginPath();
      ctx.moveTo(tv0.x, tv0.y);
      ctx.lineTo(tv1.x, tv1.y);
      ctx.lineTo(tv2.x, tv2.y);
      ctx.lineTo(tv0.x, tv0.y);
      ctx.stroke();
    } else {
      jsgl.drawTriangle(c3d.canvas_ctx_, images[0],
                        tv0.x, tv0.y,
                        tv1.x, tv1.y,
                        tv2.x, tv2.y,
                        v0.u, v0.v,
                        v1.u, v1.v,
                        v2.u, v2.v);
    }
    return;
  }
 
  // Need to subdivide.  This code could be more optimal, but I'm
  // trying to keep it reasonably short.
  var v01 = bisect(v0, v1);
  var tv01 = jsgl.projectPoint(v01);
  var v12 = bisect(v1, v2);
  var tv12 = jsgl.projectPoint(v12);
  var v20 = bisect(v2, v0);
  var tv20 = jsgl.projectPoint(v20);
 
  switch (subdiv) {
    case 1:
      // split along v01-v2
      drawPerspectiveTriUnclippedSub(c3d, v0, tv0, v01, tv01, v2, tv2);
      drawPerspectiveTriUnclippedSub(c3d, v01, tv01, v1, tv1, v2, tv2);
      break;
    case 2:
      // split along v0-v12
      drawPerspectiveTriUnclippedSub(c3d, v0, tv0, v1, tv1, v12, tv12);
      drawPerspectiveTriUnclippedSub(c3d, v0, tv0, v12, tv12, v2, tv2);
      break;
    case 3:
      // split along v01-v12
      drawPerspectiveTriUnclippedSub(c3d, v0, tv0, v01, tv01, v12, tv12);
      drawPerspectiveTriUnclippedSub(c3d, v0, tv0, v12, tv12, v2, tv2);
      drawPerspectiveTriUnclippedSub(c3d, v01, tv01, v1, tv1, v12, tv12);
      break;
    case 4:
      // split along v1-v20
      drawPerspectiveTriUnclippedSub(c3d, v0, tv0, v1, tv1, v20, tv20);
      drawPerspectiveTriUnclippedSub(c3d, v1, tv1, v2, tv2, v20, tv20);
      break;
    case 5:
      // split along v01-v20
      drawPerspectiveTriUnclippedSub(c3d, v0, tv0, v01, tv01, v20, tv20);
      drawPerspectiveTriUnclippedSub(c3d, v1, tv1, v2, tv2, v01, tv01);
      drawPerspectiveTriUnclippedSub(c3d, v2, tv2, v20, tv20, v01, tv01);
      break;
    case 6:
      // split along v12-v20
      drawPerspectiveTriUnclippedSub(c3d, v0, tv0, v1, tv1, v20, tv20);
      drawPerspectiveTriUnclippedSub(c3d, v1, tv1, v12, tv12, v20, tv20);
      drawPerspectiveTriUnclippedSub(c3d, v12, tv12, v2, tv2, v20, tv20);
      break;
    default:
    case 7:
      drawPerspectiveTriUnclippedSub(c3d, v0, tv0, v01, tv01, v20, tv20, depth_count);
      drawPerspectiveTriUnclippedSub(c3d, v1, tv1, v12, tv12, v01, tv01, depth_count);
      drawPerspectiveTriUnclippedSub(c3d, v2, tv2, v20, tv20, v12, tv12, depth_count);
      drawPerspectiveTriUnclippedSub(c3d, v01, tv01, v12, tv12, v20, tv20, depth_count);
      break;
  }
  return;
}
 
function drawPerspectiveTriUnclipped(c3d, v0, v1, v2, depth_count) {
	var tv0 = jsgl.projectPoint(v0);
	var tv1 = jsgl.projectPoint(v1);
	var tv2 = jsgl.projectPoint(v2);
	drawPerspectiveTriUnclippedSub(c3d, v0, tv0, v1, tv1, v2, tv2, depth_count);
}
 
// Given an edge that crosses the z==MIN_Z plane, return the
// intersection of the edge with z==MIN_Z.
function clip_line(v0, v1) {
  var f = (MIN_Z - v0.z) / (v1.z - v0.z);
  return {x: v0.x + (v1.x - v0.x) * f,
      y: v0.y + (v1.y - v0.y) * f,
      z: v0.z + (v1.z - v0.z) * f,
      u: v0.u + (v1.u - v0.u) * f,
      v: v0.v + (v1.v - v0.v) * f
  };
}
 


// Draw a perspective-corrected textured triangle, subdividing as
// necessary for clipping and texture mapping.
function drawPerspectiveTriConventionalClipping(c3d, v0, v1, v2) {
	var clip = ((v0.z < MIN_Z) ? 1 : 0) +
             ((v1.z < MIN_Z) ? 2 : 0) +
             ((v2.z < MIN_Z) ? 4 : 0);
	if (clip == 7) {
		// All verts are behind the near plane; don't draw.
		return;
	}
 
  if (clip != 0) {
    var v01, v12, v20;
    switch (clip) {
      case 1:
        v01 = clip_line(v0, v1);
        v20 = clip_line(v0, v2);
        drawPerspectiveTriUnclipped(c3d, v01, v1, v2);
        drawPerspectiveTriUnclipped(c3d, v01, v2, v20);
        break;
      case 2:
        v01 = clip_line(v1, v0);
        v12 = clip_line(v1, v2);
        drawPerspectiveTriUnclipped(c3d, v0, v01, v12);
        drawPerspectiveTriUnclipped(c3d, v0, v12, v2);
        break;
      case 3:
        v12 = clip_line(v1, v2);
        v20 = clip_line(v0, v2);
        drawPerspectiveTriUnclipped(c3d, v2, v20, v12);
        break;
      case 4:
        v12 = clip_line(v2, v1);
        v20 = clip_line(v2, v0);
        drawPerspectiveTriUnclipped(c3d, v0, v1, v12);
        drawPerspectiveTriUnclipped(c3d, v0, v12, v20);
        break;
      case 5:
        v01 = clip_line(v0, v1);
        v12 = clip_line(v2, v1);
        drawPerspectiveTriUnclipped(c3d, v1, v12, v01);
        break;
      case 6:
        v01 = clip_line(v0, v1);
        v20 = clip_line(v0, v2);
        drawPerspectiveTriUnclipped(c3d, v0, v01, v20);
        break;
    }
    return;
  }
 
  // No verts need clipping.
  drawPerspectiveTriUnclipped(c3d, v0, v1, v2);
}
 
// Draw a perspective-corrected textured triangle, subdividing as
// necessary for clipping and texture mapping.
//
// Unconventional clipping -- recursively subdivide, and drop whole tris on
// the wrong side of z clip plane.
function drawPerspectiveTri(c3d, v0, v1, v2, depth_count) {
  var clip = ((v0.z < MIN_Z) ? 1 : 0) +
             ((v1.z < MIN_Z) ? 2 : 0) +
             ((v2.z < MIN_Z) ? 4 : 0);
  if (clip == 0) {
    // No verts need clipping.
    drawPerspectiveTriUnclipped(c3d, v0, v1, v2, depth_count);
    return;
  }
  if (clip == 7) {
    // All verts are behind the near plane; don't draw.
    return;
  }
 
  var min_z2 = MIN_Z * 1.1;
  var clip2 = ((v0.z < min_z2) ? 1 : 0) +
              ((v1.z < min_z2) ? 2 : 0) +
              ((v2.z < min_z2) ? 4 : 0);
  if (clip2 == 7) {
    // All verts are behind the guard band, don't recurse.
    return;
  }
 
  var v01 = bisect(v0, v1);
  var v12 = bisect(v1, v2);
  var v20 = bisect(v2, v0);
 
  if (depth_count) {
    depth_count--;
  }
 
  if (1) {//xxxxxx
    drawPerspectiveTri(c3d, v0, v01, v20, depth_count);
    drawPerspectiveTri(c3d, v01, v1, v12, depth_count);
    drawPerspectiveTri(c3d, v12, v2, v20, depth_count);
    drawPerspectiveTri(c3d, v01, v12, v20, depth_count);
    return;
  }
  
  switch (clip) {
    case 1:
      drawPerspectiveTri(c3d, v01, v1, v2);
      drawPerspectiveTri(c3d, v01, v2, v20);
      drawPerspectiveTri(c3d, v0, v01, v20);
      break;
    case 2:
      drawPerspectiveTri(c3d, v0, v01, v12);
      drawPerspectiveTri(c3d, v0, v12, v2);
      drawPerspectiveTri(c3d, v1, v12, v01);
      break;
    case 3:
      drawPerspectiveTri(c3d, v2, v20, v12);
      drawPerspectiveTri(c3d, v0, v1, v12);
      drawPerspectiveTri(c3d, v0, v12, v20);
      break;
    case 4:
      drawPerspectiveTri(c3d, v0, v1, v12);
      drawPerspectiveTri(c3d, v0, v12, v20);
      drawPerspectiveTri(c3d, v12, v2, v20);
      break;
    case 5:
      drawPerspectiveTri(c3d, v1, v12, v01);
      drawPerspectiveTri(c3d, v0, v01, v12);
      drawPerspectiveTri(c3d, v0, v12, v2);
      break;
    case 6:
      drawPerspectiveTri(c3d, v0, v01, v20);
      drawPerspectiveTri(c3d, v01, v1, v2);
      drawPerspectiveTri(c3d, v01, v2, v20);
      break;
  }
}
 
function draw() {
  // Clear with white.
	var ctx = c3d.canvas_ctx_;
 
	ctx.globalAlpha = options.whiteout_alpha;
	ctx.fillStyle = '#000000';
	ctx.fillRect(0, 0, canvas_elem.width, canvas_elem.height);
	ctx.globalAlpha = 1;
 
	var view_mat = jsgl.makeViewFromOrientation(camera_mat);
 
	// Update transform.
	jsgl.multiplyAffineTo(proj_mat, view_mat, temp_mat0);
	jsgl.multiplyAffineTo(temp_mat0, object_mat, temp_mat1);
	c3d.setTransform(temp_mat1);
 
	// Draw.
	var im_width = images[0].width;
	var im_height = images[0].height;
	var verts = [
		{x:-1, y:-1, z: 0, u:0, v:0},
		{x: 1, y:-1, z: 0, u:im_width, v:0},
		{x: 1, y: 1, z: 0, u:im_width, v:im_height},
		{x:-1, y: 1, z: 0, u:0, v:im_height}
	];
	var tverts = [];
	for (var i = 0; i < verts.length; i++) {
		tverts.push(jsgl.transformPoint(c3d.transform_, verts[i]));
		tverts[i].u = verts[i].u;
		tverts[i].v = verts[i].v;
	}
 
	drawPerspectiveTri(c3d, tverts[0], tverts[1], tverts[2], options.nonadaptive_depth);
	drawPerspectiveTri(c3d, tverts[0], tverts[2], tverts[3], options.nonadaptive_depth);
 
	if (options.wireframe) {
		ctx.globalAlpha = 0.3;
		ctx.fillRect(0, 0, canvas_elem.width, canvas_elem.height);
		draw_wireframe = true;
		ctx.globalAlpha = 1;
		drawPerspectiveTri(c3d, tverts[0], tverts[1], tverts[2], options.nonadaptive_depth);
		drawPerspectiveTri(c3d, tverts[0], tverts[2], tverts[3], options.nonadaptive_depth);
		draw_wireframe = false;
	}
}

function rotateObjectByMat(e0, e4, e8, e12, e1, e5, e9, e13, e2, e6, e10, e14) {
	var mat = new jsgl.AffineMatrix(e0, e4, e8, 0, e1, e5, e9, 0, e2, e6, e10, 0);
	object_mat = mat;
	jsgl.orthonormalizeRotation(object_mat);
	draw();
}
  
function init() {
	canvas_elem = document.getElementById('canvas');
	var ctx = canvas_elem.getContext('2d');
 
	c3d = new jsgl.Context(ctx);
 
	temp_mat0 = jsgl.makeIdentityAffine();
	temp_mat1 = jsgl.makeIdentityAffine();
	temp_mat2 = jsgl.makeIdentityAffine();
	proj_mat = jsgl.makeWindowProjection(canvas_elem.width, canvas_elem.height, horizontal_fov_radians);
	object_mat = jsgl.makeOrientationAffine({x:0, y:0, z:0}, {x:1, y:0, z:0}, {x:0, y:1, z:0});
	camera_mat = jsgl.makeOrientationAffine({x:0, y:0, z: 0.2 + target_distance}, {x:0, y:0, z:-1}, {x:0, y:1, z:0});
 
	var texdim = images[0].width;
}
 
var loaded_count = 0;

function image_loaded() {
	loaded_count++;
	if (loaded_count == 1) {
		init();
	}
}
 
window.onload = function() {
	images.push(new Image());
	images[0].onload = image_loaded;
	images[0].src = 'images/cyborg.jpg';
}
