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 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
- declare type DeviceOrientationControlsOptions = {
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
- declare interface GpsOptions {
187
+ /** GPS intialisation options. */
188
+ export declare interface GpsOptions {
115
189
  gpsMinDistance?: number;
116
190
  gpsMinAccuracy?: number;
117
191
  }
118
192
 
119
- /** The main class for the LocAR.js system. */
120
- export declare class LocationBased extends EventEmitter {
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: SphMercProjection): void;
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: number | undefined, properties?: Record<string, any>): void;
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
- declare interface LonLat {
298
+ /** Longitude and latitude. */
299
+ export declare interface LonLat {
207
300
  longitude: number;
208
301
  latitude: number;
209
302
  }
210
303
 
211
- declare interface ServerLogger {
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
- /** Class representing a Spherical Mercator projection. */
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
- texture: THREE.VideoTexture;
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 T, Euler as w, Quaternion as f, EventDispatcher as A, MathUtils as h } from "three";
3
- class R {
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 H extends E {
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
- #l;
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 = null) {
56
- super(), this.scene = e, this.camera = i, this.#e = new R(), this.#t = null, this.#i = 0, this.#s = 100, this.#a = null, this.setGpsOptions(t), this.#n = null, this.#l = 0, this.#r = 0, this.#o = l;
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.#s = e.gpsMinAccuracy);
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.#s
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.#d(e);
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, l = 0) {
120
- t !== null && this.setElevation(t), this.#d({
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: l
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.#n)
142
- t[0] -= this.#n[0], t[1] -= this.#n[1];
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, l, c = {}) {
158
- e.properties = c, this.#c(e, i, t, l), this.scene.add(e), this.#o?.sendData("/object/new", {
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: c
219
+ properties: l
164
220
  });
165
221
  }
166
- #c(e, i, t, l) {
167
- const c = this.lonLatToWorldCoords(i, t);
168
- l !== void 0 && (e.position.y = l), [e.position.x, e.position.z] = c;
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
- #h(e, i) {
178
- this.#n = this.#e.project(e, i);
270
+ #u(e, i) {
271
+ this.#s = this.#e.project(e, i);
179
272
  }
180
- #d(e) {
273
+ #c(e) {
181
274
  let i = Number.MAX_VALUE;
182
- this.#l++, this.#o?.sendData("/gps/new", {
183
- gpsCount: this.#l,
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.#s && (this.#t === null ? this.#t = {
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 = this.#u(this.#t, e.coords), i >= this.#i && (this.#t.longitude = e.coords.longitude, this.#t.latitude = e.coords.latitude, this.#n || (this.#h(
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.#l,
288
+ gpsCount: this.#d,
196
289
  lat: e.coords.latitude,
197
290
  lon: e.coords.longitude,
198
291
  session: this.#r,
199
- initialPosition: this.#n
200
- })), this.#c(
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.#l,
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
- #u(e, i) {
218
- const t = u.MathUtils.degToRad(i.longitude - e.longitude), l = u.MathUtils.degToRad(i.latitude - e.latitude), c = Math.sin(l / 2) * Math.sin(l / 2) + Math.cos(u.MathUtils.degToRad(e.latitude)) * Math.cos(u.MathUtils.degToRad(i.latitude)) * (Math.sin(t / 2) * Math.sin(t / 2));
219
- return 2 * Math.atan2(Math.sqrt(c), Math.sqrt(1 - c)) * 6371e3;
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 b = "locar-device-orientation-permission-modal", M = "locar-device-orientation-permission-button", _ = "locar-device-orientation-permission-message", D = "locar-device-orientation-permission-inner", P = "locar-device-orientation-permission-button-inner", O = "This immersive website requires access to your device motion sensors.", p = navigator.userAgent.match(/iPhone|iPad|iPod/i) || /Macintosh/i.test(navigator.userAgent) && navigator.maxTouchPoints != null && navigator.maxTouchPoints > 1, N = new T(0, 0, 1), y = new w(), S = new f(), x = new f(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5)), I = 0.5 * Math.PI;
231
- class q extends A {
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 E();
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 || 1, this.enablePermissionDialog = i.enablePermissionDialog ?? !0, this.enableInlineStyling = i.enableStyling ?? !0, this.preferConfirmDialog = i.preferConfirmDialog ?? !1, this.orientationChangeThreshold = i.orientationChangeThreshold ?? 0;
251
- const l = (s) => {
252
- let { alpha: n, beta: a, gamma: r, webkitCompassHeading: o } = s;
253
- if (n = n ?? 0, a = a ?? 0, r = r ?? 0, o = o ?? 0, p) {
254
- const d = 360 - o;
255
- t.alphaOffset = h.degToRad(d - n), t.deviceOrientation = { alpha: n, beta: a, gamma: r, webkitCompassHeading: o };
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
- n < 0 && (n += 360), t.deviceOrientation = { alpha: n, beta: a, gamma: r };
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
- }, c = () => {
264
- t.screenOrientation = window.screen.orientation?.angle ?? 0, p && (t.screenOrientation === 90 ? t.orientationOffset = -I : t.screenOrientation === -90 ? t.orientationOffset = I : t.orientationOffset = 0);
265
- }, v = (s, n, a, r, o) => {
266
- y.set(a, n, -r, "YXZ"), s.setFromEuler(y), s.multiply(x), s.multiply(S.setFromAxisAngle(N, -o));
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
- c(), window.addEventListener(
362
+ l(), window.addEventListener(
270
363
  "orientationchange",
271
- c
364
+ l
272
365
  ), window.addEventListener(
273
366
  t.orientationChangeEventName,
274
- l
367
+ o
275
368
  ), t.enabled = !0;
276
369
  }, this.disconnect = () => {
277
370
  window.removeEventListener(
278
371
  "orientationchange",
279
- c
372
+ l
280
373
  ), window.removeEventListener(
281
374
  t.orientationChangeEventName,
282
- l
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((s) => {
286
- s === "granted" ? this.eventEmitter.emit("deviceorientationgranted", {
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((s) => {
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(s, null, 2)
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 s = t.deviceOrientation;
305
- if (s) {
306
- let n = s.alpha ? h.degToRad(s.alpha) + t.alphaOffset : 0, a = s.beta ? h.degToRad(s.beta) : 0, r = s.gamma ? h.degToRad(s.gamma) : 0;
307
- const o = t.screenOrientation ? h.degToRad(t.screenOrientation) : 0, d = new f();
308
- if (p) {
309
- v(d, n, a, r, o);
310
- const g = new w().setFromQuaternion(
311
- d,
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
- ), C = h.degToRad(
314
- 360 - (s.webkitCompassHeading ?? 0)
406
+ ), f = p.degToRad(
407
+ 360 - (n.webkitCompassHeading ?? 0)
315
408
  );
316
- g.y = C + (t.orientationOffset || 0), d.setFromEuler(g);
409
+ g.y = f + (t.orientationOffset || 0), h.setFromEuler(g);
317
410
  } else
318
- v(d, n, a, r, o);
319
- if (t.lastQuaternion && t.orientationChangeThreshold > 0 && d.angleTo(t.lastQuaternion) < t.orientationChangeThreshold)
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(d, g);
416
+ t.object.quaternion.slerp(h, g);
324
417
  } else
325
- t.object.quaternion.copy(d);
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: s } = t;
334
- if (!s) return 0;
335
- let n = 0;
336
- return p ? (n = 360 - (s.webkitCompassHeading ?? 0), t.orientationOffset && (n += t.orientationOffset * (180 / Math.PI), n = (n + 360) % 360)) : (s.absolute === !0 || t.orientationChangeEventName, n = s.alpha ? s.alpha : 0, n = (360 - n) % 360, n < 0 && (n += 360)), n;
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: s } = t;
343
- return s && s.alpha ? h.degToRad(s.alpha) + t.alphaOffset : 0;
435
+ const { deviceOrientation: n } = t;
436
+ return n && n.alpha ? p.degToRad(n.alpha) + t.alphaOffset : 0;
344
437
  }, this.getBeta = () => {
345
- const { deviceOrientation: s } = t;
346
- return s && s.beta ? h.degToRad(s.beta) : 0;
438
+ const { deviceOrientation: n } = t;
439
+ return n && n.beta ? p.degToRad(n.beta) : 0;
347
440
  }, this.getGamma = () => {
348
- const { deviceOrientation: s } = t;
349
- return s && s.gamma ? h.degToRad(s.gamma) : 0;
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(D);
355
- const a = document.createElement("div");
356
- a.classList.add(_);
357
- const r = document.createElement("div");
358
- r.classList.add(P);
359
- const o = document.createElement("button");
360
- o.classList.add(M), document.body.appendChild(s), this.enableInlineStyling === !0 && (s.style.fontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", s.style.display = "flex", s.style.position = "fixed", s.style.zIndex = "100000", s.style.justifyContent = "center", s.style.alignItems = "center", s.style.backgroundColor = "rgba(0,0,0,0.2)", s.style.inset = "0", s.style.padding = "20px", n.style.backgroundColor = "rgba(220, 220, 220, 0.85)", n.style.padding = "6px 0", n.style.borderRadius = "10px", n.style.width = "100%", n.style.maxWidth = "400px", a.style.padding = "10px 12px", a.style.textAlign = "center", a.style.fontWeight = "400", a.style.fontSize = "13px", a.style.display = "flex", a.style.justifyContent = "center", a.style.alignItems = "center", r.style.display = "block", r.style.textAlign = "center", r.style.textDecoration = "none", r.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"), s.appendChild(n), n.appendChild(a), n.appendChild(r), a.appendChild(
361
- document.createTextNode(O)
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 d = () => {
364
- this.requestOrientationPermissions(), s.style.display = "none";
456
+ const h = () => {
457
+ this.requestOrientationPermissions(), n.style.display = "none";
365
458
  };
366
- o.addEventListener("click", d), o.appendChild(document.createTextNode("OK")), r.appendChild(o), document.body.appendChild(s);
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(O) && this.requestOrientationPermissions() : this.createObtainPermissionGestureDialog();
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 F {
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 j extends E {
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.display = "none", document.body.appendChild(this.#e)), this.texture = this.#e ? new u.VideoTexture(this.#e) : null, navigator.mediaDevices && navigator.mediaDevices.getUserMedia ? navigator.mediaDevices.getUserMedia(e).then((t) => {
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?.setAttribute(
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
- * Free up the memory associated with the webcam.
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
- F as ClickHandler,
448
- q as DeviceOrientationControls,
449
- E as EventEmitter,
450
- H as LocationBased,
451
- R as SphMercProjection,
452
- j as Webcam,
453
- k as version
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.9",
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": "jsdoc -r lib -d docs/api"
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
- "jsdoc": "^4.0.4",
32
+ "typedoc": "^0.28.19",
33
+ "minimatch": "^9.0.7",
33
34
  "prettier-eslint": "^16.4.2",
34
- "typescript": "^5.7.2",
35
- "vite": "^7.1.3",
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
  }