graphics/line.js

'use strict';

var Thing = require('./thing.js');

/**
 * @class Line
 * @augments Thing
 * @param {number} x1 - x coordinate of starting point of line.
 * @param {number} y1 - y coordinate of starting point of line.
 * @param {number} x2 - x coordinate of end point of line.
 * @param {number} y2 - y coordinate of end point of line.
 */
function Line(x1, y1, x2, y2) {
    if (arguments.length !== 4) {
        throw new Error(
            'You should pass exactly 4 argument to <span ' +
                'class="code">new Line(x1, y1, x2, y2)</span>'
        );
    }
    if (
        typeof x1 !== 'number' ||
        typeof y1 !== 'number' ||
        typeof x2 !== 'number' ||
        typeof y2 !== 'number'
    ) {
        throw new TypeError(
            'You must pass 4 numbers to <span class="code">' +
                'new Line(x1, y1, x2, y2)</span>. Make sure each parameter you' +
                'are passing is a number.'
        );
    }
    if (!isFinite(x1) || !isFinite(y1) || !isFinite(x2) || !isFinite(y2)) {
        throw new TypeError(
            'One or more of the values you passed to <span ' +
                'class="code">new Line(x1, y1, x2, y2)</span> is an illegal ' +
                'number. Did you forget the parentheses in <span class="code">' +
                'getWidth()</span> or <span class="code">getHeight()</span>? Or ' +
                'did you perform a calculation on a variable that is not a ' +
                'number?'
        );
    }
    Thing.call(this);
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
    this.lineWidth = 2;
    this.type = 'Line';
}

Line.prototype = new Thing();
Line.prototype.constructor = Line;

/**
 * Sets the color of a line.
 *
 * @param {Color} color - Sets the color of the line.
 */
Line.prototype.setColor = function(color) {
    if (arguments.length !== 1) {
        throw new Error(
            'You should pass exactly 1 argument to <span ' + 'class="code">setColor(color)</span>.'
        );
    }
    if (color === undefined) {
        throw new TypeError('Invalid color');
    }
    this.stroke = color;
};

/**
 * Gets the color of a line.
 *
 * @returns {Color} Color of the line.
 */
Line.prototype.getColor = function() {
    return this.stroke;
};

/**
 * Draws the line in the canvas.
 *
 * @param {CodeHSGraphics} __graphics__ - Instance of the __graphics__ module.
 */
Line.prototype.draw = function(__graphics__) {
    var context = __graphics__.getContext();
    // http://stackoverflow.com/questions/17125632/html5-canvas-rotate-object-without-moving-coordinates
    context.save();
    context.fillStyle = this.color.toString();
    context.beginPath();
    context.strokeStyle = this.stroke.toString();
    context.lineWidth = this.lineWidth;
    var rotatedPoints = getRotatedPoints(this.x1, this.y1, this.x2, this.y2, this.rotation);
    context.moveTo(rotatedPoints[0], rotatedPoints[1]);
    context.lineTo(rotatedPoints[2], rotatedPoints[3]);
    context.closePath();
    context.stroke();
    context.restore();
};

/**
 * Gets the new points based on their rotated values.
 *
 */
/**
 * Gets the new points based on their rotated values.
 *
 * @param  {number} x1       X coordinate of start point
 * @param  {number} y1       Y coordinate of start point
 * @param  {number} x2       X coordinate of end point
 * @param  {number} y2       Y Coordinate of end point
 * @param  {number} rotation radians rotated (Expected in radians)
 * @return {array}          List of coordinates of both points.
 */
var getRotatedPoints = function(x1, y1, x2, y2, rotation) {
    var midX = (x1 + x2) / 2;
    var midY = (y1 + y2) / 2;
    var sinAngle = Math.sin(rotation);
    var cosAngle = Math.cos(rotation);
    var newX;
    var newY;
    // Rotate point 1
    x1 -= midX;
    y1 -= midY;
    newX = x1 * cosAngle - y1 * sinAngle;
    newY = x1 * sinAngle + y1 * cosAngle;
    x1 = newX + midX;
    y1 = newY + midY;

    // Rotate point 2
    x2 -= midX;
    y2 -= midY;
    newX = x2 * cosAngle - y2 * sinAngle;
    newY = x2 * sinAngle + y2 * cosAngle;
    x2 = newX + midX;
    y2 = newY + midY;

    return [x1, y1, x2, y2];
};

/**
 * Checks if a given point is contained in the line.
 *
 * @param {number} x - x coordinate of the point being tested.
 * @param {number} y - y coordinate of the point being tested.
 */
Line.prototype.containsPoint = function(x, y) {
    var betweenXs = (this.x1 <= x && x <= this.x2) || (this.x2 <= x && x <= this.x1);
    var betweenYs = (this.y1 <= y && y <= this.y2) || (this.y2 <= y && y <= this.y1);
    if (this.x1 == this.x2) {
        return this.x1 == x && betweenYs;
    } else {
        var slope = (this.y2 - this.y1) / (this.x2 - this.x1);
        return (
            Math.abs(slope * (x - this.x1) - (y - this.y1)) <= this.lineWidth &&
            betweenXs &&
            betweenYs
        );
    }
};

/**
 * Returns the width of the line.
 *
 * @returns {number} The width of the line.
 */
Line.prototype.getWidth = function() {
    return this.width;
};

/**
 * Returns the height of the line.
 *
 * @returns {number} The width of the line.
 */
Line.prototype.getHeight = function() {
    return this.height;
};

/**
 * Sets the width of the line.
 *
 * @param {number} width - The resulting width of the line.
 */
Line.prototype.setLineWidth = function(width) {
    if (arguments.length !== 1) {
        throw new Error(
            'You should pass exactly 1 argument to <span ' + 'class="code">setLineWidth</span>'
        );
    }
    if (typeof width !== 'number' || !isFinite(width)) {
        throw new TypeError(
            'You must pass a finite number to <span class=' +
                '"code">setLineWidth(width)</span>. Did you perform a ' +
                'calculation on a variable that is not a number?'
        );
    }
    this.lineWidth = width;
};

/**
 * Sets the *starting* point of the line.
 *
 * @param {number} x - The x coordinate of the resulting ending point.
 * @param {number} y - The y coordinate of the resulting ending point.
 */
Line.prototype.setStartpoint = function(x, y) {
    if (arguments.length !== 2) {
        throw new Error(
            'You should pass exactly 2 arguments to <span ' +
                'class="code">setStartpoint(x, y)</span>'
        );
    }
    if (typeof x !== 'number' || !isFinite(x)) {
        throw new TypeError(
            'Invalid value for x-coordinate. ' +
                'Make sure you are passing finite numbers to <span ' +
                'class="code">setStartpoint(x, y)</span>. Did you ' +
                'forget the parentheses in <span class="code">getWidth()</span> ' +
                'or <span class="code">getHeight()</span>? Or did you perform a ' +
                'calculation on a variable that is not a number?'
        );
    }
    if (typeof y !== 'number' || !isFinite(y)) {
        throw new TypeError(
            'Invalid value for y-coordinate. ' +
                'Make sure you are passing finite numbers to <span ' +
                'class="code">setStartpoint(x, y)</span>. Did you ' +
                'forget the parentheses in <span class="code">getWidth()</span> ' +
                'or <span class="code">getHeight()</span>? Or did you perform a ' +
                'calculation on a variable that is not a number?'
        );
    }

    this.setPosition(x, y);
};

/**
 * Sets the *starting* point of the line.
 *
 * @param {number} x - The x coordinate of the resulting starting point.
 * @param {number} y - The y coordinate of the resulting starting point.
 */
Line.prototype.setPosition = function(x, y) {
    if (arguments.length !== 2) {
        throw new Error(
            'You should pass exactly 2 arguments to <span ' +
                'class="code">setPosition(x, y)</span>'
        );
    }
    if (typeof x !== 'number' || !isFinite(x)) {
        throw new TypeError(
            'Invalid value for x-coordinate. ' +
                'Make sure you are passing finite numbers to <span ' +
                'class="code">setPosition(x, y)</span>. Did you ' +
                'forget the parentheses in <span class="code">getWidth()</span> ' +
                'or <span class="code">getHeight()</span>? Or did you perform a ' +
                'calculation on a variable that is not a number?'
        );
    }
    if (typeof y !== 'number' || !isFinite(y)) {
        throw new TypeError(
            'Invalid value for y-coordinate. ' +
                'Make sure you are passing finite numbers to <span ' +
                'class="code">setPosition(x, y)</span>. Did you ' +
                'forget the parentheses in <span class="code">getWidth()</span> ' +
                'or <span class="code">getHeight()</span>? Or did you perform a ' +
                'calculation on a variable that is not a number?'
        );
    }
    this.x1 = x;
    this.y1 = y;
};

/**
 * Sets the *ending* point of the line.
 *
 * @param {number} x - The x coordinate of the resulting ending point.
 * @param {number} y - The y coordinate of the resulting ending point.
 */
Line.prototype.setEndpoint = function(x, y) {
    if (arguments.length !== 2) {
        throw new Error(
            'You should pass exactly 2 arguments to <span ' +
                'class="code">setEndpoint(x, y)</span>'
        );
    }
    if (typeof x !== 'number' || !isFinite(x)) {
        throw new TypeError(
            'Invalid value for x-coordinate. ' +
                'Make sure you are passing finite numbers to <span ' +
                'class="code">setEndpoint(x, y)</span>. Did you ' +
                'forget the parentheses in <span class="code">getWidth()</span> ' +
                'or <span class="code">getHeight()</span>? Or did you perform a ' +
                'calculation on a variable that is not a number?'
        );
    }
    if (typeof y !== 'number' || !isFinite(y)) {
        throw new TypeError(
            'Invalid value for y-coordinate. ' +
                'Make sure you are passing finite numbers to <span ' +
                'class="code">setEndpoint(x, y)</span>. Did you ' +
                'forget the parentheses in <span class="code">getWidth()</span> ' +
                'or <span class="code">getHeight()</span>? Or did you perform a ' +
                'calculation on a variable that is not a number?'
        );
    }
    this.x2 = x;
    this.y2 = y;
};

/**
 * Moves the entire line.
 *
 * @param {number} dx - The change in x coordinate of both starting and ending points.
 * @param {number} dy - The change in y coordinate of both starting and ending points.
 */
Line.prototype.move = function(dx, dy) {
    if (arguments.length !== 2) {
        throw new Error(
            'You should pass exactly 2 arguments to <span ' + 'class="code">move</span>'
        );
    }
    if (typeof dx !== 'number' || !isFinite(dx)) {
        throw new TypeError(
            'Invalid number passed for <span class="code">' +
                'dx</span>. Make sure you are passing finite numbers to <span ' +
                'class="code">move(dx, dy)</span>'
        );
    }
    if (typeof dy !== 'number' || !isFinite(dy)) {
        throw new TypeError(
            'Invalid number passed for <span class="code">' +
                'dy</span>. Make sure you are passing finite numbers to <span ' +
                'class="code">move(dx, dy)</span>'
        );
    }
    this.x1 += dx;
    this.y1 += dy;
    this.x2 += dx;
    this.y2 += dy;
};

/**
 * Gets the x coordinate of the Line's start point.
 *
 * @returns {number} The x coordinate of the Line's start point.
 */
Line.prototype.getX = function() {
    return this.x1;
};

/**
 * Gets the y coordinate of the Line's start point.
 *
 * @returns {number} The y coordinate of the Line's start point.
 */
Line.prototype.getY = function() {
    return this.y1;
};

/**
 * Gets the x coordinate of the Line's start point.
 *
 * @returns {number} The x coordinate of the Line's start point.
 */
Line.prototype.getStartX = function() {
    return this.x1;
};

/**
 * Gets the y coordinate of the Line's start point.
 *
 * @returns {number} The y coordinate of the Line's start point.
 */
Line.prototype.getStartY = function() {
    return this.y1;
};

/**
 * Gets the x coordinate of the Line's end point.
 *
 * @returns {number} The x coordinate of the Line's end point.
 */
Line.prototype.getEndX = function() {
    return this.x2;
};

/**
 * Gets the y coordinate of the Line's end point.
 *
 * @returns {number} The y coordinate of the Line's end point.
 */
Line.prototype.getEndY = function() {
    return this.y2;
};

module.exports = Line;