UNPKG

zdog

Version:

Round, flat, designer-friendly pseudo-3D engine

249 lines (207 loc) 6.17 kB
/** * Anchor */ ( function( root, factory ) { // module definition if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( require('./boilerplate'), require('./vector'), require('./canvas-renderer'), require('./svg-renderer') ); } else { // browser global var Zdog = root.Zdog; Zdog.Anchor = factory( Zdog, Zdog.Vector, Zdog.CanvasRenderer, Zdog.SvgRenderer ); } }( this, function factory( utils, Vector, CanvasRenderer, SvgRenderer ) { var TAU = utils.TAU; var onePoint = { x: 1, y: 1, z: 1 }; function Anchor( options ) { this.create( options || {} ); } Anchor.prototype.create = function( options ) { this.children = []; // set defaults & options utils.extend( this, this.constructor.defaults ); this.setOptions( options ); // transform this.translate = new Vector( options.translate ); this.rotate = new Vector( options.rotate ); this.scale = new Vector( onePoint ).multiply( this.scale ); // origin this.origin = new Vector(); this.renderOrigin = new Vector(); if ( this.addTo ) { this.addTo.addChild( this ); } }; Anchor.defaults = {}; Anchor.optionKeys = Object.keys( Anchor.defaults ).concat([ 'rotate', 'translate', 'scale', 'addTo', ]); Anchor.prototype.setOptions = function( options ) { var optionKeys = this.constructor.optionKeys; for ( var key in options ) { if ( optionKeys.indexOf( key ) != -1 ) { this[ key ] = options[ key ]; } } }; Anchor.prototype.addChild = function( shape ) { if ( this.children.indexOf( shape ) != -1 ) { return; } shape.remove(); // remove previous parent shape.addTo = this; // keep parent reference this.children.push( shape ); }; Anchor.prototype.removeChild = function( shape ) { var index = this.children.indexOf( shape ); if ( index != -1 ) { this.children.splice( index, 1 ); } }; Anchor.prototype.remove = function() { if ( this.addTo ) { this.addTo.removeChild( this ); } }; // ----- update ----- // Anchor.prototype.update = function() { // update self this.reset(); // update children this.children.forEach( function( child ) { child.update(); } ); this.transform( this.translate, this.rotate, this.scale ); }; Anchor.prototype.reset = function() { this.renderOrigin.set( this.origin ); }; Anchor.prototype.transform = function( translation, rotation, scale ) { this.renderOrigin.transform( translation, rotation, scale ); // transform children this.children.forEach( function( child ) { child.transform( translation, rotation, scale ); } ); }; Anchor.prototype.updateGraph = function() { this.update(); this.updateFlatGraph(); this.flatGraph.forEach( function( item ) { item.updateSortValue(); } ); // z-sort this.flatGraph.sort( Anchor.shapeSorter ); }; Anchor.shapeSorter = function( a, b ) { return a.sortValue - b.sortValue; }; // custom getter to check for flatGraph before using it Object.defineProperty( Anchor.prototype, 'flatGraph', { get: function() { if ( !this._flatGraph ) { this.updateFlatGraph(); } return this._flatGraph; }, set: function( graph ) { this._flatGraph = graph; }, } ); Anchor.prototype.updateFlatGraph = function() { this.flatGraph = this.getFlatGraph(); }; // return Array of self & all child graph items Anchor.prototype.getFlatGraph = function() { var flatGraph = [ this ]; return this.addChildFlatGraph( flatGraph ); }; Anchor.prototype.addChildFlatGraph = function( flatGraph ) { this.children.forEach( function( child ) { var childFlatGraph = child.getFlatGraph(); Array.prototype.push.apply( flatGraph, childFlatGraph ); } ); return flatGraph; }; Anchor.prototype.updateSortValue = function() { this.sortValue = this.renderOrigin.z; }; // ----- render ----- // Anchor.prototype.render = function() {}; // TODO refactor out CanvasRenderer so its not a dependency within anchor.js Anchor.prototype.renderGraphCanvas = function( ctx ) { if ( !ctx ) { throw new Error( 'ctx is ' + ctx + '. ' + 'Canvas context required for render. Check .renderGraphCanvas( ctx ).' ); } this.flatGraph.forEach( function( item ) { item.render( ctx, CanvasRenderer ); } ); }; Anchor.prototype.renderGraphSvg = function( svg ) { if ( !svg ) { throw new Error( 'svg is ' + svg + '. ' + 'SVG required for render. Check .renderGraphSvg( svg ).' ); } this.flatGraph.forEach( function( item ) { item.render( svg, SvgRenderer ); } ); }; // ----- misc ----- // Anchor.prototype.copy = function( options ) { // copy options var itemOptions = {}; var optionKeys = this.constructor.optionKeys; optionKeys.forEach( function( key ) { itemOptions[ key ] = this[ key ]; }, this ); // add set options utils.extend( itemOptions, options ); var ItemClass = this.constructor; return new ItemClass( itemOptions ); }; Anchor.prototype.copyGraph = function( options ) { var clone = this.copy( options ); this.children.forEach( function( child ) { child.copyGraph({ addTo: clone, }); } ); return clone; }; Anchor.prototype.normalizeRotate = function() { this.rotate.x = utils.modulo( this.rotate.x, TAU ); this.rotate.y = utils.modulo( this.rotate.y, TAU ); this.rotate.z = utils.modulo( this.rotate.z, TAU ); }; // ----- subclass ----- // function getSubclass( Super ) { return function( defaults ) { // create constructor function Item( options ) { this.create( options || {} ); } Item.prototype = Object.create( Super.prototype ); Item.prototype.constructor = Item; Item.defaults = utils.extend( {}, Super.defaults ); utils.extend( Item.defaults, defaults ); // create optionKeys Item.optionKeys = Super.optionKeys.slice( 0 ); // add defaults keys to optionKeys, dedupe Object.keys( Item.defaults ).forEach( function( key ) { if ( !Item.optionKeys.indexOf( key ) != 1 ) { Item.optionKeys.push( key ); } } ); Item.subclass = getSubclass( Item ); return Item; }; } Anchor.subclass = getSubclass( Anchor ); return Anchor; } ) );