diff --git a/README.md b/README.md
index 53b16e6..522a601 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/examples/index.html b/examples/index.html
index becc5f4..6076b54 100644
--- a/examples/index.html
+++ b/examples/index.html
@@ -12,22 +12,33 @@
h1 {
font-weight: 300;
}
- a {
- color: #FAFAFA;
+ a.demo-link {
display: block;
padding: 15px 0;
}
+ a {
+ color: #FAFAFA;
+ }
A-Frame Click & Drag Component
- Basic Demo
+ Basic Demo
Click + Drag entities on the screen. Note the plane cannot be dragged (it does not have the "click-drag" attribute).
Try the WASD keys to move around while dragging an entity!
+
- Events Demo
+
+ Events Demo
Events are fired for beginning to drag, ending a drag, and for each drag event in etween.
- This example shows how those events can be used to "ghost" a dragged entity
+ This example shows how those events can be used to "ghost" a dragged entity.
+
+
+
+ Physics Demo
+ Calculating the velocity at the time of drag end.
+ Combined with a physics library (for example; aframe-extras physics), we get some very nice interactions.
+ Try gently tossing the ball around / throwing it at the ground.
diff --git a/examples/main.js b/examples/main.js
index 9e5ebc8..cedfc9c 100644
--- a/examples/main.js
+++ b/examples/main.js
@@ -1,3 +1,7 @@
import aframe from 'aframe';
+import extras from 'aframe-extras';
import clickDragComponent from '../src/index';
+
+extras.physics.registerAll(aframe);
clickDragComponent(aframe);
+
diff --git a/examples/physics/index.html b/examples/physics/index.html
new file mode 100644
index 0000000..a5e4a27
--- /dev/null
+++ b/examples/physics/index.html
@@ -0,0 +1,42 @@
+
+
+ A-Frame Click & Drag Component - Events
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index 269ecad..d229e16 100644
--- a/package.json
+++ b/package.json
@@ -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": [
diff --git a/rollup.config.js b/rollup.config.js
new file mode 100644
index 0000000..8d14c2c
--- /dev/null
+++ b/rollup.config.js
@@ -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/**',
+ }),
+ ],
+};
diff --git a/src/index.js b/src/index.js
index 2d6ad01..2c1084f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -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);
},
});
-};
+}