locar 0.1.1 → 0.1.2

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/README.md CHANGED
@@ -42,7 +42,7 @@ import * as LocAR from 'locar';
42
42
  as long as you include `locar` as a dependency, e.g. in your `package.json`:
43
43
  ```
44
44
  "dependencies": {
45
- "locar" : "^0.1.1",
45
+ "locar" : "^0.1.2",
46
46
  "three" : "^0.175.0"
47
47
  }
48
48
  ```
package/dist/locar.es.js CHANGED
@@ -564,7 +564,7 @@ class j {
564
564
  return [];
565
565
  }
566
566
  }
567
- const U = "0.1.0";
567
+ const U = "0.1.2";
568
568
  export {
569
569
  j as ClickHandler,
570
570
  V as DeviceOrientationControls,
package/dist/locar.umd.js CHANGED
@@ -1 +1 @@
1
- (function(h,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],c):(h=typeof globalThis<"u"?globalThis:h||self,c(h.locar={},h.THREE))})(this,(function(h,c){"use strict";function b(m){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(m){for(const i in m)if(i!=="default"){const e=Object.getOwnPropertyDescriptor(m,i);Object.defineProperty(t,i,e.get?e:{enumerable:!0,get:()=>m[i]})}}return t.default=m,Object.freeze(t)}const p=b(c);class A{constructor(){this.EARTH=4007501668e-2,this.HALF_EARTH=2003750834e-2}project(t,i){return[this.#t(t),this.#l(i)]}unproject(t){return[this.#e(t[0]),this.#s(t[1])]}#t(t){return t/180*this.HALF_EARTH}#l(t){var i=Math.log(Math.tan((90+t)*Math.PI/360))/(Math.PI/180);return i*this.HALF_EARTH/180}#e(t){return t/this.HALF_EARTH*180}#s(t){var i=t/this.HALF_EARTH*180;return i=180/Math.PI*(2*Math.atan(Math.exp(i*Math.PI/180))-Math.PI/2),i}getID(){return"epsg:3857"}}class M{constructor(){this.eventHandlers={}}on(t,i){this.eventHandlers[t]=i}emit(t,...i){this.eventHandlers[t]?.call(this,...i)}}class w extends M{#t;#l;#e;#s;#c;#o;#i;#r;#a;#n;constructor(t,i,e={},l=null){super(),this.scene=t,this.camera=i,this.#t=new A,this.#e=null,this.#s=0,this.#c=100,this.#o=null,this.setGpsOptions(e),this.#i=null,this.#r=0,this.#a=0,this.#n=l}setProjection(t){this.#t=t}setGpsOptions(t={}){t.gpsMinDistance!==void 0&&(this.#s=t.gpsMinDistance),t.gpsMinAccuracy!==void 0&&(this.#c=t.gpsMinAccuracy)}async startGps(){if(this.#n){const i=await(await this.#n.sendData("/gps/start",{gpsMinDistance:this.#s,gpsMinAccuracy:this.#c})).json();this.#a=i.session}return this.#o===null?(this.#o=navigator.geolocation.watchPosition(t=>{this.#h(t)},t=>{this.emit("gpserror",t)},{enableHighAccuracy:!0}),!0):!1}stopGps(){return this.#o!==null?(navigator.geolocation.clearWatch(this.#o),this.#o=null,!0):!1}fakeGps(t,i,e=null,l=0){e!==null&&this.setElevation(e),this.#h({coords:{longitude:t,latitude:i,accuracy:l}})}lonLatToWorldCoords(t,i){const e=this.#t.project(t,i);if(this.#i)e[0]-=this.#i[0],e[1]-=this.#i[1];else throw"No initial position determined";return[e[0],-e[1]]}add(t,i,e,l,g={}){t.properties=g,this.#d(t,i,e,l),this.scene.add(t),this.#n?.sendData("/object/new",{position:t.position,x:t.position.x,z:t.position.z,session:this.#a,properties:g})}#d(t,i,e,l){const g=this.lonLatToWorldCoords(i,e);l!==void 0&&(t.position.y=l),[t.position.x,t.position.z]=g}setElevation(t){this.camera.position.y=t}#u(t,i){this.#i=this.#t.project(t,i)}#h(t){let i=Number.MAX_VALUE;this.#r++,this.#n?.sendData("/gps/new",{gpsCount:this.#r,lat:t.coords.latitude,lon:t.coords.longitude,acc:t.coords.accuracy,session:this.#a}),t.coords.accuracy<=this.#c&&(this.#e===null?this.#e={latitude:t.coords.latitude,longitude:t.coords.longitude}:i=this.#m(this.#e,t.coords),i>=this.#s&&(this.#e.longitude=t.coords.longitude,this.#e.latitude=t.coords.latitude,this.#i||(this.#u(t.coords.longitude,t.coords.latitude),this.#n?.sendData("/worldorigin/new",{gpsCount:this.#r,lat:t.coords.latitude,lon:t.coords.longitude,session:this.#a,initialPosition:this.#i})),this.#d(this.camera,t.coords.longitude,t.coords.latitude),this.#n?.sendData("/gps/accepted",{gpsCount:this.#r,cameraX:this.camera.position.x,cameraZ:this.camera.position.z,session:this.#a,distMoved:i}),this.emit("gpsupdate",{position:t,distMoved:i})))}#m(t,i){const e=p.MathUtils.degToRad(i.longitude-t.longitude),l=p.MathUtils.degToRad(i.latitude-t.latitude),g=Math.sin(l/2)*Math.sin(l/2)+Math.cos(p.MathUtils.degToRad(t.latitude))*Math.cos(p.MathUtils.degToRad(i.latitude))*(Math.sin(e/2)*Math.sin(e/2));return 2*Math.atan2(Math.sqrt(g),Math.sqrt(1-g))*6371e3}}class D extends M{#t;constructor(t={video:{facingMode:"environment"}},i){super(),this.sceneWebcam=new p.Scene,i?this.#t=document.querySelector(i):(this.#t=document.createElement("video"),this.#t.setAttribute("autoplay",!0),this.#t.setAttribute("playsinline",!0),this.#t.style.display="none",document.body.appendChild(this.#t)),this.texture=new p.VideoTexture(this.#t),navigator.mediaDevices&&navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia(t).then(e=>{this.#t.addEventListener("loadedmetadata",()=>{this.#t.setAttribute("width",this.#t.videoWidth),this.#t.setAttribute("height",this.#t.videoHeight),this.#t.play(),this.emit("webcamstarted",{texture:this.texture})}),this.#t.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"})}dispose(){this.texture.dispose()}}const S="locar-device-orientation-permission-modal",T="locar-device-orientation-permission-button",N="locar-device-orientation-permission-message",R="locar-device-orientation-permission-inner",L="locar-device-orientation-permission-button-inner",P="This immersive website requires access to your device motion sensors.",I=navigator.userAgent.match(/iPhone|iPad|iPod/i)||/Macintosh/i.test(navigator.userAgent)&&navigator.maxTouchPoints!=null&&navigator.maxTouchPoints>1,x=new c.Vector3(0,0,1),_=new c.Euler,F=new c.Quaternion,U=new c.Quaternion(-Math.sqrt(.5),0,0,Math.sqrt(.5));class j extends c.EventDispatcher{constructor(t,i={}){super(),this.eventEmitter=new M;const e=this;this.object=t,this.object.rotation.reorder("YXZ"),this.enabled=!0,this.deviceOrientation=null,this.screenOrientation=0,this.alphaOffset=0,this.orientationOffset=0,this.initialOffset=null,this.lastCompassY=void 0,this.lastOrientation=null,this.TWO_PI=2*Math.PI,this.HALF_PI=.5*Math.PI,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;const l=function({alpha:n,beta:s,gamma:a,webkitCompassHeading:r}){if(I){const o=360-r;e.alphaOffset=c.MathUtils.degToRad(o-n),e.deviceOrientation={alpha:n,beta:s,gamma:a,webkitCompassHeading:r}}else n<0&&(n+=360),e.deviceOrientation={alpha:n,beta:s,gamma:a};window.dispatchEvent(new CustomEvent("camera-rotation-change",{detail:{cameraRotation:t.rotation}}))},g=function(){e.screenOrientation=window.orientation||0,I&&(e.screenOrientation===90?e.orientationOffset=-e.HALF_PI:e.screenOrientation===-90?e.orientationOffset=e.HALF_PI:e.orientationOffset=0)},E=function(n,s,a,r,o){_.set(a,s,-r,"YXZ"),n.setFromEuler(_),n.multiply(U),n.multiply(F.setFromAxisAngle(x,-o))};this.connect=function(){g(),window.addEventListener("orientationchange",g),window.addEventListener(e.orientationChangeEventName,l),e.enabled=!0},this.disconnect=function(){window.removeEventListener("orientationchange",g),window.removeEventListener(e.orientationChangeEventName,l),e.enabled=!1,e.initialOffset=!1,e.deviceOrientation=null},this.requestOrientationPermissions=function(){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(function(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=function({theta:n=0}={theta:0}){if(e.enabled===!1)return;const s=e.deviceOrientation;if(s){let a=s.alpha?c.MathUtils.degToRad(s.alpha)+e.alphaOffset:0,r=s.beta?c.MathUtils.degToRad(s.beta):0,o=s.gamma?c.MathUtils.degToRad(s.gamma):0;const v=e.screenOrientation?c.MathUtils.degToRad(e.screenOrientation):0;if(e.smoothingFactor<1){if(e.lastOrientation){const u=e.smoothingFactor;a=e._getSmoothedAngle(a,e.lastOrientation.alpha,u),r=e._getSmoothedAngle(r+Math.PI,e.lastOrientation.beta,u),o=e._getSmoothedAngle(o+e.HALF_PI,e.lastOrientation.gamma,u,Math.PI)}else r+=Math.PI,o+=e.HALF_PI;e.lastOrientation={alpha:a,beta:r,gamma:o}}if(I){const u=new c.Quaternion;E(u,a,e.smoothingFactor<1?r-Math.PI:r,e.smoothingFactor<1?o-Math.PI/2:o,v);const f=new c.Euler().setFromQuaternion(u,"YXZ");let O=c.MathUtils.degToRad(360-s.webkitCompassHeading);e.smoothingFactor<1&&e.lastCompassY!==void 0&&(O=e._getSmoothedAngle(O,e.lastCompassY,e.smoothingFactor)),e.lastCompassY=O,f.y=O+(e.orientationOffset||0),u.setFromEuler(f),e.object.quaternion.copy(u)}else E(e.object.quaternion,I?a+e.alphaOffset:a,e.smoothingFactor<1?r-Math.PI:r,e.smoothingFactor<1?o-Math.PI/2:o,v)}},this.getCorrectedHeading=function(){const{deviceOrientation:n}=e;if(!n)return 0;let s=0;return I?(s=360-n.webkitCompassHeading,e.orientationOffset&&(s+=e.orientationOffset*(180/Math.PI),s=(s+360)%360)):(n.absolute===!0||e.orientationChangeEventName,s=n.alpha?n.alpha:0,s=(360-s)%360,s<0&&(s+=360)),s},this._orderAngle=function(n,s,a=e.TWO_PI){return s>n&&Math.abs(s-n)<a/2||n>s&&Math.abs(s-n)>a/2?{left:n,right:s}:{left:s,right:n}},this._getSmoothedAngle=function(n,s,a,r=e.TWO_PI){const o=e._orderAngle(n,s,r),v=o.left,u=o.right;o.left=0,o.right-=v,o.right<0&&(o.right+=r);let f=u==s?(1-a)*o.right+a*o.left:a*o.right+(1-a)*o.left;return f+=v,f>=r&&(f-=r),f},this.updateAlphaOffset=function(){e.initialOffset=!1},this.dispose=function(){e.disconnect()},this.getAlpha=function(){const{deviceOrientation:n}=e;return n&&n.alpha?c.MathUtils.degToRad(n.alpha)+e.alphaOffset:0},this.getBeta=function(){const{deviceOrientation:n}=e;return n&&n.beta?c.MathUtils.degToRad(n.beta):0},this.getGamma=function(){const{deviceOrientation:n}=e;return n&&n.gamma?c.MathUtils.degToRad(n.gamma):0},this.createObtainPermissionGestureDialog=function(){const n=document.createElement("div");n.classList.add(S);const s=document.createElement("div");s.classList.add(R);const a=document.createElement("div");a.classList.add(N);const r=document.createElement("div");r.classList.add(L);const o=document.createElement("button");if(o.classList.add(T),document.body.appendChild(n),this.enableInlineStyling===!0){const u={fontFamily:"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",display:"flex",position:"fixed",zIndex:1e5,justifyContent:"center",alignItems:"center",backgroundColor:"rgba(0,0,0,0.2)",inset:0,padding:"20px"},f={backgroundColor:"rgba(220, 220, 220, 0.85)",padding:"6px 0",borderRadius:"10px",width:"100%",maxWidth:"400px"},O={padding:"10px 12px",textAlign:"center",fontWeight:400,fontSize:"13px",display:"flex",justifyContent:"center",alignItems:"center"},y={display:"block",textAlign:"center",textDecoration:"none",borderTop:"rgb(180,180,180) solid 1px"},C={display:"block",width:"100%",textAlign:"center",appearance:"none",background:"none",border:"none",outline:"none",padding:"10px",fontWeight:400,fontSize:"16px",color:"#2e7cf1",cursor:"pointer"};for(let d in u)n.style[d]=u[d];for(let d in f)s.style[d]=f[d];for(let d in O)a.style[d]=O[d];for(let d in y)r.style[d]=y[d];for(let d in C)o.style[d]=C[d]}n.appendChild(s),s.appendChild(a),s.appendChild(r),a.appendChild(document.createTextNode(P));const v=()=>{this.requestOrientationPermissions(),n.style.display="none"};o.addEventListener("click",v),o.appendChild(document.createTextNode("OK")),r.appendChild(o),document.body.appendChild(n)},this.obtainPermissionGesture=function(){this.preferConfirmDialog===!0?window.confirm(P)&&this.requestOrientationPermissions():this.createObtainPermissionGestureDialog()}}on(t,i){this.eventEmitter.on(t,i)}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})}}class q{constructor(t){this.raycaster=new p.Raycaster,this.normalisedMousePosition=new p.Vector2(null,null),t.domElement.addEventListener("click",i=>{this.normalisedMousePosition.set(i.clientX/t.domElement.clientWidth*2-1,-(i.clientY/t.domElement.clientHeight*2)+1)})}raycast(t,i){if(this.normalisedMousePosition.x!==null&&this.normalisedMousePosition.y!==null){this.raycaster.setFromCamera(this.normalisedMousePosition,t);const e=this.raycaster.intersectObjects(i.children,!1);return this.normalisedMousePosition.set(null,null),e}return[]}}const k="0.1.0";h.ClickHandler=q,h.DeviceOrientationControls=j,h.EventEmitter=M,h.LocationBased=w,h.SphMercProjection=A,h.Webcam=D,h.version=k,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(h,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],c):(h=typeof globalThis<"u"?globalThis:h||self,c(h.locar={},h.THREE))})(this,(function(h,c){"use strict";function b(m){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(m){for(const i in m)if(i!=="default"){const e=Object.getOwnPropertyDescriptor(m,i);Object.defineProperty(t,i,e.get?e:{enumerable:!0,get:()=>m[i]})}}return t.default=m,Object.freeze(t)}const p=b(c);class A{constructor(){this.EARTH=4007501668e-2,this.HALF_EARTH=2003750834e-2}project(t,i){return[this.#t(t),this.#l(i)]}unproject(t){return[this.#e(t[0]),this.#s(t[1])]}#t(t){return t/180*this.HALF_EARTH}#l(t){var i=Math.log(Math.tan((90+t)*Math.PI/360))/(Math.PI/180);return i*this.HALF_EARTH/180}#e(t){return t/this.HALF_EARTH*180}#s(t){var i=t/this.HALF_EARTH*180;return i=180/Math.PI*(2*Math.atan(Math.exp(i*Math.PI/180))-Math.PI/2),i}getID(){return"epsg:3857"}}class M{constructor(){this.eventHandlers={}}on(t,i){this.eventHandlers[t]=i}emit(t,...i){this.eventHandlers[t]?.call(this,...i)}}class w extends M{#t;#l;#e;#s;#c;#o;#i;#r;#a;#n;constructor(t,i,e={},l=null){super(),this.scene=t,this.camera=i,this.#t=new A,this.#e=null,this.#s=0,this.#c=100,this.#o=null,this.setGpsOptions(e),this.#i=null,this.#r=0,this.#a=0,this.#n=l}setProjection(t){this.#t=t}setGpsOptions(t={}){t.gpsMinDistance!==void 0&&(this.#s=t.gpsMinDistance),t.gpsMinAccuracy!==void 0&&(this.#c=t.gpsMinAccuracy)}async startGps(){if(this.#n){const i=await(await this.#n.sendData("/gps/start",{gpsMinDistance:this.#s,gpsMinAccuracy:this.#c})).json();this.#a=i.session}return this.#o===null?(this.#o=navigator.geolocation.watchPosition(t=>{this.#h(t)},t=>{this.emit("gpserror",t)},{enableHighAccuracy:!0}),!0):!1}stopGps(){return this.#o!==null?(navigator.geolocation.clearWatch(this.#o),this.#o=null,!0):!1}fakeGps(t,i,e=null,l=0){e!==null&&this.setElevation(e),this.#h({coords:{longitude:t,latitude:i,accuracy:l}})}lonLatToWorldCoords(t,i){const e=this.#t.project(t,i);if(this.#i)e[0]-=this.#i[0],e[1]-=this.#i[1];else throw"No initial position determined";return[e[0],-e[1]]}add(t,i,e,l,g={}){t.properties=g,this.#d(t,i,e,l),this.scene.add(t),this.#n?.sendData("/object/new",{position:t.position,x:t.position.x,z:t.position.z,session:this.#a,properties:g})}#d(t,i,e,l){const g=this.lonLatToWorldCoords(i,e);l!==void 0&&(t.position.y=l),[t.position.x,t.position.z]=g}setElevation(t){this.camera.position.y=t}#u(t,i){this.#i=this.#t.project(t,i)}#h(t){let i=Number.MAX_VALUE;this.#r++,this.#n?.sendData("/gps/new",{gpsCount:this.#r,lat:t.coords.latitude,lon:t.coords.longitude,acc:t.coords.accuracy,session:this.#a}),t.coords.accuracy<=this.#c&&(this.#e===null?this.#e={latitude:t.coords.latitude,longitude:t.coords.longitude}:i=this.#m(this.#e,t.coords),i>=this.#s&&(this.#e.longitude=t.coords.longitude,this.#e.latitude=t.coords.latitude,this.#i||(this.#u(t.coords.longitude,t.coords.latitude),this.#n?.sendData("/worldorigin/new",{gpsCount:this.#r,lat:t.coords.latitude,lon:t.coords.longitude,session:this.#a,initialPosition:this.#i})),this.#d(this.camera,t.coords.longitude,t.coords.latitude),this.#n?.sendData("/gps/accepted",{gpsCount:this.#r,cameraX:this.camera.position.x,cameraZ:this.camera.position.z,session:this.#a,distMoved:i}),this.emit("gpsupdate",{position:t,distMoved:i})))}#m(t,i){const e=p.MathUtils.degToRad(i.longitude-t.longitude),l=p.MathUtils.degToRad(i.latitude-t.latitude),g=Math.sin(l/2)*Math.sin(l/2)+Math.cos(p.MathUtils.degToRad(t.latitude))*Math.cos(p.MathUtils.degToRad(i.latitude))*(Math.sin(e/2)*Math.sin(e/2));return 2*Math.atan2(Math.sqrt(g),Math.sqrt(1-g))*6371e3}}class D extends M{#t;constructor(t={video:{facingMode:"environment"}},i){super(),this.sceneWebcam=new p.Scene,i?this.#t=document.querySelector(i):(this.#t=document.createElement("video"),this.#t.setAttribute("autoplay",!0),this.#t.setAttribute("playsinline",!0),this.#t.style.display="none",document.body.appendChild(this.#t)),this.texture=new p.VideoTexture(this.#t),navigator.mediaDevices&&navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia(t).then(e=>{this.#t.addEventListener("loadedmetadata",()=>{this.#t.setAttribute("width",this.#t.videoWidth),this.#t.setAttribute("height",this.#t.videoHeight),this.#t.play(),this.emit("webcamstarted",{texture:this.texture})}),this.#t.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"})}dispose(){this.texture.dispose()}}const S="locar-device-orientation-permission-modal",T="locar-device-orientation-permission-button",N="locar-device-orientation-permission-message",R="locar-device-orientation-permission-inner",L="locar-device-orientation-permission-button-inner",P="This immersive website requires access to your device motion sensors.",I=navigator.userAgent.match(/iPhone|iPad|iPod/i)||/Macintosh/i.test(navigator.userAgent)&&navigator.maxTouchPoints!=null&&navigator.maxTouchPoints>1,x=new c.Vector3(0,0,1),_=new c.Euler,F=new c.Quaternion,U=new c.Quaternion(-Math.sqrt(.5),0,0,Math.sqrt(.5));class j extends c.EventDispatcher{constructor(t,i={}){super(),this.eventEmitter=new M;const e=this;this.object=t,this.object.rotation.reorder("YXZ"),this.enabled=!0,this.deviceOrientation=null,this.screenOrientation=0,this.alphaOffset=0,this.orientationOffset=0,this.initialOffset=null,this.lastCompassY=void 0,this.lastOrientation=null,this.TWO_PI=2*Math.PI,this.HALF_PI=.5*Math.PI,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;const l=function({alpha:n,beta:s,gamma:a,webkitCompassHeading:r}){if(I){const o=360-r;e.alphaOffset=c.MathUtils.degToRad(o-n),e.deviceOrientation={alpha:n,beta:s,gamma:a,webkitCompassHeading:r}}else n<0&&(n+=360),e.deviceOrientation={alpha:n,beta:s,gamma:a};window.dispatchEvent(new CustomEvent("camera-rotation-change",{detail:{cameraRotation:t.rotation}}))},g=function(){e.screenOrientation=window.orientation||0,I&&(e.screenOrientation===90?e.orientationOffset=-e.HALF_PI:e.screenOrientation===-90?e.orientationOffset=e.HALF_PI:e.orientationOffset=0)},E=function(n,s,a,r,o){_.set(a,s,-r,"YXZ"),n.setFromEuler(_),n.multiply(U),n.multiply(F.setFromAxisAngle(x,-o))};this.connect=function(){g(),window.addEventListener("orientationchange",g),window.addEventListener(e.orientationChangeEventName,l),e.enabled=!0},this.disconnect=function(){window.removeEventListener("orientationchange",g),window.removeEventListener(e.orientationChangeEventName,l),e.enabled=!1,e.initialOffset=!1,e.deviceOrientation=null},this.requestOrientationPermissions=function(){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(function(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=function({theta:n=0}={theta:0}){if(e.enabled===!1)return;const s=e.deviceOrientation;if(s){let a=s.alpha?c.MathUtils.degToRad(s.alpha)+e.alphaOffset:0,r=s.beta?c.MathUtils.degToRad(s.beta):0,o=s.gamma?c.MathUtils.degToRad(s.gamma):0;const v=e.screenOrientation?c.MathUtils.degToRad(e.screenOrientation):0;if(e.smoothingFactor<1){if(e.lastOrientation){const u=e.smoothingFactor;a=e._getSmoothedAngle(a,e.lastOrientation.alpha,u),r=e._getSmoothedAngle(r+Math.PI,e.lastOrientation.beta,u),o=e._getSmoothedAngle(o+e.HALF_PI,e.lastOrientation.gamma,u,Math.PI)}else r+=Math.PI,o+=e.HALF_PI;e.lastOrientation={alpha:a,beta:r,gamma:o}}if(I){const u=new c.Quaternion;E(u,a,e.smoothingFactor<1?r-Math.PI:r,e.smoothingFactor<1?o-Math.PI/2:o,v);const f=new c.Euler().setFromQuaternion(u,"YXZ");let O=c.MathUtils.degToRad(360-s.webkitCompassHeading);e.smoothingFactor<1&&e.lastCompassY!==void 0&&(O=e._getSmoothedAngle(O,e.lastCompassY,e.smoothingFactor)),e.lastCompassY=O,f.y=O+(e.orientationOffset||0),u.setFromEuler(f),e.object.quaternion.copy(u)}else E(e.object.quaternion,I?a+e.alphaOffset:a,e.smoothingFactor<1?r-Math.PI:r,e.smoothingFactor<1?o-Math.PI/2:o,v)}},this.getCorrectedHeading=function(){const{deviceOrientation:n}=e;if(!n)return 0;let s=0;return I?(s=360-n.webkitCompassHeading,e.orientationOffset&&(s+=e.orientationOffset*(180/Math.PI),s=(s+360)%360)):(n.absolute===!0||e.orientationChangeEventName,s=n.alpha?n.alpha:0,s=(360-s)%360,s<0&&(s+=360)),s},this._orderAngle=function(n,s,a=e.TWO_PI){return s>n&&Math.abs(s-n)<a/2||n>s&&Math.abs(s-n)>a/2?{left:n,right:s}:{left:s,right:n}},this._getSmoothedAngle=function(n,s,a,r=e.TWO_PI){const o=e._orderAngle(n,s,r),v=o.left,u=o.right;o.left=0,o.right-=v,o.right<0&&(o.right+=r);let f=u==s?(1-a)*o.right+a*o.left:a*o.right+(1-a)*o.left;return f+=v,f>=r&&(f-=r),f},this.updateAlphaOffset=function(){e.initialOffset=!1},this.dispose=function(){e.disconnect()},this.getAlpha=function(){const{deviceOrientation:n}=e;return n&&n.alpha?c.MathUtils.degToRad(n.alpha)+e.alphaOffset:0},this.getBeta=function(){const{deviceOrientation:n}=e;return n&&n.beta?c.MathUtils.degToRad(n.beta):0},this.getGamma=function(){const{deviceOrientation:n}=e;return n&&n.gamma?c.MathUtils.degToRad(n.gamma):0},this.createObtainPermissionGestureDialog=function(){const n=document.createElement("div");n.classList.add(S);const s=document.createElement("div");s.classList.add(R);const a=document.createElement("div");a.classList.add(N);const r=document.createElement("div");r.classList.add(L);const o=document.createElement("button");if(o.classList.add(T),document.body.appendChild(n),this.enableInlineStyling===!0){const u={fontFamily:"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",display:"flex",position:"fixed",zIndex:1e5,justifyContent:"center",alignItems:"center",backgroundColor:"rgba(0,0,0,0.2)",inset:0,padding:"20px"},f={backgroundColor:"rgba(220, 220, 220, 0.85)",padding:"6px 0",borderRadius:"10px",width:"100%",maxWidth:"400px"},O={padding:"10px 12px",textAlign:"center",fontWeight:400,fontSize:"13px",display:"flex",justifyContent:"center",alignItems:"center"},y={display:"block",textAlign:"center",textDecoration:"none",borderTop:"rgb(180,180,180) solid 1px"},C={display:"block",width:"100%",textAlign:"center",appearance:"none",background:"none",border:"none",outline:"none",padding:"10px",fontWeight:400,fontSize:"16px",color:"#2e7cf1",cursor:"pointer"};for(let d in u)n.style[d]=u[d];for(let d in f)s.style[d]=f[d];for(let d in O)a.style[d]=O[d];for(let d in y)r.style[d]=y[d];for(let d in C)o.style[d]=C[d]}n.appendChild(s),s.appendChild(a),s.appendChild(r),a.appendChild(document.createTextNode(P));const v=()=>{this.requestOrientationPermissions(),n.style.display="none"};o.addEventListener("click",v),o.appendChild(document.createTextNode("OK")),r.appendChild(o),document.body.appendChild(n)},this.obtainPermissionGesture=function(){this.preferConfirmDialog===!0?window.confirm(P)&&this.requestOrientationPermissions():this.createObtainPermissionGestureDialog()}}on(t,i){this.eventEmitter.on(t,i)}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})}}class q{constructor(t){this.raycaster=new p.Raycaster,this.normalisedMousePosition=new p.Vector2(null,null),t.domElement.addEventListener("click",i=>{this.normalisedMousePosition.set(i.clientX/t.domElement.clientWidth*2-1,-(i.clientY/t.domElement.clientHeight*2)+1)})}raycast(t,i){if(this.normalisedMousePosition.x!==null&&this.normalisedMousePosition.y!==null){this.raycaster.setFromCamera(this.normalisedMousePosition,t);const e=this.raycaster.intersectObjects(i.children,!1);return this.normalisedMousePosition.set(null,null),e}return[]}}const k="0.1.2";h.ClickHandler=q,h.DeviceOrientationControls=j,h.EventEmitter=M,h.LocationBased=w,h.SphMercProjection=A,h.Webcam=D,h.version=k,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})}));
@@ -0,0 +1,316 @@
1
+ declare module "locar" {
2
+ import * as THREE from "three";
3
+
4
+ /**
5
+ * Generic, small event emitter that only stores a single handler per event name.
6
+ */
7
+ export class EventEmitter {
8
+ protected eventHandlers: Record<string, (...args: any[]) => void>;
9
+ on(eventName: string, handler: (...args: any[]) => void): void;
10
+ emit(eventName: string, ...params: any[]): void;
11
+ }
12
+
13
+ /**
14
+ * Spherical Mercator (EPSG:3857) projection used by `LocationBased`.
15
+ */
16
+ export class SphMercProjection {
17
+ constructor();
18
+ /**
19
+ * @returns [easting, northing]
20
+ */
21
+ project(lon: number, lat: number): [number, number];
22
+ /**
23
+ * @returns [lon, lat]
24
+ */
25
+ unproject(projected: [number, number]): [number, number];
26
+ getID(): "epsg:3857";
27
+ }
28
+
29
+ /**
30
+ * Options for GPS in `LocationBased`.
31
+ * Mirrored from `setGpsOptions(...)`
32
+ */
33
+ export interface GpsOptions {
34
+ /**
35
+ * Meters the device must move to accept the next GPS reading.
36
+ */
37
+ gpsMinDistance?: number;
38
+ /**
39
+ * Minimum accuracy in meters to accept a GPS reading.
40
+ */
41
+ gpsMinAccuracy?: number;
42
+ }
43
+
44
+ /**
45
+ * Optional logger object passed to `LocationBased` for debug.
46
+ */
47
+ export interface ServerLogger {
48
+ /**
49
+ * Should send data to server. Implementation-specific in userland.
50
+ */
51
+ sendData(endpoint: string, data: any): Promise<Response> | Response;
52
+ }
53
+
54
+ /**
55
+ * Main location AR class.
56
+ */
57
+ export class LocationBased extends EventEmitter {
58
+ readonly scene: THREE.Scene;
59
+ readonly camera: THREE.Camera;
60
+
61
+ constructor(
62
+ scene: THREE.Scene,
63
+ camera: THREE.Camera,
64
+ options?: GpsOptions,
65
+ serverLogger?: ServerLogger | null,
66
+ );
67
+
68
+ /**
69
+ * Set the projection (must have `project(lon,lat): [x,y]`).
70
+ */
71
+ setProjection(proj: {
72
+ project(lon: number, lat: number): [number, number];
73
+ }): void;
74
+
75
+ /**
76
+ * Update GPS options at runtime.
77
+ */
78
+ setGpsOptions(options?: GpsOptions): void;
79
+
80
+ /**
81
+ * Start real GPS (`navigator.geolocation.watchPosition`).
82
+ */
83
+ startGps(): Promise<boolean> | boolean;
84
+
85
+ /**
86
+ * Stop real GPS.
87
+ */
88
+ stopGps(): boolean;
89
+
90
+ /**
91
+ * Send a fake GPS position.
92
+ */
93
+ fakeGps(lon: number, lat: number, elev?: number | null, acc?: number): void;
94
+
95
+ /**
96
+ * Convert lon/lat to world coordinates (needs initial position).
97
+ */
98
+ lonLatToWorldCoords(lon: number, lat: number): [number, number];
99
+
100
+ /**
101
+ * Add a THREE object at lon/lat/(elev) and put in the scene.
102
+ */
103
+ add(
104
+ object: THREE.Object3D,
105
+ lon: number,
106
+ lat: number,
107
+ elev?: number,
108
+ properties?: Record<string, any>,
109
+ ): void;
110
+
111
+ /**
112
+ * Set camera elevation (y).
113
+ */
114
+ setElevation(elev: number): void;
115
+
116
+ /**
117
+ * Events:
118
+ * - "gpsupdate": `{ position: GeolocationPosition, distMoved: number }``
119
+ * - "gpserror": `GeolocationPositionError`
120
+ */
121
+ on(
122
+ eventName: "gpsupdate",
123
+ handler: (data: {
124
+ position: GeolocationPosition;
125
+ distMoved: number;
126
+ }) => void,
127
+ ): void;
128
+ on(
129
+ eventName: "gpserror",
130
+ handler: (error: GeolocationPositionError) => void,
131
+ ): void;
132
+ on(eventName: string, handler: (...args: any[]) => void): void;
133
+ }
134
+
135
+ /**
136
+ * Small webcam wrapper that creates a hidden `<video>` and a `THREE.VideoTexture`.
137
+ */
138
+ export interface WebcamStartedEvent {
139
+ texture: THREE.VideoTexture;
140
+ }
141
+ export interface WebcamErrorEvent {
142
+ code: string;
143
+ message: string;
144
+ }
145
+
146
+ export class Webcam extends EventEmitter {
147
+ /**
148
+ * @param constraints `MediaDevices.getUserMedia` constraints
149
+ * @param videoElementSelector selector for an existing `<video>`; if falsy, it creates one
150
+ */
151
+ constructor(
152
+ constraints?: MediaStreamConstraints,
153
+ videoElementSelector?: string | null,
154
+ );
155
+
156
+ /**
157
+ * Texture that streams the camera feed.
158
+ */
159
+ readonly texture: THREE.VideoTexture;
160
+
161
+ /**
162
+ * Free GPU resources.
163
+ */
164
+ dispose(): void;
165
+
166
+ // events
167
+ on(
168
+ eventName: "webcamstarted",
169
+ handler: (e: WebcamStartedEvent) => void,
170
+ ): void;
171
+ on(eventName: "webcamerror", handler: (e: WebcamErrorEvent) => void): void;
172
+ on(eventName: string, handler: (...args: any[]) => void): void;
173
+ }
174
+
175
+ /**
176
+ * Click handler/raycaster wrapper.
177
+ */
178
+ export class ClickHandler {
179
+ constructor(renderer: THREE.WebGLRenderer);
180
+
181
+ /**
182
+ * Cast a ray and return intersects with scene children.
183
+ */
184
+ raycast(camera: THREE.Camera, scene: THREE.Scene): THREE.Intersection[];
185
+ }
186
+
187
+ export interface DeviceOrientationControlsOptions {
188
+ /**
189
+ * 0 < k <= 1. Lower = more smoothing.
190
+ */
191
+ smoothingFactor?: number;
192
+ /**
193
+ * Show iOS permission dialog.
194
+ */
195
+ enablePermissionDialog?: boolean;
196
+ /**
197
+ * Apply inline styles on the created dialog.
198
+ */
199
+ enableStyling?: boolean;
200
+ /**
201
+ * Use `window.confirm(...)` instead of building DOM.
202
+ */
203
+ preferConfirmDialog?: boolean;
204
+ }
205
+
206
+ export interface DeviceOrientationGrantedEvent {
207
+ target: DeviceOrientationControls;
208
+ }
209
+
210
+ export interface DeviceOrientationErrorEvent {
211
+ code: string;
212
+ message: string;
213
+ error?: string;
214
+ }
215
+
216
+ export class DeviceOrientationControls extends THREE.EventDispatcher {
217
+ /**
218
+ * @param object usually a `THREE.Camera`
219
+ */
220
+ constructor(
221
+ object: THREE.Object3D,
222
+ options?: DeviceOrientationControlsOptions,
223
+ );
224
+
225
+ /**
226
+ * Initialize the device orientation controls. Should be called first to set up permission flow.
227
+ */
228
+ init(): void;
229
+
230
+ /**
231
+ * Begin listening to orientation + screenorientation (must be called after permission is granted on iOS).
232
+ */
233
+ connect(): void;
234
+
235
+ /**
236
+ * Stop listening.
237
+ */
238
+ disconnect(): void;
239
+
240
+ /**
241
+ * iOS: must be called in a user gesture to ask for perms.
242
+ */
243
+ requestOrientationPermissions(): void;
244
+
245
+ /**
246
+ * Create the DOM dialog (iOS-style) and wire it to requestOrientationPermissions.
247
+ */
248
+ createObtainPermissionGestureDialog(): void;
249
+
250
+ /**
251
+ * Choose `confirm()` vs DOM dialog.
252
+ */
253
+ obtainPermissionGesture(): void;
254
+
255
+ /**
256
+ * Call each frame.
257
+ */
258
+ update(args?: { theta?: number }): void;
259
+
260
+ /**
261
+ * iOS heading correction.
262
+ */
263
+ getCorrectedHeading(): number;
264
+
265
+ /**
266
+ * Provided in AR.js fix — forces re-evaluation of alpha offset.
267
+ */
268
+ updateAlphaOffset(): void;
269
+
270
+ /**
271
+ * Getters (radians).
272
+ */
273
+ getAlpha(): number;
274
+ getBeta(): number;
275
+ getGamma(): number;
276
+
277
+ dispose(): void;
278
+
279
+ on(
280
+ eventName: "deviceorientationgranted",
281
+ handler: (ev: DeviceOrientationGrantedEvent) => void,
282
+ ): void;
283
+ on(
284
+ eventName: "deviceorientationerror",
285
+ handler: (ev: DeviceOrientationErrorEvent) => void,
286
+ ): void;
287
+ on(eventName: string, handler: (...args: any[]) => void): void;
288
+
289
+ enabled: boolean;
290
+ }
291
+
292
+ /**
293
+ * Version string.
294
+ */
295
+ export const version: string;
296
+
297
+ /**
298
+ * Default export from build (UMD/ES) is the namespace with all the above.
299
+ * Modeled so that:
300
+ * `import * as LocAR from "locar"`
301
+ * and
302
+ * `import LocAR from "locar"``
303
+ * both typecheck.
304
+ */
305
+ const LocAR: {
306
+ EventEmitter: typeof EventEmitter;
307
+ SphMercProjection: typeof SphMercProjection;
308
+ LocationBased: typeof LocationBased;
309
+ Webcam: typeof Webcam;
310
+ ClickHandler: typeof ClickHandler;
311
+ DeviceOrientationControls: typeof DeviceOrientationControls;
312
+ version: string;
313
+ };
314
+
315
+ export default LocAR;
316
+ }
package/package.json CHANGED
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "name": "locar",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Location-based AR from AR.js.",
5
5
  "files": [
6
- "dist"
6
+ "dist",
7
+ "lib/types"
7
8
  ],
8
9
  "main": "./dist/locar.umd.js",
9
10
  "module": "./dist/locar.es.js",
11
+ "types": "lib/types/locar.d.ts",
10
12
  "exports": {
11
13
  ".": {
14
+ "types": "./lib/types/locar.d.ts",
12
15
  "import": "./dist/locar.es.js",
13
16
  "require": "./dist/locar.umd.js"
14
17
  }
@@ -23,7 +26,7 @@
23
26
  "@eslint/css": "^0.11.0",
24
27
  "@eslint/js": "^9.35.0",
25
28
  "@eslint/json": "^0.13.2",
26
- "eslint": "^9.35.0",
29
+ "eslint": "^9.38.0",
27
30
  "globals": "^16.4.0",
28
31
  "jsdoc": "^4.0.4",
29
32
  "prettier-eslint": "^16.4.2",