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 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
- /** The main class for the LocAR.js system. */
120
- export declare class LocationBased extends EventEmitter {
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: SphMercProjection): void;
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: number | undefined, properties?: Record<string, any>): void;
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
- declare interface LonLat {
278
+ /** Longitude and latitude. */
279
+ export declare interface LonLat {
207
280
  longitude: number;
208
281
  latitude: number;
209
282
  }
210
283
 
211
- declare interface ServerLogger {
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
- /** Class representing a Spherical Mercator projection. */
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
- texture: THREE.VideoTexture;
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 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. 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
- #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.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.1.8",
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.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": {
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
  }