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. + *

+ *
    + *
  1. Create a function on the object that fires the "event", then call that function + * when the event fires (this happens automatically with native events). + *
  2. + *
  3. + * Instantiate an EventPublisher using the constructor, then call fire + * when the callbacks should be run. + *
  4. + *
+ *

+ * 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