From 39845530e47af1a644711f67280a2406928448b3 Mon Sep 17 00:00:00 2001 From: dsc Date: Tue, 16 Nov 2010 22:55:09 -0800 Subject: [PATCH] Fixes teleporting bullet issue. --- notes.md | 1 + src/portal/layer.js | 10 ++- src/portal/math/line.js | 86 +++++++++--------- src/portal/math/math.js | 2 +- src/portal/math/oldline.js | 134 ++++++++++++++++++++++++++++ src/portal/util/tree/quadtree.js | 34 +++++++- src/tanks/config.js | 6 +- src/tanks/main-ui.js | 149 ------------------------------- src/tanks/main.js | 183 +++++++++++++++++++++++++++++++++++++- src/tanks/map/collision.js | 5 + src/tanks/map/loc.js | 54 +++++++----- src/tanks/map/pathmap.js | 89 +++++++++--------- src/tanks/map/trajectory.js | 155 ++++++++++++++++++++++++++++++++ src/tanks/thing/bullet.js | 69 +++++++-------- src/tanks/thing/thing.js | 44 ++++++++-- tanks.php | 5 +- 16 files changed, 715 insertions(+), 311 deletions(-) create mode 100644 src/portal/math/oldline.js delete mode 100644 src/tanks/main-ui.js create mode 100644 src/tanks/map/collision.js create mode 100644 src/tanks/map/trajectory.js diff --git a/notes.md b/notes.md index fcc68f9..4961760 100644 --- a/notes.md +++ b/notes.md @@ -9,6 +9,7 @@ # TODOs - Move game objects into namespace `tanks` - Move portal into namespace (ideas: `portal`, canvas tools... `easel`, or `ezl`) +- Classes should have generalize()-ed version of instance methods on them. # Notes diff --git a/src/portal/layer.js b/src/portal/layer.js index a3e2523..ebf2f50 100644 --- a/src/portal/layer.js +++ b/src/portal/layer.js @@ -43,7 +43,7 @@ Layer = new Y.Class('Layer', { this.negBleed = new Loc(0,0); this.posBleed = new Loc(0,0); - this.boundingBox = new Loc.Rect(0,0, 0,0); + this.boundingBox = new Loc.BoundingBox(0,0, 0,0); this.transform = { origin : new Loc('50%','50%'), // rotational origin @@ -348,9 +348,13 @@ Layer = new Y.Class('Layer', { 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._invoke(name, args); + }, + _invoke : function _invoke(name, args){ this[name].apply(this, args); - this.children.invoke.apply(this.children, ['invoke', name].concat(args)); + this.children.invoke('_invoke', name, args); return this; }, diff --git a/src/portal/math/line.js b/src/portal/math/line.js index e213436..e6b5172 100644 --- a/src/portal/math/line.js +++ b/src/portal/math/line.js @@ -1,6 +1,7 @@ (function(){ -var +var +Vec = math.Vec, HALF_PI = Math.PI/2, SIN_HALF_PI = Math.sin(HALF_PI), COS_HALF_PI = Math.cos(HALF_PI); @@ -11,21 +12,23 @@ COS_HALF_PI = Math.cos(HALF_PI); */ math.Line = new Y.Class('Line', math.Vec, { - init : function init(x1,y1, x2,y2, tdist){ + init : function initLine(x1,y1, x2,y2, tdist){ this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; - var xdelta = x2-x1, ydelta = y2-y1 - , m = this.slope = ydelta/xdelta + var x0 = x2-x1 + , y0 = y2-y1 + + , m = this.slope = y0/x0 , yi = this.yint = -x1*m + y1 , xi = this.xint = -y1/m + x1 ; - math.Vec.init.call(this, xdelta, ydelta); + math.Vec.init.call(this, x0,y0); this.p1 = new math.Vec(x1,y1); this.p2 = new math.Vec(x2,y2); - this.theta = Math.atan2(ydelta, xdelta); + this.theta = Math.atan2(y0, x0); this._cos = Math.cos(this.theta); this._sin = Math.sin(this.theta); @@ -36,32 +39,7 @@ math.Line = new Y.Class('Line', math.Vec, { return new math.Line(this.x1,this.y1, this.x2,this.y2, this.tdist); }, - equals : function equals(line){ - return ( this.slope === line.slope - && this.x1 === line.x1 && this.y1 === line.y1 - && this.x2 === line.x2 && this.y2 === line.y2 ); - }, - intersects : function intersects(x,y){ - var o = x; - if (o instanceof math.Line) - return this.slope !== o.slope || this.equals(o); - - if (o instanceof math.Vec) { - x = o.x; - y = o.y; - } - return this.calcY(x) === y; - }, - - isWithin : function isWithin(pt, w,h){ - if ( !Y.isNumber(w) ){ - h = w.height; w = w.width; - } - var dw = Math.abs(this.calcX(pt.y) - pt.x) - , dh = Math.abs(this.calcY(pt.x) - pt.y) ; - return dw <= w || dh <= h ; - }, setTScale : function setTScale(tdist){ if (tdist) { @@ -81,13 +59,10 @@ math.Line = new Y.Class('Line', math.Vec, { this.y1 + t*this.pb ); }, - pointAtX : function pointAtX(x){ - return new math.Vec(x, this.calcY(x)); - }, - - pointAtY : function pointAtY(y){ - return new math.Vec(this.calcX(y), y); - }, + pointAtX : function pointAtX(x){ return new math.Vec(x, this.calcY(x)); }, + pointAtY : function pointAtY(y){ return new math.Vec(this.calcX(y), y); }, + calcY : function calcY(x){ return (x === this.xint ? 0 : x*this.slope + this.yint); }, + calcX : function calcX(y){ return (y === this.yint ? 0 : y/this.slope + this.xint); }, near : function near(x,y){ if ( !isFinite(this.slope) ) @@ -96,12 +71,39 @@ math.Line = new Y.Class('Line', math.Vec, { return this.pointAtX(x); }, - calcY : function calcY(x){ - return (x === this.xint ? 0 : x*this.slope + this.yint); + + + equals : function equals(line){ + return ( this.slope === line.slope + && this.x1 === line.x1 && this.y1 === line.y1 + && this.x2 === line.x2 && this.y2 === line.y2 ); }, - calcX : function calcX(y){ - return (y === this.yint ? 0 : y/this.slope + this.xint); + intersects : function intersects(x,y){ + var o = x; + if (o instanceof math.Line) + return this.slope !== o.slope || this.equals(o); + + if (o instanceof math.Vec) { + x = o.x; + y = o.y; + } + return this.calcY(x) === y; + }, + + isWithin : function isWithin(pt, w,h){ + if ( !Y.isNumber(w) ){ + h = w.height; w = w.width; + } + var dw = Math.abs(this.calcX(pt.y) - pt.x) + , dh = Math.abs(this.calcY(pt.x) - pt.y) ; + return dw <= w || dh <= h ; + }, + + + + vec : function vec(){ + return new math.Vec(this.x, this.y); // reset to vector from origin }, base : function base(){ diff --git a/src/portal/math/math.js b/src/portal/math/math.js index bb8a8a1..882cdbb 100644 --- a/src/portal/math/math.js +++ b/src/portal/math/math.js @@ -10,7 +10,7 @@ math = { reflect : function reflect(v, line){ var dot = math.Vec.dot , basev = math.Vec.difference(v, line.p1); - return new math.Vec(line.x, line.y) + return line.vec() .scale(2 * dot(basev,line) / dot(line,line)) .subtract(basev) .add(line.p1); diff --git a/src/portal/math/oldline.js b/src/portal/math/oldline.js new file mode 100644 index 0000000..b13b4bf --- /dev/null +++ b/src/portal/math/oldline.js @@ -0,0 +1,134 @@ +(function(){ + +var +HALF_PI = Math.PI/2, +SIN_HALF_PI = Math.sin(HALF_PI), +COS_HALF_PI = Math.cos(HALF_PI); + + +/** + * A line in the cartesian plane. + */ +math.Line = new Y.Class('Line', math.Vec, { + + init : function initLine(x1,y1, x2,y2, tdist){ + this.x1 = x1; this.y1 = y1; + this.x2 = x2; this.y2 = y2; + + var xdelta = x2-x1, ydelta = y2-y1 + , m = this.slope = ydelta/xdelta + , yi = this.yint = -x1*m + y1 + , xi = this.xint = -y1/m + x1 + ; + math.Vec.init.call(this, xdelta, ydelta); + + this.p1 = new math.Vec(x1,y1); + this.p2 = new math.Vec(x2,y2); + + this.theta = Math.atan2(ydelta, xdelta); + this._cos = Math.cos(this.theta); + this._sin = Math.sin(this.theta); + + this.setTScale(tdist); + }, + + clone : function clone(){ + return new math.Line(this.x1,this.y1, this.x2,this.y2, this.tdist); + }, + + equals : function equals(line){ + return ( this.slope === line.slope + && this.x1 === line.x1 && this.y1 === line.y1 + && this.x2 === line.x2 && this.y2 === line.y2 ); + }, + + intersects : function intersects(x,y){ + var o = x; + if (o instanceof math.Line) + return this.slope !== o.slope || this.equals(o); + + if (o instanceof math.Vec) { + x = o.x; + y = o.y; + } + return this.calcY(x) === y; + }, + + isWithin : function isWithin(pt, w,h){ + if ( !Y.isNumber(w) ){ + h = w.height; w = w.width; + } + var dw = Math.abs(this.calcX(pt.y) - pt.x) + , dh = Math.abs(this.calcY(pt.x) - pt.y) ; + return dw <= w || dh <= h ; + }, + + setTScale : function setTScale(tdist){ + if (tdist) { + this.tdist = tdist; + this.pa = tdist * this._cos; + this.pb = tdist * this._sin; + } else { + this.tdist = this.y / this._sin; + this.pa = this.x; + this.pb = this.y; + } + return this; + }, + + parametric : function parametric(t){ + return new math.Vec( this.x1 + t*this.pa , + this.y1 + t*this.pb ); + }, + + pointAtX : function pointAtX(x){ + return new math.Vec(x, this.calcY(x)); + }, + + pointAtY : function pointAtY(y){ + return new math.Vec(this.calcX(y), y); + }, + + near : function near(x,y){ + if ( !isFinite(this.slope) ) + return this.pointAtY(y); + else + return this.pointAtX(x); + }, + + calcY : function calcY(x){ + return (x === this.xint ? 0 : x*this.slope + this.yint); + }, + + calcX : function calcX(y){ + return (y === this.yint ? 0 : y/this.slope + this.xint); + }, + + base : function base(){ + if (!this._base) + this._base = new math.Line(0,0, this.x,this.y); + return this._base; + }, + + tangent : function tangent(at){ + var slope = this.slope; + + if ( slope === 0 ) + return new math.Line(at.x,at.y, at.x,at.y+1, this.tdist); + + if ( !isFinite(slope) ) + return new math.Line(at.x,at.y, at.x+1,at.y, this.tdist); + + var x1 = at.x, y1 = at.y + , x2 = at.x - at.y + (at.y !== this.y1 ? this.y1 : this.y2) + , y2 = at.y + at.x - (at.x !== this.x1 ? this.x1 : this.x2) ; + return new math.Line(x1,y1, x2,y2, this.tdist); + }, + + toString : function toString(){ + return '['+this.p1+', '+this.p2+', slope='+this.slope.toFixed(3)+']'; + } + +}); + +})(); \ No newline at end of file diff --git a/src/portal/util/tree/quadtree.js b/src/portal/util/tree/quadtree.js index 47b9fa7..d1ac1c8 100644 --- a/src/portal/util/tree/quadtree.js +++ b/src/portal/util/tree/quadtree.js @@ -10,6 +10,16 @@ function Rect(x1,y1, x2,y2){ this.width = lg_x-sm_x; this.height = lg_y-sm_y; } +function rectToString(o){ + var p = 2 + , x1 = o.x1, y1 = o.y1 + , x2 = o.x2, y2 = o.y2 ; + x1 = ((x1 % 1 !== 0) ? x1.toFixed(p) : x1); + y1 = ((y1 % 1 !== 0) ? y1.toFixed(p) : y1); + x2 = ((x2 % 1 !== 0) ? x2.toFixed(p) : x2); + y2 = ((y2 % 1 !== 0) ? y2.toFixed(p) : y2); + return '['+x1+','+y1+', '+x2+','+y2+']'; +} var CAPACITY = 8, @@ -29,7 +39,7 @@ Region = new Y.Class('Region', { }, toString : function toString(){ - return this.className+'(id='+this.id+', value='+this.value+')'; + return this.className+'(id='+this.id+', rect='+rectToString(this)+', value='+this.value+')'; } }), @@ -71,6 +81,28 @@ QuadTree = new Y.Class('QuadTree', { return acc; }, + // _get : function _get(_x1,_y1, _x2,_y2, values){ + // var self = this + // , cxt = context || self + // , x1 = Math.min(_x1,_x2), x2 = Math.max(_x1,_x2) + // , y1 = Math.min(_y1,_y2), y2 = Math.max(_y1,_y2) ; + // + // if ( !self.overlaps(x1,y1, x2,y2) ) + // return acc; + // + // // Implies this is a leaf: check its values + // if ( self.regions ) acc = fn.call(cxt, acc, self.regions, self, self); + // + // // Recurse into any children -- Note we do not place this in an else-block, + // // as mutation during the above call might cause the tree to split. + // if (self.nw) acc = self.nw.leaves(x1,y1, x2,y2, fn, acc, context); + // if (self.ne) acc = self.ne.leaves(x1,y1, x2,y2, fn, acc, context); + // if (self.sw) acc = self.sw.leaves(x1,y1, x2,y2, fn, acc, context); + // if (self.se) acc = self.se.leaves(x1,y1, x2,y2, fn, acc, context); + // + // return acc; + // }, + set : function set(x1,y1, x2,y2, value){ return this._set(new Region(x1,y1, x2,y2, value)); }, diff --git a/src/tanks/config.js b/src/tanks/config.js index b1b958f..7092331 100644 --- a/src/tanks/config.js +++ b/src/tanks/config.js @@ -1,6 +1,10 @@ +// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*- tanks.config = { pathing : { overlayPathmap : false, - traceTrajectories : false + traceTrajectories : true + }, + debug : { + projectiles : 1 } }; \ No newline at end of file diff --git a/src/tanks/main-ui.js b/src/tanks/main-ui.js deleted file mode 100644 index c0f014e..0000000 --- a/src/tanks/main-ui.js +++ /dev/null @@ -1,149 +0,0 @@ - -// Set up UI listeners -function setupUI(){ - spark = LBT.loop.spark = new FpsSparkline(LBT.loop, '.fps-sparkline', 0,0); - - initConfig(); - $('#config input').bind('change', updateConfig); - - btank = new Tank(0); - btank.act = function(){ return this; }; - LBT.addUnit(btank, 0,0); - LBT.pathmap.removeBlocker(btank); - btank.shape.hide(); - - LBT.root.draw(); - - // Start button (click or return key) - $(document).bind('keydown', 'return', toggleGame); - $(document).bind('keydown', 'ctrl+o', toggleOverlay); - - $('#bullets').bind('blur', function(evt){ - var n = parseInt($('#bullets').val() || 0); - updateBullets(n); - }); - - $('#bullets').blur(); - - LBT.root.draw(); - setInterval(updateInfo, 1000); - - // Start the simulation! - // toggleGame(); - updateInfo(); - - // Fix grid-size on resize - // $(window).bind('resize', resizeGame); -} - -function initConfig(){ - var p = tanks.config.pathing; - $('#config [name=pathmap]').attr('checked', p.overlayPathmap); - $('#config [name=trajectories]').attr('checked', p.traceTrajectories); -} - -function updateConfig(evt){ - var p = tanks.config.pathing; - p.overlayPathmap = $('#config [name=pathmap]').attr('checked'); - p.traceTrajectories = $('#config [name=trajectories]').attr('checked'); -} - -var BULLETS = new Y.YArray(); - -function spawnBullet(x,y, atX,atY){ - if (x === undefined && y === undefined) { - var loc = randOpenLoc(); - x = loc.x; y = loc.y; - } - if (atX === undefined && atY === undefined) do { - atX = rand(0,COLUMNS*REF_SIZE); - atY = rand(0,ROWS*REF_SIZE); - } while ( atX === x && atY === y); - - btank.setLocation(x,y); - return btank.fireProjectile(atX,atY); -} - -function updateBullets(n){ - if ( !Y.isNumber(n) || isNaN(n) || n === BULLETS.size() ) return; - - while (n < BULLETS.size()) { - var i = Math.round(rand(0, BULLETS.size()-1)); - BULLETS.remove( BULLETS.attr(i).remove() ); - } - - while (n > BULLETS.size()) - BULLETS.push(spawnBullet()); -} - -function rand(a,b){ return a + Math.random()*(b-a); } -function randOpenLoc(){ - do { - var x = rand(0,COLUMNS*REF_SIZE) - , y = rand(0,ROWS*REF_SIZE); - } while ( LBT.pathmap.get(x,y, x+6,y+6).size() ); - return new math.Vec(x,y); -} - - - -// Update performance info periodically -function updateInfo(){ - var loop = LBT.loop - , fps = loop.fps() - , n_units = LBT.units.size() - , n_projs = LBT.bullets.size() - ; - - $('#info [name=fps]').val( fps.toFixed(2) + " / " + loop.framerate ); - $('#info [name=frame]').val( loop.frametime().toFixed(3)+" ms" ); - $('#info #state').text( loop.running ? 'Running!' : ('Paused (tick '+TICKS+')') ); - - $('#info [name=objects]').val( n_units+n_projs ); - $('#info [name=units]').val( n_units ); - $('#info [name=bullets]').val( n_projs ); - - spark.drawTimes(); - - return false; -} - -function toggleGame(evt){ - if (LBT.loop.running) - LBT.stop(); - else - LBT.start(); - - updateInfo(); -} - -function toggleOverlay(evt){ - LBT.showOverlay = !(LBT.showOverlay); -} - -function resizeGame(evt){ - LBT.resize(evt); - - if (!LBT.loop.running) { - LBT.start(); - LBT.stop(); - } -} - -// function initUki(){ -// uki({ view:'Box', rect:'0 0 250 500', anchor:'top width', className:'box', childViews:[ -// { view:'Box', rect:'8 8 250 0', anchor:'top width height', childViews:[ -// { view:'HFlow', rect:'250 25', anchor:'width', childViews:[ -// { view:'Label', rect:'200 25', anchor:'top left', text:'Bullets' }, -// { view:'TextField', rect:'50 25', anchor:'top right', name:'bullets', value:'10', background:'' } -// ]} -// ]} -// ]}) -// .attachTo( -// $('
').css({ -// 'position':'relative', -// 'width':'100%', -// 'top':0, 'left':0 -// }).appendTo('#controls')[0], '0 0'); -// // .attachTo(window, '10 10'); -// } diff --git a/src/tanks/main.js b/src/tanks/main.js index 5ed561d..3d3035b 100644 --- a/src/tanks/main.js +++ b/src/tanks/main.js @@ -1,12 +1,21 @@ +var btank = null +, bullets = new Y.YArray() +; + + +(function(){ + jQuery(main); + function main(){ - v = $('#viewport'); + var v = $('#viewport') + , sq = REF_SIZE + ; + LBT = new tanks.Game(); ctx = LBT.level.ctx; - - var sq = REF_SIZE; LBT.level.append( new Wall(6*sq,1*sq, 1*sq,4*sq), new Wall(6*sq,7*sq, 1*sq,2*sq), @@ -15,8 +24,174 @@ function main(){ ); T = LBT.addUnit(new Tank(0), 1,2); - P = new Player(LBT, T); + new Player(LBT, T); setupUI(); + B = bullets.attr(0); + T = B.trajectory; +} + + +// Set up UI listeners +function setupUI(){ + LBT.loop.spark = new FpsSparkline(LBT.loop, '.fps-sparkline', 0,0); + + btank = new Tank(0); + btank.act = function(){ return this; }; + LBT.addUnit(btank, 0,0); + LBT.pathmap.removeBlocker(btank); + btank.shape.hide(); + + initConfig(); + $('#config input').bind('change', updateConfig); + + LBT.root.draw(); + + // Start button (click or return key) + $(document).bind('keydown', 'return', toggleGame); + $(document).bind('keydown', 'ctrl+o', toggleOverlay); + + $('#bullets').bind('blur', function(evt){ + var n = parseInt($('#bullets').val() || 0); + updateBullets(n); + }); + + LBT.root.draw(); + setInterval(updateInfo, 1000); + + // Start the simulation! + // toggleGame(); + updateInfo(); + + // Fix grid-size on resize + // $(window).bind('resize', resizeGame); +} + + + +function toggleGame(evt){ + if (LBT.loop.running) + LBT.stop(); + else + LBT.start(); + + updateInfo(); +} + +function toggleOverlay(evt){ + LBT.showOverlay = !(LBT.showOverlay); } +function resizeGame(evt){ + LBT.resize(evt); + + if (!LBT.loop.running) { + LBT.start(); + LBT.stop(); + } +} + + +function initConfig(){ + var c = tanks.config, p = c.pathing; + + $('#config [name=pathmap]').attr('checked', p.overlayPathmap); + $('#config [name=trajectories]').attr('checked', p.traceTrajectories); + + $('#config [name=bullets]').val(c.debug.projectiles); + updateBullets( c.debug.projectiles ); +} + +function updateConfig(evt){ + var p = tanks.config.pathing; + p.overlayPathmap = $('#config [name=pathmap]').attr('checked'); + p.traceTrajectories = $('#config [name=trajectories]').attr('checked'); +} + +function spawnBullet(x,y, atX,atY){ + if (x === undefined && y === undefined) { + var loc = randOpenLoc(); + x = loc.x; y = loc.y; + } + if (atX === undefined && atY === undefined) do { + atX = rand(0,COLUMNS*REF_SIZE); + atY = rand(0,ROWS*REF_SIZE); + } while ( atX === x && atY === y); + + btank.setLocation(x,y); + return btank.fireProjectile(atX,atY); +} + +function updateBullets(n){ + if ( !Y.isNumber(n) || isNaN(n) || n === bullets.size() ) return; + + while (n < bullets.size()) { + var i = Math.round(rand(0, bullets.size()-1)); + bullets.remove( bullets.attr(i).remove() ); + } + + while (n > bullets.size()) + bullets.push(spawnBullet()); +} + +function rand(a,b){ return a + Math.random()*(b-a); } +function randOpenLoc(){ + do { + var x = rand(0,COLUMNS*REF_SIZE) + , y = rand(0,ROWS*REF_SIZE); + } while ( LBT.pathmap.get(x,y, x+6,y+6).size() ); + return new math.Vec(x,y); +} + + + +// Update performance info periodically +function updateInfo(){ + var loop = LBT.loop + , fps = loop.fps() + , n_units = LBT.units.size() + , n_projs = LBT.bullets.size() + ; + + $('#info [name=fps]').val( fps.toFixed(2) + " / " + loop.framerate ); + $('#info [name=frame]').val( loop.frametime().toFixed(3)+" ms" ); + $('#info #state').text( loop.running ? 'Running!' : ('Paused (tick '+TICKS+')') ); + + $('#info [name=objects]').val( n_units+n_projs ); + $('#info [name=units]').val( n_units ); + $('#info [name=bullets]').val( n_projs ); + + loop.spark.drawTimes(); + + return false; +} + + +// function initUki(){ +// uki({ view:'Box', rect:'0 0 250 500', anchor:'top width', className:'box', childViews:[ +// { view:'Box', rect:'8 8 250 0', anchor:'top width height', childViews:[ +// { view:'HFlow', rect:'250 25', anchor:'width', childViews:[ +// { view:'Label', rect:'200 25', anchor:'top left', text:'Bullets' }, +// { view:'TextField', rect:'50 25', anchor:'top right', name:'bullets', value:'10', background:'' } +// ]} +// ]} +// ]}) +// .attachTo( +// $('
').css({ +// 'position':'relative', +// 'width':'100%', +// 'top':0, 'left':0 +// }).appendTo('#controls')[0], '0 0'); +// // .attachTo(window, '10 10'); +// } + +})(); + +function dumpPathmap(){ + var pm = LBT.pathmap; + console.warn(new Date(), pm); + pm.collect(pm.x1,pm.y1, pm.x2,pm.y2, function(acc, v, r){ + console.log(r+''); + }); + console.log(' '); +} diff --git a/src/tanks/map/collision.js b/src/tanks/map/collision.js new file mode 100644 index 0000000..b1694f2 --- /dev/null +++ b/src/tanks/map/collision.js @@ -0,0 +1,5 @@ +Collision = new Y.Class('Collision', { + + + +}); \ No newline at end of file diff --git a/src/tanks/map/loc.js b/src/tanks/map/loc.js index ae77649..edc840a 100644 --- a/src/tanks/map/loc.js +++ b/src/tanks/map/loc.js @@ -1,5 +1,5 @@ // [x,y] -Loc = new Y.Class('Loc', math.Vec, { +Loc = new Y.Class('Loc', new math.Vec(0,0), { // init : function init(x,y){ // math.Vec.init.call(this, x,y); @@ -93,10 +93,10 @@ Y(Loc).extend({ }); - // [x1,y1, x2,y2] Loc.Rect = new Y.Class('Rect', [], { - init : function init(x1,y1, x2,y2){ + + init : function initRect(x1,y1, x2,y2){ if (x1 instanceof Loc && y1 instanceof Loc) { y2 = y1.y; x2 = y1.x; y1 = x1.y; x1 = x1.x; @@ -113,6 +113,31 @@ Loc.Rect = new Y.Class('Rect', [], { this.p1 = new Loc(x1,y1); this.p2 = new Loc(x2,y2); + }, + + contains : function contains(x,y){ + return ( x >= this.x1 && x <= this.x2 && + y >= this.y1 && y <= this.y2 ); + }, + + midpoint : function midpoint(){ + return new Loc( this.x1 + this.width /2 + , this.y1 + this.height/2 ); + }, + + clone : function clone(){ + return new Loc.Rect( this.p1.clone() + , this.p2.clone() ); + }, + + toString : function toString(){ + return '['+this.p1+' '+this.p2+']'; + } +}); + +Loc.BoundingBox = new Y.Class('BoundingBox', Loc.Rect, { + init : function initBoundingBox(x1,y1, x2,y2){ + Loc.Rect.init.call(this, x1,y1, x2,y2); this.sides = { top : new math.Line(x1,y1, x2,y1), @@ -162,36 +187,21 @@ Loc.Rect = new Y.Class('Rect', [], { attr : Y.attr.methodize(), moveTo : function moveTo(x,y){ - return new Loc.Rect(x,y, x+this.width,y+this.height); + return new Loc.BoundingBox(x,y, x+this.width,y+this.height); }, resize : function resize(w,h){ var x = this.x, y = this.y; - return new Loc.Rect(x,y, x+w,y+h); - }, - - midpoint : function midpoint(){ - return new Loc( this.x1 + this.width /2 - , this.y1 + this.height/2 ); + return new Loc.BoundingBox(x,y, x+w,y+h); }, clone : function clone(){ - return new Loc.Rect( this.top.clone() - , this.bottom.clone() ); - }, - - contains : function contains(x,y){ - return ( x >= this.x1 && x <= this.x2 && - y >= this.y1 && y <= this.y2 ); - }, - - toString : function toString(){ - return '['+this.top()+' '+this.bottom()+']'; + return new Loc.BoundingBox( this.p1.clone(), this.p2.clone() ); } }); -Loc.Square = new Y.Class('Square', Loc.Rect, { +Loc.Square = new Y.Class('Square', Loc.BoundingBox, { init : function init(col, row){ this.col = col; this.row = row; diff --git a/src/tanks/map/pathmap.js b/src/tanks/map/pathmap.js index 06f6927..9aeb818 100644 --- a/src/tanks/map/pathmap.js +++ b/src/tanks/map/pathmap.js @@ -1,3 +1,4 @@ +// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*- PathMap = new Y.Class('PathMap', QuadTree, { init : function init(game, x1,y1, x2,y2, capacity) { @@ -5,20 +6,20 @@ PathMap = new Y.Class('PathMap', QuadTree, { this.game = game; var w = this.width, h = this.height; - this.walls = { - top : new math.Line(0,0, w,0), - bottom : new math.Line(0,h, w,h), - left : new math.Line(0,0, 0,h), - right : new math.Line(w,0, w,h) - }; - // this.walls = { - // top : new Wall(0,0, w,1), - // bottom : new Wall(0,h, w,1), - // left : new Wall(0,0, 1,h), - // right : new Wall(w,0, 1,h) + // top : new math.Line(0,0, w,0), + // bottom : new math.Line(0,h, w,h), + // left : new math.Line(0,0, 0,h), + // right : new math.Line(w,0, w,h) // }; - // Y(this.walls).forEach(this.addBlocker, this); + + this.walls = { + top : new Wall(0,0, w,1), + bottom : new Wall(0,h-1, w,1), + left : new Wall(0,0, 1,h), + right : new Wall(w-1,0, 1,h) + }; + Y(this.walls).forEach(this.addBlocker, this); }, addBlocker : function addBlocker(obj){ @@ -43,40 +44,40 @@ PathMap = new Y.Class('PathMap', QuadTree, { , bb = obj.boundingBox , minW = 2, minH = 2 , maxW = this.width-2, maxH = this.height-2 - , w = bb.width, h = bb.height + , bw = bb.width, bh = bb.height , x1 = to.x, y1 = to.y - , x2 = x1+w, y2 = y1+h + , x2 = x1+bw, y2 = y1+bh ; // Check for collision with the walls to prevent teleporting units - if (x1 < minW || x2 > maxW || y1 < minH || y2 > maxH){ - blocker = this.game.level; - what = 'LevelWall on the '; - - if (x1 < minW) { - what = 'left'; - wall = this.walls.left; - to = trj.pointAtX(minW); - - } else if (x2 > maxW) { - what = 'right'; - wall = this.walls.right; - to = trj.pointAtX(maxW-w); - - } else if (y1 < minH) { - what = 'top'; - wall = this.walls.top; - to = trj.pointAtY(minH); - - } else if (y2 > maxH) { - what = 'bottom'; - wall = this.walls.bottom; - to = trj.pointAtY(maxH-h); - } - - what += ' at '+wall; - return { 'ok':!wall, 'to':to, 'wall':wall, 'blocker':blocker, 'what':what }; - } + // if (x1 < minW || x2 > maxW || y1 < minH || y2 > maxH){ + // blocker = this.game.level; + // what = 'LevelWall on the '; + // + // if (x1 < minW) { + // what = 'left'; + // wall = this.walls.left; + // to = trj.pointAtX(minW); + // + // } else if (x2 > maxW) { + // what = 'right'; + // wall = this.walls.right; + // to = trj.pointAtX(maxW-bw); + // + // } else if (y1 < minH) { + // what = 'top'; + // wall = this.walls.top; + // to = trj.pointAtY(minH); + // + // } else if (y2 > maxH) { + // what = 'bottom'; + // wall = this.walls.bottom; + // to = trj.pointAtY(maxH-bh); + // } + // + // what += ' at '+wall; + // return { 'ok':!wall, 'to':to, 'wall':wall, 'blocker':blocker, 'what':what }; + // } // Check for pathmap collisions var blockers = this.get(x1,y1, x2,y2).remove(obj); @@ -93,7 +94,7 @@ PathMap = new Y.Class('PathMap', QuadTree, { if (bb.x2 <= B.x1 && x2 > B.x1) { what += 'left'; wall = B.sides.left; - to = trj.pointAtX(B.x1-w); + to = trj.pointAtX(B.x1-bw); } else if (bb.x1 >= B.x2 && x1 < B.x2) { what += 'right'; wall = B.sides.right; @@ -101,7 +102,7 @@ PathMap = new Y.Class('PathMap', QuadTree, { } else if (bb.y2 <= B.y1 && y2 > B.y1) { what += 'top'; wall = B.sides.top; - to = trj.pointAtY(B.y1-h); + to = trj.pointAtY(B.y1-bh); } else if (bb.y1 >= B.y2 && y1 < B.y2) { what += 'bottom'; wall = B.sides.bottom; diff --git a/src/tanks/map/trajectory.js b/src/tanks/map/trajectory.js new file mode 100644 index 0000000..fae22dd --- /dev/null +++ b/src/tanks/map/trajectory.js @@ -0,0 +1,155 @@ +Trajectory = new Y.Class('Trajectory', math.Line, { + elapsed : 0, + depth : 0, + + + init : function initTrajectory(owner, x1,y1, x2,y2, tdist){ + this.owner = owner; + this.game = owner.game; + this.pathmap = this.game.pathmap; + + this.reset(x1,y1, x2,y2, tdist); + }, + + reset : function reset(x1,y1, x2,y2, tdist){ + // init with raw numbers to do calculations + math.Line.init.call(this, x1,y1, x2,y2, tdist || this.tdist); + + var pm = this.pathmap + , ex = (x1 > x2 ? -REF_SIZE : REF_SIZE+pm.width ) + , ey = (y1 > y2 ? -REF_SIZE : REF_SIZE+pm.height) + , edge = this.near(ex,ey); + + // Move goal point beyond far wall to avoid rotations + x2 = this.x2 = edge.x; + y2 = this.y2 = edge.y; + this.p2 = new math.Vec(x2,y2); + math.Vec.init.call(this, x2-x1, y2-y1); + + this.elapsed = 0; + this.resetBound(); + return this; + }, + + // Determine how much time can pass before we risk teleporting + resetBound : function resetBound(){ + var abs = Math.abs + , bb = this.owner.boundingBox; + // this.tBound = Math.max( (bb.width - this.x1) / this.pa, + // (bb.height - this.y1) / this.pb ); + this.tBound = Math.min( abs(bb.width / this.pa), + abs(bb.height / this.pb) ); + return this; + }, + + step : function step(dt){ + var _dt = dt = (dt === undefined ? ELAPSED : dt); + + var o = this.owner, offX = o.offsetX, offY = o.offsetY, _bb + , bb = _bb = o.boundingBox, bw = bb.width, bh = bb.height + , loc = bb.p1 + ; + + do { + var t, to, ng, x1,y1, x2,y2, bs, blocker, blockers, what, wall; + + t = Math.min(this.tBound, dt); + dt -= t; + this.elapsed += t; + + var _to = to = this.parametric(this.elapsed); + x1 = to.x+offX; y1 = to.y+offY; + x2 = x1+bw; y2 = y1+bh; + + bs = this.pathmap.get(x1,y1, x2,y2).remove(o).end(); + blocker = bs[0]; + + // Literal corner case :P + if (bs.length > 1) { + console.log('multiple blockers! '+bs.slice(0)); + what = 'corner of '+bs.slice(0); + to = loc; + ng = this.p1; // XXX: recalculate? + + // Normal reflection against one line + } else if ( blocker ) { + what = blocker+' on the '; + + var B = blocker.boundingBox; + if (bb.x2 <= B.x1 && x2 > B.x1) { + what += 'left'; + wall = B.sides.left; + to = this.pointAtX(B.x1-bw-offX-1); + + } else if (bb.x1 >= B.x2 && x1 < B.x2) { + what += 'right'; + wall = B.sides.right; + to = this.pointAtX(B.x2-offX+1); + + } else if (bb.y2 <= B.y1 && y2 > B.y1) { + what += 'top'; + wall = B.sides.top; + to = this.pointAtY(B.y1-bh-offY-1); + + } else if (bb.y1 >= B.y2 && y1 < B.y2) { + what += 'bottom'; + wall = B.sides.bottom; + to = this.pointAtY(B.y2-offY+1); + } + + ng = math.reflect(this.p2, wall); + } + + // No collision: don't change trajectory + if (!ng) { + this.depth = 0; + + // New goal-point: change trajectory + } else { + var og = this.p2; + this.reset(to.x,to.y, ng.x,ng.y); + + if (this.depth) { + console.log([ + '['+TICKS+':'+_dt+' ('+this.depth+')] '+this.owner+' reflected!', + ' wanted: '+_to+' x ('+(_to.x+bw)+','+(_to.y+bh)+')', + ' blocker: '+what, + ' old:', + ' loc: '+bb.p1, + ' goal: '+og, + ' new:', + ' loc: '+to, + ' goal: '+ng, + ' --> trajectory: '+this + ].join('\n')); + } + + bb = new Loc.Rect(to.x,to.y, ng.x,ng.y); + ng = null; + + this.owner.render(this.game.level); + // this.owner.render(this.game.level).draw(); + + if (this.depth++ < 5) { + // dt += t; // do this step over again + continue; + + } else { + // console.error('Reflection limit reached!'); break; + LBT.stop(); + throw new Error('Reflection limit reached!'); + } + } + + } while (dt > 0); + + // console.log('['+TICKS+':'+_dt+' ('+this.depth+')]', loc, ' ~> ', to); + this.depth = 0; + return to; + }, + + + toString : function toString(){ + return 'T['+this.p1+', '+this.p2+', slope='+this.slope.toFixed(3)+']'; + } +}); \ No newline at end of file diff --git a/src/tanks/thing/bullet.js b/src/tanks/thing/bullet.js index 425b086..3711d74 100644 --- a/src/tanks/thing/bullet.js +++ b/src/tanks/thing/bullet.js @@ -1,3 +1,4 @@ +// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*- Bullet = new Y.Class('Bullet', Thing, { traceTrajectories : true, @@ -6,17 +7,25 @@ Bullet = new Y.Class('Bullet', Thing, { * @param {tanks.Unit} owner * @param {math.Line} trajectory */ - init : function initBullet(owner, x,y){ + init : function initBullet(owner, x2,y2){ this.owner = owner; - Thing.init.call(this, owner.game, owner.align); - var loc = owner.loc; - this.trajectory = new math.Line(loc.x,loc.y, x,y, this.stats.move*REF_SIZE/1000); - this.setLocation(loc.x,loc.y); + this.game = owner.game; + Thing.init.call(this, owner.align); + var loc = owner.loc + , x1 = loc.x + , y1 = loc.y; + + this.setLocation(x1,y1); + this.trajectory = new Trajectory(this, x1,y1, x2,y2, this.stats.move*REF_SIZE/1000); }, - blocking : true, elapsed : 0, - width : 6, - height : 6, + + blocking : true, + + offsetX : -3, + offsetY : -3, + width : 6, + height : 6, stats : { move : 2.0, // move speed (squares/sec) @@ -38,45 +47,30 @@ Bullet = new Y.Class('Bullet', Thing, { createCooldowns : Y.op.nop, - - setLocation : function setLocation(x,y){ - var loc = this.loc; - if (loc && loc.x === x && loc.y === y) - return loc; - - loc = this.loc = new Loc(x,y); - - if (this.shape) this.shape.position(x,y); - var w2 = this.width/2, h2 = this.height/2; - this.boundingBox = new Loc.Rect(x-w2,y-h2, x+w2,y+h2); - - return this; - }, - - // setLocation : function setLocation(x,y){ - // var trj = this.trajectory; - // this.trajectory = new math.Line(x,y, trj.x2,trj.y2, trj.tdist); - // this.elapsed = 0; - // return Thing.prototype.setLocation.call(this, x,y); - // }, - setTarget : function setTarget(x,y){ var loc = this.loc , trj = this.trajectory; - this.trajectory = new math.Line(loc.x,loc.y, x,y, trj.tdist); - this.elapsed = 0; + this.trajectory.reset(loc.x,loc.y, x,y); + // this.trajectory = new math.Line(loc.x,loc.y, x,y, trj.tdist); + // this.elapsed = 0; return this; }, act : function act(){ - this.elapsed += ELAPSED; + // this.elapsed += ELAPSED; if (!this.dead) this.move(); return this; }, - _depth : 0, move : function move(){ + var to = this.trajectory.step( ELAPSED ); + this.game.moveAgentTo(this, to.x, to.y); + return this; + }, + + _depth : 0, + _move : function _move(){ var trj = this.trajectory, p2 = trj.p2, goal = p2, _ng , to = trj.parametric(this.elapsed) , test = this.game.pathmap.attemptMove(this, trj, to) @@ -123,7 +117,7 @@ Bullet = new Y.Class('Bullet', Thing, { if (this._depth++ < 5) return this; // this.move(); - console.log('Reflection limit reached!'); + console.error('Reflection limit reached!'); this._depth = 0; return this; } @@ -148,10 +142,13 @@ Bullet = new Y.Class('Bullet', Thing, { .appendTo( parent ); } + var loc = this.loc; this.shape = new Circle(3) - .position(this.loc.x+6, this.loc.y+6) + .position(loc.x, loc.y) .fill('#EC5B38') .appendTo( parent ); + this.shape.layer.attr('title', ''+loc); + return this; }, diff --git a/src/tanks/thing/thing.js b/src/tanks/thing/thing.js index c2119b8..79aff1c 100644 --- a/src/tanks/thing/thing.js +++ b/src/tanks/thing/thing.js @@ -32,7 +32,9 @@ Thing = new Evt.Class('Thing', { // Rotation (rads) rotation : 0, - // Bounding box dimensions + // Bounding box offsets/dimensions + offsetX : 0, + offsetY : 0, width : REF_SIZE*0.7, height : REF_SIZE*0.6, @@ -52,15 +54,45 @@ Thing = new Evt.Class('Thing', { }, setLocation : function setLocation(x,y){ - var loc = this.loc; - if (loc && loc.x === x && loc.y === y) + var loc = this.loc + , x1 = x + this.offsetX + , y1 = y + this.offsetY + , x2 = x1 + this.width + , y2 = y1 + this.height + ; + + if (!loc) + loc = this.loc = new Loc(x,y); + else if (loc.x === x && loc.y === y) return loc; - loc = this.loc = new Loc(x,y); - if (this.shape) this.shape.position(x,y); - this.boundingBox = new Loc.Rect(x,y, x+this.width,y+this.height); + else + this.loc.setXY(x,y); + + if (this.shape) + this.shape.position(x,y); + + // this.createBoundingBox(x,y); + this.boundingBox = new Loc.BoundingBox(x1,y1, x2,y2); + return this; }, + /** + * @protected + * Regenerates the bounding box of this object. + * Invoked with new values for position and/or size; unchanged values are undefined. + */ + createBoundingBox : function createBoundingBox(x,y, w,h){ + var loc = this.loc, tw = this.width, th = this.height + , dx = (x !== undefined), dy = (y !== undefined) + , dw = (w !== undefined), dh = (h !== undefined) + , x1 = (dx ? x : loc.x), y1 = (dy ? y : loc.y) + , x2 = (dw ? w : tw)+x2, y2 = (dh ? h : th)+y1 + ; + if (dx || dy || dw || dh) + this.boundingBox = new Loc.BoundingBox(x1,y1, x2,y2); + return this; + }, diff --git a/tanks.php b/tanks.php index 15f8e17..fbc90d6 100644 --- a/tanks.php +++ b/tanks.php @@ -11,8 +11,8 @@ class Tanks { static $mainScripts = array( - "src/tanks/main.js", - "src/tanks/main-ui.js" + "src/tanks/main.js" + // "src/tanks/main-ui.js" ); static $srcScripts = array( @@ -22,6 +22,7 @@ class Tanks { "src/tanks/calc.js", "src/tanks/map/loc.js", + "src/tanks/map/trajectory.js", "src/tanks/map/level.js", "src/tanks/map/pathmap.js", -- 1.7.0.4