Thứ Tư, 20 tháng 1, 2016

Giả lập tàu lượn ba chiều trong vũ trụ

/*
     This script downloaded from www.JavaScriptBank.com
     Come to view and download over 2000+ free javascript at www.JavaScriptBank.com
*/
// Copyright 2006 Google Inc.
//
// 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.

// TODO: Patterns
// TODO: Radial gradient
// TODO: Clipping paths
// TODO: Coordsize
// TODO: Painting mode
// TODO: Optimize
// TODO: canvas width/height sets content size in moz, border size in ie
// TODO: Painting outside the canvas should not be allowed

// only add this code if we do not already have a canvas implementation
if (!window.CanvasRenderingContext2D) {

(function () {
  alert("You appear to be using Microsoft Internet Explorer.\nUnfortunately, it doesn't support some required features,\nso they're being emulated. As a result, you may notice\nSIGNIFICANT slowness (think Powerpoint presentation) and missing features.");
  var G_vmlCanvasManager_ = {
    init: function (opt_doc) {
      var doc = opt_doc || document;
      if (/MSIE/.test(navigator.userAgent) && !window.opera) {
        var self = this;
        doc.attachEvent("onreadystatechange", function () {
          self.init_(doc);
        });
      }
    },

    init_: function (doc, e) {
      if (doc.readyState == "complete") {
        // create xmlns
        if (!doc.namespaces["g_vml_"]) {
          doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
        }

        // setup default css
        var ss = doc.createStyleSheet();
        ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
            "text-align:left;}" +
            "canvas *{behavior:url(#default#VML)}";

        // find all canvas elements
        var els = doc.getElementsByTagName("canvas");
        for (var i = 0; i < els.length; i++) {
          if (!els[i].getContext) {
            this.initElement(els[i]);
          }
        }
      }
    },

    fixElement_: function (el) {
      // in IE before version 5.5 we would need to add HTML: to the tag name
      // but we do not care about IE before version 6
      var outerHTML = el.outerHTML;
      var newEl = document.createElement(outerHTML);
      // if the tag is still open IE has created the children as siblings and
      // it has also created a tag with the name "/FOO"
      if (outerHTML.slice(-2) != "/>") {
        var tagName = "/" + el.tagName;
        var ns;
        // remove content
        while ((ns = el.nextSibling) && ns.tagName != tagName) {
          ns.removeNode();
        }
        // remove the incorrect closing tag
        if (ns) {
          ns.removeNode();
        }
      }
      el.parentNode.replaceChild(newEl, el);
      return newEl;
    },

    /**
     * Public initializes a canvas element so that it can be used as canvas
     * element from now on. This is called automatically before the page is
     * loaded but if you are creating elements using createElement yuo need to
     * make sure this is called on the element.
     * @param el {HTMLElement} The canvas element to initialize.
     */
    initElement: function (el) {
      el = this.fixElement_(el);
      el.getContext = function () {
        if (this.context_) {
          return this.context_;
        }
        return this.context_ = new CanvasRenderingContext2D_(this);
      };

      var self = this; //bind
      el.attachEvent("onpropertychange", function (e) {
        // we need to watch changes to width and height
        switch (e.propertyName) {
          case "width":
          case "height":
            // coord size changed?
            break;
        }
      });

      // if style.height is set

      var attrs = el.attributes;
      if (attrs.width && attrs.width.specified) {
        // TODO: use runtimeStyle and coordsize
        // el.getContext().setWidth_(attrs.width.nodeValue);
        el.style.width = attrs.width.nodeValue + "px";
      }
      if (attrs.height && attrs.height.specified) {
        // TODO: use runtimeStyle and coordsize
        // el.getContext().setHeight_(attrs.height.nodeValue);
        el.style.height = attrs.height.nodeValue + "px";
      }
      //el.getContext().setCoordsize_()
    }
  };

  G_vmlCanvasManager_.init();

  // precompute "00" to "FF"
  var dec2hex = [];
  for (var i = 0; i < 16; i++) {
    for (var j = 0; j < 16; j++) {
      dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
    }
  }

  function createMatrixIdentity() {
    return [
      [1, 0, 0],
      [0, 1, 0],
      [0, 0, 1]
    ];
  }

  function matrixMultiply(m1, m2) {
    var result = createMatrixIdentity();

    for (var x = 0; x < 3; x++) {
      for (var y = 0; y < 3; y++) {
        var sum = 0;

        for (var z = 0; z < 3; z++) {
          sum += m1[x][z] * m2[z][y];
        }

        result[x][y] = sum;
      }
    }
    return result;
  }

  function copyState(o1, o2) {
    o2.fillStyle     = o1.fillStyle;
    o2.lineCap       = o1.lineCap;
    o2.lineJoin      = o1.lineJoin;
    o2.lineWidth     = o1.lineWidth;
    o2.miterLimit    = o1.miterLimit;
    o2.shadowBlur    = o1.shadowBlur;
    o2.shadowColor   = o1.shadowColor;
    o2.shadowOffsetX = o1.shadowOffsetX;
    o2.shadowOffsetY = o1.shadowOffsetY;
    o2.strokeStyle   = o1.strokeStyle;
  }

  function processStyle(styleString) {
    var str, alpha = 1;

    styleString = String(styleString);
    if (styleString.substring(0, 3) == "rgb") {
      var start = styleString.indexOf("(", 3);
      var end = styleString.indexOf(")", start + 1);
      var guts = styleString.substring(start + 1, end).split(",");

      str = "#";
      for (var i = 0; i < 3; i++) {
        str += dec2hex[parseInt(guts[i])];
      }

      if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
        alpha = guts[3];
      }
    } else {
      str = styleString;
    }

    return [str, alpha];
  }

  function processLineCap(lineCap) {
    switch (lineCap) {
      case "butt":
        return "flat";
      case "round":
        return "round";
      case "square":
      default:
        return "square";
    }
  }

  /**
   * This class implements CanvasRenderingContext2D interface as described by
   * the WHATWG.
   * @param surfaceElement {HTMLElement} The element that the 2D context should
   * be associated with
   */
   function CanvasRenderingContext2D_(surfaceElement) {
    this.m_ = createMatrixIdentity();
    this.element_ = surfaceElement;

    this.mStack_ = [];
    this.aStack_ = [];
    this.currentPath_ = [];

    // Canvas context properties
    this.strokeStyle = "#000";
    this.fillStyle = "#ccc";

    this.lineWidth = 1;
    this.lineJoin = "miter";
    this.lineCap = "butt";
    this.miterLimit = 10;
    this.globalAlpha = 1;
  };

  var contextPrototype = CanvasRenderingContext2D_.prototype;
  contextPrototype.clearRect = function() {
    this.element_.innerHTML = "";
    this.currentPath_ = [];
  };

  contextPrototype.beginPath = function() {
    // TODO: Branch current matrix so that save/restore has no effect
    //       as per safari docs.

    this.currentPath_ = [];
  };

  contextPrototype.moveTo = function(aX, aY) {
    this.currentPath_.push({type: "moveTo", x: aX, y: aY});
  };

  contextPrototype.lineTo = function(aX, aY) {
    this.currentPath_.push({type: "lineTo", x: aX, y: aY});
  };

  contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
                                            aCP2x, aCP2y,
                                            aX, aY) {
    this.currentPath_.push({type: "bezierCurveTo",
                           cp1x: aCP1x,
                           cp1y: aCP1y,
                           cp2x: aCP2x,
                           cp2y: aCP2y,
                           x: aX,
                           y: aY});
  };

  contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
    // VML's qb produces different output to Firefox's
    // FF's behaviour seems to have changed in 1.5.0.1, check this
    this.bezierCurveTo(aCPx, aCPy, aCPx, aCPy, aX, aY);
  };

  contextPrototype.arc = function(aX, aY, aRadius,
                                  aStartAngle, aEndAngle, aClockwise) {
    if (!aClockwise) {
      var t = aStartAngle;
      aStartAngle = aEndAngle;
      aEndAngle = t;
    }

    var xStart = aX + (Math.cos(aStartAngle) * aRadius);
    var yStart = aY + (Math.sin(aStartAngle) * aRadius);

    var xEnd = aX + (Math.cos(aEndAngle) * aRadius);
    var yEnd = aY + (Math.sin(aEndAngle) * aRadius);

    this.currentPath_.push({type: "arc",
                           x: aX,
                           y: aY,
                           radius: aRadius,
                           xStart: xStart,
                           yStart: yStart,
                           xEnd: xEnd,
                           yEnd: yEnd});

  };

  contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
    this.moveTo(aX, aY);
    this.lineTo(aX + aWidth, aY);
    this.lineTo(aX + aWidth, aY + aHeight);
    this.lineTo(aX, aY + aHeight);
    this.closePath();
  };

  contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
    // Will destroy any existing path (same as FF behaviour)
    this.beginPath();
    this.moveTo(aX, aY);
    this.lineTo(aX + aWidth, aY);
    this.lineTo(aX + aWidth, aY + aHeight);
    this.lineTo(aX, aY + aHeight);
    this.closePath();
    this.stroke();
  };

  contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
    // Will destroy any existing path (same as FF behaviour)
    this.beginPath();
    this.moveTo(aX, aY);
    this.lineTo(aX + aWidth, aY);
    this.lineTo(aX + aWidth, aY + aHeight);
    this.lineTo(aX, aY + aHeight);
    this.closePath();
    this.fill();
  };

  contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
    var gradient = new CanvasGradient_("gradient");
    return gradient;
  };

  contextPrototype.createRadialGradient = function(aX0, aY0,
                                                   aR0, aX1,
                                                   aY1, aR1) {
    var gradient = new CanvasGradient_("gradientradial");
    gradient.radius1_ = aR0;
    gradient.radius2_ = aR1;
    gradient.focus_.x = aX0;
    gradient.focus_.y = aY0;
    return gradient;
  };

  contextPrototype.drawImage = function (image, var_args) {
    var dx, dy, dw, dh, sx, sy, sw, sh;
    var w = image.width;
    var h = image.height;

    if (arguments.length == 3) {
      dx = arguments[1];
      dy = arguments[2];
      sx = sy = 0;
      sw = dw = w;
      sh = dh = h;
    } else if (arguments.length == 5) {
      dx = arguments[1];
      dy = arguments[2];
      dw = arguments[3];
      dh = arguments[4];
      sx = sy = 0;
      sw = w;
      sh = h;
    } else if (arguments.length == 9) {
      sx = arguments[1];
      sy = arguments[2];
      sw = arguments[3];
      sh = arguments[4];
      dx = arguments[5];
      dy = arguments[6];
      dw = arguments[7];
      dh = arguments[8];
    } else {
      throw "Invalid number of arguments";
    }

    var d = this.getCoords_(dx, dy);

    var w2 = (sw / 2);
    var h2 = (sh / 2);

    var vmlStr = [];

    // For some reason that I've now forgotten, using divs didn't work
    vmlStr.push(' ' ,
                '',
                '');

    this.element_.insertAdjacentHTML("BeforeEnd",
                                    vmlStr.join(""));
  };

  contextPrototype.stroke = function(aFill) {
    var lineStr = [];
    var lineOpen = false;
    var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
    var color = a[0];
    var opacity = a[1] * this.globalAlpha;

    lineStr.push(' max.x) {
          max.x = c.x;
        }
        if (min.y == null || c.y < min.y) {
          min.y = c.y;
        }
        if (max.y == null || c.y > max.y) {
          max.y = c.y;
        }
      }
    }
    lineStr.push(' ">');

    if (typeof this.fillStyle == "object") {
      var focus = {x: "50%", y: "50%"};
      var width = (max.x - min.x);
      var height = (max.y - min.y);
      var dimension = (width > height) ? width : height;

      focus.x = Math.floor((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
      focus.y = Math.floor((this.fillStyle.focus_.y / height) * 100 + 50) + "%";

      var colors = [];

      // inside radius (%)
      if (this.fillStyle.type_ == "gradientradial") {
        var inside = (this.fillStyle.radius1_ / dimension * 100);

        // percentage that outside radius exceeds inside radius
        var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
      } else {
        var inside = 0;
        var expansion = 100;
      }

      var insidecolor = {offset: null, color: null};
      var outsidecolor = {offset: null, color: null};

      // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie
      // won't interpret it correctly
      this.fillStyle.colors_.sort(function (cs1, cs2) {
        return cs1.offset - cs2.offset;
      });

      for (var i = 0; i < this.fillStyle.colors_.length; i++) {
        var fs = this.fillStyle.colors_[i];

        colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");

        if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
          insidecolor.offset = fs.offset;
          insidecolor.color = fs.color;
        }

        if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
          outsidecolor.offset = fs.offset;
          outsidecolor.color = fs.color;
        }
      }
      colors.pop();

      lineStr.push('');
    } else if (aFill) {
      lineStr.push('');
    } else {
      lineStr.push(
        ''
      );
    }

    lineStr.push("");

    this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));

    this.currentPath_ = [];
  };

  contextPrototype.fill = function() {
    this.stroke(true);
  }

  contextPrototype.closePath = function() {
    this.currentPath_.push({type: "close"});
  };

  /**
   * @private
   */
  contextPrototype.getCoords_ = function(aX, aY) {
    return {
      x: (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]),
      y: (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1])
    }
  };

  contextPrototype.save = function() {
    var o = {};
    copyState(this, o);
    this.aStack_.push(o);
    this.mStack_.push(this.m_);
    this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
  };

  contextPrototype.restore = function() {
    copyState(this.aStack_.pop(), this);
    this.m_ = this.mStack_.pop();
  };

  contextPrototype.translate = function(aX, aY) {
    var m1 = [
      [1,  0,  0],
      [0,  1,  0],
      [aX, aY, 1]
    ];

    this.m_ = matrixMultiply(m1, this.m_);
  };

  contextPrototype.rotate = function(aRot) {
    var c = Math.cos(aRot);
    var s = Math.sin(aRot);

    var m1 = [
      [c,  s, 0],
      [-s, c, 0],
      [0,  0, 1]
    ];

    this.m_ = matrixMultiply(m1, this.m_);
  };

  contextPrototype.scale = function(aX, aY) {
    var m1 = [
      [aX, 0,  0],
      [0,  aY, 0],
      [0,  0,  1]
    ];

    this.m_ = matrixMultiply(m1, this.m_);
  };

  /******** STUBS ********/
  contextPrototype.clip = function() {
    // TODO: Implement
  };

  contextPrototype.arcTo = function() {
    // TODO: Implement
  };

  contextPrototype.createPattern = function() {
    return new CanvasPattern_;
  };

  // Gradient / Pattern Stubs
  function CanvasGradient_(aType) {
    this.type_ = aType;
    this.radius1_ = 0;
    this.radius2_ = 0;
    this.colors_ = [];
    this.focus_ = {x: 0, y: 0};
  }

  CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
    aColor = processStyle(aColor);
    this.colors_.push({offset: 1-aOffset, color: aColor});
  };

  function CanvasPattern_() {}

  // set up externs
  G_vmlCanvasManager = G_vmlCanvasManager_;
  CanvasRenderingContext2D = CanvasRenderingContext2D_;
  CanvasGradient = CanvasGradient_;
  CanvasPattern = CanvasPattern_;

})();

} // if






















/*
     This script downloaded from www.JavaScriptBank.com
     Come to view and download over 2000+ free javascript at www.JavaScriptBank.com
*/
// Copyright Christopher Thomas 2007
// You are free to use this code under the terms of
// the Creative Commons Attribution-ShareAlike 2.5 license

var WIDTH;
var HEIGHT;

var DRAW_GROUND = false;
var DRAW_LINE = false;
var DRAW_THRESHOLD = 2;
var DRAW_BACK_WALL = true;
var DRAW_TRIS = false;
var DRAW_TIE = true;
var DRAW_STARFIELD = true;

var TIE_DIST = 15;

var NEAR_CLIP = .01;

var canvas;
var ctx;
var points;
var NUMPOINTS = 20;
var stars;
var NUMSTARS = 100;

var timer = null;

var stopTime = false;
var time;
var camSteps = 40;
var SCALE = 250;

var interpPoints = 5;

var alpha = true;

var prevTime = new Date().getSeconds();
var frameCount = 0;

var FPS;

var prevRealTime = new Date().getTime();

function go() {
  FPS = document.getElementById("fps");
  if (timer)
    clearInterval(timer);

  canvas = document.getElementById("theCanvas");
  HEIGHT = canvas.getAttribute("height");
  WIDTH = canvas.getAttribute("width");
  ctx = canvas.getContext("2d");
  time = -1;
  points = [];

  for (var i=0; i NEAR_CLIP && p1.z > NEAR_CLIP && p2.z > NEAR_CLIP) {
    // All 3 points are in front of the camera
    ctx.beginPath();
    ctx.moveTo(p0.x/p0.z, p0.y/p0.z);
    ctx.lineTo(p1.x/p1.z, p1.y/p1.z);
    ctx.lineTo(p2.x/p2.z, p2.y/p2.z);
    ctx.lineTo(p0.x/p0.z, p0.y/p0.z);
    ctx.fill();
  }
  else if (p0.z <= NEAR_CLIP && p1.z <= NEAR_CLIP && p2.z <= NEAR_CLIP) {
    // All 3 points are behind the camera
  }
  else if ((p0.z > NEAR_CLIP) ^ (p1.z > NEAR_CLIP) ^ (p2.z > NEAR_CLIP)) {
    // Exactly two points are behind the camera
    if (p1.z > NEAR_CLIP) {
      tmpPoint = p1;
      p1 = p0;
      p0 = tmpPoint;
    }
    if (p2.z > NEAR_CLIP) {
      tmpPoint = p2;
      p2 = p0;
      p0 = tmpPoint;
    }
     // p0 is definitely the one in front of the camera
    var points = clip1(p0, p1, p2, debug);
    var first = {x: points[0].x/points[0].z, y: points[0].y/points[0].z};
    var second = {x: p0.x / p0.z, y: p0.y / p0.z};
    var third = {x: points[3].x/points[3].z, y: points[3].y/points[3].z};
    first = fixup2(first, second, debug);
    third = fixup2(third, second, debug);
    ctx.beginPath();
    ctx.moveTo(first.x, first.y);
    ctx.lineTo(second.x, second.y);
    ctx.lineTo(third.x, third.y);
    ctx.fill();
  }
  else {
    // Exactly one point is behind the camera
    if (p1.z <= NEAR_CLIP) {
      tmpPoint = p1;
      p1 = p0;
      p0 = tmpPoint;
    }
    if (p2.z <= NEAR_CLIP) {
      tmpPoint = p2;
      p2 = p0;
      p0 = tmpPoint;
    }
    // p0 is definitely the one behind the camera
    var points = clip1(p0, p1, p2, debug);
    var first =  {x: points[0].x/points[0].z, y: points[0].y/points[0].z};
    var second = {x: points[1].x/points[1].z, y: points[1].y/points[1].z};
    var third =  {x: points[2].x/points[2].z, y: points[2].y/points[2].z};
    var fourth = {x: points[3].x/points[3].z, y: points[3].y/points[3].z};
    first = fixup2(first, second, debug);
    fourth = fixup2(fourth, third, debug);
    ctx.beginPath();
    ctx.moveTo(first.x, first.y);
    ctx.lineTo(second.x, second.y);
    ctx.lineTo(third.x, third.y);
    ctx.lineTo(fourth.x, fourth.y);
    // FIXME TODO do I need to go back to first?
    //ctx.lineTo(first.x, first.y);
    ctx.fill();
  }
}

// F*cking canvas uses 16-bit coordinates.
// x/z or y/z can be very big when z is small.
// clip1 likes to clip to a small z.
// the coordinates can get very big.  16 bit
// numbers are not big.
// this function pulls offscreen points to be closer
// to being onscreen so that their coords are not big.
function fixup2(p0, p1, debug) {
  var STEP_DIST = WIDTH/SCALE;

  // I could verify that p0 is far offscreen first
  // but it looks like it's almost never close to
  // being onscreen (so it's not worth optimizing)

  // assume p1 is reasonably onscreen
  // pick a point that's almost onscreen
  // along the line [p0 -> p1]
  var dx = p1.x - p0.x;
  var dy = p1.y - p0.y;

  /*
   This is not perfectly accurate.
   Consider this case:

    -------------
    |           |
    |           |
   a| b         |
    |    c      |
    -------------
       d

   I actually project d up and to the right, which makes
   the c-d line good, but breaks the a-d line.  STEP_DIST
   is used to (hopefully) keep a and d far enough off-screen
   so that the a-d line doesn't get pulled on-screen when it
   shouldn't be.      
  */  

  // Phil prefers I use trig to my leet algebra
  var theta = Math.atan2(dy, dx);
  var newx = p1.x - STEP_DIST*Math.cos(theta);
  var newy = p1.y - STEP_DIST*Math.sin(theta);

  var rv = {x: newx, y: newy};

  if (debug)
    alertd("p0: " + debugp(p0) + "\n" +
	   "p1: " + debugp(p1) + "\n" +
	   "new: " + debugp(rv));

  return rv;
}

// p0 must be behind the camera
// p1 and p2 are in front of the camera
function clip1(p0, p1, p2, debug) {
  var x1, y1, x2, y2;
  var deltap = NEAR_CLIP - p0.z;

  var deltaz1 = p1.z - p0.z;
  var portion = deltap / deltaz1;
  x1 = (p1.x - p0.x) * portion + p0.x;
  y1 = (p1.y - p0.y) * portion + p0.y;
  var deltaz2 = p2.z - p0.z;
  var portion2 = deltap / deltaz2;
  x2 = (p2.x - p0.x) * portion2 + p0.x;
  y2 = (p2.y - p0.y) * portion2 + p0.y;

  return [{x: x1, y: y1, z: NEAR_CLIP},
	  p1, p2,
	  {x: x2, y: y2, z: NEAR_CLIP}];
}

// p0 must be in front of the camera
function clip2(p0, p1, p2, debug) {
  var x1, y1, x2, y2;
  var deltap = p0.z - NEAR_CLIP;

  var deltaz1 = p0.z - p1.z;
  var portion = deltap / deltaz1;
  x1 = (p1.x - p0.x) * portion + p0.x;
  y1 = (p1.y - p0.y) * portion + p0.y;
  var deltaz2 = p0.z - p2.z;
  var portion2 = deltap / deltaz2;
  x2 = (p2.x - p0.x) * portion2 + p0.x;
  y2 = (p2.y - p0.y) * portion2 + p0.y;

  if (debug)
    alertd("points: \n" + 
	   debugp(p0) + "\n" +
	   debugp(p1) + "\n" +
	   debugp(p2) + "\n" +
	   "New points: \n" +
	   debugp({x: x1, y: y1, z: NEAR_CLIP}) + "\n" + 
	   debugp({x: x2, y: y2, z: NEAR_CLIP}));

  return [p0,
	  {x: x1, y: y1, z: NEAR_CLIP},
	  {x: x2, y: y2, z: NEAR_CLIP}];
}

function paintGround(mat, debug) {
  var verts = [[-40, 30, 0],
	       [-40, 30, NUMPOINTS*5],
	       [40, 30, 0],
	       [40, 30, NUMPOINTS*5]];
  var tris = [[0, 1, 2],
	      [2, 3, 1]];
  for (var i = 0; i= NEAR_CLIP)
	ctx.fillRect(star.x/star.z, star.y/star.z, 1/SCALE, 1/SCALE);
    }
  }

  // paint the ground
  if (DRAW_GROUND) {
    paintGround(mat, debug);
  }

  if (DRAW_BACK_WALL) {
    // paint the back wall
    paintBackWall(mat, debug);
  }

  var drewTie = false;
  var tieZ = 0;
  if (DRAW_TIE) {
    tieZ = transform(tie.state.point, mat).z;
  }

  try {
    // save the interpolation if there are no triangles and no line
    if (DRAW_TRIS || DRAW_LINE) {
      for (var seg = points.length - 4; seg >= 0; seg--) {
	var p0 = points[seg];
	var p1 = points[seg+1];
	var p2 = points[seg+2];
	var p3 = points[seg+3];
	var tp = transform(p3, mat);

	// This sometimes clips segments it shouldn't
	// when following a sharply-curved track
	if (tp.z > NEAR_CLIP) {
	  var lastX;
	  var lastY;
	  var firstPoint = true;
	  for (var i=interpPoints; i>=0; i--) {
	    var p = interpolate(p0, p1, p2, p3, i/interpPoints);
	    var tp = transform(p, mat);

	    if (DRAW_TIE && !drewTie && tp.z < tieZ) {
	      paintTie(mat, tie, debug);
	      drewTie = true;
	    }


	    var x = tp.x/tp.z; // potential divide by 0
	    var y = tp.y/tp.z;
	    if (firstPoint && tp.z > NEAR_CLIP) {
	      lastX = tp.x/tp.z;
	      lastY = tp.y/tp.z;
	      firstPoint = false;
	    }

	    if (tp.z > NEAR_CLIP && DRAW_LINE) {
	      if (alpha)
		ctx.strokeStyle = "rgba(255, 0, 0, "+ Math.max(0, Math.min((tp.z/10), 1)) +")";
	      else
		ctx.strokeStyle = "rgb(255, 0, 0)";
	      ctx.beginPath();
	      ctx.moveTo(lastX, lastY);
	      ctx.lineTo(x, y);
	      ctx.stroke();
	      lastX = tp.x/tp.z;
	      lastY = tp.y/tp.z;
	    }

	    if (DRAW_TRIS) {
	      var sizeb = 1/tp.z;
	      if (i == interpPoints) {
		if (alpha)
		  ctx.fillStyle = "rgba(0, 128, 0, 0.75)";
		else
		  ctx.fillStyle = "rgb(0, 128, 0)";
	      }
	      else {
		if (alpha)
		  ctx.fillStyle = "rgba(0, 0, 255, 0.25)";
		else
		  ctx.fillStyle = "rgb(0, 0, 255)";
	      }
	      // the 1/tp.z check is to avoid drawing really tiny things
	      if (tp.z > NEAR_CLIP && 1/tp.z > DRAW_THRESHOLD/SCALE) {
		var ptop = {x: p.x, y: p.y-.25, z: p.z};
		var ptopt = transform(ptop, mat);
		var pleft = {x: p.x-.25, y: p.y+.1, z: p.z};
		var pleftt = transform(pleft, mat);
		var pright = {x: p.x+.25, y: p.y+.1, z: p.z};
		var prightt = transform(pright, mat);
		drawTri(ptopt, pleftt, prightt, debug);
	      }
	    }
	  }
	}
      }
    }
    if (DRAW_TIE && !drewTie)
      paintTie(mat, tie, debug);

  } catch (e) {
    clearInterval(timer);
    alert("Caught an error!\n" + e);
  }

  ctx.restore();
}

function normalize(v) {
  if ("z" in v) {
    var l = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
    return {x: v.x / l, y: v.y / l, z: v.z / l};
  }
  else if ("y" in v) {
    var l = Math.sqrt(v.x * v.x + v.y * v.y);
    return {x: v.x / l, y: v.y / l};
  }
  else {
    window.status = "garbage passed to normalize!";
    return {x: 0, y: 0, z: 0};
  }
}

function cross(v1, v2) {
    var x = v1.y*v2.z - v1.z*v2.y;
    var y = v1.z*v2.x - v1.x*v2.z;
    var z = v1.x*v2.y - v1.y*v2.x;
    return {x: x, y: y, z: z};
}

function debugp(vec) {
  if ("z" in vec)
    return Math.round(vec.x*100)/100 + ", " +
    Math.round(vec.y*100)/100 + ", " +
    Math.round(vec.z*100)/100;
  return Math.round(vec.x*100)/100 + ", " +
    Math.round(vec.y*100)/100;
}

function debugp2(mat) {
  var str = "";
  for (var i=0; i= 0) {
    cameraPoint3 = interpolate(points[cameraSeg3],
			       points[cameraSeg3+1],
			       points[cameraSeg3+2],
			       points[cameraSeg3+3],
			       cameraLocalTime3 / camSteps);
  }
  else {
    cameraPoint3 = cameraPoint;
  }

  var backward = {x: cameraPoint.x - cameraPoint3.x,
		  y: cameraPoint.y - cameraPoint3.y,
		  z: cameraPoint.z - cameraPoint3.z};
  // normalize backward vector;
  backward = normalize(forward);


  // force up vector to be up
  //var up = {x: 0, y: -1, z: 0};
  // lean into turns by looking at the horizontal acceleration
  var ddx = (forward.x + backward.x)*.5;
  var up = normalize({x: ddx, y: -1, z: 0});
  var right = normalize(cross(forward, up));

  // make up perpendicular to forward (does this hack work?)
  up = normalize(cross(right, forward));

  return { point: cameraPoint,
      forward: forward,
      up: up,
      right: right };
}

function update(debug) {
  if (!stopTime) {
    // IMPORTANT: MUST BE PRECISELY REPRESENTED AS "double"!!!! (or must it?)
    var newRealTime = new Date().getTime();
    var deltaTime = newRealTime - prevRealTime;
    prevRealTime = newRealTime;
    var timeIncr = deltaTime / 1000 * 50;
    time += timeIncr / 2.5;
  }


  // we could do points.length - 3, but run into trouble stepping ahead to find "forward"
  // since the point that's "forward" could run over the end.  using - 4 is an easy fix
  time = time % (camSteps * (points.length - 4));

  // Get our position and local axes
  var state = getPointAndAxesAtTime(time);
  var cameraPoint = state.point;
  var forward = state.forward;
  var up = state.up;
  var right = state.right;

  // Get the TIE Fighter's position and matrix
  var tie = getTIE(time);

  var mat = buildMatrix(state, false);

  paint(mat, cameraPoint, debug, tie);
}

function alertd(str) {
  if (document.getElementById("status"))
    document.getElementById("status").innerHTML += str + "\n\n";
}

window.onload = go;

0 nhận xét: