summaryrefslogtreecommitdiff
path: root/assets/javascripts/mx/mx.js
diff options
context:
space:
mode:
Diffstat (limited to 'assets/javascripts/mx/mx.js')
-rw-r--r--assets/javascripts/mx/mx.js582
1 files changed, 582 insertions, 0 deletions
diff --git a/assets/javascripts/mx/mx.js b/assets/javascripts/mx/mx.js
new file mode 100644
index 0000000..b7d0bca
--- /dev/null
+++ b/assets/javascripts/mx/mx.js
@@ -0,0 +1,582 @@
+/**
+ * Copyright (C) 2013 by Evan You
+ *
+ * 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.
+ */
+
+var MX = MX || (function (undefined) {
+
+ var MX = {
+ version: '0.1.0',
+ prefix: undefined,
+ rotationUnit: 'rad'
+ }
+
+ var floatPrecision = 5
+
+ // ========================================================================
+ // Setup & Compatibility
+ // ========================================================================
+
+ var transformProp,
+ transitionProp,
+ transformOriginProp,
+ transformStyleProp,
+ perspectiveProp,
+ transitionEndEvent
+
+ var positionAtCenter = true, // whether to auto center objects
+ centeringCSS // styles to inject for center positioning
+
+ document.addEventListener('DOMContentLoaded', setup)
+
+ function setup () {
+
+ // sniff prefix
+
+ var s = document.body.style
+
+ MX.prefix =
+ 'webkitTransform' in s ? 'webkit' :
+ 'mozTransform' in s ? 'moz' :
+ 'msTransform' in s ? 'ms' : ''
+
+ transformProp = MX.transformProp = addPrefix('transform')
+ transitionProp = MX.transitionProp = addPrefix('transition')
+ transformOriginProp = MX.transformOriginProp = addPrefix('transformOrigin')
+ transformStyleProp = MX.transformStyleProp = addPrefix('transformStyle')
+ perspectiveProp = MX.perspectiveProp = addPrefix('perspective')
+ transitionEndEvent = MX.transitionEndEvent = MX.prefix === 'webkit' ? 'webkitTransitionEnd' : 'transitionend'
+
+ // shiv rAF
+
+ var vendors = ['webkit', 'moz', 'ms']
+ for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+ window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']
+ window.cancelAnimationFrame =
+ window[vendors[x]+'CancelAnimationFrame'] ||
+ window[vendors[x]+'CancelRequestAnimationFrame']
+ }
+
+ // inject centering css
+
+ centeringCSS = document.createElement('style')
+ centeringCSS.type = 'text/css'
+ centeringCSS.innerHTML =
+ '.mx-object3d {'
+ + 'position: absolute;'
+ + 'top: 50%;'
+ + 'left: 50%;}'
+ injectCenteringCSS()
+
+ window.scrollTo(0,0)
+ }
+
+ function injectCenteringCSS () {
+ document.head.appendChild(centeringCSS)
+ }
+
+ function removeCenteringCSS () {
+ document.head.removeChild(centeringCSS)
+ }
+
+ // ========================================================================
+ // Utils
+ // ========================================================================
+
+ function toDeg (rad) {
+ return rad / Math.PI * 180
+ }
+
+ function toRad (deg) {
+ return deg / 180 * Math.PI
+ }
+
+ function buildRotationTranslation (obj) {
+
+ // used when rotationOrigin is set
+
+ var origin = obj.rotationOrigin
+ if (!origin) {
+ return
+ } else {
+ var dx = origin.x - obj.x,
+ dy = -(origin.y - obj.y),
+ dz = -(origin.z - obj.z)
+ return {
+ before: 'translate3d(' + dx.toFixed(floatPrecision) +'px,' + dy.toFixed(floatPrecision) + 'px,' + dz.toFixed(floatPrecision) + 'px) ',
+ after: 'translate3d(' + (-dx).toFixed(floatPrecision) + 'px,' + (-dy).toFixed(floatPrecision) + 'px,' + (-dz).toFixed(floatPrecision) + 'px) '
+ }
+ }
+ }
+
+ function addPrefix (string) {
+ if (MX.prefix) {
+ string = MX.prefix + string.charAt(0).toUpperCase() + string.slice(1)
+ }
+ return string
+ }
+
+ // ========================================================================
+ // Base Object3D
+ // ========================================================================
+
+ function Object3D (el) {
+
+ this.setupDomElement(el)
+ this.setCSSTransformStyle('preserve-3d')
+ this.el.classList.add('mx-object3d')
+
+ this.parent = undefined
+ this.children = []
+ this.updateChildren = true
+
+ this.inverseLookAt = false
+
+ this.persisted = true
+
+ this.reset()
+
+// this.quaternion = new MX.Quaternion ()
+// this.quaternion._euler = this
+
+ var width, height,
+ self = this
+
+ Object.defineProperty(this, 'width', {
+ get: function () {
+ return width
+ || parseInt(self.el.style.width, 10)
+ || 0
+ },
+ set: function (val) {
+ width = val
+ this.el.style.width = width + 'px'
+ }
+ })
+
+ Object.defineProperty(this, 'height', {
+ get: function () {
+ return height
+ || parseInt(self.el.style.height, 10)
+ || 0
+ },
+ set: function (val) {
+ height = val
+ this.el.style.height = height + 'px'
+ }
+ })
+ }
+
+ Object3D.prototype = {
+
+ constructor: Object3D,
+
+ reset: function () {
+ this.x = this.__x = 0
+ this.y = this.__y = 0
+ this.z = this.__z = 0
+ this.rotationX = this.__rotationX = 0
+ this.rotationY = this.__rotationY = 0
+ this.rotationZ = this.__rotationZ = 0
+ this.scaleX = this.__scaleX = 1
+ this.scaleY = this.__scaleY = 1
+ this.scaleZ = this.__scaleZ = 1
+ this.scale = this.__scale = 1
+ this.perspective = this.__perspective = 0
+ this.rotationOrigin = undefined
+ this.followTarget = undefined
+// this.quaternion = new MX.Quaternion()
+ this.dirty = true
+ this.update()
+ },
+
+ setupDomElement: function (el) {
+ this.el = undefined
+ if (el instanceof HTMLElement) {
+ this.el = el
+ } else if (typeof el === 'string') {
+ var tag = el.match(/^[^.#\s]*/)[1],
+ id = el.match(/#[^.#\s]*/),
+ classes = el.match(/\.[^.#\s]*/g)
+ this.el = document.createElement(tag || 'div')
+ if (id) {
+ this.el.id = id[0].slice(1)
+ }
+ if (classes) {
+ var i = classes.length
+ while (i--) {
+ this.el.classList.add(classes[i].slice(1))
+ }
+ }
+ } else {
+ this.el = document.createElement('div')
+ }
+ },
+
+ update: function () {
+
+ if (this.updateChildren) {
+ var i = this.children.length
+ while (i--) {
+ this.children[i].update()
+ }
+ }
+
+ if (this.followTarget) {
+ this.lookAt(this.followTarget, false)
+ }
+
+ if (this.scaleX !== this.__scaleX ||
+ this.scaleY !== this.__scaleY ||
+ this.scaleZ !== this.__scaleZ) {
+ this.__scaleX = this.scaleX
+ this.__scaleY = this.scaleY
+ this.__scaleZ = this.scaleZ
+ this.dirty = true
+ }
+
+ if (this.scale !== this.__scale) {
+ this.scaleX =
+ this.scaleY =
+ this.scaleZ =
+ this.__scaleX =
+ this.__scaleY =
+ this.__scaleZ =
+ this.__scale =
+ this.scale
+ this.dirty = true
+ }
+
+ if (this.rotationX !== this.__rotationX ||
+ this.rotationY !== this.__rotationY ||
+ this.rotationZ !== this.__rotationZ) {
+ this.__rotationX = this.rotationX
+ this.__rotationY = this.rotationY
+ this.__rotationZ = this.rotationZ
+ this.dirty = true
+ }
+
+ if (this.x !== this.__x ||
+ this.y !== this.__y ||
+ this.z !== this.__z) {
+ this.__x = this.x
+ this.__y = this.y
+ this.__z = this.z
+ this.dirty = true
+ }
+
+ if (this.perspective !== this.__perspective) {
+ this.__perspective = this.perspective
+ this.dirty = true
+ }
+
+ if (this.dirty && this.el) {
+
+ var rotationTranslation = buildRotationTranslation(this),
+ rotation = 'rotateX(' + this.rotationX.toFixed(floatPrecision) + MX.rotationUnit + ') '
+ + 'rotateY(' + this.rotationY.toFixed(floatPrecision) + MX.rotationUnit + ') '
+ + 'rotateZ(' + this.rotationZ.toFixed(floatPrecision) + MX.rotationUnit + ') '
+
+ var transformString =
+ (MX.positionAtCenter ? 'translate3d(-50%, -50%, 0) ' : '')
+ + (this.perspective ? 'perspective(' + this.perspective + 'px) ' : '')
+ + 'translate3d('
+ + this.x.toFixed(floatPrecision || 0) + 'px,'
+ + (-this.y).toFixed(floatPrecision) + 'px,'
+ + (-this.z).toFixed(floatPrecision) + 'px) '
+ + 'scale3d('
+ + this.scaleX.toFixed(floatPrecision) + ','
+ + this.scaleY.toFixed(floatPrecision) + ','
+ + this.scaleZ.toFixed(floatPrecision) + ') '
+
+ if (rotationTranslation) {
+ transformString += rotationTranslation.before
+ + rotation
+ + rotationTranslation.after
+
+ } else {
+ transformString += rotation
+ }
+
+ this.el.style[transformProp] = transformString
+ this.dirty = false
+ }
+
+ return this
+
+ },
+
+ // taken from three.js
+ setFromQuaternion: function ( q, order, update ) {
+ // q is assumed to be normalized
+
+ // http://www.mathworks.com/matlabcentral/fileexchange/20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/content/SpinCalc.m
+
+ var sqx = q.x * q.x;
+ var sqy = q.y * q.y;
+ var sqz = q.z * q.z;
+ var sqw = q.w * q.w;
+
+ this.rotationX = Math.atan2( 2 * ( q.x * q.w - q.y * q.z ), ( sqw - sqx - sqy + sqz ) );
+ this.rotationY = Math.asin( clamp( 2 * ( q.x * q.z + q.y * q.w ), -1, 1 ) );
+ this.rotationZ = Math.atan2( 2 * ( q.z * q.w - q.x * q.y ), ( sqw + sqx - sqy - sqz ) );
+ },
+
+ lookAt: function (target, update) {
+ var r = this.getLookAtEuler(target)
+ this.setRotation(r)
+ if (update !== false) this.update()
+ return this
+ },
+
+ getLookAtEuler: function (target) {
+ // euler order XYZ
+ var r = {},
+ dx = target.x - this.x,
+ dy = target.y - this.y,
+ dz = target.z - this.z
+ if (this.inverseLookAt) {
+ dx = -dx
+ dy = -dy
+ dz = -dz
+ }
+ if (dz === 0) dz = 0.001
+ r.x = -Math.atan2(dy, dz)
+ var flip = dz > 0 ? 1 : -1
+ r.y = flip * Math.atan2(dx * Math.cos(r.x), dz * -flip)
+ r.z = Math.atan2(Math.cos(r.x), Math.sin(r.x) * Math.sin(r.y)) - Math.PI / 2
+ if (MX.rotationUnit === 'deg') {
+ r.x = toDeg(r.x)
+ r.y = toDeg(r.y)
+ r.z = toDeg(r.z)
+ }
+ return r
+ },
+
+ add: function () {
+ if (!this.el) return
+ var parent = this
+ Array.prototype.forEach.call(arguments, function (child) {
+ if (!child instanceof Object3D) return
+ parent.el.appendChild(child.el)
+ if (!parent.children) parent.children = []
+ parent.children.push(child)
+ child.parent = parent
+ })
+ return this
+ },
+
+ remove: function () {
+ var parent = this
+ Array.prototype.forEach.call(arguments, function (child) {
+ var index = parent.children.indexOf(child)
+ if (index !== -1) {
+ parent.children.splice(index, 1)
+ child.parent = undefined
+ }
+ })
+ return this
+ },
+
+ addTo: function (target) {
+ if (typeof target === 'string') {
+ target = document.querySelector(target)
+ }
+ if (target instanceof HTMLElement && target.appendChild) {
+ target.appendChild(this.el)
+ } else if (target instanceof Object3D || target instanceof Scene) {
+ target.add(this)
+ }
+ return this
+ },
+
+ removeElement: function () {
+ if (this.el.parentNode) {
+ this.el.parentNode.removeChild(this.el)
+ }
+ },
+
+ setPosition: function (tar) {
+ this.x = (tar.x || tar.x === 0) ? tar.x : this.x
+ this.y = (tar.y || tar.y === 0) ? tar.y : this.y
+ this.z = (tar.z || tar.z === 0) ? tar.z : this.z
+ },
+
+ setRotation: function (tar) {
+ this.rotationX = (tar.x || tar.x === 0) ? tar.x : this.rotationX
+ this.rotationY = (tar.y || tar.y === 0) ? tar.y : this.rotationY
+ this.rotationZ = (tar.z || tar.z === 0) ? tar.z : this.rotationZ
+ },
+
+ setScale: function (tar) {
+ this.scaleX = (tar.x || tar.x === 0) ? tar.x : this.scaleX
+ this.scaleY = (tar.y || tar.y === 0) ? tar.y : this.scaleY
+ this.scaleZ = (tar.z || tar.z === 0) ? tar.z : this.scaleZ
+ },
+
+ setCSSTransformOrigin: function (origin) {
+ this.el && (this.el.style[transformOriginProp] = origin)
+ return this
+ },
+
+ setCSSTransformStyle: function (style) {
+ this.el && (this.el.style[transformStyleProp] = style)
+ return this
+ },
+
+ setCSSTransition: function (trans) {
+ this.el && (this.el.style[transitionProp] = trans)
+ return this
+ },
+
+ setCSSPerspective: function (pers) {
+ this.el && (this.el.style[perspectiveProp] = pers)
+ return this
+ },
+
+ onTransitionEnd: function (callback) {
+ this.cancelTransitionEnd()
+ var el = this.el
+ el.addEventListener(transitionEndEvent, onEnd)
+ function onEnd () {
+ el.removeEventListener(transitionEndEvent, onEnd)
+ callback()
+ }
+ },
+
+ cancelTransitionEnd: function () {
+ this.el.removeEventListener(transitionEndEvent)
+ },
+
+ toString: function(params){
+ params = params || "id width height depth x y z rotationX rotationY rotationZ scale".split(" ")
+ return this.__toString(params)
+ },
+
+ __toString: function(params, func){
+ this.id = this.id || guid()
+ var list = [],
+ obj = {},
+ type = this.type || "Object3d",
+ name = type.toLowerCase(),
+ val,
+ param
+ for (var i in params) {
+ param = params[i]
+ val = this[param]
+ if (val === 0 && ! func) continue;
+ if (typeof val == "number") {
+ if (param.indexOf("rotation") != -1) {
+ obj[param] = Number(val.toFixed(3))
+ }
+ else {
+ obj[param] = ~~val
+ }
+ }
+ else {
+ obj[param] = val
+ }
+ }
+
+ return (func || "var " + name + " = new MX." + type ) + "(" +
+ JSON.stringify(obj, undefined, 2) +
+ ")\n" + (func ? "" : "scene.add(" + name + ")")
+ },
+
+ contains: function(x,y,z){
+ var containsX = false,
+ containsY = false,
+ containsZ = false
+
+ if (x === null) {
+ containsX = true
+ }
+ else {
+ containsX = abs(this.x - x) <= this.width/2
+ }
+
+ if (y === null) {
+ containsY = true
+ }
+ else {
+ containsY = abs(this.y - y) <= this.height/2
+ }
+
+ if (z === null) {
+ containsZ = true
+ }
+ else {
+ containsZ = abs(this.z - z) <= this.depth/2
+ }
+
+ return (containsX && containsY && containsZ)
+ }
+
+ }
+
+ // ========================================================================
+ // Inheritance
+ // ========================================================================
+
+ Object3D.extend = extend.bind(Object3D)
+
+ function extend (props) {
+ var Super = this
+ var ExtendedObject3D = function () {
+ Super.call(this)
+ props.init && props.init.apply(this, arguments)
+ }
+ ExtendedObject3D.prototype = Object.create(Super.prototype)
+ for (var prop in props) {
+ if (props.hasOwnProperty(prop) && prop !== 'init') {
+ ExtendedObject3D.prototype[prop] = props[prop]
+ }
+ }
+ ExtendedObject3D.extend = extend.bind(ExtendedObject3D)
+ return ExtendedObject3D
+ }
+
+ // ========================================================================
+ // Expose API
+ // ========================================================================
+
+ MX.Object3D = Object3D
+ MX.toRad = toRad
+ MX.toDeg = toDeg
+
+ // center positioning getter setter
+ Object.defineProperty(MX, 'positionAtCenter', {
+ get: function () {
+ return positionAtCenter
+ },
+ set: function (val) {
+ if (typeof val !== 'boolean') return
+ positionAtCenter = val
+ if (positionAtCenter) {
+ injectCenteringCSS()
+ } else {
+ removeCenteringCSS()
+ }
+ }
+ })
+
+ return MX
+
+})() \ No newline at end of file