|
var Matrix = require('matrix-utilities') |
|
|
|
/* scene.create(options: { |
|
* // name of scene; scene may be accessed via exports[name] |
|
* // required, must be unique |
|
* name: 'funtime' |
|
* |
|
* // optional object that gets appended to exports[name] |
|
* , data: { whatever: 'you want' } |
|
* |
|
* // view and world entities |
|
* // optional, set to pos:0,0,0 rot:0,0,0 by default |
|
* , view: { pos: [x,y,z], rot: [x,y,z] } |
|
* , world: { pos: [x,y,z], rot: [x,y,z] } |
|
* |
|
* // the entities drawn to 2d canvas by default draw method |
|
* // drawn from 0 to entities.length ordering (bottom to top) |
|
* // optional |
|
* , entities: [ |
|
* // a 3d entity |
|
* { |
|
* verts: [[x,y,z]...] |
|
* , fill: '#c01045' // optional |
|
* , stroke: '#420666' // optional |
|
* } |
|
* |
|
* // a 2d entity |
|
* , { |
|
* verts: [[x,y]...] // screen space coordinates |
|
* , fill: 'blue' // optional |
|
* , stroke: '#f00' // optional |
|
* } |
|
* |
|
* // a text entity |
|
* , { |
|
* text: 'the text for the entity' |
|
* , pos: [x,y] |
|
* , max: 130 // max width in pixels |
|
* , font: '12px serif' // optional |
|
* , fill: '#aaaaaa' // optional |
|
* , stroke: '#800813' // optional |
|
* } |
|
* ] |
|
* |
|
* // default fill, stroke and font for entities, when not provided |
|
* // optional; if fill and stroke are set, will fill and stroke for ALL elements |
|
* , fill: '#123456' |
|
* , stroke: 'black' |
|
* , font: '69px comicsans' |
|
* |
|
* // game logic update function |
|
* // optional, doesn't emit by default |
|
* , update: function () {} |
|
* |
|
* // drawing function |
|
* // if you override this default, entities don't render automatically |
|
* // optional, doesn't emit by default |
|
* , draw: function () {} |
|
* |
|
* // utility functions I use for keyboard events |
|
* // optional, do not emit by default |
|
* , enter: function () {} |
|
* , esc: function () {} |
|
* , up: function () {} |
|
* , down: function () {} |
|
* , right: function () {} |
|
* , left: function () {} |
|
* }) |
|
* |
|
*/ |
|
exports.create = function (opts) { |
|
if (!opts || opts === undefined || opts.name === undefined) |
|
return console.log('Please supply a name for this scene!') |
|
if (exports[opts.name] !== undefined) |
|
return console.log('Please select a different name for scene ' + opts.name) |
|
|
|
opts.view = opts.view !== undefined ? opts.view : { pos: [0,0,0], rot: [0,0,0] } |
|
opts.view.pos = opts.view.pos !== undefined ? opts.view.pos : [0,0,0] |
|
opts.view.rot = opts.view.rot !== undefined ? opts.view.rot : [0,0,0] |
|
opts.view.mat = updateView(opts.view) |
|
|
|
opts.world = opts.world !== undefined ? opts.world : { pos: [0,0,0], rot: [0,0,0]} |
|
opts.world.pos = opts.world.pos !== undefined ? opts.world.pos : [0,0,0] |
|
opts.world.rot = opts.world.rot !== undefined ? opts.world.rot : [0,0,0] |
|
opts.world.mat = updateWorld(opts.world) |
|
|
|
exports[opts.name] = { |
|
// game entities |
|
view: opts.view |
|
, world: opts.world |
|
|
|
// used by internal default draw to cache/check changes in matrix state |
|
, _view: opts.view |
|
, _world: opts.world |
|
, _global: Matrix.multiply(opts.world.mat, opts.view.mat) |
|
|
|
// array of 3d and 2d polys to render |
|
// only automatically used in default draw function |
|
// warning: does not do culling, only put things that you plan to |
|
// draw into the entities array |
|
, entities: opts.entities !== undefined ? opts.entities : [] |
|
|
|
// game loop event functions |
|
, update: opts.update !== undefined ? opts.update : function () {} |
|
, draw: opts.draw !== undefined ? opts.draw : function (context) { |
|
|
|
// check if view or world matrix has changed, if so update global matrix |
|
if (this._view !== this.view || this._world !== this.world) { |
|
this.view.mat = this._view === this.view ? this.view : updateView(this.view) |
|
this._view = this.view |
|
this.world.mat = this._world === this.world ? this.world : updateWorld(this.world) |
|
this._world = this.world |
|
this._global = Matrix.multiply(this.world.mat, this.view.mat) |
|
} |
|
|
|
this._width = context.canvas.width / 2 |
|
this._height = context.canvas.height / 2 |
|
|
|
// render all entities. |
|
|
|
this.entities.map(function (entity) { |
|
|
|
context.font = entity.font !== undefined ? entity.font : this.font |
|
context.fillStyle = entity.fill !== undefined ? entity.fill : this.fill |
|
context.strokeStyle = entity.stroke !== undefined ? entity.stroke : this.stroke |
|
|
|
if (entity.verts !== undefined) { |
|
|
|
context.beginPath() |
|
|
|
// entity is already 2d poly, draw like normal poly |
|
if (entity.verts[0].length === 2) { |
|
context.moveTo(entity.verts[0][0], entity.verts[0][1]) |
|
entity.verts.map(function (vertex) { context.lineTo(vertex[0],vertex[1]) }) |
|
} |
|
|
|
// entity must be projected to 2d |
|
else if (entity.verts[0].length === 3) entity.verts.map(function (vertex, index) { |
|
|
|
// product of global matrix |
|
vertex = Matrix.multiply([[vertex[0],vertex[1],vertex[2],1]], this._global) |
|
|
|
// simple depth (divide depth into x/y) manip, then center elem on view |
|
vertex[0][0] = (vertex[0][0] / vertex[0][2]) * this._width + this._width |
|
vertex[0][1] = (vertex[0][1] / vertex[0][2]) * this._height + this._height |
|
|
|
// if this is the first vertex, move context drawing to current position |
|
if (index === 0) context.moveTo(vertex[0][0], vertex[0][1]) |
|
|
|
// move polydraw to next vertex position |
|
context.lineTo(vertex[0][0], vertex[0][1]) |
|
|
|
}) |
|
|
|
context.closePath() |
|
|
|
} else if (entity.text !== undefined) { |
|
// put the text on [0,0] if we're not told where to put it |
|
entity.pos = entity.pos !== undefined ? entity.pos : [0,0] |
|
context.fillText(entity.text, entity.pos[0], entity.pos[1], entity.max) |
|
} else { return } |
|
|
|
if (entity.fill !== undefined || this.fill !== undefined) context.fill() |
|
if (entity.stroke !== undefined || this.stroke !== undefined) context.stroke() |
|
}, this) |
|
} |
|
|
|
, fill: opts.fill // warning! will set auto fill for ALL entities |
|
, stroke: opts.stroke // ditto! |
|
, font: opts.font !== undefined ? opts.font : '12px serif' |
|
|
|
// keyboard event functions |
|
, enter: opts.enter !== undefined ? opts.enter : function () {} |
|
, esc: opts.esc !== undefined ? opts.esc : function () {} |
|
, up: opts.up !== undefined ? opts.up : function () {} |
|
, down: opts.down !== undefined ? opts.down : function () {} |
|
, left: opts.left !== undefined ? opts.left : function () {} |
|
, right : opts.right !== undefined ? opts.right : function () {} |
|
} |
|
|
|
if (opts.data !== undefined) |
|
for (var data in opts.data) |
|
if (opts.data.hasOwnProperty(data) && exports[opts.name][data] === undefined) |
|
exports[opts.name][data] = opts.data[data] |
|
} |
|
|
|
function updateView (view) { |
|
// get the sine and cosine of the negative of all angles |
|
var sin = [Math.sin(-view.rot[0]), Math.sin(-view.rot[1]), Math.sin(-view.rot[2])] |
|
, cos = [Math.cos(-view.rot[0]), Math.cos(-view.rot[1]), Math.cos(-view.rot[2])] |
|
|
|
// view matrix * translation matrix * rotz matrix * roty matrix * rotx matrix |
|
return Matrix.multiply(Matrix.multiply(Matrix.multiply(Matrix.multiply(Matrix.Identity(),[ |
|
[ 1 , 0 , 0 ,0] |
|
, [ 0 , 1 , 0 ,0] |
|
, [ 0 , 0 , 1 ,0] |
|
, [-view.pos[0],-view.pos[1],-view.pos[2],1] |
|
]), [ |
|
[ cos[2],sin[2],0,0] |
|
, [-sin[2],cos[2],0,0] |
|
, [ 0 , 0 ,1,0] |
|
, [ 0 , 0 ,0,1] |
|
]), [ |
|
[cos[1],0,-sin[1],0] |
|
, [ 0 ,1, 0 ,0] |
|
, [sin[1],0, cos[1],0] |
|
, [ 0 ,0, 0 ,1] |
|
]), [ |
|
[1, 0 , 0 ,0] |
|
, [0, cos[0],sin[0],0] |
|
, [0,-sin[0],cos[0],0] |
|
, [0, 0 , 0 ,1] |
|
]) |
|
} |
|
|
|
function updateWorld (world) { |
|
// get the sine and cosine of all angles |
|
var sin = [Math.sin(world.rot[0]), Math.sin(world.rot[1]), Math.sin(world.rot[2])] |
|
, cos = [Math.cos(world.rot[0]), Math.cos(world.rot[1]), Math.cos(world.rot[2])] |
|
|
|
// world matrix * rotx matrix * roty matrix * rotz matrix * translation matrix |
|
return Matrix.multiply(Matrix.multiply(Matrix.multiply(Matrix.multiply(Matrix.Identity(),[ |
|
[1, 0 , 0 ,0] |
|
, [0, cos[0],sin[0],0] |
|
, [0,-sin[0],cos[0],0] |
|
, [0, 0 , 0 ,1] |
|
]), [ |
|
[cos[1],0,-sin[1],0] |
|
, [ 0 ,1, 0 ,0] |
|
, [sin[1],0, cos[1],0] |
|
, [ 0 ,0, 0 ,1] |
|
]), [ |
|
[ cos[2],sin[2],0,0] |
|
, [-sin[2],cos[2],0,0] |
|
, [ 0 , 0 ,1,0] |
|
, [ 0 , 0 ,0,1] |
|
]), [ |
|
[ 1 , 0 , 0 ,0] |
|
, [ 0 , 1 , 0 ,0] |
|
, [ 0 , 0 , 1 ,0] |
|
, [world.pos[0],world.pos[1],world.pos[2],1] |
|
]) |
|
} |