3D Example – Ajax Layer – JavaScript Syntax – JS 3D Example

3D Example – Ajax Layer – JavaScript Syntax – JS 3D Example

Purpose: – Illustrates the for the example.
Prerequistes:

  • Install Visual Web Developer 2008 – We will use Visual Web Developer as the development environment. This will allow us to build a web site of JavaScript Syntax Examples.

Function: 3D Demonstrates using Explorer Canvas to draw 3D cube on the page.

Restrictions: None

Notes:

  • You can build your own library of syntax examples by using same web site over and over and just add new files to it.

Instructions:

  1. Use Visual Web Developer 2008
  2. Create new web site;
    • Click File/New Web Site
    • Select ASP.NET Website Template
    • Select C-Sharp for Language
    • name of Web Site could be JavaScript_Syntax.
  3. Add New folder named “AjaxLayer”
    • Right-click project name in solution explorer;
    • add new folder;
    • name of folder could be: AjaxLayer
  4. Add HTML Page Named 3D to AjaxLayer folder
    • Right-click AjaxLayer folder;
    • add new item;
    • Select HTML Page
    • HTML Page could be 3D
  5. Click on copy code in code below to copy code into HTML Page 3D.htm
  6. Right-click on HTML page 3D and select view in browser
  7. View Example In Browser

    <!--
      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.
    -->
    <html>
    <head>
      <title>ExplorerCanvas Example 1</title>
      <script type="text/javascript">
    /*
    ExplorerCanvas
    Copyright 2006 Google Inc.
     
    -------------------------------------------------------------------------------
    DESCRIPTION
     
    Firefox, Safari and Opera 9 support the canvas tag to allow 2D command-based 
    drawing operations. ExplorerCanvas brings the same functionality to Internet 
    Explorer; web developers only need to include a single script tag in their 
    existing canvas webpages to enable this support.
     
     
     
    Google Open Source:
      <http ://code.google.com>
      <opensource @google.com> 
     
    Developers:
      Emil A Eklund <emil @eae.net>
      Erik Arvidsson <erik @eae.net>
      Glen Murphy <glen @glenmurphy.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.
     
     
    // Known Issues:
    //
    // * Patterns are not implemented.
    // * Radial gradient are not implemented. The VML version of these look very
    //   different from the canvas one.
    // * Clipping paths are not implemented.
    // * Coordsize. The width and height attribute have higher priority than the
    //   width and height style values which isn't correct.
    // * Painting mode isn't implemented.
    // * Canvas width/height should is using content-box by default. IE in
    //   Quirks mode will draw the canvas using border-box. Either change your
    //   doctype to HTML5
    //   (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
    //   or use Box Sizing Behavior from WebFX
    //   (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
    // * Optimize. There is always room for speed improvements.
     
    // only add this code if we do not already have a canvas implementation
    if (!window.CanvasRenderingContext2D) {
     
    (function () {
     
      // alias some functions to make (compiled) code shorter
      var m = Math;
      var mr = m.round;
      var ms = m.sin;
      var mc = m.cos;
     
      // this is used for sub pixel precision
      var Z = 10;
      var Z2 = Z / 2;
     
      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) {
          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;" +
                // default size is 300x150 in Gecko and Opera
                "text-align:left;width:300px;height:150px}" +
                "g_vml_\\:*{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 = el.ownerDocument.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 you need to
         * make sure this is called on the element.
         * @param {HTMLElement} el The canvas element to initialize.
         * @return {HTMLElement} the element that was created.
         */
        initElement: function (el) {
          el = this.fixElement_(el);
          el.getContext = function () {
            if (this.context_) {
              return this.context_;
            }
            return this.context_ = new CanvasRenderingContext2D_(this);
          };
     
          // do not use inline function because that will leak memory
          el.attachEvent('onpropertychange', onPropertyChange);
          el.attachEvent('onresize', onResize);
     
          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";
          } else {
            el.width = el.clientWidth;
          }
          if (attrs.height && attrs.height.specified) {
            // TODO: use runtimeStyle and coordsize
            // el.getContext().setHeight_(attrs.height.nodeValue);
            el.style.height = attrs.height.nodeValue + "px";
          } else {
            el.height = el.clientHeight;
          }
          //el.getContext().setCoordsize_()
          return el;
        }
      };
     
      function onPropertyChange(e) {
        var el = e.srcElement;
     
        switch (e.propertyName) {
          case 'width':
            el.style.width = el.attributes.width.nodeValue + "px";
            el.getContext().clearRect();
            break;
          case 'height':
            el.style.height = el.attributes.height.nodeValue + "px";
            el.getContext().clearRect();
            break;
        }
      }
     
      function onResize(e) {
        var el = e.srcElement;
        if (el.firstChild) {
          el.firstChild.style.width =  el.clientWidth + 'px';
          el.firstChild.style.height = el.clientHeight + 'px';
        }
      }
     
      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;
        o2.arcScaleX_    = o1.arcScaleX_;
        o2.arcScaleY_    = o1.arcScaleY_;
      }
     
      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[Number(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 {HTMLElement} surfaceElement The element that the 2D context should
       * be associated with
       */
       function CanvasRenderingContext2D_(surfaceElement) {
        this.m_ = createMatrixIdentity();
     
        this.mStack_ = [];
        this.aStack_ = [];
        this.currentPath_ = [];
     
        // Canvas context properties
        this.strokeStyle = "#000";
        this.fillStyle = "#000";
     
        this.lineWidth = 1;
        this.lineJoin = "miter";
        this.lineCap = "butt";
        this.miterLimit = Z * 1;
        this.globalAlpha = 1;
        this.canvas = surfaceElement;
     
        var el = surfaceElement.ownerDocument.createElement('div');
        el.style.width =  surfaceElement.clientWidth + 'px';
        el.style.height = surfaceElement.clientHeight + 'px';
        el.style.overflow = 'hidden';
        el.style.position = 'absolute';
        surfaceElement.appendChild(el);
     
        this.element_ = el;
        this.arcScaleX_ = 1;
        this.arcScaleY_ = 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});
        this.currentX_ = aX;
        this.currentY_ = aY;
      };
     
      contextPrototype.lineTo = function(aX, aY) {
        this.currentPath_.push({type: "lineTo", x: aX, y: aY});
        this.currentX_ = aX;
        this.currentY_ = 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});
        this.currentX_ = aX;
        this.currentY_ = aY;
      };
     
      contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
        // the following is lifted almost directly from
        // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
        var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_);
        var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_);
        var cp2x = cp1x + (aX - this.currentX_) / 3.0;
        var cp2y = cp1y + (aY - this.currentY_) / 3.0;
        this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY);
      };
     
      contextPrototype.arc = function(aX, aY, aRadius,
                                      aStartAngle, aEndAngle, aClockwise) {
        aRadius *= Z;
        var arcType = aClockwise ? "at" : "wa";
     
        var xStart = aX + (mc(aStartAngle) * aRadius) - Z2;
        var yStart = aY + (ms(aStartAngle) * aRadius) - Z2;
     
        var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2;
        var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2;
     
        // IE won't render arches drawn counter clockwise if xStart == xEnd.
        if (xStart == xEnd && !aClockwise) {
          xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
                           // that can be represented in binary
        }
     
        this.currentPath_.push({type: arcType,
                               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;
     
        // to find the original width we overide the width and height
        var oldRuntimeWidth = image.runtimeStyle.width;
        var oldRuntimeHeight = image.runtimeStyle.height;
        image.runtimeStyle.width = 'auto';
        image.runtimeStyle.height = 'auto';
     
        // get the original size
        var w = image.width;
        var h = image.height;
     
        // and remove overides
        image.runtimeStyle.width = oldRuntimeWidth;
        image.runtimeStyle.height = oldRuntimeHeight;
     
        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 = [];
     
        var W = 10;
        var H = 10;
     
        // For some reason that I've now forgotten, using divs didn't work
        vmlStr.push(' <g_vml_:group',
                    ' coordsize="', Z * W, ',', Z * H, '"',
                    ' coordorigin="0,0"' ,
                    ' style="width:', W, ';height:', H, ';position:absolute;');
     
        // If filters are necessary (rotation exists), create them
        // filters are bog-slow, so only create them if abbsolutely necessary
        // The following check doesn't account for skews (which don't exist
        // in the canvas spec (yet) anyway.
     
        if (this.m_[0][0] != 1 || this.m_[0][1]) {
          var filter = [];
     
          // Note the 12/21 reversal
          filter.push("M11='", this.m_[0][0], "',",
                      "M12='", this.m_[1][0], "',",
                      "M21='", this.m_[0][1], "',",
                      "M22='", this.m_[1][1], "',",
                      "Dx='", mr(d.x / Z), "',",
                      "Dy='", mr(d.y / Z), "'");
     
          // Bounding box calculation (need to minimize displayed area so that
          // filters don't waste time on unused pixels.
          var max = d;
          var c2 = this.getCoords_(dx + dw, dy);
          var c3 = this.getCoords_(dx, dy + dh);
          var c4 = this.getCoords_(dx + dw, dy + dh);
     
          max.x = Math.max(max.x, c2.x, c3.x, c4.x);
          max.y = Math.max(max.y, c2.y, c3.y, c4.y);
     
          vmlStr.push("padding:0 ", mr(max.x / Z), "px ", mr(max.y / Z),
                      "px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
                      filter.join(""), ", sizingmethod='clip');")
        } else {
          vmlStr.push("top:", mr(d.y / Z), "px;left:", mr(d.x / Z), "px;")
        }
     
        vmlStr.push(' ">' ,
                    '<g_vml_ :image src="', image.src, '"',
                    ' style="width:', Z * dw, ';',
                    ' height:', Z * dh, ';"',
                    ' cropleft="', sx / w, '"',
                    ' croptop="', sy / h, '"',
                    ' cropright="', (w - sx - sw) / w, '"',
                    ' cropbottom="', (h - sy - sh) / h, '"',
                    ' />',
                    '');
     
        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;
     
        var W = 10;
        var H = 10;
     
        lineStr.push('<g_vml_ :shape',
                     ' fillcolor="', color, '"',
                     ' filled="', Boolean(aFill), '"',
                     ' style="position:absolute;width:', W, ';height:', H, ';"',
                     ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
                     ' stroked="', !aFill, '"',
                     ' strokeweight="', this.lineWidth, '"',
                     ' strokecolor="', color, '"',
                     ' path="');
     
        var newSeq = false;
        var min = {x: null, y: null};
        var max = {x: null, y: null};
     
        for (var i = 0; i < this.currentPath_.length; i++) {
          var p = this.currentPath_[i];
     
          if (p.type == "moveTo") {
            lineStr.push(" m ");
            var c = this.getCoords_(p.x, p.y);
            lineStr.push(mr(c.x), ",", mr(c.y));
          } else if (p.type == "lineTo") {
            lineStr.push(" l ");
            var c = this.getCoords_(p.x, p.y);
            lineStr.push(mr(c.x), ",", mr(c.y));
          } else if (p.type == "close") {
            lineStr.push(" x ");
          } else if (p.type == "bezierCurveTo") {
            lineStr.push(" c ");
            var c = this.getCoords_(p.x, p.y);
            var c1 = this.getCoords_(p.cp1x, p.cp1y);
            var c2 = this.getCoords_(p.cp2x, p.cp2y);
            lineStr.push(mr(c1.x), ",", mr(c1.y), ",",
                         mr(c2.x), ",", mr(c2.y), ",",
                         mr(c.x), ",", mr(c.y));
          } else if (p.type == "at" || p.type == "wa") {
            lineStr.push(" ", p.type, " ");
            var c  = this.getCoords_(p.x, p.y);
            var cStart = this.getCoords_(p.xStart, p.yStart);
            var cEnd = this.getCoords_(p.xEnd, p.yEnd);
     
            lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",",
                         mr(c.y - this.arcScaleY_ * p.radius), " ",
                         mr(c.x + this.arcScaleX_ * p.radius), ",",
                         mr(c.y + this.arcScaleY_ * p.radius), " ",
                         mr(cStart.x), ",", mr(cStart.y), " ",
                         mr(cEnd.x), ",", mr(cEnd.y));
          }
     
     
          // TODO: Following is broken for curves due to
          //       move to proper paths.
     
          // Figure out dimensions so we can do gradient fills
          // properly
          if(c) {
            if (min.x == null || c.x < min.x) {
              min.x = c.x;
            }
            if (max.x == null || c.x > 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 = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
          focus.y = mr((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('<g_vml_:fill',
                       ' color="', outsidecolor.color, '"',
                       ' color2="', insidecolor.color, '"',
                       ' type="', this.fillStyle.type_, '"',
                       ' focusposition="', focus.x, ', ', focus.y, '"',
                       ' colors="', colors.join(""), '"',
                       ' opacity="', opacity, '" />');
        } else if (aFill) {
          lineStr.push('<g_vml_ :fill color="', color, '" opacity="', opacity, '" />');
        } else {
          lineStr.push(
            '<g_vml_ :stroke',
            ' opacity="', opacity,'"',
            ' joinstyle="', this.lineJoin, '"',
            ' miterlimit="', this.miterLimit, '"',
            ' endcap="', processLineCap(this.lineCap) ,'"',
            ' weight="', this.lineWidth, 'px"',
            ' color="', color,'" />'
          );
        }
     
        lineStr.push("</g_vml_>");
     
        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: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2,
          y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2
        }
      };
     
      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 = mc(aRot);
        var s = ms(aRot);
     
        var m1 = [
          [c,  s, 0],
          [-s, c, 0],
          [0,  0, 1]
        ];
     
        this.m_ = matrixMultiply(m1, this.m_);
      };
     
      contextPrototype.scale = function(aX, aY) {
        this.arcScaleX_ *= aX;
        this.arcScaleY_ *= 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
     
     
      </glen></erik></emil></opensource></http></script>
     
      <script type="text/javascript">
        /* -------------------------------------------------------------------- */
     
        var canvas, ctx;
        var canvasWidth, halfCanvasWidth;
        var canvasHeight, halfCanvasHeight;
     
        var space;  // 3D Engine
        var scene;  // 3D Scene
     
        /* -------------------------------------------------------------------- */
     
        /**
         * Space is a simple 3D system.
         *
         * Y+ = up
         * Z+ = into screen
         * X+ = right
         */
        function Space() {
          this.m = this.createMatrixIdentity();
          this.mStack = [];
        }
     
        Space.prototype.createMatrixIdentity = function() {
          return [
            [1, 0, 0, 0],
            [0, 1, 0, 0],
            [0, 0, 1, 0],
            [0, 0, 0, 1]
          ];
        }
     
        /**
         * Multiplies two 4x4 matricies together.
         */
        Space.prototype.matrixMultiply = function(m1, m2) {
          var result = this.createMatrixIdentity();
     
          var width = m1[0].length;
          var height = m1.length;
     
          if (width != m2.length) {
            // error
          }
     
          for (var x = 0; x < width; x++) {
            for (var y = 0; y < height; y++) {
              var sum = 0;
     
              for (var z = 0; z < width; z++) {
                sum += m1[y][z] * m2[z][x];
              }
     
              result[y][x] = sum;
            }
          }
     
          return result;
        }
     
        /**
         * Transforms a coordinate using the current transformation
         * matrix, then flattens it using the projection matrix.
         */
        Space.prototype.flatten = function(point) {
          var p = [[point.x, point.y, point.z, 1]];
          var pm = this.matrixMultiply(p, this.m);
     
          point.tx = pm[0][0];
          point.ty = pm[0][1];
          point.tz = pm[0][2];
     
          // lazy projection
          point.fx = halfCanvasWidth + (canvasWidth * point.tx / point.tz);
          point.fy = halfCanvasHeight -(canvasWidth * point.ty / point.tz);
        }
     
        /**
         * Translate (move) the current transformation matrix
         */
        Space.prototype.translate = function(x, y, z) {
          var m = [
            [1, 0, 0, 0],
            [0, 1, 0, 0],
            [0, 0, 1, 0],
            [x, y, z, 1]
          ];
     
          this.m = this.matrixMultiply(m, this.m);
        }
     
        /**
         * Rotate the current transformation matrix. Rotations are
         * world-oriented, and occur in y,x,z order.
         */
        Space.prototype.rotate = function(x, y, z) {
          if (y) {
            var cosY = Math.cos(y);
            var sinY = Math.sin(y);
            var rotY = [
              [cosY, 0, sinY, 0],
              [0, 1, 0, 0],
              [-sinY, 0, cosY, 0],
              [0, 0, 0, 1]
            ];
     
            this.m = this.matrixMultiply(this.m, rotY);
          }
     
          if (x) {
            var cosX = Math.cos(x);
            var sinX = Math.sin(x);
            var rotX = [
              [1, 0, 0, 0],
              [0, cosX, -sinX, 0],
              [0, sinX, cosX,0],
              [0, 0, 0, 1]
            ];
            this.m = this.matrixMultiply(this.m, rotX);
          }
     
          if (z) {
            var cosZ = Math.cos(z);
            var sinZ = Math.sin(z);
            var rotZ = [
              [cosZ, -sinZ, 0, 0],
              [sinZ, cosZ, 0, 0],
              [0, 0, 1, 0],
              [0, 0, 0, 1]
            ];
     
            this.m = this.matrixMultiply(this.m, rotZ);
          }
        }
     
        /**
         * Pushes the current transformation onto the stack
         */
        Space.prototype.push = function() {
          this.mStack.push(this.m);
          this.m = [
            [this.m[0][0], this.m[0][1], this.m[0][2], this.m[0][3]],
            [this.m[1][0], this.m[1][1], this.m[1][2], this.m[1][3]],
            [this.m[2][0], this.m[2][1], this.m[2][2], this.m[2][3]],
            [this.m[3][0], this.m[3][1], this.m[3][2], this.m[3][3]]
          ];
        }
     
        /**
         * Pops the end off the transformation stack
         */
        Space.prototype.pop = function() {
          this.m = this.mStack.pop();
        }
     
        /* -------------------------------------------------------------------- */
     
        /**
         * A 3d coordinate
         */
        function Point(x, y, z) {
          this.x = x;
          this.y = y;
          this.z = z;
     
          // Relative to camera coordinates
          this.tx;
          this.ty;
          this.tz;
     
          // Flattened coordinates
          this.fx;
          this.fy;
        }
     
        /**
         * A Shape is made up of polygons
         */
        function Shape() {
          this.points = [];
          this.polygons = [];
        }
     
        /**
         * Draws the shape
         */
        Shape.prototype.draw = function(drawlist) {
          for (var i = 0; i< this.points.length; i++) {
            space.flatten(this.points[i]);
          }
     
          for (var i = 0; i< this.polygons.length; i++) {
            var poly = this.polygons[i]; // convenience
     
            space.flatten(poly.origin);
     
            // lazy backface culling
            if (poly.normal && this.backface) {
              space.flatten(poly.normal);
     
              var originDist = Math.pow(poly.origin.tx, 2)
                             + Math.pow(poly.origin.ty, 2)
                             + Math.pow(poly.origin.tz, 2);
     
              var normalDist = Math.pow(poly.normal.tx, 2)
                             + Math.pow(poly.normal.ty, 2)
                             + Math.pow(poly.normal.tz, 2);
     
              if(originDist > normalDist) {
                drawlist.push(poly);
              }
            } else {
              drawlist.push(poly);
            }
          }
        }
     
        /**
         * A polygon is a connection of points in the shape object. You
         * should probably try to make them coplanar.
         */
        function Polygon(points, normal, backface, type, color) {
          this.points = points;
     
          this.origin = new Point(0, 0, 0);
          for(var i = 0; i < this.points.length; i++) {
            this.origin.x += this.points[i].x;
            this.origin.y += this.points[i].y;
            this.origin.z += this.points[i].z;
          }
     
          this.origin.x /= this.points.length;
          this.origin.y /= this.points.length;
          this.origin.z /= this.points.length;
     
          if (normal) {
            this.normal = new Point(this.origin.x + normal.x,
                                    this.origin.y + normal.y,
                                    this.origin.z + normal.z);
          } else {
            this.normal = null;
          }
     
          this.backface = backface;
          this.type = type;
          this.color = color;
        }
     
        Polygon.SOLID = 0;
        Polygon.WIRE = 1;
     
        /**
         * Draws the polygon. Assumes that the points have already been
         * flattened.
         */
        Polygon.prototype.draw = function() {
          ctx.beginPath();
          ctx.moveTo(this.points[0].fx, this.points[0].fy);
     
          for(var i = 0; i < this.points.length; i++) {
            ctx.lineTo(this.points[i].fx, this.points[i].fy);
          }
     
          ctx.closePath();
     
          var color = this.color;
     
          /*
          // Do lighting here
          lightvector = Math.abs(this.normal.x + this.normal.y);
          if(lightvector > 1) {
            lightvector = 1;
          }
     
          color[0] = (color[0] * lightvector).toString();
          color[1] = (color[1] * lightvector).toString();
          color[2] = (color[2] * lightvector).toString();
          */
     
          if (color.length > 3) {
            var style = ["rgba(",
                         color[0], ",",
                         color[1], ",",
                         color[2], ",",
                         color[3], ")"].join("");
          } else {
            var style = ["rgb(",
                         color[0], ",",
                         color[1], ",",
                         color[2], ")"].join("");
          }
     
          if (this.type == Polygon.SOLID) {
            ctx.fillStyle = style;
            ctx.fill();
          } else if (this.type == Polygon.WIRE) {
            ctx.strokeStyle = style;
            ctx.stroke();
          }
        }
     
        /* -------------------------------------------------------------------- */
     
        /**
         * Scene describes the 3D environment
         */
        function Scene() {
          this.shapes = {};
          this.camera = new Point(0, 0, 0);
          this.cameraTarget = new Point(0, 0, 0);
          this.cameraRotation = 0;
     
          this.drawlist = [];
        }
     
        /**
         * Draw the world
         */
        Scene.prototype.draw = function() {
          space.push();
     
          // Camera transformation
          space.translate(
            -this.camera.x,
            -this.camera.y,
            -this.camera.z
          );
     
          // Camera rotation
          var xdiff = this.cameraTarget.x - this.camera.x;
          var ydiff = this.cameraTarget.y - this.camera.y;
          var zdiff = this.cameraTarget.z - this.camera.z;
     
          var xzdist = Math.sqrt(Math.pow(xdiff, 2) + Math.pow(zdiff, 2));
     
          var xrot = -Math.atan2(ydiff, xzdist); // up/down rotation
          var yrot =  Math.atan2(xdiff, zdiff);  // left/right rotation
     
          space.rotate(xrot, yrot, this.cameraRotation);
     
          // Drawing
          this.drawlist = [];
     
          for(var i in this.shapes) {
            this.shapes[i].draw(this.drawlist);
          }
     
          // Depth sorting (warning: this is only enough to drive this demo - feel
          // free to contribute a better system).
          this.drawlist.sort(function (poly1, poly2) {
            return poly2.origin.tz - poly1.origin.tz;
          });
     
          for (var i = 0; i < this.drawlist.length; i++) {
            this.drawlist[i].draw();
          }
     
          space.pop();
        }
     
        /* -------------------------------------------------------------------- */
     
        var count = 0;
     
        function loop() {
          ctx.clearRect(0, 0, canvasWidth, canvasHeight);
     
          scene.camera.x = 70*Math.sin(count);
          scene.camera.y = 70;
          scene.camera.z = 70*Math.cos(count);
          scene.cameraRotation = count / 10;
     
          count += 0.01;
          scene.draw();
        }
     
        function load() {
          // Init drawing system
          canvas = document.getElementById("cv");
          ctx = canvas.getContext("2d");
     
          canvasWidth = canvas.width;
          canvasHeight = canvas.height;
          halfCanvasWidth = canvasWidth * 0.5;
          halfCanvasHeight = canvasHeight * 0.5;
     
          // Init 3D components
          space = new Space();
          scene = new Scene();
     
          // Create a box shape and add it to the scene
          scene.shapes['box'] = new Shape();
          var p = scene.shapes['box'].points; // for convenience
     
          p[0] = new Point(-10, -10, -10); // left  bottom front
          p[1] = new Point(10, -10, -10);  // right bottom front
          p[2] = new Point(10, 10, -10);   // right top    front
          p[3] = new Point(-10, 10, -10);  // left  top    front
     
          p[4] = new Point(-10, -10, 10);  // left  bottom back
          p[5] = new Point(10, -10, 10);   // right bottom back
          p[6] = new Point(10, 10, 10);    // right top    back
          p[7] = new Point(-10, 10, 10);   // left  top    back
     
          // Back
          scene.shapes['box'].polygons.push(new Polygon(
            [ p[0], p[1], p[2], p[3] ],
            new Point(0, 0, -1),
            true /* double-sided */,
            Polygon.SOLID,
            [255, 0, 0]
          ));
     
          // Front
          scene.shapes['box'].polygons.push(new Polygon(
            [ p[4], p[5], p[6], p[7] ],
            new Point(0, 0, 1),
            true /* double-sided */,
            Polygon.SOLID,
            [0, 0, 255]
          ));
     
          // Top
          scene.shapes['box'].polygons.push(new Polygon(
            [ p[2], p[3], p[7], p[6] ],
            new Point(0, 1, 0),
            false /* single-sided */,
            Polygon.WIRE,
            [0, 255, 0]
          ));
     
          // Transparent Top
          scene.shapes['box'].polygons.push(new Polygon(
            [ p[2], p[3], p[7], p[6] ],
            new Point(0, 1, 0),
            false /* single-sided */,
            Polygon.SOLID,
            [0, 255, 0, 0.4]
          ));
     
          // Left
          scene.shapes['box'].polygons.push(new Polygon(
            [ p[0], p[4], p[7], p[3] ],
            new Point(-1, 0, 0),
            true /* double-sided */,
            Polygon.SOLID,
            [255, 255, 0]
          ));
     
          // Right
          scene.shapes['box'].polygons.push(new Polygon(
            [ p[1], p[5], p[6], p[2] ],
            new Point(1, 0, 0),
            true /* double-sided */,
            Polygon.SOLID,
            [0, 255, 255]
          ));
     
          // Create a floor shape and add it to the scene
          scene.shapes['floor'] = new Shape();
          var p = scene.shapes['floor'].points; // for convenience
     
          p[0]  = new Point(-40, -10, -40);
          p[1]  = new Point(-40, -10,  40);
          p[2] = new Point( 40, -10,  40);
          p[3] = new Point( 40, -10, -40);
     
          // Floor
          scene.shapes['floor'].polygons.push(new Polygon(
            [ p[0], p[1], p[2], p[3] ],
            new Point(0, 1, 0),
            false /* single-sided */,
            Polygon.SOLID,
            [45, 45, 45]
          ));
     
          setInterval('loop()', 20);
        }
     
        /* -------------------------------------------------------------------- */
      </script>
      <style>
      body {
        background-color:black;
        margin:50px;
        text-align:center;
      }
      </style>
    </script></head>
    <body onload="load();">
      <canvas id="cv" width="400" height="300"></canvas>
    </body>
    </html>