Return smoothed drag end velocity with event

This commit is contained in:
Jess Telford 2016-09-28 22:42:51 +10:00
parent 691dc21a4d
commit c7d1fe0c0c
7 changed files with 177 additions and 12 deletions

View file

@ -32,6 +32,7 @@ Emitted with the following info:
Emitted with the following info:
- `offset: {x, y, z}` - The offset from entity center to drag position.
- `velocity: {x, y, z}` - The smoothed velocity of the entity at dragend time.
- `depth` - the perpendicular distance from the screen to align the entity while
dragging
- `clientX` - the final mouse event's `clientX` value

View file

@ -12,22 +12,33 @@
h1 {
font-weight: 300;
}
a {
color: #FAFAFA;
a.demo-link {
display: block;
padding: 15px 0;
}
a {
color: #FAFAFA;
}
</style>
</head>
<body>
<h1>A-Frame Click & Drag Component</h1>
<a href="basic/">Basic Demo</a>
<a class="demo-link" href="basic/">Basic Demo</a>
<p>Click + Drag entities on the screen. Note the plane cannot be dragged (it does not have the "click-drag" attribute).</p>
<p>Try the WASD keys to move around while dragging an entity!</p>
<hr />
<a href="events/">Events Demo</a>
<a class="demo-link" href="events/">Events Demo</a>
<p>Events are fired for beginning to drag, ending a drag, and for each drag event in etween.</p>
<p>This example shows how those events can be used to "ghost" a dragged entity</p>
<p>This example shows how those events can be used to "ghost" a dragged entity.</p>
<hr />
<a class="demo-link" href="physics/">Physics Demo</a>
<p>Calculating the velocity at the time of drag end.</p>
<p>Combined with a physics library (for example; <a href="https://github.com/donmccurdy/aframe-extras/tree/master/src/physics">aframe-extras physics</a>), we get some very nice interactions.</p>
<p>Try gently tossing the ball around / throwing it at the ground.</p>
<!-- GitHub Corner. -->
<a href="https://github.com/jesstelford/aframe-click-drag-component" class="github-corner">

View file

@ -1,3 +1,7 @@
import aframe from 'aframe';
import extras from 'aframe-extras';
import clickDragComponent from '../src/index';
extras.physics.registerAll(aframe);
clickDragComponent(aframe);

View file

@ -0,0 +1,42 @@
<html>
<head>
<title>A-Frame Click & Drag Component - Events</title>
<script src="../build.js"></script>
</head>
<body>
<a-scene physics="debug: true">
<a-sphere
click-drag
dynamic-body="mass: 20"
position="0 3 -1"
radius="1.25"
color="#EF2D5E"
>
</a-sphere>
<a-plane static-body rotation="-90 0 0" width="200" height="200" color="#7BC8A4"></a-plane>
<a-sky color="#ECECEC"></a-sky>
<a-entity position="0 0 3.8">
<a-camera look-controls-enabled="false"></a-camera>
</a-entity>
<script>
var draggable = document.querySelector('[click-drag]');
draggable.addEventListener('dragstart', function(dragInfo) {
draggable.pause();
});
draggable.addEventListener('dragend', function(dragInfo) {
var x = dragInfo.detail.velocity.x;
var y = dragInfo.detail.velocity.y;
var z = dragInfo.detail.velocity.z;
draggable.play();
draggable.body.velocity.set(x, y, z);
console.log('drag end', dragInfo.detail.velocity);
});
</script>
</a-scene>
</body>
</html>

View file

@ -5,11 +5,11 @@
"main": "lib/index.js",
"browser": "dist/aframe-click-drag-component.min.js",
"scripts": {
"build-example": "browserify examples/main.js --debug --verbose -t babelify -t [envify --NODE_ENV development ] -o examples/build.js",
"build-example": "browserify examples/main.js --debug --verbose -g uglifyify -t [ rollupify --config rollup.config.js ] -t babelify -t [envify --NODE_ENV development ] -o examples/build.js",
"build-lib": "mkdir -p lib && babel src/index.js -o lib/build.js",
"dist": "browserify src/index.js --verbose --debug --standalone registerAframeClickDragComponent -g uglifyify -t rollupify -t babelify -t [envify --NODE_ENV production ] | exorcist dist/out.map > dist/out.js && uglifyjs dist/out.js --screw-ie8 -c -m --in-source-map dist/out.map --source-map dist/aframe-click-drag-component.min.js.map --source-map-url aframe-click-drag-component.min.js.map > dist/aframe-click-drag-component.min.js && rm dist/out*",
"dist": "browserify src/index.js --verbose --debug --standalone registerAframeClickDragComponent -g uglifyify -t [ rollupify --config rollup.config.js ] -t babelify -t [envify --NODE_ENV production ] | exorcist dist/out.map > dist/out.js && uglifyjs dist/out.js --screw-ie8 -c -m --in-source-map dist/out.map --source-map dist/aframe-click-drag-component.min.js.map --source-map-url aframe-click-drag-component.min.js.map > dist/aframe-click-drag-component.min.js && rm dist/out*",
"test": "npm run test:lint",
"test:lint": "eslint .",
"test:lint": "eslint ./src",
"start": "budo examples/main.js:../build.js --serve build.js --dir examples --port 8000 --live --open -- --debug --verbose -t babelify -t [envify --NODE_ENV development ]",
"prepublish": "in-publish && npm run dist && npm run build-lib || not-in-publish",
"preghpages": "npm run build-example && rm -rf gh-pages && mkdir gh-pages && cp -r examples/* gh-pages",
@ -38,6 +38,7 @@
},
"devDependencies": {
"aframe": "^0.3.0",
"aframe-extras": "^2.5.3",
"babel-cli": "^6.14.0",
"babel-plugin-transform-object-rest-spread": "^6.8.0",
"babel-preset-es2015": "^6.9.0",
@ -55,12 +56,15 @@
"exorcist": "^0.4.0",
"ghpages": "^0.0.8",
"in-publish": "^2.0.0",
"rollup-plugin-commonjs": "^5.0.4",
"rollup-plugin-node-resolve": "^2.0.0",
"rollupify": "^0.3.4",
"uglify-js": "^2.7.3",
"uglifyify": "^3.0.3"
},
"dependencies": {
"deep-equal": "^1.0.1"
"deep-equal": "^1.0.1",
"simple-statistics": "^2.1.0"
},
"babel": {
"presets": [

12
rollup.config.js Normal file
View file

@ -0,0 +1,12 @@
module.exports = {
plugins: [
require('rollup-plugin-node-resolve')({
jsnext: true,
main: true,
browser: true,
}),
require('rollup-plugin-commonjs')({
include: 'node_modules/**',
}),
],
};

View file

@ -1,10 +1,14 @@
import deepEqual from 'deep-equal';
import linearRegression from 'simple-statistics/src/linear_regression';
import linearRegressionLine from 'simple-statistics/src/linear_regression_line';
const COMPONENT_NAME = 'click-drag';
const DRAG_START_EVENT = 'dragstart';
const DRAG_MOVE_EVENT = 'dragmove';
const DRAG_END_EVENT = 'dragend';
const TIME_TO_KEEP_LOG = 300;
function cameraPositionToVec3(camera, vec3) {
let element = camera;
@ -339,6 +343,24 @@ const {initialize, tearDown} = (function closeOverInitAndTearDown() {
let removeDragListeners;
let draggedElement;
let dragInfo;
const positionLog = [];
function cleanUpPositionLog() {
const now = performance.now();
while (positionLog.length && now - positionLog[0].time > TIME_TO_KEEP_LOG) {
// remove the first element;
positionLog.shift();
}
}
function onDragged({detail: {nextPosition}}) {
// Continuously clean up so we don't get huge logs built up
cleanUpPositionLog();
positionLog.push({
position: Object.assign({}, nextPosition),
time: performance.now(),
});
}
function onMouseDown({clientX, clientY}) {
@ -347,7 +369,17 @@ const {initialize, tearDown} = (function closeOverInitAndTearDown() {
if (element) {
// Can only drag one item at a time, so no need to check if any
// listener is already set up
removeDragListeners = dragItem(THREE, element, offset, camera, depth, {clientX, clientY});
let removeDragItemListeners = dragItem(
THREE,
element,
offset,
camera,
depth,
{
clientX,
clientY,
}
);
draggedElement = element;
@ -359,12 +391,71 @@ const {initialize, tearDown} = (function closeOverInitAndTearDown() {
};
element.emit(DRAG_START_EVENT, dragInfo);
element.addEventListener(DRAG_MOVE_EVENT, onDragged);
removeDragListeners = _ => {
element.removeEventListener(DRAG_MOVE_EVENT, onDragged);
// eslint-disable-next-line no-unused-expressions
removeDragItemListeners && removeDragItemListeners();
// in case this removal function gets called more than once
removeDragItemListeners = null;
};
}
}
function fitLineToVelocity(dimension) {
if (positionLog.length < 2) {
return 0;
}
const velocities = positionLog
// Pull out just the x, y, or z values
.map(log => ({time: log.time, value: log.position[dimension]}))
// Then convert that into an array of array pairs [time, value]
.reduce((memo, log, index, collection) => {
// skip the first item (we're looking for pairs)
if (index === 0) {
return memo;
}
const deltaPosition = log.value - collection[index - 1].value;
const deltaTime = (log.time - collection[index - 1].time) / 1000;
// The new value is the change in position
memo.push([log.time, deltaPosition / deltaTime]);
return memo;
}, []);
// Calculate the line function
const lineFunction = linearRegressionLine(linearRegression(velocities));
// Calculate what the point was at the end of the line
// ie; the velocity at the time the drag stopped
return lineFunction(positionLog[positionLog.length - 1].time);
}
function onMouseUp({clientX, clientY}) {
draggedElement.emit(DRAG_END_EVENT, Object.assign({}, dragInfo, {clientX, clientY}));
cleanUpPositionLog();
const velocity = {
x: fitLineToVelocity('x'),
y: fitLineToVelocity('y'),
z: fitLineToVelocity('z'),
};
draggedElement.emit(
DRAG_END_EVENT,
Object.assign({}, dragInfo, {clientX, clientY, velocity})
);
removeDragListeners && removeDragListeners(); // eslint-disable-line no-unused-expressions
removeDragListeners = undefined;
}
@ -487,4 +578,4 @@ export default function aframeDraggableComponent(aframe, componentName = COMPONE
didMount(this, THREE, componentName);
},
});
};
}