JavaScript Day 8: More WireLibJS Internals

Just going to wrap up more of what’s in the WireLibJS library today.

We’ve covered the basic architecture, but let’s look what it all does. First, the vars:

[php lang=”JavaScript”]var canvas, context, width, height, lines = [], cx, cy, cz, fl = 250, interval, running;[/php]

A few are obvious: canvas, context, width, height. lines is initialized to an empty array. In WirelibJS, a line is an object with stroke attributes and an array of 3D points. So lines will be an array of these objects. cx, cy, and cz are the center points of the 3D world – its 0, 0, 0 origin point. By default, this is at the center of the 2D canvas, and “into” the 3D space somewhat. fl is focal length, probably not an accurate term optically speaking, but a convention I’ve grown used to. This controls the sense of perspective. Higher values will give less perceived perspective; lower values will really distort things as they move in and out of the space. interval is what we described yesterday for stopping the animation if needed. Finally, running is just a boolean value that indicates whether or not the animation is running.

Now the functions.

[php lang=”JavaScript”]function initWithCanvas(aCanvas) {
canvas = aCanvas;
if(canvas !== undefined) {
width = canvas.width;
height = canvas.height;
cx = width / 2;
cy = height / 2;
cz = fl * 2;
context = canvas.getContext(“2d”);
}
}[/php]

This initializes the library. Sets canvas to the canvas instance passed, grabs its withd, height, sets the center points and gets the context for drawing.

[php lang=”JavaScript”]function project(p3d) {
var p2d = {}, scale = fl / (fl + p3d.z + cz);
p2d.x = cx + p3d.x * scale;
p2d.y = cy + p3d.y * scale;
return p2d;
}[/php]

project takes a 3D point with x, y, z, and uses the origin point and focal length to project it to a 2D point that can be set directly on the canvas.

[php lang=”JavaScript”]function addLine() {
var i, numPoints, points, line;
points = (typeof arguments[0] === “object”) ? arguments[0] : arguments;

numPoints = points.length;
if(numPoints >= 6) {
line = {style:this.strokeStyle, width:this.lineWidth, points:[]};
lines.push(line);
for(i = 0; i < numPoints; i += 3) { line.points.push({x:points[i], y:points[i + 1], z:points[i + 2]}); } } else { console.error("wirelib.addLine: You need to add at least two 3d points (6 numbers) to make a line."); } return line; }[/php] addLine takes either an array of numbers, or an arbitrary amount of number parameters. So you can say: wirelib.addLine(0, 0, 0, 100, 100, 100); or var points = [0, 0, 0, 100, 100, 100]; wirelib.addLine(points); The function checks to see whether the first argument is a number or an object (array). If the former, it assigns the whole argument array to the points var. If the latter, it assigns the first argument to points. In either case, points will now contain an array of numbers. We make sure we have at least 6 numbers (x1, y1, z1, x2, y2, z2) so we have two points to make a line. Then we create a line object. This has a stroke style, a line width and an array of points. Then we create an x, y, z object with each set of 3 points and add it to the points array of the line object. The line is then returned. A user could use this to change the style, or add or delete points if they wanted. [php lang="JavaScript"]function addBox(x, y, z, w, h, d) { this.addLine(x - w / 2, y - h / 2, z - d / 2, x + w / 2, y - h / 2, z - d / 2, x + w / 2, y + h / 2, z - d / 2, x - w / 2, y + h / 2, z - d / 2, x - w / 2, y - h / 2, z - d / 2); this.addLine(x - w / 2, y - h / 2, z + d / 2, x + w / 2, y - h / 2, z + d / 2, x + w / 2, y + h / 2, z + d / 2, x - w / 2, y + h / 2, z + d / 2, x - w / 2, y - h / 2, z + d / 2); this.addLine(x - w / 2, y - h / 2, z - d / 2, x - w / 2, y - h / 2, z + d / 2); this.addLine(x + w / 2, y - h / 2, z - d / 2, x + w / 2, y - h / 2, z + d / 2); this.addLine(x + w / 2, y + h / 2, z - d / 2, x + w / 2, y + h / 2, z + d / 2); this.addLine(x - w / 2, y + h / 2, z - d / 2, x - w / 2, y + h / 2, z + d / 2); } function addRect(x, y, z, w, h) { this.addLine(x - w / 2, y - h / 2, z, x + w / 2, y - h / 2, z, x + w / 2, y + h / 2, z, x - w / 2, y + h / 2, z, x - w / 2, y - h / 2, z); } function addCircle(x, y, z, radius, segments) { var i, points = [], a; for(i = 0; i < segments; i += 1) { a = Math.PI * 2 * i / segments; points.push(x + Math.cos(a) * radius, y + Math.sin(a) * radius, z); } points.push(points[0], points[1], points[2]); this.addLine(points); }[/php] The other add methods all just layer functionality on top of addLine to make the specified shapes. [php lang="JavaScript"]function draw() { var i, j, line, p2d; if(this.clearCanvas) { context.clearRect(0, 0, width, height); } for(i = 0; i < lines.length; i += 1) { context.beginPath(); line = lines[i]; p2d = project(line.points[0]); context.moveTo(p2d.x, p2d.y); for(j = 1; j < line.points.length; j += 1) { p2d = project(line.points[j]); context.lineTo(p2d.x, p2d.y); } context.lineWidth = line.width; context.strokeStyle = line.style; context.stroke(); } if(this.showCenter) { p2d = project({x:0, y:0, z:0}); context.strokeStyle = "#ff0000"; context.lineWidth = 0.5; context.beginPath(); context.arc(p2d.x, p2d.y, 5, 0, Math.PI * 2, false); context.stroke(); } }[/php] This is the biggie. draw first clears the canvas, then loops through the lines array. For each line, it begins a path and then loops through its points array, projecting each 3D point to a 2D point and doing a lineTo. Then it sets the strokeStyle and lineWidth and strokes that line. Finally, if showCenter is true, it will draw a small circle at the origin point. [php lang="JavaScript"]function loop(fps, callback) { if(!running) { running = true; interval = setInterval(function() { callback(); wirelib.draw(); }, 1000 / fps); } }[/php] The loop function allows a user to animate their lines. You pass in a frame rate and a callback function. We check running to ensure we don't start animating twice. Then we start an interval timer passing in an anonymous function and a calculated interval. The function first calls the callback function that is passed in. We don't need to store a reference to this because it's a local variable that will be part of that closure function. Then it calls draw. But notice that it can't just call draw() or even this.draw() because the function will be executing in global namespace, where draw or this do not exist. So we call wirelib.draw. The wirelib object does exist in global, so we're ok. [php lang="JavaScript"]function stop() { running = false; clearInterval(interval); }[/php] Finally, we allow the user to stop the animation by clearing the interval, and setting running to false so that it can be restarted later. We'll end there for today, and wrap up the rest of the functions tomorrow.

This entry was posted in JavaScript. Bookmark the permalink.

Leave a Reply