some more restructuring
This commit is contained in:
@@ -1,105 +0,0 @@
|
||||
Impress.js Libraries
|
||||
====================
|
||||
|
||||
The `src/lib/*.js` files contain library functions. The main difference to plugins is that:
|
||||
|
||||
1. Libraries are closer to the impress.js core than plugins (arguably a subjective metric)
|
||||
2. Libraries are common utility functions used by many plugins
|
||||
3. Libraries are called synchronously, which is why the event based paradigm that plugins use to
|
||||
communicate isn't useful.
|
||||
|
||||
Plugins can access libraries via the API:
|
||||
|
||||
var api;
|
||||
document.addEventListener( "impress:init", function(event){
|
||||
api = event.detail.api;
|
||||
api().lib.<libraryName>.<libaryFunction>();
|
||||
});
|
||||
|
||||
...which is equivalent to:
|
||||
|
||||
impress().lib.<libraryName>.<libraryFunction>();
|
||||
|
||||
Implementing a library
|
||||
----------------------
|
||||
|
||||
1. Create a file under `src/lib/`.
|
||||
|
||||
2. Start with the standard boilerplate documentation, and the (function(document, window){})();
|
||||
wrapper.
|
||||
|
||||
3. The library should implement a factory function, and make its existence known to impress.js core:
|
||||
|
||||
window.impress.addLibraryFactory( { libName : libraryFactory} );
|
||||
|
||||
4. The library function should return a similar API object as core `impress()` function does:
|
||||
|
||||
var libraryFactory = function(rootId) {
|
||||
/* implement library functions ... */
|
||||
|
||||
var lib = {
|
||||
libFunction1: libFunction1,
|
||||
libFunction2: libFunction2
|
||||
}
|
||||
return lib;
|
||||
};
|
||||
|
||||
5. While rarely used, impress.js actually supports multiple presentation root div elements on a
|
||||
single html page. Each of these have their own API object, identified by the root element id
|
||||
attribute:
|
||||
|
||||
impress("other-root-id").init();
|
||||
|
||||
(The default rootId obviously is `"impress"`.)
|
||||
|
||||
Libraries MUST implement this support for multiple root elements as well.
|
||||
|
||||
- impress.js core will call the factory once for each separate root element being initialized via
|
||||
`impress.init(rootId)`.
|
||||
- Any state that a library might hold, MUST be stored *per `rootId`*.
|
||||
- Note that as we now support also `impress(rootId).tear()`, the same root element might be
|
||||
initialized more than once, and each of these MUST be treated as a new valid initialization.
|
||||
|
||||
Putting all of the above together, a skeleton library file will look like:
|
||||
|
||||
/**
|
||||
* Example library libName
|
||||
*
|
||||
* Henrik Ingo (c) 2016
|
||||
* MIT License
|
||||
*/
|
||||
(function ( document, window ) {
|
||||
'use strict';
|
||||
// Singleton library variables
|
||||
var roots = [];
|
||||
var singletonVar = {};
|
||||
|
||||
var libraryFactory = function(rootId) {
|
||||
if (roots["impress-root-" + rootId]) {
|
||||
return roots["impress-root-" + rootId];
|
||||
}
|
||||
|
||||
// Per root global variables (instance variables?)
|
||||
var instanceVar = {};
|
||||
|
||||
// LIBRARY FUNCTIONS
|
||||
var libraryFunction1 = function () {
|
||||
/* ... */
|
||||
};
|
||||
|
||||
var libraryFunction2 = function () {
|
||||
/* ... */
|
||||
};
|
||||
|
||||
var lib = {
|
||||
libFunction1: libFunction1,
|
||||
libFunction2: libFunction2
|
||||
}
|
||||
roots["impress-root-" + rootId] = lib;
|
||||
return lib;
|
||||
};
|
||||
|
||||
// Let impress core know about the existence of this library
|
||||
window.impress.addLibraryFactory( { libName : libraryFactory } );
|
||||
|
||||
})(document, window);
|
||||
238
src/lib/gc.js
238
src/lib/gc.js
@@ -1,238 +0,0 @@
|
||||
/**
|
||||
* Garbage collection utility
|
||||
*
|
||||
* This library allows plugins to add elements and event listeners they add to the DOM. The user
|
||||
* can call `impress().lib.gc.teardown()` to cause all of them to be removed from DOM, so that
|
||||
* the document is in the state it was before calling `impress().init()`.
|
||||
*
|
||||
* In addition to just adding elements and event listeners to the garbage collector, plugins
|
||||
* can also register callback functions to do arbitrary cleanup upon teardown.
|
||||
*
|
||||
* Henrik Ingo (c) 2016
|
||||
* MIT License
|
||||
*/
|
||||
|
||||
( function( document, window ) {
|
||||
"use strict";
|
||||
var roots = [];
|
||||
var rootsCount = 0;
|
||||
var startingState = { roots: [] };
|
||||
|
||||
var libraryFactory = function( rootId ) {
|
||||
if ( roots[ rootId ] ) {
|
||||
return roots[ rootId ];
|
||||
}
|
||||
|
||||
// Per root global variables (instance variables?)
|
||||
var elementList = [];
|
||||
var eventListenerList = [];
|
||||
var callbackList = [];
|
||||
|
||||
recordStartingState( rootId );
|
||||
|
||||
// LIBRARY FUNCTIONS
|
||||
// Definitions of the library functions we return as an object at the end
|
||||
|
||||
// `pushElement` adds a DOM element to the gc stack
|
||||
var pushElement = function( element ) {
|
||||
elementList.push( element );
|
||||
};
|
||||
|
||||
// `appendChild` is a convenience wrapper that combines DOM appendChild with gc.pushElement
|
||||
var appendChild = function( parent, element ) {
|
||||
parent.appendChild( element );
|
||||
pushElement( element );
|
||||
};
|
||||
|
||||
// `pushEventListener` adds an event listener to the gc stack
|
||||
var pushEventListener = function( target, type, listenerFunction ) {
|
||||
eventListenerList.push( { target:target, type:type, listener:listenerFunction } );
|
||||
};
|
||||
|
||||
// `addEventListener` combines DOM addEventListener with gc.pushEventListener
|
||||
var addEventListener = function( target, type, listenerFunction ) {
|
||||
target.addEventListener( type, listenerFunction );
|
||||
pushEventListener( target, type, listenerFunction );
|
||||
};
|
||||
|
||||
// `pushCallback` If the above utilities are not enough, plugins can add their own callback
|
||||
// function to do arbitrary things.
|
||||
var pushCallback = function( callback ) {
|
||||
callbackList.push( callback );
|
||||
};
|
||||
pushCallback( function( rootId ) { resetStartingState( rootId ); } );
|
||||
|
||||
// `teardown` will
|
||||
// - execute all callbacks in LIFO order
|
||||
// - call `removeChild` on all DOM elements in LIFO order
|
||||
// - call `removeEventListener` on all event listeners in LIFO order
|
||||
// The goal of a teardown is to return to the same state that the DOM was before
|
||||
// `impress().init()` was called.
|
||||
var teardown = function() {
|
||||
|
||||
// Execute the callbacks in LIFO order
|
||||
var i; // Needed by jshint
|
||||
for ( i = callbackList.length - 1; i >= 0; i-- ) {
|
||||
callbackList[ i ]( rootId );
|
||||
}
|
||||
callbackList = [];
|
||||
for ( i = 0; i < elementList.length; i++ ) {
|
||||
elementList[ i ].parentElement.removeChild( elementList[ i ] );
|
||||
}
|
||||
elementList = [];
|
||||
for ( i = 0; i < eventListenerList.length; i++ ) {
|
||||
var target = eventListenerList[ i ].target;
|
||||
var type = eventListenerList[ i ].type;
|
||||
var listener = eventListenerList[ i ].listener;
|
||||
target.removeEventListener( type, listener );
|
||||
}
|
||||
};
|
||||
|
||||
var lib = {
|
||||
pushElement: pushElement,
|
||||
appendChild: appendChild,
|
||||
pushEventListener: pushEventListener,
|
||||
addEventListener: addEventListener,
|
||||
pushCallback: pushCallback,
|
||||
teardown: teardown
|
||||
};
|
||||
roots[ rootId ] = lib;
|
||||
rootsCount++;
|
||||
return lib;
|
||||
};
|
||||
|
||||
// Let impress core know about the existence of this library
|
||||
window.impress.addLibraryFactory( { gc: libraryFactory } );
|
||||
|
||||
// CORE INIT
|
||||
// The library factory (gc(rootId)) is called at the beginning of impress(rootId).init()
|
||||
// For the purposes of teardown(), we can use this as an opportunity to save the state
|
||||
// of a few things in the DOM in their virgin state, before impress().init() did anything.
|
||||
// Note: These could also be recorded by the code in impress.js core as these values
|
||||
// are changed, but in an effort to not deviate too much from upstream, I'm adding
|
||||
// them here rather than the core itself.
|
||||
var recordStartingState = function( rootId ) {
|
||||
startingState.roots[ rootId ] = {};
|
||||
startingState.roots[ rootId ].steps = [];
|
||||
|
||||
// Record whether the steps have an id or not
|
||||
var steps = document.getElementById( rootId ).querySelectorAll( ".step" );
|
||||
for ( var i = 0; i < steps.length; i++ ) {
|
||||
var el = steps[ i ];
|
||||
startingState.roots[ rootId ].steps.push( {
|
||||
el: el,
|
||||
id: el.getAttribute( "id" )
|
||||
} );
|
||||
}
|
||||
|
||||
// In the rare case of multiple roots, the following is changed on first init() and
|
||||
// reset at last tear().
|
||||
if ( rootsCount === 0 ) {
|
||||
startingState.body = {};
|
||||
|
||||
// It is customary for authors to set body.class="impress-not-supported" as a starting
|
||||
// value, which can then be removed by impress().init(). But it is not required.
|
||||
// Remember whether it was there or not.
|
||||
if ( document.body.classList.contains( "impress-not-supported" ) ) {
|
||||
startingState.body.impressNotSupported = true;
|
||||
} else {
|
||||
startingState.body.impressNotSupported = false;
|
||||
}
|
||||
|
||||
// If there's a <meta name="viewport"> element, its contents will be overwritten by init
|
||||
var metas = document.head.querySelectorAll( "meta" );
|
||||
for ( i = 0; i < metas.length; i++ ) {
|
||||
var m = metas[ i ];
|
||||
if ( m.name === "viewport" ) {
|
||||
startingState.meta = m.content;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// CORE TEARDOWN
|
||||
var resetStartingState = function( rootId ) {
|
||||
|
||||
// Reset body element
|
||||
document.body.classList.remove( "impress-enabled" );
|
||||
document.body.classList.remove( "impress-disabled" );
|
||||
|
||||
var root = document.getElementById( rootId );
|
||||
var activeId = root.querySelector( ".active" ).id;
|
||||
document.body.classList.remove( "impress-on-" + activeId );
|
||||
|
||||
document.documentElement.style.height = "";
|
||||
document.body.style.height = "";
|
||||
document.body.style.overflow = "";
|
||||
|
||||
// Remove style values from the root and step elements
|
||||
// Note: We remove the ones set by impress.js core. Otoh, we didn't preserve any original
|
||||
// values. A more sophisticated implementation could keep track of original values and then
|
||||
// reset those.
|
||||
var steps = root.querySelectorAll( ".step" );
|
||||
for ( var i = 0; i < steps.length; i++ ) {
|
||||
steps[ i ].classList.remove( "future" );
|
||||
steps[ i ].classList.remove( "past" );
|
||||
steps[ i ].classList.remove( "present" );
|
||||
steps[ i ].classList.remove( "active" );
|
||||
steps[ i ].style.position = "";
|
||||
steps[ i ].style.transform = "";
|
||||
steps[ i ].style[ "transform-style" ] = "";
|
||||
}
|
||||
root.style.position = "";
|
||||
root.style[ "transform-origin" ] = "";
|
||||
root.style.transition = "";
|
||||
root.style[ "transform-style" ] = "";
|
||||
root.style.top = "";
|
||||
root.style.left = "";
|
||||
root.style.transform = "";
|
||||
|
||||
// Reset id of steps ("step-1" id's are auto generated)
|
||||
steps = startingState.roots[ rootId ].steps;
|
||||
var step;
|
||||
while ( step = steps.pop() ) {
|
||||
if ( step.id === null ) {
|
||||
step.el.removeAttribute( "id" );
|
||||
} else {
|
||||
step.el.setAttribute( "id", step.id );
|
||||
}
|
||||
}
|
||||
delete startingState.roots[ rootId ];
|
||||
|
||||
// Move step div elements away from canvas, then delete canvas
|
||||
// Note: There's an implicit assumption here that the canvas div is the only child element
|
||||
// of the root div. If there would be something else, it's gonna be lost.
|
||||
var canvas = root.firstChild;
|
||||
var canvasHTML = canvas.innerHTML;
|
||||
root.innerHTML = canvasHTML;
|
||||
|
||||
if ( roots[ rootId ] !== undefined ) {
|
||||
delete roots[ rootId ];
|
||||
rootsCount--;
|
||||
}
|
||||
if ( rootsCount === 0 ) {
|
||||
|
||||
// In the rare case that more than one impress root elements were initialized, these
|
||||
// are only reset when all are uninitialized.
|
||||
document.body.classList.remove( "impress-supported" );
|
||||
if ( startingState.body.impressNotSupported ) {
|
||||
document.body.classList.add( "impress-not-supported" );
|
||||
}
|
||||
|
||||
// We need to remove or reset the meta element inserted by impress.js
|
||||
var metas = document.head.querySelectorAll( "meta" );
|
||||
for ( i = 0; i < metas.length; i++ ) {
|
||||
var m = metas[ i ];
|
||||
if ( m.name === "viewport" ) {
|
||||
if ( startingState.meta !== undefined ) {
|
||||
m.content = startingState.meta;
|
||||
} else {
|
||||
m.parentElement.removeChild( m );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} )( document, window );
|
||||
0
src/lib/position.ts
Normal file
0
src/lib/position.ts
Normal file
@@ -1,429 +0,0 @@
|
||||
/**
|
||||
* Helper functions for rotation.
|
||||
*
|
||||
* Tommy Tam (c) 2021
|
||||
* MIT License
|
||||
*/
|
||||
( function( document, window ) {
|
||||
"use strict";
|
||||
|
||||
// Singleton library variables
|
||||
var roots = [];
|
||||
|
||||
var libraryFactory = function( rootId ) {
|
||||
if ( roots[ "impress-root-" + rootId ] ) {
|
||||
return roots[ "impress-root-" + rootId ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Round the number to 2 decimals, it's enough for use
|
||||
*/
|
||||
var roundNumber = function( num ) {
|
||||
return Math.round( ( num + Number.EPSILON ) * 100 ) / 100;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the length/norm of a vector.
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/Norm_(mathematics)
|
||||
*/
|
||||
var vectorLength = function( vec ) {
|
||||
return Math.sqrt( vec.x * vec.x + vec.y * vec.y + vec.z * vec.z );
|
||||
};
|
||||
|
||||
/**
|
||||
* Dot product of two vectors.
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/Dot_product
|
||||
*/
|
||||
var vectorDotProd = function( vec1, vec2 ) {
|
||||
return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cross product of two vectors.
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/Cross_product
|
||||
*/
|
||||
var vectorCrossProd = function( vec1, vec2 ) {
|
||||
return {
|
||||
x: vec1.y * vec2.z - vec1.z * vec2.y,
|
||||
y: vec1.z * vec2.x - vec1.x * vec2.z,
|
||||
z: vec1.x * vec2.y - vec1.y * vec2.x
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine wheter a vector is a zero vector
|
||||
*/
|
||||
var isZeroVector = function( vec ) {
|
||||
return !roundNumber( vec.x ) && !roundNumber( vec.y ) && !roundNumber( vec.z );
|
||||
};
|
||||
|
||||
/**
|
||||
* Scalar triple product of three vectors.
|
||||
*
|
||||
* It can be used to determine the handness of vectors.
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/Triple_product#Scalar_triple_product
|
||||
*/
|
||||
var tripleProduct = function( vec1, vec2, vec3 ) {
|
||||
return vectorDotProd( vectorCrossProd( vec1, vec2 ), vec3 );
|
||||
};
|
||||
|
||||
/**
|
||||
* The world/absolute unit coordinates.
|
||||
*
|
||||
* This coordinate is used by browser to position objects.
|
||||
* It will not be affected by object rotations.
|
||||
* All relative positions will finally be converted to this
|
||||
* coordinate to be used.
|
||||
*/
|
||||
var worldUnitCoordinate = {
|
||||
x: { x:1, y:0, z:0 },
|
||||
y: { x:0, y:1, z:0 },
|
||||
z: { x:0, y:0, z:1 }
|
||||
};
|
||||
|
||||
/**
|
||||
* Make quaternion from rotation axis and angle.
|
||||
*
|
||||
* q = [ cos(½θ), sin(½θ) axis ]
|
||||
*
|
||||
* If the angle is zero, returns the corresponded quaternion
|
||||
* of axis.
|
||||
*
|
||||
* If the angle is not zero, returns the rotating quaternion
|
||||
* which corresponds to rotation about the axis, by the angle θ.
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/Quaternion
|
||||
* https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation
|
||||
*/
|
||||
var makeQuaternion = function( axis, theta = 0 ) {
|
||||
var r = 0;
|
||||
var t = 1;
|
||||
|
||||
if ( theta ) {
|
||||
var radians = theta * Math.PI / 180;
|
||||
r = Math.cos( radians / 2 );
|
||||
t = Math.sin( radians / 2 ) / vectorLength( axis );
|
||||
}
|
||||
|
||||
var q = [ r, axis.x * t, axis.y * t, axis.z * t ];
|
||||
|
||||
return q;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract vector from quaternion
|
||||
*/
|
||||
var quaternionToVector = function( quaternion ) {
|
||||
return {
|
||||
x: roundNumber( quaternion[ 1 ] ),
|
||||
y: roundNumber( quaternion[ 2 ] ),
|
||||
z: roundNumber( quaternion[ 3 ] )
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the conjugate quaternion of a quaternion
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/Quaternion#Conjugation,_the_norm,_and_reciprocal
|
||||
*/
|
||||
var conjugateQuaternion = function( quaternion ) {
|
||||
return [ quaternion[ 0 ], -quaternion[ 1 ], -quaternion[ 2 ], -quaternion[ 3 ] ];
|
||||
};
|
||||
|
||||
/**
|
||||
* Left multiple two quaternion.
|
||||
*
|
||||
* Is's used to combine two rotating quaternion into one.
|
||||
*/
|
||||
var leftMulQuaternion = function( q1, q2 ) {
|
||||
return [
|
||||
( q1[ 0 ] * q2[ 0 ] - q1[ 1 ] * q2[ 1 ] - q1[ 2 ] * q2[ 2 ] - q1[ 3 ] * q2[ 3 ] ),
|
||||
( q1[ 1 ] * q2[ 0 ] + q1[ 0 ] * q2[ 1 ] - q1[ 3 ] * q2[ 2 ] + q1[ 2 ] * q2[ 3 ] ),
|
||||
( q1[ 2 ] * q2[ 0 ] + q1[ 3 ] * q2[ 1 ] + q1[ 0 ] * q2[ 2 ] - q1[ 1 ] * q2[ 3 ] ),
|
||||
( q1[ 3 ] * q2[ 0 ] - q1[ 2 ] * q2[ 1 ] + q1[ 1 ] * q2[ 2 ] + q1[ 0 ] * q2[ 3 ] )
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a rotation into a quaternion
|
||||
*/
|
||||
var rotationToQuaternion = function( baseCoordinate, rotation ) {
|
||||
var order = rotation.order ? rotation.order : "xyz";
|
||||
var axes = order.split( "" );
|
||||
var result = [ 1, 0, 0, 0 ];
|
||||
|
||||
for ( var i = 0; i < axes.length; i++ ) {
|
||||
var deg = rotation[ axes[ i ] ];
|
||||
if ( !deg || ( Math.abs( deg ) < 0.0001 ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// All CSS rotation is based on the rotated coordinate
|
||||
// So we need to calculate the rotated coordinate first
|
||||
var coordinate = baseCoordinate;
|
||||
if ( i > 0 ) {
|
||||
coordinate = {
|
||||
x: rotateByQuaternion( baseCoordinate.x, result ),
|
||||
y: rotateByQuaternion( baseCoordinate.y, result ),
|
||||
z: rotateByQuaternion( baseCoordinate.z, result )
|
||||
};
|
||||
}
|
||||
|
||||
result = leftMulQuaternion(
|
||||
makeQuaternion( coordinate[ axes[ i ] ], deg ),
|
||||
result );
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Rotate a vector by a quaternion.
|
||||
*/
|
||||
var rotateByQuaternion = function( vec, quaternion ) {
|
||||
var q = makeQuaternion( vec );
|
||||
|
||||
q = leftMulQuaternion(
|
||||
leftMulQuaternion( quaternion, q ),
|
||||
conjugateQuaternion( quaternion ) );
|
||||
|
||||
return quaternionToVector( q );
|
||||
};
|
||||
|
||||
/**
|
||||
* Rotate a vector by rotaion sequence.
|
||||
*/
|
||||
var rotateVector = function( baseCoordinate, vec, rotation ) {
|
||||
var quaternion = rotationToQuaternion( baseCoordinate, rotation );
|
||||
|
||||
return rotateByQuaternion( vec, quaternion );
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a rotation, return the rotationed coordinate
|
||||
*/
|
||||
var rotateCoordinate = function( coordinate, rotation ) {
|
||||
var quaternion = rotationToQuaternion( coordinate, rotation );
|
||||
|
||||
return {
|
||||
x: rotateByQuaternion( coordinate.x, quaternion ),
|
||||
y: rotateByQuaternion( coordinate.y, quaternion ),
|
||||
z: rotateByQuaternion( coordinate.z, quaternion )
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the angle between two vector.
|
||||
*
|
||||
* The axis is used to determine the rotation direction.
|
||||
*/
|
||||
var angleBetweenTwoVector = function( axis, vec1, vec2 ) {
|
||||
var vecLen1 = vectorLength( vec1 );
|
||||
var vecLen2 = vectorLength( vec2 );
|
||||
|
||||
if ( !vecLen1 || !vecLen2 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var cos = vectorDotProd( vec1, vec2 ) / vecLen1 / vecLen2 ;
|
||||
var angle = Math.acos( cos ) * 180 / Math.PI;
|
||||
|
||||
if ( tripleProduct( vec1, vec2, axis ) > 0 ) {
|
||||
return angle;
|
||||
} else {
|
||||
return -angle;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the angle between a vector and a plane.
|
||||
*
|
||||
* The plane is determined by an axis and a vector on the plane.
|
||||
*/
|
||||
var angleBetweenPlaneAndVector = function( axis, planeVec, rotatedVec ) {
|
||||
var norm = vectorCrossProd( axis, planeVec );
|
||||
|
||||
if ( isZeroVector( norm ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 90 - angleBetweenTwoVector( axis, rotatedVec, norm );
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculated a order specified rotation sequence to
|
||||
* transform from the world coordinate to required coordinate.
|
||||
*/
|
||||
var coordinateToOrderedRotation = function( coordinate, order ) {
|
||||
var axis0 = order[ 0 ];
|
||||
var axis1 = order[ 1 ];
|
||||
var axis2 = order[ 2 ];
|
||||
var reversedOrder = order.split( "" ).reverse().join( "" );
|
||||
|
||||
var rotate2 = angleBetweenPlaneAndVector(
|
||||
coordinate[ axis2 ],
|
||||
worldUnitCoordinate[ axis0 ],
|
||||
coordinate[ axis0 ] );
|
||||
|
||||
// The r2 is the reverse of rotate for axis2
|
||||
// The coordinate1 is the coordinate before rotate of axis2
|
||||
var r2 = { order: reversedOrder };
|
||||
r2[ axis2 ] = -rotate2;
|
||||
|
||||
var coordinate1 = rotateCoordinate( coordinate, r2 );
|
||||
|
||||
// Calculate the rotation for axis1
|
||||
var rotate1 = angleBetweenTwoVector(
|
||||
coordinate1[ axis1 ],
|
||||
worldUnitCoordinate[ axis0 ],
|
||||
coordinate1[ axis0 ] );
|
||||
|
||||
// Calculate the rotation for axis0
|
||||
var rotate0 = angleBetweenTwoVector(
|
||||
worldUnitCoordinate[ axis0 ],
|
||||
worldUnitCoordinate[ axis1 ],
|
||||
coordinate1[ axis1 ] );
|
||||
|
||||
var rotation = { };
|
||||
rotation.order = order;
|
||||
rotation[ axis0 ] = roundNumber( rotate0 );
|
||||
rotation[ axis1 ] = roundNumber( rotate1 );
|
||||
rotation[ axis2 ] = roundNumber( rotate2 );
|
||||
|
||||
return rotation;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the possible rotations from unit coordinate
|
||||
* to specified coordinate.
|
||||
*/
|
||||
var possibleRotations = function( coordinate ) {
|
||||
var orders = [ "xyz", "xzy", "yxz", "yzx", "zxy", "zyx" ];
|
||||
var rotations = [ ];
|
||||
|
||||
for ( var i = 0; i < orders.length; ++i ) {
|
||||
rotations.push(
|
||||
coordinateToOrderedRotation( coordinate, orders[ i ] )
|
||||
);
|
||||
}
|
||||
|
||||
return rotations;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate a degree which in range (-180, 180] of baseDeg
|
||||
*/
|
||||
var nearestAngle = function( baseDeg, deg ) {
|
||||
while ( deg > baseDeg + 180 ) {
|
||||
deg -= 360;
|
||||
}
|
||||
|
||||
while ( deg < baseDeg - 180 ) {
|
||||
deg += 360;
|
||||
}
|
||||
|
||||
return deg;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a base rotation and multiple rotations, return the best one.
|
||||
*
|
||||
* The best one is the one has least rotate from base.
|
||||
*/
|
||||
var bestRotation = function( baseRotate, rotations ) {
|
||||
var bestScore;
|
||||
var bestRotation;
|
||||
|
||||
for ( var i = 0; i < rotations.length; ++i ) {
|
||||
var rotation = {
|
||||
order: rotations[ i ].order,
|
||||
x: nearestAngle( baseRotate.x, rotations[ i ].x ),
|
||||
y: nearestAngle( baseRotate.y, rotations[ i ].y ),
|
||||
z: nearestAngle( baseRotate.z, rotations[ i ].z )
|
||||
};
|
||||
|
||||
var score = Math.abs( rotation.x - baseRotate.x ) +
|
||||
Math.abs( rotation.y - baseRotate.y ) +
|
||||
Math.abs( rotation.z - baseRotate.z );
|
||||
|
||||
if ( !i || ( score < bestScore ) ) {
|
||||
bestScore = score;
|
||||
bestRotation = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
return bestRotation;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a coordinate, return the best rotation to achieve it.
|
||||
*
|
||||
* The baseRotate is used to select the near rotation from it.
|
||||
*/
|
||||
var coordinateToRotation = function( baseRotate, coordinate ) {
|
||||
var rotations = possibleRotations( coordinate );
|
||||
|
||||
return bestRotation( baseRotate, rotations );
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply a relative rotation to the base rotation.
|
||||
*
|
||||
* Calculate the coordinate after the rotation on each axis,
|
||||
* and finally find out a one step rotation has the effect
|
||||
* of two rotation.
|
||||
*
|
||||
* If there're multiple way to accomplish, select the one
|
||||
* that is nearest to the base.
|
||||
*
|
||||
* Return one rotation has the same effect.
|
||||
*/
|
||||
var combineRotations = function( rotations ) {
|
||||
|
||||
// No rotation
|
||||
if ( rotations.length <= 0 ) {
|
||||
return { x:0, y:0, z:0, order:"xyz" };
|
||||
}
|
||||
|
||||
// Find out the base coordinate
|
||||
var coordinate = worldUnitCoordinate;
|
||||
|
||||
// One by one apply rotations in order
|
||||
for ( var i = 0; i < rotations.length; i++ ) {
|
||||
coordinate = rotateCoordinate( coordinate, rotations[ i ] );
|
||||
}
|
||||
|
||||
// Calculate one rotation from unit coordinate to rotated
|
||||
// coordinate. Because there're multiple possibles,
|
||||
// select the one nearest to the base
|
||||
var rotate = coordinateToRotation( rotations[ 0 ], coordinate );
|
||||
|
||||
return rotate;
|
||||
};
|
||||
|
||||
var translateRelative = function( relative, prevRotation ) {
|
||||
var result = rotateVector(
|
||||
worldUnitCoordinate, relative, prevRotation );
|
||||
result.rotate = combineRotations(
|
||||
[ prevRotation, relative.rotate ] );
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
var lib = {
|
||||
translateRelative: translateRelative
|
||||
};
|
||||
|
||||
roots[ "impress-root-" + rootId ] = lib;
|
||||
return lib;
|
||||
};
|
||||
|
||||
// Let impress core know about the existence of this library
|
||||
window.impress.addLibraryFactory( { rotation: libraryFactory } );
|
||||
|
||||
} )( document, window );
|
||||
0
src/lib/rotation.ts
Normal file
0
src/lib/rotation.ts
Normal file
131
src/lib/util.js
131
src/lib/util.js
@@ -1,131 +0,0 @@
|
||||
/**
|
||||
* Common utility functions
|
||||
*
|
||||
* Copyright 2011-2012 Bartek Szopka (@bartaz)
|
||||
* Henrik Ingo (c) 2016
|
||||
* MIT License
|
||||
*/
|
||||
|
||||
( function( document, window ) {
|
||||
"use strict";
|
||||
var roots = [];
|
||||
|
||||
var libraryFactory = function( rootId ) {
|
||||
if ( roots[ rootId ] ) {
|
||||
return roots[ rootId ];
|
||||
}
|
||||
|
||||
// `$` returns first element for given CSS `selector` in the `context` of
|
||||
// the given element or whole document.
|
||||
var $ = function( selector, context ) {
|
||||
context = context || document;
|
||||
return context.querySelector( selector );
|
||||
};
|
||||
|
||||
// `$$` return an array of elements for given CSS `selector` in the `context` of
|
||||
// the given element or whole document.
|
||||
var $$ = function( selector, context ) {
|
||||
context = context || document;
|
||||
return arrayify( context.querySelectorAll( selector ) );
|
||||
};
|
||||
|
||||
// `arrayify` takes an array-like object and turns it into real Array
|
||||
// to make all the Array.prototype goodness available.
|
||||
var arrayify = function( a ) {
|
||||
return [].slice.call( a );
|
||||
};
|
||||
|
||||
// `byId` returns element with given `id` - you probably have guessed that ;)
|
||||
var byId = function( id ) {
|
||||
return document.getElementById( id );
|
||||
};
|
||||
|
||||
// `getElementFromHash` returns an element located by id from hash part of
|
||||
// window location.
|
||||
var getElementFromHash = function() {
|
||||
|
||||
// Get id from url # by removing `#` or `#/` from the beginning,
|
||||
// so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work
|
||||
var encoded = window.location.hash.replace( /^#\/?/, "" );
|
||||
return byId( decodeURIComponent( encoded ) );
|
||||
};
|
||||
|
||||
// `getUrlParamValue` return a given URL parameter value if it exists
|
||||
// `undefined` if it doesn't exist
|
||||
var getUrlParamValue = function( parameter ) {
|
||||
var chunk = window.location.search.split( parameter + "=" )[ 1 ];
|
||||
var value = chunk && chunk.split( "&" )[ 0 ];
|
||||
|
||||
if ( value !== "" ) {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
// Throttling function calls, by Remy Sharp
|
||||
// http://remysharp.com/2010/07/21/throttling-function-calls/
|
||||
var throttle = function( fn, delay ) {
|
||||
var timer = null;
|
||||
return function() {
|
||||
var context = this, args = arguments;
|
||||
window.clearTimeout( timer );
|
||||
timer = window.setTimeout( function() {
|
||||
fn.apply( context, args );
|
||||
}, delay );
|
||||
};
|
||||
};
|
||||
|
||||
// `toNumber` takes a value given as `numeric` parameter and tries to turn
|
||||
// it into a number. If it is not possible it returns 0 (or other value
|
||||
// given as `fallback`).
|
||||
var toNumber = function( numeric, fallback ) {
|
||||
return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric );
|
||||
};
|
||||
|
||||
/**
|
||||
* Extends toNumber() to correctly compute also relative-to-screen-size values 5w and 5h.
|
||||
*
|
||||
* Returns the computed value in pixels with w/h postfix removed.
|
||||
*/
|
||||
var toNumberAdvanced = function( numeric, fallback ) {
|
||||
if ( typeof numeric !== "string" ) {
|
||||
return toNumber( numeric, fallback );
|
||||
}
|
||||
var ratio = numeric.match( /^([+-]*[\d\.]+)([wh])$/ );
|
||||
if ( ratio == null ) {
|
||||
return toNumber( numeric, fallback );
|
||||
} else {
|
||||
var value = parseFloat( ratio[ 1 ] );
|
||||
var config = window.impress.getConfig();
|
||||
var multiplier = ratio[ 2 ] === "w" ? config.width : config.height;
|
||||
return value * multiplier;
|
||||
}
|
||||
};
|
||||
|
||||
// `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data
|
||||
// and triggers it on element given as `el`.
|
||||
var triggerEvent = function( el, eventName, detail ) {
|
||||
var event = document.createEvent( "CustomEvent" );
|
||||
event.initCustomEvent( eventName, true, true, detail );
|
||||
el.dispatchEvent( event );
|
||||
};
|
||||
|
||||
var lib = {
|
||||
$: $,
|
||||
$$: $$,
|
||||
arrayify: arrayify,
|
||||
byId: byId,
|
||||
getElementFromHash: getElementFromHash,
|
||||
throttle: throttle,
|
||||
toNumber: toNumber,
|
||||
toNumberAdvanced: toNumberAdvanced,
|
||||
triggerEvent: triggerEvent,
|
||||
getUrlParamValue: getUrlParamValue
|
||||
};
|
||||
roots[ rootId ] = lib;
|
||||
return lib;
|
||||
};
|
||||
|
||||
// Let impress core know about the existence of this library
|
||||
window.impress.addLibraryFactory( { util: libraryFactory } );
|
||||
|
||||
} )( document, window );
|
||||
0
src/lib/util.ts
Normal file
0
src/lib/util.ts
Normal file
Reference in New Issue
Block a user