'use strict'; /* eslint-disable space-infix-ops */ // The precision used to consider an ellipse as a circle // var epsilon = 0.0000000001; // To convert degree in radians // var torad = Math.PI / 180; // Class constructor : // an ellipse centred at 0 with radii rx,ry and x - axis - angle ax. // function Ellipse(rx, ry, ax) { if (!(this instanceof Ellipse)) { return new Ellipse(rx, ry, ax); } this.rx = rx; this.ry = ry; this.ax = ax; } // Apply a linear transform m to the ellipse // m is an array representing a matrix : // - - // | m[0] m[2] | // | m[1] m[3] | // - - // Ellipse.prototype.transform = function (m) { // We consider the current ellipse as image of the unit circle // by first scale(rx,ry) and then rotate(ax) ... // So we apply ma = m x rotate(ax) x scale(rx,ry) to the unit circle. var c = Math.cos(this.ax * torad), s = Math.sin(this.ax * torad); var ma = [ this.rx * (m[0]*c + m[2]*s), this.rx * (m[1]*c + m[3]*s), this.ry * (-m[0]*s + m[2]*c), this.ry * (-m[1]*s + m[3]*c) ]; // ma * transpose(ma) = [ J L ] // [ L K ] // L is calculated later (if the image is not a circle) var J = ma[0]*ma[0] + ma[2]*ma[2], K = ma[1]*ma[1] + ma[3]*ma[3]; // the discriminant of the characteristic polynomial of ma * transpose(ma) var D = ((ma[0]-ma[3])*(ma[0]-ma[3]) + (ma[2]+ma[1])*(ma[2]+ma[1])) * ((ma[0]+ma[3])*(ma[0]+ma[3]) + (ma[2]-ma[1])*(ma[2]-ma[1])); // the "mean eigenvalue" var JK = (J + K) / 2; // check if the image is (almost) a circle if (D < epsilon * JK) { // if it is this.rx = this.ry = Math.sqrt(JK); this.ax = 0; return this; } // if it is not a circle var L = ma[0]*ma[1] + ma[2]*ma[3]; D = Math.sqrt(D); // {l1,l2} = the two eigen values of ma * transpose(ma) var l1 = JK + D/2, l2 = JK - D/2; // the x - axis - rotation angle is the argument of the l1 - eigenvector /*eslint-disable indent*/ this.ax = (Math.abs(L) < epsilon && Math.abs(l1 - K) < epsilon) ? 90 : Math.atan(Math.abs(L) > Math.abs(l1 - K) ? (l1 - J) / L : L / (l1 - K) ) * 180 / Math.PI; /*eslint-enable indent*/ // if ax > 0 => rx = sqrt(l1), ry = sqrt(l2), else exchange axes and ax += 90 if (this.ax >= 0) { // if ax in [0,90] this.rx = Math.sqrt(l1); this.ry = Math.sqrt(l2); } else { // if ax in ]-90,0[ => exchange axes this.ax += 90; this.rx = Math.sqrt(l2); this.ry = Math.sqrt(l1); } return this; }; // Check if the ellipse is (almost) degenerate, i.e. rx = 0 or ry = 0 // Ellipse.prototype.isDegenerate = function () { return (this.rx < epsilon * this.ry || this.ry < epsilon * this.rx); }; module.exports = Ellipse;