Add relative move and rotate to rel plugin (#794)
The relative position in rel plugin is currently based on the world coordinate. So for the same effect, like fly in from the right-hand side, we must use different `data-rel-x/y/z` value. Why not let the plugin do the hard part? So I introduce a `data-rel-position`, when set to `relative`, all relative attribute is based on the position and rotation of previous slide. So no matter the rotation of previous slide, data-rel-x="1000" always looks like fly in from the right-hand side. We can change the position and rotation of one slide, and the position of all following slides will be changed too. When `data-rel-position` is set to `relative`, relative rotation has a clear meaning. It describes the relative rotations between slides. We don't need to set rotations for all slide, setting the key slides is enough. If `data-rel-position` is not relative, the effect of `data-rel-rotate-x/y/z` is not clear, so they're only used when `data-rel-position="relative"`. After the introduction of relative rotation, there're 6 attribute that will inherit from previous slide. If we want to set a relative X move, we have to set all other 5 attributes to 0. It's boring. So a `data-rel-clear` is used to set all 6 attributes to 0, and then the value specified in current slide is applied. The `examples/3D-positions/index.html` shows some usage. As you can see, the html code of two slide ring is the same, and slides except for the first two in a ring has no position attributes. It work by inheriting the previous one. This PR invokes a lot math calculations. Basically, the rotation of a slide is translated into the coordinate describing the directions of X/Y/Z axes. And `data-rel-x/y/z` can be easily calculated by that. The rotations is the hard part, I mainly use the algorithm in the Quaternions and spatial rotation - Wikipedia to compose two and more rotations. I'm not a math guy, hope I don't make much mistakes.
This commit is contained in:
429
src/lib/rotation.js
Normal file
429
src/lib/rotation.js
Normal file
@@ -0,0 +1,429 @@
|
||||
/**
|
||||
* 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 );
|
||||
@@ -22,6 +22,13 @@ Following html attributes are supported for step elements:
|
||||
data-rel-z
|
||||
data-rel-to
|
||||
|
||||
data-rel-rotate-x
|
||||
data-rel-rotate-y
|
||||
data-rel-rotate-z
|
||||
|
||||
data-rel-position
|
||||
data-rel-reset
|
||||
|
||||
Non-zero values are also inherited from the previous step. This makes it easy to
|
||||
create a boring presentation where each slide shifts for example 1000px down
|
||||
from the previous.
|
||||
@@ -47,6 +54,31 @@ If you need a reference to a step that is shown later make use of the goto plugi
|
||||
<div id="shown-earlier" class="step" data-rel-to="shown-later" data-rel-x="1000" data-rel-y="500" data-goto-prev="shown-first" data-goto-next="shown-later">
|
||||
<div id="shown-last" class="step" data-goto-prev="shown-later">
|
||||
|
||||
Relative positioning
|
||||
--------------------
|
||||
|
||||
All `data-rel-x`/`y`/`z` is used world coordinate by default. So the value should be alternated according to the rotation state of previous slides.
|
||||
|
||||
To easy relative positioning, the `data-rel-position` attribute is introduced.
|
||||
|
||||
`data-rel-position` has a default value of "absolute", which works like this attribute is not introduced.
|
||||
|
||||
When `data-rel-position="relative"`, everything changed. The rotation of previous is no need to be considered, you can set all the `data-rel-` attributes as if the previous has no rotation. This plugin will calculate the acual position according to the position and rotation of the previous slide and the relative settings. This make it possible to split a presentation into parts, construct each parts standalone without consider the final position, and then assemble them together.
|
||||
|
||||
For example, if you want the slide slided in from the right hand side, all you need is `data-rel-x="1000"`, no matter the rotation of the previous slide. If the previous slide has `data-rotate-z="90"`, the actual attribute of this slide will work like `data-rel-y="1000"`.
|
||||
|
||||
Not only relative positions, the relative rotations can be used while `data-rel-position="relative"`.
|
||||
|
||||
For example, `data-rel-rotate-y="45"` will make the slide has an angle of 45 degree to the previous slide. It make it easy to build a circle and do other complicated positioning.
|
||||
|
||||
If not set, the `data-rel-position` attribute will be inherited from previous slide. So we only need to set it at the first slide, then all done.
|
||||
|
||||
To avoid the boring need to set most `data-rel-*` to zero, but set only one or two ones, `data-rel-reset` attribute can be used:
|
||||
|
||||
- `data-rel-reset` equals to: `data-rel-x="0" data-rel-y="0" data-rel-z="0" data-rel-rotate-x="0" data-rel-rotate-y="0" data-rel-rotate-z="0"`
|
||||
- `data-rel-reset="all"` works like `data-rel-reset`, in additions `data-rotate-x="0" data-rotate-y="0" data-rotate-z="0"`
|
||||
|
||||
When `data-rel-position="relative"` and `data-rel-to` is specified, `data-rotate-*` and `data-rel-*` will be inherited from specified slide too.
|
||||
|
||||
IMPORTANT: Incompatible change
|
||||
------------------------------
|
||||
|
||||
@@ -48,9 +48,9 @@
|
||||
( function( document, window ) {
|
||||
"use strict";
|
||||
|
||||
var api;
|
||||
var startingState = {};
|
||||
|
||||
var api;
|
||||
var toNumber;
|
||||
var toNumberAdvanced;
|
||||
|
||||
@@ -60,12 +60,21 @@
|
||||
if ( !prev ) {
|
||||
|
||||
// For the first step, inherit these defaults
|
||||
prev = { x:0, y:0, z:0, relative: { x:0, y:0, z:0 } };
|
||||
prev = {
|
||||
x:0, y:0, z:0,
|
||||
rotate: { x:0, y:0, z:0, order:"xyz" },
|
||||
relative: {
|
||||
position: "absolute",
|
||||
x:0, y:0, z:0,
|
||||
rotate: { x:0, y:0, z:0, order:"xyz" }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var ref = prev;
|
||||
if ( data.relTo ) {
|
||||
|
||||
var ref = document.getElementById( data.relTo );
|
||||
ref = document.getElementById( data.relTo );
|
||||
if ( ref ) {
|
||||
|
||||
// Test, if it is a previous step that already has some assigned position data
|
||||
@@ -73,7 +82,42 @@
|
||||
prev.x = toNumber( ref.getAttribute( "data-x" ) );
|
||||
prev.y = toNumber( ref.getAttribute( "data-y" ) );
|
||||
prev.z = toNumber( ref.getAttribute( "data-z" ) );
|
||||
prev.relative = {};
|
||||
|
||||
var prevPosition = ref.getAttribute( "data-rel-position" ) || "absolute";
|
||||
|
||||
if ( prevPosition !== "relative" ) {
|
||||
|
||||
// For compatibility with the old behavior, doesn't inherit otherthings,
|
||||
// just like a reset.
|
||||
prev.rotate = { x:0, y:0, z:0, order: "xyz" };
|
||||
prev.relative = {
|
||||
position: "absolute",
|
||||
x:0, y:0, z:0,
|
||||
rotate: { x:0, y:0, z:0, order:"xyz" }
|
||||
};
|
||||
} else {
|
||||
|
||||
// For data-rel-position="relative", inherit all
|
||||
prev.rotate.y = toNumber( ref.getAttribute( "data-rotate-y" ) );
|
||||
prev.rotate.x = toNumber( ref.getAttribute( "data-rotate-x" ) );
|
||||
prev.rotate.z = toNumber(
|
||||
ref.getAttribute( "data-rotate-z" ) ||
|
||||
ref.getAttribute( "data-rotate" ) );
|
||||
|
||||
// We also inherit relatives from relTo slide
|
||||
prev.relative = {
|
||||
position: prevPosition,
|
||||
x: toNumberAdvanced( ref.getAttribute( "data-rel-x" ), 0 ),
|
||||
y: toNumberAdvanced( ref.getAttribute( "data-rel-y" ), 0 ),
|
||||
z: toNumberAdvanced( ref.getAttribute( "data-rel-z" ), 0 ),
|
||||
rotate: {
|
||||
x: toNumberAdvanced( ref.getAttribute( "data-rel-rotate-x" ), 0 ),
|
||||
y: toNumberAdvanced( ref.getAttribute( "data-rel-rotate-y" ), 0 ),
|
||||
z: toNumberAdvanced( ref.getAttribute( "data-rel-rotate-z" ), 0 ),
|
||||
order: ( ref.getAttribute( "data-rel-rotate-order" ) || "xyz" )
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
window.console.error(
|
||||
"impress.js rel plugin: Step \"" + data.relTo + "\" is not defined " +
|
||||
@@ -94,34 +138,95 @@
|
||||
}
|
||||
}
|
||||
|
||||
// While ``data-rel-reset="relative"`` or just ``data-rel-reset``,
|
||||
// ``data-rel-x/y/z`` and ``data-rel-rotate-x/y/z`` will have default value of 0,
|
||||
// instead of inherit from previous slide.
|
||||
//
|
||||
// If ``data-rel-reset="all"``, ``data-rotate-*`` are not inherited from previous slide too.
|
||||
// So ``data-rel-reset="all" data-rotate-x="90"`` means
|
||||
// ``data-rotate-x="90" data-rotate-y="0" data-rotate-z="0"``, we doesn't need to
|
||||
// bother clearing all unneeded attributes.
|
||||
|
||||
var inheritRotation = true;
|
||||
if ( el.hasAttribute( "data-rel-reset" ) ) {
|
||||
|
||||
// Don't inherit from prev, just use the relative setting for current element
|
||||
prev.relative = {
|
||||
position: prev.relative.position,
|
||||
x:0, y:0, z:0,
|
||||
rotate: { x:0, y:0, z:0, order: "xyz" } };
|
||||
|
||||
if ( data.relReset === "all" ) {
|
||||
inheritRotation = false;
|
||||
}
|
||||
}
|
||||
|
||||
var step = {
|
||||
x: toNumber( data.x, prev.x ),
|
||||
y: toNumber( data.y, prev.y ),
|
||||
z: toNumber( data.z, prev.z ),
|
||||
rotate: {
|
||||
x: toNumber( data.rotateX, 0 ),
|
||||
y: toNumber( data.rotateY, 0 ),
|
||||
z: toNumber( data.rotateZ || data.rotate, 0 ),
|
||||
order: data.rotateOrder || "xyz"
|
||||
},
|
||||
relative: {
|
||||
position: data.relPosition || prev.relative.position,
|
||||
x: toNumberAdvanced( data.relX, prev.relative.x ),
|
||||
y: toNumberAdvanced( data.relY, prev.relative.y ),
|
||||
z: toNumberAdvanced( data.relZ, prev.relative.z )
|
||||
z: toNumberAdvanced( data.relZ, prev.relative.z ),
|
||||
rotate: {
|
||||
x: toNumber( data.relRotateX, prev.relative.rotate.x ),
|
||||
y: toNumber( data.relRotateY, prev.relative.rotate.y ),
|
||||
z: toNumber( data.relRotateZ, prev.relative.rotate.z ),
|
||||
order: data.rotateOrder || "xyz"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The final relatives maybe or maybe not the same with orignal data-rel-*
|
||||
var relative = step.relative;
|
||||
|
||||
if ( step.relative.position === "relative" && inheritRotation ) {
|
||||
|
||||
// Calculate relatives based on previous slide
|
||||
relative = api.lib.rotation.translateRelative(
|
||||
step.relative, prev.rotate );
|
||||
|
||||
// Convert rotations to values that works with step.rotate
|
||||
relative.rotate.x -= step.rotate.x;
|
||||
relative.rotate.y -= step.rotate.y;
|
||||
relative.rotate.z -= step.rotate.z;
|
||||
}
|
||||
|
||||
// Relative position is ignored/zero if absolute is given.
|
||||
// Note that this also has the effect of resetting any inherited relative values.
|
||||
if ( data.x !== undefined ) {
|
||||
step.relative.x = 0;
|
||||
relative.x = step.relative.x = 0;
|
||||
}
|
||||
if ( data.y !== undefined ) {
|
||||
step.relative.y = 0;
|
||||
relative.y = step.relative.y = 0;
|
||||
}
|
||||
if ( data.z !== undefined ) {
|
||||
step.relative.z = 0;
|
||||
relative.z = step.relative.z = 0;
|
||||
}
|
||||
if ( data.rotateX !== undefined || !inheritRotation ) {
|
||||
relative.rotate.x = step.relative.rotate.x = 0;
|
||||
}
|
||||
if ( data.rotateY !== undefined || !inheritRotation ) {
|
||||
relative.rotate.y = step.relative.rotate.y = 0;
|
||||
}
|
||||
if ( data.rotateZ !== undefined || data.rotate !== undefined || !inheritRotation ) {
|
||||
relative.rotate.z = step.relative.rotate.z = 0;
|
||||
}
|
||||
|
||||
// Apply relative position to absolute position, if non-zero
|
||||
// Note that at this point, the relative values contain a number value of pixels.
|
||||
step.x = step.x + step.relative.x;
|
||||
step.y = step.y + step.relative.y;
|
||||
step.z = step.z + step.relative.z;
|
||||
step.x = step.x + relative.x;
|
||||
step.y = step.y + relative.y;
|
||||
step.z = step.z + relative.z;
|
||||
step.rotate.x = step.rotate.x + relative.rotate.x;
|
||||
step.rotate.y = step.rotate.y + relative.rotate.y;
|
||||
step.rotate.z = step.rotate.z + relative.rotate.z;
|
||||
|
||||
return step;
|
||||
};
|
||||
@@ -143,7 +248,16 @@
|
||||
z: el.getAttribute( "data-z" ),
|
||||
relX: el.getAttribute( "data-rel-x" ),
|
||||
relY: el.getAttribute( "data-rel-y" ),
|
||||
relZ: el.getAttribute( "data-rel-z" )
|
||||
relZ: el.getAttribute( "data-rel-z" ),
|
||||
rotateX: el.getAttribute( "data-rotate-x" ),
|
||||
rotateY: el.getAttribute( "data-rotate-y" ),
|
||||
rotateZ: el.getAttribute( "data-rotate-z" ),
|
||||
rotate: el.getAttribute( "data-rotate" ),
|
||||
relRotateX: el.getAttribute( "data-rel-rotate-x" ),
|
||||
relRotateY: el.getAttribute( "data-rel-rotate-y" ),
|
||||
relRotateZ: el.getAttribute( "data-rel-rotate-z" ),
|
||||
relPosition: el.getAttribute( "data-rel-position" ),
|
||||
rotateOrder: el.getAttribute( "data-rotate-order" )
|
||||
} );
|
||||
var step = computeRelativePositions( el, prev );
|
||||
|
||||
@@ -151,6 +265,17 @@
|
||||
el.setAttribute( "data-x", step.x );
|
||||
el.setAttribute( "data-y", step.y );
|
||||
el.setAttribute( "data-z", step.z );
|
||||
el.setAttribute( "data-rotate-x", step.rotate.x );
|
||||
el.setAttribute( "data-rotate-y", step.rotate.y );
|
||||
el.setAttribute( "data-rotate-z", step.rotate.z );
|
||||
el.setAttribute( "data-rotate-order", step.rotate.order );
|
||||
el.setAttribute( "data-rel-position", step.relative.position );
|
||||
el.setAttribute( "data-rel-x", step.relative.x );
|
||||
el.setAttribute( "data-rel-y", step.relative.y );
|
||||
el.setAttribute( "data-rel-z", step.relative.z );
|
||||
el.setAttribute( "data-rel-rotate-x", step.relative.rotate.x );
|
||||
el.setAttribute( "data-rel-rotate-y", step.relative.rotate.y );
|
||||
el.setAttribute( "data-rel-rotate-z", step.relative.rotate.z );
|
||||
prev = step;
|
||||
}
|
||||
};
|
||||
@@ -164,28 +289,27 @@
|
||||
event.detail.api.lib.gc.pushCallback( function() {
|
||||
var steps = startingState[ root.id ];
|
||||
var step;
|
||||
var attrs = [
|
||||
[ "x", "relX" ],
|
||||
[ "y", "relY" ],
|
||||
[ "z", "relZ" ],
|
||||
[ "rotate-x", "relRotateX" ],
|
||||
[ "rotate-y", "relRotateY" ],
|
||||
[ "rotate-z", "relRotateZ" ],
|
||||
[ "rotate-order", "relRotateOrder" ]
|
||||
];
|
||||
|
||||
while ( step = steps.pop() ) {
|
||||
|
||||
// Reset x/y/z in cases where this plugin has changed it.
|
||||
if ( step.relX !== null ) {
|
||||
if ( step.x === null ) {
|
||||
step.el.removeAttribute( "data-x" );
|
||||
} else {
|
||||
step.el.setAttribute( "data-x", step.x );
|
||||
}
|
||||
}
|
||||
if ( step.relY !== null ) {
|
||||
if ( step.y === null ) {
|
||||
step.el.removeAttribute( "data-y" );
|
||||
} else {
|
||||
step.el.setAttribute( "data-y", step.y );
|
||||
}
|
||||
}
|
||||
if ( step.relZ !== null ) {
|
||||
if ( step.z === null ) {
|
||||
step.el.removeAttribute( "data-z" );
|
||||
} else {
|
||||
step.el.setAttribute( "data-z", step.z );
|
||||
for ( var i = 0; i < attrs.length; i++ ) {
|
||||
if ( step[ attrs[ i ][ 1 ] ] !== null ) {
|
||||
if ( step[ attrs[ i ][ 0 ] ] === null ) {
|
||||
step.el.removeAttribute( "data-" + attrs[ i ][ 0 ] );
|
||||
} else {
|
||||
step.el.setAttribute(
|
||||
"data-" + attrs[ i ][ 0 ], step[ attrs[ i ][ 0 ] ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user