locar 0.1.8 → 0.2.0
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 +94 -19
- package/dist/locar.es.js +231 -140
- package/dist/locar.umd.js +10 -1
- package/package.json +10 -3
package/dist/locar.d.ts
CHANGED
|
@@ -3,6 +3,54 @@ 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. Must contain "camera", a THREE.PerspectiveCamera.
|
|
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
|
+
declare interface AppOptions {
|
|
35
|
+
cameraOptions?: {
|
|
36
|
+
hFov: number;
|
|
37
|
+
near: number;
|
|
38
|
+
far: number;
|
|
39
|
+
}; /** the three.js camera options to use - note however we specify horizontal, not vertical, field of view */
|
|
40
|
+
canvas?: HTMLCanvasElement; /** the canvas to render the AR scene into (one will be created if omitted) */
|
|
41
|
+
gpsOptions?: GpsOptions; /** GPS options */
|
|
42
|
+
videoConstraints?: {
|
|
43
|
+
video: {
|
|
44
|
+
facingMode: string;
|
|
45
|
+
};
|
|
46
|
+
}; /** Video constraints for Media Devices API */
|
|
47
|
+
deviceOrientationOptions?: DeviceOrientationControlsOptions & {
|
|
48
|
+
enabled: boolean;
|
|
49
|
+
}; /** Device orientation options for DeviceOrientationControls */
|
|
50
|
+
projection?: Projection; /** Projection to use (default: SphMercProjection) */
|
|
51
|
+
serverLogger?: ServerLogger; /** Server logger to use - ensure you gain consent from the user if you are doing this, it's usually a Data Protection legal requirement */
|
|
52
|
+
}
|
|
53
|
+
|
|
6
54
|
/**
|
|
7
55
|
* Class to handle object detection via mouse clicks/touch events
|
|
8
56
|
* and raycasting.
|
|
@@ -87,6 +135,17 @@ declare type DeviceOrientationControlsOptions = {
|
|
|
87
135
|
preferConfirmDialog?: boolean;
|
|
88
136
|
};
|
|
89
137
|
|
|
138
|
+
/** Event emitted when there is an error with device orientation. */
|
|
139
|
+
export declare interface DeviceOrientationErrorEvent {
|
|
140
|
+
code: string;
|
|
141
|
+
message: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Event emitted when device orientation permission has been granted. */
|
|
145
|
+
export declare interface DeviceOrientationGrantedEvent {
|
|
146
|
+
target: DeviceOrientationControls;
|
|
147
|
+
}
|
|
148
|
+
|
|
90
149
|
/** Event emitter class to handle events. */
|
|
91
150
|
export declare class EventEmitter {
|
|
92
151
|
eventHandlers: Record<string, ((...args: any[]) => void)[]>;
|
|
@@ -116,8 +175,14 @@ declare interface GpsOptions {
|
|
|
116
175
|
gpsMinAccuracy?: number;
|
|
117
176
|
}
|
|
118
177
|
|
|
119
|
-
/**
|
|
120
|
-
export declare
|
|
178
|
+
/** Event emitted when a GPS position is received. */
|
|
179
|
+
export declare interface GpsReceivedEvent {
|
|
180
|
+
position: GeolocationPosition;
|
|
181
|
+
distMoved: number;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The main engine class for the LocAR.js system. */
|
|
185
|
+
export declare class LocAR extends EventEmitter {
|
|
121
186
|
#private;
|
|
122
187
|
scene: THREE.Scene;
|
|
123
188
|
camera: THREE.Camera;
|
|
@@ -129,14 +194,14 @@ export declare class LocationBased extends EventEmitter {
|
|
|
129
194
|
* setGpsOptions() below.
|
|
130
195
|
* @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
196
|
*/
|
|
132
|
-
constructor(scene: THREE.Scene, camera: THREE.Camera, options?: GpsOptions, serverLogger?: ServerLogger | null);
|
|
197
|
+
constructor(scene: THREE.Scene, camera: THREE.Camera, options?: GpsOptions, serverLogger?: ServerLogger | null, projection?: Projection);
|
|
133
198
|
/**
|
|
134
199
|
* Set the projection to use.
|
|
135
200
|
* @param {Object} any object which includes a project() method
|
|
136
201
|
* taking longitude and latitude as arguments and returning an array
|
|
137
202
|
* containing easting and northing.
|
|
138
203
|
*/
|
|
139
|
-
setProjection(proj:
|
|
204
|
+
setProjection(proj: Projection): void;
|
|
140
205
|
/**
|
|
141
206
|
* Set the GPS options.
|
|
142
207
|
* @param {Object} object containing gpsMinDistance and/or gpsMinAccuracy
|
|
@@ -189,12 +254,19 @@ export declare class LocationBased extends EventEmitter {
|
|
|
189
254
|
* @param {Object} properties - properties describing the object (for example,
|
|
190
255
|
* the contents of the GeoJSON properties field).
|
|
191
256
|
*/
|
|
192
|
-
add(object: THREE.Object3D, lon: number, lat: number, elev
|
|
257
|
+
add(object: THREE.Object3D, lon: number, lat: number, elev?: number | undefined, properties?: Record<string, any>): void;
|
|
258
|
+
addGeoLine(points: Array<[number, number, number?]>, material: THREE.Material, lineWidth?: number): void;
|
|
193
259
|
/**
|
|
194
260
|
* Set the elevation (y coordinate) of the camera.
|
|
195
261
|
* @param {number} elev - the elevation in metres.
|
|
196
262
|
*/
|
|
197
263
|
setElevation(elev: number): void;
|
|
264
|
+
/**
|
|
265
|
+
* Calculate haversine distance between two lat/lon pairs.
|
|
266
|
+
*
|
|
267
|
+
* Taken from original A-Frame AR.js location-based components
|
|
268
|
+
*/
|
|
269
|
+
static haversineDist(src: LonLat, dest: LonLat): number;
|
|
198
270
|
/**
|
|
199
271
|
* Obtain the last known GPS location.
|
|
200
272
|
*
|
|
@@ -203,17 +275,24 @@ export declare class LocationBased extends EventEmitter {
|
|
|
203
275
|
getLastKnownLocation(): LonLat | null;
|
|
204
276
|
}
|
|
205
277
|
|
|
206
|
-
|
|
278
|
+
/** Longitude and latitude. */
|
|
279
|
+
export declare interface LonLat {
|
|
207
280
|
longitude: number;
|
|
208
281
|
latitude: number;
|
|
209
282
|
}
|
|
210
283
|
|
|
211
|
-
|
|
284
|
+
/** Projection type. */
|
|
285
|
+
export declare interface Projection {
|
|
286
|
+
project: (lon: number, lat: number) => [number, number];
|
|
287
|
+
unproject: (projected: [number, number]) => [number, number];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Server logger interface. */
|
|
291
|
+
export declare interface ServerLogger {
|
|
212
292
|
sendData(endpoint: string, data: any): Promise<Response> | Response;
|
|
213
293
|
}
|
|
214
294
|
|
|
215
|
-
|
|
216
|
-
export declare class SphMercProjection {
|
|
295
|
+
export declare class SphMercProjection implements Projection {
|
|
217
296
|
#private;
|
|
218
297
|
EARTH: number;
|
|
219
298
|
HALF_EARTH: number;
|
|
@@ -233,7 +312,7 @@ export declare class SphMercProjection {
|
|
|
233
312
|
* @param {Array} projected - Two-member array containing easting and northing
|
|
234
313
|
* @return {Array} Two-member array containing longitude and latitude
|
|
235
314
|
*/
|
|
236
|
-
unproject: (projected: [number, number]) => number
|
|
315
|
+
unproject: (projected: [number, number]) => [number, number];
|
|
237
316
|
/**
|
|
238
317
|
* Return the projection's ID.
|
|
239
318
|
* @return {string} The value "epsg:3857".
|
|
@@ -241,13 +320,10 @@ export declare class SphMercProjection {
|
|
|
241
320
|
getID: () => string;
|
|
242
321
|
}
|
|
243
322
|
|
|
244
|
-
export declare const version = "0.1.8";
|
|
245
|
-
|
|
246
323
|
/** Class to setup the webcam. */
|
|
247
324
|
export declare class Webcam extends EventEmitter {
|
|
248
325
|
#private;
|
|
249
326
|
sceneWebcam: THREE.Scene;
|
|
250
|
-
texture: THREE.VideoTexture | null;
|
|
251
327
|
/**
|
|
252
328
|
* Create a Webcam.
|
|
253
329
|
* @param constraints {Object} - options to use for initialising the camera.
|
|
@@ -261,20 +337,19 @@ export declare class Webcam extends EventEmitter {
|
|
|
261
337
|
facingMode: string;
|
|
262
338
|
};
|
|
263
339
|
}, videoElementSelector?: string);
|
|
264
|
-
|
|
265
|
-
* Free up the memory associated with the webcam.
|
|
266
|
-
* Should be called when your application closes.
|
|
267
|
-
*/
|
|
268
|
-
dispose(): void;
|
|
340
|
+
getVideoDimensions(): string;
|
|
269
341
|
}
|
|
270
342
|
|
|
343
|
+
/** Event emitted when the webcam encounters an error. */
|
|
271
344
|
export declare interface WebcamErrorEvent {
|
|
272
345
|
code: string;
|
|
273
346
|
message: string;
|
|
274
347
|
}
|
|
275
348
|
|
|
349
|
+
/** Event emitted when the webcam starts. */
|
|
276
350
|
export declare interface WebcamStartedEvent {
|
|
277
|
-
|
|
351
|
+
videoWidth: number;
|
|
352
|
+
videoHeight: number;
|
|
278
353
|
}
|
|
279
354
|
|
|
280
355
|
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. Must contain "camera", a THREE.PerspectiveCamera.
|
|
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);
|
|
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, this.camera.updateProjectionMatrix();
|
|
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.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,this.camera.updateProjectionMatrix()}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.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Location-based AR from AR.js.",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -30,12 +30,19 @@
|
|
|
30
30
|
"eslint": "^9.38.0",
|
|
31
31
|
"globals": "^16.4.0",
|
|
32
32
|
"jsdoc": "^4.0.4",
|
|
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": {
|
|
40
|
+
"minimatch": "^9.0.7",
|
|
39
41
|
"three": "^0.180.0"
|
|
42
|
+
},
|
|
43
|
+
"overrides" : {
|
|
44
|
+
"prettier-eslint": {
|
|
45
|
+
"minimatch": "$minimatch"
|
|
46
|
+
}
|
|
40
47
|
}
|
|
41
48
|
}
|