Starts work on transform stack.
authordsc <david.schoonover@gmail.com>
Thu, 4 Nov 2010 05:46:26 +0000 (22:46 -0700)
committerdsc <david.schoonover@gmail.com>
Thu, 4 Nov 2010 05:46:26 +0000 (22:46 -0700)
35 files changed:
css/lttl.css
index.php
notes.md
src/Y/core.js
src/Y/modules/y.control.js [new file with mode: 0644]
src/Y/modules/y.event.js
src/Y/modules/y.op.js [deleted file]
src/Y/y-class.js
src/Y/y-core.js
src/Y/y-function.js
src/Y/y-number.js
src/Y/y-string.js
src/lessly/bitgrid.js
src/lessly/draw.js
src/lessly/log.js
src/lessly/viewport.js
src/portal/layer.js
src/portal/shape.js
src/portal/transform.js [new file with mode: 0644]
src/portal/util/eventloop.js
src/portal/util/fps.js
src/portal/util/loc.js
src/portal/util/pointquadtree.js
src/portal/util/quadtree.js
src/portal/util/rbtree.js
src/tanks/game/game.js
src/tanks/game/map.js
src/tanks/game/player.js
src/tanks/globals.js [new file with mode: 0644]
src/tanks/ui.js
src/tanks/unit/tank.js
src/tanks/unit/thing.js
src/tanks/util/calc.js [new file with mode: 0644]
src/tanks/util/grid.js
src/tanks/util/pathmap.js

index 0bd2596..5b26d43 100644 (file)
@@ -6,8 +6,8 @@ ul, ol, li { list-style: none ! important; margin:0; padding:0; }
 
 .rounded { border-radius:1em; -moz-border-radius:1em; -webkit-border-radius:1em; }
 
-#viewport { position:relative; top:1em; width:500px; height:500px; margin:0 auto;
-    /* outline:1px solid #ccc; */ }
+#viewport { position:relative; top:1em; width:500px; height:500px; margin:0 auto; overflow:hidden;
+    /* outline:1px solid #aaa; */ }
 
 #howto { position:fixed; top:3em; right:1em; color:#BFBFBF; }
 
index e2b5d7a..f22a776 100644 (file)
--- a/index.php
+++ b/index.php
@@ -50,17 +50,15 @@ $scripts = array(
     "src/portal/util/cooldown.js",
     "src/portal/util/loc.js",
     
+    "src/tanks/globals.js",
+    "src/tanks/util/calc.js",
     "src/tanks/util/pathmap.js",
     "src/tanks/util/grid.js",
-    
     "src/tanks/game/game.js",
     "src/tanks/game/map.js",
-    
     "src/tanks/unit/thing.js",
     "src/tanks/unit/tank.js",
-    
     "src/tanks/game/player.js",
-    
     "src/tanks/lttl.js",
     "src/tanks/ui.js"
     
index 1d5c4a2..dc5d10b 100644 (file)
--- a/notes.md
+++ b/notes.md
@@ -1 +1,2 @@
-- Clipping is going to suck
\ No newline at end of file
+- Clipping is going to suck
+- TODO Replace *2 and /2 with shifts at compile-time
\ No newline at end of file
index bc22ed7..4536fb6 100644 (file)
@@ -20,6 +20,16 @@ function reduce(o, fn, acc, cxt){
     return acc;
 }
 
+function set(o, key, value, def){
+    if ( o && notSelfOrWrapped(o.set) )
+        return o.set.apply(o, slice.call(arguments,1));
+    
+    if ( o && key !== undefined )
+        o[key] = (value !== undefined ? value : def);
+    
+    return o;
+}
+
 function attr(o, key, value, def){
     if ( o && notSelfOrWrapped(o.attr) )
         return o.attr.apply(o, slice.call(arguments,1));
@@ -30,8 +40,7 @@ function attr(o, key, value, def){
         return extend(o, key);
     
     if ( value !== undefined || def !== undefined ){
-        o[key] = (value !== undefined ? value : def);
-        return o;
+        return set(o, key, value, def);
     } else
         return o[key];
 }
diff --git a/src/Y/modules/y.control.js b/src/Y/modules/y.control.js
new file mode 100644 (file)
index 0000000..84631ef
--- /dev/null
@@ -0,0 +1,48 @@
+(function(Y, undefined){ if (!Y) return;
+
+/* Functional Control "Statements" */
+Y.extend(Y.op, { 
+    
+    // flow
+    either: function( test, t, f ){ return function(v){ return test(v) ? t : f; }; },
+    dowhile: function( test, action ){
+        return function(v){
+            var acc = v;
+            while( test(acc) ){ acc = action(acc); }
+            return acc;
+        };
+    },
+    dountil: function( test, action ){ 
+        return Y.ops.dowhile( function(v){ return !test(v); }, action ); 
+    },
+    forloop: function( test, action, pretest, posttest ){
+        pretest = pretest || Y.ops.I;
+        posttest = posttest || Y.ops.I;
+        return function(v){
+            for(var acc = pretest(v); test(acc); acc = posttest(acc) ){
+                acc = action(acc);
+            }
+            return acc;
+        };
+    },
+    repeat: function( action, n ){
+        return function(v){
+            for( var acc = v, i = 0; i < n; ++i ){ acc = action(acc); }
+            return acc;
+        };
+    },
+    maybe: function( pitcher, catcher ){
+        return function(){
+            var args = Y(arguments);
+            try { 
+                return pitcher.apply( this, args );
+            } catch(e) {
+                args.unshift(e);
+                return catcher.apply( this, args );
+            }
+        };
+    }
+});
+
+
+})(this.Y);
index 8b8cdce..7e085cf 100644 (file)
@@ -11,7 +11,7 @@ ns = Y.event = {}
  */
 , YEvent = ns.YEvent = 
 Y.YObject.subclass('YEvent', {
-    init : function( type, target, trigger, data ){
+    init : function init( type, target, trigger, data ){
         data = data || {};
         for (var k in data) this[k] = data[k];
         this.data = this._o = data;
@@ -28,23 +28,23 @@ Y.YObject.subclass('YEvent', {
  * A simple multicaster.
  */
 , methods = {
-    getQueue : function(evt){
+    getQueue : function getQueue(evt){
         var Qs = this.queues;
         if ( !Qs[evt] )
             Qs[evt] = Y([]);
         return Qs[evt];
     },
     
-    addEventListener : function(evt, fn){
+    addEventListener : function addEventListener(evt, fn){
         this.getQueue(evt).push(fn);
         return this.target;
     },
     
-    removeEventListener : function(evt, fn){
+    removeEventListener : function removeEventListener(evt, fn){
         this.getQueue(evt).remove(fn);
     },
     
-    fire : function(evtname, trigger, data, async){
+    fire : function fire(evtname, trigger, data, async){
         var evt = new YEvent(evtname, this.target, trigger, data);
         if (async)
             setTimeout(this.dispatchEvent.bind(this, evt), 10);
@@ -54,7 +54,7 @@ Y.YObject.subclass('YEvent', {
     },
     
     // XXX: does not handle degenerate or recursive event dispatch
-    dispatchEvent : function(evt){
+    dispatchEvent : function dispatchEvent(evt){
         this.getQueue(evt.type).invoke('call', evt.target, evt);
         if (this.parent) this.parent.fire(evt.type, evt.trigger, evt.data);
         return evt;
@@ -63,7 +63,7 @@ Y.YObject.subclass('YEvent', {
     /**
      * Decorates object with bound methods to act as a delegate of this hub.
      */
-    decorate : function(delegate){
+    decorate : function decorate(delegate){
         if (!delegate) return;
         for (var k in methods)
             delegate[k] = methods[k].bind(this);
diff --git a/src/Y/modules/y.op.js b/src/Y/modules/y.op.js
deleted file mode 100644 (file)
index d8260a4..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-(function(Y, undefined){ if (!Y) return;
-
-/* Functional Operators (Taste Great with Curry!) */
-Y.op = { 
-    
-    // comparison
-    cmp:    function(x,y){  return x == y ? 0 : (x > y ? 1 : -1); },
-    eq:     function(x,y){  return x == y; },
-    ne:     function(x,y){  return x != y; },
-    gt:     function(x,y){  return x  > y; },
-    ge:     function(x,y){  return x >= y; },
-    lt:     function(x,y){  return x  < y; },
-    le:     function(x,y){  return x <= y; },
-    
-    // math
-    add:    function(x,y){  return x  + y; },
-    sub:    function(x,y){  return x  - y; },
-    mul:    function(x,y){  return x  * y; },
-    div:    function(x,y){  return x  / y; },
-    flrdiv: function(x,y){  return Math.floor(x / y); },
-    mod:    function(x,y){  return x  % y; },
-    
-    // logic
-    and:    function(x,y){  return x && y; },
-    or:     function(x,y){  return x || y; },
-    xor:    function(x,y){  return x != y; },
-    not:    function(x){    return !x; },
-    neg:    function(x){    return -x; },
-    
-    // bitwise
-    bitnot: function(x){    return ~x; },
-    bitand: function(x,y){  return x  & y; },
-    bitor:  function(x,y){  return x  | y; },
-    bitxor: function(x,y){  return x  ^ y; },
-    lshift: function(x,y){  return x << y; },
-    rshift: function(x,y){  return x >> y; },
-    zrshift: function(x,y){ return x >>> y; },
-    
-    // typecast
-    bool:   function(x){    return !!x; },
-    number: function(x){    return Number(x); },
-    int:    function(x){    return parseInt(x); },
-    float:  function(x){    return parseFloat(x); },
-    str:    function(x){    return String(x); },
-    
-    // functional
-    I:      function(x){    return x; },
-    K:      function(k){    return function(){ return k; }; },
-    nop:    function(){},
-    
-    // accessors
-    isset:  function(o,def){ return o !== undefined ? o : def; },
-    has:    function(x,y){  return y in x; },
-    getkey: function(k,o){  return o[k] },
-    getdef: function(o,k,def){ return (k in o ? o[k] : def); },
-    set:    function(o,k,v){ if (o) o[k] = v; return o; },
-    
-    
-    // flow
-    either: function( test, t, f ){ return function(v){ return test(v) ? t : f; }; },
-    dowhile: function( test, action ){
-        return function(v){
-            var acc = v;
-            while( test(acc) ){ acc = action(acc); }
-            return acc;
-        };
-    },
-    dountil: function( test, action ){ 
-        return Y.ops.dowhile( function(v){ return !test(v); }, action ); 
-    },
-    forloop: function( test, action, pretest, posttest ){
-        pretest = pretest || Y.ops.I;
-        posttest = posttest || Y.ops.I;
-        return function(v){
-            for(var acc = pretest(v); test(acc); acc = posttest(acc) ){
-                acc = action(acc);
-            }
-            return acc;
-        };
-    },
-    repeat: function( action, n ){
-        return function(v){
-            for( var acc = v, i = 0; i < n; ++i ){ acc = action(acc); }
-            return acc;
-        };
-    },
-    maybe: function( pitcher, catcher ){
-        return function(){
-            var args = Y(arguments);
-            try { 
-                return pitcher.apply( this, args );
-            } catch(e) {
-                args.unshift(e);
-                return catcher.apply( this, args );
-            }
-        };
-    }
-};
-
-
-})(this.Y);
index d297e05..ee07b8c 100644 (file)
@@ -118,9 +118,10 @@ Y.subclass = Class;
 Class.instantiate = 
     function(){
         var instance = this.fabricate();
-        if ( instance.init )
-            return instance.init.apply(instance, arguments);
-        else
+        if ( instance.init ) {
+            var r = instance.init.apply(instance, arguments);
+            return (r !== undefined ? r : instance);
+        } else
             return instance;
     };
     
index 10a9b26..995751b 100644 (file)
@@ -1,5 +1,6 @@
 
 Y.reduce = reduce;
+Y.set    = set;
 Y.attr   = attr;
 Y.extend = extend;
 
index ef502f9..01edb5a 100644 (file)
@@ -18,7 +18,7 @@ Y.extend(YFunction.prototype, {
     init   : YFunction,
     reduce : methodize(Y.reduce),
     extend : methodize(Y.extend),
-    end : function(){ return this; }
+    end : function end(){ return this; }
 });
 YFunction.prototype.attr   = methodize(Y.attr);
 // YFunction.prototype.extend = methodize(Y.extend);
@@ -185,28 +185,40 @@ function genericize( fn ) {
 // XXX: hashCode()
 Y.memoize = memoize;
 YFunction.prototype.memoize = methodize(memoize);
-function memoize(fn){
+
+/**
+ * @param {Function} fn Function to memorize.
+ * @param {Array -> String} [keyfn] Serializes an array of arguments to a string. By default, joins with three nulls.
+ * @param {Object} [cacher] Object on which to store the memorization cache for lookups. Useful for meta-caches. Defaults to the memorized function.
+ * @param {String} [cacheName="cache"] Property this cache will be stored under.
+ */
+function memoize(fn, keyfn, cacher, cacheName){
     if (fn.__memoized__) return fn.__memoized__;
-    var cache;
     
-    fn.__memoized__ = memorizer;
-    function memorizer(){
-        // I'd like to use toJSON here, but that won't work cross-browser.
-        var key = Y(arguments).join('\0');
-        if ( !(key in cache) )
-            cache[key] = fn.apply(this, arguments);
-        return cache[key];
-    }
+    var m =
+    fn.__memoized__ =
+        function memorizer(){
+            var key = keyfn(Y(arguments))
+            ,   cache = cacher[cacheName] ;
+            if ( !(key in cache) )
+                cache[key] = fn.apply(this, arguments);
+            return cache[key];
+        };
     
-    memorizer.purge = purge;
-    function purge(){
-        memorizer.cache = cache = {};
-    }
+    // toJSON would be a better alternative, but that won't work cross-browser
+    keyfn     = keyfn  || function keyfn(args){ return args.join('\0\0\0'); };
+    cacher    = cacher || m;
+    cacheName = cacheName || 'cache';
     
-    purge();
-    memorizer.__wraps__ = fn;
+    m.__wraps__ = fn;
+    m.purge = function purge(){
+        var cache = cacher[cacheName];
+        cacher[cacheName] = {};
+        return cache;
+    };
     
-    return memorizer;
+    m.purge();
+    return m;
 }
 
 // Memorized to reduce eval costs
@@ -214,28 +226,52 @@ var
 _ofArityWrapper =
 YFunction._ofArityWrapper =
     memoize(function(n, limit){
-        return eval('(function(fn){ '+
+        return eval('(function '+(limit ? 'limited' : 'artized')+'(fn){ '+
             'return function('+Y.range(n).map( Y.op.add('$') ).join(',')+'){ '+
                 'return fn.apply(this,' + (limit ? 'Y(arguments).slice(0,'+n+')' : 'arguments')+ 
             '); }; })');
     });
 
-YFunction.prototype.aritize = function(n){
-    return _ofArityWrapper(n, false)(this);
+YFunction.prototype.aritize = 
+function aritize(n){
+    var fn = this
+    ,   cache = fn.__aritized__ ;
+    
+    if (fn.length === n)
+        return fn;
+    
+    if ( !cache )
+        cache = fn.__aritized__ = {};
+    else if ( cache[n] )
+        return cache[n];
+    
+    return ( cache[n] = _ofArityWrapper(n, false)(fn) );
 };
 
-YFunction.prototype.limit = function(n){
-    return _ofArityWrapper(n, true)(this);
+YFunction.prototype.limit = 
+function limit(fn, n){
+    var fn = this
+    ,   cache = fn.__limited__ ;
+    
+    if (fn.length === n)
+        return fn;
+    
+    if ( !cache )
+        cache = fn.__limited__ = {};
+    else if ( cache[n] )
+        return cache[n];
+    
+    return ( cache[n] = _ofArityWrapper(n, true)(fn) );
 };
 
 
 /**
  * Filter the arguments passed to the wrapper function
  */
-YFunction.prototype.mask = mask;
-function mask(){
-    
-}
+// YFunction.prototype.mask = mask;
+// function mask(){
+//     
+// }
 
 
 
@@ -270,6 +306,7 @@ function mixinNames(o, Donor, names, override, yWrap){
 
 YFunction(Y);
 Y(Y.reduce);
+Y(Y.set);
 Y(Y.attr);
 Y(Y.extend);
 Y.reduce(YFunction.prototype, function(_,fn,name){
index c390bb5..dba6137 100644 (file)
@@ -8,13 +8,13 @@ YCollection.subclass('YNumber', {
         YCollection.init.call(this, o);
     },
     
-    compare : function(n){
+    compare : function compare(n){
         var m = this._o;
         return (m > n ?  1 :
                (m < n ? -1 : 0 ));
     },
     
-    toString : function(){
+    toString : function toString(){
         return this.end()+'';
     }
 });
index 57d2ddc..572d443 100644 (file)
@@ -40,7 +40,7 @@ var
 YString =
 Y.YString =
 YCollection.subclass('YString', {
-    init : function(o){
+    init : function init(o){
         if (!o) o = "";
         this._o = o;
         YCollection.init.call(this, o);
@@ -52,7 +52,7 @@ YCollection.subclass('YString', {
     startsWith: startsWith,
     endsWith: endsWith,
     
-    compare : function(n){
+    compare : function compare(n){
         var m = this._o;
         return (m > n ?  1 :
                (m < n ? -1 : 0 ));
index 37efb7b..815b3b5 100644 (file)
@@ -21,7 +21,7 @@ function toY(y){ return Math.floor(y/CELL_Y); }
  * TODO: Optimize set operations
  */
 BitGrid = new Y.Class('BitGrid', {
-    init : function(w, h){
+    init : function init(w, h){
         this.width = w;
         this.height = h;
         
@@ -37,7 +37,7 @@ BitGrid = new Y.Class('BitGrid', {
         // }
     },
     
-    _getCol : function(x){
+    _getCol : function _getCol(x){
         var xi    = toX(x)
         ,   cells = this.cells
         ,   xcell = cells[xi]
@@ -45,7 +45,7 @@ BitGrid = new Y.Class('BitGrid', {
         return xcell || (cells[xi] = []);
     },
     
-    _getCell : function(x,y){
+    _getCell : function _getCell(x,y){
         var xi    = toX(x)
         ,   cells = this.cells
         ,   xcell = cells[xi]
@@ -53,7 +53,7 @@ BitGrid = new Y.Class('BitGrid', {
         return (xcell ? xcell[ toY(y) ] || 0 : 0);
     },
     
-    _setCell : function(x,y, b){
+    _setCell : function _setCell(x,y, b){
         this._getCol(x)[ toY(y) ] = b;
     },
     
@@ -64,7 +64,7 @@ BitGrid = new Y.Class('BitGrid', {
      *      function iter(acc, cell, x, y) -> new acc
      * Returns the final accumulated value.
      */
-    reduce : function(iter, acc, context){
+    reduce : function reduce(iter, acc, context){
         context = context || this;
         var cells = this.cells
         ,   xi_max = toX(this.width)
@@ -85,7 +85,7 @@ BitGrid = new Y.Class('BitGrid', {
      * iter has sig:
      *      function iter(cell, x, y) -> new value
      */
-    map : function(iter, context){
+    map : function map(iter, context){
         context = context || this;
         var col, cell
         ,   cells = this.cells
@@ -109,7 +109,7 @@ BitGrid = new Y.Class('BitGrid', {
      * iter has sig:
      *      function iter(cell, x, y) -> void
      */
-    each : function(iter, context){
+    each : function each(iter, context){
         context = context || this;
         var col
         ,   cells = this.cells
@@ -125,18 +125,18 @@ BitGrid = new Y.Class('BitGrid', {
         return this;
     },
     
-    isSet : function(x,y){
+    isSet : function isSet(x,y){
         return this._getCell(x,y) & toBits(x,y);
     },
     
-    addPoint : function(x,y){
+    addPoint : function addPoint(x,y){
         // if (x < 0 || x > this.width || y < 0 || y > this.height)
         //     return undefined;
         this._getCol(x)[ toY(y) ] |= toBits(x,y);
         return this;
     },
     
-    addRect : function(x1,y1, x2,y2){
+    addRect : function addRect(x1,y1, x2,y2){
         var cells = this.cells
         ,   x_min = toX(x1), x_max = toX(x2)
         ,   y_min = toY(y1), y_max = toY(y2)
@@ -168,7 +168,7 @@ BitGrid = new Y.Class('BitGrid', {
         return this;
     },
     
-    addGrid : function(other){
+    addGrid : function addGrid(other){
         var cells = this.cells
         ,   o_cells = other.cells
         ,   x_len = o_cells.length
@@ -188,7 +188,7 @@ BitGrid = new Y.Class('BitGrid', {
         return this;
     },
     
-    add : function(x1,y1, x2,y2){
+    add : function add(x1,y1, x2,y2){
         var L = arguments.length;
         if (L == 4) {
             this.addRect(x1,y1, x2,y2);
@@ -202,11 +202,11 @@ BitGrid = new Y.Class('BitGrid', {
         return this;
     },
     
-    subtractPoint : function(x,y){
+    subtractPoint : function subtractPoint(x,y){
         this._getCol(x)[ toY(y) ] &= CELL_MASK ^ toBits(x,y);
     },
     
-    subtractRect : function(x1,y1, x2,y2){
+    subtractRect : function subtractRect(x1,y1, x2,y2){
         var cells = this.cells
         ,   x_min = toX(x1), x_max = toX(x2)
         ,   y_min = toY(y1), y_max = toY(y2)
@@ -238,7 +238,7 @@ BitGrid = new Y.Class('BitGrid', {
         return this;
     },
     
-    subtractGrid : function(other){
+    subtractGrid : function subtractGrid(other){
         var cells = this.cells
         ,   o_cells = other.cells
         ,   x_len = o_cells.length
@@ -260,7 +260,7 @@ BitGrid = new Y.Class('BitGrid', {
         return this;
     },
     
-    subtract : function(x1,y1, x2,y2){
+    subtract : function subtract(x1,y1, x2,y2){
         var L = arguments.length;
         if (L == 4) {
             this.subtractRect(x1,y1, x2,y2);
@@ -274,32 +274,32 @@ BitGrid = new Y.Class('BitGrid', {
         return this;
     },
     
-    intersection : function(other){
+    intersection : function intersection(other){
         return this.reduce(function(g, cell, x, y){
             g._setCell(x,y, cell & other._getCell(x,y));
             return g;
         }, new Grid(this.width, this.height));
     },
     
-    union : function(other){
+    union : function union(other){
         return this.reduce(function(g, cell, x, y){
             g._setCell(x,y, cell | other._getCell(x,y));
             return g;
         }, new Grid(this.width, this.height));
     },
     
-    difference : function(other){
+    difference : function difference(other){
         return this.reduce(function(g, cell, x, y){
             g._setCell(x,y, cell & (CELL_MASK ^ other._getCell(x,y)));
             return g;
         }, new Grid(this.width, this.height));
     },
     
-    symmetricDifference : function(grid){
+    symmetricDifference : function symmetricDifference(grid){
         return this.difference(grid).addGrid( grid.difference(this) );
     },
     
-    toDiagram : function(label, selector){
+    toDiagram : function toDiagram(label, selector){
         var 
         self = this,
         el = $('<div class="grid diagram"><div class="close">&otimes;</div><canvas/><label/></div>'),
@@ -371,7 +371,7 @@ BitGrid = new Y.Class('BitGrid', {
         });
     },
     
-    toString : function(){
+    toString : function toString(){
         return 'Grid['+this.width+','+this.height+']()';
     }
 });
index b23b5b0..2024499 100644 (file)
@@ -31,7 +31,7 @@ function YProcessing(P){
 }
 var methods = {
     init : YProcessing,
-    rectXY : function(x1,y1, x2,y2){
+    rectXY : function rectXY(x1,y1, x2,y2){
         return this.rect( x1,y2, x2-x1,y2-y1 );
     }
     
index 7125be6..b1d85bb 100644 (file)
@@ -5,7 +5,7 @@ Log = new Y.Class('Log', {
         prefix : ''
     },
     
-    init : function(el, options){
+    init : function init(el, options){
         this.el = el;
         var o = Y({}, this.DEFAULT_OPTIONS, options || {});
         this.options = o.end();
@@ -25,7 +25,7 @@ Log = new Y.Class('Log', {
         return Logger;
     },
     
-    ready : function(evt){
+    ready : function ready(evt){
         var self = this
         ,   el = self.el = $(self.el)
         ,   n = this.id
@@ -52,13 +52,13 @@ Log = new Y.Class('Log', {
         });
     },
     
-    log : function(){
+    log : function log(){
         var msgs = this.msgs;
         msgs.append('<div class="log_item">'+this.prefix+Y(arguments).join(' ')+'</div>');
         if (this.options.autoScroll) msgs.scrollTop(msgs.attr('scrollHeight'));
     },
     
-    clear : function(){ 
+    clear : function clear(){ 
         this.msgs.empty().append(this.meta_spacer);
     }
 });
index 9a82530..b6de63e 100644 (file)
@@ -13,7 +13,7 @@ Y.subclass(Viewport, Y.event.Emitter, {
      * Creates Processing instance and begins dispatch. 
      * Clients must listen for `setup` prior to init().
      */
-    init : function(){
+    init : function init(){
         var self = this;
         self.processing = new Processing(
             self.canvas, 
@@ -34,7 +34,7 @@ Y.subclass(Viewport, Y.event.Emitter, {
             });
     },
     
-    dispatchEvent : function(evt){
+    dispatchEvent : function dispatchEvent(evt){
         // log.debug('dispatchEvent('+evt.type+')', this);
         this.getQueue(evt.type).each(function(fn){
             fn.call(evt.target, evt, evt.data.api, evt.data.constants);
index 31ac03d..244a684 100644 (file)
@@ -1,28 +1,5 @@
 (function($, undefined){
 
-// Install CSS styles
-$(function(){
-    $('<style />')
-        .text([
-            '.portal.layer { position:absolute; z-index:1; overflow:hidden; top:0; left:0; }',
-            '.portal.layer canvas { z-index:0; }'
-        ].join('\n'))
-        .appendTo('head');
-});
-
-function makeDelegate(name, dirties, prop){
-    prop = prop || 'layer';
-    return function(){
-        if (dirties && arguments.length)
-            this.dirty = true;
-        
-        var target = this[prop]
-        ,   result = target[name].apply(target, arguments);
-        
-        return (result !== target ? result : this);
-    };
-}
-
 Layer = new Y.Class('Layer', {
     _cssClasses : 'portal layer',
     
@@ -34,12 +11,37 @@ Layer = new Y.Class('Layer', {
     ctx      : null,
     dirty    : true,
     
+    canvasWidth  : 0,   layerWidth  : 0,
+    canvasHeight : 0,   layerHeight : 0,
+    
+    x0: 0, y0: 0,
+    
+    // Rotational origin of the layer (or shape)
+    _origin : null, // loc
+    
+    // Bleeds are marks outside the declared layer-size
+    negBleed : null, // loc
+    posBleed : null, // loc
+    
+    // Transforms
+    transforms : null, // YArray
+    
+    
     
     /// Setup ///
     
-    init : function(){
+    init : function init(){
+        this._loc    = new Loc(0,0);
+        this._origin = new Loc(0,0);
+        this._offset = new Loc(0,0);
+        
         this.children = new Y.YArray();
         
+        var transforms = this.transforms = new Y.YArray();
+        transforms.rotation  = 0;
+        transforms.scale     = new Loc(1.0,1.0);
+        transforms.translate = new Loc(0,0);
+        
         this.canvas = jQuery('<canvas />');
         this.ctx = this.canvas[0].getContext('2d');
         
@@ -53,14 +55,14 @@ Layer = new Y.Class('Layer', {
     /// Scene Graph Heirarchy ///
     
     /** @param {Layer} child */
-    append : function(child){
+    append : function append(child){
         new Y(arguments).invoke('appendTo', this);
         // if (child) child.appendTo(this);
         return this;
     },
     
     /** @param {Layer} parent */
-    appendTo : function(parent){
+    appendTo : function appendTo(parent){
         if (!parent) return this;
         
         // Always ensure we detach from the DOM and redraw this node
@@ -83,44 +85,144 @@ Layer = new Y.Class('Layer', {
     /**
      * Removes this layer from its parent and the DOM.
      */
-    remove : function(){
+    remove : function remove(){
         if (this.parent)
             this.parent.children.remove(this);
         this.parent = null;
         this.layer.remove();
     },
     
+    /**
+     * Clears this layer and destroys all children.
+     */
+    empty : function empty(ctx){
+        this.children.invoke('remove');
+        this.clear();
+        return this;
+    },
+    
+    /**
+     * @returns The root of the scene graph.
+     */
+    root : function root(){
+        if (this.parent)
+            return this.parent.root();
+        else
+            return this;
+    },
+    
     
     
     
     
     /// Attributes ///
     
-    width : function(w){
+    /**
+     * Changes the layer's width and then updates the canvas.
+     */
+    width : function width(w){
         if (w === undefined)
             return this.layer.width();
         
-        this.layer.add(this.canvas)
-            .attr({ width : w      })
-            .css( { width : w+'px' });
-        this.canvas[0].width = w;
+        this.layerWidth = w;
+        this.layer.width(w);
+        
+        // We want a canvas larger than our viewport so rotation doesn't require a
+        // bunch of math. We only really need v*sqrt(2), but whatever, this is faster.
+        var off = this._offset.x
+        ,   w2  = this.canvasWidth = w*2 + off;
+        this.canvas.css({
+            'width' : w2+'px',
+            'margin-left' : (-1 * w/2 + off)+'px'
+        });
+        this.canvas[0].width = w2;
         
         return this;
     },
     
-    height : function(h){
+    height : function height(h){
         if (h === undefined)
             return this.layer.height();
         
-        this.layer.add(this.canvas)
-            .css( { height : h+'px' });
-        this.canvas[0].height = h;
+        this._height = h;
+        this.layer.height(h);
+        
+        var h2 = h*2;
+        this.canvas.css({
+            'height' : h2+'px',
+            'margin-top': (-h/2)+'px'
+        });
+        this.canvas[0].height = h2;
+        
+        return this;
+    },
+    
+    origin : function origin(x,y){
+        var o = this._origin;
+        if (arguments.length === 0)
+            return o.clone();
+        
+        o.x = x;
+        o.y = y;
+        return this;
+    },
+    
+    /**
+     * Rotates this layer by theta radians, and then applies rotation relatively
+     * to all sublayers (preserving knowledge of their individual rotations).
+     */
+    rotate : function rotate(rotation){
+        if (rotation === undefined)
+            return this.transforms.rotation;
         
+        // Record my relative rotation...
+        this.transforms.rotation = rotation;
+        
+        // Propogate...
+        var p = this.parent;
+        return this._rotate(p ? p.rotation : 0);
+    },
+    
+    _rotate : function _rotate(cumRotation){
+        this.dirty = true;
+        var absR = this.absRotation = this.rotation+cumRotation;
+        this.children.invoke('_rotate', absR);
         return this;
     },
     
-    viewportWidth  : makeDelegate('width' , true, 'canvas'),
-    viewportHeight : makeDelegate('height', true, 'canvas'),
+    
+    /**
+     * Scales this layer by (sx,sy), and then applies scaling relatively
+     * to all sublayers (preserving knowledge of their individual scaling).
+     */
+    scale : function scale(sx,sy){
+        if (arguments.length === 0)
+            return { 'x': this.absScaleX, 'y': this.absScaleY };
+        
+        // Record my relative scaling...
+        this.dirty = true;
+        this.scaleX = sx;
+        this.scaleY = sy;
+        
+        // Propogate...
+        var p = this.parent
+        ,   ps = (p ? p.scale() : { x:1.0, y:1.0 }) ;
+        return this._scale(ps.x, ps.y);
+    },
+    
+    _scale : function _scale(parentScaleX, parentScaleY){
+        var absX = this.absScaleX = this.scaleX * parentScaleX
+        ,   absY = this.absScaleY = this.scaleY * parentScaleY
+        ;
+        
+        // Apply absolute scaling...
+        this.ctx.scale(absX, absY);
+        
+        // And propogate down the line
+        this.children.invoke('_scale', absX, absY);
+        
+        return this;
+    },
     
     hide : makeDelegate('hide'),
     show : makeDelegate('show'),
@@ -143,7 +245,7 @@ Layer = new Y.Class('Layer', {
      * Sets the position of this node, and then returns it.
      * @param {Object} pos An object with "top" and/or "left" properties as in `position(top, left)`.
      */
-    position : function(left, top){
+    position : function position(left, top){
         if (top === undefined && left === undefined)
             return this.layer.position();
         
@@ -152,6 +254,9 @@ Layer = new Y.Class('Layer', {
         else
             var pos = { 'top': top, 'left':left };
         
+        // if (pos.left !== undefined)  pos.left -= this.offsetX;
+        // if (pos.top  !== undefined)  pos.top  -= this.offsetY;
+        
         this.css(pos);
         return this;
     },
@@ -163,83 +268,164 @@ Layer = new Y.Class('Layer', {
     
     /// Drawing Functions ///
     
-    /**
-     * To be implemented by subclasses.
-     */
-    drawShape : function(ctx){ return this; },
-    
+    // for debugging
+    point : function point(x,y, color, noTransforms){
+        var ctx = this.ctx;
+        this._openPath(ctx);
+        if (!noTransforms) this._applyTransforms(ctx);
+        
+        var r = 2;
+        ctx.arc(x+r,y+r, r, 0, Math.PI*2, false);
+        ctx.fillStyle = color || '#FFFFFF';
+        ctx.fill();
+        
+        this._closePath(ctx);
+        return this;
+    },
     /**
      * @param {CanvasDrawingContext2D} [ctx=this.ctx] Forces context to use rather than the layer's own.
      * @param {Boolean} [force=false] Forces redraw.
      */
-    draw : function(ctx, force){
-        if ( !(this.dirty || force) )
-            return this;
-        
-        var _ctx = ctx || this.ctx;
-        this._openPath(_ctx);
-        this.drawShape(_ctx);
-        this._closePath(_ctx);
+    draw : function draw(ctx, force){
+        if ( this.dirty || force ){
+            var _ctx = ctx || this.ctx;
+            this._openPath(_ctx);
+            this.drawShape(_ctx);
+            this._closePath(_ctx);
+        }
         
         this.dirty = false;
-        this.children.invoke('draw', ctx);
+        this.children.invoke('draw', ctx, force);
         return this;
     },
     
-    _openPath : function(ctx){
+    _openPath : function _openPath(ctx){
+        var w = this.canvas.width()
+        ,   h = this.canvas.height();
+        
         ctx.beginPath();
-        // ctx.lineWidth = 0;
-        // ctx.fillStyle = "transparent";
+        ctx.setTransform(1,0,0,1,0,0);
+        ctx.clearRect(-w,-h, 2*w,2*h);
+        
+        ctx.translate(w/4 + this.originX, h/4 + this.originY);
+        ctx.rotate(this.absRotation);
+        ctx.translate(-this.originX, -this.originY);
+        
+        ctx.scale(this.absScaleX, this.absScaleY);
+        
         return this;
     },
     
-    _closePath : function(ctx){
+    _applyTransforms : function _applyTransforms(){
+        
+    },
+    
+    /**
+     * To be implemented by subclasses.
+     */
+    drawShape : function drawShape(ctx){ return this; },
+    
+    _closePath : function _closePath(ctx){
         ctx.closePath();
         return this;
     },
     
-    clear : function(ctx){
+    /**
+     * Clears this layer and all children.
+     */
+    clear : function clear(ctx){
+        var w = this.canvas.width()
+        ,   h = this.canvas.height();
         ctx = ctx || this.ctx;
         ctx.beginPath();
-        ctx.clearRect(0,0, this.canvas.width(),this.canvas.height());
+        ctx.setTransform(1,0,0,1,0,0);
+        ctx.clearRect(-w,-h, 2*w,2*h);
         ctx.closePath();
+        this.children.invoke('clear');
         return this;
     },
     
     
     
+    
+    
+    
     /// Iterators ///
     
+    invoke : function invoke(name){
+        var args = Y(arguments,1);
+        
+        this[name].apply(this, args);
+        this.children.invoke.apply(this.children, ['invoke', name].concat(args));
+        return this;
+    },
+    
+    setAll : function setAll(k,v){
+        this[k] = v;
+        this.children.invoke('setAll', k,v);
+        return this;
+    },
+    
     /**
-     * fold across this and parents, inner to outer:
+     * Reduce "up", across this and parents, inner to outer:
      *      acc = fn.call(context || node, acc, node)
      */
-    foldParents : function(acc, fn, context){
+    reduceup : function reduceup(acc, fn, context){
         // if ( Y.isFunction(fn) )
         //     acc = fn.call(context || this, acc, this);
         // else
         //     acc = this[fn].call(context || this, acc, this);
         acc = fn.call(context || this, acc, this);
-        return ( this.parent ? this.parent.foldParents(acc, fn, context) : acc );
+        return ( this.parent ? this.parent.reduceup(acc, fn, context) : acc );
     },
     
     /**
-     * fold across this and children, depth-first:
+     * Reduce "down", across this and children, depth-first:
      *      acc = fn.call(context || node, acc, node)
      */
-    foldChildren : function(acc, fn, context){
+    reduce : function reduce(acc, fn, context){
         acc = fn.call(context || this, acc, this);
-        return this.children.foldChildren(acc, fn, context);
+        return this.children.reduce(acc, fn, context);
     },
     
     
     
     /// Misc ///
     
-    toString : function(){
+    toString : function toString(){
         var pos = (this.layer ? this.position() : {top:NaN, left:NaN});
         return this.className+'['+pos.left+','+pos.top+']( children='+this.children.size()+' )';
     }
 });
 
+
+// Helpers for building the class
+
+function makeDelegate(name, dirties, prop){
+    prop = prop || 'layer';
+    return function(){
+        if (dirties && arguments.length)
+            this.dirty = true;
+        
+        var target = this[prop]
+        ,   result = target[name].apply(target, arguments);
+        
+        return (result !== target ? result : this);
+    };
+}
+
+// Install CSS styles
+$(function(){
+    $('<style />')
+        .text([
+            '.portal.layer { position:absolute; z-index:1; top:0; left:0; }',
+            '.portal.layer canvas { z-index:0; }'
+        ].join('\n'))
+        .appendTo('head');
+});
+
+
+
+
+
 })(jQuery);
index 364263a..b36a333 100644 (file)
@@ -8,13 +8,7 @@ CONTEXT_ATTRS = Y([
 Shape = new Y.Class('Shape', Layer, {
     _cssClasses : 'portal layer shape',
     
-    x0: 0, y0: 0,
-    
-    _offsetX : 0, // We need our shape to be in the first quadrant
-    _offsetY : 0, // so we'll have to offset any negative shapes at the position call
-    
-    
-    attr : function(key, value, def){
+    attr : function attr(key, value, def){
         if (!key) return this;
         
         if ( Y.isPlainObject(key) ) {
@@ -36,56 +30,31 @@ Shape = new Y.Class('Shape', Layer, {
             return Y.attr(this, key, value, def);
     },
     
-    _calcOffset : function(which, values){
+    _calcDimension : function _calcDimension(which, values){
         values.unshift(0);
         var self = this
         ,   off = -1 * Math.min.apply(Math, values);
         
-        self['_offset'+which.toUpperCase()] = off;
-        self[which+'s'] = values = values.map(function(v, i){
-                // v += (i === 0 ? 0 : off);
-                return (self[which+i] = v + off);
+        self._offset.attr(which, off);
+        return values.map(function(v, i){
+                return (self[which+i] = v);
             });
-        return values;
-    },
-    
-    position : function(left, top){
-        if (top === undefined && left === undefined)
-            return this.layer.position();
-        
-        if (top && Y.isPlainObject(top))
-            var pos = top;
-        else
-            var pos = { 'top':top, 'left':left };
-        
-        if (pos.top  !== undefined)  pos.top  -= this._offsetY;
-        if (pos.left !== undefined)  pos.left -= this._offsetX;
-        
-        this.css(pos);
-        return this;
     }
     
-    
 });
 
 Rect = new Y.Class('Rect', Shape, {
     _cssClasses : 'portal layer shape rect',
     
-    _width  : 0,
-    _height : 0,
-    
-    
-    init : function(w, h){
+    init : function init(w, h){
         Layer.init.call(this);
         
-        this._width = w;
-        this.width(w);
-        
-        this._height = h;
-        this.height(h);
+        this.width(w)
+            .height(h)
+            .origin(w/2, h/2);
     },
     
-    drawShape : function(ctx){
+    drawShape : function drawShape(ctx){
         ctx.rect(0,0, this._width,this._height);
         ctx.fill();
     }
@@ -100,21 +69,24 @@ Polygon = new Y.Class('Polygon', Shape, {
      * together to make the numbered coordinates.
      * x0 and y0 will always be 0.
      */
-    init : function(xs, ys){
+    init : function init(xs, ys){
         Layer.init.call(this);
         
-        var xs = this._calcOffset('x', xs)
-        ,   ys = this._calcOffset('y', ys) ;
+        var xs = this._calcDimension('x', xs)
+        ,   ys = this._calcDimension('y', ys)
+        ,   w  = Math.max.apply(Math, xs)
+        ,   h  = Math.max.apply(Math, ys)
+        ;
         
-        this.points = Y(xs).zip(ys);
-        
-        this.width(  Math.max.apply(Math, xs) );
-        this.height( Math.max.apply(Math, ys) );
+        this.points = Y(xs).zip(ys).map(Loc.instantiate, Loc);
+        this.width(w)
+            .height(h)
+            .origin(w/2, h/2);
     },
     
-    drawShape : function(ctx){
-        this.points.forEach(function(pair, i){
-            ctx.lineTo.apply(ctx, pair);
+    drawShape : function drawShape(ctx){
+        this.points.forEach(function(loc, i){
+            ctx.lineTo(loc.x, loc.y);
         });
         ctx.fill();
     }
@@ -123,15 +95,29 @@ Polygon = new Y.Class('Polygon', Shape, {
 Triangle = new Y.Class('Triangle', Polygon, {
     _cssClasses : 'portal layer shape polygon triangle',
     
-    init : function(x1,y1, x2,y2){
+    init : function init(x1,y1, x2,y2){
         Polygon.init.call(this, [x1,x2], [y1,y2]);
-    }
+    },
+    
+    circumcenter : function circumcenter(){
+        var offX = this.offsetX, offY = this.offsetY
+        ,   x1 = this.x1 - offX, y1 = -1 * (this.y1 - offY) // remember, DOM is Y-inverted
+        ,   x2 = this.x2 - offX, y2 = -1 * (this.y2 - offY) // which affects the signs
+        
+        ,   D = 2 * (x1*y2 - y1*x2)
+        ,   B = Math.pow(x1,2) + Math.pow(y1,2)
+        ,   C = Math.pow(x2,2) + Math.pow(y2,2)
+        ;
+        return { 'x' : offX + (y2*B - y1*C)/D
+               , 'y' : offY + (x1*C - x2*B)/D * -1 // fix inverted Y-axis
+               };
+    },
 });
 
 Quad = new Y.Class('Quad', Polygon, {
     _cssClasses : 'portal layer shape polygon quad',
     
-    init : function(x1,y1, x2,y2, x3,y3){
+    init : function init(x1,y1, x2,y2, x3,y3){
         Polygon.init.call(this, [x1,x2,x3], [y1,y2,y3]);
     }
 });
@@ -139,16 +125,21 @@ Quad = new Y.Class('Quad', Polygon, {
 Circle = new Y.Class('Circle', Shape, {
     _cssClasses : 'portal layer shape circle',
     
-    init : function(radius){
+    init : function init(radius){
         Layer.init.call(this);
         
         var d = radius * 2;
-        this.width(d).height(d);
         this.radius = radius;
+        this.width(d)
+            .height(d)
+            .center(r,r);
     },
     
-    drawShape : function(ctx){
-        var r = this.radius;
+    drawShape : function drawShape(ctx){
+        var r  = this.radius
+        // ,   cx = -this.originX
+        // ,   cy = -this.originY ;
+        // ctx.arc(cx,cy, r, 0, Math.PI*2, false);
         ctx.arc(r,r, r, 0, Math.PI*2, false);
         ctx.fill();
     }
diff --git a/src/portal/transform.js b/src/portal/transform.js
new file mode 100644 (file)
index 0000000..6e829f6
--- /dev/null
@@ -0,0 +1,61 @@
+Transform = new Y.Class('Transform', {
+    
+    scale : function scale(x,y, layer, ctx){
+        
+    },
+    
+    translate : function translate(x,y, layer, ctx){
+        
+    },
+    
+    crop : function crop(){
+        
+    }
+    
+});
+
+Transform.Rotation = new Y.Class('Rotation', Transform, {
+    
+    init : function initRotation(theta, x,y){
+        this.theta = theta;
+        this.x = x;
+        this.y = y;
+    },
+    
+    apply : function applyRotation(layer, ctx){
+        ctx = ctx || layer.ctx;
+        ctx.translate(this.x, this.y);
+        ctx.rotate(this.theta);
+        ctx.translate(-this.x, -this.y);
+    }
+    
+});
+
+Transform.Scale = new Y.Class('Scale', Transform, {
+    
+    init : function initScale(x,y){
+        this.x = x;
+        this.y = y;
+    },
+    
+    apply : function apply(layer, ctx){
+        ctx = ctx || layer.ctx;
+        ctx.scale(this.x, this.y);
+    }
+    
+});
+
+Transform.Translate = new Y.Class('Translate', Transform, {
+    
+    init : function init(x,y){
+        this.x = x;
+        this.y = y;
+    },
+    
+    apply : function apply(layer, ctx){
+        ctx = ctx || layer.ctx;
+        ctx.translate(this.x, this.y);
+    }
+    
+});
+
index 9a8bd27..815ab89 100644 (file)
@@ -29,7 +29,7 @@ methods = {
     // times     : null, // Last `samples` frame durations
     
     
-    init : function(target, framerate, samples){
+    init : function init(target, framerate, samples){
         Y.event.Emitter.init.call(this, target);
         decorate.call(this, target);
         this.tick = this.tick.bind(this);
@@ -42,7 +42,7 @@ methods = {
         this.reset();
     },
     
-    reset : function(){
+    reset : function reset(){
         this.stop();
         this.elapsedAtStop = 0;
         this.ticks = 0;
@@ -50,7 +50,7 @@ methods = {
         this.realtimes = [];
     },
     
-    start : function(){
+    start : function start(){
         if (this.running) return;
         
         this.running = true;
@@ -64,7 +64,7 @@ methods = {
         this.elapsedAtStop = 0;
     },
     
-    stop : function(){
+    stop : function stop(){
         if (!this.running) return;
         
         clearTimeout(this.timer);
@@ -74,14 +74,14 @@ methods = {
         this.fire('stop');
     },
     
-    sleepMinus : function(tFrame){
+    sleepMinus : function sleepMinus(tFrame){
         if (this.timer !== null) return;
         
         var tickInt = Math.max(MIN_TICK_INTERVAL, this.targetTime - tFrame);
         this.timer = setTimeout(this.tick, tickInt);
     },
     
-    tick : function(lastTick){
+    tick : function tick(lastTick){
         lastTick     = lastTick || this.now;
         this.now     = new Date().getTime();
         this.elapsed = this.now - lastTick;
@@ -106,11 +106,11 @@ methods = {
         }
     },
     
-    fps : function(){
+    fps : function fps(){
         return 1000 / (this.times.reduce(Y.op.add,0) / this.times.length);
     },
     
-    frametime : function(){
+    frametime : function frametime(){
         return (this.realtimes.reduce(Y.op.add,0) / this.realtimes.length);
     }
     
index c66cc33..947c7c0 100644 (file)
@@ -1,6 +1,6 @@
 FpsSparkline = new Y.Class('FpsSparkline', {
     
-    init : function(loop, el, w,h, maxBuffer, interval){
+    init : function init(loop, el, w,h, maxBuffer, interval){
         this.buffer    = [];
         
         this.el        = el;
@@ -22,7 +22,7 @@ FpsSparkline = new Y.Class('FpsSparkline', {
         $(this.setup.bind(this));
     },
     
-    setup : function(){
+    setup : function setup(){
         this.el = $(this.el);
         
         var w = this.el.parent().width();
@@ -34,7 +34,7 @@ FpsSparkline = new Y.Class('FpsSparkline', {
             this.maxBuffer = Math.floor(w / 3);
     },
     
-    tickTime : function(lastTick){
+    tickTime : function tickTime(lastTick){
         var loop = this.loop
         ,   buf  = this.buffer
         ;
@@ -53,7 +53,7 @@ FpsSparkline = new Y.Class('FpsSparkline', {
     /**
      * Draws average render time across interval frames
      */
-    drawTimes : function(){
+    drawTimes : function drawTimes(){
         var buf = this.buffer
         ,   max = this.maxBuffer
         ,   diff = max - buf.length
@@ -73,8 +73,8 @@ FpsSparkline = new Y.Class('FpsSparkline', {
                 , width            : this.width
                 , height           : this.height
                 
-                , lineColor        : '#333333'
-                , fillColor        : '#333333'
+                , lineColor        : '#2F2F2F'
+                , fillColor        : '#2F2F2F'
                 , spotColor        : false
                 , minSpotColor     : '#83BB32'
                 , maxSpotColor     : '#AF2A31'
@@ -90,7 +90,7 @@ FpsSparkline = new Y.Class('FpsSparkline', {
     /**
      * Draws average FPS over interval.
      */
-    drawFps : function(){
+    drawFps : function drawFps(){
         
     }
     
index 88f66e2..7228012 100644 (file)
@@ -1,31 +1,58 @@
-Loc = new Y.Class('Loc', {
-    init : function(x, y){
-        this.x = Math.max(x,0);
-        this.y = Math.max(y,0);
+// [x,y]
+Loc = new Y.Class('Loc', [], {
+    
+    init : function init(x, y){
+        if ( Array.isArray(x) ) {
+            y = x[1];
+            x = x[0];
+        }
+        
+        this.length = 2;
+        this.x = this[0] = x;
+        this.y = this[1] = y;
     },
     
-    moveBy : function(dir, amount){
-        var mod = 1;
-        switch (dir) {
-            case Loc.LEFT: mod = -1;
-            case Loc.RIGHT:
-                return new Loc(this.x+amount*mod, this.y);
-            
-            case Loc.UP: mod = -1;
-            case Loc.DOWN:
-                return new Loc(this.x, this.y+amount*mod);
+    set : function set(k, v, def){
+        v = (v !== undefined ? v : def);
+        switch (k) {
+            case 'x': case 0:
+                this.x = this[0] = v; break;
+            case 'y': case 1:
+                this.y = this[1] = v; break;
+            default:
+                this[k] = v;
         }
+        return this;
+    },
+    attr : Y.attr.methodize(),
+    
+    equals : function equals(loc){
+        return (this.x === loc.x) && (this.y === loc.y);
     },
     
-    clone : function(){
+    clone : function clone(){
         return new Loc(this.x, this.y);
     },
     
-    toSquare : function(){
+    moveBy : function moveBy(x,y){
+        return new Loc(this.x+x, this.y+y);
+    },
+    
+    moveByAngle : function moveByAngle(theta, v){
+        var x = this.x + v*Math.sin(theta)
+        ,   y = this.y + v*Math.cos(theta);
+        return new Loc(x,y);
+    },
+    
+    moveByDir : function moveByDir(dir, v){
+        return this.moveByAngle(Loc.dirToAngle(dir), v);
+    },
+    
+    toSquare : function toSquare(){
         return Loc.Square.fromLoc(this.x, this.y);
     },
     
-    toString : function(){
+    toString : function toString(){
         return '('+Math.round(this.x, 2)+','+Math.round(this.y, 2)+')';
     }
     
@@ -34,21 +61,34 @@ Y(Loc).extend({
     UP    : 'up',    DOWN : 'down',
     RIGHT : 'right', LEFT : 'left',
     
-    fromSquare : function(col, row){
+    dirToAngle : function dirToAngle(dir){
+        switch (dir) {
+            case Loc.RIGHT: return 0;
+            case Loc.UP:    return HALF_PI;
+            case Loc.LEFT:  return PI;
+            case Loc.DOWN:  return PI_AND_HALF;
+            
+            default: throw new Error("wtf direction is "+dir+"??");
+        }
+    },
+    
+    fromSquare : function fromSquare(col, row){
         return new Loc(
             col * REF_SIZE,
             row * REF_SIZE );
     },
     
-    centerInSquare : function(v){
+    // XXX: Probably wrong for negative locations
+    centerInSquare : function centerInSquare(v){
         return Math.floor(v/REF_SIZE)*REF_SIZE + REF_SIZE/2;
     }
     
 });
 
 
-Loc.Rect = new Y.Class('Rect', {
-    init : function(x1,y1, x2,y2){
+// [x1,y1, x2,y2]
+Loc.Rect = new Y.Class('Rect', [], {
+    init : function init(x1,y1, x2,y2){
         if (x1 instanceof Loc && y1 instanceof Loc) {
             var top    = x1,
                 bottom = y1;
@@ -58,68 +98,102 @@ Loc.Rect = new Y.Class('Rect', {
         }
         this.left  = this.top    = top;
         this.right = this.bottom = bottom;
-        this.x1 = top.x; this.x2 = bottom.x;
-        this.y1 = top.y; this.y2 = bottom.y;
+        
+        this.length = 4;
+        x1 = this.x1 = this[0] = this.x = top.x;
+        y1 = this.y1 = this[1] = this.y = top.y;
+        x2 = this.x2 = this[2] = bottom.x;
+        y2 = this.y2 = this[3] = bottom.y;
+        
+        this.width  = x2 - x1;
+        this.height = y2 - y1;
+    },
+    
+    set : function set(k, v, def){
+        v = (v !== undefined ? v : def);
+        switch (k) {
+            case 'x1': case 0: case 'x':
+                this.x1 = this[0] = this.x = this.top.x = this.left.x = v;
+                break;
+            case 'y1': case 1: case 'y':
+                this.y1 = this[1] = this.y = this.top.y = this.left.y = v;
+                break;
+            case 'x2': case 2:
+                this.x2 = this[2] = this.bottom.x = this.right.x = v;
+                break;
+            case 'y1': case 3:
+                this.y2 = this[3] = this.bottom.y = this.right.y = v;
+                break;
+            default:
+                this[k] = v;
+        }
+        return this;
     },
     
+    attr : Y.attr.methodize(),
+    
+    // XXX: This would slow performance, but prevent state duplication and out of sync errors
+    
     // top    : function(){ return new Loc(this.x1, this.y1); },
-    // bottom : function(){ return new Loc(this.x2, this.y2); },
+    // bottom : function bottom(){ return new Loc(this.x2, this.y2); },
     // 
     // left   : function(){ return new Loc(this.x1, this.y1); },
     // right  : function(){ return new Loc(this.x2, this.y2); },
     
-    midpoint : function(){
-        var t = this.top,
-            b = this.bottom;
+    moveTo : function moveTo(x,y){
+        return new Loc.Rect(x,y, x+this.width,y+this.height);
+    },
+    
+    midpoint : function midpoint(){
         return new Loc(
-            t.x + (b.x-t.x)/2,
-            t.y + (b.y-t.y)/2 );
+            this.x1 + this.width /2,
+            this.y1 + this.height/2 );
     },
     
-    clone : function(){
+    clone : function clone(){
         return new Loc.Rect(
             this.top.clone(),
             this.bottom.clone() );
     },
     
-    contains : function(x,y){
-        var t = this.top, b = this.bottom;
-        return ( x >= t.x && x <= b.x  &&
-                 y >= t.y && y <= b.y  );
+    contains : function contains(x,y){
+        return ( x >= this.x1 && x <= this.x2  &&
+                 y >= this.y1 && y <= this.y2  );
     },
     
-    toString : function(){
+    toString : function toString(){
         return '['+this.top+' '+this.bottom+']';
     }
     
 });
 
-Loc.Square = new Y.Class('Square', Rect, {
-    init : function(col, row){
-        col = this.col = Math.max(col,0);
-        row = this.row = Math.max(row,0);
+Loc.Square = new Y.Class('Square', Loc.Rect, {
+    init : function init(col, row){
+        this.col = col;
+        this.row = row;
         
-        var x1 = this.x = col * REF_SIZE
-        ,   y1 = this.y = row * REF_SIZE
-        ,   x2 = x1 + REF_SIZE
-        ,   y2 = y1 + REF_SIZE;
+        var x1 = col * REF_SIZE,   y1 = row * REF_SIZE
+        ,   x2 = x1  + REF_SIZE,   y2 = y1  + REF_SIZE
+        ;
         
         Loc.Rect.init.call(this, x1,y1, x2,y2);
     },
     
-    clone : function(){
+    clone : function clone(){
         return new Square(this.col, this.row);
     },
     
-    toLoc : function(){
+    toLoc : function toLoc(){
         return Loc.fromSquare(this.col, this.row);
     },
     
-    toString : function(){
+    toString : function toString(){
         return this.col+'_'+this.row;
     }
     
 });
+
+// XXX: Negative grid positions don't round correctly
 Loc.Square.fromLoc = function(x, y){
     return new Loc.Square(
         Math.floor(x / REF_SIZE),
index e2bc43b..28d2e16 100644 (file)
@@ -6,7 +6,7 @@ NodeType = {
     EMPTY    : 0,
     LEAF     : 1,
     POINTER  : 2,
-    toString : function(type){
+    toString : function toString(type){
         switch (type) {
             case NodeType.EMPTY:    return "Empty";
             case NodeType.LEAF:     return "Leaf";
@@ -27,7 +27,7 @@ LeafType = {
     RANGE_TR : 1 | RangeCellType.TOP    | RangeCellType.RIGHT,
     RANGE_BL : 1 | RangeCellType.BOTTOM | RangeCellType.LEFT,
     RANGE_BR : 1 | RangeCellType.BOTTOM | RangeCellType.RIGHT,
-    toString : function(type){
+    toString : function toString(type){
         switch (type) {
             case LeafType.POINT:    return "Point";
             case LeafType.RANGE:    return "Range";
@@ -56,7 +56,7 @@ Node = new Y.Class('Node', {
     se    : null,
     value : null,
     
-    reduce : function(fn, acc, context){
+    reduce : function reduce(fn, acc, context){
         context = context || this;
         if (this.nw) acc = fn.call(context, acc, this.nw, 'nw', this);
         if (this.ne) acc = fn.call(context, acc, this.ne, 'ne', this);
@@ -65,7 +65,7 @@ Node = new Y.Class('Node', {
         return acc;
     },
     
-    toString : function(){
+    toString : function toString(){
         return (NodeType.toString(this.type)+'( '+
             'x='+this.x+',y='+this.y+', '+
             'w='+this.w+',h='+this.h+
@@ -74,7 +74,7 @@ Node = new Y.Class('Node', {
 }),
 
 Range = new Y.Class('Range', Y.YCollection, {
-    init : function(tree, x1,y1, x2,y2, value){
+    init : function init(tree, x1,y1, x2,y2, value){
         this.x1 = this.x = x1;
         this.y1 = this.y = y1;
         this.x2 = x2;
@@ -88,29 +88,29 @@ Range = new Y.Class('Range', Y.YCollection, {
         tree._ranges.push(this);
     },
     
-    contains : function(x,y){
+    contains : function contains(x,y){
         return (this.x1 <= x && x <= this.x2)
             && (this.y1 <= y && y <= this.y2);
     },
     
-    containsRange : function(x1,y1, x2,y2){
+    containsRange : function containsRange(x1,y1, x2,y2){
         var sm_x = Math.min(x1,x2), lg_x = Math.max(x1,x2)
         ,   sm_y = Math.min(y1,y2), lg_y = Math.max(y1,y2);
         return !( sm_x > this.x2 || sm_y > this.y2
                || lg_x < this.x1 || lg_y < this.y1 );
     },
     
-    getCell : function(x1,y1, x2,y2){
+    getCell : function getCell(x1,y1, x2,y2){
         var cell =  new RangeCell(cellId++, x1,y1, x2,y2, this);
         this.cells[cell.id] = cell;
         return cell;
     },
     
-    removeCell : function(cell){
+    removeCell : function removeCell(cell){
         delete this.cells[cell.id];
     },
     
-    removeAll : function(){
+    removeAll : function removeAll(){
         var tree = this.tree;
         this.forEach(function(cell){
             this.removeCell(cell);
@@ -120,18 +120,18 @@ Range = new Y.Class('Range', Y.YCollection, {
         tree._ranges.remove(this);
     },
     
-    reduce : function(fn, acc, context){
+    reduce : function reduce(fn, acc, context){
         context = context || this;
         return Y(this.cells).clone().reduce(fn, acc, context);
     },
     
-    toString : function(){
+    toString : function toString(){
         return 'Range('+this.x1+','+this.y1+', '+this.x2+','+this.y2+', value='+this.value+')'
     }
 }),
 
 Leaf = new Y.Class('Leaf', {
-    toString : function(){
+    toString : function toString(){
         return LeafType.toString(this.type)+'('+this.x+','+this.y+', value='+this.value+')';
     }
 }),
@@ -144,7 +144,7 @@ Point = new Y.Class('Point', Leaf, {
     }
 }),
 RangeCell = new Y.Class('RangeCell', Leaf, {
-    init : function(id, x1,y1, x2,y2, owner) {
+    init : function init(id, x1,y1, x2,y2, owner) {
         this.id = id;
         this.x1 = this.x = x1;
         this.y1 = this.y = y1;
@@ -157,14 +157,14 @@ RangeCell = new Y.Class('RangeCell', Leaf, {
         this.value = owner.value;
     },
     
-    containsRange : function(x1,y1, x2,y2){
+    containsRange : function containsRange(x1,y1, x2,y2){
         var sm_x = Math.min(x1,x2), lg_x = Math.max(x1,x2)
         ,   sm_y = Math.min(y1,y2), lg_y = Math.max(y1,y2);
         return !( sm_x > this.x2 || sm_y > this.y2
                || lg_x < this.x1 || lg_y < this.y1 );
     },
     
-    calc : function(node){
+    calc : function calc(node){
         var r = this, t = LeafType.RANGE;
         
         if (node.x2 > r.x2)
@@ -186,7 +186,7 @@ RangeCell = new Y.Class('RangeCell', Leaf, {
         this.type = t;
     },
     
-    removeRange : function(){
+    removeRange : function removeRange(){
         this.owner.removeAll();
     }
 }),
@@ -205,12 +205,12 @@ PointQuadTree = new Y.Class('PointQuadTree', {
     count : 0,
     _ranges : null,
     
-    inBounds : function(x,y){
+    inBounds : function inBounds(x,y){
         var root = this._root;
         return !( x<root.x1 || y<root.y1 || x>root.x2 || y>root.y2 );
     },
     
-    _inSubrange : function(x1,y1, x2,y2){
+    _inSubrange : function _inSubrange(x1,y1, x2,y2){
         if (arguments.length === 4)
             return this._ranges.invoke('containsRange', x1,y1, x2,y2).any();
         else
@@ -218,24 +218,24 @@ PointQuadTree = new Y.Class('PointQuadTree', {
     },
     
     // XXX: Hm. Should this be contains ALL or contains ANY for the range query?
-    contains : function(x1,y1, x2,y2){
+    contains : function contains(x1,y1, x2,y2){
         if (arguments.length === 4)
             return this._inSubrange(x1,y1, x2,y2);
         else
             return this._inSubrange(x1,y1) || this.get(x1,y1) !== null;
     },
     
-    isEmpty : function(){
+    isEmpty : function isEmpty(){
         return this._root.type == NodeType.EMPTY;