Permalink
Browse files

added property updater and object orbiting, updated launcher section

  • Loading branch information...
1 parent 0ef8369 commit e3527bc85355935e9247f6ca1dfb8e23f89217fa @collinhover committed Apr 30, 2012
View
16 src/web/assets/modules/core/CameraControls.js
@@ -63,6 +63,7 @@
_CameraControls.Instance = CameraControls;
_CameraControls.Instance.prototype.rotate = rotate;
_CameraControls.Instance.prototype.rotate_update = rotate_update;
+ _CameraControls.Instance.prototype.rotate_revert = rotate_revert;
_CameraControls.Instance.prototype.zoom = zoom;
_CameraControls.Instance.prototype.update = update;
@@ -256,6 +257,18 @@
}
+ function rotate_revert ( speed ) {
+
+ var pRot = this.settingsRotation,
+ rotBaseRevertSpeed = speed || pRot.baseRevertSpeed,
+ rotOffsetBase = pRot.offsetBase,
+ rotOffset = pRot.offset;
+
+ if ( rotOffset.x !== rotOffsetBase.x ) rotOffset.x += (rotOffsetBase.x - rotOffset.x) * rotBaseRevertSpeed;
+ if ( rotOffset.y !== rotOffsetBase.y ) rotOffset.y += (rotOffsetBase.y - rotOffset.y) * rotBaseRevertSpeed;
+
+ }
+
/*===================================================
zoom
@@ -411,8 +424,7 @@
// move rotation offset back towards original
else if ( typeof pRot.mouse === 'undefined' && player.moving === true ) {
- if ( rotOffset.x !== rotOffsetBase.x ) rotOffset.x += (rotOffsetBase.x - rotOffset.x) * rotBaseRevertSpeed;
- if ( rotOffset.y !== rotOffsetBase.y ) rotOffset.y += (rotOffsetBase.y - rotOffset.y) * rotBaseRevertSpeed;
+ this.rotate_revert();
}
View
9 src/web/assets/modules/core/Model.js
@@ -45,6 +45,7 @@
_Model.Instance.prototype = new THREE.Mesh();
_Model.Instance.prototype.constructor = _Model.Instance;
_Model.Instance.prototype.clone = clone;
+
_Model.Instance.prototype.tween_properties = tween_properties;
// catch parent changes and add / remove physics automatically
@@ -93,6 +94,8 @@
get : function () { return this._geometry; },
set : function ( geometry ) {
+ var i, l;
+
if ( geometry instanceof THREE.Geometry && this.geometry !== geometry ) {
// clear all morphs
@@ -125,11 +128,11 @@
this.morphTargetForcedOrder = [];
this.morphTargetInfluences = [];
this.morphTargetDictionary = {};
-
- for( var m = 0; m < this._geometry.morphTargets.length; m ++ ) {
+
+ for( i = 0, l = this._geometry.morphTargets.length; i < l; i++ ) {
this.morphTargetInfluences.push( 0 );
- this.morphTargetDictionary[ this._geometry.morphTargets[ m ].name ] = m;
+ this.morphTargetDictionary[ this._geometry.morphTargets[ i ].name ] = i;
}
View
4 src/web/assets/modules/core/Player.js
@@ -58,6 +58,10 @@
}
});
+ Object.defineProperty(_Player, 'cameraControls', {
+ get : function () { return cameraControls; }
+ });
+
Object.defineProperty(_Player, 'camera', {
get : function () { return cameraControls.camera; }
});
View
264 src/web/assets/modules/core/PropertyUpdater.js
@@ -0,0 +1,264 @@
+/*
+ *
+ * PropertyUpdater.js
+ * Object that constantly updates a property.
+ *
+ * @author Collin Hover / http://collinhover.com/
+ *
+ */
+(function (main) {
+
+ var shared = main.shared = main.shared || {},
+ assetPath = "assets/modules/core/PropertyUpdater.js",
+ _PropertyUpdater = {};
+
+ /*===================================================
+
+ public
+
+ =====================================================*/
+
+ main.asset_register( assetPath, {
+ data: _PropertyUpdater/*,
+ requirements: [
+ "assets/modules/core/PropertyUpdater.js"
+ ],
+ callbacksOnReqs: init_internal,
+ wait: true*/
+ });
+
+ /*===================================================
+
+ internal init
+
+ =====================================================*/
+
+ //function init_internal ( o ) {
+ console.log('internal property updater', _PropertyUpdater);
+
+ _PropertyUpdater.Instance = PropertyUpdater;
+
+ _PropertyUpdater.Instance.prototype.start = start;
+ _PropertyUpdater.Instance.prototype.stop = stop;
+ _PropertyUpdater.Instance.prototype.add = add;
+ _PropertyUpdater.Instance.prototype.remove = remove;
+ _PropertyUpdater.Instance.prototype.detach = detach;
+ _PropertyUpdater.Instance.prototype.update = update;
+ _PropertyUpdater.Instance.prototype.integrate = integrate;
+ _PropertyUpdater.Instance.prototype.apply = apply;
+
+ Object.defineProperty( _PropertyUpdater.Instance.prototype, 'parent', {
+ get : function () { return this._parent; },
+ set : function ( parent ) {
+
+ this._parent = parent;
+
+ // remove signal, parent will handle update
+ if ( this._parent instanceof _PropertyUpdater.Instance ) {
+
+ shared.signals.update.remove( this.update, this );
+
+ }
+ // add signal to handle own update
+ else if ( this.updating === true ) {
+
+ shared.signals.update.add( this.update, this );
+
+ }
+
+ }
+
+ } );
+
+ //}
+
+ /*===================================================
+
+ updater
+
+ =====================================================*/
+
+ function PropertyUpdater ( parameters ) {
+
+ // handle parameters
+
+ parameters = parameters || {};
+
+ this.object = parameters.object;
+
+ this.updating = false;
+
+ this.children = [];
+
+ }
+
+ /*===================================================
+
+ properties
+
+ =====================================================*/
+
+ function start () {
+
+ // set updating
+
+ if ( this.updating !== true ) {
+
+ this.updating = true;
+
+ // signal
+
+ shared.signals.update.add( this.update, this );
+
+ }
+
+ }
+
+ function stop () {
+
+ this.updating = false;
+
+ // signal
+
+ shared.signals.update.remove( this.update, this );
+
+ // detach
+
+ this.detach();
+
+ }
+
+ function add ( updaters ) {
+
+ var i, l,
+ updater,
+ children = this.children;
+
+ // add
+
+ updaters = main.ensure_array( updaters );
+
+ for ( i = 0, l = updaters.length; i < l; i++ ) {
+
+ updater = updaters[ i ];
+
+ // children
+
+ if ( children.indexOf( updater ) === -1 ) {
+
+ children.push( updater );
+
+ }
+
+ // set parent
+
+ updater.parent = this;
+
+ }
+
+ // updating
+
+ if ( children.length > 0 ) {
+
+ this.start();
+
+ }
+
+ }
+
+ function remove ( updaters ) {
+
+ var i, l,
+ index,
+ updater,
+ children = this.children;
+
+ // remove
+
+ if ( typeof updaters !== 'undefined' ) {
+
+ updaters = main.ensure_array( updaters );
+
+ for ( i = 0, l = updaters.length; i < l; i++ ) {
+
+ updater = updaters[ i ];
+
+ index = children.indexOf( updater );
+
+ if ( index !== -1 ) {
+
+ children.splice( index, 1 );
+
+ }
+
+ // set parent
+
+ updater.parent = undefined;
+
+ }
+
+ // stop if no children
+
+ if ( children.length === 0 ) {
+
+ this.stop();
+
+ }
+
+ }
+ // clear all
+ else {
+
+ this.remove( children );
+
+ }
+
+ }
+
+ function detach () {
+
+ if ( this.parent instanceof _PropertyUpdater.Instance ) {
+
+ this.parent.remove( this );
+
+ }
+
+ }
+
+ function update () {
+
+ var i, l,
+ children = this.children,
+ child;
+
+ // children
+
+ for ( i = 0, l = children.length; i < l; i++ ) {
+
+ child = children[ i ];
+
+ // update
+
+ child.update.call( child.context || this );
+
+ // integrate child
+
+ this.integrate( child );
+
+ }
+
+ // apply
+
+ this.apply();
+
+ }
+
+ function integrate () {
+
+ }
+
+ function apply () {
+
+ }
+
+} (KAIOPUA) );
View
86 src/web/assets/modules/env/Cloud.js
@@ -11,7 +11,8 @@
var shared = main.shared = main.shared || {},
assetPath = "assets/modules/env/Cloud.js",
_Cloud = {},
- _Model;
+ _Model,
+ _OrbitUpdater;
/*===================================================
@@ -23,6 +24,7 @@
data: _Cloud,
requirements: [
"assets/modules/core/Model.js",
+ "assets/modules/models/OrbitUpdater.js",
{ path: "assets/models/Cloud_001.js", type: 'model' },
{ path: "assets/models/Cloud_002.js", type: 'model' }
],
@@ -36,10 +38,11 @@
=====================================================*/
- function init_internal ( m, cloudBase1, cloudBase2 ) {
+ function init_internal ( m, ou, cloudBase1, cloudBase2 ) {
console.log('internal cloud', _Cloud);
_Model = m;
+ _OrbitUpdater = ou;
// properties
@@ -52,9 +55,6 @@
_Cloud.Instance.prototype.constructor = _Cloud.Instance;
_Cloud.Instance.prototype.supr = _Model.Instance.prototype;
- _Cloud.Instance.prototype.orbit = orbit;
- _Cloud.Instance.prototype.orbit_stop = orbit_stop;
-
}
/*===================================================
@@ -77,76 +77,14 @@
// properties
- this.orbitOrigin = new THREE.Vector3();
- this.orbitAxis = shared.cardinalAxes.up.clone();
-
- }
-
- /*===================================================
-
- orbit
-
- =====================================================*/
-
- function orbit ( parameters ) {
-
- // handle parameters
-
- parameters = parameters || {};
-
- // stop
-
- if ( parameters.stop === true ) {
-
- // properties
-
- this.orbiting = false;
-
- // signal
-
- shared.signals.update.remove( orbit_update, this );
-
- }
- // start
- else {
-
- // properties
-
- this.orbiting = true;
-
- if ( parameters.origin ) {
-
- this.orbitOrigin.copy( parameters.origin );
-
- }
-
- if ( parameters.axis ) {
-
- this.orbitAxis.copy( parameters.axis );
-
- }
-
- this.orbitRadius = parameters.radius || this.position.distanceTo( this.orbitOrigin );
-
- this.orbitWander = typeof parameters.wander === 'boolean' ? parameters.wander : true;
-
- // signal
-
- shared.signals.update.add( orbit_update, this );
-
- }
-
- }
-
- function orbit_stop () {
-
- this.orbit( { stop: true } );
-
- }
-
- function orbit_update () {
-
+ this.orbit = new _OrbitUpdater.Instance( { object: this } );
+ /*
+ this.wander = {
+ positionOffsetMax: new THREE.Vector3( 50, 50, 50 ),
+ positionOffsetMin: new THREE.Vector3( -50, -50, -50 ),
+ properties: new ModifierProperties()
+ }*/
}
View
47 src/web/assets/modules/env/Sky.js
@@ -87,6 +87,9 @@
_Sky.Instance.prototype.set_world = set_world;
_Sky.Instance.prototype.set_clouds = set_clouds;
+ _Sky.Instance.prototype.animate = animate;
+ _Sky.Instance.prototype.stop = stop;
+
Object.defineProperty( _Sky.Instance.prototype, 'world', {
get: function () { return this._world; },
set: function ( world ) { this.set_world( world ); }
@@ -300,15 +303,7 @@
cloud.scale.set( scale, scale, scale );
- }
-
- // pull towards world
-
- for ( i = 0, l = this.clouds.length; i < l; i++ ) {
-
- cloud = this.clouds[ i ];
-
- // pull
+ // pull towards world
distance = Math.random() * ( this.cloudDistanceFromSurfaceMax - this.cloudDistanceFromSurfaceMin ) + this.cloudDistanceFromSurfaceMin;
@@ -355,4 +350,38 @@
}
+ function animate () {
+
+ var i, l,
+ cloud;
+
+ // clouds
+
+ for ( i = 0, l = this.clouds.length; i < l; i++ ) {
+
+ cloud = this.clouds[ i ];
+
+ cloud.orbit.start();
+
+ }
+
+ }
+
+ function stop () {
+
+ var i, l,
+ cloud;
+
+ // clouds
+
+ for ( i = 0, l = this.clouds.length; i < l; i++ ) {
+
+ cloud = this.clouds[ i ];
+
+ cloud.orbit.stop();
+
+ }
+
+ }
+
} (KAIOPUA) );
View
228 src/web/assets/modules/models/OrbitUpdater.js
@@ -0,0 +1,228 @@
+/*
+ *
+ * OrbitUpdater.js
+ * Property updater for orbiting.
+ *
+ * @author Collin Hover / http://collinhover.com/
+ *
+ */
+(function (main) {
+
+ var shared = main.shared = main.shared || {},
+ assetPath = "assets/modules/models/OrbitUpdater.js",
+ _OrbitUpdater = {},
+ _PropertyUpdater,
+ _MathHelper,
+ _ObjectHelper,
+ accelerationDamping = 0.9,
+ speedMax = 0.00005,
+ speedMin = 0.000001;
+
+ /*===================================================
+
+ public
+
+ =====================================================*/
+
+ main.asset_register( assetPath, {
+ data: _OrbitUpdater,
+ requirements: [
+ "assets/modules/core/PropertyUpdater.js",
+ "assets/modules/utils/MathHelper.js",
+ "assets/modules/utils/ObjectHelper.js"
+ ],
+ callbacksOnReqs: init_internal,
+ wait: true
+ });
+
+ /*===================================================
+
+ internal init
+
+ =====================================================*/
+
+ function init_internal ( pu, mh, oh ) {
+ console.log('internal orbit updater', _OrbitUpdater);
+
+ _PropertyUpdater = pu;
+ _MathHelper = mh;
+ _ObjectHelper = oh;
+
+ _OrbitUpdater.Instance = OrbitUpdater;
+ _OrbitUpdater.Instance.prototype = new _PropertyUpdater.Instance();
+ _OrbitUpdater.Instance.prototype.constructor = _OrbitUpdater.Instance;
+ _OrbitUpdater.Instance.prototype.supr = _PropertyUpdater.Instance.prototype;
+
+ _OrbitUpdater.Instance.prototype.start = start;
+ _OrbitUpdater.Instance.prototype.update = update;
+ _OrbitUpdater.Instance.prototype.reset = reset;
+ _OrbitUpdater.Instance.prototype.integrate = integrate;
+ _OrbitUpdater.Instance.prototype.apply = apply;
+
+ }
+
+ /*===================================================
+
+ updater
+
+ =====================================================*/
+
+ function OrbitUpdater ( parameters ) {
+
+ // prototype
+
+ _PropertyUpdater.Instance.call( this, parameters );
+
+ // utility
+
+ this.utilVec31Orbit = new THREE.Vector3();
+
+ // properties
+
+ this.origin = new THREE.Vector3();
+ this.positionOffset = new THREE.Vector3();
+ this.rotationBase = new THREE.Quaternion();
+ this.rotationOffset = new THREE.Quaternion();
+
+ this.axis = shared.cardinalAxes.up.clone();
+ this.acceleration = 0;
+ this.accelerationDamping = accelerationDamping;
+ this.speedMax = speedMax;
+ this.speedMin = speedMin;
+ this.speed = 0;
+ this.radius = 0;
+ this.angle = 0;
+
+ }
+
+ function start ( parameters ) {
+
+ var posVec = this.utilVec31Orbit,
+ angleA,
+ lengthA,
+ lengthB,
+ lengthC;
+
+ // handle parameters
+
+ parameters = parameters || {};
+
+ // properties
+
+ if ( parameters.axis ) {
+
+ this.axis.copy( parameters.axis );
+
+ }
+
+ if ( parameters.origin ) {
+
+ this.origin.copy( parameters.origin );
+
+ }
+ // find origin based on axis and current position
+ else {
+
+ posVec.copy( this.object.position ).normalize();
+
+ angleA = _MathHelper.angle_between_vectors( this.axis, posVec );
+
+ lengthC = this.object.position.length();
+ lengthA = ( Math.sin( angleA ) * lengthC ) / Math.sin( Math.PI * 0.5 );
+ lengthB = Math.sqrt( Math.pow( lengthC, 2 ) - Math.pow( lengthA, 2 ) );
+
+ this.origin.copy( this.axis ).multiplyScalar( lengthB );
+
+ }
+
+ this.radius = parameters.radius || this.object.position.distanceTo( this.origin );
+ this.positionOffset.z = this.radius;
+
+ this.angle = 0;
+ this.rotationBase.copy( this.object.quaternion );
+
+ this.accelerationDamping = parameters.accelerationDamping || this.accelerationDamping;
+ this.speedMax = parameters.speedMax || parameters.speed || this.speedMax;
+ this.speedMin = parameters.speedMin || parameters.speed || this.speedMin;
+ this.speed = Math.random() * ( this.speedMax - this.speedMin ) + this.speedMin;
+
+ // prototype
+
+ _OrbitUpdater.Instance.prototype.supr.start.call( this, parameters );
+
+ }
+
+ function update () {
+
+ // acceleration
+
+ this.acceleration += this.speed;
+
+ // position
+
+ if ( this.positionOffset.z !== this.radius ) {
+
+ this.positionOffset.z += this.acceleration * _MathHelper.sign( this.radius - this.positionOffset.z );
+
+ }
+
+ // rotation
+
+ this.angle += this.acceleration;
+
+ this.rotationOffset.setFromAxisAngle( this.axis, this.angle );
+
+ // damping
+
+ this.acceleration *= this.accelerationDamping;
+
+ // prototype
+
+ _OrbitUpdater.Instance.prototype.supr.update.call( this );
+
+ }
+
+ function reset () {
+
+ this.origin.set( 0, 0, 0 );
+ this.positionOffset.set( 0, 0, 0 );
+ this.rotationBase.set( 0, 0, 0, 1 );
+ this.rotationOffset.set( 0, 0, 0, 1 );
+
+ }
+
+ function integrate ( child ) {
+
+ if ( child.origin ) {
+
+ this.origin.addSelf( child.origin );
+
+ }
+
+ if ( child.positionOffset ) {
+
+ this.positionOffset.addSelf( child.positionOffset );
+
+ }
+
+ if ( child.rotationBase ) {
+
+ this.rotationBase.multiplySelf( child.rotationBase );
+
+ }
+
+ if ( child.rotationOffset ) {
+
+ this.rotationOffset.multiplySelf( child.rotationOffset );
+
+ }
+
+ }
+
+ function apply () {
+
+ _ObjectHelper.object_orbit_source( this.object, this.origin, this.rotationBase, this.rotationOffset, this.positionOffset );
+
+ }
+
+} (KAIOPUA) );
View
1 src/web/assets/modules/sections/Intro.js
@@ -138,6 +138,7 @@
_Player.character.position.set( 35, 3000, 765 );
_Player.character.quaternion.set( 0, -1, -0.25, 0 );
+ _Player.cameraControls.rotate_revert( 1 );
//_Player.cameraMode = 'freelook';
View
8 src/web/assets/modules/sections/Launcher.js
@@ -42,8 +42,8 @@
rangeTransMinY: -250,
speedTransX: 0.01,
speedTransY: 0.01,
- rangeRotMaxX: 15,
- rangeRotMinX: -15,
+ rangeRotMaxX: 0,
+ rangeRotMinX: -25,
rangeRotMaxY: 10,
rangeRotMinY: -10,
speedRotX: 0.05,
@@ -228,6 +228,8 @@
water.morphs.play( 'waves', { duration: 4000, loop: true } );
+ sky.animate();
+
// add items
_Game.add_to_scene( addOnShow, scene );
@@ -257,6 +259,8 @@
water.morphs.stopAll();
+ sky.stop();
+
shared.signals.mousemoved.remove( on_mouse_moved );
shared.signals.update.remove( update );
View
8 src/web/assets/modules/utils/MathHelper.js
@@ -109,6 +109,14 @@
};
+ _MathHelper.angle_between_vectors = function ( vFrom, vTo ) {
+
+ var dist = _MathHelper.clamp( vFrom.dot( vTo ), -1, 1 );
+
+ return Math.acos( dist );
+
+ };
+
_MathHelper.get_orthonormal_vectors = function ( v1 ) {
// returns 2 orthographic ( perpendicular ) vectors to the first
View
131 src/web/assets/modules/utils/ObjectHelper.js
@@ -17,6 +17,8 @@
utilVec31Casting,
utilVec31Follow,
utilVec32Follow,
+ utilVec31Orbit,
+ utilVec32Orbit,
utilVec31Bounds,
utilVec32Bounds,
utilVec31Dimensions,
@@ -32,6 +34,8 @@
utilQ2Follow,
utilQ3Follow,
utilQ4Follow,
+ utilQ1Orbit,
+ utilQ2Orbit,
utilQ1Axis,
utilQ1ApplyQ,
utilQ2ApplyQ,
@@ -72,6 +76,7 @@
_ObjectHelper.normalize_faces = normalize_faces
_ObjectHelper.object_follow_object = object_follow_object;
+ _ObjectHelper.object_orbit_source = object_orbit_source;
_ObjectHelper.object_rotate_relative_to_source = object_rotate_relative_to_source;
_ObjectHelper.object_pull_to_source = object_pull_to_source;
@@ -116,6 +121,8 @@
utilVec31Casting = new THREE.Vector3();
utilVec31Follow = new THREE.Vector3();
utilVec32Follow = new THREE.Vector3();
+ utilVec31Orbit = new THREE.Vector3();
+ utilVec32Orbit = new THREE.Vector3();
utilVec31Bounds = new THREE.Vector3();
utilVec32Bounds = new THREE.Vector3();
utilVec31Dimensions = new THREE.Vector3();
@@ -131,6 +138,8 @@
utilQ2Follow = new THREE.Quaternion();
utilQ3Follow = new THREE.Quaternion();
utilQ4Follow = new THREE.Quaternion();
+ utilQ1Orbit = new THREE.Quaternion();
+ utilQ2Orbit = new THREE.Quaternion();
utilQ1Axis = new THREE.Quaternion();
utilQ1ApplyQ = new THREE.Quaternion();
utilQ2ApplyQ = new THREE.Quaternion();
@@ -1341,6 +1350,128 @@
/*===================================================
+ orbit
+
+ =====================================================*/
+
+ function object_orbit_source ( object, source, rotationBase, rotationOffset, positionOffset ) {
+
+ var sPos = utilVec31Orbit,
+ oPos = object.position,
+ oQ = object.quaternion,
+ oBaseRot = utilQ1Orbit,
+ oOffsetRot = utilQ2Orbit,
+ oOffsetPos = utilVec32Orbit,
+ skipBaseRot,
+ skipOffsetRot,
+ skipOffsetPos;
+
+ // base rotation
+
+ if ( rotationBase instanceof THREE.Quaternion ) {
+
+ oBaseRot.copy( rotationBase );
+
+ }
+ else if ( rotationBase instanceof THREE.Vector3 ) {
+
+ oBaseRot.setFromEuler( rotationBase ).normalize();
+
+ }
+ else {
+
+ skipBaseRot = true;
+
+ }
+
+ // offset rotation
+
+ if ( rotationOffset instanceof THREE.Quaternion ) {
+
+ oOffsetRot.copy( rotationOffset );
+
+ }
+ else if ( rotationOffset instanceof THREE.Vector3 ) {
+
+ oOffsetRot.setFromEuler( rotationOffset ).normalize();
+
+ }
+ else {
+
+ skipOffsetRot = true;
+
+ }
+
+ // offset position
+
+ if ( positionOffset instanceof THREE.Vector3 ) {
+
+ oOffsetPos.copy( positionOffset );
+
+ }
+ else {
+
+ skipOffsetPos = true;
+
+ }
+
+ // modify offset position
+
+ if ( skipOffsetPos !== true ) {
+
+ if ( skipBaseRot !== true ) {
+
+ oBaseRot.multiplyVector3( oOffsetPos );
+
+ }
+
+ if ( skipOffsetRot !== true ) {
+
+ oOffsetRot.multiplyVector3( oOffsetPos );
+
+ }
+
+ }
+
+ // position
+
+ if ( source instanceof THREE.Object3D ) {
+
+ oPos.copy( source.position );
+
+ }
+ else {
+
+ oPos.copy( source );
+
+ }
+
+ if ( skipOffsetPos !== true ) {
+
+ oPos.addSelf( oOffsetPos );
+
+ }
+
+ // rotation
+
+ oQ.set( 0, 0, 0, 1 );
+
+ if ( skipOffsetRot !== true ) {
+
+ oQ.multiplySelf( oOffsetRot );
+
+ }
+
+ if ( skipBaseRot !== true ) {
+
+ oQ.multiplySelf( oBaseRot );
+
+ }
+
+ }
+
+ /*===================================================
+
rotate
=====================================================*/

0 comments on commit e3527bc

Please sign in to comment.