render arena

This commit is contained in:
Stephen McQuay 2015-08-27 22:19:53 -07:00
parent 6bad86730e
commit 33f94af784
10 changed files with 3954 additions and 1 deletions

113
ui/arena/index.html Normal file
View File

@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Arena</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
<link href="/ui/bs/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="/ui/bs/css/bootstrap-theme.min.css" rel="stylesheet" media="screen">
<link href="/ui/css/hackerbots.css" rel="stylesheet" media="screen">
</head>
<body>
<div class="container">
<div id="error" class="alert alert-danger">
<div class="container"></div>
</div>
</div>
<div style="float:right; text-align:right;">
<strong><a href="/ui">HackerBots</a>&nbsp&nbsp</strong>
<br>{{ server.hostname }}&nbsp&nbsp{{game}}&nbsp&nbsp
</div>
<ul class="nav nav-tabs" id='tabs'>
<li><a href="#code" data-toggle="tab" class='active'>Code</a></li>
<li><a href="#arena" data-toggle="tab">Arena</a></li>
<li><a href="#raw" data-toggle="tab">Raw Bot Data</a></li>
<li><a href="#json" data-toggle="tab">Bot Data</a></li>
<li><a href="#about" data-toggle="tab">About</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="code">
<div id="output" class="error"></div>
<div style="float:left; padding:20px">
<div id="starters">
<button id='starter' class="btn btn-primary" style="width:100px;">Play</button><br><br>
<button id='watch' class="btn btn-success" style="width:100px;">Watch</button><br><br>
</div>
<br>
<div>
<select id="robot_picker" class="form-control" style='width:100px'>
{% for bot in bots %}
<option value="{{bot.id}}">{{bot.name}}</option>
{% endfor %}
</select><br>
<button class='btn bt-primary' id='save' style="width:100px;">Save Bot</button><br>
<span class='under-button' id='lastsave'></span><br><br>
<button class='btn bt-primary' id='new' style="width:100px;">New Bot</button><br><br>
<div id='rename-block' style='display:none;'>
<input type='text' id='newname' style="width:100px; "><br><br>
</div>
<button class='btn bt-primary' id='rename' style="width:100px;">Rename Bot</button><br><br>
<br><br>
</div>
</div>
<pre id="editor" class="center"></pre>
</div>
<div class="tab-pane" id="arena">
<p>
<div>
<pre id="players"></pre>
</div>
<div id="bf" class="center">
<canvas id="battlefield" width="1000px" height="800px"></canvas>
Use 'v' to cycle views<br>
Use 't' to cycle robot focus<br>
Use 'd' to toggle debug display<br>
Use '-' / '=' to zoom in/out<br>
</div>
</div>
<div class="tab-pane" id="raw">
<p>
<pre id="raw-debug" class="center debug"></pre>
<p>
<pre class='center debug'>Raw Data is exactly what the server send unprocessed. 'p' to pause</pre>
</div>
<div class="tab-pane" id="json">
<p>
<pre id="debug" class="center debug"></pre>
<p>
<pre class='center debug'>Bot Data is the JSON from the server after it's been processed by the gorobots javascript code to fill our the scanner fields with data from the bot list.
'p' to pause</pre>
</div>
<div class="tab-pane" id="about">
<h4> Hackerbots is Awesome </h4>
<p>
I would love to tell you more, but we're too busy writing the code.
You should follow @smcquay or @twisted_weasel.
</div>
</div>
<script src="/ui/jquery.js"></script>
<script src="/ui/bs/js/bootstrap.min.js"></script>
<script src="/ui/js/botui.js"></script>
<script src="/ui/js/arena.js"></script>
<script src="/ui/js/vec2d.js"></script>
<script src="/ui/bs/js/bootstrap.js" %}"></script>
<script src="/ui/js/stats.min.js"></script>
<script src="/ui/js/three.min.js"></script>
<script src="/ui/js/OBJLoader.js"></script>
<script src="/ui/js/TrackballControls.js"></script>
<script src="/ui/js/gorobots_render_webgl.js"></script>
<script src="/ui/js/gorobots.js"></script>
<script src="/ui/js/ace/ace.js" type="text/javascript" charset="utf-8"></script>
<script>
</script>
</body>
</html>

431
ui/js/OBJLoader.js Normal file
View File

@ -0,0 +1,431 @@
/**
* @author mrdoob / http://mrdoob.com/
*/
THREE.OBJLoader = function ( manager ) {
this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
};
THREE.OBJLoader.prototype = {
constructor: THREE.OBJLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new THREE.XHRLoader( scope.manager );
loader.setCrossOrigin( this.crossOrigin );
loader.load( url, function ( text ) {
onLoad( scope.parse( text ) );
} );
},
parse: function ( text ) {
function vector( x, y, z ) {
return new THREE.Vector3( x, y, z );
}
function uv( u, v ) {
return new THREE.Vector2( u, v );
}
function face3( a, b, c, normals ) {
return new THREE.Face3( a, b, c, normals );
}
var object = new THREE.Object3D();
var geometry, material, mesh;
// create mesh if no objects in text
if ( /^o /gm.test( text ) === false ) {
geometry = new THREE.Geometry();
material = new THREE.MeshLambertMaterial();
mesh = new THREE.Mesh( geometry, material );
object.add( mesh );
}
var vertices = [];
var verticesCount = 0;
var normals = [];
var uvs = [];
// v float float float
var vertex_pattern = /v( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/;
// vn float float float
var normal_pattern = /vn( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/;
// vt float float
var uv_pattern = /vt( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/;
// f vertex vertex vertex ...
var face_pattern1 = /f( +\d+)( +\d+)( +\d+)( +\d+)?/;
// f vertex/uv vertex/uv vertex/uv ...
var face_pattern2 = /f( +(\d+)\/(\d+))( +(\d+)\/(\d+))( +(\d+)\/(\d+))( +(\d+)\/(\d+))?/;
// f vertex/uv/normal vertex/uv/normal vertex/uv/normal ...
var face_pattern3 = /f( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))?/;
// f vertex//normal vertex//normal vertex//normal ...
var face_pattern4 = /f( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))?/
//
var lines = text.split( '\n' );
for ( var i = 0; i < lines.length; i ++ ) {
var line = lines[ i ];
line = line.trim();
var result;
if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
continue;
} else if ( ( result = vertex_pattern.exec( line ) ) !== null ) {
// ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
vertices.push( vector(
parseFloat( result[ 1 ] ),
parseFloat( result[ 2 ] ),
parseFloat( result[ 3 ] )
) );
} else if ( ( result = normal_pattern.exec( line ) ) !== null ) {
// ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
normals.push( vector(
parseFloat( result[ 1 ] ),
parseFloat( result[ 2 ] ),
parseFloat( result[ 3 ] )
) );
} else if ( ( result = uv_pattern.exec( line ) ) !== null ) {
// ["vt 0.1 0.2", "0.1", "0.2"]
uvs.push( uv(
parseFloat( result[ 1 ] ),
parseFloat( result[ 2 ] )
) );
} else if ( ( result = face_pattern1.exec( line ) ) !== null ) {
// ["f 1 2 3", "1", "2", "3", undefined]
if ( result[ 4 ] === undefined ) {
geometry.vertices.push(
vertices[ parseInt( result[ 1 ] ) - 1 ],
vertices[ parseInt( result[ 2 ] ) - 1 ],
vertices[ parseInt( result[ 3 ] ) - 1 ]
);
geometry.faces.push( face3(
verticesCount ++,
verticesCount ++,
verticesCount ++
) );
} else {
geometry.vertices.push(
vertices[ parseInt( result[ 1 ] ) - 1 ],
vertices[ parseInt( result[ 2 ] ) - 1 ],
vertices[ parseInt( result[ 3 ] ) - 1 ],
vertices[ parseInt( result[ 4 ] ) - 1 ]
);
geometry.faces.push( face3(
verticesCount,
verticesCount + 1,
verticesCount + 3
) );
geometry.faces.push( face3(
verticesCount + 1,
verticesCount + 2,
verticesCount + 3
) );
verticesCount += 4;
}
} else if ( ( result = face_pattern2.exec( line ) ) !== null ) {
// ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined]
if ( result[ 10 ] === undefined ) {
geometry.vertices.push(
vertices[ parseInt( result[ 2 ] ) - 1 ],
vertices[ parseInt( result[ 5 ] ) - 1 ],
vertices[ parseInt( result[ 8 ] ) - 1 ]
);
geometry.faces.push( face3(
verticesCount ++,
verticesCount ++,
verticesCount ++
) );
geometry.faceVertexUvs[ 0 ].push( [
uvs[ parseInt( result[ 3 ] ) - 1 ],
uvs[ parseInt( result[ 6 ] ) - 1 ],
uvs[ parseInt( result[ 9 ] ) - 1 ]
] );
} else {
geometry.vertices.push(
vertices[ parseInt( result[ 2 ] ) - 1 ],
vertices[ parseInt( result[ 5 ] ) - 1 ],
vertices[ parseInt( result[ 8 ] ) - 1 ],
vertices[ parseInt( result[ 11 ] ) - 1 ]
);
geometry.faces.push( face3(
verticesCount,
verticesCount + 1,
verticesCount + 3
) );
geometry.faceVertexUvs[ 0 ].push( [
uvs[ parseInt( result[ 3 ] ) - 1 ],
uvs[ parseInt( result[ 6 ] ) - 1 ],
uvs[ parseInt( result[ 12 ] ) - 1 ]
] );
geometry.faces.push( face3(
verticesCount + 1,
verticesCount + 2,
verticesCount + 3
) );
geometry.faceVertexUvs[ 0 ].push( [
uvs[ parseInt( result[ 6 ] ) - 1 ],
uvs[ parseInt( result[ 9 ] ) - 1 ],
uvs[ parseInt( result[ 12 ] ) - 1 ]
] );
verticesCount += 4;
}
} else if ( ( result = face_pattern3.exec( line ) ) !== null ) {
// ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined]
if ( result[ 13 ] === undefined ) {
geometry.vertices.push(
vertices[ parseInt( result[ 2 ] ) - 1 ],
vertices[ parseInt( result[ 6 ] ) - 1 ],
vertices[ parseInt( result[ 10 ] ) - 1 ]
);
geometry.faces.push( face3(
verticesCount ++,
verticesCount ++,
verticesCount ++,
[
normals[ parseInt( result[ 4 ] ) - 1 ],
normals[ parseInt( result[ 8 ] ) - 1 ],
normals[ parseInt( result[ 12 ] ) - 1 ]
]
) );
geometry.faceVertexUvs[ 0 ].push( [
uvs[ parseInt( result[ 3 ] ) - 1 ],
uvs[ parseInt( result[ 7 ] ) - 1 ],
uvs[ parseInt( result[ 11 ] ) - 1 ]
] );
} else {
geometry.vertices.push(
vertices[ parseInt( result[ 2 ] ) - 1 ],
vertices[ parseInt( result[ 6 ] ) - 1 ],
vertices[ parseInt( result[ 10 ] ) - 1 ],
vertices[ parseInt( result[ 14 ] ) - 1 ]
);
geometry.faces.push( face3(
verticesCount,
verticesCount + 1,
verticesCount + 3,
[
normals[ parseInt( result[ 4 ] ) - 1 ],
normals[ parseInt( result[ 8 ] ) - 1 ],
normals[ parseInt( result[ 16 ] ) - 1 ]
]
) );
geometry.faceVertexUvs[ 0 ].push( [
uvs[ parseInt( result[ 3 ] ) - 1 ],
uvs[ parseInt( result[ 7 ] ) - 1 ],
uvs[ parseInt( result[ 15 ] ) - 1 ]
] );
geometry.faces.push( face3(
verticesCount + 1,
verticesCount + 2,
verticesCount + 3,
[
normals[ parseInt( result[ 8 ] ) - 1 ],
normals[ parseInt( result[ 12 ] ) - 1 ],
normals[ parseInt( result[ 16 ] ) - 1 ]
]
) );
geometry.faceVertexUvs[ 0 ].push( [
uvs[ parseInt( result[ 7 ] ) - 1 ],
uvs[ parseInt( result[ 11 ] ) - 1 ],
uvs[ parseInt( result[ 15 ] ) - 1 ]
] );
verticesCount += 4;
}
} else if ( ( result = face_pattern4.exec( line ) ) !== null ) {
// ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined]
if ( result[ 10 ] === undefined ) {
geometry.vertices.push(
vertices[ parseInt( result[ 2 ] ) - 1 ],
vertices[ parseInt( result[ 5 ] ) - 1 ],
vertices[ parseInt( result[ 8 ] ) - 1 ]
);
geometry.faces.push( face3(
verticesCount ++,
verticesCount ++,
verticesCount ++,
[
normals[ parseInt( result[ 3 ] ) - 1 ],
normals[ parseInt( result[ 6 ] ) - 1 ],
normals[ parseInt( result[ 9 ] ) - 1 ]
]
) );
} else {
geometry.vertices.push(
vertices[ parseInt( result[ 2 ] ) - 1 ],
vertices[ parseInt( result[ 5 ] ) - 1 ],
vertices[ parseInt( result[ 8 ] ) - 1 ],
vertices[ parseInt( result[ 11 ] ) - 1 ]
);
geometry.faces.push( face3(
verticesCount,
verticesCount + 1,
verticesCount + 3,
[
normals[ parseInt( result[ 3 ] ) - 1 ],
normals[ parseInt( result[ 6 ] ) - 1 ],
normals[ parseInt( result[ 12 ] ) - 1 ]
]
) );
geometry.faces.push( face3(
verticesCount + 1,
verticesCount + 2,
verticesCount + 3,
[
normals[ parseInt( result[ 6 ] ) - 1 ],
normals[ parseInt( result[ 9 ] ) - 1 ],
normals[ parseInt( result[ 12 ] ) - 1 ]
]
) );
verticesCount += 4;
}
} else if ( /^o /.test( line ) ) {
// object
geometry = new THREE.Geometry();
material = new THREE.MeshLambertMaterial();
mesh = new THREE.Mesh( geometry, material );
mesh.name = line.substring( 2 ).trim();
object.add( mesh );
verticesCount = 0;
} else if ( /^g /.test( line ) ) {
// group
} else if ( /^usemtl /.test( line ) ) {
// material
material.name = line.substring( 7 ).trim();
} else if ( /^mtllib /.test( line ) ) {
// mtl file
} else if ( /^s /.test( line ) ) {
// smooth shading
} else {
// console.log( "THREE.OBJLoader: Unhandled line " + line );
}
}
for ( var i = 0, l = object.children.length; i < l; i ++ ) {
var geometry = object.children[ i ].geometry;
geometry.computeCentroids();
geometry.computeFaceNormals();
geometry.computeBoundingSphere();
}
return object;
}
};

607
ui/js/TrackballControls.js Executable file
View File

@ -0,0 +1,607 @@
/**
* @author Eberhard Graether / http://egraether.com/
* @author Mark Lundin / http://mark-lundin.com
*/
THREE.TrackballControls = function ( object, domElement ) {
var _this = this;
var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 };
this.object = object;
this.domElement = ( domElement !== undefined ) ? domElement : document;
// API
this.enabled = true;
this.screen = { left: 0, top: 0, width: 0, height: 0 };
this.rotateSpeed = 1.0;
this.zoomSpeed = 1.2;
this.panSpeed = 0.3;
this.noRotate = false;
this.noZoom = false;
this.noPan = false;
this.noRoll = true;
this.staticMoving = true;
this.dynamicDampingFactor = 0.2;
this.minDistance = 0;
this.maxDistance = Infinity;
this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
// internals
this.target = new THREE.Vector3();
var lastPosition = new THREE.Vector3();
var _state = STATE.NONE,
_prevState = STATE.NONE,
_eye = new THREE.Vector3(),
_rotateStart = new THREE.Vector3(),
_rotateEnd = new THREE.Vector3(),
_zoomStart = new THREE.Vector2(),
_zoomEnd = new THREE.Vector2(),
_touchZoomDistanceStart = 0,
_touchZoomDistanceEnd = 0,
_panStart = new THREE.Vector2(),
_panEnd = new THREE.Vector2();
// for reset
this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
this.up0 = this.object.up.clone();
// events
var changeEvent = { type: 'change' };
var startEvent = { type: 'start'};
var endEvent = { type: 'end'};
// methods
this.handleResize = function () {
if ( this.domElement === document ) {
this.screen.left = 0;
this.screen.top = 0;
this.screen.width = window.innerWidth;
this.screen.height = window.innerHeight;
} else {
this.screen = this.domElement.getBoundingClientRect();
// adjustments come from similar code in the jquery offset() function
var d = this.domElement.ownerDocument.documentElement
this.screen.left += window.pageXOffset - d.clientLeft
this.screen.top += window.pageYOffset - d.clientTop
}
};
this.handleEvent = function ( event ) {
if ( typeof this[ event.type ] == 'function' ) {
this[ event.type ]( event );
}
};
this.getMouseOnScreen = function ( pageX, pageY, vector ) {
return vector.set(
( pageX - _this.screen.left ) / _this.screen.width,
( pageY - _this.screen.top ) / _this.screen.height
);
};
this.getMouseProjectionOnBall = (function(){
var objectUp = new THREE.Vector3(),
mouseOnBall = new THREE.Vector3();
return function ( pageX, pageY, projection ) {
mouseOnBall.set(
( pageX - _this.screen.width * 0.5 - _this.screen.left ) / (_this.screen.width*.5),
( _this.screen.height * 0.5 + _this.screen.top - pageY ) / (_this.screen.height*.5),
0.0
);
var length = mouseOnBall.length();
if ( _this.noRoll ) {
if ( length < Math.SQRT1_2 ) {
mouseOnBall.z = Math.sqrt( 1.0 - length*length );
} else {
mouseOnBall.z = .5 / length;
}
} else if ( length > 1.0 ) {
mouseOnBall.normalize();
} else {
mouseOnBall.z = Math.sqrt( 1.0 - length * length );
}
_eye.copy( _this.object.position ).sub( _this.target );
projection.copy( _this.object.up ).setLength( mouseOnBall.y )
projection.add( objectUp.copy( _this.object.up ).cross( _eye ).setLength( mouseOnBall.x ) );
projection.add( _eye.setLength( mouseOnBall.z ) );
return projection;
}
}());
this.rotateCamera = (function(){
var axis = new THREE.Vector3(),
quaternion = new THREE.Quaternion();
return function () {
var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
if ( angle ) {
axis.crossVectors( _rotateStart, _rotateEnd ).normalize();
angle *= _this.rotateSpeed;
quaternion.setFromAxisAngle( axis, -angle );
_eye.applyQuaternion( quaternion );
_this.object.up.applyQuaternion( quaternion );
_rotateEnd.applyQuaternion( quaternion );
if ( _this.staticMoving ) {
_rotateStart.copy( _rotateEnd );
} else {
quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
_rotateStart.applyQuaternion( quaternion );
}
}
}
}());
this.zoomCamera = function () {
if ( _state === STATE.TOUCH_ZOOM ) {
var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
_touchZoomDistanceStart = _touchZoomDistanceEnd;
_eye.multiplyScalar( factor );
} else {
var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
if ( factor !== 1.0 && factor > 0.0 ) {
_eye.multiplyScalar( factor );
if ( _this.staticMoving ) {
_zoomStart.copy( _zoomEnd );
} else {
_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
}
}
}
};
this.panCamera = (function(){
var mouseChange = new THREE.Vector2(),
objectUp = new THREE.Vector3(),
pan = new THREE.Vector3();
return function () {
mouseChange.copy( _panEnd ).sub( _panStart );
if ( mouseChange.lengthSq() ) {
mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );
pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x );
pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) );
_this.object.position.add( pan );
_this.target.add( pan );
if ( _this.staticMoving ) {
_panStart.copy( _panEnd );
} else {
_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
}
}
}
}());
this.checkDistances = function () {
if ( !_this.noZoom || !_this.noPan ) {
if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) {
_this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) );
}
if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {
_this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
}
}
};
this.update = function () {
var old = new THREE.Vector3();
old.copy(_this.object.position);
_eye.subVectors( _this.object.position, _this.target );
if ( !_this.noRotate ) {
_this.rotateCamera();
}
if ( !_this.noZoom ) {
_this.zoomCamera();
}
if ( !_this.noPan ) {
_this.panCamera();
}
_this.object.position.addVectors( _this.target, _eye );
// HACKERBOTS CHANGES
// Up is always up
_this.object.up.copy( _this.up0 );
// Stay above ground
if (_this.object.position.z <= 20){
_this.object.position.copy(old);
}
if (_this.object.position.z >= 500){
_this.object.position.copy(old);
}
// END HACKERBOTS
_this.checkDistances();
_this.object.lookAt( _this.target );
if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) {
_this.dispatchEvent( changeEvent );
lastPosition.copy( _this.object.position );
}
};
this.reset = function () {
_state = STATE.NONE;
_prevState = STATE.NONE;
_this.target.copy( _this.target0 );
_this.object.position.copy( _this.position0 );
_this.object.up.copy( _this.up0 );
_eye.subVectors( _this.object.position, _this.target );
_this.object.lookAt( _this.target );
_this.dispatchEvent( changeEvent );
lastPosition.copy( _this.object.position );
};
// listeners
function keydown( event ) {
if ( _this.enabled === false ) return;
window.removeEventListener( 'keydown', keydown );
_prevState = _state;
if ( _state !== STATE.NONE ) {
return;
} else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) {
_state = STATE.ROTATE;
} else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) {
_state = STATE.ZOOM;
} else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) {
_state = STATE.PAN;
}
}
function keyup( event ) {
if ( _this.enabled === false ) return;
_state = _prevState;
window.addEventListener( 'keydown', keydown, false );
}
function mousedown( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
if ( _state === STATE.NONE ) {
_state = event.button;
}
if ( _state === STATE.ROTATE && !_this.noRotate ) {
_this.getMouseProjectionOnBall( event.pageX, event.pageY, _rotateStart );
_rotateEnd.copy(_rotateStart)
} else if ( _state === STATE.ZOOM && !_this.noZoom ) {
_this.getMouseOnScreen( event.pageX, event.pageY, _zoomStart );
_zoomEnd.copy(_zoomStart);
} else if ( _state === STATE.PAN && !_this.noPan ) {
_this.getMouseOnScreen( event.pageX, event.pageY, _panStart );
_panEnd.copy(_panStart)
}
document.addEventListener( 'mousemove', mousemove, false );
document.addEventListener( 'mouseup', mouseup, false );
_this.dispatchEvent( startEvent );
}
function mousemove( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
if ( _state === STATE.ROTATE && !_this.noRotate ) {
_this.getMouseProjectionOnBall( event.pageX, event.pageY, _rotateEnd );
} else if ( _state === STATE.ZOOM && !_this.noZoom ) {
_this.getMouseOnScreen( event.pageX, event.pageY, _zoomEnd );
} else if ( _state === STATE.PAN && !_this.noPan ) {
_this.getMouseOnScreen( event.pageX, event.pageY, _panEnd );
}
}
function mouseup( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
_state = STATE.NONE;
document.removeEventListener( 'mousemove', mousemove );
document.removeEventListener( 'mouseup', mouseup );
_this.dispatchEvent( endEvent );
}
function mousewheel( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
var delta = 0;
if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9
delta = event.wheelDelta / 40;
} else if ( event.detail ) { // Firefox
delta = - event.detail / 3;
}
_zoomStart.y += delta * 0.01;
_this.dispatchEvent( startEvent );
_this.dispatchEvent( endEvent );
}
function touchstart( event ) {
if ( _this.enabled === false ) return;
switch ( event.touches.length ) {
case 1:
_state = STATE.TOUCH_ROTATE;
_rotateEnd.copy( _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _rotateStart ));
break;
case 2:
_state = STATE.TOUCH_ZOOM;
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
break;
case 3:
_state = STATE.TOUCH_PAN;
_panEnd.copy( _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _panStart ));
break;
default:
_state = STATE.NONE;
}
_this.dispatchEvent( startEvent );
}
function touchmove( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
switch ( event.touches.length ) {
case 1:
_this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _rotateEnd );
break;
case 2:
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy )
break;
case 3:
_this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _panEnd );
break;
default:
_state = STATE.NONE;
}
}
function touchend( event ) {
if ( _this.enabled === false ) return;
switch ( event.touches.length ) {
case 1:
_rotateStart.copy( _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _rotateEnd ));
break;
case 2:
_touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
break;
case 3:
_panStart.copy( _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _panEnd ));
break;
}
_state = STATE.NONE;
_this.dispatchEvent( endEvent );
}
this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
this.domElement.addEventListener( 'mousedown', mousedown, false );
this.domElement.addEventListener( 'mousewheel', mousewheel, false );
this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox
this.domElement.addEventListener( 'touchstart', touchstart, false );
this.domElement.addEventListener( 'touchend', touchend, false );
this.domElement.addEventListener( 'touchmove', touchmove, false );
window.addEventListener( 'keydown', keydown, false );
window.addEventListener( 'keyup', keyup, false );
this.handleResize();
};
THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );

148
ui/js/arena.js Normal file
View File

@ -0,0 +1,148 @@
var savebot = function(name, callback){
data = {
"id": $("#robot_picker").val(),
"code": editor.getValue()
};
if (name){
data.name = name;
}
$.ajax({
method:"POST",
data: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
},
url:"/api/v1/bot/" + $("#robot_picker").val() + "/",
success: function(data){
var d = new Date();
$("#lastsave").html("Saved at " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds());
if (callback){
callback();
}
}
});
};
var newbot = function(){
data = {
"code": editor.getValue(),
};
$.ajax({
method:"POST",
data: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
},
url:"/api/v1/bot/" + $("#robot_picker").val() + "/",
success: function(data){
console.log(data);
if (data.id){
$("#robot_picker").val(data.id);
}
// I'm lazy
set_current_bot(data.id);
location.reload();
}
});
};
var set_current_bot = function(name){
var queryParameters = {}, queryString = location.search.substring(1),
re = /([^&=]+)=([^&]*)/g, m;
while (m = re.exec(queryString)) {
queryParameters[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
}
queryParameters['bot'] = name;
history.pushState(null, null, "?"+ $.param(queryParameters));
};
$(document).ready(function(){
$("#error").hide();
editor = ace.edit("editor");
editor.setTheme("ace/theme/monokai");
editor.getSession().setMode("ace/mode/javascript");
function resizeAce() {
return $('#editor').height($(window).height() - $("#editor").offset().top - 20);
};
$(window).resize(resizeAce);
resizeAce();
var gorobots = gorobots_init({}, true);
$("#robot_picker").change(function(e){
$.ajax({
url:"/api/v1/bot/" + $("#robot_picker").val(),
success: function(data){
editor.setValue(data.code);
set_current_bot($("#robot_picker").val());
}
});
});
$("#save").click(function(e){
savebot();
});
$("#new").click(function(e){
newbot();
});
var renaming = false;
$("#rename").click(function(e){
if (renaming){
// POST new name to server
savebot($("#newname").val(), function(){
// BE LAZY
location.reload();
});
$("#rename-block").hide();
$("#rename").html("Rename Bot");
renaming = false;
} else {
$("#rename-block").show();
$("#rename").html("Ok");
renaming = true;
}
});
$.ajax({
url:"/api/v1/bot/1/",
success: function(data){
editor.setValue(data.code);
$("#robot_picker").val(1);
}
});
$('#starter').click(function() {
savebot();
$('#tabs a[href="#arena"]').tab('show');
var url = "ws://{{ server.hostname }}:8666/ws/";
gorobots.set_spectator(false);
gorobots.set_server(
url,
"{{ game }}"
);
});
$('#watch').click(function() {
savebot();
$('#tabs a[href="#arena"]').tab('show');
var url = "ws://{{ server.hostname }}:8666/ws/";
gorobots.set_spectator(true);
gorobots.set_server(
url,
"{{ game }}"
);
});
$('#tabs a[href="#code"]').tab('show');
});

View File

@ -11,7 +11,7 @@ function loadGames() {
$("#games").empty();
for (var key in data) {
var name = data[key]["id"];
var s = "<li><a href='/ui/game/" + name + "/'>join</a> " + name + "</li>";
var s = "<li><a href='/ui/arena/?id=" + name + "'>join</a> " + name + "</li>";
$("#games").append(s);
}
});

561
ui/js/gorobots.js Normal file
View File

@ -0,0 +1,561 @@
var gorobots_init = function gorobots(my, webgl){
my.width = null;
my.height = null;
my.server = "ws://localhost:8666/ws/";
my.websocket = null;
my.id = null;
my.connection_retry = 3000;
my.state = null;
my.spectator = false;
my.debug = false;
my.debug_draw = true;
var grey = "#999999";
var colors = [
"#222222", // Grey
"#000099",
"#009900",
"#990000",
"#009999",
"#990099",
"#999900",
"#000044",
"#004400",
"#440000",
"#004444",
"#440044",
"#444400",
"#0000cc",
"#00cc00",
"#cc0000",
"#00cccc",
"#cc00cc",
"#cccc00"
];
my.connect = function(server){
connection = new WebSocket(server, null);
my.websocket = connection;
console.log(connection);
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};
connection.onopen = function(){
console.log("Connected to " + server);
my.send_game_request();
};
connection.onclose = function(){
my.id = null;
my.state = null;
my.renderer.reset();
console.log("Connection Closed");
if (my.connection_retry > 0){
// Retry every few seconds
console.log("Lost Connection: " + server);
setTimeout(function(){
my.websocket = my.connect(my.server);
}, my.connection_retry);
}
};
connection.onmessage = function (e) {
// Handshake protocol, we must establish a game ID and send
// the robot config for approval before we can play
new_data = JSON.parse(e.data);
if (my.state === null) {
if (new_data.type == "idreq") {
if ('id' in new_data){
my.id = new_data['id'];
console.log("Assigned ID " + my.id + " by server");
} else {
console.log("server failed to send us an id");
}
my.send_client_id();
my.state = "gameparam";
// send supported encodings
my.websocket.send(JSON.stringify(["json"]));
}
} else if (my.state == "gameparam"){
if (new_data.type == "gameparam") {
// > [OK | FULL | NOT AUTH], board size, game params
my.parse_game_params(new_data);
my.state = "handshake";
my.setup_robot();
}
} else if (my.state == "handshake") {
// We have game details, make the render target
console.log("completed handshake");
console.log(new_data);
if('success' in new_data) {
if (!new_data.success){
alert("Your Robot Config is not valid, adjust your point distributions and try again");
return false;
}
}
my.renderer.setup_render_target(my.width, my.height, my.width, my.height);
my.state = "play";
} else if(my.state == "play") {
if (new_data.type == "handshake") {
// This is the handshake response, we've been assigned an ID
// and are in the game (or TODO: in a lobby).
console.log("we got a handshake when we expect a boardstate packet");
} else if (new_data.type == "stats") {
// XXX: need to actually assign this to the player/robots
// ...
console.log("\\o/ we have stats: " + JSON.stringify(new_data));
} else if (new_data.type == "boardstate") {
my.renderer.begin_frame(new_data['turn']);
var draw_list = my.process_gameplay_packet(new_data);
my.renderer.render(draw_list);
my.renderer.end_frame();
} else if (new_data.type == "gameover") {
my.renderer.reset();
} else {
console.error("unexpected state ...");
}
}
};
return connection;
};
my.prepare_data = function(data){
// get all the robot id's in the scanner list and find them in the
// overall robot list so we can pass them to the robot code
if (data['my_robots']){
for (var i=0; i < data['my_robots'].length; i++){
var bot = data['my_robots'][i];
bot.messages = data.messages;
for(var p in bot['scanners']){
var bot_id = bot['scanners'][p]['id'];
// find the scanner object in the pool of objects
if (bot['scanners'][p]['type'] == 'robot'){
for (var b in data['robots']){
if (data['robots'][b]['id'] == bot_id){
bot['scanners'][p] = data['robots'][b];
bot['scanners'][p].type = "robot";
bot['scanners'][p].friend = false;
}
}
for (var mybot in data['my_robots']){
if (data['my_robots'][mybot].id == bot['scanners'][p].id){
bot['scanners'][p].friend = true;
}
}
}
if (bot['scanners'][p]['type'] == 'projectile'){
for (var b in data['projectiles']){
if (data['projectiles'][b]['id'] == bot_id){
bot['scanners'][p] = data['projectiles'][b];
bot['scanners'][p].type = "projectile";
}
}
}
}
}
}
return data;
};
my.process_gameplay_packet = function(new_data){
my.stats.begin();
my.renderer.clear_debug();
var draw_list = [];
if (new_data.reset){
// my.setup_robot();
my.renderer.reset();
return;
}
if (!my.paused) {
var debug_div = document.getElementById("raw-debug");
if (debug_div){
debug_div.innerHTML = JSON.stringify(new_data, undefined, 2);
}
}
// Prep the data
new_data = my.prepare_data(new_data);
// Dump the contents of the message from the server
if (!my.paused) {
var debug_div = document.getElementById("debug");
if (debug_div){
debug_div.innerHTML = JSON.stringify(new_data, undefined, 2);
}
}
// Update the robots and build a list of what to draw
var my_robots = new_data['my_robots'];
var robots = new_data['robots'];
var all_bots = new_data['all_bots'];
my.all_bots = [];
my.my_bots = my_robots;
// Set the list of players and assign colors
var players = "";
var col = 2;
for (i=0; i < all_bots.length; i++){
my.all_bots.push(all_bots[i]['robot_id']);
// I hate javascript
var health_str = "";
if (all_bots[i]['health'] < 0) {
health_str = "000";
all_bots[i]['color'] = grey;
}
else if (all_bots[i]['health'] < 10) {
health_str = "00" + all_bots[i]['health'];
all_bots[i]['color'] = colors[col];
}
else if (all_bots[i]['health'] < 100){
health_str = "0" + all_bots[i]['health'];
all_bots[i]['color'] = colors[col];
}
else{
health_str = all_bots[i]['health'];
all_bots[i]['color'] = colors[col];
}
var mine = false;
for (var mybot in my.my_bots){
if (my.my_bots[mybot].id == all_bots[i]['robot_id']){
mine = true;
all_bots[i]['color'] = colors[1];
}
}
if (!mine){
col++;
}
players += ("<span style='color: " +
all_bots[i]['color'] +
";'>&nbsp&nbsp" +
all_bots[i]['robot_id'] +
" [" + health_str + "]</span>");
}
var players_div = document.getElementById("players");
if (players_div){
players_div.innerHTML = players;
}
// Now let's deal with all the things in the world
var i = 0;
if (my_robots){
instructions = {};
for (i=0; i < my_robots.length; i++){
if (my_robots[i].health > 0) {
instructions[my_robots[i]['id']] = my.update_robot(my_robots[i], new_data['obj']);
}
for (var bot in all_bots){
if (all_bots[bot]['robot_id'] == my_robots[i]['id']){
col = all_bots[bot]['color'];
}
}
if (my_robots[i].health <= 0){
col = grey;
}
if ("position" in my_robots[i]){
my_robots[i]['color'] = col;
my_robots[i]['type'] = 'robot';
draw_list.push(my_robots[i]);
}
}
if (my.websocket){
var payload = JSON.stringify(instructions);
// console.log(payload);
my.websocket.send(payload);
}
if (my.target < 0 && !my.spectator){
console.log("Watching " + my.my_bots[0].id);
my.renderer.set_focus_robot(my.my_bots[0].id);
my.target = 0;
}
}
if (robots){
for (i=0; i < robots.length; i++){
// Get the color from all_bots
var col = grey;
for (var bot in all_bots){
if (all_bots[bot]['robot_id'] == robots[i]['id']){
col = all_bots[bot]['color'];
}
}
if (robots[i].health <= 0){
col = grey;
}
if ("position" in robots[i]){
robots[i]['color'] = col;
robots[i]['type'] = 'robot';
draw_list.push(robots[i]);
}
}
}
// Draw the projecticles
var projectiles = new_data['projectiles'];
if (projectiles){
for (i=0; i < projectiles.length; i++){
if ("position" in projectiles[i]){
projectiles[i]['type'] = 'bullet';
draw_list.push(projectiles[i]);
}
}
}
// Draw the projecticles
var splosions = new_data['splosions'];
if (splosions){
for (i=0; i < splosions.length; i++){
if ("position" in splosions[i]){
splosions[i]['type'] = 'explosion';
draw_list.push(splosions[i]);
}
}
}
// Draw the objects
var obj = new_data['objects'];
if (obj){
for (i=0; i < obj.length; i++){
var o = {
bounds : obj[i]
};
o['type'] = "object";
o['position'] = {x: obj[i][0], y: obj[i][1]};
draw_list.push(o);
}
}
my.stats.end();
return draw_list;
};
my.eval_input = function( input, output ){
var theResult, evalSucceeded;
try{
theResult = eval( input );
evalSucceeded = true;
}
catch(e){
output.innerHTML = e;
}
if ( evalSucceeded ) {
output.innerHTML = "";
// console.log("OK");
}
return theResult;
};
my.get_robot_code = function(){
var robot_code = editor.getSession().getValue();
var output = document.getElementById('output');
var code = "( " + robot_code + " )";
var rc = my.eval_input(code, output);
return rc.call(this);
};
my.send_game_request = function() {
game = {
"id": my.game_id,
};
console.log(my.websocket.readyState);
my.websocket.send(JSON.stringify(game));
console.log("sent clientid: " + JSON.stringify(game));
};
my.send_client_id = function() {
client_id = {
"type": my.spectator ? "spectator" : "robot",
"name": "dummy",
"id": "24601",
"useragent": "gorobots.js",
};
my.websocket.send(JSON.stringify(client_id));
};
my.parse_game_params = function(params) {
// TODO: flesh out validation?
my.width = new_data.boardsize.width;
my.height = new_data.boardsize.height;
return true;
};
my.setup_robot = function(){
var robot = my.get_robot_code();
if ('setup' in robot){
var map = {"width": my.width, "height": my.height};
var config = {
"stats": robot.setup(map),
"id": my.id
};
// console.log(config);
if (!my.spectator) {
my.websocket.send(JSON.stringify(config));
}
}
};
my.update_robot = function(data, objects){
var robot = my.get_robot_code();
var map = {"width": my.width, "height": my.height, "obj": objects};
var instructions = null;
if ('update' in robot){
var dbg = {
"msg" : function(x, y, msg){
my.renderer.debug_label(x,y,msg);
},
"msg_screen" : function(x, y, msg){
my.renderer.debug_label_screen(x,y,msg);
},
"line" : function(x, y, x2, y2){
my.renderer.debug_line(x,y,x2,y2);
},
};
instructions = robot.update(data, map, dbg);
}
return instructions;
};
my.set_spectator = function(s){
my.spectator = s;
my.target=-1;
$('body').keypress(function(e){
if (my.renderer && my.renderer.canvas.clientWidth === 0){
// dont respond to key events when canvas is not in view
return;
}
if(e.which == 116){
// t
if (my.spectator){
my.target++;
if( my.target < my.all_bots.length){
console.log("Watching " + my.all_bots[my.target]);
my.renderer.set_focus_robot(my.all_bots[my.target]);
}
else {
my.target = -1;
my.renderer.set_focus_robot(null);
}
} else {
my.target++;
if( my.target < my.my_bots.length){
console.log(my.my_bots);
console.log(my.my_bots[my.target].id);
console.log("Watching " + my.my_bots[my.target].id);
my.renderer.set_focus_robot(my.my_bots[my.target].id);
}
else {
my.target = -1;
my.renderer.set_focus_robot(null);
}
}
}
});
};
my.set_server = function(server_name, game_id){
my.server = server_name;
my.game_id = game_id;
my.state = null;
if (my.websocket){
console.log("Switching Server: " + my.server);
my.websocket.close();
my.renderer.shutdown();
}
else{
console.log("Setting Server: " + my.server);
my.websocket = my.connect(my.server);
}
};
my.init = function(){
console.log("Welcome to GoRobots");
if (webgl){
my.renderer = gorobots_render_webgl();
}
else{
my.renderer = gorobots_render_canvas();
}
my.renderer.init("battlefield");
my.stats = new Stats();
my.stats.setMode(0); // 0: fps, 1: ms
// Align top-left
my.stats.domElement.style.position = 'absolute';
my.stats.domElement.style.left = '5px';
my.stats.domElement.style.top = '120px';
document.body.appendChild( my.stats.domElement );
$(my.stats.domElement).hide();
my.paused = false;
$('body').keypress(function(e){
// alert(e.which);
if(e.which == 112){
// p
my.paused = !my.paused;
}
if (my.renderer && my.renderer.canvas.clientWidth === 0){
// dont respond to key events when canvas is not in view
return;
}
if(e.which == 100){
// d
$(my.stats.domElement).toggle();
}
});
};
my.init();
return my;
};

View File

@ -0,0 +1,608 @@
var gorobots_render_webgl = function(){
var my = {};
var grey = "rgba(0, 0, 0, 0.5)";
var red = "rgba(200, 0, 0, 0.5)";
// Tracking last 50 frame lengths
var d = new Date();
my.delta_history = [];
my.last_frame_time = d.getTime();
my.delta = 0;
my.width = null;
my.height = null;
my.y_base = 16; // The top status bar
my.scene = null;
// Tracking the objects in the scene
my.robots = {};
my.objects = {};
my.dom_id = null;
my.target = null;
my.init = function(dom_id) {
my.dom_id = dom_id;
my.canvas = document.getElementById(dom_id);
$('body').keypress(function(e){
if (my.canvas.clientWidth === 0){
// dont respond to key events when canvas is not in view
return;
}
if(e.which == 118){
// v
my.currentcamera += 1;
if (my.currentcamera >= my.cameras.length) {
my.currentcamera = 0;
}
my.use_camera = my.cameras[my.currentcamera];
}
if (e.which == 45){
// -
if (my.use_camera == my.followcamera){
my.followcamera.fov = my.followcamera.fov - 5;
if (my.followcamera.fov < 5){
my.followcamera.fov = 5;
}
my.followcamera.updateProjectionMatrix();
}
if (my.use_camera == my.trackcamera){
my.track_area -= 10;
if (my.track_area < 50){
my.track_area = 50;
}
my.resize_view();
}
}
if (e.which == 61){
// =
if (my.use_camera == my.followcamera){
my.followcamera.fov = my.followcamera.fov + 5;
if (my.followcamera.fov > 120){
my.followcamera.fov = 120;
}
my.followcamera.updateProjectionMatrix();
}
if (my.use_camera == my.trackcamera){
my.track_area += 10;
if (my.track_area > 1000){
my.track_area = 1000;
}
my.resize_view();
}
}
if (e.which == 100){
// d
$(my.stats.domElement).toggle();
}
});
my.stats = new Stats();
my.stats.setMode(0); // 0: fps, 1: ms
// Align top-left
my.stats.domElement.style.position = 'absolute';
my.stats.domElement.style.left = '5px';
my.stats.domElement.style.top = '200px';
document.body.appendChild( my.stats.domElement );
$(my.stats.domElement).hide();
};
var random_color = function () {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.round(Math.random() * 15)];
}
return color;
};
my.setup_cameras = function(){
console.log("Setting up cameras");
my.canvas = document.getElementById(my.dom_id);
my.cameras = [];
var pos = new THREE.Vector3(my.width / 2, my.height / 2, 0);
var game_aspect = my.height / my.width;
var aspect = my.canvas.clientHeight / my.canvas.clientWidth;
var scale = game_aspect/aspect;
if (scale < 1){
var new_scale = (1/game_aspect) / (1/aspect);
var s = (my.height * new_scale) / 2;
my.camera = new THREE.OrthographicCamera( 0, my.width , s + (my.height/2), -(s - (my.height/2)), 0.1, 200 );
} else {
var s = (my.width * scale) / 2;
my.camera = new THREE.OrthographicCamera( -(s - (my.width/2)), s + (my.width/2), my.height, 0, 0.1, 200 );
}
my.camera.position.z = 99;
my.camera.updateProjectionMatrix();
my.cameras.push(my.camera);
var view_aspect = my.canvas.clientHeight / my.canvas.clientWidth;
my.track_area = 300;
my.trackcamera = new THREE.OrthographicCamera( -my.track_area, my.track_area, my.track_area * view_aspect, -my.track_area * view_aspect, -100, 200 );
my.trackcamera.position.z = 100;
my.trackcamera.updateProjectionMatrix();
my.cameras.push(my.trackcamera);
my.followcamera = new THREE.PerspectiveCamera( 35, my.canvas.clientWidth / my.canvas.clientHeight, 1, 10000 );
my.followcamera.position = new THREE.Vector3(my.width / 2, -my.height + (my.height/2), (my.height / 2) + 200);
my.followcamera.up = new THREE.Vector3(0,0,1);
my.followcamera.lookAt(pos);
my.followcamera.updateProjectionMatrix();
my.controls = new THREE.TrackballControls( my.followcamera );
my.cameras.push(my.followcamera);
};
my.setup_render_target = function(map_width, map_height, screen_width, screen_height){
if (my.renderer){
console.log("Renderer already initialized");
my.reset();
return;
}
my.turn = 0;
my.robots = {};
my.objects = {};
my.labels = [];
my.scene = new THREE.Scene();
my.quit = false;
my.width = screen_width;
my.height = screen_height;
my.renderer = new THREE.WebGLRenderer({"canvas": my.canvas, antialias: true });
my.setup_cameras();
my.floorobj = new THREE.CubeGeometry(my.width, my.height, 0.1);
var material = new THREE.MeshLambertMaterial( { color: random_color() } );
my.floor = new THREE.Mesh(my.floorobj, material);
my.floor.position.x = my.width / 2;
my.floor.position.y = my.height / 2 ;
my.floor.position.z = -2;
my.scene.add(my.floor);
// Basic primitives
my.grey_material = new THREE.MeshLambertMaterial( { color: "#999999", transparent: false } );
my.red_material = new THREE.MeshLambertMaterial( { color: "#ff0000", transparent: true } );
my.black_material = new THREE.MeshLambertMaterial( { color: "#000000", transparent: false } );
my.robot_geom = new THREE.CubeGeometry(5,5,5);
var loader = new THREE.OBJLoader();
loader.load( '/s/robot.obj', function ( object ) {
object.position.x = 100;
object.position.y = 100;
object.position.z = 1;
object.up = new THREE.Vector3(0,0,1);
object.scale.set(5,5,5);
my.robot_geom = object;
} );
my.bullet_geom = new THREE.CubeGeometry(3,3,3);
my.splosion_geom = new THREE.SphereGeometry(1,50,50);
var axis = new THREE.AxisHelper(100);
my.scene.add(axis);
my.wallobjx = new THREE.CubeGeometry(my.width, 3, 3);
my.wallobjy = new THREE.CubeGeometry(3, my.height, 3);
var material = new THREE.MeshLambertMaterial( { color: "#cccccc" } );
my.wall = new THREE.Mesh(my.wallobjx, material);
my.wall.position.x = my.width / 2;
my.scene.add(my.wall);
my.wall2 = new THREE.Mesh(my.wallobjx, material);
my.wall2.position.x = my.width / 2;
my.wall2.position.y = my.height ;
my.scene.add(my.wall2);
my.wall3 = new THREE.Mesh(my.wallobjy, material);
my.wall3.position.y = my.height / 2;
my.scene.add(my.wall3);
my.wall4 = new THREE.Mesh(my.wallobjy, material);
my.wall4.position.y = my.height / 2;
my.wall4.position.x = my.width ;
my.scene.add(my.wall4);
my.scene.add(my.camera);
var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.9 );
directionalLight.position.set( 0, 0, -1 );
my.scene.add( directionalLight );
var directionalLight2 = new THREE.DirectionalLight( 0xffffff, 0.9 );
directionalLight2.position.set( 0, 0.707, 0.707 );
my.scene.add( directionalLight2 );
var ambientLight = new THREE.AmbientLight(0x222222, 0.7);
my.scene.add(ambientLight);
my.currentcamera = 2;
my.use_camera = my.cameras[my.currentcamera];
var render = function render() {
my.stats.begin();
my.renderer.render(my.scene, my.use_camera);
my.update_labels();
if (my.controls){
my.controls.update();
}
if (!my.quit)
requestAnimationFrame(render);
my.stats.end();
};
render();
$(window).resize(function(){
my.resize_view();
});
my.resize_view();
};
my.resize_view = function(){
my.canvas.height = ($(window).height() - $(my.canvas).offset().top);
my.canvas.width = $(window).width();
var game_aspect = my.height / my.width;
var aspect = my.canvas.clientHeight / my.canvas.clientWidth;
var scale = game_aspect/aspect;
if (scale < 1){
var new_scale = (1/game_aspect) / (1/aspect);
var s = (my.height * new_scale) / 2;
my.camera.left = 0;
my.camera.right = my.width;
my.camera.top = s + (my.height/2);
my.camera.bottom = -(s - (my.height/2));
} else {
var s = (my.width * scale) / 2;
my.camera.left = -(s - (my.width/2));
my.camera.right = s + (my.width/2);
my.camera.top = my.height;
my.camera.bottom = 0;
}
my.camera.updateProjectionMatrix();
var view_aspect = my.canvas.clientHeight / my.canvas.clientWidth;
my.trackcamera.left = -my.track_area;
my.trackcamera.right = my.track_area;
my.trackcamera.top = my.track_area * view_aspect;
my.trackcamera.bottom = -my.track_area * view_aspect;
my.trackcamera.updateProjectionMatrix();
my.followcamera.aspect = my.canvas.clientWidth / my.canvas.clientHeight;
my.followcamera.updateProjectionMatrix();
my.use_camera = my.cameras[my.currentcamera];
my.renderer.context.viewport(0, 0, my.canvas.width, my.canvas.height);
};
my.begin_frame = function(turn){
my.turn = turn;
var d = new Date();
var time = d.getTime();
my.delta = time - my.last_frame_time;
my.delta_history.unshift(my.delta);
if (my.delta_history.length > 50){
my.delta_history.pop();
}
my.last_frame_time = time;
// Status Bar Text
var slowest = Math.max.apply(null, my.delta_history);
var debug_string = slowest + "ms";
};
my.render = function(draw_list){
var obj_count = 0;
for (var i in draw_list) {
var robot_data = draw_list[i];
var key, geometry, material, cube = null;
if (robot_data['type'] == 'object'){
key = robot_data['type'] + robot_data.bounds[0] + robot_data.bounds[1];
if (!(key in my.objects)){
obj_count++;
console.log(robot_data);
x = robot_data.bounds[0];
y = robot_data.bounds[1];
w = robot_data.bounds[2] - robot_data.bounds[0];
h = robot_data.bounds[3] - robot_data.bounds[1];
geometry = new THREE.CubeGeometry(w,h,5 + Math.round(Math.random() * 25));
var material = new THREE.MeshLambertMaterial( { color: random_color(), transparent: false } );
cube = new THREE.Mesh( geometry, material );
my.scene.add( cube );
cube.position.x = x + (w / 2);
cube.position.y = y + (h / 2);
cube.position.z = 0;
cube.turn = my.turn;
my.objects[key] = cube;
continue;
} else {
my.objects[key].turn = my.turn;
}
}
else{
key = robot_data['type'] + robot_data['id'];
if (!(key in my.robots)){
if (robot_data['type'] == 'bullet'){
material = my.black_material;
geometry = my.bullet_geom;
cube = new THREE.Mesh( geometry, material );
}
else if (robot_data['type'] == 'explosion'){
size = robot_data.radius;
material = my.red_material;
geometry = my.splosion_geom;
material.opacity = 0.8;
cube = new THREE.Mesh( geometry, material );
cube.scale.set(size, size, size);
}
else {
material = new THREE.MeshLambertMaterial( { color: robot_data['color'], transparent: false } );
geometry = my.robot_geom;
cube = geometry.clone();
cube.traverse(function ( geo ) {
geo.material = new THREE.MeshLambertMaterial( { color: robot_data['color'] } );
} );
my.make_bot_label(robot_data.id, robot_data.name, cube);
}
my.robots[key] = cube;
my.scene.add(cube);
cube.position.x = robot_data['position']['x'];
cube.position.y = robot_data['position']['y'];
cube.position.z = -1;
cube.turn = my.turn;
}
else{
my.robots[key].position.x = robot_data['position']['x'];
my.robots[key].position.y = robot_data['position']['y'];
my.robots[key].turn = my.turn;
if ("heading" in robot_data){
if (robot_data['heading']['x'] == 0 && robot_data['heading']['y'] == 0) {
robot_data['heading']['y'] = 1;
}
var tmp = new THREE.Vector3(
robot_data['heading']['x'] + robot_data.position.x,
robot_data['heading']['y'] + robot_data.position.y,
-1);
my.robots[key].up = new THREE.Vector3(0,0,1);
my.robots[key].lookAt( tmp );
}
if (robot_data.health){
var h = Math.round((robot_data.health / 200) * 100) + "%";
var x = "#health_" + robot_data.id;
$(x).css({
"width": h
});
}
}
}
}
// Worth noting this list contains robots, bullets and explosions
// it's badly named, I am sorry!
for (var q in my.robots){
if (my.turn - my.robots[q].turn > 1){
// find and remove label?
for (var w in my.labels){
if (my.labels[w].target == my.robots[q]){
my.labels[w].label.remove();
my.labels.splice(w, 1);
}
}
// Can I do this? I dont think so, we dont know
// if these things will ever be back
// my.robots[q].traverse(function ( geo ) {
// geo.visible = false;
// });
my.scene.remove(my.robots[q]);
delete my.robots[q];
}
}
for (var q in my.objects){
if (my.turn - my.objects[q].turn > 1){
my.objects[q].visible = false;
} else {
my.objects[q].visible = true;
}
}
for (var p in draw_list){
if (draw_list[p]['type'] == "robot"){
if (draw_list[p]['id'] == my.target){
var pos = new THREE.Vector3(draw_list[p].position.x, draw_list[p].position.y, 0);
my.followcamera.lookAt(pos);
my.controls.target = pos;
my.trackcamera.position.x = pos.x;
my.trackcamera.position.y = pos.y;
my.trackcamera.updateProjectionMatrix();
break;
}
}
}
};
var projector = new THREE.Projector();
my.update_labels = function(){
for (var i in my.labels){
var label = my.labels[i];
var screen_pos = label.target.position.clone();
var x = $(my.canvas).position();
if (my.canvas.clientWidth === 0){
$(label.label).css({
display: "none"});
} else {
projector.projectVector( screen_pos, my.use_camera );
if (screen_pos.x > 1 || screen_pos.x < -1 || screen_pos.y > 1 || screen_pos.y < -1){
$(label.label).css({
display: "none"});
continue;
}
var sx = (screen_pos.x * ((my.canvas.clientWidth)/2)) + ((my.canvas.clientWidth)/2) + x.left;
var sy = -(screen_pos.y * ((my.canvas.clientHeight)/2)) + ((my.canvas.clientHeight)/2) + x.top - 30;
$(label.label).css({
position: "absolute",
display:"block",
marginLeft: 0,
marginTop: 0,
top: sy,
left: sx});
}
}
};
my.make_bot_label = function(id, text, track_object){
var obj = $("<div class='label' style='display:none;'>" + text + "<div class='health' id='health_" + id + "'></div></div>");
$("body").append(obj);
my.labels.push({
label: obj,
target: track_object
});
};
my.debug_labels = [];
my.debug_lines = [];
my.clear_debug = function(){
for (var i in my.debug_labels){
my.debug_labels[i].remove();
}
my.debug_labels = [];
for (var i in my.debug_lines){
my.scene.remove(my.debug_lines[i]);
}
delete my.debug_lines;
my.debug_lines = [];
};
my.debug_label = function(x, y, msg){
if (my.canvas.clientWidth > 0){
var screen_pos = new THREE.Vector3(x, y, 0);
var canvas_pos = $(my.canvas).position();
projector.projectVector( screen_pos, my.use_camera );
if (screen_pos.x > 1 || screen_pos.x < -1 || screen_pos.y > 1 || screen_pos.y < -1){
return;
}
var sx = (screen_pos.x * ((my.canvas.clientWidth)/2)) + ((my.canvas.clientWidth)/2) + canvas_pos.left;
var sy = -(screen_pos.y * ((my.canvas.clientHeight)/2)) + ((my.canvas.clientHeight)/2) + canvas_pos.top;
var obj = $("<div class='label'>" + msg + "</div>");
$("body").append(obj);
$(obj).css({
position: "absolute",
marginLeft: 0,
marginTop: 0,
top: sy,
left: sx});
my.debug_labels.push(obj);
}
};
my.debug_label_screen = function(x, y, msg){
if (my.canvas.clientWidth > 0){
var canvas_pos = $(my.canvas).position();
var obj = $("<div class='label'>" + msg + "</div>");
$("body").append(obj);
$(obj).css({
position: "absolute",
marginLeft: 0,
marginTop: 0,
top: y + canvas_pos.top,
left: x + canvas_pos.left});
my.debug_labels.push(obj);
}
};
my.debug_line = function(x1, y1, x2, y2){
var material = new THREE.LineBasicMaterial({
color: 0x0000ff
});
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(x1, y1, 0));
geometry.vertices.push(new THREE.Vector3(x2, y2, 0));
var line = new THREE.Line(geometry, material);
my.scene.add(line);
my.debug_lines.push(line);
};
my.set_focus_robot = function(id){
my.target = id;
};
my.reset = function(){
for (var i in my.objects){
my.scene.remove(my.objects[i]);
}
delete my.objects;
my.objects = {};
for (i in my.robots){
my.scene.remove(my.robots[i]);
}
delete my.robots;
my.robots = {};
my.clear_debug();
for (i in my.labels){
my.labels[i].label.remove();
}
my.labels = [];
};
my.end_frame = function(){
};
my.shutdown = function(){
my.reset();
my.quit = true;
};
return my;
};

6
ui/js/stats.min.js vendored Normal file
View File

@ -0,0 +1,6 @@
// stats.js - http://github.com/mrdoob/stats.js
var Stats=function(){var l=Date.now(),m=l,g=0,n=Infinity,o=0,h=0,p=Infinity,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";
i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div");
k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display=
"block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{REVISION:11,domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height=
a+"px",m=b,r=0);return b},update:function(){l=this.end()}}};

705
ui/js/three.min.js vendored Normal file

File diff suppressed because one or more lines are too long

774
ui/js/vec2d.js Normal file
View File

@ -0,0 +1,774 @@
// Copyright (c) 2013 Evan Shortiss
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
(function() {
// Default is to not use objects
var useObjects = false;
// Default is to use standard array
var AxesArray = Array;
/**
* Parse the arguments passed into Vec2D and Vec3D.
* @param {Mixed} args The argument array from caller function.
* @return {Array} The resulting axes array.
*/
function parseArgs(args) {
// User passed x and y
if (args.length === 2) {
return new Array(args[0], args[1]);
}
// User passed an array
else if (Object.prototype.toString.call(args[0]) === '[object Array]') {
return args[0];
}
// User passed x and y in an object
else if (typeof(args[0]) === 'object') {
return [args[0].x, args[0].y];
} else {
return [0, 0];
}
}
/**
* Check is the passed param a Vec2D instance.
* @param {Mixed} v The var to check.
* @return {Boolean} The result of check.
*/
function isVector(v) {
return v instanceof Vector || v instanceof ObjectVector;
};
/****************************************************************************
* Main array based vector class. Has instance methods attached to prototype.
* @constructor
* @param {Array} axes
****************************************************************************/
var Vector = function(axes) {
// Constructor for each array type is different. (Float32Array vs Array)
if (AxesArray.name === 'Float32Array') {
this._axes = new AxesArray([axes[0], axes[1]]);
} else {
this._axes = new AxesArray(axes[0], axes[1]);
}
};
Vector.prototype = {
/**
* Set both x and y
* @param x New x val
* @param y New y val
*/
setAxes: function(x, y) {
this._axes[0] = x;
this._axes[1] = y;
},
/**
* Getter for x axis.
* @return {Number}
*/
getX: function() {
return this._axes[0];
},
/**
* Setter for x axis.
*/
setX: function(x) {
this._axes[0] = x;
},
/**
* Getter for y axis.
* @return {Number}
*/
getY: function() {
return this._axes[1];
},
/**
* Setter for y axis.
*/
setY: function(y) {
this._axes[1] = y;
},
/**
* View vector as a string such as "Vec2D: (0, 4)"
* @param {Boolean}
* @return {String}
*/
toString: function(round) {
if (round) {
return '(' + Math.round(this.getX()) + ', ' + Math.round(this.getY()) + ')'
}
return '(' + this.getX() + ', ' + this.getY() + ')'
},
/**
* Return an array containing the vector axes.
* @return {Array}
*/
toArray: function() {
return new Array(this.getX(), this.getY());
},
/**
* Return an array containing the vector axes.
* @return {Object}
*/
toObject: function() {
return {
"x": this.getX(),
"y": this.getY()
};
},
/**
* Add the provided Vector to this one.
* @param {Vector} vec
*/
add: function(vec) {
this._axes[0] += vec._axes[0];
this._axes[1] += vec._axes[1];
return this;
},
/**
* Subtract the provided vector from this one.
* @param {Vector} vec
*/
subtract: function(vec) {
this._axes[0] -= vec._axes[0];
this._axes[1] -= vec._axes[1];
return this;
},
/**
* Check is the vector provided equal to this one.
* @param {Vec2D} vec
* @return {Boolean}
*/
equals: function(vec) {
return (vec._axes[0] == this._axes[0] && vec._axes[1] == this._axes[1]);
},
/**
* Multiply this vector by the provided vector.
* @param {Vector} vec
*/
multiplyByVector: function(vec) {
this._axes[0] *= vec._axes[0];
this._axes[1] *= vec._axes[1];
return this;
},
/**
* Multiply this vector by the provided vector.
* @param {Vector} vec
*/
divideByVector: function(vec) {
this._axes[0] /= vec._axes[0];
this._axes[1] /= vec._axes[1];
return this;
},
/**
* Multiply this vector by the provided number
* @param {Number} n
*/
multiplyByScalar: function(n) {
this._axes[0] *= n;
this._axes[1] *= n;
return this;
},
/**
* Divive this vector by the provided number
* @param {Number} n
*/
divideByScalar: function(n) {
this._axes[0] /= n;
this._axes[1] /= n;
return this;
},
/**
* Normalise this vector. Directly affects this vector.
* Use Vec2D.normalise(vector) to create a normalised clone of this.
*/
normalise: function() {
return this.multiplyByScalar(1 / this.magnitude());
},
/**
* For American spelling.
* Same as unit/normalise function.
*/
normalize: function() {
return this.normalise();
},
/**
* The same as normalise.
*/
unit: function() {
return this.normalise();
},
/**
* Return the magnitude (length) of this vector.
* @return {Number}
*/
magnitude: function() {
return Math.sqrt((this._axes[0] * this._axes[0]) + (this._axes[1] * this._axes[1]));
},
/**
* Return the magnitude (length) of this vector.
* @return {Number}
*/
length: function() {
return this.magnitude();
},
/**
* Get the dot product of this vector by another.
* @param {Vector} vec
* @return {Number}
*/
dot: function(vec) {
return (vec._axes[0] * this._axes[0]) + (vec._axes[1] * this._axes[1]);
},
/**
* Get the cross product of this vector by another.
* @param {Vector} vec
* @return {Number}
*/
cross: function(vec) {
return ((this._axes[0] * vec._axes[1]) - (this._axes[1] * vec._axes[0]));
},
/**
* Returns the reverse of the provided vector.
* @param {Vector} vec
*/
reverse: function(vec) {
this._axes[0] = -this._axes[0];
this._axes[1] = -this._axes[1];
return this;
},
/**
* Create a copy of this vector.
* @return {Vector}
*/
clone: function() {
return new Vector([this._axes[0], this._axes[1]]);
}
};
/****************************************************************************
* Main object based vector class. Has instance methods attached to prototype.
* @constructor
* @param {Array} axes
****************************************************************************/
var ObjectVector = function(axes) {
// Constructor for each array type is different. (Float32Array vs Array)
this._axes = {
x: axes[0],
y: axes[1]
};
};
ObjectVector.prototype = {
/**
* Set both x and y
* @param x New x val
* @param y New y val
*/
setAxes: function(x, y) {
this._axes.x = x;
this._axes.y = y;
},
/**
* Getter for x axis.
* @return {Number}
*/
getX: function() {
return this._axes.x;
},
/**
* Setter for x axis.
*/
setX: function(x) {
this._axes.x = x;
},
/**
* Getter for y axis.
* @return {Number}
*/
getY: function() {
return this._axes.y;
},
/**
* Setter for y axis.
*/
setY: function(y) {
this._axes.y = y;
},
/**
* View vector as a string such as "Vec2D: (0, 4)"
* @param {Boolean}
* @return {String}
*/
toString: function(round) {
if (round) {
return '(' + Math.round(this.getX()) + ', ' + Math.round(this.getY()) + ')'
}
return '(' + this.getX() + ', ' + this.getY() + ')'
},
/**
* Return an array containing the vector axes.
* @return {Array}
*/
toArray: function() {
return new Array(this.getX(), this.getY());
},
/**
* Return an array containing the vector axes.
* @return {Object}
*/
toObject: function() {
return {
"x": this.getX(),
"y": this.getY()
};
},
/**
* Add the provided Vector to this one.
* @param {Vector} vec
*/
add: function(vec) {
this._axes.x += vec._axes.x;
this._axes.y += vec._axes.y;
return this;
},
/**
* Subtract the provided vector from this one.
* @param {Vector} vec
*/
subtract: function(vec) {
this._axes.x -= vec._axes.x;
this._axes.y -= vec._axes.y;
return this;
},
/**
* Check is the vector provided equal to this one.
* @param {Vec2D} vec
* @return {Boolean}
*/
equals: function(vec) {
return (vec._axes.x == this._axes.x && vec._axes.y == this._axes.y);
},
/**
* Multiply this vector by the provided vector.
* @param {Vector} vec
*/
multiplyByVector: function(vec) {
this._axes.x *= vec._axes.x;
this._axes.y *= vec._axes.y;
return this;
},
/**
* Multiply this vector by the provided vector.
* @param {Vector} vec
*/
divideByVector: function(vec) {
this._axes.x /= vec._axes.x;
this._axes.y /= vec._axes.y;
return this;
},
/**
* Multiply this vector by the provided number
* @param {Number} n
*/
multiplyByScalar: function(n) {
this._axes.x *= n;
this._axes.y *= n;
return this;
},
/**
* Divive this vector by the provided number
* @param {Number} n
*/
divideByScalar: function(n) {
this._axes.x /= n;
this._axes.y /= n;
return this;
},
/**
* Normalise this vector. Directly affects this vector.
* Use Vec2D.normalise(vector) to create a normalised clone of this.
*/
normalise: function() {
return this.multiplyByScalar(1 / this.magnitude());
},
/**
* For American spelling.
* Same as unit/normalise function.
*/
normalize: function() {
return this.normalise();
},
/**
* The same as normalise.
*/
unit: function() {
return this.normalise();
},
/**
* Return the magnitude (length) of this vector.
* @return {Number}
*/
magnitude: function() {
return Math.sqrt((this._axes.x * this._axes.x) + (this._axes.y * this._axes.y));
},
/**
* Return the magnitude (length) of this vector.
* @return {Number}
*/
length: function() {
return this.magnitude();
},
/**
* Get the dot product of this vector by another.
* @param {Vector} vec
* @return {Number}
*/
dot: function(vec) {
return (vec._axes.x * this._axes.x) + (vec._axes.y * this._axes.y);
},
/**
* Get the cross product of this vector by another.
* @param {Vector} vec
* @return {Number}
*/
cross: function(vec) {
return ((this._axes.x * vec._axes.y) - (this._axes.y * vec._axes.x));
},
/**
* Returns the reverse of the provided vector.
* @param {Vector} vec
*/
reverse: function(vec) {
this._axes.x = -this._axes.x;
this._axes.y = -this._axes.y;
return this;
},
/**
* Create a copy of this vector.
* @return {Vector}
*/
clone: function() {
return new ObjectVector([this._axes.x, this._axes.y]);
}
};
/****************************************************************************
Publically exposed Vector interface.
****************************************************************************/
var Vec2D = function() {
};
Vec2D.prototype = {
Vector: Vector,
ObjectVector: ObjectVector,
/**
* Create a new Vector.
* @param {Number} [x]
* @param {Number} [y]
* @param {Object} [axes]
* @param {Array} [axes]
*/
create: function() {
if(useObjects) {
return new this.ObjectVector(parseArgs(arguments));
}
return new this.Vector(parseArgs(arguments));
},
/**
* Instruct the library to use standard JavaScript arrays.
* Otherwise the library will try use Float32Arrays if available. (This is default)
*/
useStandardArrays: function() {
useObjects = false;
AxesArray = Array;
},
/**
* Instruct the library to use Float32 JavaScript arrays. (This is default)
* Otherwise the library will use standard array.
*/
useFloat32Arrays: function() {
useObjects = false;
AxesArray = Float32Array;
},
/**
* Instruct library to use Objects to represent vectors.
*/
useObjects: function() {
useObjects = true;
},
/**
* Add v0 to v1 to produce a new vector
* @param {Vector} v0
* @param {Vector} v1
* @return {Vector}
*/
add: function(v0, v1) {
return this.create(v0.getX() + v1.getX(), v0.getY() + v1.getY())
},
/**
* Subtract v0 from v1 to produce a new Vector.
* @param {Vector} v0
* @param {Vector} v1
* @return {Vector}
*/
subtract: function(v0, v1) {
return this.create(v0.getX() - v1.getX(), v0.getY()- v1.getY())
},
/**
* Check are the provided vectors equal.
* @param {Vector} v0
* @param {Vector} v1
* @return {Boolean}
*/
equals: function(v0, v1) {
return v0.equals(v1);
},
/**
* Multiply a vector by a vector to produce a new Vector
* @param {Vector} v0
* @param {Vector} v1
* @return {Vector}
*/
vectorTimesVector: function(v0, v1) {
return this.create(v0.getX() * v1.getX(), v0.getY() * v1.getY());
},
/**
* Multiply a vector by a number to produce a new vector.
* @param {Vector} vec The var to store a result in.
* @param {Number} n The Vector to subtract from this one.
* @return {Vector}
*/
vectorTimesScalar: function(vec, n) {
return this.create(vec.getX() * n, vec.getY() * n);
},
/**
* Return a normalised version of provided vector.
* @param {Vector} vec
* @return {Vector}
*/
nomalise: function(vec) {
return this.timesScalar(vec, 1 / vec.magnitude());
},
/**
* Same as normalise
*/
normalize: function(vec) {
return this.normalise(vec);
},
/**
* Same as normalise.
*/
unit: function(vec) {
return this.normalise(vec);
},
/**
* Return the dot product of two vectors or this vector dot another.
* @param {Vector} v0
* @param {Vector} v1
* @return {Number}
*/
dot: function(v0, v1) {
return (v0.getX() * v1.getX()) + (v0.getY() * v1.getY());
},
/**
* Calculate the cross product of two vectors.
* @param {Vector} v0
* @param {Vector} v1
* @param {Vector}
*/
cross: function(v0, v1) {
return ((v0.getX() * v1.getY()) - (v0.getY() * v1.getX()));
},
/**
* Return the magnitude (length) of a vector.
* @param {Vector} vec
* @return {Number}
*/
magnitude: function(vec) {
return vec.magnitude();
},
/**
* Same as the magnitude.
*/
length: function(vec) {
return vec.magnitude();
},
/**
* Find sqaure distance between two vectors.
* @param {Vector} v0
* @param {Vector} v1
* @return {Number}
*/
distance: function(v0, v1) {
return Math.sqrt((v0.getX() - v1.getX()) * (v0.getX() - v1.getX()) + (v0.getY() - v1.getY()) * (v0.getY() - v1.getY()))
},
/**
* Returns the reverse of the provided vector.
* @param {Vector} vec
* @return {Vector}
*/
reverse: function(vec) {
return this.create(-vec.getX(), -vec.getY());
}
};
// Expose publically
if (typeof window !== 'undefined') {
window.Vec2D = new Vec2D();
} else {
module.exports = new Vec2D();
}
})();