locar 0.2.3 → 0.2.4

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.es.js CHANGED
@@ -344,9 +344,10 @@ var x = .5 * Math.PI, S = class extends n {
344
344
  }
345
345
  }, w = class extends o {
346
346
  #e;
347
+ #t;
347
348
  constructor(t = { video: { facingMode: "environment" } }, n) {
348
- super(), this.sceneWebcam = new e.Scene(), n ? this.#e = document.querySelector(n) : (this.#e = document.createElement("video"), this.#e.setAttribute("autoplay", "true"), this.#e.setAttribute("playsinline", "true"), this.#e.style.cssText += "\n width: 100%;\n height: 100%;\n object-fit: cover;\n background: black;\n position: absolute;\n top: 0px;\n left: 0px;\n z-index: -100; \n ", document.body.appendChild(this.#e)), navigator.mediaDevices && navigator.mediaDevices.getUserMedia ? navigator.mediaDevices.getUserMedia(t).then((e) => {
349
- this.#e?.addEventListener("loadedmetadata", () => {
349
+ super(), this.sceneWebcam = new e.Scene(), n ? this.#e = document.querySelector(n) : (this.#e = document.createElement("video"), this.#e.setAttribute("autoplay", "true"), this.#e.setAttribute("playsinline", "true"), this.#e.style.cssText += "\n width: 100%;\n height: 100%;\n object-fit: cover;\n background: black;\n position: absolute;\n top: 0px;\n left: 0px;\n z-index: -100; \n ", document.body.appendChild(this.#e)), this.#t = null, navigator.mediaDevices && navigator.mediaDevices.getUserMedia ? navigator.mediaDevices.getUserMedia(t).then((e) => {
350
+ this.#t = e, this.#e?.addEventListener("loadedmetadata", () => {
350
351
  this.#e.play(), this.emit("webcamstarted", {
351
352
  videoWidth: this.#e.videoWidth,
352
353
  videoHeight: this.#e.videoHeight
@@ -365,6 +366,11 @@ var x = .5 * Math.PI, S = class extends n {
365
366
  getVideoDimensions() {
366
367
  return `w ${this.#e.videoWidth}, h ${this.#e.videoHeight}`;
367
368
  }
369
+ dispose() {
370
+ (this.#t?.getTracks() ?? []).forEach((e) => {
371
+ e.stop();
372
+ }), this.#t = null, this.#e?.remove(), this.#e = null;
373
+ }
368
374
  };
369
375
  //#endregion
370
376
  export { s as App, C as ClickHandler, S as DeviceOrientationControls, o as EventEmitter, l as LocAR, c as SphMercProjection, w as Webcam };
package/dist/locar.umd.js CHANGED
@@ -1,4 +1,4 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("three")):typeof define==`function`&&define.amd?define([`exports`,`three`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.locar={},e.THREE))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=Object.create,r=Object.defineProperty,i=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,o=Object.getPrototypeOf,s=Object.prototype.hasOwnProperty,c=(e,t,n,o)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=a(t),l=0,u=c.length,d;l<u;l++)d=c[l],!s.call(e,d)&&d!==n&&r(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(o=i(t,d))||o.enumerable});return e};t=((e,t,i)=>(i=e==null?{}:n(o(e)),c(t||!e||!e.__esModule?r(i,`default`,{value:e,enumerable:!0}):i,e)))(t);var l=class{constructor(){this.on=(e,t)=>{this.eventHandlers[e]===void 0&&(this.eventHandlers[e]=[]),this.eventHandlers[e].push(t)},this.emit=(e,...t)=>{this.eventHandlers[e]?.forEach(e=>{e(...t)})},this.off=(e,t)=>{if(this.eventHandlers[e]){let n=this.eventHandlers[e].indexOf(t);n>-1&&this.eventHandlers[e].splice(n,1)}},this.eventHandlers={}}},u=class extends l{constructor({cameraOptions:e,canvas:n,gpsOptions:r,videoConstraints:i,deviceOrientationOptions:a,serverLogger:o,projection:s}){super(),this.origHfov=e?.hFov||80,this.cameraFeedDimensions=null;let c=window.innerWidth/window.innerHeight;this.camera=new t.PerspectiveCamera(this.origHfov/c,c,e?.near||.001,e?.far||1e3),n?(this.renderer=new t.WebGLRenderer({canvas:n,alpha:!0}),this.renderer.setClearColor(65280,0)):(this.renderer=new t.WebGLRenderer({alpha:!0}),this.renderer.setClearColor(65280,0),document.body.appendChild(this.renderer.domElement)),this.renderer.setSize(window.innerWidth,window.innerHeight),this.scene=new t.Scene;let l=a||{enabled:!0};window.addEventListener(`resize`,()=>{this.renderer.setSize(window.innerWidth,window.innerHeight);let e=window.innerWidth/window.innerHeight;if(this.camera.aspect=e,this.cameraFeedDimensions!==null){let t=e>1?this.cameraFeedDimensions.landWidth:this.cameraFeedDimensions.landHeight,n=e>1?this.cameraFeedDimensions.landHeight:this.cameraFeedDimensions.landWidth;this.#e(t,n,e)}this.camera.updateProjectionMatrix()}),this.locar=new f(this.scene,this.camera,r,o,s),this.webcam=new D(i),this.deviceOrientationControls=l.enabled===!0?new T(this.camera,l):null,this.renderer.setAnimationLoop(()=>{this.deviceOrientationControls?.update(),this.renderer.render(this.scene,this.camera)})}start(){return new Promise((e,t)=>{this.webcam.on(`webcamstarted`,e=>{let t=e.videoWidth>e.videoHeight;this.cameraFeedDimensions={landWidth:t?e.videoWidth:e.videoHeight,landHeight:t?e.videoHeight:e.videoWidth},this.#e(e.videoWidth,e.videoHeight,window.innerWidth/window.innerHeight),this.camera.updateProjectionMatrix()}),this.webcam.on(`webcamerror`,e=>{t({code:e.code,message:e.message})}),this.deviceOrientationControls===null?e(this.locar):(this.deviceOrientationControls?.on(`deviceorientationgranted`,t=>{t.target.connect(),e(this.locar)}),this.deviceOrientationControls.on(`deviceorientationerror`,e=>{t({code:e.code,message:e.message})}),this.deviceOrientationControls.init())})}#e(e,t,n){if(n<e/t){let r=e*(window.innerHeight/t),i=this.origHfov*(window.innerWidth/r);this.camera.fov=i/n}else this.camera.fov=this.origHfov/n}},d=class{constructor(){this.project=(e,t)=>[this.#e(e),this.#t(t)],this.unproject=e=>[this.#n(e[0]),this.#r(e[1])],this.#e=e=>e/180*this.HALF_EARTH,this.#t=e=>Math.log(Math.tan((90+e)*Math.PI/360))/(Math.PI/180)*this.HALF_EARTH/180,this.#n=e=>e/this.HALF_EARTH*180,this.#r=e=>{var t=e/this.HALF_EARTH*180;return t=180/Math.PI*(2*Math.atan(Math.exp(t*Math.PI/180))-Math.PI/2),t},this.getID=()=>`epsg:3857`,this.EARTH=40075016.68,this.HALF_EARTH=20037508.34}#e;#t;#n;#r},f=class e extends l{#e;#t;#n;#r;#i;#a;#o;#s;#c;constructor(e,t,n={},r=null,i=new d){super(),this.scene=e,this.camera=t,this.#e=i,this.#t=null,this.#n=0,this.#r=100,this.#i=null,this.setGpsOptions(n),this.#a=null,this.#o=0,this.#s=0,this.#c=r}setProjection(e){this.#e=e}setGpsOptions(e={}){e.gpsMinDistance!==void 0&&(this.#n=e.gpsMinDistance),e.gpsMinAccuracy!==void 0&&(this.#r=e.gpsMinAccuracy)}async startGps(){if(this.#c){let e=await(await this.#c.sendData(`/gps/start`,{gpsMinDistance:this.#n,gpsMinAccuracy:this.#r})).json();this.#s=e.session}return this.#i===null?(this.#i=navigator.geolocation.watchPosition(e=>{this.#f(e)},e=>{this.emit(`gpserror`,e)},{enableHighAccuracy:!0}),!0):!1}stopGps(){return this.#i===null?!1:(navigator.geolocation.clearWatch(this.#i),this.#i=null,!0)}fakeGps(e,t,n=null,r=0){n!==null&&this.setElevation(n),this.#f({coords:{longitude:e,latitude:t,accuracy:r}})}lonLatToWorldCoords(e,t){let n=this.#e.project(e,t);return this.eastNorthToWorldCoords(n)}eastNorthToWorldCoords(e){if(this.#a)e[0]-=this.#a[0],e[1]-=this.#a[1];else throw`No initial position determined`;return[e[0],-e[1]]}add(e,t,n,r,i={}){e.properties=i,this.#u(e,t,n,r||0),this.scene.add(e),this.#c?.sendData(`/object/new`,{position:e.position,x:e.position.x,z:e.position.z,session:this.#s,properties:i})}addGeoLine(e,n,r=1){let i=e.map((e=>{let[n,r]=this.lonLatToWorldCoords(e[0],e[1]);return new t.Vector3(n,e[2]||0,r)})),a=this.#l(i,r);n.setValues({side:t.DoubleSide});let o=new t.Mesh(a,n);return this.scene.add(o),o}#l(e,n){let r,i,a,o,s=0,c=0,l=[],u,d=e.length-1,f=[];for(let t=0;t<d;t++)r=e[t+1].x-e[t].x,i=e[t+1].z-e[t].z,a=e[t+1].y-e[t].y,o=Math.sqrt(r*r+a*a+i*i),s=-(n/2*i)/o,c=n/2*r/o,u=[e[t].x-s,e[t].y,e[t].z-c,e[t].x+s,e[t].y,e[t].z+c],t>0&&u.forEach((e,t)=>{e=(e+l[t])/2}),f.push(...u),l=[e[t+1].x-s,e[t+1].y,e[t+1].z-c,e[t+1].x+s,e[t+1].y,e[t+1].z+c];f.push(e[d].x-s),f.push(e[d].y),f.push(e[d].z-c),f.push(e[d].x+s),f.push(e[d].y),f.push(e[d].z+c);let p=[];for(let e=0;e<d;e++)p.push(e*2,e*2+1,e*2+2),p.push(e*2+1,e*2+3,e*2+2);let m=new t.BufferGeometry,h=new Float32Array(f);return m.setIndex(p),m.setAttribute(`position`,new t.BufferAttribute(h,3)),m.computeBoundingBox(),m}#u(e,t,n,r){let i=this.lonLatToWorldCoords(t,n);r!==void 0&&(e.position.y=r),[e.position.x,e.position.z]=i}setElevation(e){this.camera.position.y=e}#d(e,t){this.#a=this.#e.project(e,t)}#f(t){let n=Number.MAX_VALUE;this.#o++,this.#c?.sendData(`/gps/new`,{gpsCount:this.#o,lat:t.coords.latitude,lon:t.coords.longitude,acc:t.coords.accuracy,session:this.#s}),t.coords.accuracy<=this.#r&&(this.#t===null?this.#t={latitude:t.coords.latitude,longitude:t.coords.longitude}:n=e.haversineDist(this.#t,t.coords),n>=this.#n&&(this.#t.longitude=t.coords.longitude,this.#t.latitude=t.coords.latitude,this.#a||(this.#d(t.coords.longitude,t.coords.latitude),this.#c?.sendData(`/worldorigin/new`,{gpsCount:this.#o,lat:t.coords.latitude,lon:t.coords.longitude,session:this.#s,initialPosition:this.#a})),this.#u(this.camera,t.coords.longitude,t.coords.latitude),this.#c?.sendData(`/gps/accepted`,{gpsCount:this.#o,cameraX:this.camera.position.x,cameraZ:this.camera.position.z,session:this.#s,distMoved:n}),this.emit(`gpsupdate`,{position:t,distMoved:n})))}static haversineDist(e,n){let r=t.MathUtils.degToRad(n.longitude-e.longitude),i=t.MathUtils.degToRad(n.latitude-e.latitude),a=Math.sin(i/2)*Math.sin(i/2)+Math.cos(t.MathUtils.degToRad(e.latitude))*Math.cos(t.MathUtils.degToRad(n.latitude))*(Math.sin(r/2)*Math.sin(r/2));return 2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a))*6371e3}getLastKnownLocation(){return this.#t}},p=`locar-device-orientation-permission-modal`,m=`locar-device-orientation-permission-button`,h=`locar-device-orientation-permission-message`,g=`locar-device-orientation-permission-inner`,_=`locar-device-orientation-permission-button-inner`,v=`This immersive website requires access to your device motion sensors.`,y=navigator.userAgent.match(/iPhone|iPad|iPod/i)||/Macintosh/i.test(navigator.userAgent)&&navigator.maxTouchPoints!=null&&navigator.maxTouchPoints>1,b=new t.Vector3(0,0,1),x=new t.Euler,S=new t.Quaternion,C=new t.Quaternion(-Math.sqrt(.5),0,0,Math.sqrt(.5));2*Math.PI;var w=.5*Math.PI,T=class extends t.EventDispatcher{constructor(e,n={}){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 l;let r=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=n.smoothingFactor||.2,this.enablePermissionDialog=n.enablePermissionDialog??!0,this.enableInlineStyling=n.enableStyling??!0,this.preferConfirmDialog=n.preferConfirmDialog??!1,this.orientationChangeThreshold=n.orientationChangeThreshold??0;let i=n=>{let{alpha:i,beta:a,gamma:o,webkitCompassHeading:s}=n;if(i??=0,a??=0,o??=0,s??=0,y){let e=360-s;r.alphaOffset=t.MathUtils.degToRad(e-i),r.deviceOrientation={alpha:i,beta:a,gamma:o,webkitCompassHeading:s}}else i<0&&(i+=360),r.deviceOrientation={alpha:i,beta:a,gamma:o};window.dispatchEvent(new CustomEvent(`camera-rotation-change`,{detail:{cameraRotation:e.rotation}}))},a=()=>{r.screenOrientation=window.screen.orientation?.angle??0,y&&(r.screenOrientation===90?r.orientationOffset=-w:r.screenOrientation===-90?r.orientationOffset=w:r.orientationOffset=0)},o=(e,t,n,r,i)=>{x.set(n,t,-r,`YXZ`),e.setFromEuler(x),e.multiply(C),e.multiply(S.setFromAxisAngle(b,-i))};this.connect=()=>{a(),window.addEventListener(`orientationchange`,a),window.addEventListener(r.orientationChangeEventName,i),r.enabled=!0},this.disconnect=()=>{window.removeEventListener(`orientationchange`,a),window.removeEventListener(r.orientationChangeEventName,i),r.enabled=!1,r.initialOffset=!1,r.deviceOrientation=null,r.lastQuaternion=null},this.requestOrientationPermissions=()=>{window.DeviceOrientationEvent!==void 0&&typeof window.DeviceOrientationEvent.requestPermission==`function`?window.DeviceOrientationEvent.requestPermission().then(e=>{e===`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(e=>{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(e,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(r.enabled===!1)return;let e=r.deviceOrientation;if(e){let n=e.alpha?t.MathUtils.degToRad(e.alpha)+r.alphaOffset:0,i=e.beta?t.MathUtils.degToRad(e.beta):0,a=e.gamma?t.MathUtils.degToRad(e.gamma):0,s=r.screenOrientation?t.MathUtils.degToRad(r.screenOrientation):0,c=new t.Quaternion;if(y){o(c,n,i,a,s);let l=new t.Euler().setFromQuaternion(c,`YXZ`);l.y=t.MathUtils.degToRad(360-(e.webkitCompassHeading??0))+(r.orientationOffset||0),c.setFromEuler(l)}else o(c,n,i,a,s);if(r.lastQuaternion&&r.orientationChangeThreshold>0&&c.angleTo(r.lastQuaternion)<r.orientationChangeThreshold)return;if(r.smoothingFactor<1&&r.lastQuaternion){let e=1-r.smoothingFactor;r.object.quaternion.slerp(c,e)}else r.object.quaternion.copy(c);r.lastQuaternion=r.object.quaternion.clone(),window.dispatchEvent(new CustomEvent(`camera-rotation-change`,{detail:{cameraRotation:r.object.rotation}}))}},this.getCorrectedHeading=()=>{let{deviceOrientation:e}=r;if(!e)return 0;let t=0;return y?(t=360-(e.webkitCompassHeading??0),r.orientationOffset&&(t+=r.orientationOffset*(180/Math.PI),t=(t+360)%360)):(e.absolute===!0||r.orientationChangeEventName,t=e.alpha?e.alpha:0,t=(360-t)%360,t<0&&(t+=360)),t},this.updateAlphaOffset=()=>{r.initialOffset=!1},this.dispose=()=>{r.disconnect()},this.getAlpha=()=>{let{deviceOrientation:e}=r;return e&&e.alpha?t.MathUtils.degToRad(e.alpha)+r.alphaOffset:0},this.getBeta=()=>{let{deviceOrientation:e}=r;return e&&e.beta?t.MathUtils.degToRad(e.beta):0},this.getGamma=()=>{let{deviceOrientation:e}=r;return e&&e.gamma?t.MathUtils.degToRad(e.gamma):0},this.createObtainPermissionGestureDialog=()=>{let e=document.createElement(`div`);e.classList.add(p);let t=document.createElement(`div`);t.classList.add(g);let n=document.createElement(`div`);n.classList.add(h);let r=document.createElement(`div`);r.classList.add(_);let i=document.createElement(`button`);i.classList.add(m),document.body.appendChild(e),this.enableInlineStyling===!0&&(e.style.fontFamily=`-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'`,e.style.display=`flex`,e.style.position=`fixed`,e.style.zIndex=`100000`,e.style.justifyContent=`center`,e.style.alignItems=`center`,e.style.backgroundColor=`rgba(0,0,0,0.2)`,e.style.inset=`0`,e.style.padding=`20px`,t.style.backgroundColor=`rgba(220, 220, 220, 0.85)`,t.style.padding=`6px 0`,t.style.borderRadius=`10px`,t.style.width=`100%`,t.style.maxWidth=`400px`,n.style.padding=`10px 12px`,n.style.textAlign=`center`,n.style.fontWeight=`400`,n.style.fontSize=`13px`,n.style.display=`flex`,n.style.justifyContent=`center`,n.style.alignItems=`center`,r.style.display=`block`,r.style.textAlign=`center`,r.style.textDecoration=`none`,r.style.borderTop=`rgb(180,180,180) solid 1px`,i.style.display=`block`,i.style.width=`100%`,i.style.textAlign=`center`,i.style.appearance=`none`,i.style.background=`none`,i.style.border=`none`,i.style.outline=`none`,i.style.padding=`10px`,i.style.fontWeight=`400`,i.style.fontSize=`16px`,i.style.color=`#2e7cf1`,i.style.cursor=`pointer`),e.appendChild(t),t.appendChild(n),t.appendChild(r),n.appendChild(document.createTextNode(v)),i.addEventListener(`click`,()=>{this.requestOrientationPermissions(),e.style.display=`none`}),i.appendChild(document.createTextNode(`OK`)),r.appendChild(i),document.body.appendChild(e)},this.obtainPermissionGesture=()=>{this.preferConfirmDialog===!0?window.confirm(v)&&this.requestOrientationPermissions():this.createObtainPermissionGestureDialog()}}on(e,t){this.eventEmitter.on(e,t)}},E=class{constructor(e){this.raycaster=new t.Raycaster,this.normalisedMousePosition=null,e.domElement.addEventListener(`click`,n=>{this.normalisedMousePosition=new t.Vector2(n.clientX/e.domElement.clientWidth*2-1,-(n.clientY/e.domElement.clientHeight*2)+1)})}raycast(e,t){if(this.normalisedMousePosition!==null){this.raycaster.setFromCamera(this.normalisedMousePosition,e);let n=this.raycaster.intersectObjects(t.children,!1);return this.normalisedMousePosition=null,n}return[]}},D=class extends l{#e;constructor(e={video:{facingMode:`environment`}},n){super(),this.sceneWebcam=new t.Scene,n?this.#e=document.querySelector(n):(this.#e=document.createElement(`video`),this.#e.setAttribute(`autoplay`,`true`),this.#e.setAttribute(`playsinline`,`true`),this.#e.style.cssText+=`
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("three")):typeof define==`function`&&define.amd?define([`exports`,`three`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.locar={},e.THREE))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=Object.create,r=Object.defineProperty,i=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,o=Object.getPrototypeOf,s=Object.prototype.hasOwnProperty,c=(e,t,n,o)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=a(t),l=0,u=c.length,d;l<u;l++)d=c[l],!s.call(e,d)&&d!==n&&r(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(o=i(t,d))||o.enumerable});return e};t=((e,t,i)=>(i=e==null?{}:n(o(e)),c(t||!e||!e.__esModule?r(i,`default`,{value:e,enumerable:!0}):i,e)))(t);var l=class{constructor(){this.on=(e,t)=>{this.eventHandlers[e]===void 0&&(this.eventHandlers[e]=[]),this.eventHandlers[e].push(t)},this.emit=(e,...t)=>{this.eventHandlers[e]?.forEach(e=>{e(...t)})},this.off=(e,t)=>{if(this.eventHandlers[e]){let n=this.eventHandlers[e].indexOf(t);n>-1&&this.eventHandlers[e].splice(n,1)}},this.eventHandlers={}}},u=class extends l{constructor({cameraOptions:e,canvas:n,gpsOptions:r,videoConstraints:i,deviceOrientationOptions:a,serverLogger:o,projection:s}){super(),this.origHfov=e?.hFov||80,this.cameraFeedDimensions=null;let c=window.innerWidth/window.innerHeight;this.camera=new t.PerspectiveCamera(this.origHfov/c,c,e?.near||.001,e?.far||1e3),n?(this.renderer=new t.WebGLRenderer({canvas:n,alpha:!0}),this.renderer.setClearColor(65280,0)):(this.renderer=new t.WebGLRenderer({alpha:!0}),this.renderer.setClearColor(65280,0),document.body.appendChild(this.renderer.domElement)),this.renderer.setSize(window.innerWidth,window.innerHeight),this.scene=new t.Scene;let l=a||{enabled:!0};window.addEventListener(`resize`,()=>{this.renderer.setSize(window.innerWidth,window.innerHeight);let e=window.innerWidth/window.innerHeight;if(this.camera.aspect=e,this.cameraFeedDimensions!==null){let t=e>1?this.cameraFeedDimensions.landWidth:this.cameraFeedDimensions.landHeight,n=e>1?this.cameraFeedDimensions.landHeight:this.cameraFeedDimensions.landWidth;this.#e(t,n,e)}this.camera.updateProjectionMatrix()}),this.locar=new f(this.scene,this.camera,r,o,s),this.webcam=new D(i),this.deviceOrientationControls=l.enabled===!0?new T(this.camera,l):null,this.renderer.setAnimationLoop(()=>{this.deviceOrientationControls?.update(),this.renderer.render(this.scene,this.camera)})}start(){return new Promise((e,t)=>{this.webcam.on(`webcamstarted`,e=>{let t=e.videoWidth>e.videoHeight;this.cameraFeedDimensions={landWidth:t?e.videoWidth:e.videoHeight,landHeight:t?e.videoHeight:e.videoWidth},this.#e(e.videoWidth,e.videoHeight,window.innerWidth/window.innerHeight),this.camera.updateProjectionMatrix()}),this.webcam.on(`webcamerror`,e=>{t({code:e.code,message:e.message})}),this.deviceOrientationControls===null?e(this.locar):(this.deviceOrientationControls?.on(`deviceorientationgranted`,t=>{t.target.connect(),e(this.locar)}),this.deviceOrientationControls.on(`deviceorientationerror`,e=>{t({code:e.code,message:e.message})}),this.deviceOrientationControls.init())})}#e(e,t,n){if(n<e/t){let r=e*(window.innerHeight/t),i=this.origHfov*(window.innerWidth/r);this.camera.fov=i/n}else this.camera.fov=this.origHfov/n}},d=class{constructor(){this.project=(e,t)=>[this.#e(e),this.#t(t)],this.unproject=e=>[this.#n(e[0]),this.#r(e[1])],this.#e=e=>e/180*this.HALF_EARTH,this.#t=e=>Math.log(Math.tan((90+e)*Math.PI/360))/(Math.PI/180)*this.HALF_EARTH/180,this.#n=e=>e/this.HALF_EARTH*180,this.#r=e=>{var t=e/this.HALF_EARTH*180;return t=180/Math.PI*(2*Math.atan(Math.exp(t*Math.PI/180))-Math.PI/2),t},this.getID=()=>`epsg:3857`,this.EARTH=40075016.68,this.HALF_EARTH=20037508.34}#e;#t;#n;#r},f=class e extends l{#e;#t;#n;#r;#i;#a;#o;#s;#c;constructor(e,t,n={},r=null,i=new d){super(),this.scene=e,this.camera=t,this.#e=i,this.#t=null,this.#n=0,this.#r=100,this.#i=null,this.setGpsOptions(n),this.#a=null,this.#o=0,this.#s=0,this.#c=r}setProjection(e){this.#e=e}setGpsOptions(e={}){e.gpsMinDistance!==void 0&&(this.#n=e.gpsMinDistance),e.gpsMinAccuracy!==void 0&&(this.#r=e.gpsMinAccuracy)}async startGps(){if(this.#c){let e=await(await this.#c.sendData(`/gps/start`,{gpsMinDistance:this.#n,gpsMinAccuracy:this.#r})).json();this.#s=e.session}return this.#i===null?(this.#i=navigator.geolocation.watchPosition(e=>{this.#f(e)},e=>{this.emit(`gpserror`,e)},{enableHighAccuracy:!0}),!0):!1}stopGps(){return this.#i===null?!1:(navigator.geolocation.clearWatch(this.#i),this.#i=null,!0)}fakeGps(e,t,n=null,r=0){n!==null&&this.setElevation(n),this.#f({coords:{longitude:e,latitude:t,accuracy:r}})}lonLatToWorldCoords(e,t){let n=this.#e.project(e,t);return this.eastNorthToWorldCoords(n)}eastNorthToWorldCoords(e){if(this.#a)e[0]-=this.#a[0],e[1]-=this.#a[1];else throw`No initial position determined`;return[e[0],-e[1]]}add(e,t,n,r,i={}){e.properties=i,this.#u(e,t,n,r||0),this.scene.add(e),this.#c?.sendData(`/object/new`,{position:e.position,x:e.position.x,z:e.position.z,session:this.#s,properties:i})}addGeoLine(e,n,r=1){let i=e.map((e=>{let[n,r]=this.lonLatToWorldCoords(e[0],e[1]);return new t.Vector3(n,e[2]||0,r)})),a=this.#l(i,r);n.setValues({side:t.DoubleSide});let o=new t.Mesh(a,n);return this.scene.add(o),o}#l(e,n){let r,i,a,o,s=0,c=0,l=[],u,d=e.length-1,f=[];for(let t=0;t<d;t++)r=e[t+1].x-e[t].x,i=e[t+1].z-e[t].z,a=e[t+1].y-e[t].y,o=Math.sqrt(r*r+a*a+i*i),s=-(n/2*i)/o,c=n/2*r/o,u=[e[t].x-s,e[t].y,e[t].z-c,e[t].x+s,e[t].y,e[t].z+c],t>0&&u.forEach((e,t)=>{e=(e+l[t])/2}),f.push(...u),l=[e[t+1].x-s,e[t+1].y,e[t+1].z-c,e[t+1].x+s,e[t+1].y,e[t+1].z+c];f.push(e[d].x-s),f.push(e[d].y),f.push(e[d].z-c),f.push(e[d].x+s),f.push(e[d].y),f.push(e[d].z+c);let p=[];for(let e=0;e<d;e++)p.push(e*2,e*2+1,e*2+2),p.push(e*2+1,e*2+3,e*2+2);let m=new t.BufferGeometry,h=new Float32Array(f);return m.setIndex(p),m.setAttribute(`position`,new t.BufferAttribute(h,3)),m.computeBoundingBox(),m}#u(e,t,n,r){let i=this.lonLatToWorldCoords(t,n);r!==void 0&&(e.position.y=r),[e.position.x,e.position.z]=i}setElevation(e){this.camera.position.y=e}#d(e,t){this.#a=this.#e.project(e,t)}#f(t){let n=Number.MAX_VALUE;this.#o++,this.#c?.sendData(`/gps/new`,{gpsCount:this.#o,lat:t.coords.latitude,lon:t.coords.longitude,acc:t.coords.accuracy,session:this.#s}),t.coords.accuracy<=this.#r&&(this.#t===null?this.#t={latitude:t.coords.latitude,longitude:t.coords.longitude}:n=e.haversineDist(this.#t,t.coords),n>=this.#n&&(this.#t.longitude=t.coords.longitude,this.#t.latitude=t.coords.latitude,this.#a||(this.#d(t.coords.longitude,t.coords.latitude),this.#c?.sendData(`/worldorigin/new`,{gpsCount:this.#o,lat:t.coords.latitude,lon:t.coords.longitude,session:this.#s,initialPosition:this.#a})),this.#u(this.camera,t.coords.longitude,t.coords.latitude),this.#c?.sendData(`/gps/accepted`,{gpsCount:this.#o,cameraX:this.camera.position.x,cameraZ:this.camera.position.z,session:this.#s,distMoved:n}),this.emit(`gpsupdate`,{position:t,distMoved:n})))}static haversineDist(e,n){let r=t.MathUtils.degToRad(n.longitude-e.longitude),i=t.MathUtils.degToRad(n.latitude-e.latitude),a=Math.sin(i/2)*Math.sin(i/2)+Math.cos(t.MathUtils.degToRad(e.latitude))*Math.cos(t.MathUtils.degToRad(n.latitude))*(Math.sin(r/2)*Math.sin(r/2));return 2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a))*6371e3}getLastKnownLocation(){return this.#t}},p=`locar-device-orientation-permission-modal`,m=`locar-device-orientation-permission-button`,h=`locar-device-orientation-permission-message`,g=`locar-device-orientation-permission-inner`,_=`locar-device-orientation-permission-button-inner`,v=`This immersive website requires access to your device motion sensors.`,y=navigator.userAgent.match(/iPhone|iPad|iPod/i)||/Macintosh/i.test(navigator.userAgent)&&navigator.maxTouchPoints!=null&&navigator.maxTouchPoints>1,b=new t.Vector3(0,0,1),x=new t.Euler,S=new t.Quaternion,C=new t.Quaternion(-Math.sqrt(.5),0,0,Math.sqrt(.5));2*Math.PI;var w=.5*Math.PI,T=class extends t.EventDispatcher{constructor(e,n={}){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 l;let r=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=n.smoothingFactor||.2,this.enablePermissionDialog=n.enablePermissionDialog??!0,this.enableInlineStyling=n.enableStyling??!0,this.preferConfirmDialog=n.preferConfirmDialog??!1,this.orientationChangeThreshold=n.orientationChangeThreshold??0;let i=n=>{let{alpha:i,beta:a,gamma:o,webkitCompassHeading:s}=n;if(i??=0,a??=0,o??=0,s??=0,y){let e=360-s;r.alphaOffset=t.MathUtils.degToRad(e-i),r.deviceOrientation={alpha:i,beta:a,gamma:o,webkitCompassHeading:s}}else i<0&&(i+=360),r.deviceOrientation={alpha:i,beta:a,gamma:o};window.dispatchEvent(new CustomEvent(`camera-rotation-change`,{detail:{cameraRotation:e.rotation}}))},a=()=>{r.screenOrientation=window.screen.orientation?.angle??0,y&&(r.screenOrientation===90?r.orientationOffset=-w:r.screenOrientation===-90?r.orientationOffset=w:r.orientationOffset=0)},o=(e,t,n,r,i)=>{x.set(n,t,-r,`YXZ`),e.setFromEuler(x),e.multiply(C),e.multiply(S.setFromAxisAngle(b,-i))};this.connect=()=>{a(),window.addEventListener(`orientationchange`,a),window.addEventListener(r.orientationChangeEventName,i),r.enabled=!0},this.disconnect=()=>{window.removeEventListener(`orientationchange`,a),window.removeEventListener(r.orientationChangeEventName,i),r.enabled=!1,r.initialOffset=!1,r.deviceOrientation=null,r.lastQuaternion=null},this.requestOrientationPermissions=()=>{window.DeviceOrientationEvent!==void 0&&typeof window.DeviceOrientationEvent.requestPermission==`function`?window.DeviceOrientationEvent.requestPermission().then(e=>{e===`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(e=>{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(e,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(r.enabled===!1)return;let e=r.deviceOrientation;if(e){let n=e.alpha?t.MathUtils.degToRad(e.alpha)+r.alphaOffset:0,i=e.beta?t.MathUtils.degToRad(e.beta):0,a=e.gamma?t.MathUtils.degToRad(e.gamma):0,s=r.screenOrientation?t.MathUtils.degToRad(r.screenOrientation):0,c=new t.Quaternion;if(y){o(c,n,i,a,s);let l=new t.Euler().setFromQuaternion(c,`YXZ`);l.y=t.MathUtils.degToRad(360-(e.webkitCompassHeading??0))+(r.orientationOffset||0),c.setFromEuler(l)}else o(c,n,i,a,s);if(r.lastQuaternion&&r.orientationChangeThreshold>0&&c.angleTo(r.lastQuaternion)<r.orientationChangeThreshold)return;if(r.smoothingFactor<1&&r.lastQuaternion){let e=1-r.smoothingFactor;r.object.quaternion.slerp(c,e)}else r.object.quaternion.copy(c);r.lastQuaternion=r.object.quaternion.clone(),window.dispatchEvent(new CustomEvent(`camera-rotation-change`,{detail:{cameraRotation:r.object.rotation}}))}},this.getCorrectedHeading=()=>{let{deviceOrientation:e}=r;if(!e)return 0;let t=0;return y?(t=360-(e.webkitCompassHeading??0),r.orientationOffset&&(t+=r.orientationOffset*(180/Math.PI),t=(t+360)%360)):(e.absolute===!0||r.orientationChangeEventName,t=e.alpha?e.alpha:0,t=(360-t)%360,t<0&&(t+=360)),t},this.updateAlphaOffset=()=>{r.initialOffset=!1},this.dispose=()=>{r.disconnect()},this.getAlpha=()=>{let{deviceOrientation:e}=r;return e&&e.alpha?t.MathUtils.degToRad(e.alpha)+r.alphaOffset:0},this.getBeta=()=>{let{deviceOrientation:e}=r;return e&&e.beta?t.MathUtils.degToRad(e.beta):0},this.getGamma=()=>{let{deviceOrientation:e}=r;return e&&e.gamma?t.MathUtils.degToRad(e.gamma):0},this.createObtainPermissionGestureDialog=()=>{let e=document.createElement(`div`);e.classList.add(p);let t=document.createElement(`div`);t.classList.add(g);let n=document.createElement(`div`);n.classList.add(h);let r=document.createElement(`div`);r.classList.add(_);let i=document.createElement(`button`);i.classList.add(m),document.body.appendChild(e),this.enableInlineStyling===!0&&(e.style.fontFamily=`-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'`,e.style.display=`flex`,e.style.position=`fixed`,e.style.zIndex=`100000`,e.style.justifyContent=`center`,e.style.alignItems=`center`,e.style.backgroundColor=`rgba(0,0,0,0.2)`,e.style.inset=`0`,e.style.padding=`20px`,t.style.backgroundColor=`rgba(220, 220, 220, 0.85)`,t.style.padding=`6px 0`,t.style.borderRadius=`10px`,t.style.width=`100%`,t.style.maxWidth=`400px`,n.style.padding=`10px 12px`,n.style.textAlign=`center`,n.style.fontWeight=`400`,n.style.fontSize=`13px`,n.style.display=`flex`,n.style.justifyContent=`center`,n.style.alignItems=`center`,r.style.display=`block`,r.style.textAlign=`center`,r.style.textDecoration=`none`,r.style.borderTop=`rgb(180,180,180) solid 1px`,i.style.display=`block`,i.style.width=`100%`,i.style.textAlign=`center`,i.style.appearance=`none`,i.style.background=`none`,i.style.border=`none`,i.style.outline=`none`,i.style.padding=`10px`,i.style.fontWeight=`400`,i.style.fontSize=`16px`,i.style.color=`#2e7cf1`,i.style.cursor=`pointer`),e.appendChild(t),t.appendChild(n),t.appendChild(r),n.appendChild(document.createTextNode(v)),i.addEventListener(`click`,()=>{this.requestOrientationPermissions(),e.style.display=`none`}),i.appendChild(document.createTextNode(`OK`)),r.appendChild(i),document.body.appendChild(e)},this.obtainPermissionGesture=()=>{this.preferConfirmDialog===!0?window.confirm(v)&&this.requestOrientationPermissions():this.createObtainPermissionGestureDialog()}}on(e,t){this.eventEmitter.on(e,t)}},E=class{constructor(e){this.raycaster=new t.Raycaster,this.normalisedMousePosition=null,e.domElement.addEventListener(`click`,n=>{this.normalisedMousePosition=new t.Vector2(n.clientX/e.domElement.clientWidth*2-1,-(n.clientY/e.domElement.clientHeight*2)+1)})}raycast(e,t){if(this.normalisedMousePosition!==null){this.raycaster.setFromCamera(this.normalisedMousePosition,e);let n=this.raycaster.intersectObjects(t.children,!1);return this.normalisedMousePosition=null,n}return[]}},D=class extends l{#e;#t;constructor(e={video:{facingMode:`environment`}},n){super(),this.sceneWebcam=new t.Scene,n?this.#e=document.querySelector(n):(this.#e=document.createElement(`video`),this.#e.setAttribute(`autoplay`,`true`),this.#e.setAttribute(`playsinline`,`true`),this.#e.style.cssText+=`
2
2
  width: 100%;
3
3
  height: 100%;
4
4
  object-fit: cover;
@@ -7,4 +7,4 @@
7
7
  top: 0px;
8
8
  left: 0px;
9
9
  z-index: -100;
10
- `,document.body.appendChild(this.#e)),navigator.mediaDevices&&navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia(e).then(e=>{this.#e?.addEventListener(`loadedmetadata`,()=>{this.#e.play(),this.emit(`webcamstarted`,{videoWidth:this.#e.videoWidth,videoHeight:this.#e.videoHeight})}),this.#e&&(this.#e.srcObject=e)}).catch(e=>{this.emit(`webcamerror`,{code:e.name,message:e.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}`}};e.App=u,e.ClickHandler=E,e.DeviceOrientationControls=T,e.EventEmitter=l,e.LocAR=f,e.SphMercProjection=d,e.Webcam=D});
10
+ `,document.body.appendChild(this.#e)),this.#t=null,navigator.mediaDevices&&navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia(e).then(e=>{this.#t=e,this.#e?.addEventListener(`loadedmetadata`,()=>{this.#e.play(),this.emit(`webcamstarted`,{videoWidth:this.#e.videoWidth,videoHeight:this.#e.videoHeight})}),this.#e&&(this.#e.srcObject=e)}).catch(e=>{this.emit(`webcamerror`,{code:e.name,message:e.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}`}dispose(){(this.#t?.getTracks()??[]).forEach(e=>{e.stop()}),this.#t=null,this.#e?.remove(),this.#e=null}};e.App=u,e.ClickHandler=E,e.DeviceOrientationControls=T,e.EventEmitter=l,e.LocAR=f,e.SphMercProjection=d,e.Webcam=D});
@@ -0,0 +1,32 @@
1
+ import { LocAR, Webcam, DeviceOrientationControls } from './main';
2
+ import { default as EventEmitter } from './event-emitter';
3
+ import { AppOptions } from '../types/locar';
4
+ import * as THREE from 'three';
5
+ /** Application class to orchestrate the interaction between the individual LocAR classes and the Three.js camera, renderer and scene. */
6
+ declare class App extends EventEmitter {
7
+ #private;
8
+ locar: LocAR;
9
+ camera: THREE.PerspectiveCamera;
10
+ renderer: THREE.WebGLRenderer;
11
+ scene: THREE.Scene;
12
+ webcam: Webcam;
13
+ deviceOrientationControls: DeviceOrientationControls | null;
14
+ cameraFeedDimensions: {
15
+ landWidth: number;
16
+ landHeight: number;
17
+ } | null; /** camera feed dimensions in LANDSCAPE */
18
+ origHfov: number;
19
+ /**
20
+ * Create an App object.
21
+ * @param {AppOptions} - Startup options.
22
+ */
23
+ constructor({ cameraOptions, canvas, gpsOptions, videoConstraints, deviceOrientationOptions, serverLogger, projection }: AppOptions);
24
+ /**
25
+ * Start the app.
26
+ * Must be called after construction.
27
+ * @returns {Promise<LocAR>}
28
+ * Promise resolving with LocAR object. Rejects with object containing code and message.
29
+ */
30
+ start(): Promise<LocAR>;
31
+ }
32
+ export default App;
@@ -0,0 +1,25 @@
1
+ import * as THREE from "three";
2
+ /**
3
+ * Class to handle object detection via mouse clicks/touch events
4
+ * and raycasting.
5
+ */
6
+ declare class ClickHandler {
7
+ raycaster: THREE.Raycaster;
8
+ normalisedMousePosition: THREE.Vector2 | null;
9
+ /**
10
+ * Create a ClickHandler.
11
+ * @param {THREE.WebGLRenderer} - The Three.js renderer on which the click
12
+ * events will be handled.
13
+ */
14
+ constructor(renderer: THREE.WebGLRenderer);
15
+ /**
16
+ * Cast a ray into the scene to detect objects.
17
+ * @param {THREE.Camera} - The active Three.js camera, from which the ray
18
+ * will be cast.
19
+ * @param {THREE.Scene} - The active Three.js scene, which the ray will be
20
+ * cast into.
21
+ * @return {Array} - array of all intersected objects.
22
+ */
23
+ raycast(camera: THREE.Camera, scene: THREE.Scene): THREE.Intersection[];
24
+ }
25
+ export default ClickHandler;
@@ -0,0 +1,55 @@
1
+ import { EventDispatcher, Object3D, Quaternion } from 'three';
2
+ import { default as EventEmitter } from './event-emitter';
3
+ import { DeviceOrientationControlsOptions } from '../types/locar';
4
+ declare class DeviceOrientationControls extends EventDispatcher {
5
+ eventEmitter: EventEmitter;
6
+ object: Object3D;
7
+ enabled: boolean;
8
+ deviceOrientation: {
9
+ alpha?: number;
10
+ beta?: number;
11
+ gamma?: number;
12
+ webkitCompassHeading?: number;
13
+ absolute?: boolean;
14
+ } | null;
15
+ screenOrientation: number;
16
+ alphaOffset: number;
17
+ orientationOffset: number;
18
+ initialOffset: boolean | null;
19
+ lastQuaternion: Quaternion | null;
20
+ orientationChangeEventName: "deviceorientation" | "deviceorientationabsolute";
21
+ smoothingFactor: number;
22
+ enablePermissionDialog: boolean;
23
+ enableInlineStyling: boolean;
24
+ preferConfirmDialog: boolean;
25
+ orientationChangeThreshold: number;
26
+ connect: () => void;
27
+ disconnect: () => void;
28
+ requestOrientationPermissions: () => void;
29
+ update: () => void;
30
+ getCorrectedHeading: () => number;
31
+ updateAlphaOffset: () => void;
32
+ dispose: () => void;
33
+ getAlpha: () => number;
34
+ getBeta: () => number;
35
+ getGamma: () => number;
36
+ createObtainPermissionGestureDialog: () => void;
37
+ obtainPermissionGesture: () => void;
38
+ /**
39
+ * Create an instance of DeviceOrientationControls.
40
+ * @param {Object} object - the object to attach the controls to
41
+ * (usually your Three.js camera)
42
+ * @param {Object} options - options for DeviceOrientationControls:
43
+ * currently accepts smoothingFactor (< 1), enablePermissionDialog, orientationChangeThreshold (radians)
44
+ */
45
+ constructor(object: Object3D, options?: DeviceOrientationControlsOptions);
46
+ on(eventName: string, eventHandler: (event: any) => void): void;
47
+ /**
48
+ * Initialise device orientation controls.
49
+ * Should be called after you have created the DeviceOrientationControls
50
+ * object and set up the deviceorientationgranted and deviceorientationerror
51
+ * event handlers.
52
+ */
53
+ init: () => void;
54
+ }
55
+ export default DeviceOrientationControls;
@@ -0,0 +1,24 @@
1
+ /** Event emitter class to handle events. */
2
+ declare class EventEmitter {
3
+ eventHandlers: Record<string, ((...args: any[]) => void)[]>;
4
+ constructor();
5
+ /**
6
+ * Add an event handler.
7
+ * @param {string} eventName - the event to handle.
8
+ * @param {Function} eventHandler - the event handler function.
9
+ */
10
+ on: (eventName: string, eventHandler: (...args: any[]) => void) => void;
11
+ /**
12
+ * Emit an event.
13
+ * @param {string} eventName - the event to emit.
14
+ * @param ...params - parameters to pass to the event handlers.
15
+ */
16
+ emit: (eventName: string, ...params: any[]) => void;
17
+ /**
18
+ * Remove an event handler.
19
+ * @param {string} eventName - the event to remove a handler from.
20
+ * @param {Function} eventHandler - the event handler function to remove.
21
+ */
22
+ off: (eventName: string, eventHandler: (...args: any[]) => void) => void;
23
+ }
24
+ export default EventEmitter;
@@ -0,0 +1,112 @@
1
+ import { default as EventEmitter } from './event-emitter';
2
+ import { LonLat, Projection, ServerLogger, GpsOptions } from '../types/locar';
3
+ import * as THREE from "three";
4
+ /** The main engine class for the LocAR.js system.
5
+ * Can be obtained either via App.start() - which resolves with a LocAR object - or on its own.
6
+ * 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.
7
+ */
8
+ declare class LocAR extends EventEmitter {
9
+ #private;
10
+ scene: THREE.Scene;
11
+ camera: THREE.Camera;
12
+ /**
13
+ * @param {THREE.Scene} scene - The Three.js scene to use.
14
+ * @param {THREE.Camera} camera - The Three.js camera to use. Should usually
15
+ * be a THREE.PerspectiveCamera.
16
+ * @param {Object} options - Initialisation options for the GPS; see
17
+ * setGpsOptions() below.
18
+ * @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.
19
+ */
20
+ constructor(scene: THREE.Scene, camera: THREE.Camera, options?: GpsOptions, serverLogger?: ServerLogger | null, projection?: Projection);
21
+ /**
22
+ * Set the projection to use.
23
+ * @param {Object} any object which includes a project() method
24
+ * taking longitude and latitude as arguments and returning an array
25
+ * containing easting and northing.
26
+ */
27
+ setProjection(proj: Projection): void;
28
+ /**
29
+ * Set the GPS options.
30
+ * @param {Object} object containing gpsMinDistance and/or gpsMinAccuracy
31
+ * properties. The former specifies the number of metres which the device
32
+ * must move to process a new GPS reading, and the latter specifies the
33
+ * minimum accuracy, in metres, for a GPS reading to be counted.
34
+ */
35
+ setGpsOptions(options?: GpsOptions): void;
36
+ /**
37
+ * Start the GPS on a real device
38
+ * @return {boolean} code indicating whether the GPS was started successfully.
39
+ * GPS errors can be handled by handling the gpserror event.
40
+ */
41
+ startGps(): Promise<boolean>;
42
+ /**
43
+ * Stop the GPS on a real device
44
+ * @return {boolean} true if the GPS was stopped, false if it could not be
45
+ * stopped (i.e. it was never started).
46
+ */
47
+ stopGps(): boolean;
48
+ /**
49
+ * Send a fake GPS signal. Useful for testing on a desktop or laptop.
50
+ * @param {number} lon - The longitude.
51
+ * @param {number} lat - The latitude.
52
+ * @param {number} elev - The elevation in metres. (optional, set to null
53
+ * for no elevation).
54
+ * @param {number} acc - The accuracy of the GPS reading in metres. May be
55
+ * ignored if lower than the specified minimum accuracy.
56
+ */
57
+ fakeGps(lon: number, lat: number, elev?: number | null, acc?: number): void;
58
+ /**
59
+ * Convert longitude and latitude to three.js/WebGL world coordinates.
60
+ * Uses the specified projection, and negates the northing (in typical
61
+ * projections, northings increase northwards, but in the WebGL coordinate
62
+ * system, we face negative z if the camera is at the origin with default
63
+ * rotation).
64
+ * Must not be called until an initial position is determined.
65
+ * @param {number} lon - The longitude.
66
+ * @param {number} lat - The latitude.
67
+ * @return {Array} a two member array containing the WebGL x and z coordinates
68
+ */
69
+ lonLatToWorldCoords(lon: number, lat: number): [number, number];
70
+ /**
71
+ * Convert projected east/north coordinates to three.js/WebGL world coordinates.
72
+ * Negates the northing (in typical projections, northings increase northwards,
73
+ * but in the WebGL coordinate system, we face negative z if the camera is at
74
+ * the origin with default rotation).
75
+ * Must not be called until an initial position is determined.
76
+ * It is assumed that the projected position is in the correct projection - no check
77
+ * for this is made.
78
+ * @param {Array} projectedPos - the projected position.
79
+ * @return {Array} a two member array containing the WebGL x and z coordinates
80
+ */
81
+ eastNorthToWorldCoords(projectedPos: [number, number]): [number, number];
82
+ /**
83
+ * Add a new AR object at a given latitude, longitude and elevation.
84
+ * @param {THREE.Mesh} object the object
85
+ * @param {number} lon - the longitude.
86
+ * @param {number} lat - the latitude.
87
+ * @param {number} elev - the elevation in metres
88
+ * (if not specified, 0 is assigned)
89
+ * @param {Object} properties - properties describing the object (for example,
90
+ * the contents of the GeoJSON properties field).
91
+ */
92
+ add(object: THREE.Object3D, lon: number, lat: number, elev?: number | undefined, properties?: Record<string, any>): void;
93
+ addGeoLine(points: Array<[number, number, number?]>, material: THREE.Material, lineWidth?: number): THREE.Mesh;
94
+ /**
95
+ * Set the elevation (y coordinate) of the camera.
96
+ * @param {number} elev - the elevation in metres.
97
+ */
98
+ setElevation(elev: number): void;
99
+ /**
100
+ * Calculate haversine distance between two lat/lon pairs.
101
+ *
102
+ * Taken from original A-Frame AR.js location-based components
103
+ */
104
+ static haversineDist(src: LonLat, dest: LonLat): number;
105
+ /**
106
+ * Obtain the last known GPS location.
107
+ *
108
+ * @return {Object} object containing latitude and longitude fields, or null if no previous GPS location.
109
+ */
110
+ getLastKnownLocation(): LonLat | null;
111
+ }
112
+ export default LocAR;
@@ -0,0 +1,9 @@
1
+ import { default as App } from './app';
2
+ import { default as LocAR } from './locar';
3
+ import { default as SphMercProjection } from './sphmerc-projection';
4
+ import { default as DeviceOrientationControls } from './device-orientation-controls';
5
+ import { default as ClickHandler } from './click-handler';
6
+ import { default as EventEmitter } from './event-emitter';
7
+ import { default as Webcam } from './webcam';
8
+ export type { LonLat, ServerLogger, WebcamStartedEvent, WebcamErrorEvent, DeviceOrientationErrorEvent, DeviceOrientationGrantedEvent, GpsReceivedEvent, Projection, AppOptions, GpsOptions, DeviceOrientationControlsOptions } from '../types/locar';
9
+ export { App, LocAR, Webcam, SphMercProjection, DeviceOrientationControls, ClickHandler, EventEmitter };
@@ -0,0 +1,29 @@
1
+ import { Projection } from '../types/locar';
2
+ declare class SphMercProjection implements Projection {
3
+ #private;
4
+ EARTH: number;
5
+ HALF_EARTH: number;
6
+ /**
7
+ * Create a SphMercProjection.
8
+ */
9
+ constructor();
10
+ /**
11
+ * Project a longitude and latitude into Spherical Mercator.
12
+ * @param {number} lon - the longitude.
13
+ * @param {number} lat - the latitude.
14
+ * @return {Array} Two-member array containing easting and northing.
15
+ */
16
+ project: (lon: number, lat: number) => [number, number];
17
+ /**
18
+ * Unproject a Spherical Mercator easting and northing.
19
+ * @param {Array} projected - Two-member array containing easting and northing
20
+ * @return {Array} Two-member array containing longitude and latitude
21
+ */
22
+ unproject: (projected: [number, number]) => [number, number];
23
+ /**
24
+ * Return the projection's ID.
25
+ * @return {string} The value "epsg:3857".
26
+ */
27
+ getID: () => string;
28
+ }
29
+ export default SphMercProjection;
@@ -0,0 +1,27 @@
1
+ import { default as EventEmitter } from './event-emitter';
2
+ import * as THREE from "three";
3
+ export type { WebcamStartedEvent, WebcamErrorEvent } from '../types/locar';
4
+ /** Class to setup the webcam. */
5
+ declare class Webcam extends EventEmitter {
6
+ #private;
7
+ sceneWebcam: THREE.Scene;
8
+ /**
9
+ * Create a Webcam.
10
+ * @param constraints {Object} - options to use for initialising the camera.
11
+ * This is the same constraints object as used by standard MediaDevices API.
12
+ * @param {string} videoElementSelector - selector to obtain the HTML video
13
+ * element to render the webcam feed. If a falsy value (e.g. null or
14
+ * undefined), a video element will be created.
15
+ */
16
+ constructor(constraints?: {
17
+ video: {
18
+ facingMode: string;
19
+ };
20
+ }, videoElementSelector?: string);
21
+ getVideoDimensions(): string;
22
+ /**
23
+ * Free up the resources associated with the Webcam.
24
+ */
25
+ dispose(): void;
26
+ }
27
+ export default Webcam;
@@ -0,0 +1,89 @@
1
+ import { default as DeviceOrientationControls } from '../three/device-orientation-controls';
2
+ /** Longitude and latitude. */
3
+ export interface LonLat {
4
+ longitude: number;
5
+ latitude: number;
6
+ }
7
+ /** Projection interface, you can create your own custom projection by implementing project() and unproject(). */
8
+ export interface Projection {
9
+ project: (lon: number, lat: number) => [number, number];
10
+ unproject: (projected: [number, number]) => [number, number];
11
+ }
12
+ /** Server logger interface. */
13
+ export interface ServerLogger {
14
+ sendData(endpoint: string, data: any): Promise<Response> | Response;
15
+ }
16
+ /** Generic event. */
17
+ export interface Event {
18
+ }
19
+ /** Event emitted when the webcam starts. */
20
+ export interface WebcamStartedEvent {
21
+ videoWidth: number;
22
+ videoHeight: number;
23
+ }
24
+ /** Event emitted when the webcam encounters an error. */
25
+ export interface WebcamErrorEvent {
26
+ code: string;
27
+ message: string;
28
+ }
29
+ /** Event emitted when device orientation permission has been granted. */
30
+ export interface DeviceOrientationGrantedEvent {
31
+ target: DeviceOrientationControls;
32
+ }
33
+ /** Event emitted when there is an error with device orientation. */
34
+ export interface DeviceOrientationErrorEvent {
35
+ code: string;
36
+ message: string;
37
+ }
38
+ /** Event emitted when a GPS position is received. */
39
+ export interface GpsReceivedEvent {
40
+ /** The new GPS position */
41
+ position: GeolocationPosition;
42
+ /** distance moved in metres since last GpsReceivedEvent */
43
+ distMoved: number;
44
+ }
45
+ /** GPS intialisation options. */
46
+ export interface GpsOptions {
47
+ gpsMinDistance?: number;
48
+ gpsMinAccuracy?: number;
49
+ }
50
+ /** Options to pass into DeviceOrientationControls. */
51
+ export type DeviceOrientationControlsOptions = {
52
+ /** smoothingFactor - default 0.2. If too high, AR content movement will lag behind the sensors. If too low, the scene will be jittery. */
53
+ smoothingFactor?: number;
54
+ /** Movement threshold to detect an orientation change (radians). */
55
+ orientationChangeThreshold?: number;
56
+ /** On iOS, enable permission dialog to seek permission to use device orientation through a user gesture. Recommended to set to true */
57
+ enablePermissionDialog?: boolean;
58
+ /** Set iOS-look and feel styling for the permission dialog for device orientation */
59
+ enableStyling?: boolean;
60
+ /** Use a standard confirm dialog rather than a custom element to grant device orientation permissions */
61
+ preferConfirmDialog?: boolean;
62
+ };
63
+ /** Options to pass into the App object. */
64
+ export interface AppOptions {
65
+ /** the three.js camera options to use - note however we specify horizontal, not vertical, field of view */
66
+ cameraOptions?: {
67
+ hFov: number;
68
+ near: number;
69
+ far: number;
70
+ };
71
+ /** the canvas to render the AR scene into (one will be created if omitted) */
72
+ canvas?: HTMLCanvasElement;
73
+ /** GPS options, see GpsOptions documentation for details */
74
+ gpsOptions?: GpsOptions;
75
+ /** Video constraints for Media Devices API */
76
+ videoConstraints?: {
77
+ video: {
78
+ facingMode: string;
79
+ };
80
+ };
81
+ /** Device orientation options for DeviceOrientationControls */
82
+ deviceOrientationOptions?: DeviceOrientationControlsOptions & {
83
+ enabled: boolean;
84
+ };
85
+ /** Projection to use (default: SphMercProjection) */
86
+ projection?: Projection;
87
+ /** Server logger to use - ensure you gain consent from the user if you are doing this, it's usually a Data Protection legal requirement */
88
+ serverLogger?: ServerLogger;
89
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "locar",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Location-based AR from AR.js.",
5
5
  "files": [
6
6
  "dist"
@@ -23,30 +23,20 @@
23
23
  "makedocs": "typedoc --out docs/api lib/three/main.ts"
24
24
  },
25
25
  "devDependencies": {
26
- "@eslint/css": "^0.11.0",
27
- "@eslint/js": "^9.35.0",
28
- "@eslint/json": "^0.13.2",
26
+ "@eslint/css": "^1.4.0",
27
+ "@eslint/js": "^10.0.1",
28
+ "@eslint/json": "^2.0.1",
29
29
  "@types/three": "^0.181.0",
30
30
  "esbuild":"^0.28.1",
31
- "eslint": "^9.38.0",
31
+ "eslint": "^10.8.0",
32
32
  "globals": "^16.4.0",
33
- "typedoc": "^0.28.19",
34
- "minimatch": "^9.0.7",
35
- "prettier-eslint": "^16.4.2",
33
+ "typedoc": "^0.28.20",
34
+ "prettier-eslint": "^17.1.1",
36
35
  "typescript": "^5.9.3",
37
- "vite": "^8.0.16",
38
- "vite-plugin-dts": "^4.5.4"
36
+ "vite": "^8.1.5",
37
+ "vite-plugin-dts": "^5.0.3"
39
38
  },
40
39
  "dependencies": {
41
- "minimatch": "^9.0.7",
42
- "three": "^0.180.0"
43
- },
44
- "overrides" : {
45
- "prettier-eslint": {
46
- "minimatch": "$minimatch"
47
- },
48
- "vite": {
49
- "esbuild": "$esbuild"
50
- }
40
+ "three": "^0.181.0"
51
41
  }
52
42
  }
package/dist/locar.d.ts DELETED
@@ -1,387 +0,0 @@
1
- import { EventDispatcher } from 'three';
2
- import { Object3D } from 'three';
3
- import { Quaternion } from 'three';
4
- import * as THREE from 'three';
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
-
62
- /**
63
- * Class to handle object detection via mouse clicks/touch events
64
- * and raycasting.
65
- */
66
- export declare class ClickHandler {
67
- raycaster: THREE.Raycaster;
68
- normalisedMousePosition: THREE.Vector2 | null;
69
- /**
70
- * Create a ClickHandler.
71
- * @param {THREE.WebGLRenderer} - The Three.js renderer on which the click
72
- * events will be handled.
73
- */
74
- constructor(renderer: THREE.WebGLRenderer);
75
- /**
76
- * Cast a ray into the scene to detect objects.
77
- * @param {THREE.Camera} - The active Three.js camera, from which the ray
78
- * will be cast.
79
- * @param {THREE.Scene} - The active Three.js scene, which the ray will be
80
- * cast into.
81
- * @return {Array} - array of all intersected objects.
82
- */
83
- raycast(camera: THREE.Camera, scene: THREE.Scene): THREE.Intersection[];
84
- }
85
-
86
- export declare class DeviceOrientationControls extends EventDispatcher {
87
- eventEmitter: EventEmitter;
88
- object: Object3D;
89
- enabled: boolean;
90
- deviceOrientation: {
91
- alpha?: number;
92
- beta?: number;
93
- gamma?: number;
94
- webkitCompassHeading?: number;
95
- absolute?: boolean;
96
- } | null;
97
- screenOrientation: number;
98
- alphaOffset: number;
99
- orientationOffset: number;
100
- initialOffset: boolean | null;
101
- lastQuaternion: Quaternion | null;
102
- orientationChangeEventName: "deviceorientation" | "deviceorientationabsolute";
103
- smoothingFactor: number;
104
- enablePermissionDialog: boolean;
105
- enableInlineStyling: boolean;
106
- preferConfirmDialog: boolean;
107
- orientationChangeThreshold: number;
108
- connect: () => void;
109
- disconnect: () => void;
110
- requestOrientationPermissions: () => void;
111
- update: () => void;
112
- getCorrectedHeading: () => number;
113
- updateAlphaOffset: () => void;
114
- dispose: () => void;
115
- getAlpha: () => number;
116
- getBeta: () => number;
117
- getGamma: () => number;
118
- createObtainPermissionGestureDialog: () => void;
119
- obtainPermissionGesture: () => void;
120
- /**
121
- * Create an instance of DeviceOrientationControls.
122
- * @param {Object} object - the object to attach the controls to
123
- * (usually your Three.js camera)
124
- * @param {Object} options - options for DeviceOrientationControls:
125
- * currently accepts smoothingFactor (< 1), enablePermissionDialog, orientationChangeThreshold (radians)
126
- */
127
- constructor(object: Object3D, options?: DeviceOrientationControlsOptions);
128
- on(eventName: string, eventHandler: (event: any) => void): void;
129
- /**
130
- * Initialise device orientation controls.
131
- * Should be called after you have created the DeviceOrientationControls
132
- * object and set up the deviceorientationgranted and deviceorientationerror
133
- * event handlers.
134
- */
135
- init: () => void;
136
- }
137
-
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. */
141
- smoothingFactor?: number;
142
- /** Movement threshold to detect an orientation change (radians). */
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 */
145
- enablePermissionDialog?: boolean;
146
- /** Set iOS-look and feel styling for the permission dialog for device orientation */
147
- enableStyling?: boolean;
148
- /** Use a standard confirm dialog rather than a custom element to grant device orientation permissions */
149
- preferConfirmDialog?: boolean;
150
- };
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
-
163
- /** Event emitter class to handle events. */
164
- export declare class EventEmitter {
165
- eventHandlers: Record<string, ((...args: any[]) => void)[]>;
166
- constructor();
167
- /**
168
- * Add an event handler.
169
- * @param {string} eventName - the event to handle.
170
- * @param {Function} eventHandler - the event handler function.
171
- */
172
- on: (eventName: string, eventHandler: (...args: any[]) => void) => void;
173
- /**
174
- * Emit an event.
175
- * @param {string} eventName - the event to emit.
176
- * @param ...params - parameters to pass to the event handlers.
177
- */
178
- emit: (eventName: string, ...params: any[]) => void;
179
- /**
180
- * Remove an event handler.
181
- * @param {string} eventName - the event to remove a handler from.
182
- * @param {Function} eventHandler - the event handler function to remove.
183
- */
184
- off: (eventName: string, eventHandler: (...args: any[]) => void) => void;
185
- }
186
-
187
- /** GPS intialisation options. */
188
- export declare interface GpsOptions {
189
- gpsMinDistance?: number;
190
- gpsMinAccuracy?: number;
191
- }
192
-
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 {
206
- #private;
207
- scene: THREE.Scene;
208
- camera: THREE.Camera;
209
- /**
210
- * @param {THREE.Scene} scene - The Three.js scene to use.
211
- * @param {THREE.Camera} camera - The Three.js camera to use. Should usually
212
- * be a THREE.PerspectiveCamera.
213
- * @param {Object} options - Initialisation options for the GPS; see
214
- * setGpsOptions() below.
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.
216
- */
217
- constructor(scene: THREE.Scene, camera: THREE.Camera, options?: GpsOptions, serverLogger?: ServerLogger | null, projection?: Projection);
218
- /**
219
- * Set the projection to use.
220
- * @param {Object} any object which includes a project() method
221
- * taking longitude and latitude as arguments and returning an array
222
- * containing easting and northing.
223
- */
224
- setProjection(proj: Projection): void;
225
- /**
226
- * Set the GPS options.
227
- * @param {Object} object containing gpsMinDistance and/or gpsMinAccuracy
228
- * properties. The former specifies the number of metres which the device
229
- * must move to process a new GPS reading, and the latter specifies the
230
- * minimum accuracy, in metres, for a GPS reading to be counted.
231
- */
232
- setGpsOptions(options?: GpsOptions): void;
233
- /**
234
- * Start the GPS on a real device
235
- * @return {boolean} code indicating whether the GPS was started successfully.
236
- * GPS errors can be handled by handling the gpserror event.
237
- */
238
- startGps(): Promise<boolean>;
239
- /**
240
- * Stop the GPS on a real device
241
- * @return {boolean} true if the GPS was stopped, false if it could not be
242
- * stopped (i.e. it was never started).
243
- */
244
- stopGps(): boolean;
245
- /**
246
- * Send a fake GPS signal. Useful for testing on a desktop or laptop.
247
- * @param {number} lon - The longitude.
248
- * @param {number} lat - The latitude.
249
- * @param {number} elev - The elevation in metres. (optional, set to null
250
- * for no elevation).
251
- * @param {number} acc - The accuracy of the GPS reading in metres. May be
252
- * ignored if lower than the specified minimum accuracy.
253
- */
254
- fakeGps(lon: number, lat: number, elev?: number | null, acc?: number): void;
255
- /**
256
- * Convert longitude and latitude to three.js/WebGL world coordinates.
257
- * Uses the specified projection, and negates the northing (in typical
258
- * projections, northings increase northwards, but in the WebGL coordinate
259
- * system, we face negative z if the camera is at the origin with default
260
- * rotation).
261
- * Must not be called until an initial position is determined.
262
- * @param {number} lon - The longitude.
263
- * @param {number} lat - The latitude.
264
- * @return {Array} a two member array containing the WebGL x and z coordinates
265
- */
266
- lonLatToWorldCoords(lon: number, lat: number): [number, number];
267
- /**
268
- * Convert projected east/north coordinates to three.js/WebGL world coordinates.
269
- * Negates the northing (in typical projections, northings increase northwards,
270
- * but in the WebGL coordinate system, we face negative z if the camera is at
271
- * the origin with default rotation).
272
- * Must not be called until an initial position is determined.
273
- * It is assumed that the projected position is in the correct projection - no check
274
- * for this is made.
275
- * @param {Array} projectedPos - the projected position.
276
- * @return {Array} a two member array containing the WebGL x and z coordinates
277
- */
278
- eastNorthToWorldCoords(projectedPos: [number, number]): [number, number];
279
- /**
280
- * Add a new AR object at a given latitude, longitude and elevation.
281
- * @param {THREE.Mesh} object the object
282
- * @param {number} lon - the longitude.
283
- * @param {number} lat - the latitude.
284
- * @param {number} elev - the elevation in metres
285
- * (if not specified, 0 is assigned)
286
- * @param {Object} properties - properties describing the object (for example,
287
- * the contents of the GeoJSON properties field).
288
- */
289
- add(object: THREE.Object3D, lon: number, lat: number, elev?: number | undefined, properties?: Record<string, any>): void;
290
- addGeoLine(points: Array<[number, number, number?]>, material: THREE.Material, lineWidth?: number): THREE.Mesh;
291
- /**
292
- * Set the elevation (y coordinate) of the camera.
293
- * @param {number} elev - the elevation in metres.
294
- */
295
- setElevation(elev: number): void;
296
- /**
297
- * Calculate haversine distance between two lat/lon pairs.
298
- *
299
- * Taken from original A-Frame AR.js location-based components
300
- */
301
- static haversineDist(src: LonLat, dest: LonLat): number;
302
- /**
303
- * Obtain the last known GPS location.
304
- *
305
- * @return {Object} object containing latitude and longitude fields, or null if no previous GPS location.
306
- */
307
- getLastKnownLocation(): LonLat | null;
308
- }
309
-
310
- /** Longitude and latitude. */
311
- export declare interface LonLat {
312
- longitude: number;
313
- latitude: number;
314
- }
315
-
316
- /** Projection interface, you can create your own custom projection by implementing project() and unproject(). */
317
- export declare interface Projection {
318
- project: (lon: number, lat: number) => [number, number];
319
- unproject: (projected: [number, number]) => [number, number];
320
- }
321
-
322
- /** Server logger interface. */
323
- export declare interface ServerLogger {
324
- sendData(endpoint: string, data: any): Promise<Response> | Response;
325
- }
326
-
327
- export declare class SphMercProjection implements Projection {
328
- #private;
329
- EARTH: number;
330
- HALF_EARTH: number;
331
- /**
332
- * Create a SphMercProjection.
333
- */
334
- constructor();
335
- /**
336
- * Project a longitude and latitude into Spherical Mercator.
337
- * @param {number} lon - the longitude.
338
- * @param {number} lat - the latitude.
339
- * @return {Array} Two-member array containing easting and northing.
340
- */
341
- project: (lon: number, lat: number) => [number, number];
342
- /**
343
- * Unproject a Spherical Mercator easting and northing.
344
- * @param {Array} projected - Two-member array containing easting and northing
345
- * @return {Array} Two-member array containing longitude and latitude
346
- */
347
- unproject: (projected: [number, number]) => [number, number];
348
- /**
349
- * Return the projection's ID.
350
- * @return {string} The value "epsg:3857".
351
- */
352
- getID: () => string;
353
- }
354
-
355
- /** Class to setup the webcam. */
356
- export declare class Webcam extends EventEmitter {
357
- #private;
358
- sceneWebcam: THREE.Scene;
359
- /**
360
- * Create a Webcam.
361
- * @param constraints {Object} - options to use for initialising the camera.
362
- * This is the same constraints object as used by standard MediaDevices API.
363
- * @param {string} videoElementSelector - selector to obtain the HTML video
364
- * element to render the webcam feed. If a falsy value (e.g. null or
365
- * undefined), a video element will be created.
366
- */
367
- constructor(constraints?: {
368
- video: {
369
- facingMode: string;
370
- };
371
- }, videoElementSelector?: string);
372
- getVideoDimensions(): string;
373
- }
374
-
375
- /** Event emitted when the webcam encounters an error. */
376
- export declare interface WebcamErrorEvent {
377
- code: string;
378
- message: string;
379
- }
380
-
381
- /** Event emitted when the webcam starts. */
382
- export declare interface WebcamStartedEvent {
383
- videoWidth: number;
384
- videoHeight: number;
385
- }
386
-
387
- export { }