UNPKG

isomer

Version:

A simple isometric graphics library for HTML5 canvas

142 lines (117 loc) 3.86 kB
var Canvas = require('./canvas'); var Color = require('./color'); var Path = require('./path'); var Point = require('./point'); var Shape = require('./shape'); var Vector = require('./vector'); /** * The Isomer class * * This file contains the Isomer base definition */ function Isomer(canvasId, options) { options = options || {}; this.canvas = new Canvas(canvasId); this.angle = Math.PI / 6; this.scale = options.scale || 70; this._calculateTransformation(); this.originX = options.originX || this.canvas.width / 2; this.originY = options.originY || this.canvas.height * 0.9; /** * Light source as defined as the angle from * the object to the source. * * We'll define somewhat arbitrarily for now. */ this.lightPosition = options.lightPosition || new Vector(2, -1, 3); this.lightAngle = this.lightPosition.normalize(); /** * The maximum color difference from shading */ this.colorDifference = 0.20; this.lightColor = options.lightColor || new Color(255, 255, 255); } /** * Sets the light position for drawing. */ Isomer.prototype.setLightPosition = function(x, y, z) { this.lightPosition = new Vector(x, y, z); this.lightAngle = this.lightPosition.normalize(); }; Isomer.prototype._translatePoint = function(point) { /** * X rides along the angle extended from the origin * Y rides perpendicular to this angle (in isometric view: PI - angle) * Z affects the y coordinate of the drawn point */ var xMap = new Point(point.x * this.transformation[0][0], point.x * this.transformation[0][1]); var yMap = new Point(point.y * this.transformation[1][0], point.y * this.transformation[1][1]); var x = this.originX + xMap.x + yMap.x; var y = this.originY - xMap.y - yMap.y - (point.z * this.scale); return new Point(x, y); }; /** * Adds a shape or path to the scene * * This method also accepts arrays */ Isomer.prototype.add = function(item, baseColor) { if (Object.prototype.toString.call(item) == '[object Array]') { for (var i = 0; i < item.length; i++) { this.add(item[i], baseColor); } } else if (item instanceof Path) { this._addPath(item, baseColor); } else if (item instanceof Shape) { /* Fetch paths ordered by distance to prevent overlaps */ var paths = item.orderedPaths(); for (var j = 0; j < paths.length; j++) { this._addPath(paths[j], baseColor); } } }; /** * Adds a path to the scene */ Isomer.prototype._addPath = function(path, baseColor) { /* Default baseColor */ baseColor = baseColor || new Color(120, 120, 120); /* Compute color */ var v1 = Vector.fromTwoPoints(path.points[1], path.points[0]); var v2 = Vector.fromTwoPoints(path.points[2], path.points[1]); var normal = Vector.crossProduct(v1, v2).normalize(); /** * Brightness is between -1 and 1 and is computed based * on the dot product between the light source vector and normal. */ var brightness = Vector.dotProduct(normal, this.lightAngle); var color = baseColor.lighten(brightness * this.colorDifference, this.lightColor); this.canvas.path(path.points.map(this._translatePoint.bind(this)), color); }; /** * Precalculates transformation values based on the current angle and scale * which in theory reduces costly cos and sin calls */ Isomer.prototype._calculateTransformation = function() { this.transformation = [ [ this.scale * Math.cos(this.angle), this.scale * Math.sin(this.angle) ], [ this.scale * Math.cos(Math.PI - this.angle), this.scale * Math.sin(Math.PI - this.angle) ] ]; }; /* Namespace our primitives */ Isomer.Canvas = Canvas; Isomer.Color = Color; Isomer.Path = Path; Isomer.Point = Point; Isomer.Shape = Shape; Isomer.Vector = Vector; /* Expose Isomer API */ module.exports = Isomer;