var ktk = ktk || {}; /* rip popular js libs */ ktk.pipes = (function() { /**** types ****/ var T = { Object: function() { this.set = function(key, value) { this._[key] = value; } this.get = function(key) { return this._[key]; } this.has = function(key) { return (typeof this._[key] === undefined || this._[key] === null) ? false : true } }, Pipe: function() { T.Object.call(this); /*** private ***/ var _ = { x : 0, y : 0, absolute_x: 0, absolute_y: 0, angle : 0, name : '', absolute_angle: 0, rotation_force: 0, // force in angles per gametick rotation_velocity: 0, // current rotational velocity rotation_drag: .05, // .05 angles per gametick(16ms) origin_x : 0, origin_y : 0, pivot_x : 0, pivot_y : 0, total_origin_x: 0, total_origin_y: 0, image : new Image(), children : [], parent : null }; this._ = _; _.image.src = "img/pipe.png"; /*** public ***/ this.onUpdate = function(delta) { this.set('angle', this.get('angle')+this.get('rotation_force')); var x = 0, y = 0; var ox = 0, oy = 0; if (this.has('parent')) { this.set('absolute_x', this.get('x') + this.get('parent').get('absolute_x') + this.get('parent').get('pivot_x')); this.set('absolute_y', this.get('y') + this.get('parent').get('absolute_y') + this.get('parent').get('pivot_y')); this.set('absolute_angle', this.get('angle') + this.get('parent').get('absolute_angle')); } else { this.set('absolute_x', this.get('x')); this.set('absolute_y', this.get('y')); this.set('absolute_angle', this.get('angle')); } var xy; if (this.has('parent')) { xy = $.rotatePoint(this.get('absolute_x')-this.get('origin_x'), this.get('absolute_y')-this.get('origin_y'), this.get('parent').get('absolute_x')+this.get('parent').get('pivot_x'), this.get('parent').get('absolute_y')+this.get('parent').get('pivot_y'), this.get('angle')); } else { xy = [this.get('absolute_x'), this.get('absolute_y')]; } this.set('absolute_x', xy[0]); this.set('absolute_y', xy[1]); // Update our children for (var child_index = 0; child_index < _.children.length; child_index++) { _.children[child_index].onUpdate(delta); } }; this.onRender = function() { $.getContext().save(); $.getContext().translate(this.get('absolute_x'), this.get('absolute_y')); $.getContext().rotate(this.get('absolute_angle') * Math.PI / 180); $.getContext().drawImage(_.image, 0, 0); if (this.get('name') !== '') { $.getContext().fillText(this.get('name')+this.get('absolute_angle'), 0, 0); } $.getContext().restore(); // Render our children for (var child_index = 0; child_index < _.children.length; child_index++) { _.children[child_index].onRender(); } }; /* hierarchy methods */ this.disown = function(object) { var childIndex = _.children.indexOf(object); if (childIndex >= 0) { _.children.splice(childIndex, 1); object.set('parent', null); return true; } return false; } this.attach = function(object) { if (object.has('parent')) { object.get('parent').disown(object); } object.set('parent', this); _.children.push(object); } } }; /**** private ****/ var _ = { objects: [], accumulator: 0, curr_timestamp: new Date(), last_timestamp: new Date(), last_timeout: -1, curr_delta: 0, last_delta: 0, last_rendertimestamp: 0, doUpdateLoop: function() { _.last_timestamp = _.curr_timestamp; _.curr_timestamp = new Date(); _.curr_delta = _.curr_timestamp - _.last_timestamp; _.accumulator += _.curr_delta; var thisFrameStart = new Date(); while (_.accumulator >= $.tickrate) { _.onUpdate($.tickrate); _.accumulator -= $.tickrate; } var thisFrameEnd = new Date(); var thisFrameDelta = _.last_delta = thisFrameEnd - thisFrameStart; var delay = $.tickrate - _.accumulator; _.last_timeout = window.setTimeout(_.doUpdateLoop, (delay >= 0 ? delay : 0)); }, /* Called whenever the physics or state should be updated */ onUpdate: function(delta) { for (var i = 0; i < _.objects.length; i++) { _.objects[i].onUpdate(delta); } }, /* Called whenever possible to re-render the scene */ onRender: function(ts) { // ts = timestamp var delta_rendertimestamp = ts - _.last_rendertimestamp; // Clear our screen _.ctx.clearRect(0, 0, _.canvas.width, _.canvas.height); _.ctx.fillText("frametime: " + _.last_delta, 0, 10); _.ctx.fillText("fpms: " + delta_rendertimestamp, 0, 20); // Do our rendering. for (var i = 0; i < _.objects.length; i++) { _.objects[i].onRender(); } _.last_rendertimestamp = ts; // Continue our loop window.requestAnimationFrame(_.onRender); } }; /**** public ****/ var $ = { tickrate: 16, // 16 updates per second /* Initialization method, called externally to set up everything */ onLoad: function(el) { if (el.tagName !== 'CANVAS') { alert('target element _must_ be a canvas'); throw(el); } _.canvas = el; _.ctx = el.getContext("2d"); _.ctx.mozImageSmoothingEnabled = false; _.ctx.webkitImageSmoothingEnabled = false; _.ctx.msImageSmoothingEnabled = false; _.ctx.imageSmoothingEnabled = false; $.setupPlayfield(); _.doUpdateLoop(); _.onRender(); }, createObject: function(object_name) { if (!T[object_name]) { throw object_name+" is not a valid object."; } return new T[object_name]; }, addObject: function(object) { _.objects.push(object); }, /* Called to setup our playfield. public to allow overriding. */ setupPlayfield: function() { let obj = $.createObject("Pipe"); //obj.set('rotation_force', .25); obj.set('x', _.canvas.width/2); obj.set('y', _.canvas.height/2); obj.set('name', 'A'); obj.set('origin_x', 4); obj.set('origin_y', 8); obj.set('pivot_x', 56); obj.set('pivot_y', 4); let obj2 = $.createObject("Pipe"); obj2.set('rotation_force', .5); obj2.set('angle', 0); obj2.set('name', 'B'); obj2.set('origin_x', 4); obj2.set('origin_y', 8); obj.attach(obj2); $.addObject(obj); }, getContext: function() { return _.ctx; }, rotatePoint: function(ox, oy, px, py, angle) { var c = Math.cos(angle * Math.PI / 180); var s = Math.sin(angle * Math.PI / 180); ox -= px; oy -= py; var x = ox * c - oy * s; var y = ox * s + oy * c; ox = x + px; oy = y + py; /*ox = c * (ox - px) - s * (oy - py) + px; oy = s * (ox - px) + c * (oy - py) + py;*/ return [ox, oy]; } }; return $; })();