Skip to content

Commit

Permalink
Add a mouse based cursor / raycaster and applies to the link traversa…
Browse files Browse the repository at this point in the history
…l example
  • Loading branch information
dmarcos committed Jul 13, 2017
1 parent a0f400d commit 9e348bb
Show file tree
Hide file tree
Showing 13 changed files with 124 additions and 23 deletions.
1 change: 1 addition & 0 deletions docs/components/cursor.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ AFRAME.registerComponent('cursor-listener', {
| downEvents | Array of additional events on the entity to *listen* to for triggering `mousedown` (e.g., `triggerdown` for vive-controls). | [] |
| fuse | Whether cursor is fuse-based. | false on desktop, true on mobile |
| fuseTimeout | How long to wait (in milliseconds) before triggering a fuse-based click event. | 1500 |
| mode | Camera (gaze) or mouse controlled. | gaze
| upEvents | Array of additional events on the entity to *listen* to for triggering `mouseup` (e.g., `trackpadup` for daydream-controls). | [] |

To further customize the cursor component, we configure the cursor's dependency
Expand Down
1 change: 1 addition & 0 deletions docs/components/raycaster.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ AFRAME.registerComponent('collider-check', {
| origin | Vector3 coordinate of where the ray should originate from relative to the entity's origin. | 0, 0, 0 |
| recursive | Checks all children of objects if set. Else only checks intersections with root objects. | true |
| showLine | Whether or not to display the raycaster visually with the [line component][line]. | false |
| worldCoordinates | Determine if origin and direction are given in local or world coordinates. | false |

## Events

Expand Down
1 change: 1 addition & 0 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ <h2>Showcase</h2>
<li><a href="showcase/composite/">Composite</a></li>
<li><a href="showcase/curved-mockups/">Curved Mockups</a></li>
<li><a href="showcase/dynamic-lights/">Dynamic Lights</a></li>
<li><a href="showcase/link-traversal/">Link Traversal</a></li>
<li><a href="showcase/tracked-controls/">Tracked Controls</a></li>
<li><a href="showcase/shopping/">Shopping</a></li>
<li><a href="showcase/spheres-and-fog/">Spheres and Fog</a></li>
Expand Down
11 changes: 2 additions & 9 deletions examples/showcase/link-traversal/city.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,17 @@
<meta name="description" content="Links – A-Frame">
<script src="../../../dist/aframe-master.js"></script>
<script src="js/components/aframe-tooltip-component.js"></script>
<script src="js/components/camera-position.js"></script>
<script src="js/components/ground.js"></script>
<script src="js/components/link-controls.js"></script>
<script src="shaders/skyGradient.js"></script>
</head>
<body>
<a-scene fog="color: #241417; near: 0; far: 30;">
<a-scene fog="color: #241417; near: 0; far: 30;" raycaster="far: 100; objects: [link];" cursor="mode: mouse" camera-position>
<a-assets>
<img id="thumbHome" crossOrigin="anonymous" src="https://cdn.aframe.io/link-traversal/thumbs/home.png">
<img id="thumbSunrise" crossOrigin="anonymous" src="https://cdn.aframe.io/link-traversal/thumbs/sunrise.png">
<img id="thumbMountains" crossOrigin="anonymous" src="https://cdn.aframe.io/link-traversal/thumbs/mountains.png">
<a-mixin id="cursor"
cursor="fuse: true; fuseTimeout: 1500"
geometry="primitive: ring; radiusOuter: 0.015;
radiusInner: 0.01; segmentsTheta: 32"
material="color: yellow; shader: flat"
raycaster="far: 30"
position="0 0 -0.75"></a-mixin>
<a-mixin id="-hovering" material="color: springgreen"></a-mixin>
</a-assets>
<a-link href="index.html" position="-3.5 1.5 -1.0" image="#thumbHome"></a-link>
<a-link href="sunrise.html" position="0 1.5 -1.0" image="#thumbSunrise"></a-link>
Expand Down
5 changes: 3 additions & 2 deletions examples/showcase/link-traversal/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
<html>
<head>
<meta charset="utf-8">
<title>Room Scale</title>
<title>Links</title>
<meta name="description" content="Motion Capture Components – A-Frame">
<script src="../../../dist/aframe-master.js"></script>
<script src="js/components/aframe-tooltip-component.js"></script>
<script src="js/components/camera-position.js"></script>
<script src="js/components/ground.js"></script>
<script src="js/components/link-controls.js"></script>
<script src="shaders/skyGradient.js"></script>
</head>
<body>
<a-scene>
<a-scene raycaster="far: 100; objects: [link];" cursor="mode: mouse" camera-position>
<a-assets>
<img id="arena" crossOrigin="anonymous" src="https://cdn.aframe.io/link-traversal/aframeArena.png">
<img id="thumbCity" crossOrigin="anonymous" src="https://cdn.aframe.io/link-traversal/thumbs/city.png">
Expand Down
34 changes: 34 additions & 0 deletions examples/showcase/link-traversal/js/components/camera-position.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* global AFRAME */
AFRAME.registerComponent('camera-position', {
schema: {
mobile: {type: 'vec3', default: '0 1.6 3'},
desktop: {type: 'vec3', default: '0 1.6 3'}
},
init: function () {
this.onCameraSetActive = this.onCameraSetActive.bind(this);
this.resetCamera = this.resetCamera.bind(this);
this.el.addEventListener('camera-set-active', this.onCameraSetActive);
this.el.addEventListener('exit-vr', this.onCameraSetActive);
this.el.addEventListener('enter-vr', this.resetCamera);
},

onCameraSetActive: function () {
var cameraEl = this.el.camera.el;
var data = this.data;
var isMobile = AFRAME.utils.device.isMobile();
var position = isMobile ? data.mobile : data.desktop;
var savedPose = cameraEl.components.camera.savedPose;
if (savedPose) { savedPose.position.z = position.z; }
this.el.camera.el.setAttribute('position', position);
},

resetCamera: function () {
var cameraEl = this.el.camera.el;
var position = cameraEl.getAttribute('position');
cameraEl.setAttribute('position', {
x: position.x,
y: position.y,
z: 0
});
}
});
16 changes: 8 additions & 8 deletions examples/showcase/link-traversal/js/components/link-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,16 +163,16 @@ AFRAME.registerComponent('link-controls', {
},

play: function () {
var el = this.el;
el.addEventListener('mouseenter', this.onMouseEnter);
el.addEventListener('mouseleave', this.onMouseLeave);
var sceneEl = this.el.sceneEl;
sceneEl.addEventListener('mouseenter', this.onMouseEnter);
sceneEl.addEventListener('mouseleave', this.onMouseLeave);
this.addControllerEventListeners();
},

pause: function () {
var el = this.el;
el.removeEventListener('mouseenter', this.onMouseEnter);
el.removeEventListener('mouseleave', this.onMouseLeave);
var sceneEl = this.el.sceneEl;
sceneEl.removeEventListener('mouseenter', this.onMouseEnter);
sceneEl.removeEventListener('mouseleave', this.onMouseLeave);
this.removeControllerEventListeners();
},

Expand Down Expand Up @@ -312,7 +312,7 @@ AFRAME.registerComponent('link-controls', {
var previousSelectedLinkEl = this.selectedLinkEl;
var selectedLinkEl = evt.detail.intersectedEl;
var urlEl = this.urlEl;
if (previousSelectedLinkEl || selectedLinkEl.components.link === undefined) { return; }
if (!selectedLinkEl || previousSelectedLinkEl || selectedLinkEl.components.link === undefined) { return; }
selectedLinkEl.setAttribute('link', 'highlighted', true);
this.selectedLinkElPosition = selectedLinkEl.getAttribute('position');
this.selectedLinkEl = selectedLinkEl;
Expand All @@ -325,7 +325,7 @@ AFRAME.registerComponent('link-controls', {
onMouseLeave: function (evt) {
var selectedLinkEl = this.selectedLinkEl;
var urlEl = this.urlEl;
if (!selectedLinkEl) { return; }
if (!selectedLinkEl || !evt.detail.intersectedEl) { return; }
selectedLinkEl.setAttribute('link', 'highlighted', false);
this.selectedLinkEl = undefined;
if (!urlEl) { return; }
Expand Down
3 changes: 2 additions & 1 deletion examples/showcase/link-traversal/mountains.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
<meta name="description" content="Links – A-Frame">
<script src="../../../dist/aframe-master.js"></script>
<script src="js/components/aframe-tooltip-component.js"></script>
<script src="js/components/camera-position.js"></script>
<script src="js/components/ground.js"></script>
<script src="js/components/link-controls.js"></script>
<script src="shaders/skyGradient.js"></script>
</head>
<body>
<a-scene fog="color: #47c9ff; near: 0; far: 65;">
<a-scene fog="color: #241417; near: 0; far: 30;" raycaster="far: 100; objects: [link];" cursor="mode: mouse" camera-position>
<a-assets>
<img id="thumbHome" crossOrigin="anonymous" src="https://cdn.aframe.io/link-traversal/thumbs/home.png">
<img id="thumbSunrise" crossOrigin="anonymous" src="https://cdn.aframe.io/link-traversal/thumbs/sunrise.png">
Expand Down
3 changes: 2 additions & 1 deletion examples/showcase/link-traversal/sunrise.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
<meta name="description" content="Links – A-Frame">
<script src="../../../dist/aframe-master.js"></script>
<script src="js/components/aframe-tooltip-component.js"></script>
<script src="js/components/camera-position.js"></script>
<script src="js/components/ground.js"></script>
<script src="js/components/link-controls.js"></script>
<script src="shaders/skyGradient.js"></script>
</head>
<body>
<a-scene fog="color: #bc483e; near: 0; far: 65;">
<a-scene fog="color: #241417; near: 0; far: 30;" raycaster="far: 100; objects: [link];" cursor="mode: mouse" camera-position>
<a-assets>
<img id="thumbHome" crossOrigin="anonymous" src="https://cdn.aframe.io/link-traversal/thumbs/home.png">
<img id="thumbCity" crossOrigin="anonymous" src="https://cdn.aframe.io/link-traversal/thumbs/city.png">
Expand Down
35 changes: 34 additions & 1 deletion src/components/cursor.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* global THREE */
var registerComponent = require('../core/component').registerComponent;
var utils = require('../utils/');

Expand Down Expand Up @@ -37,7 +38,8 @@ module.exports.Component = registerComponent('cursor', {
downEvents: {default: []},
fuse: {default: utils.device.isMobile()},
fuseTimeout: {default: 1500, min: 0},
upEvents: {default: []}
upEvents: {default: []},
mode: {default: 'gaze', oneOf: ['gaze', 'mouse']}
},

init: function () {
Expand All @@ -51,6 +53,11 @@ module.exports.Component = registerComponent('cursor', {
this.onCursorUp = bind(this.onCursorUp, this);
this.onIntersection = bind(this.onIntersection, this);
this.onIntersectionCleared = bind(this.onIntersectionCleared, this);
this.onMouseMove = bind(this.onMouseMove, this);
},

update: function () {
this.updateMouseEventListeners();
},

play: function () {
Expand Down Expand Up @@ -118,8 +125,34 @@ module.exports.Component = registerComponent('cursor', {
});
el.removeEventListener('raycaster-intersection', this.onIntersection);
el.removeEventListener('raycaster-intersection-cleared', this.onIntersectionCleared);
window.removeEventListener('mousemove', this.onMouseMove);
},

updateMouseEventListeners: function () {
var el = this.el;
window.removeEventListener('mousemove', this.onMouseMove);
el.setAttribute('raycaster', 'worldCoordinates', false);
if (this.data.mode !== 'mouse') { return; }
window.addEventListener('mousemove', this.onMouseMove, false);
el.setAttribute('raycaster', 'worldCoordinates', true);
},

onMouseMove: (function () {
var mouse = new THREE.Vector2();
var origin = new THREE.Vector3();
var direction = new THREE.Vector3();
return function (evt) {
var camera = this.el.sceneEl.camera;
camera.parent.updateMatrixWorld();
camera.updateMatrixWorld();
mouse.x = (evt.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(evt.clientY / window.innerHeight) * 2 + 1;
origin.setFromMatrixPosition(camera.matrixWorld);
direction.set(mouse.x, mouse.y, 0.5).unproject(camera).sub(origin).normalize();
this.el.setAttribute('raycaster', {origin: origin, direction: direction});
};
})(),

/**
* Trigger mousedown and keep track of the mousedowned entity.
*/
Expand Down
8 changes: 7 additions & 1 deletion src/components/raycaster.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ module.exports.Component = registerComponent('raycaster', {
objects: {default: ''},
origin: {type: 'vec3'},
recursive: {default: true},
showLine: {default: false}
showLine: {default: false},
worldCoordinates: {default: false}
},

init: function () {
Expand Down Expand Up @@ -226,6 +227,11 @@ module.exports.Component = registerComponent('raycaster', {
var el = this.el;
var data = this.data;

if (data.worldCoordinates) {
this.raycaster.set(data.origin, data.direction);
return;
}

// Grab the position and rotation.
el.object3D.updateMatrixWorld();
el.object3D.matrixWorld.decompose(originVec3, quaternion, dummyVec);
Expand Down
11 changes: 11 additions & 0 deletions tests/components/cursor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ suite('cursor', function () {
done();
});
});

suite('update', function () {
test('attach mousemove event listener on mouse mode', function () {
var updateSpy = this.sinon.spy(el.components.cursor, 'update');
var updateMouseEventListenersSpy = this.sinon.spy(el.components.cursor, 'updateMouseEventListeners');
var onMouseMoveSpy = this.sinon.spy(el.components.cursor, 'onMouseMove');
el.setAttribute('cursor', 'mode', 'mouse');
assert.ok(updateSpy.called);
assert.ok(updateMouseEventListenersSpy.called);
});
});
});

suite('onCursorDown', function () {
Expand Down
18 changes: 18 additions & 0 deletions tests/components/raycaster.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,24 @@ suite('raycaster', function () {
direction = raycaster.ray.direction;
assert.equal(direction.y, 1);
});

test('applies origin and direction without transformation if worldCoordinates enabled', function () {
el.setAttribute('raycaster', 'worldCoordinates', true);
el.setAttribute('raycaster', 'origin', '1 1 1');
el.setAttribute('raycaster', 'direction', '2 2 2');
el.setAttribute('position', '5 5 5');
el.setAttribute('rotation', '30 45 90');
sceneEl.object3D.updateMatrixWorld(); // Normally handled by renderer.
component.tick();
var origin = raycaster.ray.origin;
var direction = raycaster.ray.direction;
assert.equal(origin.x, 1);
assert.equal(origin.y, 1);
assert.equal(origin.z, 1);
assert.equal(direction.x, 2);
assert.equal(direction.y, 2);
assert.equal(direction.z, 2);
});
});

suite('line', function () {
Expand Down

0 comments on commit 9e348bb

Please sign in to comment.