locar 0.1.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/locar.d.ts +116 -21
- package/dist/locar.es.js +231 -140
- package/dist/locar.umd.js +10 -1
- package/package.json +7 -6
package/dist/locar.d.ts
CHANGED
|
@@ -3,6 +3,62 @@ import { Object3D } from 'three';
|
|
|
3
3
|
import { Quaternion } from 'three';
|
|
4
4
|
import * as THREE from 'three';
|
|
5
5
|
|
|
6
|
+
/** Application class to orchestrate the interaction between the individual LocAR classes and the Three.js camera, renderer and scene. */
|
|
7
|
+
export declare class App extends EventEmitter {
|
|
8
|
+
#private;
|
|
9
|
+
locar: LocAR;
|
|
10
|
+
camera: THREE.PerspectiveCamera;
|
|
11
|
+
renderer: THREE.WebGLRenderer;
|
|
12
|
+
scene: THREE.Scene;
|
|
13
|
+
webcam: Webcam;
|
|
14
|
+
deviceOrientationControls: DeviceOrientationControls | null;
|
|
15
|
+
cameraFeedDimensions: {
|
|
16
|
+
landWidth: number;
|
|
17
|
+
landHeight: number;
|
|
18
|
+
} | null; /** camera feed dimensions in LANDSCAPE */
|
|
19
|
+
origHfov: number;
|
|
20
|
+
/**
|
|
21
|
+
* Create an App object.
|
|
22
|
+
* @param {AppOptions} - Startup options.
|
|
23
|
+
*/
|
|
24
|
+
constructor({ cameraOptions, canvas, gpsOptions, videoConstraints, deviceOrientationOptions, serverLogger, projection }: AppOptions);
|
|
25
|
+
/**
|
|
26
|
+
* Start the app.
|
|
27
|
+
* Must be called after construction.
|
|
28
|
+
* @returns {Promise<LocAR>}
|
|
29
|
+
* Promise resolving with LocAR object. Rejects with object containing code and message.
|
|
30
|
+
*/
|
|
31
|
+
start(): Promise<LocAR>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Options to pass into the App object. */
|
|
35
|
+
export declare interface AppOptions {
|
|
36
|
+
/** the three.js camera options to use - note however we specify horizontal, not vertical, field of view */
|
|
37
|
+
cameraOptions?: {
|
|
38
|
+
hFov: number;
|
|
39
|
+
near: number;
|
|
40
|
+
far: number;
|
|
41
|
+
};
|
|
42
|
+
/** the canvas to render the AR scene into (one will be created if omitted) */
|
|
43
|
+
canvas?: HTMLCanvasElement;
|
|
44
|
+
/** GPS options, see GpsOptions documentation for details */
|
|
45
|
+
gpsOptions?: GpsOptions;
|
|
46
|
+
/** Video constraints for Media Devices API */
|
|
47
|
+
videoConstraints?: {
|
|
48
|
+
video: {
|
|
49
|
+
facingMode: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
/** Device orientation options for DeviceOrientationControls */
|
|
53
|
+
deviceOrientationOptions?: DeviceOrientationControlsOptions & {
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
};
|
|
56
|
+
/** Projection to use (default: SphMercProjection) */
|
|
57
|
+
projection?: Projection;
|
|
58
|
+
/** Server logger to use - ensure you gain consent from the user if you are doing this, it's usually a Data Protection legal requirement */
|
|
59
|
+
serverLogger?: ServerLogger;
|
|
60
|
+
}
|
|
61
|
+
|
|
6
62
|
/**
|
|
7
63
|
* Class to handle object detection via mouse clicks/touch events
|
|
8
64
|
* and raycasting.
|
|
@@ -79,14 +135,31 @@ export declare class DeviceOrientationControls extends EventDispatcher {
|
|
|
79
135
|
init: () => void;
|
|
80
136
|
}
|
|
81
137
|
|
|
82
|
-
|
|
138
|
+
/** Options to pass into DeviceOrientationControls. */
|
|
139
|
+
export declare type DeviceOrientationControlsOptions = {
|
|
140
|
+
/** smoothingFactor - default 0.2. If too high, AR content movement will lag behind the sensors. If too low, the scene will be jittery. */
|
|
83
141
|
smoothingFactor?: number;
|
|
142
|
+
/** Movement threshold to detect an orientation change (radians). */
|
|
84
143
|
orientationChangeThreshold?: number;
|
|
144
|
+
/** On iOS, enable permission dialog to seek permission to use device orientation through a user gesture. Recommended to set to true */
|
|
85
145
|
enablePermissionDialog?: boolean;
|
|
146
|
+
/** Set iOS-look and feel styling for the permission dialog for device orientation */
|
|
86
147
|
enableStyling?: boolean;
|
|
148
|
+
/** Use a standard confirm dialog rather than a custom element to grant device orientation permissions */
|
|
87
149
|
preferConfirmDialog?: boolean;
|
|
88
150
|
};
|
|
89
151
|
|
|
152
|
+
/** Event emitted when there is an error with device orientation. */
|
|
153
|
+
export declare interface DeviceOrientationErrorEvent {
|
|
154
|
+
code: string;
|
|
155
|
+
message: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Event emitted when device orientation permission has been granted. */
|
|
159
|
+
export declare interface DeviceOrientationGrantedEvent {
|
|
160
|
+
target: DeviceOrientationControls;
|
|
161
|
+
}
|
|
162
|
+
|
|
90
163
|
/** Event emitter class to handle events. */
|
|
91
164
|
export declare class EventEmitter {
|
|
92
165
|
eventHandlers: Record<string, ((...args: any[]) => void)[]>;
|
|
@@ -111,13 +184,25 @@ export declare class EventEmitter {
|
|
|
111
184
|
off: (eventName: string, eventHandler: (...args: any[]) => void) => void;
|
|
112
185
|
}
|
|
113
186
|
|
|
114
|
-
|
|
187
|
+
/** GPS intialisation options. */
|
|
188
|
+
export declare interface GpsOptions {
|
|
115
189
|
gpsMinDistance?: number;
|
|
116
190
|
gpsMinAccuracy?: number;
|
|
117
191
|
}
|
|
118
192
|
|
|
119
|
-
/**
|
|
120
|
-
export declare
|
|
193
|
+
/** Event emitted when a GPS position is received. */
|
|
194
|
+
export declare interface GpsReceivedEvent {
|
|
195
|
+
/** The new GPS position */
|
|
196
|
+
position: GeolocationPosition;
|
|
197
|
+
/** distance moved in metres since last GpsReceivedEvent */
|
|
198
|
+
distMoved: number;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** The main engine class for the LocAR.js system.
|
|
202
|
+
* Can be obtained either via App.start() - which resolves with a LocAR object - or on its own.
|
|
203
|
+
* If you use this class without App, you must set up the three.js scene yourself, as you did with locar.js 0.1.x.
|
|
204
|
+
*/
|
|
205
|
+
export declare class LocAR extends EventEmitter {
|
|
121
206
|
#private;
|
|
122
207
|
scene: THREE.Scene;
|
|
123
208
|
camera: THREE.Camera;
|
|
@@ -129,14 +214,14 @@ export declare class LocationBased extends EventEmitter {
|
|
|
129
214
|
* setGpsOptions() below.
|
|
130
215
|
* @param {Object} serverLogger - an object which can optionally log GPS position to a server for debugging. null by default, so no logging will be done. This object should implement a sendData() method to send data (2nd arg) to a given endpoint (1st arg). Please see source code for details. Ensure you comply with privacy laws (GDPR or equivalent) if implementing this.
|
|
131
216
|
*/
|
|
132
|
-
constructor(scene: THREE.Scene, camera: THREE.Camera, options?: GpsOptions, serverLogger?: ServerLogger | null);
|
|
217
|
+
constructor(scene: THREE.Scene, camera: THREE.Camera, options?: GpsOptions, serverLogger?: ServerLogger | null, projection?: Projection);
|
|
133
218
|
/**
|
|
134
219
|
* Set the projection to use.
|
|
135
220
|
* @param {Object} any object which includes a project() method
|
|
136
221
|
* taking longitude and latitude as arguments and returning an array
|
|
137
222
|
* containing easting and northing.
|
|
138
223
|
*/
|
|
139
|
-
setProjection(proj:
|
|
224
|
+
setProjection(proj: Projection): void;
|
|
140
225
|
/**
|
|
141
226
|
* Set the GPS options.
|
|
142
227
|
* @param {Object} object containing gpsMinDistance and/or gpsMinAccuracy
|
|
@@ -189,12 +274,19 @@ export declare class LocationBased extends EventEmitter {
|
|
|
189
274
|
* @param {Object} properties - properties describing the object (for example,
|
|
190
275
|
* the contents of the GeoJSON properties field).
|
|
191
276
|
*/
|
|
192
|
-
add(object: THREE.Object3D, lon: number, lat: number, elev
|
|
277
|
+
add(object: THREE.Object3D, lon: number, lat: number, elev?: number | undefined, properties?: Record<string, any>): void;
|
|
278
|
+
addGeoLine(points: Array<[number, number, number?]>, material: THREE.Material, lineWidth?: number): void;
|
|
193
279
|
/**
|
|
194
280
|
* Set the elevation (y coordinate) of the camera.
|
|
195
281
|
* @param {number} elev - the elevation in metres.
|
|
196
282
|
*/
|
|
197
283
|
setElevation(elev: number): void;
|
|
284
|
+
/**
|
|
285
|
+
* Calculate haversine distance between two lat/lon pairs.
|
|
286
|
+
*
|
|
287
|
+
* Taken from original A-Frame AR.js location-based components
|
|
288
|
+
*/
|
|
289
|
+
static haversineDist(src: LonLat, dest: LonLat): number;
|
|
198
290
|
/**
|
|
199
291
|
* Obtain the last known GPS location.
|
|
200
292
|
*
|
|
@@ -203,17 +295,24 @@ export declare class LocationBased extends EventEmitter {
|
|
|
203
295
|
getLastKnownLocation(): LonLat | null;
|
|
204
296
|
}
|
|
205
297
|
|
|
206
|
-
|
|
298
|
+
/** Longitude and latitude. */
|
|
299
|
+
export declare interface LonLat {
|
|
207
300
|
longitude: number;
|
|
208
301
|
latitude: number;
|
|
209
302
|
}
|
|
210
303
|
|
|
211
|
-
|
|
304
|
+
/** Projection interface, you can create your own custom projection by implementing project() and unproject(). */
|
|
305
|
+
export declare interface Projection {
|
|
306
|
+
project: (lon: number, lat: number) => [number, number];
|
|
307
|
+
unproject: (projected: [number, number]) => [number, number];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Server logger interface. */
|
|
311
|
+
export declare interface ServerLogger {
|
|
212
312
|
sendData(endpoint: string, data: any): Promise<Response> | Response;
|
|
213
313
|
}
|
|
214
314
|
|
|
215
|
-
|
|
216
|
-
export declare class SphMercProjection {
|
|
315
|
+
export declare class SphMercProjection implements Projection {
|
|
217
316
|
#private;
|
|
218
317
|
EARTH: number;
|
|
219
318
|
HALF_EARTH: number;
|
|
@@ -233,7 +332,7 @@ export declare class SphMercProjection {
|
|
|
233
332
|
* @param {Array} projected - Two-member array containing easting and northing
|
|
234
333
|
* @return {Array} Two-member array containing longitude and latitude
|
|
235
334
|
*/
|
|
236
|
-
unproject: (projected: [number, number]) => number
|
|
335
|
+
unproject: (projected: [number, number]) => [number, number];
|
|
237
336
|
/**
|
|
238
337
|
* Return the projection's ID.
|
|
239
338
|
* @return {string} The value "epsg:3857".
|
|
@@ -241,13 +340,10 @@ export declare class SphMercProjection {
|
|
|
241
340
|
getID: () => string;
|
|
242
341
|
}
|
|
243
342
|
|
|
244
|
-
export declare const version = "0.1.8";
|
|
245
|
-
|
|
246
343
|
/** Class to setup the webcam. */
|
|
247
344
|
export declare class Webcam extends EventEmitter {
|
|
248
345
|
#private;
|
|
249
346
|
sceneWebcam: THREE.Scene;
|
|
250
|
-
texture: THREE.VideoTexture | null;
|
|
251
347
|
/**
|
|
252
348
|
* Create a Webcam.
|
|
253
349
|
* @param constraints {Object} - options to use for initialising the camera.
|
|
@@ -261,20 +357,19 @@ export declare class Webcam extends EventEmitter {
|
|
|
261
357
|
facingMode: string;
|
|
262
358
|
};
|
|
263
359
|
}, videoElementSelector?: string);
|
|
264
|
-
|
|
265
|
-
* Free up the memory associated with the webcam.
|
|
266
|
-
* Should be called when your application closes.
|
|
267
|
-
*/
|
|
268
|
-
dispose(): void;
|
|
360
|
+
getVideoDimensions(): string;
|
|
269
361
|
}
|
|
270
362
|
|
|
363
|
+
/** Event emitted when the webcam encounters an error. */
|
|
271
364
|
export declare interface WebcamErrorEvent {
|
|
272
365
|
code: string;
|
|
273
366
|
message: string;
|
|
274
367
|
}
|
|
275
368
|
|
|
369
|
+
/** Event emitted when the webcam starts. */
|
|
276
370
|
export declare interface WebcamStartedEvent {
|
|
277
|
-
|
|
371
|
+
videoWidth: number;
|
|
372
|
+
videoHeight: number;
|
|
278
373
|
}
|
|
279
374
|
|
|
280
375
|
export { }
|
package/dist/locar.es.js
CHANGED
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
import * as u from "three";
|
|
2
|
-
import { Vector3 as
|
|
3
|
-
class
|
|
4
|
-
/**
|
|
5
|
-
* Create a SphMercProjection.
|
|
6
|
-
*/
|
|
7
|
-
constructor() {
|
|
8
|
-
this.project = (e, i) => [this.#e(e), this.#t(i)], this.unproject = (e) => [this.#i(e[0]), this.#s(e[1])], this.#e = (e) => e / 180 * this.HALF_EARTH, this.#t = (e) => {
|
|
9
|
-
var i = Math.log(Math.tan((90 + e) * Math.PI / 360)) / (Math.PI / 180);
|
|
10
|
-
return i * this.HALF_EARTH / 180;
|
|
11
|
-
}, this.#i = (e) => e / this.HALF_EARTH * 180, this.#s = (e) => {
|
|
12
|
-
var i = e / this.HALF_EARTH * 180;
|
|
13
|
-
return i = 180 / Math.PI * (2 * Math.atan(Math.exp(i * Math.PI / 180)) - Math.PI / 2), i;
|
|
14
|
-
}, this.getID = () => "epsg:3857", this.EARTH = 4007501668e-2, this.HALF_EARTH = 2003750834e-2;
|
|
15
|
-
}
|
|
16
|
-
#e;
|
|
17
|
-
#t;
|
|
18
|
-
#i;
|
|
19
|
-
#s;
|
|
20
|
-
}
|
|
21
|
-
class E {
|
|
2
|
+
import { Vector3 as D, Euler as T, Quaternion as O, EventDispatcher as P, MathUtils as p } from "three";
|
|
3
|
+
class w {
|
|
22
4
|
constructor() {
|
|
23
5
|
this.on = (e, i) => {
|
|
24
6
|
this.eventHandlers[e] === void 0 && (this.eventHandlers[e] = []), this.eventHandlers[e].push(i);
|
|
@@ -34,14 +16,88 @@ class E {
|
|
|
34
16
|
}, this.eventHandlers = {};
|
|
35
17
|
}
|
|
36
18
|
}
|
|
37
|
-
class
|
|
19
|
+
class j extends w {
|
|
20
|
+
/**
|
|
21
|
+
* Create an App object.
|
|
22
|
+
* @param {AppOptions} - Startup options.
|
|
23
|
+
*/
|
|
24
|
+
constructor({ cameraOptions: e, canvas: i, gpsOptions: t, videoConstraints: o, deviceOrientationOptions: l, serverLogger: m, projection: n }) {
|
|
25
|
+
super(), this.origHfov = e?.hFov || 80;
|
|
26
|
+
const s = 0;
|
|
27
|
+
this.cameraFeedDimensions = null;
|
|
28
|
+
const d = window.innerWidth / window.innerHeight;
|
|
29
|
+
this.camera = new u.PerspectiveCamera(this.origHfov / d, d, e?.near || 1e-3, e?.far || 1e3), i ? (this.renderer = new u.WebGLRenderer({ canvas: i, alpha: !0 }), this.renderer.setClearColor(65280, s)) : (this.renderer = new u.WebGLRenderer({ alpha: !0 }), this.renderer.setClearColor(65280, s), document.body.appendChild(this.renderer.domElement)), this.renderer.setSize(window.innerWidth, window.innerHeight), this.scene = new u.Scene();
|
|
30
|
+
const c = l || { enabled: !0 };
|
|
31
|
+
window.addEventListener("resize", () => {
|
|
32
|
+
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
33
|
+
const a = window.innerWidth / window.innerHeight;
|
|
34
|
+
if (this.camera.aspect = a, this.cameraFeedDimensions !== null) {
|
|
35
|
+
const h = a > 1 ? this.cameraFeedDimensions.landWidth : this.cameraFeedDimensions.landHeight, g = a > 1 ? this.cameraFeedDimensions.landHeight : this.cameraFeedDimensions.landWidth;
|
|
36
|
+
this.#e(h, g, a);
|
|
37
|
+
}
|
|
38
|
+
this.camera.updateProjectionMatrix();
|
|
39
|
+
}), this.locar = new y(this.scene, this.camera, t, m, n), this.webcam = new q(o), this.deviceOrientationControls = c.enabled === !0 ? new z(this.camera, c) : null, this.renderer.setAnimationLoop(() => {
|
|
40
|
+
this.deviceOrientationControls?.update(), this.renderer.render(this.scene, this.camera);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Start the app.
|
|
45
|
+
* Must be called after construction.
|
|
46
|
+
* @returns {Promise<LocAR>}
|
|
47
|
+
* Promise resolving with LocAR object. Rejects with object containing code and message.
|
|
48
|
+
*/
|
|
49
|
+
start() {
|
|
50
|
+
return new Promise((i, t) => {
|
|
51
|
+
this.webcam.on("webcamstarted", (o) => {
|
|
52
|
+
const l = o.videoWidth > o.videoHeight;
|
|
53
|
+
this.cameraFeedDimensions = {
|
|
54
|
+
landWidth: l ? o.videoWidth : o.videoHeight,
|
|
55
|
+
landHeight: l ? o.videoHeight : o.videoWidth
|
|
56
|
+
}, this.#e(o.videoWidth, o.videoHeight, window.innerWidth / window.innerHeight), this.camera.updateProjectionMatrix();
|
|
57
|
+
}), this.webcam.on("webcamerror", (o) => {
|
|
58
|
+
t({ code: o.code, message: o.message });
|
|
59
|
+
}), this.deviceOrientationControls === null ? i(this.locar) : (this.deviceOrientationControls?.on("deviceorientationgranted", (o) => {
|
|
60
|
+
o.target.connect(), i(this.locar);
|
|
61
|
+
}), this.deviceOrientationControls.on("deviceorientationerror", (o) => {
|
|
62
|
+
t({ code: o.code, message: o.message });
|
|
63
|
+
}), this.deviceOrientationControls.init());
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
#e(e, i, t) {
|
|
67
|
+
const o = e / i;
|
|
68
|
+
if (t < o) {
|
|
69
|
+
const l = e * (window.innerHeight / i), m = this.origHfov * (window.innerWidth / l);
|
|
70
|
+
this.camera.fov = m / t;
|
|
71
|
+
} else
|
|
72
|
+
this.camera.fov = this.origHfov / t;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
class x {
|
|
76
|
+
/**
|
|
77
|
+
* Create a SphMercProjection.
|
|
78
|
+
*/
|
|
79
|
+
constructor() {
|
|
80
|
+
this.project = (e, i) => [this.#e(e), this.#t(i)], this.unproject = (e) => [this.#i(e[0]), this.#n(e[1])], this.#e = (e) => e / 180 * this.HALF_EARTH, this.#t = (e) => {
|
|
81
|
+
var i = Math.log(Math.tan((90 + e) * Math.PI / 360)) / (Math.PI / 180);
|
|
82
|
+
return i * this.HALF_EARTH / 180;
|
|
83
|
+
}, this.#i = (e) => e / this.HALF_EARTH * 180, this.#n = (e) => {
|
|
84
|
+
var i = e / this.HALF_EARTH * 180;
|
|
85
|
+
return i = 180 / Math.PI * (2 * Math.atan(Math.exp(i * Math.PI / 180)) - Math.PI / 2), i;
|
|
86
|
+
}, this.getID = () => "epsg:3857", this.EARTH = 4007501668e-2, this.HALF_EARTH = 2003750834e-2;
|
|
87
|
+
}
|
|
88
|
+
#e;
|
|
89
|
+
#t;
|
|
90
|
+
#i;
|
|
91
|
+
#n;
|
|
92
|
+
}
|
|
93
|
+
class y extends w {
|
|
38
94
|
#e;
|
|
39
95
|
#t;
|
|
40
96
|
#i;
|
|
41
|
-
#s;
|
|
42
|
-
#a;
|
|
43
97
|
#n;
|
|
44
|
-
#
|
|
98
|
+
#a;
|
|
99
|
+
#s;
|
|
100
|
+
#d;
|
|
45
101
|
#r;
|
|
46
102
|
#o;
|
|
47
103
|
/**
|
|
@@ -52,8 +108,8 @@ class H extends E {
|
|
|
52
108
|
* setGpsOptions() below.
|
|
53
109
|
* @param {Object} serverLogger - an object which can optionally log GPS position to a server for debugging. null by default, so no logging will be done. This object should implement a sendData() method to send data (2nd arg) to a given endpoint (1st arg). Please see source code for details. Ensure you comply with privacy laws (GDPR or equivalent) if implementing this.
|
|
54
110
|
*/
|
|
55
|
-
constructor(e, i, t = {}, l =
|
|
56
|
-
super(), this.scene = e, this.camera = i, this.#e =
|
|
111
|
+
constructor(e, i, t = {}, o = null, l = new x()) {
|
|
112
|
+
super(), this.scene = e, this.camera = i, this.#e = l, this.#t = null, this.#i = 0, this.#n = 100, this.#a = null, this.setGpsOptions(t), this.#s = null, this.#d = 0, this.#r = 0, this.#o = o;
|
|
57
113
|
}
|
|
58
114
|
/**
|
|
59
115
|
* Set the projection to use.
|
|
@@ -72,7 +128,7 @@ class H extends E {
|
|
|
72
128
|
* minimum accuracy, in metres, for a GPS reading to be counted.
|
|
73
129
|
*/
|
|
74
130
|
setGpsOptions(e = {}) {
|
|
75
|
-
e.gpsMinDistance !== void 0 && (this.#i = e.gpsMinDistance), e.gpsMinAccuracy !== void 0 && (this.#
|
|
131
|
+
e.gpsMinDistance !== void 0 && (this.#i = e.gpsMinDistance), e.gpsMinAccuracy !== void 0 && (this.#n = e.gpsMinAccuracy);
|
|
76
132
|
}
|
|
77
133
|
/**
|
|
78
134
|
* Start the GPS on a real device
|
|
@@ -83,13 +139,13 @@ class H extends E {
|
|
|
83
139
|
if (this.#o) {
|
|
84
140
|
const i = await (await this.#o.sendData("/gps/start", {
|
|
85
141
|
gpsMinDistance: this.#i,
|
|
86
|
-
gpsMinAccuracy: this.#
|
|
142
|
+
gpsMinAccuracy: this.#n
|
|
87
143
|
})).json();
|
|
88
144
|
this.#r = i.session;
|
|
89
145
|
}
|
|
90
146
|
return this.#a === null ? (this.#a = navigator.geolocation.watchPosition(
|
|
91
147
|
(e) => {
|
|
92
|
-
this.#
|
|
148
|
+
this.#c(e);
|
|
93
149
|
},
|
|
94
150
|
(e) => {
|
|
95
151
|
this.emit("gpserror", e);
|
|
@@ -116,12 +172,12 @@ class H extends E {
|
|
|
116
172
|
* @param {number} acc - The accuracy of the GPS reading in metres. May be
|
|
117
173
|
* ignored if lower than the specified minimum accuracy.
|
|
118
174
|
*/
|
|
119
|
-
fakeGps(e, i, t = null,
|
|
120
|
-
t !== null && this.setElevation(t), this.#
|
|
175
|
+
fakeGps(e, i, t = null, o = 0) {
|
|
176
|
+
t !== null && this.setElevation(t), this.#c({
|
|
121
177
|
coords: {
|
|
122
178
|
longitude: e,
|
|
123
179
|
latitude: i,
|
|
124
|
-
accuracy:
|
|
180
|
+
accuracy: o
|
|
125
181
|
}
|
|
126
182
|
});
|
|
127
183
|
}
|
|
@@ -138,8 +194,8 @@ class H extends E {
|
|
|
138
194
|
*/
|
|
139
195
|
lonLatToWorldCoords(e, i) {
|
|
140
196
|
const t = this.#e.project(e, i);
|
|
141
|
-
if (this.#
|
|
142
|
-
t[0] -= this.#
|
|
197
|
+
if (this.#s)
|
|
198
|
+
t[0] -= this.#s[0], t[1] -= this.#s[1];
|
|
143
199
|
else
|
|
144
200
|
throw "No initial position determined";
|
|
145
201
|
return [t[0], -t[1]];
|
|
@@ -154,18 +210,55 @@ class H extends E {
|
|
|
154
210
|
* @param {Object} properties - properties describing the object (for example,
|
|
155
211
|
* the contents of the GeoJSON properties field).
|
|
156
212
|
*/
|
|
157
|
-
add(e, i, t,
|
|
158
|
-
e.properties =
|
|
213
|
+
add(e, i, t, o, l = {}) {
|
|
214
|
+
e.properties = l, this.#l(e, i, t, o || 0), this.scene.add(e), this.#o?.sendData("/object/new", {
|
|
159
215
|
position: e.position,
|
|
160
216
|
x: e.position.x,
|
|
161
217
|
z: e.position.z,
|
|
162
218
|
session: this.#r,
|
|
163
|
-
properties:
|
|
219
|
+
properties: l
|
|
164
220
|
});
|
|
165
221
|
}
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
222
|
+
addGeoLine(e, i, t = 1) {
|
|
223
|
+
const o = e.map(((n) => {
|
|
224
|
+
const [s, d] = this.lonLatToWorldCoords(n[0], n[1]);
|
|
225
|
+
return new u.Vector3(s, n[2] || 0, d);
|
|
226
|
+
})), l = this.#h(o, t);
|
|
227
|
+
i.setValues({ side: u.DoubleSide });
|
|
228
|
+
const m = new u.Mesh(l, i);
|
|
229
|
+
this.scene.add(m);
|
|
230
|
+
}
|
|
231
|
+
#h(e, i) {
|
|
232
|
+
let t, o, l, m, n = 0, s = 0, d = [], c;
|
|
233
|
+
const a = e.length - 1, h = [];
|
|
234
|
+
for (let r = 0; r < a; r++)
|
|
235
|
+
t = e[r + 1].x - e[r].x, o = e[r + 1].z - e[r].z, l = e[r + 1].y - e[r].y, m = Math.sqrt(t * t + l * l + o * o), n = -(o * (i / 2)) / m, s = t * (i / 2) / m, c = [
|
|
236
|
+
e[r].x - n,
|
|
237
|
+
e[r].y,
|
|
238
|
+
e[r].z - s,
|
|
239
|
+
e[r].x + n,
|
|
240
|
+
e[r].y,
|
|
241
|
+
e[r].z + s
|
|
242
|
+
], r > 0 && c.forEach((C, R) => {
|
|
243
|
+
C = (C + d[R]) / 2;
|
|
244
|
+
}), h.push(...c), d = [
|
|
245
|
+
e[r + 1].x - n,
|
|
246
|
+
e[r + 1].y,
|
|
247
|
+
e[r + 1].z - s,
|
|
248
|
+
e[r + 1].x + n,
|
|
249
|
+
e[r + 1].y,
|
|
250
|
+
e[r + 1].z + s
|
|
251
|
+
];
|
|
252
|
+
h.push(e[a].x - n), h.push(e[a].y), h.push(e[a].z - s), h.push(e[a].x + n), h.push(e[a].y), h.push(e[a].z + s);
|
|
253
|
+
let g = [];
|
|
254
|
+
for (let r = 0; r < a; r++)
|
|
255
|
+
g.push(r * 2, r * 2 + 1, r * 2 + 2), g.push(r * 2 + 1, r * 2 + 3, r * 2 + 2);
|
|
256
|
+
let f = new u.BufferGeometry(), M = new Float32Array(h);
|
|
257
|
+
return f.setIndex(g), f.setAttribute("position", new u.BufferAttribute(M, 3)), f.computeBoundingBox(), f;
|
|
258
|
+
}
|
|
259
|
+
#l(e, i, t, o) {
|
|
260
|
+
const l = this.lonLatToWorldCoords(i, t);
|
|
261
|
+
o !== void 0 && (e.position.y = o), [e.position.x, e.position.z] = l;
|
|
169
262
|
}
|
|
170
263
|
/**
|
|
171
264
|
* Set the elevation (y coordinate) of the camera.
|
|
@@ -174,35 +267,35 @@ class H extends E {
|
|
|
174
267
|
setElevation(e) {
|
|
175
268
|
this.camera.position.y = e;
|
|
176
269
|
}
|
|
177
|
-
#
|
|
178
|
-
this.#
|
|
270
|
+
#u(e, i) {
|
|
271
|
+
this.#s = this.#e.project(e, i);
|
|
179
272
|
}
|
|
180
|
-
#
|
|
273
|
+
#c(e) {
|
|
181
274
|
let i = Number.MAX_VALUE;
|
|
182
|
-
this.#
|
|
183
|
-
gpsCount: this.#
|
|
275
|
+
this.#d++, this.#o?.sendData("/gps/new", {
|
|
276
|
+
gpsCount: this.#d,
|
|
184
277
|
lat: e.coords.latitude,
|
|
185
278
|
lon: e.coords.longitude,
|
|
186
279
|
acc: e.coords.accuracy,
|
|
187
280
|
session: this.#r
|
|
188
|
-
}), e.coords.accuracy <= this.#
|
|
281
|
+
}), e.coords.accuracy <= this.#n && (this.#t === null ? this.#t = {
|
|
189
282
|
latitude: e.coords.latitude,
|
|
190
283
|
longitude: e.coords.longitude
|
|
191
|
-
} : i =
|
|
284
|
+
} : i = y.haversineDist(this.#t, e.coords), i >= this.#i && (this.#t.longitude = e.coords.longitude, this.#t.latitude = e.coords.latitude, this.#s || (this.#u(
|
|
192
285
|
e.coords.longitude,
|
|
193
286
|
e.coords.latitude
|
|
194
287
|
), this.#o?.sendData("/worldorigin/new", {
|
|
195
|
-
gpsCount: this.#
|
|
288
|
+
gpsCount: this.#d,
|
|
196
289
|
lat: e.coords.latitude,
|
|
197
290
|
lon: e.coords.longitude,
|
|
198
291
|
session: this.#r,
|
|
199
|
-
initialPosition: this.#
|
|
200
|
-
})), this.#
|
|
292
|
+
initialPosition: this.#s
|
|
293
|
+
})), this.#l(
|
|
201
294
|
this.camera,
|
|
202
295
|
e.coords.longitude,
|
|
203
296
|
e.coords.latitude
|
|
204
297
|
), this.#o?.sendData("/gps/accepted", {
|
|
205
|
-
gpsCount: this.#
|
|
298
|
+
gpsCount: this.#d,
|
|
206
299
|
cameraX: this.camera.position.x,
|
|
207
300
|
cameraZ: this.camera.position.z,
|
|
208
301
|
session: this.#r,
|
|
@@ -214,9 +307,9 @@ class H extends E {
|
|
|
214
307
|
*
|
|
215
308
|
* Taken from original A-Frame AR.js location-based components
|
|
216
309
|
*/
|
|
217
|
-
|
|
218
|
-
const t = u.MathUtils.degToRad(i.longitude - e.longitude),
|
|
219
|
-
return 2 * Math.atan2(Math.sqrt(
|
|
310
|
+
static haversineDist(e, i) {
|
|
311
|
+
const t = u.MathUtils.degToRad(i.longitude - e.longitude), o = u.MathUtils.degToRad(i.latitude - e.latitude), l = Math.sin(o / 2) * Math.sin(o / 2) + Math.cos(u.MathUtils.degToRad(e.latitude)) * Math.cos(u.MathUtils.degToRad(i.latitude)) * (Math.sin(t / 2) * Math.sin(t / 2));
|
|
312
|
+
return 2 * Math.atan2(Math.sqrt(l), Math.sqrt(1 - l)) * 6371e3;
|
|
220
313
|
}
|
|
221
314
|
/**
|
|
222
315
|
* Obtain the last known GPS location.
|
|
@@ -227,8 +320,8 @@ class H extends E {
|
|
|
227
320
|
return this.#t;
|
|
228
321
|
}
|
|
229
322
|
}
|
|
230
|
-
const
|
|
231
|
-
class
|
|
323
|
+
const _ = "locar-device-orientation-permission-modal", H = "locar-device-orientation-permission-button", L = "locar-device-orientation-permission-message", N = "locar-device-orientation-permission-inner", S = "locar-device-orientation-permission-button-inner", b = "This immersive website requires access to your device motion sensors.", E = navigator.userAgent.match(/iPhone|iPad|iPod/i) || /Macintosh/i.test(navigator.userAgent) && navigator.maxTouchPoints != null && navigator.maxTouchPoints > 1, W = new D(0, 0, 1), I = new T(), F = new O(), V = new O(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5)), A = 0.5 * Math.PI;
|
|
324
|
+
class z extends P {
|
|
232
325
|
/**
|
|
233
326
|
* Create an instance of DeviceOrientationControls.
|
|
234
327
|
* @param {Object} object - the object to attach the controls to
|
|
@@ -245,55 +338,55 @@ class q extends A {
|
|
|
245
338
|
code: "LOCAR_DEVICE_ORIENTATION_NO_HTTPS",
|
|
246
339
|
message: "DeviceOrientationEvent is only available in secure contexts (https)"
|
|
247
340
|
}) : typeof window.DeviceOrientationEvent.requestPermission == "function" && this.enablePermissionDialog ? this.obtainPermissionGesture() : this.eventEmitter.emit("deviceorientationgranted", { target: this });
|
|
248
|
-
}, this.eventEmitter = new
|
|
341
|
+
}, this.eventEmitter = new w();
|
|
249
342
|
const t = this;
|
|
250
|
-
this.object = e, this.object.rotation.reorder("YXZ"), this.enabled = !0, this.deviceOrientation = null, this.screenOrientation = 0, this.alphaOffset = 0, this.orientationOffset = 0, this.initialOffset = null, this.lastQuaternion = null, this.orientationChangeEventName = "ondeviceorientationabsolute" in window ? "deviceorientationabsolute" : "deviceorientation", this.smoothingFactor = i.smoothingFactor ||
|
|
251
|
-
const
|
|
252
|
-
let { alpha:
|
|
253
|
-
if (
|
|
254
|
-
const
|
|
255
|
-
t.alphaOffset =
|
|
343
|
+
this.object = e, this.object.rotation.reorder("YXZ"), this.enabled = !0, this.deviceOrientation = null, this.screenOrientation = 0, this.alphaOffset = 0, this.orientationOffset = 0, this.initialOffset = null, this.lastQuaternion = null, this.orientationChangeEventName = "ondeviceorientationabsolute" in window ? "deviceorientationabsolute" : "deviceorientation", this.smoothingFactor = i.smoothingFactor || 0.2, this.enablePermissionDialog = i.enablePermissionDialog ?? !0, this.enableInlineStyling = i.enableStyling ?? !0, this.preferConfirmDialog = i.preferConfirmDialog ?? !1, this.orientationChangeThreshold = i.orientationChangeThreshold ?? 0;
|
|
344
|
+
const o = (n) => {
|
|
345
|
+
let { alpha: s, beta: d, gamma: c, webkitCompassHeading: a } = n;
|
|
346
|
+
if (s = s ?? 0, d = d ?? 0, c = c ?? 0, a = a ?? 0, E) {
|
|
347
|
+
const h = 360 - a;
|
|
348
|
+
t.alphaOffset = p.degToRad(h - s), t.deviceOrientation = { alpha: s, beta: d, gamma: c, webkitCompassHeading: a };
|
|
256
349
|
} else
|
|
257
|
-
|
|
350
|
+
s < 0 && (s += 360), t.deviceOrientation = { alpha: s, beta: d, gamma: c };
|
|
258
351
|
window.dispatchEvent(
|
|
259
352
|
new CustomEvent("camera-rotation-change", {
|
|
260
353
|
detail: { cameraRotation: e.rotation }
|
|
261
354
|
})
|
|
262
355
|
);
|
|
263
|
-
},
|
|
264
|
-
t.screenOrientation = window.screen.orientation?.angle ?? 0,
|
|
265
|
-
},
|
|
266
|
-
|
|
356
|
+
}, l = () => {
|
|
357
|
+
t.screenOrientation = window.screen.orientation?.angle ?? 0, E && (t.screenOrientation === 90 ? t.orientationOffset = -A : t.screenOrientation === -90 ? t.orientationOffset = A : t.orientationOffset = 0);
|
|
358
|
+
}, m = (n, s, d, c, a) => {
|
|
359
|
+
I.set(d, s, -c, "YXZ"), n.setFromEuler(I), n.multiply(V), n.multiply(F.setFromAxisAngle(W, -a));
|
|
267
360
|
};
|
|
268
361
|
this.connect = () => {
|
|
269
|
-
|
|
362
|
+
l(), window.addEventListener(
|
|
270
363
|
"orientationchange",
|
|
271
|
-
|
|
364
|
+
l
|
|
272
365
|
), window.addEventListener(
|
|
273
366
|
t.orientationChangeEventName,
|
|
274
|
-
|
|
367
|
+
o
|
|
275
368
|
), t.enabled = !0;
|
|
276
369
|
}, this.disconnect = () => {
|
|
277
370
|
window.removeEventListener(
|
|
278
371
|
"orientationchange",
|
|
279
|
-
|
|
372
|
+
l
|
|
280
373
|
), window.removeEventListener(
|
|
281
374
|
t.orientationChangeEventName,
|
|
282
|
-
|
|
375
|
+
o
|
|
283
376
|
), t.enabled = !1, t.initialOffset = !1, t.deviceOrientation = null, t.lastQuaternion = null;
|
|
284
377
|
}, this.requestOrientationPermissions = () => {
|
|
285
|
-
window.DeviceOrientationEvent !== void 0 && typeof window.DeviceOrientationEvent.requestPermission == "function" ? window.DeviceOrientationEvent.requestPermission().then((
|
|
286
|
-
|
|
378
|
+
window.DeviceOrientationEvent !== void 0 && typeof window.DeviceOrientationEvent.requestPermission == "function" ? window.DeviceOrientationEvent.requestPermission().then((n) => {
|
|
379
|
+
n === "granted" ? this.eventEmitter.emit("deviceorientationgranted", {
|
|
287
380
|
target: this
|
|
288
381
|
}) : this.eventEmitter.emit("deviceorientationerror", {
|
|
289
382
|
code: "LOCAR_DEVICE_ORIENTATION_PERMISSION_DENIED",
|
|
290
383
|
message: "Permission for device orientation denied - AR will not work correctly"
|
|
291
384
|
});
|
|
292
|
-
}).catch((
|
|
385
|
+
}).catch((n) => {
|
|
293
386
|
this.eventEmitter.emit("deviceorientationerror", {
|
|
294
387
|
code: "LOCAR_DEVICE_ORIENTATION_PERMISSION_FAILED",
|
|
295
388
|
message: "Permission request for device orientation failed - AR will not work correctly",
|
|
296
|
-
error: JSON.stringify(
|
|
389
|
+
error: JSON.stringify(n, null, 2)
|
|
297
390
|
});
|
|
298
391
|
}) : this.eventEmitter.emit("deviceorientationerror", {
|
|
299
392
|
code: "LOCAR_DEVICE_ORIENTATION_INTERNAL_ERROR",
|
|
@@ -301,28 +394,28 @@ class q extends A {
|
|
|
301
394
|
});
|
|
302
395
|
}, this.update = () => {
|
|
303
396
|
if (t.enabled === !1) return;
|
|
304
|
-
const
|
|
305
|
-
if (
|
|
306
|
-
let
|
|
307
|
-
const
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
const g = new
|
|
311
|
-
|
|
397
|
+
const n = t.deviceOrientation;
|
|
398
|
+
if (n) {
|
|
399
|
+
let s = n.alpha ? p.degToRad(n.alpha) + t.alphaOffset : 0, d = n.beta ? p.degToRad(n.beta) : 0, c = n.gamma ? p.degToRad(n.gamma) : 0;
|
|
400
|
+
const a = t.screenOrientation ? p.degToRad(t.screenOrientation) : 0, h = new O();
|
|
401
|
+
if (E) {
|
|
402
|
+
m(h, s, d, c, a);
|
|
403
|
+
const g = new T().setFromQuaternion(
|
|
404
|
+
h,
|
|
312
405
|
"YXZ"
|
|
313
|
-
),
|
|
314
|
-
360 - (
|
|
406
|
+
), f = p.degToRad(
|
|
407
|
+
360 - (n.webkitCompassHeading ?? 0)
|
|
315
408
|
);
|
|
316
|
-
g.y =
|
|
409
|
+
g.y = f + (t.orientationOffset || 0), h.setFromEuler(g);
|
|
317
410
|
} else
|
|
318
|
-
|
|
319
|
-
if (t.lastQuaternion && t.orientationChangeThreshold > 0 &&
|
|
411
|
+
m(h, s, d, c, a);
|
|
412
|
+
if (t.lastQuaternion && t.orientationChangeThreshold > 0 && h.angleTo(t.lastQuaternion) < t.orientationChangeThreshold)
|
|
320
413
|
return;
|
|
321
414
|
if (t.smoothingFactor < 1 && t.lastQuaternion) {
|
|
322
415
|
const g = 1 - t.smoothingFactor;
|
|
323
|
-
t.object.quaternion.slerp(
|
|
416
|
+
t.object.quaternion.slerp(h, g);
|
|
324
417
|
} else
|
|
325
|
-
t.object.quaternion.copy(
|
|
418
|
+
t.object.quaternion.copy(h);
|
|
326
419
|
t.lastQuaternion = t.object.quaternion.clone(), window.dispatchEvent(
|
|
327
420
|
new CustomEvent("camera-rotation-change", {
|
|
328
421
|
detail: { cameraRotation: t.object.rotation }
|
|
@@ -330,49 +423,49 @@ class q extends A {
|
|
|
330
423
|
);
|
|
331
424
|
}
|
|
332
425
|
}, this.getCorrectedHeading = () => {
|
|
333
|
-
const { deviceOrientation:
|
|
334
|
-
if (!
|
|
335
|
-
let
|
|
336
|
-
return
|
|
426
|
+
const { deviceOrientation: n } = t;
|
|
427
|
+
if (!n) return 0;
|
|
428
|
+
let s = 0;
|
|
429
|
+
return E ? (s = 360 - (n.webkitCompassHeading ?? 0), t.orientationOffset && (s += t.orientationOffset * (180 / Math.PI), s = (s + 360) % 360)) : (n.absolute === !0 || t.orientationChangeEventName, s = n.alpha ? n.alpha : 0, s = (360 - s) % 360, s < 0 && (s += 360)), s;
|
|
337
430
|
}, this.updateAlphaOffset = () => {
|
|
338
431
|
t.initialOffset = !1;
|
|
339
432
|
}, this.dispose = () => {
|
|
340
433
|
t.disconnect();
|
|
341
434
|
}, this.getAlpha = () => {
|
|
342
|
-
const { deviceOrientation:
|
|
343
|
-
return
|
|
435
|
+
const { deviceOrientation: n } = t;
|
|
436
|
+
return n && n.alpha ? p.degToRad(n.alpha) + t.alphaOffset : 0;
|
|
344
437
|
}, this.getBeta = () => {
|
|
345
|
-
const { deviceOrientation:
|
|
346
|
-
return
|
|
438
|
+
const { deviceOrientation: n } = t;
|
|
439
|
+
return n && n.beta ? p.degToRad(n.beta) : 0;
|
|
347
440
|
}, this.getGamma = () => {
|
|
348
|
-
const { deviceOrientation:
|
|
349
|
-
return
|
|
441
|
+
const { deviceOrientation: n } = t;
|
|
442
|
+
return n && n.gamma ? p.degToRad(n.gamma) : 0;
|
|
350
443
|
}, this.createObtainPermissionGestureDialog = () => {
|
|
351
|
-
const s = document.createElement("div");
|
|
352
|
-
s.classList.add(b);
|
|
353
444
|
const n = document.createElement("div");
|
|
354
|
-
n.classList.add(
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
445
|
+
n.classList.add(_);
|
|
446
|
+
const s = document.createElement("div");
|
|
447
|
+
s.classList.add(N);
|
|
448
|
+
const d = document.createElement("div");
|
|
449
|
+
d.classList.add(L);
|
|
450
|
+
const c = document.createElement("div");
|
|
451
|
+
c.classList.add(S);
|
|
452
|
+
const a = document.createElement("button");
|
|
453
|
+
a.classList.add(H), document.body.appendChild(n), this.enableInlineStyling === !0 && (n.style.fontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", n.style.display = "flex", n.style.position = "fixed", n.style.zIndex = "100000", n.style.justifyContent = "center", n.style.alignItems = "center", n.style.backgroundColor = "rgba(0,0,0,0.2)", n.style.inset = "0", n.style.padding = "20px", s.style.backgroundColor = "rgba(220, 220, 220, 0.85)", s.style.padding = "6px 0", s.style.borderRadius = "10px", s.style.width = "100%", s.style.maxWidth = "400px", d.style.padding = "10px 12px", d.style.textAlign = "center", d.style.fontWeight = "400", d.style.fontSize = "13px", d.style.display = "flex", d.style.justifyContent = "center", d.style.alignItems = "center", c.style.display = "block", c.style.textAlign = "center", c.style.textDecoration = "none", c.style.borderTop = "rgb(180,180,180) solid 1px", a.style.display = "block", a.style.width = "100%", a.style.textAlign = "center", a.style.appearance = "none", a.style.background = "none", a.style.border = "none", a.style.outline = "none", a.style.padding = "10px", a.style.fontWeight = "400", a.style.fontSize = "16px", a.style.color = "#2e7cf1", a.style.cursor = "pointer"), n.appendChild(s), s.appendChild(d), s.appendChild(c), d.appendChild(
|
|
454
|
+
document.createTextNode(b)
|
|
362
455
|
);
|
|
363
|
-
const
|
|
364
|
-
this.requestOrientationPermissions(),
|
|
456
|
+
const h = () => {
|
|
457
|
+
this.requestOrientationPermissions(), n.style.display = "none";
|
|
365
458
|
};
|
|
366
|
-
|
|
459
|
+
a.addEventListener("click", h), a.appendChild(document.createTextNode("OK")), c.appendChild(a), document.body.appendChild(n);
|
|
367
460
|
}, this.obtainPermissionGesture = () => {
|
|
368
|
-
this.preferConfirmDialog === !0 ? window.confirm(
|
|
461
|
+
this.preferConfirmDialog === !0 ? window.confirm(b) && this.requestOrientationPermissions() : this.createObtainPermissionGestureDialog();
|
|
369
462
|
};
|
|
370
463
|
}
|
|
371
464
|
on(e, i) {
|
|
372
465
|
this.eventEmitter.on(e, i);
|
|
373
466
|
}
|
|
374
467
|
}
|
|
375
|
-
class
|
|
468
|
+
class G {
|
|
376
469
|
/**
|
|
377
470
|
* Create a ClickHandler.
|
|
378
471
|
* @param {THREE.WebGLRenderer} - The Three.js renderer on which the click
|
|
@@ -403,7 +496,7 @@ class F {
|
|
|
403
496
|
return [];
|
|
404
497
|
}
|
|
405
498
|
}
|
|
406
|
-
class
|
|
499
|
+
class q extends w {
|
|
407
500
|
#e;
|
|
408
501
|
/**
|
|
409
502
|
* Create a Webcam.
|
|
@@ -414,15 +507,18 @@ class j extends E {
|
|
|
414
507
|
* undefined), a video element will be created.
|
|
415
508
|
*/
|
|
416
509
|
constructor(e = { video: { facingMode: "environment" } }, i) {
|
|
417
|
-
super(), this.sceneWebcam = new u.Scene(), i ? this.#e = document.querySelector(i) : (this.#e = document.createElement("video"), this.#e.setAttribute("autoplay", "true"), this.#e.setAttribute("playsinline", "true"), this.#e.style.
|
|
510
|
+
super(), this.sceneWebcam = new u.Scene(), i ? this.#e = document.querySelector(i) : (this.#e = document.createElement("video"), this.#e.setAttribute("autoplay", "true"), this.#e.setAttribute("playsinline", "true"), this.#e.style.cssText += `
|
|
511
|
+
width: 100%;
|
|
512
|
+
height: 100%;
|
|
513
|
+
object-fit: cover;
|
|
514
|
+
background: black;
|
|
515
|
+
position: absolute;
|
|
516
|
+
top: 0px;
|
|
517
|
+
left: 0px;
|
|
518
|
+
z-index: -100;
|
|
519
|
+
`, document.body.appendChild(this.#e)), navigator.mediaDevices && navigator.mediaDevices.getUserMedia ? navigator.mediaDevices.getUserMedia(e).then((t) => {
|
|
418
520
|
this.#e?.addEventListener("loadedmetadata", () => {
|
|
419
|
-
this.#e
|
|
420
|
-
"width",
|
|
421
|
-
this.#e?.videoWidth.toString() ?? "0"
|
|
422
|
-
), this.#e?.setAttribute(
|
|
423
|
-
"height",
|
|
424
|
-
this.#e?.videoHeight.toString() ?? "0"
|
|
425
|
-
), this.#e?.play(), this.emit("webcamstarted", { texture: this.texture });
|
|
521
|
+
this.#e.play(), this.emit("webcamstarted", { videoWidth: this.#e.videoWidth, videoHeight: this.#e.videoHeight });
|
|
426
522
|
}), this.#e && (this.#e.srcObject = t);
|
|
427
523
|
}).catch((t) => {
|
|
428
524
|
this.emit("webcamerror", {
|
|
@@ -434,21 +530,16 @@ class j extends E {
|
|
|
434
530
|
message: "Media devices API not supported"
|
|
435
531
|
});
|
|
436
532
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
* Should be called when your application closes.
|
|
440
|
-
*/
|
|
441
|
-
dispose() {
|
|
442
|
-
this.texture?.dispose();
|
|
533
|
+
getVideoDimensions() {
|
|
534
|
+
return `w ${this.#e.videoWidth}, h ${this.#e.videoHeight}`;
|
|
443
535
|
}
|
|
444
536
|
}
|
|
445
|
-
const k = "0.1.8";
|
|
446
537
|
export {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
538
|
+
j as App,
|
|
539
|
+
G as ClickHandler,
|
|
540
|
+
z as DeviceOrientationControls,
|
|
541
|
+
w as EventEmitter,
|
|
542
|
+
y as LocAR,
|
|
543
|
+
x as SphMercProjection,
|
|
544
|
+
q as Webcam
|
|
454
545
|
};
|
package/dist/locar.umd.js
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
-
(function(d,a){typeof exports=="object"&&typeof module<"u"?a(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],a):(d=typeof globalThis<"u"?globalThis:d||self,a(d.locar={},d.THREE))})(this,(function(d,a){"use strict";function b(h){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(h){for(const i in h)if(i!=="default"){const t=Object.getOwnPropertyDescriptor(h,i);Object.defineProperty(e,i,t.get?t:{enumerable:!0,get:()=>h[i]})}}return e.default=h,Object.freeze(e)}const m=b(a);class y{constructor(){this.project=(e,i)=>[this.#e(e),this.#t(i)],this.unproject=e=>[this.#i(e[0]),this.#n(e[1])],this.#e=e=>e/180*this.HALF_EARTH,this.#t=e=>{var i=Math.log(Math.tan((90+e)*Math.PI/360))/(Math.PI/180);return i*this.HALF_EARTH/180},this.#i=e=>e/this.HALF_EARTH*180,this.#n=e=>{var i=e/this.HALF_EARTH*180;return i=180/Math.PI*(2*Math.atan(Math.exp(i*Math.PI/180))-Math.PI/2),i},this.getID=()=>"epsg:3857",this.EARTH=4007501668e-2,this.HALF_EARTH=2003750834e-2}#e;#t;#i;#n}class f{constructor(){this.on=(e,i)=>{this.eventHandlers[e]===void 0&&(this.eventHandlers[e]=[]),this.eventHandlers[e].push(i)},this.emit=(e,...i)=>{this.eventHandlers[e]?.forEach(t=>{t(...i)})},this.off=(e,i)=>{if(this.eventHandlers[e]){const t=this.eventHandlers[e].indexOf(i);t>-1&&this.eventHandlers[e].splice(t,1)}},this.eventHandlers={}}}class w extends f{#e;#t;#i;#n;#a;#s;#l;#r;#o;constructor(e,i,t={},c=null){super(),this.scene=e,this.camera=i,this.#e=new y,this.#t=null,this.#i=0,this.#n=100,this.#a=null,this.setGpsOptions(t),this.#s=null,this.#l=0,this.#r=0,this.#o=c}setProjection(e){this.#e=e}setGpsOptions(e={}){e.gpsMinDistance!==void 0&&(this.#i=e.gpsMinDistance),e.gpsMinAccuracy!==void 0&&(this.#n=e.gpsMinAccuracy)}async startGps(){if(this.#o){const i=await(await this.#o.sendData("/gps/start",{gpsMinDistance:this.#i,gpsMinAccuracy:this.#n})).json();this.#r=i.session}return this.#a===null?(this.#a=navigator.geolocation.watchPosition(e=>{this.#d(e)},e=>{this.emit("gpserror",e)},{enableHighAccuracy:!0}),!0):!1}stopGps(){return this.#a!==null?(navigator.geolocation.clearWatch(this.#a),this.#a=null,!0):!1}fakeGps(e,i,t=null,c=0){t!==null&&this.setElevation(t),this.#d({coords:{longitude:e,latitude:i,accuracy:c}})}lonLatToWorldCoords(e,i){const t=this.#e.project(e,i);if(this.#s)t[0]-=this.#s[0],t[1]-=this.#s[1];else throw"No initial position determined";return[t[0],-t[1]]}add(e,i,t,c,u={}){e.properties=u,this.#c(e,i,t,c),this.scene.add(e),this.#o?.sendData("/object/new",{position:e.position,x:e.position.x,z:e.position.z,session:this.#r,properties:u})}#c(e,i,t,c){const u=this.lonLatToWorldCoords(i,t);c!==void 0&&(e.position.y=c),[e.position.x,e.position.z]=u}setElevation(e){this.camera.position.y=e}#h(e,i){this.#s=this.#e.project(e,i)}#d(e){let i=Number.MAX_VALUE;this.#l++,this.#o?.sendData("/gps/new",{gpsCount:this.#l,lat:e.coords.latitude,lon:e.coords.longitude,acc:e.coords.accuracy,session:this.#r}),e.coords.accuracy<=this.#n&&(this.#t===null?this.#t={latitude:e.coords.latitude,longitude:e.coords.longitude}:i=this.#u(this.#t,e.coords),i>=this.#i&&(this.#t.longitude=e.coords.longitude,this.#t.latitude=e.coords.latitude,this.#s||(this.#h(e.coords.longitude,e.coords.latitude),this.#o?.sendData("/worldorigin/new",{gpsCount:this.#l,lat:e.coords.latitude,lon:e.coords.longitude,session:this.#r,initialPosition:this.#s})),this.#c(this.camera,e.coords.longitude,e.coords.latitude),this.#o?.sendData("/gps/accepted",{gpsCount:this.#l,cameraX:this.camera.position.x,cameraZ:this.camera.position.z,session:this.#r,distMoved:i}),this.emit("gpsupdate",{position:e,distMoved:i})))}#u(e,i){const t=m.MathUtils.degToRad(i.longitude-e.longitude),c=m.MathUtils.degToRad(i.latitude-e.latitude),u=Math.sin(c/2)*Math.sin(c/2)+Math.cos(m.MathUtils.degToRad(e.latitude))*Math.cos(m.MathUtils.degToRad(i.latitude))*(Math.sin(t/2)*Math.sin(t/2));return 2*Math.atan2(Math.sqrt(u),Math.sqrt(1-u))*6371e3}getLastKnownLocation(){return this.#t}}const C="locar-device-orientation-permission-modal",A="locar-device-orientation-permission-button",_="locar-device-orientation-permission-message",P="locar-device-orientation-permission-inner",D="locar-device-orientation-permission-button-inner",E="This immersive website requires access to your device motion sensors.",v=navigator.userAgent.match(/iPhone|iPad|iPod/i)||/Macintosh/i.test(navigator.userAgent)&&navigator.maxTouchPoints!=null&&navigator.maxTouchPoints>1,T=new a.Vector3(0,0,1),I=new a.Euler,R=new a.Quaternion,S=new a.Quaternion(-Math.sqrt(.5),0,0,Math.sqrt(.5)),M=.5*Math.PI;class N extends a.EventDispatcher{constructor(e,i={}){super(),this.init=()=>{window.DeviceOrientationEvent===void 0?this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_NOT_SUPPORTED",message:"Device orientation API not supported"}):window.isSecureContext===!1?this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_NO_HTTPS",message:"DeviceOrientationEvent is only available in secure contexts (https)"}):typeof window.DeviceOrientationEvent.requestPermission=="function"&&this.enablePermissionDialog?this.obtainPermissionGesture():this.eventEmitter.emit("deviceorientationgranted",{target:this})},this.eventEmitter=new f;const t=this;this.object=e,this.object.rotation.reorder("YXZ"),this.enabled=!0,this.deviceOrientation=null,this.screenOrientation=0,this.alphaOffset=0,this.orientationOffset=0,this.initialOffset=null,this.lastQuaternion=null,this.orientationChangeEventName="ondeviceorientationabsolute"in window?"deviceorientationabsolute":"deviceorientation",this.smoothingFactor=i.smoothingFactor||1,this.enablePermissionDialog=i.enablePermissionDialog??!0,this.enableInlineStyling=i.enableStyling??!0,this.preferConfirmDialog=i.preferConfirmDialog??!1,this.orientationChangeThreshold=i.orientationChangeThreshold??0;const c=n=>{let{alpha:s,beta:r,gamma:l,webkitCompassHeading:o}=n;if(s=s??0,r=r??0,l=l??0,o=o??0,v){const g=360-o;t.alphaOffset=a.MathUtils.degToRad(g-s),t.deviceOrientation={alpha:s,beta:r,gamma:l,webkitCompassHeading:o}}else s<0&&(s+=360),t.deviceOrientation={alpha:s,beta:r,gamma:l};window.dispatchEvent(new CustomEvent("camera-rotation-change",{detail:{cameraRotation:e.rotation}}))},u=()=>{t.screenOrientation=window.screen.orientation?.angle??0,v&&(t.screenOrientation===90?t.orientationOffset=-M:t.screenOrientation===-90?t.orientationOffset=M:t.orientationOffset=0)},O=(n,s,r,l,o)=>{I.set(r,s,-l,"YXZ"),n.setFromEuler(I),n.multiply(S),n.multiply(R.setFromAxisAngle(T,-o))};this.connect=()=>{u(),window.addEventListener("orientationchange",u),window.addEventListener(t.orientationChangeEventName,c),t.enabled=!0},this.disconnect=()=>{window.removeEventListener("orientationchange",u),window.removeEventListener(t.orientationChangeEventName,c),t.enabled=!1,t.initialOffset=!1,t.deviceOrientation=null,t.lastQuaternion=null},this.requestOrientationPermissions=()=>{window.DeviceOrientationEvent!==void 0&&typeof window.DeviceOrientationEvent.requestPermission=="function"?window.DeviceOrientationEvent.requestPermission().then(n=>{n==="granted"?this.eventEmitter.emit("deviceorientationgranted",{target:this}):this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_PERMISSION_DENIED",message:"Permission for device orientation denied - AR will not work correctly"})}).catch(n=>{this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_PERMISSION_FAILED",message:"Permission request for device orientation failed - AR will not work correctly",error:JSON.stringify(n,null,2)})}):this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_INTERNAL_ERROR",message:"Internal error: no requestPermission() found although requestOrientationPermissions() was called - please raise an issue on GitHub"})},this.update=()=>{if(t.enabled===!1)return;const n=t.deviceOrientation;if(n){let s=n.alpha?a.MathUtils.degToRad(n.alpha)+t.alphaOffset:0,r=n.beta?a.MathUtils.degToRad(n.beta):0,l=n.gamma?a.MathUtils.degToRad(n.gamma):0;const o=t.screenOrientation?a.MathUtils.degToRad(t.screenOrientation):0,g=new a.Quaternion;if(v){O(g,s,r,l,o);const p=new a.Euler().setFromQuaternion(g,"YXZ"),U=a.MathUtils.degToRad(360-(n.webkitCompassHeading??0));p.y=U+(t.orientationOffset||0),g.setFromEuler(p)}else O(g,s,r,l,o);if(t.lastQuaternion&&t.orientationChangeThreshold>0&&g.angleTo(t.lastQuaternion)<t.orientationChangeThreshold)return;if(t.smoothingFactor<1&&t.lastQuaternion){const p=1-t.smoothingFactor;t.object.quaternion.slerp(g,p)}else t.object.quaternion.copy(g);t.lastQuaternion=t.object.quaternion.clone(),window.dispatchEvent(new CustomEvent("camera-rotation-change",{detail:{cameraRotation:t.object.rotation}}))}},this.getCorrectedHeading=()=>{const{deviceOrientation:n}=t;if(!n)return 0;let s=0;return v?(s=360-(n.webkitCompassHeading??0),t.orientationOffset&&(s+=t.orientationOffset*(180/Math.PI),s=(s+360)%360)):(n.absolute===!0||t.orientationChangeEventName,s=n.alpha?n.alpha:0,s=(360-s)%360,s<0&&(s+=360)),s},this.updateAlphaOffset=()=>{t.initialOffset=!1},this.dispose=()=>{t.disconnect()},this.getAlpha=()=>{const{deviceOrientation:n}=t;return n&&n.alpha?a.MathUtils.degToRad(n.alpha)+t.alphaOffset:0},this.getBeta=()=>{const{deviceOrientation:n}=t;return n&&n.beta?a.MathUtils.degToRad(n.beta):0},this.getGamma=()=>{const{deviceOrientation:n}=t;return n&&n.gamma?a.MathUtils.degToRad(n.gamma):0},this.createObtainPermissionGestureDialog=()=>{const n=document.createElement("div");n.classList.add(C);const s=document.createElement("div");s.classList.add(P);const r=document.createElement("div");r.classList.add(_);const l=document.createElement("div");l.classList.add(D);const o=document.createElement("button");o.classList.add(A),document.body.appendChild(n),this.enableInlineStyling===!0&&(n.style.fontFamily="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",n.style.display="flex",n.style.position="fixed",n.style.zIndex="100000",n.style.justifyContent="center",n.style.alignItems="center",n.style.backgroundColor="rgba(0,0,0,0.2)",n.style.inset="0",n.style.padding="20px",s.style.backgroundColor="rgba(220, 220, 220, 0.85)",s.style.padding="6px 0",s.style.borderRadius="10px",s.style.width="100%",s.style.maxWidth="400px",r.style.padding="10px 12px",r.style.textAlign="center",r.style.fontWeight="400",r.style.fontSize="13px",r.style.display="flex",r.style.justifyContent="center",r.style.alignItems="center",l.style.display="block",l.style.textAlign="center",l.style.textDecoration="none",l.style.borderTop="rgb(180,180,180) solid 1px",o.style.display="block",o.style.width="100%",o.style.textAlign="center",o.style.appearance="none",o.style.background="none",o.style.border="none",o.style.outline="none",o.style.padding="10px",o.style.fontWeight="400",o.style.fontSize="16px",o.style.color="#2e7cf1",o.style.cursor="pointer"),n.appendChild(s),s.appendChild(r),s.appendChild(l),r.appendChild(document.createTextNode(E));const g=()=>{this.requestOrientationPermissions(),n.style.display="none"};o.addEventListener("click",g),o.appendChild(document.createTextNode("OK")),l.appendChild(o),document.body.appendChild(n)},this.obtainPermissionGesture=()=>{this.preferConfirmDialog===!0?window.confirm(E)&&this.requestOrientationPermissions():this.createObtainPermissionGestureDialog()}}on(e,i){this.eventEmitter.on(e,i)}}class L{constructor(e){this.raycaster=new m.Raycaster,this.normalisedMousePosition=null,e.domElement.addEventListener("click",i=>{this.normalisedMousePosition=new m.Vector2(i.clientX/e.domElement.clientWidth*2-1,-(i.clientY/e.domElement.clientHeight*2)+1)})}raycast(e,i){if(this.normalisedMousePosition!==null){this.raycaster.setFromCamera(this.normalisedMousePosition,e);const t=this.raycaster.intersectObjects(i.children,!1);return this.normalisedMousePosition=null,t}return[]}}class x extends f{#e;constructor(e={video:{facingMode:"environment"}},i){super(),this.sceneWebcam=new m.Scene,i?this.#e=document.querySelector(i):(this.#e=document.createElement("video"),this.#e.setAttribute("autoplay","true"),this.#e.setAttribute("playsinline","true"),this.#e.style.display="none",document.body.appendChild(this.#e)),this.texture=this.#e?new m.VideoTexture(this.#e):null,navigator.mediaDevices&&navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia(e).then(t=>{this.#e?.addEventListener("loadedmetadata",()=>{this.#e?.setAttribute("width",this.#e?.videoWidth.toString()??"0"),this.#e?.setAttribute("height",this.#e?.videoHeight.toString()??"0"),this.#e?.play(),this.emit("webcamstarted",{texture:this.texture})}),this.#e&&(this.#e.srcObject=t)}).catch(t=>{this.emit("webcamerror",{code:t.name,message:t.message})}):this.emit("webcamerror",{code:"LOCAR_NO_MEDIA_DEVICES_API",message:"Media devices API not supported"})}dispose(){this.texture?.dispose()}}const j="0.1.8";d.ClickHandler=L,d.DeviceOrientationControls=N,d.EventEmitter=f,d.LocationBased=w,d.SphMercProjection=y,d.Webcam=x,d.version=j,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(g,u){typeof exports=="object"&&typeof module<"u"?u(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],u):(g=typeof globalThis<"u"?globalThis:g||self,u(g.locar={},g.THREE))})(this,(function(g,u){"use strict";function _(f){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(f){for(const i in f)if(i!=="default"){const t=Object.getOwnPropertyDescriptor(f,i);Object.defineProperty(e,i,t.get?t:{enumerable:!0,get:()=>f[i]})}}return e.default=f,Object.freeze(e)}const m=_(u);class w{constructor(){this.on=(e,i)=>{this.eventHandlers[e]===void 0&&(this.eventHandlers[e]=[]),this.eventHandlers[e].push(i)},this.emit=(e,...i)=>{this.eventHandlers[e]?.forEach(t=>{t(...i)})},this.off=(e,i)=>{if(this.eventHandlers[e]){const t=this.eventHandlers[e].indexOf(i);t>-1&&this.eventHandlers[e].splice(t,1)}},this.eventHandlers={}}}class T extends w{constructor({cameraOptions:e,canvas:i,gpsOptions:t,videoConstraints:o,deviceOrientationOptions:l,serverLogger:p,projection:n}){super(),this.origHfov=e?.hFov||80;const s=0;this.cameraFeedDimensions=null;const d=window.innerWidth/window.innerHeight;this.camera=new m.PerspectiveCamera(this.origHfov/d,d,e?.near||.001,e?.far||1e3),i?(this.renderer=new m.WebGLRenderer({canvas:i,alpha:!0}),this.renderer.setClearColor(65280,s)):(this.renderer=new m.WebGLRenderer({alpha:!0}),this.renderer.setClearColor(65280,s),document.body.appendChild(this.renderer.domElement)),this.renderer.setSize(window.innerWidth,window.innerHeight),this.scene=new m.Scene;const c=l||{enabled:!0};window.addEventListener("resize",()=>{this.renderer.setSize(window.innerWidth,window.innerHeight);const a=window.innerWidth/window.innerHeight;if(this.camera.aspect=a,this.cameraFeedDimensions!==null){const h=a>1?this.cameraFeedDimensions.landWidth:this.cameraFeedDimensions.landHeight,v=a>1?this.cameraFeedDimensions.landHeight:this.cameraFeedDimensions.landWidth;this.#e(h,v,a)}this.camera.updateProjectionMatrix()}),this.locar=new y(this.scene,this.camera,t,p,n),this.webcam=new D(o),this.deviceOrientationControls=c.enabled===!0?new A(this.camera,c):null,this.renderer.setAnimationLoop(()=>{this.deviceOrientationControls?.update(),this.renderer.render(this.scene,this.camera)})}start(){return new Promise((i,t)=>{this.webcam.on("webcamstarted",o=>{const l=o.videoWidth>o.videoHeight;this.cameraFeedDimensions={landWidth:l?o.videoWidth:o.videoHeight,landHeight:l?o.videoHeight:o.videoWidth},this.#e(o.videoWidth,o.videoHeight,window.innerWidth/window.innerHeight),this.camera.updateProjectionMatrix()}),this.webcam.on("webcamerror",o=>{t({code:o.code,message:o.message})}),this.deviceOrientationControls===null?i(this.locar):(this.deviceOrientationControls?.on("deviceorientationgranted",o=>{o.target.connect(),i(this.locar)}),this.deviceOrientationControls.on("deviceorientationerror",o=>{t({code:o.code,message:o.message})}),this.deviceOrientationControls.init())})}#e(e,i,t){const o=e/i;if(t<o){const l=e*(window.innerHeight/i),p=this.origHfov*(window.innerWidth/l);this.camera.fov=p/t}else this.camera.fov=this.origHfov/t}}class b{constructor(){this.project=(e,i)=>[this.#e(e),this.#t(i)],this.unproject=e=>[this.#i(e[0]),this.#n(e[1])],this.#e=e=>e/180*this.HALF_EARTH,this.#t=e=>{var i=Math.log(Math.tan((90+e)*Math.PI/360))/(Math.PI/180);return i*this.HALF_EARTH/180},this.#i=e=>e/this.HALF_EARTH*180,this.#n=e=>{var i=e/this.HALF_EARTH*180;return i=180/Math.PI*(2*Math.atan(Math.exp(i*Math.PI/180))-Math.PI/2),i},this.getID=()=>"epsg:3857",this.EARTH=4007501668e-2,this.HALF_EARTH=2003750834e-2}#e;#t;#i;#n}class y extends w{#e;#t;#i;#n;#a;#s;#d;#r;#o;constructor(e,i,t={},o=null,l=new b){super(),this.scene=e,this.camera=i,this.#e=l,this.#t=null,this.#i=0,this.#n=100,this.#a=null,this.setGpsOptions(t),this.#s=null,this.#d=0,this.#r=0,this.#o=o}setProjection(e){this.#e=e}setGpsOptions(e={}){e.gpsMinDistance!==void 0&&(this.#i=e.gpsMinDistance),e.gpsMinAccuracy!==void 0&&(this.#n=e.gpsMinAccuracy)}async startGps(){if(this.#o){const i=await(await this.#o.sendData("/gps/start",{gpsMinDistance:this.#i,gpsMinAccuracy:this.#n})).json();this.#r=i.session}return this.#a===null?(this.#a=navigator.geolocation.watchPosition(e=>{this.#c(e)},e=>{this.emit("gpserror",e)},{enableHighAccuracy:!0}),!0):!1}stopGps(){return this.#a!==null?(navigator.geolocation.clearWatch(this.#a),this.#a=null,!0):!1}fakeGps(e,i,t=null,o=0){t!==null&&this.setElevation(t),this.#c({coords:{longitude:e,latitude:i,accuracy:o}})}lonLatToWorldCoords(e,i){const t=this.#e.project(e,i);if(this.#s)t[0]-=this.#s[0],t[1]-=this.#s[1];else throw"No initial position determined";return[t[0],-t[1]]}add(e,i,t,o,l={}){e.properties=l,this.#l(e,i,t,o||0),this.scene.add(e),this.#o?.sendData("/object/new",{position:e.position,x:e.position.x,z:e.position.z,session:this.#r,properties:l})}addGeoLine(e,i,t=1){const o=e.map((n=>{const[s,d]=this.lonLatToWorldCoords(n[0],n[1]);return new m.Vector3(s,n[2]||0,d)})),l=this.#h(o,t);i.setValues({side:m.DoubleSide});const p=new m.Mesh(l,i);this.scene.add(p)}#h(e,i){let t,o,l,p,n=0,s=0,d=[],c;const a=e.length-1,h=[];for(let r=0;r<a;r++)t=e[r+1].x-e[r].x,o=e[r+1].z-e[r].z,l=e[r+1].y-e[r].y,p=Math.sqrt(t*t+l*l+o*o),n=-(o*(i/2))/p,s=t*(i/2)/p,c=[e[r].x-n,e[r].y,e[r].z-s,e[r].x+n,e[r].y,e[r].z+s],r>0&&c.forEach((P,V)=>{P=(P+d[V])/2}),h.push(...c),d=[e[r+1].x-n,e[r+1].y,e[r+1].z-s,e[r+1].x+n,e[r+1].y,e[r+1].z+s];h.push(e[a].x-n),h.push(e[a].y),h.push(e[a].z-s),h.push(e[a].x+n),h.push(e[a].y),h.push(e[a].z+s);let v=[];for(let r=0;r<a;r++)v.push(r*2,r*2+1,r*2+2),v.push(r*2+1,r*2+3,r*2+2);let O=new m.BufferGeometry,z=new Float32Array(h);return O.setIndex(v),O.setAttribute("position",new m.BufferAttribute(z,3)),O.computeBoundingBox(),O}#l(e,i,t,o){const l=this.lonLatToWorldCoords(i,t);o!==void 0&&(e.position.y=o),[e.position.x,e.position.z]=l}setElevation(e){this.camera.position.y=e}#u(e,i){this.#s=this.#e.project(e,i)}#c(e){let i=Number.MAX_VALUE;this.#d++,this.#o?.sendData("/gps/new",{gpsCount:this.#d,lat:e.coords.latitude,lon:e.coords.longitude,acc:e.coords.accuracy,session:this.#r}),e.coords.accuracy<=this.#n&&(this.#t===null?this.#t={latitude:e.coords.latitude,longitude:e.coords.longitude}:i=y.haversineDist(this.#t,e.coords),i>=this.#i&&(this.#t.longitude=e.coords.longitude,this.#t.latitude=e.coords.latitude,this.#s||(this.#u(e.coords.longitude,e.coords.latitude),this.#o?.sendData("/worldorigin/new",{gpsCount:this.#d,lat:e.coords.latitude,lon:e.coords.longitude,session:this.#r,initialPosition:this.#s})),this.#l(this.camera,e.coords.longitude,e.coords.latitude),this.#o?.sendData("/gps/accepted",{gpsCount:this.#d,cameraX:this.camera.position.x,cameraZ:this.camera.position.z,session:this.#r,distMoved:i}),this.emit("gpsupdate",{position:e,distMoved:i})))}static haversineDist(e,i){const t=m.MathUtils.degToRad(i.longitude-e.longitude),o=m.MathUtils.degToRad(i.latitude-e.latitude),l=Math.sin(o/2)*Math.sin(o/2)+Math.cos(m.MathUtils.degToRad(e.latitude))*Math.cos(m.MathUtils.degToRad(i.latitude))*(Math.sin(t/2)*Math.sin(t/2));return 2*Math.atan2(Math.sqrt(l),Math.sqrt(1-l))*6371e3}getLastKnownLocation(){return this.#t}}const R="locar-device-orientation-permission-modal",x="locar-device-orientation-permission-button",L="locar-device-orientation-permission-message",S="locar-device-orientation-permission-inner",N="locar-device-orientation-permission-button-inner",C="This immersive website requires access to your device motion sensors.",E=navigator.userAgent.match(/iPhone|iPad|iPod/i)||/Macintosh/i.test(navigator.userAgent)&&navigator.maxTouchPoints!=null&&navigator.maxTouchPoints>1,H=new u.Vector3(0,0,1),M=new u.Euler,W=new u.Quaternion,F=new u.Quaternion(-Math.sqrt(.5),0,0,Math.sqrt(.5)),I=.5*Math.PI;class A extends u.EventDispatcher{constructor(e,i={}){super(),this.init=()=>{window.DeviceOrientationEvent===void 0?this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_NOT_SUPPORTED",message:"Device orientation API not supported"}):window.isSecureContext===!1?this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_NO_HTTPS",message:"DeviceOrientationEvent is only available in secure contexts (https)"}):typeof window.DeviceOrientationEvent.requestPermission=="function"&&this.enablePermissionDialog?this.obtainPermissionGesture():this.eventEmitter.emit("deviceorientationgranted",{target:this})},this.eventEmitter=new w;const t=this;this.object=e,this.object.rotation.reorder("YXZ"),this.enabled=!0,this.deviceOrientation=null,this.screenOrientation=0,this.alphaOffset=0,this.orientationOffset=0,this.initialOffset=null,this.lastQuaternion=null,this.orientationChangeEventName="ondeviceorientationabsolute"in window?"deviceorientationabsolute":"deviceorientation",this.smoothingFactor=i.smoothingFactor||.2,this.enablePermissionDialog=i.enablePermissionDialog??!0,this.enableInlineStyling=i.enableStyling??!0,this.preferConfirmDialog=i.preferConfirmDialog??!1,this.orientationChangeThreshold=i.orientationChangeThreshold??0;const o=n=>{let{alpha:s,beta:d,gamma:c,webkitCompassHeading:a}=n;if(s=s??0,d=d??0,c=c??0,a=a??0,E){const h=360-a;t.alphaOffset=u.MathUtils.degToRad(h-s),t.deviceOrientation={alpha:s,beta:d,gamma:c,webkitCompassHeading:a}}else s<0&&(s+=360),t.deviceOrientation={alpha:s,beta:d,gamma:c};window.dispatchEvent(new CustomEvent("camera-rotation-change",{detail:{cameraRotation:e.rotation}}))},l=()=>{t.screenOrientation=window.screen.orientation?.angle??0,E&&(t.screenOrientation===90?t.orientationOffset=-I:t.screenOrientation===-90?t.orientationOffset=I:t.orientationOffset=0)},p=(n,s,d,c,a)=>{M.set(d,s,-c,"YXZ"),n.setFromEuler(M),n.multiply(F),n.multiply(W.setFromAxisAngle(H,-a))};this.connect=()=>{l(),window.addEventListener("orientationchange",l),window.addEventListener(t.orientationChangeEventName,o),t.enabled=!0},this.disconnect=()=>{window.removeEventListener("orientationchange",l),window.removeEventListener(t.orientationChangeEventName,o),t.enabled=!1,t.initialOffset=!1,t.deviceOrientation=null,t.lastQuaternion=null},this.requestOrientationPermissions=()=>{window.DeviceOrientationEvent!==void 0&&typeof window.DeviceOrientationEvent.requestPermission=="function"?window.DeviceOrientationEvent.requestPermission().then(n=>{n==="granted"?this.eventEmitter.emit("deviceorientationgranted",{target:this}):this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_PERMISSION_DENIED",message:"Permission for device orientation denied - AR will not work correctly"})}).catch(n=>{this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_PERMISSION_FAILED",message:"Permission request for device orientation failed - AR will not work correctly",error:JSON.stringify(n,null,2)})}):this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_INTERNAL_ERROR",message:"Internal error: no requestPermission() found although requestOrientationPermissions() was called - please raise an issue on GitHub"})},this.update=()=>{if(t.enabled===!1)return;const n=t.deviceOrientation;if(n){let s=n.alpha?u.MathUtils.degToRad(n.alpha)+t.alphaOffset:0,d=n.beta?u.MathUtils.degToRad(n.beta):0,c=n.gamma?u.MathUtils.degToRad(n.gamma):0;const a=t.screenOrientation?u.MathUtils.degToRad(t.screenOrientation):0,h=new u.Quaternion;if(E){p(h,s,d,c,a);const v=new u.Euler().setFromQuaternion(h,"YXZ"),O=u.MathUtils.degToRad(360-(n.webkitCompassHeading??0));v.y=O+(t.orientationOffset||0),h.setFromEuler(v)}else p(h,s,d,c,a);if(t.lastQuaternion&&t.orientationChangeThreshold>0&&h.angleTo(t.lastQuaternion)<t.orientationChangeThreshold)return;if(t.smoothingFactor<1&&t.lastQuaternion){const v=1-t.smoothingFactor;t.object.quaternion.slerp(h,v)}else t.object.quaternion.copy(h);t.lastQuaternion=t.object.quaternion.clone(),window.dispatchEvent(new CustomEvent("camera-rotation-change",{detail:{cameraRotation:t.object.rotation}}))}},this.getCorrectedHeading=()=>{const{deviceOrientation:n}=t;if(!n)return 0;let s=0;return E?(s=360-(n.webkitCompassHeading??0),t.orientationOffset&&(s+=t.orientationOffset*(180/Math.PI),s=(s+360)%360)):(n.absolute===!0||t.orientationChangeEventName,s=n.alpha?n.alpha:0,s=(360-s)%360,s<0&&(s+=360)),s},this.updateAlphaOffset=()=>{t.initialOffset=!1},this.dispose=()=>{t.disconnect()},this.getAlpha=()=>{const{deviceOrientation:n}=t;return n&&n.alpha?u.MathUtils.degToRad(n.alpha)+t.alphaOffset:0},this.getBeta=()=>{const{deviceOrientation:n}=t;return n&&n.beta?u.MathUtils.degToRad(n.beta):0},this.getGamma=()=>{const{deviceOrientation:n}=t;return n&&n.gamma?u.MathUtils.degToRad(n.gamma):0},this.createObtainPermissionGestureDialog=()=>{const n=document.createElement("div");n.classList.add(R);const s=document.createElement("div");s.classList.add(S);const d=document.createElement("div");d.classList.add(L);const c=document.createElement("div");c.classList.add(N);const a=document.createElement("button");a.classList.add(x),document.body.appendChild(n),this.enableInlineStyling===!0&&(n.style.fontFamily="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",n.style.display="flex",n.style.position="fixed",n.style.zIndex="100000",n.style.justifyContent="center",n.style.alignItems="center",n.style.backgroundColor="rgba(0,0,0,0.2)",n.style.inset="0",n.style.padding="20px",s.style.backgroundColor="rgba(220, 220, 220, 0.85)",s.style.padding="6px 0",s.style.borderRadius="10px",s.style.width="100%",s.style.maxWidth="400px",d.style.padding="10px 12px",d.style.textAlign="center",d.style.fontWeight="400",d.style.fontSize="13px",d.style.display="flex",d.style.justifyContent="center",d.style.alignItems="center",c.style.display="block",c.style.textAlign="center",c.style.textDecoration="none",c.style.borderTop="rgb(180,180,180) solid 1px",a.style.display="block",a.style.width="100%",a.style.textAlign="center",a.style.appearance="none",a.style.background="none",a.style.border="none",a.style.outline="none",a.style.padding="10px",a.style.fontWeight="400",a.style.fontSize="16px",a.style.color="#2e7cf1",a.style.cursor="pointer"),n.appendChild(s),s.appendChild(d),s.appendChild(c),d.appendChild(document.createTextNode(C));const h=()=>{this.requestOrientationPermissions(),n.style.display="none"};a.addEventListener("click",h),a.appendChild(document.createTextNode("OK")),c.appendChild(a),document.body.appendChild(n)},this.obtainPermissionGesture=()=>{this.preferConfirmDialog===!0?window.confirm(C)&&this.requestOrientationPermissions():this.createObtainPermissionGestureDialog()}}on(e,i){this.eventEmitter.on(e,i)}}class j{constructor(e){this.raycaster=new m.Raycaster,this.normalisedMousePosition=null,e.domElement.addEventListener("click",i=>{this.normalisedMousePosition=new m.Vector2(i.clientX/e.domElement.clientWidth*2-1,-(i.clientY/e.domElement.clientHeight*2)+1)})}raycast(e,i){if(this.normalisedMousePosition!==null){this.raycaster.setFromCamera(this.normalisedMousePosition,e);const t=this.raycaster.intersectObjects(i.children,!1);return this.normalisedMousePosition=null,t}return[]}}class D extends w{#e;constructor(e={video:{facingMode:"environment"}},i){super(),this.sceneWebcam=new m.Scene,i?this.#e=document.querySelector(i):(this.#e=document.createElement("video"),this.#e.setAttribute("autoplay","true"),this.#e.setAttribute("playsinline","true"),this.#e.style.cssText+=`
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: 100%;
|
|
4
|
+
object-fit: cover;
|
|
5
|
+
background: black;
|
|
6
|
+
position: absolute;
|
|
7
|
+
top: 0px;
|
|
8
|
+
left: 0px;
|
|
9
|
+
z-index: -100;
|
|
10
|
+
`,document.body.appendChild(this.#e)),navigator.mediaDevices&&navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia(e).then(t=>{this.#e?.addEventListener("loadedmetadata",()=>{this.#e.play(),this.emit("webcamstarted",{videoWidth:this.#e.videoWidth,videoHeight:this.#e.videoHeight})}),this.#e&&(this.#e.srcObject=t)}).catch(t=>{this.emit("webcamerror",{code:t.name,message:t.message})}):this.emit("webcamerror",{code:"LOCAR_NO_MEDIA_DEVICES_API",message:"Media devices API not supported"})}getVideoDimensions(){return`w ${this.#e.videoWidth}, h ${this.#e.videoHeight}`}}g.App=T,g.ClickHandler=j,g.DeviceOrientationControls=A,g.EventEmitter=w,g.LocAR=y,g.SphMercProjection=b,g.Webcam=D,Object.defineProperty(g,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "locar",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Location-based AR from AR.js.",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"lint:fix": "prettier --write ./lib && eslint ./lib --ext .js,.json,.css --fix",
|
|
21
21
|
"typecheck": "tsc --noEmit",
|
|
22
22
|
"build": "npm run lint && npm run typecheck && vite build && npm pack",
|
|
23
|
-
"makedocs": "
|
|
23
|
+
"makedocs": "typedoc --out docs/api lib/three/main.ts"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@eslint/css": "^0.11.0",
|
|
@@ -29,17 +29,18 @@
|
|
|
29
29
|
"@types/three": "^0.181.0",
|
|
30
30
|
"eslint": "^9.38.0",
|
|
31
31
|
"globals": "^16.4.0",
|
|
32
|
-
"
|
|
32
|
+
"typedoc": "^0.28.19",
|
|
33
|
+
"minimatch": "^9.0.7",
|
|
33
34
|
"prettier-eslint": "^16.4.2",
|
|
34
|
-
"typescript": "^5.
|
|
35
|
-
"vite": "^7.
|
|
35
|
+
"typescript": "^5.9.3",
|
|
36
|
+
"vite": "^7.3.2",
|
|
36
37
|
"vite-plugin-dts": "^4.5.4"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
40
|
"minimatch": "^9.0.7",
|
|
40
41
|
"three": "^0.180.0"
|
|
41
42
|
},
|
|
42
|
-
"overrides": {
|
|
43
|
+
"overrides" : {
|
|
43
44
|
"prettier-eslint": {
|
|
44
45
|
"minimatch": "$minimatch"
|
|
45
46
|
}
|