Skip to content

Commit 6812d75

Browse files
authored
Merge pull request #346 from nickw1/three-location-based
Three location based - against correct branch
2 parents 180745f + 2a7d243 commit 6812d75

File tree

10 files changed

+467
-0
lines changed

10 files changed

+467
-0
lines changed

three.js/location-based/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# AR.js Location Based Implementation with Pure Three.js
2+
3+
This is a simple, lightweight implementation of location-based AR.js using pure three.js. **It will only work on Chrome due to limitations with obtaining compass bearing on other browsers. Also, it is only tested on Android, not iOS. I believe it is possible to get it to work on Webkit-based browsers due to `webkitCompassHeading`, but I don't have any iDevices so cannot test this. However pull requests to fix this are welcome!**
4+
5+
## Directory structure
6+
7+
The `js` directory contains the source files. It's written using ECMAScript 6 modules. There is a `package.json` provided to allow you to install three.js from NPM:
8+
9+
`npm install`
10+
11+
## Building
12+
13+
Using Webpack is assumed. There is a simple `webpack.config.js` which is configured to place a `bundle.js` inside `example/dist`. Build with
14+
15+
`npx webpack`
16+
17+
## Full docs
18+
19+
Will follow.
20+
21+
## Example
22+
23+
Provided in the `example` directory is an example. This will behave differently
24+
depending on the query string parameter `m` supplied to it:
25+
26+
- `m` missing or `m=0`: uses a default location and does not setup device orientation controls. Useful for testing on a desktop.
27+
28+
- `m=1` : uses a default location but does setup device orientation controls. Useful for testing on a mobile device. Move the device round and you should see a red box to the northeast, green box to the north and blue box to the west (approximately).
29+
30+
- `m=2` : will use GPS tracking and device orientation controls. Unless you are located near the default location, you will need to modify the `index.js` code to add meshes near your current location.
31+
32+
If `m=1` is used (default location with device orientation controls) you should see the following:
33+
34+
- Red box a short distance to your north
35+
- Green box a short distance to your east
36+
- Yellow box a short distance to your south
37+
- Blue box a short distance to your west
38+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<script type='text/javascript' src='dist/bundle.js' defer></script>
6+
<style>
7+
html, body { height: 100%; }
8+
</style>
9+
</head>
10+
<body>
11+
<video id='video1' autoplay playsinline style='display:none'></video>
12+
<canvas id="canvas1" style='background-color: black; width:100%; height:100%; display:block'></canvas>
13+
</body>
14+
</html>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import * as THREE from 'three';
2+
import * as Arjs from '../js/arjs.js';
3+
4+
const scene = new THREE.Scene();
5+
const camera = new THREE.PerspectiveCamera(80, 2, 0.1, 50000);
6+
const renderer = new THREE.WebGLRenderer({ canvas: document.querySelector('#canvas1') });
7+
8+
const geom = new THREE.BoxGeometry(20,20,20);
9+
const material = new THREE.MeshBasicMaterial({color: 0xff0000});
10+
const mesh = new THREE.Mesh(geom, material);
11+
12+
const arjs = new Arjs.LocationBased(scene, camera);
13+
const cam = new Arjs.WebcamRenderer(renderer, '#video1');
14+
15+
// If using your own GPS location, change the lon and lat of the three meshes
16+
arjs.add(mesh, -0.72, 51.051); // slightly north
17+
const material2 = new THREE.MeshBasicMaterial({color: 0xffff00});
18+
const material3 = new THREE.MeshBasicMaterial({color: 0x0000ff});
19+
const material4 = new THREE.MeshBasicMaterial({color: 0x00ff00});
20+
arjs.add(new THREE.Mesh(geom, material2), -0.72, 51.049); // slightly south
21+
arjs.add(new THREE.Mesh(geom, material3), -0.722, 51.05); // slightly west
22+
arjs.add(new THREE.Mesh(geom, material4), -0.718, 51.05); // slightly east
23+
24+
const get = { m : 0 };
25+
const parts = window.location.href.split('?');
26+
27+
if(parts.length==2) {
28+
if(parts[1].endsWith('#')) {
29+
parts[1] = parts[1].slice(0, -1);
30+
}
31+
const params = parts[1].split('&');
32+
for(let i=0; i<params.length; i++) {
33+
const param = params[i].split('=');
34+
get[param[0]] = param[1];
35+
}
36+
}
37+
38+
let orientationControls;
39+
40+
// Use query string to control behaviour
41+
// m=1 or m=2, use DeviceOrientationControls (use on mobile device)
42+
// m=2, use actual GPS location
43+
// m not 1, use a fake GPS location
44+
// so m other than 1 or 2 can be used to test on a desktop machine
45+
if(get.m == 1 || get.m == 2) {
46+
orientationControls = new Arjs.DeviceOrientationControls(camera);
47+
}
48+
if(get.m == 2) {
49+
arjs.startGps();
50+
} else {
51+
arjs.fakeGps(-0.72, 51.05);
52+
}
53+
54+
requestAnimationFrame(render);
55+
56+
function render(time) {
57+
resizeUpdate();
58+
if(orientationControls) orientationControls.update();
59+
cam.update();
60+
renderer.render(scene, camera);
61+
requestAnimationFrame(render);
62+
}
63+
64+
function resizeUpdate() {
65+
const canvas = renderer.domElement;
66+
const width = canvas.clientWidth, height = canvas.clientHeight;
67+
if(width != canvas.width || height != canvas.height) {
68+
renderer.setSize(width, height, false);
69+
}
70+
camera.aspect = canvas.clientWidth / canvas.clientHeight;
71+
camera.updateProjectionMatrix();
72+
}

three.js/location-based/js/arjs.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { LocationBased } from './location-based.js';
2+
import { WebcamRenderer } from './webcam-renderer.js';
3+
import { DeviceOrientationControls } from './device-orientation-controls.js';
4+
5+
export {
6+
LocationBased,
7+
WebcamRenderer,
8+
DeviceOrientationControls
9+
};
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Modified version of THREE.DeviceOrientationControls from three.js
2+
// will use the deviceorientationabsolute event if available
3+
4+
import {
5+
Euler,
6+
EventDispatcher,
7+
MathUtils,
8+
Quaternion,
9+
Vector3
10+
} from 'three';
11+
12+
const _zee = new Vector3( 0, 0, 1 );
13+
const _euler = new Euler();
14+
const _q0 = new Quaternion();
15+
const _q1 = new Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis
16+
17+
const _changeEvent = { type: 'change' };
18+
19+
class DeviceOrientationControls extends EventDispatcher {
20+
21+
constructor( object ) {
22+
23+
super();
24+
25+
if ( window.isSecureContext === false ) {
26+
27+
console.error( 'THREE.DeviceOrientationControls: DeviceOrientationEvent is only available in secure contexts (https)' );
28+
29+
}
30+
31+
const scope = this;
32+
33+
const EPS = 0.000001;
34+
const lastQuaternion = new Quaternion();
35+
36+
this.object = object;
37+
this.object.rotation.reorder( 'YXZ' );
38+
39+
this.enabled = true;
40+
41+
this.deviceOrientation = {};
42+
this.screenOrientation = 0;
43+
44+
this.alphaOffset = 0; // radians
45+
46+
this.orientationChangeEventName = 'ondeviceorientationabsolute' in window ? 'deviceorientationabsolute' : 'deviceorientation';
47+
48+
const onDeviceOrientationChangeEvent = function ( event ) {
49+
50+
scope.deviceOrientation = event;
51+
52+
};
53+
54+
const onScreenOrientationChangeEvent = function () {
55+
56+
scope.screenOrientation = window.orientation || 0;
57+
58+
};
59+
60+
// The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y''
61+
62+
const setObjectQuaternion = function ( quaternion, alpha, beta, gamma, orient ) {
63+
64+
_euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us
65+
66+
quaternion.setFromEuler( _euler ); // orient the device
67+
68+
quaternion.multiply( _q1 ); // camera looks out the back of the device, not the top
69+
70+
quaternion.multiply( _q0.setFromAxisAngle( _zee, - orient ) ); // adjust for screen orientation
71+
72+
};
73+
74+
this.connect = function () {
75+
76+
onScreenOrientationChangeEvent(); // run once on load
77+
78+
// iOS 13+
79+
80+
if ( window.DeviceOrientationEvent !== undefined && typeof window.DeviceOrientationEvent.requestPermission === 'function' ) {
81+
82+
window.DeviceOrientationEvent.requestPermission().then( function ( response ) {
83+
84+
if ( response == 'granted' ) {
85+
86+
window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent );
87+
window.addEventListener( this.orientationChangeEventName, onDeviceOrientationChangeEvent );
88+
89+
}
90+
91+
} ).catch( function ( error ) {
92+
93+
console.error( 'THREE.DeviceOrientationControls: Unable to use DeviceOrientation API:', error );
94+
95+
} );
96+
97+
} else {
98+
99+
window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent );
100+
window.addEventListener( this.orientationChangeEventName, onDeviceOrientationChangeEvent );
101+
102+
}
103+
104+
scope.enabled = true;
105+
106+
};
107+
108+
this.disconnect = function () {
109+
110+
window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent );
111+
window.removeEventListener( this.orientationChangeEventName , onDeviceOrientationChangeEvent );
112+
113+
scope.enabled = false;
114+
115+
};
116+
117+
this.update = function () {
118+
119+
if ( scope.enabled === false ) return;
120+
121+
const device = scope.deviceOrientation;
122+
123+
if ( device ) {
124+
125+
const alpha = device.alpha ? MathUtils.degToRad( device.alpha ) + scope.alphaOffset : 0; // Z
126+
127+
const beta = device.beta ? MathUtils.degToRad( device.beta ) : 0; // X'
128+
129+
const gamma = device.gamma ? MathUtils.degToRad( device.gamma ) : 0; // Y''
130+
131+
const orient = scope.screenOrientation ? MathUtils.degToRad( scope.screenOrientation ) : 0; // O
132+
133+
setObjectQuaternion( scope.object.quaternion, alpha, beta, gamma, orient );
134+
135+
if ( 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
136+
137+
lastQuaternion.copy( scope.object.quaternion );
138+
scope.dispatchEvent( _changeEvent );
139+
140+
}
141+
142+
}
143+
144+
};
145+
146+
this.dispose = function () {
147+
148+
scope.disconnect();
149+
150+
};
151+
152+
this.connect();
153+
154+
}
155+
156+
}
157+
158+
export { DeviceOrientationControls };
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { SphMercProjection } from './sphmerc-projection.js';
2+
3+
class LocationBased {
4+
5+
constructor (scene, camera) {
6+
this.scene = scene;
7+
this.camera = camera;
8+
this.proj = new SphMercProjection();
9+
}
10+
11+
setProjection(proj) {
12+
this.proj = proj;
13+
}
14+
15+
startGps(maximumAge = 0) {
16+
this.watchPositionId = navigator.geolocation.watchPosition(
17+
position => {
18+
this.setWorldPosition(
19+
this.camera,
20+
position.coords.longitude,
21+
position.coords.latitude
22+
);
23+
}, error => {
24+
alert(`GPS listen error: code ${error}`);
25+
}, {
26+
enableHighAccuracy: true,
27+
maximumAge: maximumAge
28+
}
29+
);
30+
}
31+
32+
stopGps() {
33+
if(this.watchPositionId) {
34+
navigator.geolocation.clearWatch(this.watchPositionId);
35+
this.watchPositionId = null;
36+
}
37+
}
38+
39+
fakeGps(lon, lat, elev) {
40+
this.setWorldPosition(this.camera, lon, lat, elev);
41+
}
42+
43+
lonLatToWorldCoords(lon, lat) {
44+
const projectedPos = this.proj.project(lon, lat);
45+
return [projectedPos[0], -projectedPos[1]];
46+
}
47+
48+
add(object, lon, lat, elev) {
49+
this.setWorldPosition(object, lon, lat, elev);
50+
this.scene.add(object);
51+
}
52+
53+
setWorldPosition(object, lon, lat, elev) {
54+
const worldCoords = this.lonLatToWorldCoords(lon, lat);
55+
[ object.position.x, object.position.z ] = worldCoords;
56+
if(elev !== undefined) {
57+
object.position.y = elev;
58+
}
59+
}
60+
61+
setElevation(elev) {
62+
this.camera.position.y = elev;
63+
}
64+
}
65+
66+
export { LocationBased };

0 commit comments

Comments
 (0)