diff --git a/lib/ape-ep-dom.js b/lib/ape-ep-dom.js
new file mode 100644
index 00000000..e23944ff
--- /dev/null
+++ b/lib/ape-ep-dom.js
@@ -0,0 +1,1763 @@
+/*
+ Copyright (c) Garrett Smith.
+ Licensed under the AFL license.
+*/
+
+/**
+ * @fileoverview
+ * APE provides core features, including namespacing and object creational aspects.
+ *
+ *
APE JavaScript Library
+ *
+ * Released under Academic Free Licence 3.0.
+ *
+ *
+ * @author Garrett Smith
+ */
+
+/** @name APE
+ * @namespace */
+if(APE !== undefined) throw Error("APE is already defined.");
+var APE = {
+
+ /**
+ * @memberOf APE
+ * @description Prototype inheritance.
+ * @param {Object} subclass
+ * @param {Object} superclass
+ * @param {Object} mixin If present, mixin 's own properties are copied to receiver
+ * using APE.mixin(subclass.prototoype, superclass.prototype).
+ */
+ extend : function(subclass, superclass, mixin) {
+ if(arguments.length === 0) return;
+ var f = arguments.callee, subp;
+ f.prototype = superclass.prototype;
+ subclass.prototype = subp = new f;
+ if(typeof mixin == "object")
+ APE.mixin(subp, mixin);
+ subp.constructor = subclass;
+ return subclass;
+ },
+
+ /**
+ * Shallow copy of properties; does not look up prototype chain.
+ * Copies all properties in s to r, using hasOwnProperty.
+ * @param {Object} r the receiver of properties.
+ * @param {Object} s the supplier of properties.
+ * Accounts for JScript DontEnum bug for valueOf and toString.
+ * @return {Object} r the receiver.
+ */
+ mixin : function(r, s) {
+ var jscriptSkips = ['toString', 'valueOf'],
+ prop,
+ i = 0,
+ skipped;
+ for(prop in s) {
+ if(s.hasOwnProperty(prop))
+ r[prop] = s[prop];
+ }
+ // JScript DontEnum bug.
+ for( ; i < jscriptSkips.length; i++) {
+ skipped = jscriptSkips[i];
+ if(s.hasOwnProperty(skipped))
+ r[skipped] = s[skipped];
+ }
+ return r;
+ },
+
+ toString : function() { return "[APE JavaScript Library]"; },
+
+ /** Creational method meant for being cross-cut.
+ * Uses APE.newApply to create
+ * @param {HTMLElement} el An element. If el does not have
+ * an ID, then an ID will be automatically generated, based on the
+ * constructor's (this) identifier, or, If this is anonymous, "APE".
+ * @requires {Object} an object to be attached to as a property.
+ * @aspect
+ * @scope {Function} that accepts an HTMLElement for
+ * its first argument.
+ * APE.getByNode is intended to be bouund to a constructor function.
+ * @return {new this(el [,args...])}
+ */
+ getByNode : function(el) {
+ var id = el.id,
+ fName;
+ if(!id) {
+ if(!APE.getByNode._i) APE.getByNode._i = 0;
+ fName = APE.getFunctionName(this);
+ if(!fName) fName = "APE";
+ id = el.id = fName+"_" + (APE.getByNode._i++);
+ }
+ if(!this.hasOwnProperty("instances")) this.instances = {};
+ return this.instances[id] || (this.instances[id] = APE.newApply(this, arguments));
+ },
+
+ /** Tries to get a name of a function object, returns "" if anonymous.
+ */
+ getFunctionName : function(fun) {
+ if(typeof fun.name == "string") return fun.name;
+ var name = Function.prototype.toString.call(fun).match(/\s([a-z]+)\(/i);
+ return name && name[1]||"";
+ },
+
+ /** Creational method meant for being cross-cut.
+ * @param {HTMLElement} el An element that has an id.
+ * @requires {Object} an object to bind to.
+ * @aspect
+ * @description getById must be assigned to a function constructor
+ * that accepts an HTMLElement's id for
+ * its first argument.
+ * @example
+ * function Slider(el, config){ }
+ * Slider.getById = APE.getById;
+ *
+ * This allows for implementations to use a factory method with the constructor.
+ *
+ * Slider.getById( "weight", 1 );
+ *
+ * Subsequent calls to:
+ *
+ * Slider.getById( "weight" );
+ *
+ * will return the same Slider instance.
+ * An instances property is added to the constructor object
+ * that getById is assigned to.
+ * @return new this(id [,args...])
+ */
+ getById : function(id) {
+ if(!this.hasOwnProperty("instances")) this.instances = {};
+ return this.instances[id] || (this.instances[id] = APE.newApply(this, arguments));
+ },
+
+ /** Creates a Factory method out of a function.
+ * @param {Function} constructor
+ * @param {Object} prototype
+ * @memberOf APE
+ */
+ createFactory : function(constructor, prot) {
+ var baseObject = {},
+ instances = baseObject.instances = {}; // Export, for purge or cleanup.
+ if(prot) {
+ constructor.prototype = prot;
+ }
+ baseObject.getById = getById;
+ return baseObject;
+ function getById(id) {
+ return instances[id] || (instances[id] = APE.newApply(constructor, arguments));
+ }
+ },
+
+ newApply : (function() {
+ function F(){}
+ return newApply;
+ /**
+ * @param {Function} constructor constructor to be invoked.
+ * @param {Array} args arguments to pass to the constructor.
+ * Instantiates a constructor and uses apply().
+ * @memberOf APE
+ */
+ function newApply(constructor, args) {
+ var i;
+ F.prototype = constructor.prototype;// Copy prototype.
+ F.prototype.constructor = constructor;
+ i = new F;
+ constructor.apply(i, args); // Apply the original constructor.
+ return i;
+ }
+ })(),
+
+ /** Throws the error in a setTimeout 1ms.
+ * Deferred errors are useful for Event Notification systems,
+ * Animation, and testing.
+ * @param {Error} error that occurred.
+ */
+ deferError : function(error) {
+ //setTimeout(function(){throw error;},1);
+ throw error;
+ }
+};
+
+(function(){
+
+ APE.namespace = namespace;
+
+ /**
+ * @memberOf APE
+ * @description creates a namespace split on "."
+ * does not automatically add APE to the front of the chain, as YUI does.
+ * @param {String} s the namespace. "foo.bar" would create a namespace foo.bar, but only
+ * if that namespace did not exist.
+ * @return {Package} the namespace.
+ */
+ function namespace(s) {
+ var packages = s.split("."),
+ pkg = window,
+ hasOwnProperty = Object.prototype.hasOwnProperty,
+ qName = pkg.qualifiedName,
+ i = 0,
+ len = packages.length,
+ name;
+ for (; i < len; i++) {
+ name = packages[i];
+
+ // Internet Explorer does not support
+ // hasOwnProperty on things like window, so call Object.prototype.hasOwnProperty.
+ // Opera does not support the global object or [[Put]] properly (see below)
+ if(!hasOwnProperty.call(pkg, name)) {
+ pkg[name] = new Package((qName||"APE")+"."+name);
+ }
+ pkg = pkg[name];
+ }
+
+ return pkg;
+ }
+
+ Package.prototype.toString = function(){
+ return"["+this.qualifiedName+"]";
+ };
+
+ /* constructor Package
+ */
+ function Package(qualifiedName) {
+ this.qualifiedName = qualifiedName;
+ }
+})();
+
+(function(){
+/**@class
+ * A safe patch to the Object object. This patch addresses a bug that only affects Opera.
+ * It does not affect any for-in loops in any browser (see tests).
+ */
+var O = Object.prototype, hasOwnProperty = O.hasOwnProperty;
+if(typeof window != "undefined" && hasOwnProperty && !hasOwnProperty.call(window, "Object")) {
+/**
+ * @overrides Object.prototype.hasOwnProperty
+ * @method
+ * This is a conditional patch that affects some versions of Opera.
+ * It is perfectly safe to do this and does not affect enumeration.
+ */
+ Object.prototype.hasOwnProperty = function(p) {
+ if(this === window) return (p in this) && (O[p] !== this[p]);
+ return hasOwnProperty.call(this, p);
+ };
+}
+})();/**
+ * @fileoverview
+ * EventPublisher
+ *
+ * Released under Academic Free Licence 3.0.
+ * @author Garrett Smith
+ * @class
+ * APE.EventPublisher can be used for native browser events or custom events.
+ *
+ * For native browser events, use APE.EventPublisher
+ * steals the event handler off native elements and creates a callStack.
+ * that fires in its place.
+ *
+ *
+ * There are two ways to create custom events.
+ *
+ *
+ * Create a function on the object that fires the "event", then call that function
+ * when the event fires (this happens automatically with native events).
+ *
+ *
+ * Instantiate an EventPublisher using the constructor, then call fire
+ * when the callbacks should be run.
+ *
+ *
+ *
+ * An EventPublisher itself publishes beforeFire and afterFire.
+ * This makes it possible to add AOP before advice to the callStack.
+ *
+ * adding before-before advice is possible, but will impair performance.
+ * Instead, add multiple beforeAdvice with:
+ * publisher.addBefore(fp, thisArg).add(fp2, thisArg);
+ *
+ * There are no beforeEach and afterEach methods; to create advice
+ * for each callback would require modification
+ * to the registry (see comments below). I have not yet found a real need for this.
+ *
+ */
+/**
+ * @constructor
+ * @description creates an EventPublisher with methods add(),
+ * fire, et c.
+ */
+APE.EventPublisher = function(src, type) {
+ this.src = src;
+ // Really could use a List of bound methods here.
+ this._callStack = [];
+ this.type = type;
+};
+
+APE.EventPublisher.prototype = {
+
+/**
+ * @param {Function} fp the callback function that gets called when src[sEvent] is called.
+ * @param {Object} thisArg the context that the function executes in.
+ * @return {EventPublisher} this;
+ */
+ add : function(fp, thisArg) {
+ this._callStack.push([fp, thisArg||this.src]);
+ return this;
+ },
+/** Adds beforeAdvice to the callStack. This fires before the callstack.
+ * @param {Function:boolean} fp the callback function that gets called when src[sEvent] is called.
+ * function's returnValue proceed false stops the callstack and returns false to the original call.
+ * @param {Object} thisArg the context that the function executes in.
+ * @return {EventPublisher} this;
+ */
+ addBefore : function(f, thisArg) {
+ return APE.EventPublisher.add(this, "beforeFire", f, thisArg);
+ },
+
+/** Adds afterAdvice to the callStack. This fires after the callstack.
+ * @param {Function:boolean} fp the callback function that gets called when src[sEvent] is called.
+ * function's returnValue of false returns false to the original call.
+ * @param {Object} thisArg the context that the function executes in.
+ * @return {EventPublisher} this;
+ */
+ addAfter : function(f, thisArg) {
+ return APE.EventPublisher.add(this, "afterFire", f, thisArg);
+ },
+
+ /**
+ * @param {String} "beforeFire", "afterFire" conveneince.
+ * @return {EventPublisher} this;
+ */
+ getEvent : function(type) {
+ return APE.EventPublisher.get(this, type);
+ },
+
+/** Removes fp from callstack.
+ * @param {Function:boolean} fp the callback function to remove.
+ * @param {Object} [thisArg] the context that the function executes in.
+ * @return {Function} the function that was passed in, or null if not found;
+ */
+ remove : function(fp, thisArg) {
+ var cs = this._callStack, i = 0, len, call;
+ if(!thisArg) thisArg = this.src;
+ for(len = cs.length; i < len; i++) {
+ call = cs[i];
+ if(call[0] === fp && call[1] === thisArg) {
+ return cs.splice(i, 1);
+ }
+ }
+ return null;
+ },
+
+/** Removes fp from callstack's beforeFire.
+ * @param {Function:boolean} fp the callback function to remove.
+ * @param {Object} [thisArg] the context that the function executes in.
+ * @return {Function} the function that was passed in, or null if not found (uses remove());
+ */
+ removeBefore : function(fp, thisArg) {
+ return this.getEvent("beforeFire").remove(fp, thisArg);
+ },
+
+
+/** Removes fp from callstack's afterFire.
+ * @param {Function:boolean} fp the callback function to remove.
+ * @param {Object} [thisArg] the context that the function executes in.
+ * @return {Function} the function that was passed in, or null if not found (uses remove());
+ */
+ removeAfter : function(fp, thisArg) {
+ return this.getEvent("afterFire").remove(fp, thisArg);
+ },
+
+/** Fires the event. */
+ fire : function(payload) {
+ return APE.EventPublisher.fire(this)(payload);
+ },
+
+/** helpful debugging info */
+ toString : function() {
+ return "APE.EventPublisher: {src=" + this.src + ", type=" + this.type +
+ ", length="+this._callStack.length+"}";
+ }
+};
+
+/**
+ * @static
+ * @param {Object} src the object which calls the function
+ * @param {String} sEvent the function that gets called.
+ * @param {Function} fp the callback function that gets called when src[sEvent] is called.
+ * @param {Object} thisArg the context that the function executes in.
+ */
+APE.EventPublisher.add = function(src, sEvent, fp, thisArg) {
+ return APE.EventPublisher.get(src, sEvent).add(fp, thisArg);
+};
+
+/**
+ * @static
+ * @private
+ * @memberOf {APE.EventPublisher}
+ * @return {boolean} false if any one of callStack's methods return false.
+ */
+APE.EventPublisher.fire = function(publisher) {
+ // This closure sucks. We should have partial/bind in ES.
+ // If we did, this could more reasonably be a prototype method.
+
+ // return function w/identifier doesn't work in Safari 2.
+ return fireEvent;
+ function fireEvent(e) {
+ var preventDefault = false,
+ i = 0, len,
+ cs = publisher._callStack, csi;
+
+ // beforeFire can affect return value.
+ if(typeof publisher.beforeFire == "function") {
+ try {
+ if(publisher.beforeFire(e) == false)
+ preventDefault = true;
+ } catch(ex){APE.deferError(ex);}
+ }
+
+ for(len = cs.length; i < len; i++) {
+ csi = cs[i];
+ // If an error occurs, continue the event fire,
+ // but still throw the error.
+ try {
+ // TODO: beforeEach to prevent or advise each call.
+ if(csi[0].call(csi[1], e || window.event) == false)
+ preventDefault = true; // continue main callstack and return false afterwards.
+ // TODO: afterEach
+ }
+ catch(ex) {
+ APE.deferError(ex);
+ }
+ }
+ // afterFire can prevent default.
+ if(typeof publisher.afterFire == "function") {
+ if(publisher.afterFire(e) == false)
+ preventDefault = true;
+ }
+ return !preventDefault;
+ }
+};
+
+/**
+ * @static
+ * @param {Object} src the object which calls the function
+ * @param {String} sEvent the function that gets called.
+ * @memberOf {APE.EventPublisher}
+ * Looks for an APE.EventPublisher in the Registry.
+ * If none found, creates and adds one to the Registry.
+ */
+APE.EventPublisher.get = function(src, sEvent) {
+
+ var publisherList = this.Registry.hasOwnProperty(sEvent) && this.Registry[sEvent] ||
+ (this.Registry[sEvent] = []),
+ i = 0, len = publisherList.length,
+ publisher;
+
+ for(; i < len; i++)
+ if(publisherList[i].src === src)
+ return publisherList[i];
+
+ // not found.
+ publisher = new APE.EventPublisher(src, sEvent);
+ // Steal.
+ if(src[sEvent])
+ publisher.add(src[sEvent], src);
+ src[sEvent] = this.fire(publisher);
+ publisherList[publisherList.length] = publisher;
+ return publisher;
+};
+
+/**
+ * Map of [APE.EventPublisher], keyed by type.
+ * @private
+ * @static
+ * @memberOf {APE.EventPublisher}
+ */
+APE.EventPublisher.Registry = {};
+
+/**
+ * @static
+ * @memberOf {APE.EventPublisher}
+ * called onunload, automatically onunload.
+ * This is only called for if window.CollectGarbage is
+ * supported. IE has memory leak problems; other browsers have fast forward/back,
+ * but that won't work if there's an onunload handler.
+ */
+APE.EventPublisher.cleanUp = function() {
+ var type, publisherList, publisher, i, len;
+ for(type in this.Registry) {
+ publisherList = this.Registry[type];
+ for(i = 0, len = publisherList.length; i < len; i++) {
+ publisher = publisherList[i];
+ publisher.src[publisher.type] = null;
+ }
+ }
+};
+if(window.CollectGarbage)
+ APE.EventPublisher.get( window, "onunload" ).addAfter( APE.EventPublisher.cleanUp, APE.EventPublisher );/**dom.js rollup: constants.js, viewport-f.js, position-f.js, classname-f.js, traversal-f.js, Event.js, Event-coords.js, style-f.js, gebi-f.js */
+APE.namespace("APE.dom" );
+(function(){
+ var dom = APE.dom,
+ docEl = document.documentElement,
+ textContent = "textContent",
+ view = document.defaultView;
+
+ dom.IS_COMPUTED_STYLE = (typeof view != "undefined" && "getComputedStyle" in view);
+ dom.textContent = textContent in docEl ? textContent : "innerText";
+})();/**
+ * @author Garret Smith
+ */
+
+
+(function() {
+
+ // Public exports.
+ APE.mixin(APE.dom, {
+ getScrollOffsets : getScrollOffsets,
+ getViewportDimensions : getViewportDimensions
+ });
+
+
+ var documentElement = "documentElement",
+ docEl = document[documentElement],
+ IS_BODY_ACTING_ROOT = docEl && docEl.clientWidth === 0;
+ docEl = null;
+
+ /** @memberOf APE.dom
+ * @name getScrollOffsets
+ * @function
+ * @return an object with width and height.
+ * This will exhibit a bug in Mozilla, which is often 5-7 pixels off.
+ */
+ function getScrollOffsets(win) {
+ win = win || window;
+ var f, d = win.document, node = d[documentElement];
+ if("pageXOffset"in win)
+ f = function() {
+ return{ left:win.pageXOffset, top: win.pageYOffset};
+ };
+ else {
+ if(IS_BODY_ACTING_ROOT) node = d.body;
+ f = function() {
+ return{ left : node.scrollLeft, top : node.scrollTop };
+ };
+ }
+ d = null;
+ this.getScrollOffsets = f;
+ return f();
+ }
+
+ /** @memberOf APE.dom
+ * @name getViewportDimensions
+ * @function
+ * @return an object with width and height.
+ */
+ function getViewportDimensions(win) {
+ win = win || window;
+ var node = win.document, d = node, propPrefix = "client",
+ wName, hName;
+
+ // Safari 2 uses document.clientWidth (default).
+ if(typeof d.clientWidth == "number");
+
+ // Opera < 9.5, or IE in quirks mode.
+ else if(IS_BODY_ACTING_ROOT || isDocumentElementHeightOff(win)) {
+ node = d.body;
+
+ // Modern Webkit, Firefox, IE.
+ // Might be undefined. 0 in older mozilla.
+ } else if(d[documentElement].clientHeight > 0){
+ node = d[documentElement];
+
+ // For older versions of Mozilla.
+ } else if(typeof innerHeight == "number") {
+ node = win;
+ propPrefix = "inner";
+ }
+ wName = propPrefix + "Width";
+ hName = propPrefix + "Height";
+
+ return (this.getViewportDimensions = function() {
+ return{width: node[wName], height: node[hName]};
+ })();
+
+ // Used to feature test Opera returning wrong values
+ // for documentElement.clientHeight.
+ function isDocumentElementHeightOff(win){
+ var d = win.document,
+ div = d.createElement('div');
+ div.style.height = "2500px";
+ d.body.insertBefore(div, d.body.firstChild);
+ var r = d[documentElement].clientHeight > 2400;
+ d.body.removeChild(div);
+ return r;
+ }
+ }
+})();/**
+ * @fileoverview
+ * @static
+ * @author Garrett Smith
+ * APE.dom package functions for calculating element position properties.
+ */
+/** @name APE.dom */
+
+(function() {
+ APE.mixin(
+ APE.dom,
+ /** @scope APE.dom */ {
+ getOffsetCoords : getOffsetCoords,
+ isAboveElement : isAboveElement,
+ isBelowElement : isBelowElement,
+ isInsideElement: isInsideElement
+ });
+
+ var doc = this.document,
+ inited,
+ documentElement = doc.documentElement,
+ round = Math.round, max = Math.max,
+
+ // Load-time constants.
+ IS_BODY_ACTING_ROOT = documentElement && documentElement.clientWidth === 0,
+
+ // IE, Safari, and Opera support clientTop. FF 2 doesn't
+ IS_CLIENT_TOP_SUPPORTED = 'clientTop'in documentElement,
+
+ TABLE = /^h/.test(documentElement.tagName) ? "table" : "TABLE",
+
+ IS_CURRENT_STYLE_SUPPORTED = 'currentStyle'in documentElement,
+
+ // XXX Opera <= 9.2 - parent border widths are included in offsetTop.
+ IS_PARENT_BODY_BORDER_INCLUDED_IN_OFFSET,
+
+ // XXX Opera <= 9.2 - body offsetTop is inherited to children's offsetTop
+ // when body position is not static.
+ // opera will inherit the offsetTop/offsetLeft of body for relative offsetParents.
+
+ IS_BODY_MARGIN_INHERITED,
+ IS_BODY_TOP_INHERITED,
+ IS_BODY_OFFSET_EXCLUDING_MARGIN,
+
+ // XXX Mozilla includes a table border in the TD's offsetLeft.
+ // There is 1 exception:
+ // When the TR has position: relative and the TD has block level content.
+ // In that case, the TD does not include the TABLE's border in it's offsetLeft.
+ // We do not account for this peculiar bug.
+ IS_TABLE_BORDER_INCLUDED_IN_TD_OFFSET,
+ IS_STATIC_BODY_OFFSET_PARENT_BUT_ABSOLUTE_CHILD_SUBTRACTS_BODY_BORDER_WIDTH,
+
+ IS_BODY_OFFSET_IGNORED_WHEN_BODY_RELATIVE_AND_LAST_CHILD_POSITIONED,
+
+ IS_CONTAINER_BODY_STATIC_INCLUDING_HTML_PADDING,
+ IS_CONTAINER_BODY_RELATIVE_INCLUDING_HTML_PADDING_REL_CHILD,
+ IS_CONTAINER_BODY_RELATIVE_INCLUDING_HTML_PADDING_ABS_CHILD,
+ IS_CONTAINER_BODY_INCLUDING_HTML_MARGIN,
+
+ // In Safari 2.0.4, BODY can have offsetTop when offsetParent is null.
+ // but offsetParent will be HTML (root) when HTML has position.
+ // IS_BODY_OFFSET_TOP_NO_OFFSETPARENT,
+
+ IS_COMPUTED_STYLE_SUPPORTED = doc.defaultView
+ && typeof doc.defaultView.getComputedStyle != "undefined",
+ getBoundingClientRect = "getBoundingClientRect",
+ relative = "relative",
+ borderTopWidth = "borderTopWidth",
+ borderLeftWidth = "borderLeftWidth",
+ positionedExp = /^(?:r|a)/,
+ absoluteExp = /^(?:a|f)/;
+
+ /**
+ * @memberOf APE.dom
+ * @param {HTMLElement} el you want coords of.
+ * @param {HTMLElement} positionedContainer container to look up to. The container must have
+ * position: (relative|absolute|fixed);
+ *
+ * @param {x:Number, y:Number} coords object to pass in.
+ * @return {x:Number, y:Number} coords of el from container.
+ *
+ * Passing in a container will improve performance in browsers that don't support
+ * getBoundingClientRect, but those that do will have a recursive call. Test accordingly.
+ *
+ * Container is sometimes irrelevant. Container is irrelevant when comparing positions
+ * of objects who do not share a common ancestor. In this case, pass in document.
+ *
+ *
+ * Passing in re-used coords can improve performance in all browsers.
+ * There is a side effect to passing in coords:
+ * For drag drop operations, reuse coords:
+ *
+ *
+ * // Update our coords:
+ * dom.getOffsetCoords(el, container, this.coords);
+ *
+ * Where this.coords = {};
+ */
+ function getOffsetCoords(el, container, coords) {
+
+ var doc = el.ownerDocument,
+ documentElement = doc.documentElement,
+ body = doc.body;
+
+ if(!container)
+ container = doc;
+
+ if(!coords)
+ coords = {x:0, y:0};
+
+ if(el === container) {
+ coords.x = coords.y = 0;
+ return coords;
+ }
+ if(getBoundingClientRect in el) {
+
+ // In BackCompat mode, body's border goes to the window. BODY is ICB.
+ var rootBorderEl = IS_BODY_ACTING_ROOT ? body : documentElement,
+ box = el[getBoundingClientRect](),
+ x = box.left + max( documentElement.scrollLeft, body.scrollLeft ),
+ y = box.top + max( documentElement.scrollTop, body.scrollTop ),
+ bodyCurrentStyle,
+ borderTop = rootBorderEl.clientTop,
+ borderLeft = rootBorderEl.clientLeft;
+
+ if(IS_CLIENT_TOP_SUPPORTED) {
+ x -= borderLeft;
+ y -= borderTop;
+ }
+ if(container !== doc) {
+ box = getOffsetCoords(container, null);
+ x -= box.x;
+ y -= box.y;
+ if(IS_BODY_ACTING_ROOT && container === body && IS_CLIENT_TOP_SUPPORTED) {
+ x -= borderLeft;
+ y -= borderTop;
+ }
+ }
+
+ if(IS_BODY_ACTING_ROOT && IS_CURRENT_STYLE_SUPPORTED
+ && container != doc && container !== body) {
+ bodyCurrentStyle = body.currentStyle;
+ x += parseFloat(bodyCurrentStyle.marginLeft)||0 +
+ parseFloat(bodyCurrentStyle.left)||0;
+ y += parseFloat(bodyCurrentStyle.marginTop)||0 +
+ parseFloat(bodyCurrentStyle.top)||0;
+ }
+ coords.x = x;
+ coords.y = y;
+
+ return coords;
+ }
+
+ // Crawling up the tree.
+ else if(IS_COMPUTED_STYLE_SUPPORTED) {
+ if(!inited) init();
+
+ var offsetLeft = el.offsetLeft,
+ offsetTop = el.offsetTop,
+ defaultView = doc.defaultView,
+ cs = defaultView.getComputedStyle(el, '');
+ if(cs.position == "fixed") {
+ coords.x = offsetLeft + documentElement.scrollLeft;
+ coords.y = offsetTop + documentElement.scrollTop;
+ return coords;
+ }
+ var bcs = defaultView.getComputedStyle(body,''),
+ isBodyStatic = !positionedExp.test(bcs.position),
+ lastOffsetParent = el,
+ parent = el.parentNode,
+ offsetParent = el.offsetParent;
+
+ // Main loop -----------------------------------------------------------------------
+ // Loop up, gathering scroll offsets on parentNodes.
+ // when we get to a parent that's an offsetParent, update
+ // the current offsetParent marker.
+ for( ; parent && parent !== container; parent = parent.parentNode) {
+ if(parent !== body && parent !== documentElement) {
+ offsetLeft -= parent.scrollLeft;
+ offsetTop -= parent.scrollTop;
+ }
+ if(parent === offsetParent) {
+ // If we get to BODY and have static position, skip it.
+ if(parent === body && isBodyStatic);
+ else {
+
+ // XXX Mozilla; Exclude static body; if static, it's offsetTop will be wrong.
+ // Include parent border widths. This matches behavior of clientRect approach.
+ // XXX Opera <= 9.2 includes parent border widths.
+ // See IS_PARENT_BODY_BORDER_INCLUDED_IN_OFFSET below.
+ if( !IS_PARENT_BODY_BORDER_INCLUDED_IN_OFFSET &&
+ ! (parent.tagName === TABLE && IS_TABLE_BORDER_INCLUDED_IN_TD_OFFSET)) {
+ var pcs = defaultView.getComputedStyle(parent, "");
+ // Mozilla doesn't support clientTop. Add borderWidth to the sum.
+ offsetLeft += parseFloat(pcs[borderLeftWidth])||0;
+ offsetTop += parseFloat(pcs[borderTopWidth])||0;
+ }
+ if(parent !== body) {
+ offsetLeft += offsetParent.offsetLeft;
+ offsetTop += offsetParent.offsetTop;
+ lastOffsetParent = offsetParent;
+ offsetParent = parent.offsetParent; // next marker to check for offsetParent.
+ }
+ }
+ }
+ }
+
+ //--------Post - loop, body adjustments----------------------------------------------
+ // Complications due to CSSOM Views - the browsers try to implement a contradictory
+ // spec: http://www.w3.org/TR/cssom-view/#offset-attributes
+
+ // XXX Mozilla, Safari 3, Opera: body margin is never
+ // included in body offsetLeft/offsetTop.
+ // This is wrong. Body's offsetTop should work like any other element.
+ // In Safari 2.0.4, BODY can have offsetParent, and even
+ // if it doesn't, it can still have offsetTop.
+ // But Safari 2.0.4 doubles offsetTop for relatively positioned elements
+ // and this script does not account for that.
+
+ // XXX Mozilla: When body has a border, body's offsetTop === negative borderWidth;
+ // Don't use body.offsetTop.
+ var bodyOffsetLeft = 0,
+ bodyOffsetTop = 0,
+ isLastElementAbsolute,
+ isLastOffsetElementPositioned,
+ isContainerDocOrDocEl = container === doc || container === documentElement,
+ dcs,
+ lastOffsetPosition;
+
+ // If the lastOffsetParent is document,
+ // it is not positioned (and hence, not absolute).
+ if(lastOffsetParent != doc) {
+ lastOffsetPosition = defaultView.getComputedStyle(lastOffsetParent,'').position;
+ isLastElementAbsolute = absoluteExp.test(lastOffsetPosition);
+ isLastOffsetElementPositioned = isLastElementAbsolute ||
+ positionedExp.test(lastOffsetPosition);
+ }
+
+ // do we need to add margin?
+ if(
+ (lastOffsetParent === el && el.offsetParent === body && !IS_BODY_MARGIN_INHERITED
+ && container !== body && !(isBodyStatic && IS_BODY_OFFSET_EXCLUDING_MARGIN))
+ || (IS_BODY_MARGIN_INHERITED && lastOffsetParent === el && !isLastOffsetElementPositioned)
+ || !isBodyStatic
+ && isLastOffsetElementPositioned
+ && IS_BODY_OFFSET_IGNORED_WHEN_BODY_RELATIVE_AND_LAST_CHILD_POSITIONED
+ && isContainerDocOrDocEl) {
+ bodyOffsetTop += parseFloat(bcs.marginTop)||0;
+ bodyOffsetLeft += parseFloat(bcs.marginLeft)||0;
+ }
+
+ // Case for padding on documentElement.
+ if(container === body) {
+ dcs = defaultView.getComputedStyle(documentElement,'');
+ if(
+ (!isBodyStatic &&
+ ((IS_CONTAINER_BODY_RELATIVE_INCLUDING_HTML_PADDING_REL_CHILD && !isLastElementAbsolute)
+ ||
+ (IS_CONTAINER_BODY_RELATIVE_INCLUDING_HTML_PADDING_ABS_CHILD && isLastElementAbsolute))
+ )
+ || isBodyStatic && IS_CONTAINER_BODY_STATIC_INCLUDING_HTML_PADDING
+ ) {
+ bodyOffsetTop -= parseFloat(dcs.paddingTop)||0;
+ bodyOffsetLeft -= parseFloat(dcs.paddingLeft)||0;
+ }
+
+ if(IS_CONTAINER_BODY_INCLUDING_HTML_MARGIN){
+ if(!isLastOffsetElementPositioned
+ || isLastOffsetElementPositioned && !isBodyStatic)
+ bodyOffsetTop -= parseFloat(dcs.marginTop)||0;
+ bodyOffsetLeft -= parseFloat(dcs.marginLeft)||0;
+ }
+ }
+ if(isBodyStatic) {
+ // XXX Safari subtracts border width of body from element's offsetTop (opera does it, too)
+ if(IS_STATIC_BODY_OFFSET_PARENT_BUT_ABSOLUTE_CHILD_SUBTRACTS_BODY_BORDER_WIDTH
+ // XXX: Safari will use HTML for containing block (CSS),
+ // but will subtract the body's border from the body's absolutely positioned
+ // child.offsetTop. Safari reports the child's offsetParent is BODY, but
+ // doesn't treat it that way (Safari bug).
+ || (!isLastElementAbsolute && !IS_PARENT_BODY_BORDER_INCLUDED_IN_OFFSET
+ && isContainerDocOrDocEl)) {
+ bodyOffsetTop += parseFloat(bcs[borderTopWidth]);
+ bodyOffsetLeft += parseFloat(bcs[borderLeftWidth]);
+ }
+ }
+
+ // body is positioned, and if it excludes margin,
+ // it's probably partly using the AVK-CSSOM disaster.
+ else if(IS_BODY_OFFSET_EXCLUDING_MARGIN) {
+ if(isContainerDocOrDocEl) {
+ if(!IS_BODY_TOP_INHERITED) {
+
+ // If the body is positioned, add its left and top value.
+ bodyOffsetTop += parseFloat(bcs.top)||0;
+ bodyOffsetLeft += parseFloat(bcs.left)||0;
+
+ // XXX: Opera normally include the parentBorder in offsetTop.
+ // We have a preventative measure in the loop above.
+ if(isLastElementAbsolute && IS_PARENT_BODY_BORDER_INCLUDED_IN_OFFSET) {
+ bodyOffsetTop += parseFloat(bcs[borderTopWidth]);
+ bodyOffsetLeft += parseFloat(bcs[borderLeftWidth]);
+ }
+ }
+
+ // Padding on documentElement is not included,
+ // but in this case, we're searching to documentElement, so we
+ // have to add it back in.
+ if(container === doc && !isBodyStatic
+ && !IS_CONTAINER_BODY_RELATIVE_INCLUDING_HTML_PADDING_REL_CHILD) {
+ if(!dcs) dcs = defaultView.getComputedStyle(documentElement,'');
+ bodyOffsetTop += parseFloat(dcs.paddingTop)||0;
+ bodyOffsetLeft += parseFloat(dcs.paddingLeft)||0;
+ }
+ }
+ else if(IS_BODY_TOP_INHERITED) {
+ bodyOffsetTop -= parseFloat(bcs.top);
+ bodyOffsetLeft -= parseFloat(bcs.left);
+ }
+ if(IS_BODY_MARGIN_INHERITED && (!isLastOffsetElementPositioned || container === body)) {
+ bodyOffsetTop -= parseFloat(bcs.marginTop)||0;
+ bodyOffsetLeft -= parseFloat(bcs.marginLeft)||0;
+ }
+ }
+ coords.x = round(offsetLeft + bodyOffsetLeft);
+ coords.y = round(offsetTop + bodyOffsetTop);
+
+ return coords;
+ }
+ }
+
+// For initializing load time constants.
+ function init() {
+ inited = true;
+ var body = doc.body;
+ if(!body) return;
+ var marginTop = "marginTop", position = "position", padding = "padding",
+ stat = "static",
+ border = "border", s = body.style,
+ bCssText = s.cssText,
+ bv = '1px solid transparent',
+ z = "0",
+ one = "1px",
+ offsetTop = "offsetTop",
+ ds = documentElement.style,
+ dCssText = ds.cssText,
+ x = doc.createElement('div'),
+ xs = x.style,
+ table = doc.createElement(TABLE);
+
+ s[padding] = s[marginTop] = s.top = z;
+ ds.position = stat;
+
+ s[border] = bv;
+
+ xs.margin = z;
+ xs[position] = stat;
+
+ // insertBefore - to avoid environment conditions with bottom script
+ // where appendChild would fail.
+ x = body.insertBefore(x, body.firstChild);
+ IS_PARENT_BODY_BORDER_INCLUDED_IN_OFFSET = (x[offsetTop] === 1);
+
+ s[border] = z;
+
+ // Table test.
+ table.innerHTML = "x ";
+ table.style[border] = "7px solid";
+ table.cellSpacing = table.cellPadding = z;
+
+ body.insertBefore(table, body.firstChild);
+ IS_TABLE_BORDER_INCLUDED_IN_TD_OFFSET = table.getElementsByTagName("td")[0].offsetLeft === 7;
+
+ body.removeChild(table);
+
+ // Now add margin to determine if body offsetTop is inherited.
+ s[marginTop] = one;
+ s[position] = relative;
+ IS_BODY_MARGIN_INHERITED = (x[offsetTop] === 1);
+
+ //IS_BODY_OFFSET_TOP_NO_OFFSETPARENT = body.offsetTop && !body.offsetParent;
+
+ IS_BODY_OFFSET_EXCLUDING_MARGIN = body[offsetTop] === 0;
+ s[marginTop] = z;
+ s.top = one;
+ IS_BODY_TOP_INHERITED = x[offsetTop] === 1;
+
+ s.top = z;
+ s[marginTop] = one;
+ s[position] = xs[position] = relative;
+ IS_BODY_OFFSET_IGNORED_WHEN_BODY_RELATIVE_AND_LAST_CHILD_POSITIONED = x[offsetTop] === 0;
+
+ xs[position] = "absolute";
+ s[position] = stat;
+ if(x.offsetParent === body) {
+ s[border] = bv;
+ xs.top = "2px";
+ // XXX Safari gets offsetParent wrong (says 'body' when body is static,
+ // but then positions element from ICB and then subtracts body's clientWidth.
+ // Safari is half wrong.
+ //
+ // XXX Mozilla says body is offsetParent but does NOT subtract EL's offsetLeft/Top.
+ // Mozilla is completely wrong.
+ IS_STATIC_BODY_OFFSET_PARENT_BUT_ABSOLUTE_CHILD_SUBTRACTS_BODY_BORDER_WIDTH = x[offsetTop] === 1;
+ s[border] = z;
+
+ xs[position] = relative;
+ ds[padding] = one;
+ s[marginTop] = z;
+
+ IS_CONTAINER_BODY_STATIC_INCLUDING_HTML_PADDING = x[offsetTop] === 3;
+
+ // Opera does not respect position: relative on BODY.
+ s[position] = relative;
+ IS_CONTAINER_BODY_RELATIVE_INCLUDING_HTML_PADDING_REL_CHILD = x[offsetTop] === 3;
+
+ xs[position] = "absolute";
+ IS_CONTAINER_BODY_RELATIVE_INCLUDING_HTML_PADDING_ABS_CHILD = x[offsetTop] === 3;
+
+ ds[padding] = z;
+ ds[marginTop] = one;
+
+ // Opera inherits HTML margin when body is relative and child is relative or absolute.
+ IS_CONTAINER_BODY_INCLUDING_HTML_MARGIN = x[offsetTop] === 3;
+ }
+
+ // xs.position = "fixed";
+ // FIXED_HAS_OFFSETPARENT = x.offsetParent != null;
+
+ body.removeChild(x);
+ s.cssText = bCssText||"";
+ ds.cssText = dCssText||"";
+ }
+
+ // TODO: add an optional commonAncestor parameter to the below.
+ /**
+ * @memberOf APE.dom
+ * @return {boolean} true if a is vertically within b's content area (and does not overlap,
+ * top nor bottom).
+ */
+ function isInsideElement(a, b) {
+ var aTop = getOffsetCoords(a).y,
+ bTop = getOffsetCoords(b).y;
+ return aTop + a.offsetHeight <= bTop + b.offsetHeight && aTop >= bTop;
+ }
+
+ /**
+ * @memberOf APE.dom
+ * @return {boolean} true if a overlaps the top of b's content area.
+ */
+ function isAboveElement(a, b) {
+ return (getOffsetCoords(a).y <= getOffsetCoords(b).y);
+ }
+
+ /**
+ * @memberOf APE.dom
+ * @return {boolean} true if a overlaps the bottom of b's content area.
+ */
+ function isBelowElement(a, b) {
+ return (getOffsetCoords(a).y + a.offsetHeight >= getOffsetCoords(b).y + b.offsetHeight);
+ }
+
+ // release from closure.
+ isInsideElement = isAboveElement = isBelowElement = null;
+})();
+/**
+ * @fileoverview dom ClassName Functions.
+ * @namespace APE.dom
+ * @author Garrett Smith
+ *
+ * ClassName functions are added to APE.dom.
+ *
+ */
+
+
+(function() {
+ APE.mixin(APE.dom,
+ {
+ hasToken : hasToken,
+ removeClass : removeClass,
+ addClass : addClass,
+ hasClass: hasClass,
+ getElementsByClassName : getElementsByClassName,
+ findAncestorWithClass : findAncestorWithClass
+ });
+
+ var className = "className";
+
+ /** @param {String} s string to search
+ * @param {String} token white-space delimited token the delimiter of the token.
+ * This is generally used with element className:
+ * @example if(dom.hasToken(el.className, "menu")) // element has class "menu".
+ */
+ function hasToken (s, token) {
+ return getTokenizedExp(token,"").test(s);
+ }
+
+ /** @param {HTMLElement} el
+ * @param {String} klass className token(s) to be removed.
+ * @description removes all occurances of klass from element's className.
+ */
+ function removeClass(el, klass) {
+ var cn = el[className];
+ if(!cn) return;
+ if(cn === klass) {
+ el[className] = "";
+ return;
+ }
+
+ el[className] = normalizeString(cn.replace(getTokenizedExp(klass, "g")," "));
+ }
+ /** @param {HTMLElement} el
+ * @param {String} klass className token(s) to be added.
+ * @description adds klass to the element's class attribute, if it does not
+ * exist.
+ */
+ function addClass(el, klass) {
+ if(!el[className]) el[className] = klass;
+ if(!getTokenizedExp(klass).test(el[className])) el[className] += " " + klass;
+ }
+
+ /** @param {HTMLElement} el
+ * @param {String} klass value to be tested against
+ * @description Checks whether an element has klass as part of its className
+ */
+ function hasClass(el, klass) {
+ if (!el[className]) return false;
+ return hasToken(el[className], klass);
+ }
+
+ var Exps = { };
+ function getTokenizedExp(token, flag){
+ var p = token + "$" + flag;
+ return (Exps[p] || (Exps[p] = RegExp("(?:^|\\s)"+token+"(?:$|\\s)", flag)));
+ }
+
+ /** @param {HTMLElement} el
+ * @param {String} tagName tagName to be searched. Use "*" for any tag.
+ * @param {String} klass className token(s) to be added.
+ * @return {Array|NodeList} Elements with the specified tagName and className.
+ * Searches will generally be faster with a smaller HTMLCollection
+ * and shorter tree.
+ */
+ function getElementsByClassName(el, tagName, klass){
+ if(!klass) return [];
+ tagName = tagName||"*";
+ if(el.getElementsByClassName && (tagName === "*")) {
+ // Native performance boost.
+ return el.getElementsByClassName(klass);
+ }
+ var exp = getTokenizedExp(klass,""),
+ collection = el.getElementsByTagName(tagName),
+ len = collection.length,
+ counter = 0,
+ i,
+ ret = Array(len);
+ for(i = 0; i < len; i++){
+ if(exp.test(collection[i][className]))
+ ret[counter++] = collection[i];
+ }
+ ret.length = counter; // trim array.
+ return ret;
+ }
+
+ /** Finds an ancestor with specified className
+ * @param {Element|Document} [container] where to stop traversing up (optional).
+ */
+ function findAncestorWithClass(el, klass, container) {
+ if(el == null || el === container)
+ return null;
+ var exp = getTokenizedExp(klass,""), parent;
+ for(parent = el.parentNode;parent != container;){
+ if( exp.test(parent[className]) )
+ return parent;
+ parent = parent.parentNode;
+ }
+ return null;
+ }
+
+var STRING_TRIM_EXP = /^\s+|\s+$/g,
+ WS_MULT_EXP = /\s\s+/g;
+function normalizeString(s) { return s.replace(STRING_TRIM_EXP,'').replace(WS_MULT_EXP, " "); }
+})();
+(function(){
+
+ var docEl = document.documentElement,
+ nodeType = "nodeType",
+ tagName = "tagName",
+ parentNode = "parentNode",
+ compareDocumentPosition = "compareDocumentPosition",
+ caseTransform = /^H/.test(docEl[tagName]) ? 'toUpperCase' : 'toLowerCase',
+ tagExp = /^[A-Z]/;
+
+ APE.mixin(
+ APE.dom, {
+ contains : getContains(),
+ findAncestorWithAttribute : findAncestorWithAttribute,
+ findAncestorWithTagName : findAncestorWithTagName,
+ findNextSiblingElement : findNextSiblingElement,
+ findPreviousSiblingElement : findPreviousSiblingElement,
+ getChildElements : getChildElements,
+ isTagName: isTagName
+ });
+
+ /**
+ * @memberOf APE.dom
+ * @return {boolean} true if a contains b.
+ * Internet Explorer's native contains() is different. It will return true for:
+ * code body.contains(body);
+ * Whereas APE.dom.contains will return false.
+ */
+
+ function getContains(){
+ if(compareDocumentPosition in docEl)
+ return function(el, b) {
+ return (el[compareDocumentPosition](b) & 16) !== 0;
+ };
+ else if('contains'in docEl)
+ return function(el, b) {
+ return el !== b && el.contains(b);
+ };
+ return function(el, b) {
+ if(el === b) return false;
+ while(el != b && (b = b[parentNode]) !== null);
+ return el === b;
+ };
+ }
+
+ //docEl = null;
+
+ /**
+ * @memberOf APE.dom
+ * @param {HTMLElement} el the element to start from.
+ * @param {String} attName the name of the attribute.
+ * @param {String} [value] the value of the attribute. If omitted, then only the
+ * presence of attribute is checked and the value is anything.
+ * @return {HTMLElement} closest ancestor with attName matching value.
+ * Returns null if not found.
+ */
+ function findAncestorWithAttribute(el, attName, value) {
+ for(var map, parent = el[parentNode];parent !== null;){
+ map = parent.attributes;
+ if(!map) return null;
+ var att = map[attName];
+ if(att && att.specified)
+ if(att.value === value || (value === undefined))
+ return parent;
+ parent = parent[parentNode];
+ }
+ return null;
+ }
+
+ function findAncestorWithTagName(el, tag) {
+ tag = tag[caseTransform]();
+ for(var parent = el[parentNode];parent !== null; ){
+ if( parent[tagName] === tag )
+ return parent;
+ parent = parent[parentNode];
+ }
+ return null;
+ }
+
+ /** Filter out text nodes and, in IE, comment nodes. */
+ function findNextSiblingElement(el) {
+ for(var ns = el.nextSibling; ns !== null; ns = ns.nextSibling)
+ if(ns[nodeType] === 1)
+ return ns;
+ return null;
+ }
+
+ function findPreviousSiblingElement(el) {
+ for(var ps = el.previousSibling; ps !== null; ps = ps.previousSibling) {
+ if(ps[nodeType] === 1)
+ return ps;
+ }
+ return null;
+ }
+
+ function getChildElements(el) {
+ var i = 0, ret = [], len, tag,
+ cn = el.children || el.childNodes, c;
+
+ // IE throws error when calling
+ // Array.prototype.slice.call(el.children).
+ // IE also includes comment nodes.
+ for(len = cn.length; i < len; i++) {
+ c = cn[i];
+ if(c[nodeType] !== 1) continue;
+ ret[ret.length] = c;
+ }
+ return ret;
+ }
+
+ /**
+ * @memberOf APE.dom
+ * @param {HTMLElement} el element whose tagName is to be tested
+ * @param {String} tagName value to test against
+ * @return {boolean} true if element's tagName matches given one
+ */
+ function isTagName(el, tagName) {
+ return el.tagName == tagName[caseTransform]();
+ }
+})();/**
+ * @requires APE.dom.Viewport
+ */
+/** @namespace APE.dom */
+
+
+(function() {
+
+ var hasEventTarget = "addEventListener"in this,
+ eventTarget = hasEventTarget ? "target" : "srcElement";
+
+ APE.mixin(
+ APE.dom.Event = {}, {
+ eventTarget : eventTarget,
+ getTarget : getTarget,
+ addCallback : addCallback,
+ removeCallback : removeCallback,
+ preventDefault : preventDefault,
+ stopPropagation: stopPropagation
+ });
+
+ /**
+ * @param {Event}
+ */
+ function stopPropagation(e) {
+ e = e || window.event;
+ var f;
+ if (typeof e.stopPropagation == 'function') {
+ f = function(e) {
+ e.stopPropagation();
+ }
+ }
+ else if ('cancelBubble' in e) {
+ f = function(e) {
+ e = e || window.event;
+ e.cancelBubble = true;
+ }
+ }
+ (APE.dom.Event.stopPropagation = f)(e);
+ }
+
+ function getTarget(e) {
+ return (e || event)[eventTarget];
+ }
+
+ /**
+ * If EventTarget is supported, cb (input param) is returned.
+ * Otherwise, a closure is used to wrap a call to the callback
+ * in context of o.
+ * @param {Object} o the desired would-be EventTarget
+ * @param {Function} cb the callback.
+ */
+ function getBoundCallback(o, cb) {
+ return hasEventTarget ? cb : function(ev) {
+ cb.call(o, ev);
+ };
+ }
+
+ /**
+ * addEventListener/attachEvent for DOM objects.
+ * @param {Object} o host object, Element, Document, Window.
+ * @param (string} type
+ * @param {Function} cb
+ * @return {Function} cb If EventTarget is not supported,
+ * a bound callback is created and returned. Otherwise,
+ * cb (input param) is returned.
+ */
+ function addCallback(o, type, cb) {
+ if (hasEventTarget) {
+ o.addEventListener(type, cb, false);
+ } else {
+ var bound = getBoundCallback(o, cb);
+ o.attachEvent("on" + type, bound);
+ }
+ return bound||cb;
+ }
+
+ /**
+ * removeEventListener/detachEvent for DOM objects.
+ * @param {EventTarget} o host object, Element, Document, Window.
+ * @param (string} type
+ * @param {Function} cb
+ * @return {Function} bound If EventTarget is not supported,
+ * a bound callback is created and returned. Otherwise,
+ * cb (input param) is returned.
+ */
+ function removeCallback(o, type, bound) {
+ if (hasEventTarget) {
+ o.removeEventListener(type, bound, false);
+ } else {
+ o.detachEvent("on" + type, bound);
+ }
+ return bound;
+ }
+
+ /**
+ * @param {Event}
+ */
+ function preventDefault(ev) {
+ ev = ev || event;
+ if(typeof ev.preventDefault == "function") {
+ ev.preventDefault();
+ } else if('returnValue' in ev) {
+ ev.returnValue = false;
+ }
+ }
+})();/**
+ * @requires viewport-f.js (for scrollOffsets in IE).
+ */
+APE.namespace("APE.dom.Event");
+(function() {
+ var dom = APE.dom,
+ Event = dom.Event;
+ Event.getCoords = getCoords;
+ function getCoords(e) {
+ var f;
+ if ("pageX" in e) {
+ f = function(e) {
+ return {
+ x : e.pageX,
+ y : e.pageY
+ };
+ };
+ } else {
+ f = function(e) {
+ var scrollOffsets = dom.getScrollOffsets();
+ e = e || window.event;
+ return {
+ x : e.clientX + scrollOffsets.left,
+ y : e.clientY + scrollOffsets.top
+ }
+ };
+ }
+ return (Event.getCoords = f)(e);
+ }
+})();/** @fileoverview
+ * Getting computed styles, opacity functions.
+ *
+ * @author Garrett Smith
+ */
+
+/**@name APE.dom
+ * @namespace*/
+
+(function(){
+
+ var multiLengthPropExp = /^(?:margin|(border)(Width)|padding)$/,
+ borderRadiusExp = /^[a-zA-Z]*[bB]orderRadius$/,
+ dom = APE.dom;
+
+ APE.mixin(dom, /** @scope APE.dom */{
+ /** @function */ getStyle : getStyle,
+ setOpacity : setOpacity,
+ getFilterOpacity : getFilterOpacity,
+
+ // Capture (border)(Width) because we need to put "Top" in the middle.
+ multiLengthPropExp : /^(?:margin|(border)(Width)|padding)$/,
+ borderRadiusExp : /^[a-zA-Z]*[bB]orderRadius$/,
+ tryGetShorthandValues : tryGetShorthandValues,
+ getCurrentStyleValueFromAuto : getCurrentStyleValueFromAuto,
+ getCurrentStyleClipValues : getCurrentStyleClipValues,
+ convertNonPixelToPixel : convertNonPixelToPixel
+ });
+
+ var view = document.defaultView,
+ getCS = "getComputedStyle",
+ IS_COMPUTED_STYLE = dom.IS_COMPUTED_STYLE,
+ currentStyle = "currentStyle",
+ style = "style";
+ view = null;
+
+ /**
+ * Special method for a browser that supports el.filters and not style.opacity.
+ * @memberOf APE.dom
+ * @param {HTMLElement} el the element to find opacity on.
+ * @return {ufloat} [0-1] amount of opacity.
+ * calling this method on a browser that does not support filters
+ * results in 1 being returned. Use dom.getStyle or dom.getCascadedStyle instead
+ */
+ function getFilterOpacity(el) {
+ var filters = el.filters;
+ if(!filters) return"";
+ try { // Will throw error if no DXImageTransform.
+ return filters['DXImageTransform.Microsoft.Alpha'].opacity/100;
+
+ } catch(e) {
+ try {
+ return filters('alpha').opacity/100;
+ } catch(e) {
+ return 1;
+ }
+ }
+ }
+
+ /**
+ * Cross-browser adapter method for style.filters vs style.opacity.
+ * @memberOf APE.dom
+ * @param {HTMLElement} el the element to set opacity on.
+ * @param {ufloat} i [0-1] the amount of opacity.
+ * @return {ufloat} [0-1] amount of opacity.
+ */
+ function setOpacity(el, i) {
+ var s = el[style], cs;
+ if("opacity"in s) {
+ s.opacity = i;
+ }
+ else if("filter"in s) {
+ cs = el[currentStyle];
+ s.filter = 'alpha(opacity=' + (i * 100) + ')';
+ if(cs && ("hasLayout"in cs) && !cs.hasLayout) {
+ style.zoom = 1;
+ }
+ }
+ }
+
+ /**
+ * @memberOf APE.dom
+ * @name getStyle
+ *
+ * @function
+ * @description returns the computed style of property p of el.
+ * Returns different results in IE, so user beware! If your
+ * styleSheet has units like "em" or "in", this method does
+ * not attempt to convert those to px.
+ *
+ * Use "cssFloat" for getting an element's float and special
+ * "filters" treatment for "opacity".
+ *
+ * @param {HTMLElement} el the element to set opacity on.
+ * @param {String} p the property to retrieve.
+ * @return {String} the computed style value or the empty string if no value was found.
+ */
+ function getStyle(el, p) {
+ var value = "", cs, matches, splitVal, i, len, doc = el.ownerDocument,
+ defaultView = doc.defaultView;
+ if(IS_COMPUTED_STYLE) {
+ cs = defaultView[getCS](el, "");
+ if(p == "borderRadius" && !("borderRadius"in cs)) {
+ p = "MozBorderRadius"in cs ? "MozBorderRadius" :
+ "WebkitBorderRadius"in cs ? "WebkitBorderRadius" : "";
+ }
+
+ if(!(p in cs)) return "";
+ value = cs[p];
+ if(value === "") {
+ // would try to get a rect, but Webkit doesn't support that.
+ value = (tryGetShorthandValues(cs, p)).join(" ");
+ }
+ }
+ else {
+ cs = el[currentStyle];
+ if(p == "opacity" && !("opacity"in el[currentStyle]))
+ value = getFilterOpacity(el);
+ else {
+ if(p == "cssFloat")
+ p = "styleFloat";
+ value = cs[p];
+
+ if(p == "clip" && !value && ("clipTop"in cs)) {
+ value = getCurrentStyleClipValues(el, cs);
+ }
+ else if(value == "auto")
+ value = getCurrentStyleValueFromAuto(el, p);
+ else if(!(p in cs)) return "";
+ }
+ matches = nonPixelExp.exec(value);
+ if(matches) {
+ splitVal = value.split(" ");
+ splitVal[0] = convertNonPixelToPixel( el, matches);
+ for(i = 1, len = splitVal.length; i < len; i++) {
+ matches = nonPixelExp.exec(splitVal[i]);
+ splitVal[i] = convertNonPixelToPixel( el, matches);
+ }
+ value = splitVal.join(" ");
+ }
+ }
+ return value;
+ }
+
+ function getCurrentStyleClipValues(el, cs) {
+ var values = [], i = 0, prop;
+ for( ;i < 4; i++){
+ prop = props[i];
+ clipValue = cs['clip'+prop];
+ if(clipValue == "auto") {
+ clipValue = (prop == "Left" || prop == "Top" ? "0px" : prop == "Right" ?
+ el.offsetWidth + px : el.offsetHeight + px);
+ }
+ values.push(clipValue);
+ }
+ return {
+ top:values[0], right:values[1], bottom:values[2], left:values[3],
+ toString : function() {return 'rect(' + values.join(' ')+')';}
+ };
+ }
+
+ var sty = document.documentElement[style],
+ floatProp = 'cssFloat'in sty ? 'cssFloat': 'styleFloat',
+ props = ["Top", "Right", "Bottom", "Left"],
+ cornerProps = ["Topright", "Bottomright", "Bottomleft", "Topleft"];
+ sty = null;
+
+ function getCurrentStyleValueFromAuto(el, p) {
+
+ var s = el[style], v, borderWidth, doc = el.ownerDocument;
+ if("pixelWidth"in s && pixelDimensionExp.test(p)) {
+ var pp = "pixel" + (p.charAt(0).toUpperCase()) + p.substring(1);
+ v = s[pp];
+ if(v === 0) {
+ if(p == "width") {
+ borderWidth = parseFloat(getStyle(el, "borderRightWidth"))||0;
+ paddingWidth = parseFloat(getStyle(el, "paddingLeft"))||0
+ + parseFloat(getStyle(el, "paddingRight"))||0;
+
+ return el.offsetWidth - el.clientLeft - borderWidth - paddingWidth + px;
+ }
+ else if(p == "height") {
+ borderWidth = parseFloat(getStyle(el, "borderBottomWidth"))||0;
+ paddingWidth = parseFloat(getStyle(el, "paddingTop"))||0
+ + parseFloat(getStyle(el, "paddingBottom"))||0;
+ return el.offsetHeight - el.clientTop - borderWidth + px;
+ }
+ }
+ return s[pp] + px;
+ }
+ if(p == "margin" && el[currentStyle].position != "absolute" &&
+ doc.compatMode != "BackCompat") {
+ v = parseFloat(getStyle(el.parentNode, 'width')) - el.offsetWidth;
+ if(v == 0) return "0px";
+ v = "0px " + v;
+ return v + " " + v;
+ }
+
+ // Can't get borderWidth because we only have clientTop and clientLeft.
+ }
+
+ // TODO: Consider removing this; "don't do that."
+ /**
+ * Tries to get a shorthand value for margin|padding|borderWidth.
+ * @return {[string]} Either 4 values or, if all four values are equal,
+ * then one collapsed value (in an array).
+ */
+ function tryGetShorthandValues(cs, p) {
+ var multiMatch = multiLengthPropExp.exec(p),
+ prefix, suffix,
+ prevValue, nextValue,
+ values,
+ allEqual = true,
+ propertyList,
+ i = 1;
+
+ if(multiMatch && multiMatch[0]) {
+ propertyList = props;
+ prefix = multiMatch[1]||multiMatch[0];
+ suffix = multiMatch[2] || ""; // ["borderWidth", "border", "Width"]
+ }
+ else if(borderRadiusExp.test(p)) {
+ propertyList = cornerProps;
+ prefix = borderRadiusExp.exec(p)[0];
+ suffix = "";
+ }
+ else return [""];
+
+ prevValue = cs[prefix + propertyList[0] + suffix ];
+ values = [prevValue];
+
+ while(i < 4) {
+ nextValue = cs[prefix + propertyList[i] + suffix];
+ allEqual = allEqual && nextValue == prevValue;
+ prevValue = nextValue;
+ values[i++] = nextValue;
+ }
+ if(allEqual)
+ return [prevValue];
+ return values;
+ }
+
+ var nonPixelExp = /(-?\d+|(?:-?\d*\.\d+))(?:em|ex|pt|pc|in|cm|mm\s*)/,
+ pixelDimensionExp = /width|height|top|left/,
+ px = "px";
+
+ /**
+ * @requires nonPixelExp
+ * @param {HTMLElement} el
+ * @param {Array} String[] of matches from nonPixelExp.exec( val ).
+ */
+ function convertNonPixelToPixel(el, matches) {
+
+ if(el.runtimeStyle) {
+
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels.
+
+ var val = matches[0]; // grab the -1.2em or whatever.
+ if(parseFloat(val) == 0) {
+ return "0px";
+ }
+
+ var s = el[style],
+ sLeft = s.left,
+ rs = el.runtimeStyle,
+ rsLeft = rs.left;
+
+ rs.left = el[currentStyle].left;
+ s.left = (val || 0);
+
+ // The element does not need to have position: to get values.
+ // IE's math is a little off with converting em to px; IE rounds to
+ // the nearest pixel.
+ val = s.pixelLeft + px;
+ // put it back.
+ s.left = sLeft;
+ rs.left = rsLeft;
+ return val;
+ }
+ }
+})();/**
+ * XXX: IE Fix for getElementById returning elements by name.
+ */
+(function(){
+ var d = document, x = d.body, c,
+ g = 'getElementById',
+ orig = document[g];
+
+ if(!x) return setTimeout(arguments.callee,50);
+
+ try {
+ c = d.createElement("");
+ x.insertBefore(c, x.firstChild);
+ if(d[g]('0')){
+ x.removeChild(c);
+ d[g] = getElementById;
+ }
+ } catch(x){}
+ function getElementById(id) {
+ var el = Function.prototype.call.call(orig, this, id), els, i;
+
+ if(el && el.id == id) return el;
+ els = this.getElementsByName(id);
+
+ for(i = 0; i < els.length; i++)
+ if(els[i].id === id) return els[i];
+ return null;
+ };
+})();
+
+(function(){
+
+ APE.mixin(APE.dom, {
+ selectOptionByValue: selectOptionByValue
+ });
+
+ /**
+ * @method selectOptionByValue
+ * @param {HTMLElement} element
+ * @param {String} value
+ */
+ function selectOptionByValue(element, value) {
+ for (var i=0, l=element.options.length; i0){this.init(x,y);}}
- Point2D.prototype = {
- constructor:Point2D,
- init:function(x,y){this.x=x;this.y=y;},
- add:function(that){return new Point2D(this.x+that.x,this.y+that.y);},
- addEquals:function(that){this.x+=that.x;this.y+=that.y;return this;},
- scalarAdd:function(scalar){return new Point2D(this.x+scalar,this.y+scalar);},
- scalarAddEquals:function(scalar){this.x+=scalar;this.y+=scalar;return this;},
- subtract:function(that){return new Point2D(this.x-that.x,this.y-that.y);},
- subtractEquals:function(that){this.x-=that.x;this.y-=that.y;return this;},
- scalarSubtract:function(scalar){return new Point2D(this.x-scalar,this.y-scalar);},
- scalarSubtractEquals:function(scalar){this.x-=scalar;this.y-=scalar;return this;},
- multiply:function(scalar){return new Point2D(this.x*scalar,this.y*scalar);},
- multiplyEquals:function(scalar){this.x*=scalar;this.y*=scalar;return this;},
- divide:function(scalar){return new Point2D(this.x/scalar, this.y/scalar);},
- divideEquals:function(scalar){this.x/=scalar;this.y/=scalar;return this;},
- eq:function(that){return(this.x==that.x&&this.y==that.y);},
- lt:function(that){return(this.xthat.x&&this.y>that.y);},
- gte:function(that){return(this.x>=that.x&&this.y>=that.y);},
- lerp:function(that,t){return new Point2D(this.x+(that.x-this.x)*t,this.y+(that.y-this.y)*t);},
- distanceFrom:function(that){var dx=this.x-that.x;var dy=this.y-that.y;return Math.sqrt(dx*dx+dy*dy);},
- min:function(that){return new Point2D(Math.min(this.x,that.x),Math.min(this.y,that.y));},
- max:function(that){return new Point2D(Math.max(this.x,that.x),Math.max(this.y,that.y));},
- toString:function(){return this.x+","+this.y;},
- setXY:function(x,y){this.x=x;this.y=y;},
- setFromPoint:function(that){this.x=that.x;this.y=that.y;},
- swap:function(that){var x=this.x;var y=this.y;this.x=that.x;this.y=that.y;that.x=x;that.y=y;}
- };
-
- Canvas.Point2D = Point2D;
-
/*****
*
* The contents of this file were written by Kevin Lindsey
@@ -45,42 +12,210 @@
* A Perl utility written by Kevin Lindsey (kevin@kevlindev.com)
*
*****/
- function Intersection(status){if(arguments.length>0){this.init(status);}}
- Intersection.prototype.init=function(status){this.status=status;this.points=new Array();};
- Intersection.prototype.appendPoint=function(point){this.points.push(point);};
- Intersection.prototype.appendPoints=function(points){this.points=this.points.concat(points);};
- Intersection.intersectShapes=function(shape1,shape2){var ip1=shape1.getIntersectionParams();var ip2=shape2.getIntersectionParams();var result;if(ip1!=null&&ip2!=null){if(ip1.name=="Path"){result=Intersection.intersectPathShape(shape1,shape2);}else if(ip2.name=="Path"){result=Intersection.intersectPathShape(shape2,shape1);}else{var method;var params;if(ip1.name0&&yRoots.length>0){checkRoots:for(var j=0;j0&&yRoots.length>0){var TOLERANCE=1e-4;checkRoots:for(var j=0;j0)result.status="Intersection";return result;};
- Intersection.intersectBezier2Circle=function(p1,p2,p3,c,r){return Intersection.intersectBezier2Ellipse(p1,p2,p3,c,r,r);};
- Intersection.intersectBezier2Ellipse=function(p1,p2,p3,ec,rx,ry){var a,b;var c2,c1,c0;var result=new Intersection("No Intersection");a=p2.multiply(-2);c2=p1.add(a.add(p3));a=p1.multiply(-2);b=p2.multiply(2);c1=a.add(b);c0=new Point2D(p1.x,p1.y);var rxrx=rx*rx;var ryry=ry*ry;var roots=new Polynomial(ryry*c2.x*c2.x+rxrx*c2.y*c2.y,2*(ryry*c2.x*c1.x+rxrx*c2.y*c1.y),ryry*(2*c2.x*c0.x+c1.x*c1.x)+rxrx*(2*c2.y*c0.y+c1.y*c1.y)-2*(ryry*ec.x*c2.x+rxrx*ec.y*c2.y),2*(ryry*c1.x*(c0.x-ec.x)+rxrx*c1.y*(c0.y-ec.y)),ryry*(c0.x*c0.x+ec.x*ec.x)+rxrx*(c0.y*c0.y+ec.y*ec.y)-2*(ryry*ec.x*c0.x+rxrx*ec.y*c0.y)-rxrx*ryry).getRoots();for(var i=0;i0)result.status="Intersection";return result;};
- Intersection.intersectBezier2Line=function(p1,p2,p3,a1,a2){var a,b;var c2,c1,c0;var cl;var n;var min=a1.min(a2);var max=a1.max(a2);var result=new Intersection("No Intersection");a=p2.multiply(-2);c2=p1.add(a.add(p3));a=p1.multiply(-2);b=p2.multiply(2);c1=a.add(b);c0=new Point2D(p1.x,p1.y);n=new Vector2D(a1.y-a2.y,a2.x-a1.x);cl=a1.x*a2.y-a2.x*a1.y;roots=new Polynomial(n.dot(c2),n.dot(c1),n.dot(c0)+cl).getRoots();for(var i=0;i0)result.status="Intersection";return result;};
- Intersection.intersectBezier2Rectangle=function(p1,p2,p3,r1,r2){var min=r1.min(r2);var max=r1.max(r2);var topRight=new Point2D(max.x,min.y);var bottomLeft=new Point2D(min.x,max.y);var inter1=Intersection.intersectBezier2Line(p1,p2,p3,min,topRight);var inter2=Intersection.intersectBezier2Line(p1,p2,p3,topRight,max);var inter3=Intersection.intersectBezier2Line(p1,p2,p3,max,bottomLeft);var inter4=Intersection.intersectBezier2Line(p1,p2,p3,bottomLeft,min);var result=new Intersection("No Intersection");result.appendPoints(inter1.points);result.appendPoints(inter2.points);result.appendPoints(inter3.points);result.appendPoints(inter4.points);if(result.points.length>0)result.status="Intersection";return result;};
- Intersection.intersectBezier3Bezier3=function(a1,a2,a3,a4,b1,b2,b3,b4){var a,b,c,d;var c13,c12,c11,c10;var c23,c22,c21,c20;var result=new Intersection("No Intersection");a=a1.multiply(-1);b=a2.multiply(3);c=a3.multiply(-3);d=a.add(b.add(c.add(a4)));c13=new Vector2D(d.x,d.y);a=a1.multiply(3);b=a2.multiply(-6);c=a3.multiply(3);d=a.add(b.add(c));c12=new Vector2D(d.x,d.y);a=a1.multiply(-3);b=a2.multiply(3);c=a.add(b);c11=new Vector2D(c.x,c.y);c10=new Vector2D(a1.x,a1.y);a=b1.multiply(-1);b=b2.multiply(3);c=b3.multiply(-3);d=a.add(b.add(c.add(b4)));c23=new Vector2D(d.x,d.y);a=b1.multiply(3);b=b2.multiply(-6);c=b3.multiply(3);d=a.add(b.add(c));c22=new Vector2D(d.x,d.y);a=b1.multiply(-3);b=b2.multiply(3);c=a.add(b);c21=new Vector2D(c.x,c.y);c20=new Vector2D(b1.x,b1.y);var c10x2=c10.x*c10.x;var c10x3=c10.x*c10.x*c10.x;var c10y2=c10.y*c10.y;var c10y3=c10.y*c10.y*c10.y;var c11x2=c11.x*c11.x;var c11x3=c11.x*c11.x*c11.x;var c11y2=c11.y*c11.y;var c11y3=c11.y*c11.y*c11.y;var c12x2=c12.x*c12.x;var c12x3=c12.x*c12.x*c12.x;var c12y2=c12.y*c12.y;var c12y3=c12.y*c12.y*c12.y;var c13x2=c13.x*c13.x;var c13x3=c13.x*c13.x*c13.x;var c13y2=c13.y*c13.y;var c13y3=c13.y*c13.y*c13.y;var c20x2=c20.x*c20.x;var c20x3=c20.x*c20.x*c20.x;var c20y2=c20.y*c20.y;var c20y3=c20.y*c20.y*c20.y;var c21x2=c21.x*c21.x;var c21x3=c21.x*c21.x*c21.x;var c21y2=c21.y*c21.y;var c22x2=c22.x*c22.x;var c22x3=c22.x*c22.x*c22.x;var c22y2=c22.y*c22.y;var c23x2=c23.x*c23.x;var c23x3=c23.x*c23.x*c23.x;var c23y2=c23.y*c23.y;var c23y3=c23.y*c23.y*c23.y;var poly=new Polynomial(-c13x3*c23y3+c13y3*c23x3-3*c13.x*c13y2*c23x2*c23.y+3*c13x2*c13.y*c23.x*c23y2,-6*c13.x*c22.x*c13y2*c23.x*c23.y+6*c13x2*c13.y*c22.y*c23.x*c23.y+3*c22.x*c13y3*c23x2-3*c13x3*c22.y*c23y2-3*c13.x*c13y2*c22.y*c23x2+3*c13x2*c22.x*c13.y*c23y2,-6*c21.x*c13.x*c13y2*c23.x*c23.y-6*c13.x*c22.x*c13y2*c22.y*c23.x+6*c13x2*c22.x*c13.y*c22.y*c23.y+3*c21.x*c13y3*c23x2+3*c22x2*c13y3*c23.x+3*c21.x*c13x2*c13.y*c23y2-3*c13.x*c21.y*c13y2*c23x2-3*c13.x*c22x2*c13y2*c23.y+c13x2*c13.y*c23.x*(6*c21.y*c23.y+3*c22y2)+c13x3*(-c21.y*c23y2-2*c22y2*c23.y-c23.y*(2*c21.y*c23.y+c22y2)),c11.x*c12.y*c13.x*c13.y*c23.x*c23.y-c11.y*c12.x*c13.x*c13.y*c23.x*c23.y+6*c21.x*c22.x*c13y3*c23.x+3*c11.x*c12.x*c13.x*c13.y*c23y2+6*c10.x*c13.x*c13y2*c23.x*c23.y-3*c11.x*c12.x*c13y2*c23.x*c23.y-3*c11.y*c12.y*c13.x*c13.y*c23x2-6*c10.y*c13x2*c13.y*c23.x*c23.y-6*c20.x*c13.x*c13y2*c23.x*c23.y+3*c11.y*c12.y*c13x2*c23.x*c23.y-2*c12.x*c12y2*c13.x*c23.x*c23.y-6*c21.x*c13.x*c22.x*c13y2*c23.y-6*c21.x*c13.x*c13y2*c22.y*c23.x-6*c13.x*c21.y*c22.x*c13y2*c23.x+6*c21.x*c13x2*c13.y*c22.y*c23.y+2*c12x2*c12.y*c13.y*c23.x*c23.y+c22x3*c13y3-3*c10.x*c13y3*c23x2+3*c10.y*c13x3*c23y2+3*c20.x*c13y3*c23x2+c12y3*c13.x*c23x2-c12x3*c13.y*c23y2-3*c10.x*c13x2*c13.y*c23y2+3*c10.y*c13.x*c13y2*c23x2-2*c11.x*c12.y*c13x2*c23y2+c11.x*c12.y*c13y2*c23x2-c11.y*c12.x*c13x2*c23y2+2*c11.y*c12.x*c13y2*c23x2+3*c20.x*c13x2*c13.y*c23y2-c12.x*c12y2*c13.y*c23x2-3*c20.y*c13.x*c13y2*c23x2+c12x2*c12.y*c13.x*c23y2-3*c13.x*c22x2*c13y2*c22.y+c13x2*c13.y*c23.x*(6*c20.y*c23.y+6*c21.y*c22.y)+c13x2*c22.x*c13.y*(6*c21.y*c23.y+3*c22y2)+c13x3*(-2*c21.y*c22.y*c23.y-c20.y*c23y2-c22.y*(2*c21.y*c23.y+c22y2)-c23.y*(2*c20.y*c23.y+2*c21.y*c22.y)),6*c11.x*c12.x*c13.x*c13.y*c22.y*c23.y+c11.x*c12.y*c13.x*c22.x*c13.y*c23.y+c11.x*c12.y*c13.x*c13.y*c22.y*c23.x-c11.y*c12.x*c13.x*c22.x*c13.y*c23.y-c11.y*c12.x*c13.x*c13.y*c22.y*c23.x-6*c11.y*c12.y*c13.x*c22.x*c13.y*c23.x-6*c10.x*c22.x*c13y3*c23.x+6*c20.x*c22.x*c13y3*c23.x+6*c10.y*c13x3*c22.y*c23.y+2*c12y3*c13.x*c22.x*c23.x-2*c12x3*c13.y*c22.y*c23.y+6*c10.x*c13.x*c22.x*c13y2*c23.y+6*c10.x*c13.x*c13y2*c22.y*c23.x+6*c10.y*c13.x*c22.x*c13y2*c23.x-3*c11.x*c12.x*c22.x*c13y2*c23.y-3*c11.x*c12.x*c13y2*c22.y*c23.x+2*c11.x*c12.y*c22.x*c13y2*c23.x+4*c11.y*c12.x*c22.x*c13y2*c23.x-6*c10.x*c13x2*c13.y*c22.y*c23.y-6*c10.y*c13x2*c22.x*c13.y*c23.y-6*c10.y*c13x2*c13.y*c22.y*c23.x-4*c11.x*c12.y*c13x2*c22.y*c23.y-6*c20.x*c13.x*c22.x*c13y2*c23.y-6*c20.x*c13.x*c13y2*c22.y*c23.x-2*c11.y*c12.x*c13x2*c22.y*c23.y+3*c11.y*c12.y*c13x2*c22.x*c23.y+3*c11.y*c12.y*c13x2*c22.y*c23.x-2*c12.x*c12y2*c13.x*c22.x*c23.y-2*c12.x*c12y2*c13.x*c22.y*c23.x-2*c12.x*c12y2*c22.x*c13.y*c23.x-6*c20.y*c13.x*c22.x*c13y2*c23.x-6*c21.x*c13.x*c21.y*c13y2*c23.x-6*c21.x*c13.x*c22.x*c13y2*c22.y+6*c20.x*c13x2*c13.y*c22.y*c23.y+2*c12x2*c12.y*c13.x*c22.y*c23.y+2*c12x2*c12.y*c22.x*c13.y*c23.y+2*c12x2*c12.y*c13.y*c22.y*c23.x+3*c21.x*c22x2*c13y3+3*c21x2*c13y3*c23.x-3*c13.x*c21.y*c22x2*c13y2-3*c21x2*c13.x*c13y2*c23.y+c13x2*c22.x*c13.y*(6*c20.y*c23.y+6*c21.y*c22.y)+c13x2*c13.y*c23.x*(6*c20.y*c22.y+3*c21y2)+c21.x*c13x2*c13.y*(6*c21.y*c23.y+3*c22y2)+c13x3*(-2*c20.y*c22.y*c23.y-c23.y*(2*c20.y*c22.y+c21y2)-c21.y*(2*c21.y*c23.y+c22y2)-c22.y*(2*c20.y*c23.y+2*c21.y*c22.y)),c11.x*c21.x*c12.y*c13.x*c13.y*c23.y+c11.x*c12.y*c13.x*c21.y*c13.y*c23.x+c11.x*c12.y*c13.x*c22.x*c13.y*c22.y-c11.y*c12.x*c21.x*c13.x*c13.y*c23.y-c11.y*c12.x*c13.x*c21.y*c13.y*c23.x-c11.y*c12.x*c13.x*c22.x*c13.y*c22.y-6*c11.y*c21.x*c12.y*c13.x*c13.y*c23.x-6*c10.x*c21.x*c13y3*c23.x+6*c20.x*c21.x*c13y3*c23.x+2*c21.x*c12y3*c13.x*c23.x+6*c10.x*c21.x*c13.x*c13y2*c23.y+6*c10.x*c13.x*c21.y*c13y2*c23.x+6*c10.x*c13.x*c22.x*c13y2*c22.y+6*c10.y*c21.x*c13.x*c13y2*c23.x-3*c11.x*c12.x*c21.x*c13y2*c23.y-3*c11.x*c12.x*c21.y*c13y2*c23.x-3*c11.x*c12.x*c22.x*c13y2*c22.y+2*c11.x*c21.x*c12.y*c13y2*c23.x+4*c11.y*c12.x*c21.x*c13y2*c23.x-6*c10.y*c21.x*c13x2*c13.y*c23.y-6*c10.y*c13x2*c21.y*c13.y*c23.x-6*c10.y*c13x2*c22.x*c13.y*c22.y-6*c20.x*c21.x*c13.x*c13y2*c23.y-6*c20.x*c13.x*c21.y*c13y2*c23.x-6*c20.x*c13.x*c22.x*c13y2*c22.y+3*c11.y*c21.x*c12.y*c13x2*c23.y-3*c11.y*c12.y*c13.x*c22x2*c13.y+3*c11.y*c12.y*c13x2*c21.y*c23.x+3*c11.y*c12.y*c13x2*c22.x*c22.y-2*c12.x*c21.x*c12y2*c13.x*c23.y-2*c12.x*c21.x*c12y2*c13.y*c23.x-2*c12.x*c12y2*c13.x*c21.y*c23.x-2*c12.x*c12y2*c13.x*c22.x*c22.y-6*c20.y*c21.x*c13.x*c13y2*c23.x-6*c21.x*c13.x*c21.y*c22.x*c13y2+6*c20.y*c13x2*c21.y*c13.y*c23.x+2*c12x2*c21.x*c12.y*c13.y*c23.y+2*c12x2*c12.y*c21.y*c13.y*c23.x+2*c12x2*c12.y*c22.x*c13.y*c22.y-3*c10.x*c22x2*c13y3+3*c20.x*c22x2*c13y3+3*c21x2*c22.x*c13y3+c12y3*c13.x*c22x2+3*c10.y*c13.x*c22x2*c13y2+c11.x*c12.y*c22x2*c13y2+2*c11.y*c12.x*c22x2*c13y2-c12.x*c12y2*c22x2*c13.y-3*c20.y*c13.x*c22x2*c13y2-3*c21x2*c13.x*c13y2*c22.y+c12x2*c12.y*c13.x*(2*c21.y*c23.y+c22y2)+c11.x*c12.x*c13.x*c13.y*(6*c21.y*c23.y+3*c22y2)+c21.x*c13x2*c13.y*(6*c20.y*c23.y+6*c21.y*c22.y)+c12x3*c13.y*(-2*c21.y*c23.y-c22y2)+c10.y*c13x3*(6*c21.y*c23.y+3*c22y2)+c11.y*c12.x*c13x2*(-2*c21.y*c23.y-c22y2)+c11.x*c12.y*c13x2*(-4*c21.y*c23.y-2*c22y2)+c10.x*c13x2*c13.y*(-6*c21.y*c23.y-3*c22y2)+c13x2*c22.x*c13.y*(6*c20.y*c22.y+3*c21y2)+c20.x*c13x2*c13.y*(6*c21.y*c23.y+3*c22y2)+c13x3*(-2*c20.y*c21.y*c23.y-c22.y*(2*c20.y*c22.y+c21y2)-c20.y*(2*c21.y*c23.y+c22y2)-c21.y*(2*c20.y*c23.y+2*c21.y*c22.y)),-c10.x*c11.x*c12.y*c13.x*c13.y*c23.y+c10.x*c11.y*c12.x*c13.x*c13.y*c23.y+6*c10.x*c11.y*c12.y*c13.x*c13.y*c23.x-6*c10.y*c11.x*c12.x*c13.x*c13.y*c23.y-c10.y*c11.x*c12.y*c13.x*c13.y*c23.x+c10.y*c11.y*c12.x*c13.x*c13.y*c23.x+c11.x*c11.y*c12.x*c12.y*c13.x*c23.y-c11.x*c11.y*c12.x*c12.y*c13.y*c23.x+c11.x*c20.x*c12.y*c13.x*c13.y*c23.y+c11.x*c20.y*c12.y*c13.x*c13.y*c23.x+c11.x*c21.x*c12.y*c13.x*c13.y*c22.y+c11.x*c12.y*c13.x*c21.y*c22.x*c13.y-c20.x*c11.y*c12.x*c13.x*c13.y*c23.y-6*c20.x*c11.y*c12.y*c13.x*c13.y*c23.x-c11.y*c12.x*c20.y*c13.x*c13.y*c23.x-c11.y*c12.x*c21.x*c13.x*c13.y*c22.y-c11.y*c12.x*c13.x*c21.y*c22.x*c13.y-6*c11.y*c21.x*c12.y*c13.x*c22.x*c13.y-6*c10.x*c20.x*c13y3*c23.x-6*c10.x*c21.x*c22.x*c13y3-2*c10.x*c12y3*c13.x*c23.x+6*c20.x*c21.x*c22.x*c13y3+2*c20.x*c12y3*c13.x*c23.x+2*c21.x*c12y3*c13.x*c22.x+2*c10.y*c12x3*c13.y*c23.y-6*c10.x*c10.y*c13.x*c13y2*c23.x+3*c10.x*c11.x*c12.x*c13y2*c23.y-2*c10.x*c11.x*c12.y*c13y2*c23.x-4*c10.x*c11.y*c12.x*c13y2*c23.x+3*c10.y*c11.x*c12.x*c13y2*c23.x+6*c10.x*c10.y*c13x2*c13.y*c23.y+6*c10.x*c20.x*c13.x*c13y2*c23.y-3*c10.x*c11.y*c12.y*c13x2*c23.y+2*c10.x*c12.x*c12y2*c13.x*c23.y+2*c10.x*c12.x*c12y2*c13.y*c23.x+6*c10.x*c20.y*c13.x*c13y2*c23.x+6*c10.x*c21.x*c13.x*c13y2*c22.y+6*c10.x*c13.x*c21.y*c22.x*c13y2+4*c10.y*c11.x*c12.y*c13x2*c23.y+6*c10.y*c20.x*c13.x*c13y2*c23.x+2*c10.y*c11.y*c12.x*c13x2*c23.y-3*c10.y*c11.y*c12.y*c13x2*c23.x+2*c10.y*c12.x*c12y2*c13.x*c23.x+6*c10.y*c21.x*c13.x*c22.x*c13y2-3*c11.x*c20.x*c12.x*c13y2*c23.y+2*c11.x*c20.x*c12.y*c13y2*c23.x+c11.x*c11.y*c12y2*c13.x*c23.x-3*c11.x*c12.x*c20.y*c13y2*c23.x-3*c11.x*c12.x*c21.x*c13y2*c22.y-3*c11.x*c12.x*c21.y*c22.x*c13y2+2*c11.x*c21.x*c12.y*c22.x*c13y2+4*c20.x*c11.y*c12.x*c13y2*c23.x+4*c11.y*c12.x*c21.x*c22.x*c13y2-2*c10.x*c12x2*c12.y*c13.y*c23.y-6*c10.y*c20.x*c13x2*c13.y*c23.y-6*c10.y*c20.y*c13x2*c13.y*c23.x-6*c10.y*c21.x*c13x2*c13.y*c22.y-2*c10.y*c12x2*c12.y*c13.x*c23.y-2*c10.y*c12x2*c12.y*c13.y*c23.x-6*c10.y*c13x2*c21.y*c22.x*c13.y-c11.x*c11.y*c12x2*c13.y*c23.y-2*c11.x*c11y2*c13.x*c13.y*c23.x+3*c20.x*c11.y*c12.y*c13x2*c23.y-2*c20.x*c12.x*c12y2*c13.x*c23.y-2*c20.x*c12.x*c12y2*c13.y*c23.x-6*c20.x*c20.y*c13.x*c13y2*c23.x-6*c20.x*c21.x*c13.x*c13y2*c22.y-6*c20.x*c13.x*c21.y*c22.x*c13y2+3*c11.y*c20.y*c12.y*c13x2*c23.x+3*c11.y*c21.x*c12.y*c13x2*c22.y+3*c11.y*c12.y*c13x2*c21.y*c22.x-2*c12.x*c20.y*c12y2*c13.x*c23.x-2*c12.x*c21.x*c12y2*c13.x*c22.y-2*c12.x*c21.x*c12y2*c22.x*c13.y-2*c12.x*c12y2*c13.x*c21.y*c22.x-6*c20.y*c21.x*c13.x*c22.x*c13y2-c11y2*c12.x*c12.y*c13.x*c23.x+2*c20.x*c12x2*c12.y*c13.y*c23.y+6*c20.y*c13x2*c21.y*c22.x*c13.y+2*c11x2*c11.y*c13.x*c13.y*c23.y+c11x2*c12.x*c12.y*c13.y*c23.y+2*c12x2*c20.y*c12.y*c13.y*c23.x+2*c12x2*c21.x*c12.y*c13.y*c22.y+2*c12x2*c12.y*c21.y*c22.x*c13.y+c21x3*c13y3+3*c10x2*c13y3*c23.x-3*c10y2*c13x3*c23.y+3*c20x2*c13y3*c23.x+c11y3*c13x2*c23.x-c11x3*c13y2*c23.y-c11.x*c11y2*c13x2*c23.y+c11x2*c11.y*c13y2*c23.x-3*c10x2*c13.x*c13y2*c23.y+3*c10y2*c13x2*c13.y*c23.x-c11x2*c12y2*c13.x*c23.y+c11y2*c12x2*c13.y*c23.x-3*c21x2*c13.x*c21.y*c13y2-3*c20x2*c13.x*c13y2*c23.y+3*c20y2*c13x2*c13.y*c23.x+c11.x*c12.x*c13.x*c13.y*(6*c20.y*c23.y+6*c21.y*c22.y)+c12x3*c13.y*(-2*c20.y*c23.y-2*c21.y*c22.y)+c10.y*c13x3*(6*c20.y*c23.y+6*c21.y*c22.y)+c11.y*c12.x*c13x2*(-2*c20.y*c23.y-2*c21.y*c22.y)+c12x2*c12.y*c13.x*(2*c20.y*c23.y+2*c21.y*c22.y)+c11.x*c12.y*c13x2*(-4*c20.y*c23.y-4*c21.y*c22.y)+c10.x*c13x2*c13.y*(-6*c20.y*c23.y-6*c21.y*c22.y)+c20.x*c13x2*c13.y*(6*c20.y*c23.y+6*c21.y*c22.y)+c21.x*c13x2*c13.y*(6*c20.y*c22.y+3*c21y2)+c13x3*(-2*c20.y*c21.y*c22.y-c20y2*c23.y-c21.y*(2*c20.y*c22.y+c21y2)-c20.y*(2*c20.y*c23.y+2*c21.y*c22.y)),-c10.x*c11.x*c12.y*c13.x*c13.y*c22.y+c10.x*c11.y*c12.x*c13.x*c13.y*c22.y+6*c10.x*c11.y*c12.y*c13.x*c22.x*c13.y-6*c10.y*c11.x*c12.x*c13.x*c13.y*c22.y-c10.y*c11.x*c12.y*c13.x*c22.x*c13.y+c10.y*c11.y*c12.x*c13.x*c22.x*c13.y+c11.x*c11.y*c12.x*c12.y*c13.x*c22.y-c11.x*c11.y*c12.x*c12.y*c22.x*c13.y+c11.x*c20.x*c12.y*c13.x*c13.y*c22.y+c11.x*c20.y*c12.y*c13.x*c22.x*c13.y+c11.x*c21.x*c12.y*c13.x*c21.y*c13.y-c20.x*c11.y*c12.x*c13.x*c13.y*c22.y-6*c20.x*c11.y*c12.y*c13.x*c22.x*c13.y-c11.y*c12.x*c20.y*c13.x*c22.x*c13.y-c11.y*c12.x*c21.x*c13.x*c21.y*c13.y-6*c10.x*c20.x*c22.x*c13y3-2*c10.x*c12y3*c13.x*c22.x+2*c20.x*c12y3*c13.x*c22.x+2*c10.y*c12x3*c13.y*c22.y-6*c10.x*c10.y*c13.x*c22.x*c13y2+3*c10.x*c11.x*c12.x*c13y2*c22.y-2*c10.x*c11.x*c12.y*c22.x*c13y2-4*c10.x*c11.y*c12.x*c22.x*c13y2+3*c10.y*c11.x*c12.x*c22.x*c13y2+6*c10.x*c10.y*c13x2*c13.y*c22.y+6*c10.x*c20.x*c13.x*c13y2*c22.y-3*c10.x*c11.y*c12.y*c13x2*c22.y+2*c10.x*c12.x*c12y2*c13.x*c22.y+2*c10.x*c12.x*c12y2*c22.x*c13.y+6*c10.x*c20.y*c13.x*c22.x*c13y2+6*c10.x*c21.x*c13.x*c21.y*c13y2+4*c10.y*c11.x*c12.y*c13x2*c22.y+6*c10.y*c20.x*c13.x*c22.x*c13y2+2*c10.y*c11.y*c12.x*c13x2*c22.y-3*c10.y*c11.y*c12.y*c13x2*c22.x+2*c10.y*c12.x*c12y2*c13.x*c22.x-3*c11.x*c20.x*c12.x*c13y2*c22.y+2*c11.x*c20.x*c12.y*c22.x*c13y2+c11.x*c11.y*c12y2*c13.x*c22.x-3*c11.x*c12.x*c20.y*c22.x*c13y2-3*c11.x*c12.x*c21.x*c21.y*c13y2+4*c20.x*c11.y*c12.x*c22.x*c13y2-2*c10.x*c12x2*c12.y*c13.y*c22.y-6*c10.y*c20.x*c13x2*c13.y*c22.y-6*c10.y*c20.y*c13x2*c22.x*c13.y-6*c10.y*c21.x*c13x2*c21.y*c13.y-2*c10.y*c12x2*c12.y*c13.x*c22.y-2*c10.y*c12x2*c12.y*c22.x*c13.y-c11.x*c11.y*c12x2*c13.y*c22.y-2*c11.x*c11y2*c13.x*c22.x*c13.y+3*c20.x*c11.y*c12.y*c13x2*c22.y-2*c20.x*c12.x*c12y2*c13.x*c22.y-2*c20.x*c12.x*c12y2*c22.x*c13.y-6*c20.x*c20.y*c13.x*c22.x*c13y2-6*c20.x*c21.x*c13.x*c21.y*c13y2+3*c11.y*c20.y*c12.y*c13x2*c22.x+3*c11.y*c21.x*c12.y*c13x2*c21.y-2*c12.x*c20.y*c12y2*c13.x*c22.x-2*c12.x*c21.x*c12y2*c13.x*c21.y-c11y2*c12.x*c12.y*c13.x*c22.x+2*c20.x*c12x2*c12.y*c13.y*c22.y-3*c11.y*c21x2*c12.y*c13.x*c13.y+6*c20.y*c21.x*c13x2*c21.y*c13.y+2*c11x2*c11.y*c13.x*c13.y*c22.y+c11x2*c12.x*c12.y*c13.y*c22.y+2*c12x2*c20.y*c12.y*c22.x*c13.y+2*c12x2*c21.x*c12.y*c21.y*c13.y-3*c10.x*c21x2*c13y3+3*c20.x*c21x2*c13y3+3*c10x2*c22.x*c13y3-3*c10y2*c13x3*c22.y+3*c20x2*c22.x*c13y3+c21x2*c12y3*c13.x+c11y3*c13x2*c22.x-c11x3*c13y2*c22.y+3*c10.y*c21x2*c13.x*c13y2-c11.x*c11y2*c13x2*c22.y+c11.x*c21x2*c12.y*c13y2+2*c11.y*c12.x*c21x2*c13y2+c11x2*c11.y*c22.x*c13y2-c12.x*c21x2*c12y2*c13.y-3*c20.y*c21x2*c13.x*c13y2-3*c10x2*c13.x*c13y2*c22.y+3*c10y2*c13x2*c22.x*c13.y-c11x2*c12y2*c13.x*c22.y+c11y2*c12x2*c22.x*c13.y-3*c20x2*c13.x*c13y2*c22.y+3*c20y2*c13x2*c22.x*c13.y+c12x2*c12.y*c13.x*(2*c20.y*c22.y+c21y2)+c11.x*c12.x*c13.x*c13.y*(6*c20.y*c22.y+3*c21y2)+c12x3*c13.y*(-2*c20.y*c22.y-c21y2)+c10.y*c13x3*(6*c20.y*c22.y+3*c21y2)+c11.y*c12.x*c13x2*(-2*c20.y*c22.y-c21y2)+c11.x*c12.y*c13x2*(-4*c20.y*c22.y-2*c21y2)+c10.x*c13x2*c13.y*(-6*c20.y*c22.y-3*c21y2)+c20.x*c13x2*c13.y*(6*c20.y*c22.y+3*c21y2)+c13x3*(-2*c20.y*c21y2-c20y2*c22.y-c20.y*(2*c20.y*c22.y+c21y2)),-c10.x*c11.x*c12.y*c13.x*c21.y*c13.y+c10.x*c11.y*c12.x*c13.x*c21.y*c13.y+6*c10.x*c11.y*c21.x*c12.y*c13.x*c13.y-6*c10.y*c11.x*c12.x*c13.x*c21.y*c13.y-c10.y*c11.x*c21.x*c12.y*c13.x*c13.y+c10.y*c11.y*c12.x*c21.x*c13.x*c13.y-c11.x*c11.y*c12.x*c21.x*c12.y*c13.y+c11.x*c11.y*c12.x*c12.y*c13.x*c21.y+c11.x*c20.x*c12.y*c13.x*c21.y*c13.y+6*c11.x*c12.x*c20.y*c13.x*c21.y*c13.y+c11.x*c20.y*c21.x*c12.y*c13.x*c13.y-c20.x*c11.y*c12.x*c13.x*c21.y*c13.y-6*c20.x*c11.y*c21.x*c12.y*c13.x*c13.y-c11.y*c12.x*c20.y*c21.x*c13.x*c13.y-6*c10.x*c20.x*c21.x*c13y3-2*c10.x*c21.x*c12y3*c13.x+6*c10.y*c20.y*c13x3*c21.y+2*c20.x*c21.x*c12y3*c13.x+2*c10.y*c12x3*c21.y*c13.y-2*c12x3*c20.y*c21.y*c13.y-6*c10.x*c10.y*c21.x*c13.x*c13y2+3*c10.x*c11.x*c12.x*c21.y*c13y2-2*c10.x*c11.x*c21.x*c12.y*c13y2-4*c10.x*c11.y*c12.x*c21.x*c13y2+3*c10.y*c11.x*c12.x*c21.x*c13y2+6*c10.x*c10.y*c13x2*c21.y*c13.y+6*c10.x*c20.x*c13.x*c21.y*c13y2-3*c10.x*c11.y*c12.y*c13x2*c21.y+2*c10.x*c12.x*c21.x*c12y2*c13.y+2*c10.x*c12.x*c12y2*c13.x*c21.y+6*c10.x*c20.y*c21.x*c13.x*c13y2+4*c10.y*c11.x*c12.y*c13x2*c21.y+6*c10.y*c20.x*c21.x*c13.x*c13y2+2*c10.y*c11.y*c12.x*c13x2*c21.y-3*c10.y*c11.y*c21.x*c12.y*c13x2+2*c10.y*c12.x*c21.x*c12y2*c13.x-3*c11.x*c20.x*c12.x*c21.y*c13y2+2*c11.x*c20.x*c21.x*c12.y*c13y2+c11.x*c11.y*c21.x*c12y2*c13.x-3*c11.x*c12.x*c20.y*c21.x*c13y2+4*c20.x*c11.y*c12.x*c21.x*c13y2-6*c10.x*c20.y*c13x2*c21.y*c13.y-2*c10.x*c12x2*c12.y*c21.y*c13.y-6*c10.y*c20.x*c13x2*c21.y*c13.y-6*c10.y*c20.y*c21.x*c13x2*c13.y-2*c10.y*c12x2*c21.x*c12.y*c13.y-2*c10.y*c12x2*c12.y*c13.x*c21.y-c11.x*c11.y*c12x2*c21.y*c13.y-4*c11.x*c20.y*c12.y*c13x2*c21.y-2*c11.x*c11y2*c21.x*c13.x*c13.y+3*c20.x*c11.y*c12.y*c13x2*c21.y-2*c20.x*c12.x*c21.x*c12y2*c13.y-2*c20.x*c12.x*c12y2*c13.x*c21.y-6*c20.x*c20.y*c21.x*c13.x*c13y2-2*c11.y*c12.x*c20.y*c13x2*c21.y+3*c11.y*c20.y*c21.x*c12.y*c13x2-2*c12.x*c20.y*c21.x*c12y2*c13.x-c11y2*c12.x*c21.x*c12.y*c13.x+6*c20.x*c20.y*c13x2*c21.y*c13.y+2*c20.x*c12x2*c12.y*c21.y*c13.y+2*c11x2*c11.y*c13.x*c21.y*c13.y+c11x2*c12.x*c12.y*c21.y*c13.y+2*c12x2*c20.y*c21.x*c12.y*c13.y+2*c12x2*c20.y*c12.y*c13.x*c21.y+3*c10x2*c21.x*c13y3-3*c10y2*c13x3*c21.y+3*c20x2*c21.x*c13y3+c11y3*c21.x*c13x2-c11x3*c21.y*c13y2-3*c20y2*c13x3*c21.y-c11.x*c11y2*c13x2*c21.y+c11x2*c11.y*c21.x*c13y2-3*c10x2*c13.x*c21.y*c13y2+3*c10y2*c21.x*c13x2*c13.y-c11x2*c12y2*c13.x*c21.y+c11y2*c12x2*c21.x*c13.y-3*c20x2*c13.x*c21.y*c13y2+3*c20y2*c21.x*c13x2*c13.y,c10.x*c10.y*c11.x*c12.y*c13.x*c13.y-c10.x*c10.y*c11.y*c12.x*c13.x*c13.y+c10.x*c11.x*c11.y*c12.x*c12.y*c13.y-c10.y*c11.x*c11.y*c12.x*c12.y*c13.x-c10.x*c11.x*c20.y*c12.y*c13.x*c13.y+6*c10.x*c20.x*c11.y*c12.y*c13.x*c13.y+c10.x*c11.y*c12.x*c20.y*c13.x*c13.y-c10.y*c11.x*c20.x*c12.y*c13.x*c13.y-6*c10.y*c11.x*c12.x*c20.y*c13.x*c13.y+c10.y*c20.x*c11.y*c12.x*c13.x*c13.y-c11.x*c20.x*c11.y*c12.x*c12.y*c13.y+c11.x*c11.y*c12.x*c20.y*c12.y*c13.x+c11.x*c20.x*c20.y*c12.y*c13.x*c13.y-c20.x*c11.y*c12.x*c20.y*c13.x*c13.y-2*c10.x*c20.x*c12y3*c13.x+2*c10.y*c12x3*c20.y*c13.y-3*c10.x*c10.y*c11.x*c12.x*c13y2-6*c10.x*c10.y*c20.x*c13.x*c13y2+3*c10.x*c10.y*c11.y*c12.y*c13x2-2*c10.x*c10.y*c12.x*c12y2*c13.x-2*c10.x*c11.x*c20.x*c12.y*c13y2-c10.x*c11.x*c11.y*c12y2*c13.x+3*c10.x*c11.x*c12.x*c20.y*c13y2-4*c10.x*c20.x*c11.y*c12.x*c13y2+3*c10.y*c11.x*c20.x*c12.x*c13y2+6*c10.x*c10.y*c20.y*c13x2*c13.y+2*c10.x*c10.y*c12x2*c12.y*c13.y+2*c10.x*c11.x*c11y2*c13.x*c13.y+2*c10.x*c20.x*c12.x*c12y2*c13.y+6*c10.x*c20.x*c20.y*c13.x*c13y2-3*c10.x*c11.y*c20.y*c12.y*c13x2+2*c10.x*c12.x*c20.y*c12y2*c13.x+c10.x*c11y2*c12.x*c12.y*c13.x+c10.y*c11.x*c11.y*c12x2*c13.y+4*c10.y*c11.x*c20.y*c12.y*c13x2-3*c10.y*c20.x*c11.y*c12.y*c13x2+2*c10.y*c20.x*c12.x*c12y2*c13.x+2*c10.y*c11.y*c12.x*c20.y*c13x2+c11.x*c20.x*c11.y*c12y2*c13.x-3*c11.x*c20.x*c12.x*c20.y*c13y2-2*c10.x*c12x2*c20.y*c12.y*c13.y-6*c10.y*c20.x*c20.y*c13x2*c13.y-2*c10.y*c20.x*c12x2*c12.y*c13.y-2*c10.y*c11x2*c11.y*c13.x*c13.y-c10.y*c11x2*c12.x*c12.y*c13.y-2*c10.y*c12x2*c20.y*c12.y*c13.x-2*c11.x*c20.x*c11y2*c13.x*c13.y-c11.x*c11.y*c12x2*c20.y*c13.y+3*c20.x*c11.y*c20.y*c12.y*c13x2-2*c20.x*c12.x*c20.y*c12y2*c13.x-c20.x*c11y2*c12.x*c12.y*c13.x+3*c10y2*c11.x*c12.x*c13.x*c13.y+3*c11.x*c12.x*c20y2*c13.x*c13.y+2*c20.x*c12x2*c20.y*c12.y*c13.y-3*c10x2*c11.y*c12.y*c13.x*c13.y+2*c11x2*c11.y*c20.y*c13.x*c13.y+c11x2*c12.x*c20.y*c12.y*c13.y-3*c20x2*c11.y*c12.y*c13.x*c13.y-c10x3*c13y3+c10y3*c13x3+c20x3*c13y3-c20y3*c13x3-3*c10.x*c20x2*c13y3-c10.x*c11y3*c13x2+3*c10x2*c20.x*c13y3+c10.y*c11x3*c13y2+3*c10.y*c20y2*c13x3+c20.x*c11y3*c13x2+c10x2*c12y3*c13.x-3*c10y2*c20.y*c13x3-c10y2*c12x3*c13.y+c20x2*c12y3*c13.x-c11x3*c20.y*c13y2-c12x3*c20y2*c13.y-c10.x*c11x2*c11.y*c13y2+c10.y*c11.x*c11y2*c13x2-3*c10.x*c10y2*c13x2*c13.y-c10.x*c11y2*c12x2*c13.y+c10.y*c11x2*c12y2*c13.x-c11.x*c11y2*c20.y*c13x2+3*c10x2*c10.y*c13.x*c13y2+c10x2*c11.x*c12.y*c13y2+2*c10x2*c11.y*c12.x*c13y2-2*c10y2*c11.x*c12.y*c13x2-c10y2*c11.y*c12.x*c13x2+c11x2*c20.x*c11.y*c13y2-3*c10.x*c20y2*c13x2*c13.y+3*c10.y*c20x2*c13.x*c13y2+c11.x*c20x2*c12.y*c13y2-2*c11.x*c20y2*c12.y*c13x2+c20.x*c11y2*c12x2*c13.y-c11.y*c12.x*c20y2*c13x2-c10x2*c12.x*c12y2*c13.y-3*c10x2*c20.y*c13.x*c13y2+3*c10y2*c20.x*c13x2*c13.y+c10y2*c12x2*c12.y*c13.x-c11x2*c20.y*c12y2*c13.x+2*c20x2*c11.y*c12.x*c13y2+3*c20.x*c20y2*c13x2*c13.y-c20x2*c12.x*c12y2*c13.y-3*c20x2*c20.y*c13.x*c13y2+c12x2*c20y2*c12.y*c13.x);var roots=poly.getRootsInInterval(0,1);for(var i=0;i0&&yRoots.length>0){var TOLERANCE=1e-4;checkRoots:for(var j=0;j0)result.status="Intersection";return result;};
- Intersection.intersectBezier3Circle=function(p1,p2,p3,p4,c,r){return Intersection.intersectBezier3Ellipse(p1,p2,p3,p4,c,r,r);};
- Intersection.intersectBezier3Ellipse=function(p1,p2,p3,p4,ec,rx,ry){var a,b,c,d;var c3,c2,c1,c0;var result=new Intersection("No Intersection");a=p1.multiply(-1);b=p2.multiply(3);c=p3.multiply(-3);d=a.add(b.add(c.add(p4)));c3=new Vector2D(d.x,d.y);a=p1.multiply(3);b=p2.multiply(-6);c=p3.multiply(3);d=a.add(b.add(c));c2=new Vector2D(d.x,d.y);a=p1.multiply(-3);b=p2.multiply(3);c=a.add(b);c1=new Vector2D(c.x,c.y);c0=new Vector2D(p1.x,p1.y);var rxrx=rx*rx;var ryry=ry*ry;var poly=new Polynomial(c3.x*c3.x*ryry+c3.y*c3.y*rxrx,2*(c3.x*c2.x*ryry+c3.y*c2.y*rxrx),2*(c3.x*c1.x*ryry+c3.y*c1.y*rxrx)+c2.x*c2.x*ryry+c2.y*c2.y*rxrx,2*c3.x*ryry*(c0.x-ec.x)+2*c3.y*rxrx*(c0.y-ec.y)+2*(c2.x*c1.x*ryry+c2.y*c1.y*rxrx),2*c2.x*ryry*(c0.x-ec.x)+2*c2.y*rxrx*(c0.y-ec.y)+c1.x*c1.x*ryry+c1.y*c1.y*rxrx,2*c1.x*ryry*(c0.x-ec.x)+2*c1.y*rxrx*(c0.y-ec.y),c0.x*c0.x*ryry-2*c0.y*ec.y*rxrx-2*c0.x*ec.x*ryry+c0.y*c0.y*rxrx+ec.x*ec.x*ryry+ec.y*ec.y*rxrx-rxrx*ryry);var roots=poly.getRootsInInterval(0,1);for(var i=0;i0)result.status="Intersection";return result;};
- Intersection.intersectBezier3Line=function(p1,p2,p3,p4,a1,a2){var a,b,c,d;var c3,c2,c1,c0;var cl;var n;var min=a1.min(a2);var max=a1.max(a2);var result=new Intersection("No Intersection");a=p1.multiply(-1);b=p2.multiply(3);c=p3.multiply(-3);d=a.add(b.add(c.add(p4)));c3=new Vector2D(d.x,d.y);a=p1.multiply(3);b=p2.multiply(-6);c=p3.multiply(3);d=a.add(b.add(c));c2=new Vector2D(d.x,d.y);a=p1.multiply(-3);b=p2.multiply(3);c=a.add(b);c1=new Vector2D(c.x,c.y);c0=new Vector2D(p1.x,p1.y);n=new Vector2D(a1.y-a2.y,a2.x-a1.x);cl=a1.x*a2.y-a2.x*a1.y;roots=new Polynomial(n.dot(c3),n.dot(c2),n.dot(c1),n.dot(c0)+cl).getRoots();for(var i=0;i0)result.status="Intersection";return result;};
- Intersection.intersectBezier3Rectangle=function(p1,p2,p3,p4,r1,r2){var min=r1.min(r2);var max=r1.max(r2);var topRight=new Point2D(max.x,min.y);var bottomLeft=new Point2D(min.x,max.y);var inter1=Intersection.intersectBezier3Line(p1,p2,p3,p4,min,topRight);var inter2=Intersection.intersectBezier3Line(p1,p2,p3,p4,topRight,max);var inter3=Intersection.intersectBezier3Line(p1,p2,p3,p4,max,bottomLeft);var inter4=Intersection.intersectBezier3Line(p1,p2,p3,p4,bottomLeft,min);var result=new Intersection("No Intersection");result.appendPoints(inter1.points);result.appendPoints(inter2.points);result.appendPoints(inter3.points);result.appendPoints(inter4.points);if(result.points.length>0)result.status="Intersection";return result;};
- Intersection.intersectCircleCircle=function(c1,r1,c2,r2){var result;var r_max=r1+r2;var r_min=Math.abs(r1-r2);var c_dist=c1.distanceFrom(c2);if(c_dist>r_max){result=new Intersection("Outside");}else if(c_dist1)&&(u2<0||u2>1)){if((u1<0&&u2<0)||(u1>1&&u2>1)){result=new Intersection("Outside");}else{result=new Intersection("Inside");}}else{result=new Intersection("Intersection");if(0<=u1&&u1<=1)result.points.push(a1.lerp(a2,u1));if(0<=u2&&u2<=1)result.points.push(a1.lerp(a2,u2));}}return result;};
- Intersection.intersectCirclePolygon=function(c,r,points){var result=new Intersection("No Intersection");var length=points.length;var inter;for(var i=0;i0)result.status="Intersection";else result.status=inter.status;return result;};
- Intersection.intersectCircleRectangle=function(c,r,r1,r2){var min=r1.min(r2);var max=r1.max(r2);var topRight=new Point2D(max.x,min.y);var bottomLeft=new Point2D(min.x,max.y);var inter1=Intersection.intersectCircleLine(c,r,min,topRight);var inter2=Intersection.intersectCircleLine(c,r,topRight,max);var inter3=Intersection.intersectCircleLine(c,r,max,bottomLeft);var inter4=Intersection.intersectCircleLine(c,r,bottomLeft,min);var result=new Intersection("No Intersection");result.appendPoints(inter1.points);result.appendPoints(inter2.points);result.appendPoints(inter3.points);result.appendPoints(inter4.points);if(result.points.length>0)result.status="Intersection";else result.status=inter1.status;return result;};
- Intersection.intersectEllipseEllipse=function(c1,rx1,ry1,c2,rx2,ry2){var a=[ry1*ry1,0,rx1*rx1,-2*ry1*ry1*c1.x,-2*rx1*rx1*c1.y,ry1*ry1*c1.x*c1.x+rx1*rx1*c1.y*c1.y-rx1*rx1*ry1*ry1];var b=[ry2*ry2,0,rx2*rx2,-2*ry2*ry2*c2.x,-2*rx2*rx2*c2.y,ry2*ry2*c2.x*c2.x+rx2*rx2*c2.y*c2.y-rx2*rx2*ry2*ry2];var yPoly=Intersection.bezout(a,b);var yRoots=yPoly.getRoots();var epsilon=1e-3;var norm0=(a[0]*a[0]+2*a[1]*a[1]+a[2]*a[2])*epsilon;var norm1=(b[0]*b[0]+2*b[1]*b[1]+b[2]*b[2])*epsilon;var result=new Intersection("No Intersection");for(var y=0;y0)result.status="Intersection";return result;};
- Intersection.intersectEllipseLine=function(c,rx,ry,a1,a2){var result;var origin=new Vector2D(a1.x,a1.y);var dir=Vector2D.fromPoints(a1,a2);var center=new Vector2D(c.x,c.y);var diff=origin.subtract(center);var mDir=new Vector2D(dir.x/(rx*rx), dir.y/(ry*ry));var mDiff=new Vector2D(diff.x/(rx*rx), diff.y/(ry*ry));var a=dir.dot(mDir);var b=dir.dot(mDiff);var c=diff.dot(mDiff)-1.0;var d=b*b-a*c;if(d<0){result=new Intersection("Outside");}else if(d>0){var root=Math.sqrt(d);var t_a=(-b-root)/a;var t_b=(-b+root)/a;if((t_a<0||11&&t_b>1))result=new Intersection("Outside");else result=new Intersection("Inside");}else{result=new Intersection("Intersection");if(0<=t_a&&t_a<=1)result.appendPoint(a1.lerp(a2,t_a));if(0<=t_b&&t_b<=1)result.appendPoint(a1.lerp(a2,t_b));}}else{var t=-b/a;if(0<=t&&t<=1){result=new Intersection("Intersection");result.appendPoint(a1.lerp(a2,t));}else{result=new Intersection("Outside");}}return result;};
- Intersection.intersectEllipsePolygon=function(c,rx,ry,points){var result=new Intersection("No Intersection");var length=points.length;for(var i=0;i0)result.status="Intersection";return result;};
- Intersection.intersectEllipseRectangle=function(c,rx,ry,r1,r2){var min=r1.min(r2);var max=r1.max(r2);var topRight=new Point2D(max.x,min.y);var bottomLeft=new Point2D(min.x,max.y);var inter1=Intersection.intersectEllipseLine(c,rx,ry,min,topRight);var inter2=Intersection.intersectEllipseLine(c,rx,ry,topRight,max);var inter3=Intersection.intersectEllipseLine(c,rx,ry,max,bottomLeft);var inter4=Intersection.intersectEllipseLine(c,rx,ry,bottomLeft,min);var result=new Intersection("No Intersection");result.appendPoints(inter1.points);result.appendPoints(inter2.points);result.appendPoints(inter3.points);result.appendPoints(inter4.points);if(result.points.length>0)result.status="Intersection";return result;};
- Intersection.intersectLineLine=function(a1,a2,b1,b2){var result;var ua_t=(b2.x-b1.x)*(a1.y-b1.y)-(b2.y-b1.y)*(a1.x-b1.x);var ub_t=(a2.x-a1.x)*(a1.y-b1.y)-(a2.y-a1.y)*(a1.x-b1.x);var u_b=(b2.y-b1.y)*(a2.x-a1.x)-(b2.x-b1.x)*(a2.y-a1.y);if(u_b!=0){var ua=ua_t/u_b;var ub=ub_t/u_b;if(0<=ua&&ua<=1&&0<=ub&&ub<=1){result=new Intersection("Intersection");result.points.push(new Point2D(a1.x+ua*(a2.x-a1.x),a1.y+ua*(a2.y-a1.y)));}else{result=new Intersection("No Intersection");}}else{if(ua_t==0||ub_t==0){result=new Intersection("Coincident");}else{result=new Intersection("Parallel");}}return result;};
- Intersection.intersectLinePolygon=function(a1,a2,points){var result=new Intersection("No Intersection");var length=points.length;for(var i=0;i0)result.status="Intersection";return result;};
- Intersection.intersectLineRectangle=function(a1,a2,r1,r2){var min=r1.min(r2);var max=r1.max(r2);var topRight=new Point2D(max.x,min.y);var bottomLeft=new Point2D(min.x,max.y);var inter1=Intersection.intersectLineLine(min,topRight,a1,a2);var inter2=Intersection.intersectLineLine(topRight,max,a1,a2);var inter3=Intersection.intersectLineLine(max,bottomLeft,a1,a2);var inter4=Intersection.intersectLineLine(bottomLeft,min,a1,a2);var result=new Intersection("No Intersection");result.appendPoints(inter1.points);result.appendPoints(inter2.points);result.appendPoints(inter3.points);result.appendPoints(inter4.points);if(result.points.length>0)result.status="Intersection";return result;};
- Intersection.intersectPolygonPolygon=function(points1,points2){var result=new Intersection("No Intersection");var length=points1.length;for(var i=0;i0)result.status="Intersection";return result;};
- Intersection.intersectPolygonRectangle=function(points,r1,r2){var min=r1.min(r2);var max=r1.max(r2);var topRight=new Point2D(max.x,min.y);var bottomLeft=new Point2D(min.x,max.y);var inter1=Intersection.intersectLinePolygon(min,topRight,points);var inter2=Intersection.intersectLinePolygon(topRight,max,points);var inter3=Intersection.intersectLinePolygon(max,bottomLeft,points);var inter4=Intersection.intersectLinePolygon(bottomLeft,min,points);var result=new Intersection("No Intersection");result.appendPoints(inter1.points);result.appendPoints(inter2.points);result.appendPoints(inter3.points);result.appendPoints(inter4.points);if(result.points.length>0)result.status="Intersection";return result;};
- Intersection.intersectRayRay=function(a1,a2,b1,b2){var result;var ua_t=(b2.x-b1.x)*(a1.y-b1.y)-(b2.y-b1.y)*(a1.x-b1.x);var ub_t=(a2.x-a1.x)*(a1.y-b1.y)-(a2.y-a1.y)*(a1.x-b1.x);var u_b=(b2.y-b1.y)*(a2.x-a1.x)-(b2.x-b1.x)*(a2.y-a1.y);if(u_b!=0){var ua=ua_t/u_b;result=new Intersection("Intersection");result.points.push(new Point2D(a1.x+ua*(a2.x-a1.x),a1.y+ua*(a2.y-a1.y)));}else{if(ua_t==0||ub_t==0){result=new Intersection("Coincident");}else{result=new Intersection("Parallel");}}return result;};
- Intersection.intersectRectangleRectangle=function(a1,a2,b1,b2){var min=a1.min(a2);var max=a1.max(a2);var topRight=new Point2D(max.x,min.y);var bottomLeft=new Point2D(min.x,max.y);var inter1=Intersection.intersectLineRectangle(min,topRight,b1,b2);var inter2=Intersection.intersectLineRectangle(topRight,max,b1,b2);var inter3=Intersection.intersectLineRectangle(max,bottomLeft,b1,b2);var inter4=Intersection.intersectLineRectangle(bottomLeft,min,b1,b2);var result=new Intersection("No Intersection");result.appendPoints(inter1.points);result.appendPoints(inter2.points);result.appendPoints(inter3.points);result.appendPoints(inter4.points);if(result.points.length>0)result.status="Intersection";return result;};
- Intersection.bezout=function(e1,e2){var AB=e1[0]*e2[1]-e2[0]*e1[1];var AC=e1[0]*e2[2]-e2[0]*e1[2];var AD=e1[0]*e2[3]-e2[0]*e1[3];var AE=e1[0]*e2[4]-e2[0]*e1[4];var AF=e1[0]*e2[5]-e2[0]*e1[5];var BC=e1[1]*e2[2]-e2[1]*e1[2];var BE=e1[1]*e2[4]-e2[1]*e1[4];var BF=e1[1]*e2[5]-e2[1]*e1[5];var CD=e1[2]*e2[3]-e2[2]*e1[3];var DE=e1[3]*e2[4]-e2[3]*e1[4];var DF=e1[3]*e2[5]-e2[3]*e1[5];var BFpDE=BF+DE;var BEmCD=BE-CD;return new Polynomial(AB*BC-AC*AC,AB*BEmCD+AD*BC-2*AC*AE,AB*BFpDE+AD*BEmCD-AE*AE-2*AC*AF,AB*DF+AD*BFpDE-2*AE*AF,AD*DF-AF*AF);};
+
+ function Point2D(x, y) {
+ if (arguments.length > 0) {
+ this.init(x,y);
+ }
+ }
+ Point2D.prototype = {
+ constructor: Point2D,
+ init: function (x, y) {
+ this.x = x;
+ this.y = y;
+ },
+ add: function (that) {
+ return new Point2D(this.x + that.x, this.y + that.y);
+ },
+ addEquals: function (that) {
+ this.x += that.x;
+ this.y += that.y;
+ return this;
+ },
+ scalarAdd: function (scalar) {
+ return new Point2D(this.x + scalar, this.y + scalar);
+ },
+ scalarAddEquals: function (scalar) {
+ this.x += scalar;
+ this.y += scalar;
+ return this;
+ },
+ subtract: function (that) {
+ return new Point2D(this.x - that.x, this.y - that.y);
+ },
+ subtractEquals: function (that) {
+ this.x -= that.x;
+ this.y -= that.y;
+ return this;
+ },
+ scalarSubtract: function (scalar) {
+ return new Point2D(this.x - scalar, this.y - scalar);
+ },
+ scalarSubtractEquals: function (scalar) {
+ this.x -= scalar;
+ this.y -= scalar;
+ return this;
+ },
+ multiply: function (scalar) {
+ return new Point2D(this.x * scalar, this.y * scalar);
+ },
+ multiplyEquals: function (scalar) {
+ this.x *= scalar;
+ this.y *= scalar;
+ return this;
+ },
+ divide: function (scalar) {
+ return new Point2D(this.x / scalar, this.y / scalar);
+ },
+ divideEquals: function (scalar) {
+ this.x /= scalar;
+ this.y /= scalar;
+ return this;
+ },
+ eq: function (that) {
+ return (this.x == that.x && this.y == that.y);
+ },
+ lt: function (that) {
+ return (this.x < that.x && this.y < that.y);
+ },
+ lte: function (that) {
+ return (this.x <= that.x && this.y <= that.y);
+ },
+ gt: function (that) {
+ return (this.x > that.x && this.y > that.y);
+ },
+ gte: function (that) {
+ return (this.x >= that.x && this.y >= that.y);
+ },
+ lerp: function (that, t) {
+ return new Point2D(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t);
+ },
+ distanceFrom: function (that) {
+ var dx = this.x - that.x;
+ var dy = this.y - that.y;
+ return Math.sqrt(dx * dx + dy * dy);
+ },
+ min: function (that) {
+ return new Point2D(Math.min(this.x, that.x), Math.min(this.y, that.y));
+ },
+ max: function (that) {
+ return new Point2D(Math.max(this.x, that.x), Math.max(this.y, that.y));
+ },
+ toString: function () {
+ return this.x + "," + this.y;
+ },
+ setXY: function (x, y) {
+ this.x = x;
+ this.y = y;
+ },
+ setFromPoint: function (that) {
+ this.x = that.x;
+ this.y = that.y;
+ },
+ swap: function (that) {
+ var x = this.x;
+ var y = this.y;
+ this.x = that.x;
+ this.y = that.y;
+ that.x = x;
+ that.y = y;
+ }
+ };
+
+ Canvas.Point2D = Point2D;
+
+ function Intersection(status) {
+ if (arguments.length > 0) {
+ this.init(status);
+ }
+ }
+ Intersection.prototype.init = function (status) {
+ this.status = status;
+ this.points = [];
+ };
+ Intersection.prototype.appendPoint = function (point) {
+ this.points.push(point);
+ };
+ Intersection.prototype.appendPoints = function (points) {
+ this.points = this.points.concat(points);
+ };
+
+ Intersection.intersectLineLine = function (a1,a2,b1,b2) {
+ var result;
+ var ua_t = (b2.x-b1.x) * (a1.y-b1.y) - (b2.y-b1.y) * (a1.x-b1.x);
+ var ub_t = (a2.x-a1.x) * (a1.y-b1.y) - (a2.y-a1.y) * (a1.x-b1.x);
+ var u_b = (b2.y-b1.y) * (a2.x-a1.x) - (b2.x-b1.x) * (a2.y-a1.y);
+ if (u_b != 0) {
+ var ua = ua_t/u_b;
+ var ub = ub_t/u_b;
+ if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
+ result = new Intersection("Intersection");
+ result.points.push(new Point2D(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)));
+ }
+ else {
+ result = new Intersection("No Intersection");
+ }
+ }
+ else {
+ if (ua_t == 0 || ub_t == 0) {
+ result = new Intersection("Coincident");
+ }
+ else {
+ result = new Intersection("Parallel");
+ }
+ }
+ return result;
+ };
+
+ Intersection.intersectLinePolygon = function(a1,a2,points){
+ var result = new Intersection("No Intersection");
+ var length = points.length;
+ for(var i = 0; i < length; i++) {
+ var b1 = points[i];
+ var b2 = points[(i+1) % length];
+ var inter = Intersection.intersectLineLine(a1, a2, b1, b2);
+ result.appendPoints(inter.points);
+ }
+ if (result.points.length > 0) {
+ result.status = "Intersection";
+ }
+ return result;
+ };
+
+ Intersection.intersectPolygonPolygon = function (points1, points2) {
+ var result = new Intersection("No Intersection");
+ var length = points1.length;
+ for(var i = 0; i < length; i++) {
+ var a1 = points1[i];
+ var a2 = points1[(i+1) % length];
+ var inter = Intersection.intersectLinePolygon(a1, a2, points2);
+ result.appendPoints(inter.points);
+ }
+ if (result.points.length > 0) {
+ result.status = "Intersection";
+ }
+ return result;
+ };
+
+ Intersection.intersectPolygonRectangle = function (points, r1, r2) {
+ var min = r1.min(r2);
+ var max = r1.max(r2);
+ var topRight = new Point2D(max.x, min.y);
+ var bottomLeft = new Point2D(min.x, max.y);
+ var inter1 = Intersection.intersectLinePolygon(min, topRight, points);
+ var inter2 = Intersection.intersectLinePolygon(topRight, max, points);
+ var inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points);
+ var inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points);
+ var result = new Intersection("No Intersection");
+ result.appendPoints(inter1.points);
+ result.appendPoints(inter2.points);
+ result.appendPoints(inter3.points);
+ result.appendPoints(inter4.points);
+ if (result.points.length > 0) {
+ result.status="Intersection";
+ }
+ return result;
+ };
Canvas.Intersection = Intersection;
diff --git a/src/sylvester.js b/src/sylvester.js
deleted file mode 100755
index d24411f8..00000000
--- a/src/sylvester.js
+++ /dev/null
@@ -1,1265 +0,0 @@
-(function(){
-
- var global = this;
-
- if (!global.Canvas) global.Canvas = { };
-
- // === Sylvester ===
- // Vector and Matrix mathematics modules for JavaScript
- // Copyright (c) 2007 James Coglan
- //
- // Permission is hereby granted, free of charge, to any person obtaining
- // a copy of this software and associated documentation files (the "Software"),
- // to deal in the Software without restriction, including without limitation
- // the rights to use, copy, modify, merge, publish, distribute, sublicense,
- // and/or sell copies of the Software, and to permit persons to whom the
- // Software is furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included
- // in all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- // DEALINGS IN THE SOFTWARE.
-
- var Sylvester = {
- version: '0.1.3',
- precision: 1e-6
- };
-
- function Vector() {}
- Vector.prototype = {
-
- // Returns element i of the vector
- e: function(i) {
- return (i < 1 || i > this.elements.length) ? null : this.elements[i-1];
- },
-
- // Returns the number of elements the vector has
- dimensions: function() {
- return this.elements.length;
- },
-
- // Returns the modulus ('length') of the vector
- modulus: function() {
- return Math.sqrt(this.dot(this));
- },
-
- // Returns true iff the vector is equal to the argument
- eql: function(vector) {
- var n = this.elements.length;
- var V = vector.elements || vector;
- if (n != V.length) { return false; }
- do {
- if (Math.abs(this.elements[n-1] - V[n-1]) > Sylvester.precision) { return false; }
- } while (--n);
- return true;
- },
-
- // Returns a copy of the vector
- dup: function() {
- return Vector.create(this.elements);
- },
-
- // Maps the vector to another vector according to the given function
- map: function(fn) {
- var elements = [];
- this.each(function(x, i) {
- elements.push(fn(x, i));
- });
- return Vector.create(elements);
- },
-
- // Calls the iterator for each element of the vector in turn
- each: function(fn) {
- var n = this.elements.length, k = n, i;
- do { i = k - n;
- fn(this.elements[i], i+1);
- } while (--n);
- },
-
- // Returns a new vector created by normalizing the receiver
- toUnitVector: function() {
- var r = this.modulus();
- if (r === 0) { return this.dup(); }
- return this.map(function(x) { return x/r; });
- },
-
- // Returns the angle between the vector and the argument (also a vector)
- angleFrom: function(vector) {
- var V = vector.elements || vector;
- var n = this.elements.length, k = n, i;
- if (n != V.length) { return null; }
- var dot = 0, mod1 = 0, mod2 = 0;
- // Work things out in parallel to save time
- this.each(function(x, i) {
- dot += x * V[i-1];
- mod1 += x * x;
- mod2 += V[i-1] * V[i-1];
- });
- mod1 = Math.sqrt(mod1); mod2 = Math.sqrt(mod2);
- if (mod1*mod2 === 0) { return null; }
- var theta = dot / (mod1*mod2);
- if (theta < -1) { theta = -1; }
- if (theta > 1) { theta = 1; }
- return Math.acos(theta);
- },
-
- // Returns true iff the vector is parallel to the argument
- isParallelTo: function(vector) {
- var angle = this.angleFrom(vector);
- return (angle === null) ? null : (angle <= Sylvester.precision);
- },
-
- // Returns true iff the vector is antiparallel to the argument
- isAntiparallelTo: function(vector) {
- var angle = this.angleFrom(vector);
- return (angle === null) ? null : (Math.abs(angle - Math.PI) <= Sylvester.precision);
- },
-
- // Returns true iff the vector is perpendicular to the argument
- isPerpendicularTo: function(vector) {
- var dot = this.dot(vector);
- return (dot === null) ? null : (Math.abs(dot) <= Sylvester.precision);
- },
-
- // Returns the result of adding the argument to the vector
- add: function(vector) {
- var V = vector.elements || vector;
- if (this.elements.length != V.length) { return null; }
- return this.map(function(x, i) { return x + V[i-1]; });
- },
-
- // Returns the result of subtracting the argument from the vector
- subtract: function(vector) {
- var V = vector.elements || vector;
- if (this.elements.length != V.length) { return null; }
- return this.map(function(x, i) { return x - V[i-1]; });
- },
-
- // Returns the result of multiplying the elements of the vector by the argument
- multiply: function(k) {
- return this.map(function(x) { return x*k; });
- },
-
- x: function(k) { return this.multiply(k); },
-
- // Returns the scalar product of the vector with the argument
- // Both vectors must have equal dimensionality
- dot: function(vector) {
- var V = vector.elements || vector;
- var i, product = 0, n = this.elements.length;
- if (n != V.length) { return null; }
- do { product += this.elements[n-1] * V[n-1]; } while (--n);
- return product;
- },
-
- // Returns the vector product of the vector with the argument
- // Both vectors must have dimensionality 3
- cross: function(vector) {
- var B = vector.elements || vector;
- if (this.elements.length != 3 || B.length != 3) { return null; }
- var A = this.elements;
- return Vector.create([
- (A[1] * B[2]) - (A[2] * B[1]),
- (A[2] * B[0]) - (A[0] * B[2]),
- (A[0] * B[1]) - (A[1] * B[0])
- ]);
- },
-
- // Returns the (absolute) largest element of the vector
- max: function() {
- var m = 0, n = this.elements.length, k = n, i;
- do { i = k - n;
- if (Math.abs(this.elements[i]) > Math.abs(m)) { m = this.elements[i]; }
- } while (--n);
- return m;
- },
-
- // Returns the index of the first match found
- indexOf: function(x) {
- var index = null, n = this.elements.length, k = n, i;
- do { i = k - n;
- if (index === null && this.elements[i] == x) {
- index = i + 1;
- }
- } while (--n);
- return index;
- },
-
- // Returns a diagonal matrix with the vector's elements as its diagonal elements
- toDiagonalMatrix: function() {
- return Matrix.Diagonal(this.elements);
- },
-
- // Returns the result of rounding the elements of the vector
- round: function() {
- return this.map(function(x) { return Math.round(x); });
- },
-
- // Returns a copy of the vector with elements set to the given value if they
- // differ from it by less than Sylvester.precision
- snapTo: function(x) {
- return this.map(function(y) {
- return (Math.abs(y - x) <= Sylvester.precision) ? x : y;
- });
- },
-
- // Returns the vector's distance from the argument, when considered as a point in space
- distanceFrom: function(obj) {
- if (obj.anchor) { return obj.distanceFrom(this); }
- var V = obj.elements || obj;
- if (V.length != this.elements.length) { return null; }
- var sum = 0, part;
- this.each(function(x, i) {
- part = x - V[i-1];
- sum += part * part;
- });
- return Math.sqrt(sum);
- },
-
- // Returns true if the vector is point on the given line
- liesOn: function(line) {
- return line.contains(this);
- },
-
- // Return true iff the vector is a point in the given plane
- liesIn: function(plane) {
- return plane.contains(this);
- },
-
- // Rotates the vector about the given object. The object should be a
- // point if the vector is 2D, and a line if it is 3D. Be careful with line directions!
- rotate: function(t, obj) {
- var V, R, x, y, z;
- switch (this.elements.length) {
- case 2:
- V = obj.elements || obj;
- if (V.length != 2) { return null; }
- R = Matrix.Rotation(t).elements;
- x = this.elements[0] - V[0];
- y = this.elements[1] - V[1];
- return Vector.create([
- V[0] + R[0][0] * x + R[0][1] * y,
- V[1] + R[1][0] * x + R[1][1] * y
- ]);
- break;
- case 3:
- if (!obj.direction) { return null; }
- var C = obj.pointClosestTo(this).elements;
- R = Matrix.Rotation(t, obj.direction).elements;
- x = this.elements[0] - C[0];
- y = this.elements[1] - C[1];
- z = this.elements[2] - C[2];
- return Vector.create([
- C[0] + R[0][0] * x + R[0][1] * y + R[0][2] * z,
- C[1] + R[1][0] * x + R[1][1] * y + R[1][2] * z,
- C[2] + R[2][0] * x + R[2][1] * y + R[2][2] * z
- ]);
- break;
- default:
- return null;
- }
- },
-
- // Returns the result of reflecting the point in the given point, line or plane
- reflectionIn: function(obj) {
- if (obj.anchor) {
- // obj is a plane or line
- var P = this.elements.slice();
- var C = obj.pointClosestTo(P).elements;
- return Vector.create([C[0] + (C[0] - P[0]), C[1] + (C[1] - P[1]), C[2] + (C[2] - (P[2] || 0))]);
- } else {
- // obj is a point
- var Q = obj.elements || obj;
- if (this.elements.length != Q.length) { return null; }
- return this.map(function(x, i) { return Q[i-1] + (Q[i-1] - x); });
- }
- },
-
- // Utility to make sure vectors are 3D. If they are 2D, a zero z-component is added
- to3D: function() {
- var V = this.dup();
- switch (V.elements.length) {
- case 3: break;
- case 2: V.elements.push(0); break;
- default: return null;
- }
- return V;
- },
-
- // Returns a string representation of the vector
- inspect: function() {
- return '[' + this.elements.join(', ') + ']';
- },
-
- // Set vector's elements from an array
- setElements: function(els) {
- this.elements = (els.elements || els).slice();
- return this;
- }
- };
-
- // Constructor function
- Vector.create = function(elements) {
- var V = new Vector();
- return V.setElements(elements);
- };
-
- // i, j, k unit vectors
- Vector.i = Vector.create([1,0,0]);
- Vector.j = Vector.create([0,1,0]);
- Vector.k = Vector.create([0,0,1]);
-
- // Random vector of size n
- Vector.Random = function(n) {
- var elements = [];
- do { elements.push(Math.random());
- } while (--n);
- return Vector.create(elements);
- };
-
- // Vector filled with zeros
- Vector.Zero = function(n) {
- var elements = [];
- do { elements.push(0);
- } while (--n);
- return Vector.create(elements);
- };
-
- // global.Canvas.Vector = Vector;
-
- function Matrix() {}
- Matrix.prototype = {
-
- // Returns element (i,j) of the matrix
- e: function(i,j) {
- if (i < 1 || i > this.elements.length || j < 1 || j > this.elements[0].length) { return null; }
- return this.elements[i-1][j-1];
- },
-
- // Returns row k of the matrix as a vector
- row: function(i) {
- if (i > this.elements.length) { return null; }
- return Vector.create(this.elements[i-1]);
- },
-
- // Returns column k of the matrix as a vector
- col: function(j) {
- if (j > this.elements[0].length) { return null; }
- var col = [], n = this.elements.length, k = n, i;
- do { i = k - n;
- col.push(this.elements[i][j-1]);
- } while (--n);
- return Vector.create(col);
- },
-
- // Returns the number of rows/columns the matrix has
- dimensions: function() {
- return {rows: this.elements.length, cols: this.elements[0].length};
- },
-
- // Returns the number of rows in the matrix
- rows: function() {
- return this.elements.length;
- },
-
- // Returns the number of columns in the matrix
- cols: function() {
- return this.elements[0].length;
- },
-
- // Returns true iff the matrix is equal to the argument. You can supply
- // a vector as the argument, in which case the receiver must be a
- // one-column matrix equal to the vector.
- eql: function(matrix) {
- var M = matrix.elements || matrix;
- if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
- if (this.elements.length != M.length ||
- this.elements[0].length != M[0].length) { return false; }
- var ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
- do { i = ki - ni;
- nj = kj;
- do { j = kj - nj;
- if (Math.abs(this.elements[i][j] - M[i][j]) > Sylvester.precision) { return false; }
- } while (--nj);
- } while (--ni);
- return true;
- },
-
- // Returns a copy of the matrix
- dup: function() {
- return Matrix.create(this.elements);
- },
-
- // Maps the matrix to another matrix (of the same dimensions) according to the given function
- map: function(fn) {
- var els = [], ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
- do { i = ki - ni;
- nj = kj;
- els[i] = [];
- do { j = kj - nj;
- els[i][j] = fn(this.elements[i][j], i + 1, j + 1);
- } while (--nj);
- } while (--ni);
- return Matrix.create(els);
- },
-
- // Returns true iff the argument has the same dimensions as the matrix
- isSameSizeAs: function(matrix) {
- var M = matrix.elements || matrix;
- if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
- return (this.elements.length == M.length &&
- this.elements[0].length == M[0].length);
- },
-
- // Returns the result of adding the argument to the matrix
- add: function(matrix) {
- var M = matrix.elements || matrix;
- if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
- if (!this.isSameSizeAs(M)) { return null; }
- return this.map(function(x, i, j) { return x + M[i-1][j-1]; });
- },
-
- // Returns the result of subtracting the argument from the matrix
- subtract: function(matrix) {
- var M = matrix.elements || matrix;
- if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
- if (!this.isSameSizeAs(M)) { return null; }
- return this.map(function(x, i, j) { return x - M[i-1][j-1]; });
- },
-
- // Returns true iff the matrix can multiply the argument from the left
- canMultiplyFromLeft: function(matrix) {
- var M = matrix.elements || matrix;
- if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
- // this.columns should equal matrix.rows
- return (this.elements[0].length == M.length);
- },
-
- // Returns the result of multiplying the matrix from the right by the argument.
- // If the argument is a scalar then just multiply all the elements. If the argument is
- // a vector, a vector is returned, which saves you having to remember calling
- // col(1) on the result.
- multiply: function(matrix) {
- if (!matrix.elements) {
- return this.map(function(x) { return x * matrix; });
- }
- var returnVector = matrix.modulus ? true : false;
- var M = matrix.elements || matrix;
- if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
- if (!this.canMultiplyFromLeft(M)) { return null; }
- var ni = this.elements.length, ki = ni, i, nj, kj = M[0].length, j;
- var cols = this.elements[0].length, elements = [], sum, nc, c;
- do { i = ki - ni;
- elements[i] = [];
- nj = kj;
- do { j = kj - nj;
- sum = 0;
- nc = cols;
- do { c = cols - nc;
- sum += this.elements[i][c] * M[c][j];
- } while (--nc);
- elements[i][j] = sum;
- } while (--nj);
- } while (--ni);
- var M = Matrix.create(elements);
- return returnVector ? M.col(1) : M;
- },
-
- x: function(matrix) { return this.multiply(matrix); },
-
- // Returns a submatrix taken from the matrix
- // Argument order is: start row, start col, nrows, ncols
- // Element selection wraps if the required index is outside the matrix's bounds, so you could
- // use this to perform row/column cycling or copy-augmenting.
- minor: function(a, b, c, d) {
- var elements = [], ni = c, i, nj, j;
- var rows = this.elements.length, cols = this.elements[0].length;
- do { i = c - ni;
- elements[i] = [];
- nj = d;
- do { j = d - nj;
- elements[i][j] = this.elements[(a+i-1)%rows][(b+j-1)%cols];
- } while (--nj);
- } while (--ni);
- return Matrix.create(elements);
- },
-
- // Returns the transpose of the matrix
- transpose: function() {
- var rows = this.elements.length, cols = this.elements[0].length;
- var elements = [], ni = cols, i, nj, j;
- do { i = cols - ni;
- elements[i] = [];
- nj = rows;
- do { j = rows - nj;
- elements[i][j] = this.elements[j][i];
- } while (--nj);
- } while (--ni);
- return Matrix.create(elements);
- },
-
- // Returns true iff the matrix is square
- isSquare: function() {
- return (this.elements.length == this.elements[0].length);
- },
-
- // Returns the (absolute) largest element of the matrix
- max: function() {
- var m = 0, ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
- do { i = ki - ni;
- nj = kj;
- do { j = kj - nj;
- if (Math.abs(this.elements[i][j]) > Math.abs(m)) { m = this.elements[i][j]; }
- } while (--nj);
- } while (--ni);
- return m;
- },
-
- // Returns the indeces of the first match found by reading row-by-row from left to right
- indexOf: function(x) {
- var index = null, ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
- do { i = ki - ni;
- nj = kj;
- do { j = kj - nj;
- if (this.elements[i][j] == x) { return {i: i+1, j: j+1}; }
- } while (--nj);
- } while (--ni);
- return null;
- },
-
- // If the matrix is square, returns the diagonal elements as a vector.
- // Otherwise, returns null.
- diagonal: function() {
- if (!this.isSquare) { return null; }
- var els = [], n = this.elements.length, k = n, i;
- do { i = k - n;
- els.push(this.elements[i][i]);
- } while (--n);
- return Vector.create(els);
- },
-
- // Make the matrix upper (right) triangular by Gaussian elimination.
- // This method only adds multiples of rows to other rows. No rows are
- // scaled up or switched, and the determinant is preserved.
- toRightTriangular: function() {
- var M = this.dup(), els;
- var n = this.elements.length, k = n, i, np, kp = this.elements[0].length, p;
- do { i = k - n;
- if (M.elements[i][i] == 0) {
- for (j = i + 1; j < k; j++) {
- if (M.elements[j][i] != 0) {
- els = []; np = kp;
- do { p = kp - np;
- els.push(M.elements[i][p] + M.elements[j][p]);
- } while (--np);
- M.elements[i] = els;
- break;
- }
- }
- }
- if (M.elements[i][i] != 0) {
- for (j = i + 1; j < k; j++) {
- var multiplier = M.elements[j][i] / M.elements[i][i];
- els = []; np = kp;
- do { p = kp - np;
- // Elements with column numbers up to an including the number
- // of the row that we're subtracting can safely be set straight to
- // zero, since that's the point of this routine and it avoids having
- // to loop over and correct rounding errors later
- els.push(p <= i ? 0 : M.elements[j][p] - M.elements[i][p] * multiplier);
- } while (--np);
- M.elements[j] = els;
- }
- }
- } while (--n);
- return M;
- },
-
- toUpperTriangular: function() { return this.toRightTriangular(); },
-
- // Returns the determinant for square matrices
- determinant: function() {
- if (!this.isSquare()) { return null; }
- var M = this.toRightTriangular();
- var det = M.elements[0][0], n = M.elements.length - 1, k = n, i;
- do { i = k - n + 1;
- det = det * M.elements[i][i];
- } while (--n);
- return det;
- },
-
- det: function() { return this.determinant(); },
-
- // Returns true iff the matrix is singular
- isSingular: function() {
- return (this.isSquare() && this.determinant() === 0);
- },
-
- // Returns the trace for square matrices
- trace: function() {
- if (!this.isSquare()) { return null; }
- var tr = this.elements[0][0], n = this.elements.length - 1, k = n, i;
- do { i = k - n + 1;
- tr += this.elements[i][i];
- } while (--n);
- return tr;
- },
-
- tr: function() { return this.trace(); },
-
- // Returns the rank of the matrix
- rank: function() {
- var M = this.toRightTriangular(), rank = 0;
- var ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
- do { i = ki - ni;
- nj = kj;
- do { j = kj - nj;
- if (Math.abs(M.elements[i][j]) > Sylvester.precision) { rank++; break; }
- } while (--nj);
- } while (--ni);
- return rank;
- },
-
- rk: function() { return this.rank(); },
-
- // Returns the result of attaching the given argument to the right-hand side of the matrix
- augment: function(matrix) {
- var M = matrix.elements || matrix;
- if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
- var T = this.dup(), cols = T.elements[0].length;
- var ni = T.elements.length, ki = ni, i, nj, kj = M[0].length, j;
- if (ni != M.length) { return null; }
- do { i = ki - ni;
- nj = kj;
- do { j = kj - nj;
- T.elements[i][cols + j] = M[i][j];
- } while (--nj);
- } while (--ni);
- return T;
- },
-
- // Returns the inverse (if one exists) using Gauss-Jordan
- inverse: function() {
- if (!this.isSquare() || this.isSingular()) { return null; }
- var ni = this.elements.length, ki = ni, i, j;
- var M = this.augment(Matrix.I(ni)).toRightTriangular();
- var np, kp = M.elements[0].length, p, els, divisor;
- var inverse_elements = [], new_element;
- // Matrix is non-singular so there will be no zeros on the diagonal
- // Cycle through rows from last to first
- do { i = ni - 1;
- // First, normalise diagonal elements to 1
- els = []; np = kp;
- inverse_elements[i] = [];
- divisor = M.elements[i][i];
- do { p = kp - np;
- new_element = M.elements[i][p] / divisor;
- els.push(new_element);
- // Shuffle of the current row of the right hand side into the results
- // array as it will not be modified by later runs through this loop
- if (p >= ki) { inverse_elements[i].push(new_element); }
- } while (--np);
- M.elements[i] = els;
- // Then, subtract this row from those above it to
- // give the identity matrix on the left hand side
- for (j = 0; j < i; j++) {
- els = []; np = kp;
- do { p = kp - np;
- els.push(M.elements[j][p] - M.elements[i][p] * M.elements[j][i]);
- } while (--np);
- M.elements[j] = els;
- }
- } while (--ni);
- return Matrix.create(inverse_elements);
- },
-
- inv: function() { return this.inverse(); },
-
- // Returns the result of rounding all the elements
- round: function() {
- return this.map(function(x) { return Math.round(x); });
- },
-
- // Returns a copy of the matrix with elements set to the given value if they
- // differ from it by less than Sylvester.precision
- snapTo: function(x) {
- return this.map(function(p) {
- return (Math.abs(p - x) <= Sylvester.precision) ? x : p;
- });
- },
-
- // Returns a string representation of the matrix
- inspect: function() {
- var matrix_rows = [];
- var n = this.elements.length, k = n, i;
- do { i = k - n;
- matrix_rows.push(Vector.create(this.elements[i]).inspect());
- } while (--n);
- return matrix_rows.join('\n');
- },
-
- // Set the matrix's elements from an array. If the argument passed
- // is a vector, the resulting matrix will be a single column.
- setElements: function(els) {
- var i, elements = els.elements || els;
- if (typeof(elements[0][0]) != 'undefined') {
- var ni = elements.length, ki = ni, nj, kj, j;
- this.elements = [];
- do { i = ki - ni;
- nj = elements[i].length; kj = nj;
- this.elements[i] = [];
- do { j = kj - nj;
- this.elements[i][j] = elements[i][j];
- } while (--nj);
- } while(--ni);
- return this;
- }
- var n = elements.length, k = n;
- this.elements = [];
- do { i = k - n;
- this.elements.push([elements[i]]);
- } while (--n);
- return this;
- }
- };
-
- // Constructor function
- Matrix.create = function(elements) {
- var M = new Matrix();
- return M.setElements(elements);
- };
-
- // Identity matrix of size n
- Matrix.I = function(n) {
- var els = [], k = n, i, nj, j;
- do { i = k - n;
- els[i] = []; nj = k;
- do { j = k - nj;
- els[i][j] = (i == j) ? 1 : 0;
- } while (--nj);
- } while (--n);
- return Matrix.create(els);
- };
-
- // Diagonal matrix - all off-diagonal elements are zero
- Matrix.Diagonal = function(elements) {
- var n = elements.length, k = n, i;
- var M = Matrix.I(n);
- do { i = k - n;
- M.elements[i][i] = elements[i];
- } while (--n);
- return M;
- };
-
- // Rotation matrix about some axis. If no axis is
- // supplied, assume we're after a 2D transform
- Matrix.Rotation = function(theta, a) {
- if (!a) {
- return Matrix.create([
- [Math.cos(theta), -Math.sin(theta)],
- [Math.sin(theta), Math.cos(theta)]
- ]);
- }
- var axis = a.dup();
- if (axis.elements.length != 3) { return null; }
- var mod = axis.modulus();
- var x = axis.elements[0]/mod, y = axis.elements[1]/mod, z = axis.elements[2]/mod;
- var s = Math.sin(theta), c = Math.cos(theta), t = 1 - c;
- // Formula derived here: http://www.gamedev.net/reference/articles/article1199.asp
- // That proof rotates the co-ordinate system so theta
- // becomes -theta and sin becomes -sin here.
- return Matrix.create([
- [ t*x*x + c, t*x*y - s*z, t*x*z + s*y ],
- [ t*x*y + s*z, t*y*y + c, t*y*z - s*x ],
- [ t*x*z - s*y, t*y*z + s*x, t*z*z + c ]
- ]);
- };
-
- // Special case rotations
- Matrix.RotationX = function(t) {
- var c = Math.cos(t), s = Math.sin(t);
- return Matrix.create([
- [ 1, 0, 0 ],
- [ 0, c, -s ],
- [ 0, s, c ]
- ]);
- };
- Matrix.RotationY = function(t) {
- var c = Math.cos(t), s = Math.sin(t);
- return Matrix.create([
- [ c, 0, s ],
- [ 0, 1, 0 ],
- [ -s, 0, c ]
- ]);
- };
- Matrix.RotationZ = function(t) {
- var c = Math.cos(t), s = Math.sin(t);
- return Matrix.create([
- [ c, -s, 0 ],
- [ s, c, 0 ],
- [ 0, 0, 1 ]
- ]);
- };
-
- // Random matrix of n rows, m columns
- Matrix.Random = function(n, m) {
- return Matrix.Zero(n, m).map(
- function() { return Math.random(); }
- );
- };
-
- // Matrix filled with zeros
- Matrix.Zero = function(n, m) {
- var els = [], ni = n, i, nj, j;
- do { i = n - ni;
- els[i] = [];
- nj = m;
- do { j = m - nj;
- els[i][j] = 0;
- } while (--nj);
- } while (--ni);
- return Matrix.create(els);
- };
-
- global.Canvas.Matrix = Matrix;
-
- function Line() {}
- Line.prototype = {
-
- // Returns true if the argument occupies the same space as the line
- eql: function(line) {
- return (this.isParallelTo(line) && this.contains(line.anchor));
- },
-
- // Returns a copy of the line
- dup: function() {
- return Line.create(this.anchor, this.direction);
- },
-
- // Returns the result of translating the line by the given vector/array
- translate: function(vector) {
- var V = vector.elements || vector;
- return Line.create([
- this.anchor.elements[0] + V[0],
- this.anchor.elements[1] + V[1],
- this.anchor.elements[2] + (V[2] || 0)
- ], this.direction);
- },
-
- // Returns true if the line is parallel to the argument. Here, 'parallel to'
- // means that the argument's direction is either parallel or antiparallel to
- // the line's own direction. A line is parallel to a plane if the two do not
- // have a unique intersection.
- isParallelTo: function(obj) {
- if (obj.normal) { return obj.isParallelTo(this); }
- var theta = this.direction.angleFrom(obj.direction);
- return (Math.abs(theta) <= Sylvester.precision || Math.abs(theta - Math.PI) <= Sylvester.precision);
- },
-
- // Returns the line's perpendicular distance from the argument,
- // which can be a point, a line or a plane
- distanceFrom: function(obj) {
- if (obj.normal) { return obj.distanceFrom(this); }
- if (obj.direction) {
- // obj is a line
- if (this.isParallelTo(obj)) { return this.distanceFrom(obj.anchor); }
- var N = this.direction.cross(obj.direction).toUnitVector().elements;
- var A = this.anchor.elements, B = obj.anchor.elements;
- return Math.abs((A[0] - B[0]) * N[0] + (A[1] - B[1]) * N[1] + (A[2] - B[2]) * N[2]);
- } else {
- // obj is a point
- var P = obj.elements || obj;
- var A = this.anchor.elements, D = this.direction.elements;
- var PA1 = P[0] - A[0], PA2 = P[1] - A[1], PA3 = (P[2] || 0) - A[2];
- var modPA = Math.sqrt(PA1*PA1 + PA2*PA2 + PA3*PA3);
- if (modPA === 0) return 0;
- // Assumes direction vector is normalized
- var cosTheta = (PA1 * D[0] + PA2 * D[1] + PA3 * D[2]) / modPA;
- var sin2 = 1 - cosTheta*cosTheta;
- return Math.abs(modPA * Math.sqrt(sin2 < 0 ? 0 : sin2));
- }
- },
-
- // Returns true iff the argument is a point on the line
- contains: function(point) {
- var dist = this.distanceFrom(point);
- return (dist !== null && dist <= Sylvester.precision);
- },
-
- // Returns true iff the line lies in the given plane
- liesIn: function(plane) {
- return plane.contains(this);
- },
-
- // Returns true iff the line has a unique point of intersection with the argument
- intersects: function(obj) {
- if (obj.normal) { return obj.intersects(this); }
- return (!this.isParallelTo(obj) && this.distanceFrom(obj) <= Sylvester.precision);
- },
-
- // Returns the unique intersection point with the argument, if one exists
- intersectionWith: function(obj) {
- if (obj.normal) { return obj.intersectionWith(this); }
- if (!this.intersects(obj)) { return null; }
- var P = this.anchor.elements, X = this.direction.elements,
- Q = obj.anchor.elements, Y = obj.direction.elements;
- var X1 = X[0], X2 = X[1], X3 = X[2], Y1 = Y[0], Y2 = Y[1], Y3 = Y[2];
- var PsubQ1 = P[0] - Q[0], PsubQ2 = P[1] - Q[1], PsubQ3 = P[2] - Q[2];
- var XdotQsubP = - X1*PsubQ1 - X2*PsubQ2 - X3*PsubQ3;
- var YdotPsubQ = Y1*PsubQ1 + Y2*PsubQ2 + Y3*PsubQ3;
- var XdotX = X1*X1 + X2*X2 + X3*X3;
- var YdotY = Y1*Y1 + Y2*Y2 + Y3*Y3;
- var XdotY = X1*Y1 + X2*Y2 + X3*Y3;
- var k = (XdotQsubP * YdotY / XdotX + XdotY * YdotPsubQ) / (YdotY - XdotY * XdotY);
- return Vector.create([P[0] + k*X1, P[1] + k*X2, P[2] + k*X3]);
- },
-
- // Returns the point on the line that is closest to the given point or line
- pointClosestTo: function(obj) {
- if (obj.direction) {
- // obj is a line
- if (this.intersects(obj)) { return this.intersectionWith(obj); }
- if (this.isParallelTo(obj)) { return null; }
- var D = this.direction.elements, E = obj.direction.elements;
- var D1 = D[0], D2 = D[1], D3 = D[2], E1 = E[0], E2 = E[1], E3 = E[2];
- // Create plane containing obj and the shared normal and intersect this with it
- // Thank you: http://www.cgafaq.info/wiki/Line-line_distance
- var x = (D3 * E1 - D1 * E3), y = (D1 * E2 - D2 * E1), z = (D2 * E3 - D3 * E2);
- var N = Vector.create([x * E3 - y * E2, y * E1 - z * E3, z * E2 - x * E1]);
- var P = Plane.create(obj.anchor, N);
- return P.intersectionWith(this);
- } else {
- // obj is a point
- var P = obj.elements || obj;
- if (this.contains(P)) { return Vector.create(P); }
- var A = this.anchor.elements, D = this.direction.elements;
- var D1 = D[0], D2 = D[1], D3 = D[2], A1 = A[0], A2 = A[1], A3 = A[2];
- var x = D1 * (P[1]-A2) - D2 * (P[0]-A1), y = D2 * ((P[2] || 0) - A3) - D3 * (P[1]-A2),
- z = D3 * (P[0]-A1) - D1 * ((P[2] || 0) - A3);
- var V = Vector.create([D2 * x - D3 * z, D3 * y - D1 * x, D1 * z - D2 * y]);
- var k = this.distanceFrom(P) / V.modulus();
- return Vector.create([
- P[0] + V.elements[0] * k,
- P[1] + V.elements[1] * k,
- (P[2] || 0) + V.elements[2] * k
- ]);
- }
- },
-
- // Returns a copy of the line rotated by t radians about the given line. Works by
- // finding the argument's closest point to this line's anchor point (call this C) and
- // rotating the anchor about C. Also rotates the line's direction about the argument's.
- // Be careful with this - the rotation axis' direction affects the outcome!
- rotate: function(t, line) {
- // If we're working in 2D
- if (typeof(line.direction) == 'undefined') { line = Line.create(line.to3D(), Vector.k); }
- var R = Matrix.Rotation(t, line.direction).elements;
- var C = line.pointClosestTo(this.anchor).elements;
- var A = this.anchor.elements, D = this.direction.elements;
- var C1 = C[0], C2 = C[1], C3 = C[2], A1 = A[0], A2 = A[1], A3 = A[2];
- var x = A1 - C1, y = A2 - C2, z = A3 - C3;
- return Line.create([
- C1 + R[0][0] * x + R[0][1] * y + R[0][2] * z,
- C2 + R[1][0] * x + R[1][1] * y + R[1][2] * z,
- C3 + R[2][0] * x + R[2][1] * y + R[2][2] * z
- ], [
- R[0][0] * D[0] + R[0][1] * D[1] + R[0][2] * D[2],
- R[1][0] * D[0] + R[1][1] * D[1] + R[1][2] * D[2],
- R[2][0] * D[0] + R[2][1] * D[1] + R[2][2] * D[2]
- ]);
- },
-
- // Returns the line's reflection in the given point or line
- reflectionIn: function(obj) {
- if (obj.normal) {
- // obj is a plane
- var A = this.anchor.elements, D = this.direction.elements;
- var A1 = A[0], A2 = A[1], A3 = A[2], D1 = D[0], D2 = D[1], D3 = D[2];
- var newA = this.anchor.reflectionIn(obj).elements;
- // Add the line's direction vector to its anchor, then mirror that in the plane
- var AD1 = A1 + D1, AD2 = A2 + D2, AD3 = A3 + D3;
- var Q = obj.pointClosestTo([AD1, AD2, AD3]).elements;
- var newD = [Q[0] + (Q[0] - AD1) - newA[0], Q[1] + (Q[1] - AD2) - newA[1], Q[2] + (Q[2] - AD3) - newA[2]];
- return Line.create(newA, newD);
- } else if (obj.direction) {
- // obj is a line - reflection obtained by rotating PI radians about obj
- return this.rotate(Math.PI, obj);
- } else {
- // obj is a point - just reflect the line's anchor in it
- var P = obj.elements || obj;
- return Line.create(this.anchor.reflectionIn([P[0], P[1], (P[2] || 0)]), this.direction);
- }
- },
-
- // Set the line's anchor point and direction.
- setVectors: function(anchor, direction) {
- // Need to do this so that line's properties are not
- // references to the arguments passed in
- anchor = Vector.create(anchor);
- direction = Vector.create(direction);
- if (anchor.elements.length == 2) {anchor.elements.push(0); }
- if (direction.elements.length == 2) { direction.elements.push(0); }
- if (anchor.elements.length > 3 || direction.elements.length > 3) { return null; }
- var mod = direction.modulus();
- if (mod === 0) { return null; }
- this.anchor = anchor;
- this.direction = Vector.create([
- direction.elements[0] / mod,
- direction.elements[1] / mod,
- direction.elements[2] / mod
- ]);
- return this;
- }
- };
-
-
- // Constructor function
- Line.create = function(anchor, direction) {
- var L = new Line();
- return L.setVectors(anchor, direction);
- };
-
- // Axes
- Line.X = Line.create(Vector.Zero(3), Vector.i);
- Line.Y = Line.create(Vector.Zero(3), Vector.j);
- Line.Z = Line.create(Vector.Zero(3), Vector.k);
-
- //global.Canvas.Line = Line;
-
- function Plane() {}
- Plane.prototype = {
-
- // Returns true iff the plane occupies the same space as the argument
- eql: function(plane) {
- return (this.contains(plane.anchor) && this.isParallelTo(plane));
- },
-
- // Returns a copy of the plane
- dup: function() {
- return Plane.create(this.anchor, this.normal);
- },
-
- // Returns the result of translating the plane by the given vector
- translate: function(vector) {
- var V = vector.elements || vector;
- return Plane.create([
- this.anchor.elements[0] + V[0],
- this.anchor.elements[1] + V[1],
- this.anchor.elements[2] + (V[2] || 0)
- ], this.normal);
- },
-
- // Returns true iff the plane is parallel to the argument. Will return true
- // if the planes are equal, or if you give a line and it lies in the plane.
- isParallelTo: function(obj) {
- var theta;
- if (obj.normal) {
- // obj is a plane
- theta = this.normal.angleFrom(obj.normal);
- return (Math.abs(theta) <= Sylvester.precision || Math.abs(Math.PI - theta) <= Sylvester.precision);
- } else if (obj.direction) {
- // obj is a line
- return this.normal.isPerpendicularTo(obj.direction);
- }
- return null;
- },
-
- // Returns true iff the receiver is perpendicular to the argument
- isPerpendicularTo: function(plane) {
- var theta = this.normal.angleFrom(plane.normal);
- return (Math.abs(Math.PI/2 - theta) <= Sylvester.precision);
- },
-
- // Returns the plane's distance from the given object (point, line or plane)
- distanceFrom: function(obj) {
- if (this.intersects(obj) || this.contains(obj)) { return 0; }
- if (obj.anchor) {
- // obj is a plane or line
- var A = this.anchor.elements, B = obj.anchor.elements, N = this.normal.elements;
- return Math.abs((A[0] - B[0]) * N[0] + (A[1] - B[1]) * N[1] + (A[2] - B[2]) * N[2]);
- } else {
- // obj is a point
- var P = obj.elements || obj;
- var A = this.anchor.elements, N = this.normal.elements;
- return Math.abs((A[0] - P[0]) * N[0] + (A[1] - P[1]) * N[1] + (A[2] - (P[2] || 0)) * N[2]);
- }
- },
-
- // Returns true iff the plane contains the given point or line
- contains: function(obj) {
- if (obj.normal) { return null; }
- if (obj.direction) {
- return (this.contains(obj.anchor) && this.contains(obj.anchor.add(obj.direction)));
- } else {
- var P = obj.elements || obj;
- var A = this.anchor.elements, N = this.normal.elements;
- var diff = Math.abs(N[0]*(A[0] - P[0]) + N[1]*(A[1] - P[1]) + N[2]*(A[2] - (P[2] || 0)));
- return (diff <= Sylvester.precision);
- }
- },
-
- // Returns true iff the plane has a unique point/line of intersection with the argument
- intersects: function(obj) {
- if (typeof(obj.direction) == 'undefined' && typeof(obj.normal) == 'undefined') { return null; }
- return !this.isParallelTo(obj);
- },
-
- // Returns the unique intersection with the argument, if one exists. The result
- // will be a vector if a line is supplied, and a line if a plane is supplied.
- intersectionWith: function(obj) {
- if (!this.intersects(obj)) { return null; }
- if (obj.direction) {
- // obj is a line
- var A = obj.anchor.elements, D = obj.direction.elements,
- P = this.anchor.elements, N = this.normal.elements;
- var multiplier = (N[0]*(P[0]-A[0]) + N[1]*(P[1]-A[1]) + N[2]*(P[2]-A[2])) / (N[0]*D[0] + N[1]*D[1] + N[2]*D[2]);
- return Vector.create([A[0] + D[0]*multiplier, A[1] + D[1]*multiplier, A[2] + D[2]*multiplier]);
- } else if (obj.normal) {
- // obj is a plane
- var direction = this.normal.cross(obj.normal).toUnitVector();
- // To find an anchor point, we find one co-ordinate that has a value
- // of zero somewhere on the intersection, and remember which one we picked
- var N = this.normal.elements, A = this.anchor.elements,
- O = obj.normal.elements, B = obj.anchor.elements;
- var solver = Matrix.Zero(2,2), i = 0;
- while (solver.isSingular()) {
- i++;
- solver = Matrix.create([
- [ N[i%3], N[(i+1)%3] ],
- [ O[i%3], O[(i+1)%3] ]
- ]);
- }
- // Then we solve the simultaneous equations in the remaining dimensions
- var inverse = solver.inverse().elements;
- var x = N[0]*A[0] + N[1]*A[1] + N[2]*A[2];
- var y = O[0]*B[0] + O[1]*B[1] + O[2]*B[2];
- var intersection = [
- inverse[0][0] * x + inverse[0][1] * y,
- inverse[1][0] * x + inverse[1][1] * y
- ];
- var anchor = [];
- for (var j = 1; j <= 3; j++) {
- // This formula picks the right element from intersection by
- // cycling depending on which element we set to zero above
- anchor.push((i == j) ? 0 : intersection[(j + (5 - i)%3)%3]);
- }
- return Line.create(anchor, direction);
- }
- },
-
- // Returns the point in the plane closest to the given point
- pointClosestTo: function(point) {
- var P = point.elements || point;
- var A = this.anchor.elements, N = this.normal.elements;
- var dot = (A[0] - P[0]) * N[0] + (A[1] - P[1]) * N[1] + (A[2] - (P[2] || 0)) * N[2];
- return Vector.create([P[0] + N[0] * dot, P[1] + N[1] * dot, (P[2] || 0) + N[2] * dot]);
- },
-
- // Returns a copy of the plane, rotated by t radians about the given line
- // See notes on Line#rotate.
- rotate: function(t, line) {
- var R = Matrix.Rotation(t, line.direction).elements;
- var C = line.pointClosestTo(this.anchor).elements;
- var A = this.anchor.elements, N = this.normal.elements;
- var C1 = C[0], C2 = C[1], C3 = C[2], A1 = A[0], A2 = A[1], A3 = A[2];
- var x = A1 - C1, y = A2 - C2, z = A3 - C3;
- return Plane.create([
- C1 + R[0][0] * x + R[0][1] * y + R[0][2] * z,
- C2 + R[1][0] * x + R[1][1] * y + R[1][2] * z,
- C3 + R[2][0] * x + R[2][1] * y + R[2][2] * z
- ], [
- R[0][0] * N[0] + R[0][1] * N[1] + R[0][2] * N[2],
- R[1][0] * N[0] + R[1][1] * N[1] + R[1][2] * N[2],
- R[2][0] * N[0] + R[2][1] * N[1] + R[2][2] * N[2]
- ]);
- },
-
- // Returns the reflection of the plane in the given point, line or plane.
- reflectionIn: function(obj) {
- if (obj.normal) {
- // obj is a plane
- var A = this.anchor.elements, N = this.normal.elements;
- var A1 = A[0], A2 = A[1], A3 = A[2], N1 = N[0], N2 = N[1], N3 = N[2];
- var newA = this.anchor.reflectionIn(obj).elements;
- // Add the plane's normal to its anchor, then mirror that in the other plane
- var AN1 = A1 + N1, AN2 = A2 + N2, AN3 = A3 + N3;
- var Q = obj.pointClosestTo([AN1, AN2, AN3]).elements;
- var newN = [Q[0] + (Q[0] - AN1) - newA[0], Q[1] + (Q[1] - AN2) - newA[1], Q[2] + (Q[2] - AN3) - newA[2]];
- return Plane.create(newA, newN);
- } else if (obj.direction) {
- // obj is a line
- return this.rotate(Math.PI, obj);
- } else {
- // obj is a point
- var P = obj.elements || obj;
- return Plane.create(this.anchor.reflectionIn([P[0], P[1], (P[2] || 0)]), this.normal);
- }
- },
-
- // Sets the anchor point and normal to the plane. If three arguments are specified,
- // the normal is calculated by assuming the three points should lie in the same plane.
- // If only two are sepcified, the second is taken to be the normal. Normal vector is
- // normalised before storage.
- setVectors: function(anchor, v1, v2) {
- anchor = Vector.create(anchor);
- anchor = anchor.to3D(); if (anchor === null) { return null; }
- v1 = Vector.create(v1);
- v1 = v1.to3D(); if (v1 === null) { return null; }
- if (typeof(v2) == 'undefined') {
- v2 = null;
- } else {
- v2 = Vector.create(v2);
- v2 = v2.to3D(); if (v2 === null) { return null; }
- }
- var A1 = anchor.elements[0], A2 = anchor.elements[1], A3 = anchor.elements[2];
- var v11 = v1.elements[0], v12 = v1.elements[1], v13 = v1.elements[2];
- var normal, mod;
- if (v2 !== null) {
- var v21 = v2.elements[0], v22 = v2.elements[1], v23 = v2.elements[2];
- normal = Vector.create([
- (v12 - A2) * (v23 - A3) - (v13 - A3) * (v22 - A2),
- (v13 - A3) * (v21 - A1) - (v11 - A1) * (v23 - A3),
- (v11 - A1) * (v22 - A2) - (v12 - A2) * (v21 - A1)
- ]);
- mod = normal.modulus();
- if (mod === 0) { return null; }
- normal = Vector.create([normal.elements[0] / mod, normal.elements[1] / mod, normal.elements[2] / mod]);
- } else {
- mod = Math.sqrt(v11*v11 + v12*v12 + v13*v13);
- if (mod === 0) { return null; }
- normal = Vector.create([v1.elements[0] / mod, v1.elements[1] / mod, v1.elements[2] / mod]);
- }
- this.anchor = anchor;
- this.normal = normal;
- return this;
- }
- };
-
- // Constructor function
- Plane.create = function(anchor, v1, v2) {
- var P = new Plane();
- return P.setVectors(anchor, v1, v2);
- };
-
- // X-Y-Z planes
- Plane.XY = Plane.create(Vector.Zero(3), Vector.k);
- Plane.YZ = Plane.create(Vector.Zero(3), Vector.i);
- Plane.ZX = Plane.create(Vector.Zero(3), Vector.j);
- Plane.YX = Plane.XY; Plane.ZY = Plane.YZ; Plane.XZ = Plane.ZX;
-
- //global.Canvas.Plane = Plane;
-
- // Utility functions
- // not exported
- var $V = Vector.create;
- var $M = Matrix.create;
- var $L = Line.create;
- var $P = Plane.create;
-
-})();
\ No newline at end of file
diff --git a/test/benchmark.html b/test/benchmark.html
new file mode 100644
index 00000000..c77e3c0d
--- /dev/null
+++ b/test/benchmark.html
@@ -0,0 +1,95 @@
+
+
+
+ Unit test file :: Canvas.Text
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Canvas benchmark
+
+
+
+
+
+
+
\ No newline at end of file