513 lines
12 KiB
HTML
513 lines
12 KiB
HTML
<!--
|
|
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>
|
|
<!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
|
|
<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>
|
|
</head>
|
|
<body onload="load();">
|
|
<canvas id="cv" width="400" height="300"></canvas>
|
|
</body>
|
|
</html> |