@navaramap/three-plugins 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -634,8 +634,12 @@ export declare type TeleportOptions = {
634
634
  * fields (`bounds`, `center`, `grids`, ...) are ignored.
635
635
  */
636
636
  export declare type TileJson = {
637
- /** Semver of the TileJSON spec the document conforms to, e.g. `"3.0.0"`. */
638
- tilejson: string;
637
+ /**
638
+ * Semver of the TileJSON spec the document conforms to, e.g. `"3.0.0"`.
639
+ * Optional because many tilesets omit it (MapLibre accepts that); a value
640
+ * that is present must still be a `major.minor.patch` version.
641
+ */
642
+ tilejson?: string;
639
643
  /** Tile URL templates (`{z}/{x}/{y}`). Required and non-empty per the spec. */
640
644
  tiles: string[];
641
645
  /** Attribution/credit HTML shown through the view's attribution UI. */
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { EventHandler as e, MAPBOX_ELEVATION_DECODER as t, Plugin as n, TERRARIUM_ELEVATION_DECODER as r, convertWorldToScreen as i, degreeToRadian as a, eastNorthUpToFixedFrame as o, geodeticToVector3 as s, radianToDegree as c, vector3ToGeodetic as l } from "@navaramap/three";
2
2
  import { MathUtils as u, Matrix4 as d, Vector3 as f } from "three";
3
- //#region src/PersonViewPlugin.ts
3
+ // src/PersonViewPlugin.ts
4
4
  var p = {
5
5
  x: 0,
6
6
  y: 0,
@@ -180,7 +180,7 @@ var p = {
180
180
  z: n
181
181
  }
182
182
  }), this.modelRef = this.handle.ref, this.modelRef.on("load", () => {
183
- !this.view || this.animId == null || this.resumeLoop();
183
+ this.view && this.animId != null && this.resumeLoop();
184
184
  });
185
185
  } else this.resumeLoop();
186
186
  this.applyInitialCamera(), this.applyModelVisibility();
@@ -310,7 +310,7 @@ var p = {
310
310
  this.releaseControl(), this.handle &&= (this.handle.delete(), null), this.modelRef = null, this.placed = !1, this.pinnedStartHeight = void 0, this.listeners.clear(), this.actionListeners.clear(), this.view = void 0;
311
311
  }
312
312
  playAnimation(e) {
313
- !e || !this.character || !this.modelRef || e !== this.currentAnimState && (this.modelRef.crossFadeAnimation(this.currentAnimState ?? "", e, this.character.animation.crossfadeDuration), this.modelRef.setAnimationSpeed(this.clipSpeed(e)), this.currentAnimState = e);
313
+ e && this.character && this.modelRef && e !== this.currentAnimState && (this.modelRef.crossFadeAnimation(this.currentAnimState ?? "", e, this.character.animation.crossfadeDuration), this.modelRef.setAnimationSpeed(this.clipSpeed(e)), this.currentAnimState = e);
314
314
  }
315
315
  resolveCharacter(e) {
316
316
  return {
@@ -487,34 +487,12 @@ var p = {
487
487
  if (e.repeat || this.emitAction(n), n === "toggleView") {
488
488
  if (e.repeat) return;
489
489
  this.toggleViewMode();
490
- return;
491
- }
492
- if (n === "orbitCamera") {
493
- this.orbitKeyHeld = !0, this.orbitLatched = !0;
494
- return;
495
- }
496
- if (!this.movementSuppressed) {
497
- if (n === "dash") {
498
- this.dashHeld = !0;
499
- return;
500
- }
501
- this.isMovementAction(n) && (this.orbitLatched = !1, this.pinnedStartHeight = void 0), this.heldActions.add(n);
502
- }
490
+ } else n === "orbitCamera" ? (this.orbitKeyHeld = !0, this.orbitLatched = !0) : this.movementSuppressed || (n === "dash" ? this.dashHeld = !0 : (this.isMovementAction(n) && (this.orbitLatched = !1, this.pinnedStartHeight = void 0), this.heldActions.add(n)));
503
491
  }
504
492
  }
505
493
  onKeyUp(e) {
506
494
  let t = this.keyToAction.get(e.code);
507
- if (t !== void 0 && t !== "toggleView") {
508
- if (t === "orbitCamera") {
509
- this.orbitKeyHeld = !1;
510
- return;
511
- }
512
- if (t === "dash") {
513
- this.dashHeld = !1;
514
- return;
515
- }
516
- this.heldActions.delete(t);
517
- }
495
+ t !== void 0 && t !== "toggleView" && (t === "orbitCamera" ? this.orbitKeyHeld = !1 : t === "dash" ? this.dashHeld = !1 : this.heldActions.delete(t));
518
496
  }
519
497
  tick = (e) => {
520
498
  if (!this.view || this.animId == null) return;
@@ -583,8 +561,7 @@ var p = {
583
561
  return e.lng !== t.lng || e.lat !== t.lat || e.alt !== t.alt || e.heading !== t.heading || e.speed !== t.speed || e.animationState !== t.animationState || e.mode !== t.mode;
584
562
  }
585
563
  };
586
- //#endregion
587
- //#region src/OverlayPlugin.ts
564
+ // src/OverlayPlugin.ts
588
565
  function T(e, t, n) {
589
566
  e.style.transform = `translate(${t}px, ${n}px)`, e.style.willChange = "transform";
590
567
  }
@@ -690,7 +667,7 @@ var E = 1e5, D = class extends n {
690
667
  }
691
668
  }, A = /^[23]\.\d+\.\d+$/, j = 512, M = "mapbox";
692
669
  function N(e) {
693
- if (typeof e.tilejson != "string" || !A.test(e.tilejson)) throw Error(`TileJsonPlugin: invalid or missing "tilejson" version field (got ${JSON.stringify(e.tilejson)}).`);
670
+ if (e.tilejson !== void 0 && (typeof e.tilejson != "string" || !A.test(e.tilejson))) throw Error(`TileJsonPlugin: invalid "tilejson" version field (got ${JSON.stringify(e.tilejson)}).`);
694
671
  if (!Array.isArray(e.tiles) || e.tiles.length === 0) throw Error("TileJsonPlugin: \"tiles\" must be a non-empty array of URL templates.");
695
672
  }
696
673
  function P(e) {
@@ -762,5 +739,4 @@ var F = class extends n {
762
739
  this.credits.includes(e) || (this.credits.push(e), this.view?.attribution?.add([{ attributionHtml: e }]));
763
740
  }
764
741
  };
765
- //#endregion
766
742
  export { k as CesiumIonPlugin, D as OverlayPlugin, w as PersonViewPlugin, F as TileJsonPlugin, T as moveOverlayElement };
@@ -1 +1 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@navaramap/three"),require("three")):typeof define==`function`&&define.amd?define([`exports`,`@navaramap/three`,`three`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.NavaraThreePlugins={},e._navaramap_three,e.three))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};t=((e,t,n)=>(n=e==null?{}:r(s(e)),l(t||!e||!e.__esModule||!c.call(e,`default`)?i(n,`default`,{value:e,enumerable:!0}):n,e)))(t,1);var u={x:0,y:0,z:0},d={forward:[`KeyW`],backward:[`KeyS`],turnLeft:[`KeyA`],turnRight:[`KeyD`],ascend:[`ArrowUp`,`Space`],descend:[`ArrowDown`,`ControlLeft`,`ControlRight`],dash:[`ShiftLeft`,`ShiftRight`],orbitCamera:[`AltLeft`,`AltRight`],toggleView:[`KeyV`]},f={mode:`off`,groundOffset:0,alignToSlope:!0,slopeSampleDistance:4,maxSlopeTilt:45,cameraSlopeFollow:1},p=10,m=6,h=Math.PI/4,g=[0,1,2,3,4,5,6,7].map(e=>{let t=e*Math.PI/4;return{sin:Math.sin(t),cos:Math.cos(t)}}),_=(e,t)=>e>0&&t>0?1-Math.exp(-e*t):1,v={allowCameraControl:!1,initialView:`tpv`,moveSpeed:50,rotationSpeed:3,altSpeed:30,dashSpeedMultiplier:2.5,minAlt:50,maxAlt:5e3,cameraDistance:50,cameraPitch:0,cameraLerpSpeed:3,fpvForwardOffset:0,fpvHeightOffset:1,fpvPitch:0,startLat:35.6812,startLng:139.7671,startHeight:500,startHeading:234},y=3,b=1e3,x=class extends t.Plugin{view;config;collision;character;keys;keyToAction;handle=null;modelRef=null;animId=null;placed=!1;pinnedStartHeight;lastTime=0;state;listeners=new Set;actionListeners=new Set;heldActions=new Set;dashHeld=!1;orbitKeyHeld=!1;orbitLatched=!1;currentAnimState;modelHeading;cameraHeading;viewMode;movementSuppressed=!1;boundKeyDown;boundKeyUp;_curPos=new n.Vector3;_east=new n.Vector3;_north=new n.Vector3;_worldForward=new n.Vector3;_offset=new n.Vector3;_probePos=new n.Vector3;_headingMatrix=new n.Matrix4;_tiltMatrix=new n.Matrix4;_characterFrame=new n.Matrix4;cameraSlopePitch=0;slopeGradEast=0;slopeGradNorth=0;lastGroundSample;unabsorbedGroundShift=0;settleVelocity=0;groundSurfaceHeight;constructor(e={}){super();let{character:t,keys:r,collision:i,...a}=e;this.config={...v,...a},this.collision={...f,...i},this.character=t?this.resolveCharacter(t):null,this.keys={...d,...r},this.keyToAction=this.buildKeyMap(this.keys),this.modelHeading=n.MathUtils.degToRad(this.config.startHeading),this.cameraHeading=this.modelHeading,this.viewMode=this.config.initialView,this.currentAnimState=this.character?this.character.animation.idleClip:null,this.boundKeyDown=this.onKeyDown.bind(this),this.boundKeyUp=this.onKeyUp.bind(this)}async init(e,t){this.view=e,this.state={lng:this.config.startLng,lat:this.config.startLat,alt:this.config.startHeight,heading:this.config.startHeading,speed:0,animationState:this.currentAnimState,mode:this.viewMode}}async resolveStartHeight(e){let t=this.view;if(!t)return;let{startLat:n,startLng:r}=this.config,[i]=await t.sampleTerrainMostDetailed(e,[{lat:n,lng:r}]);if(i?.height===void 0)return;let{mode:a,groundOffset:o}=this.collision,s=i.height+(a===`off`?0:o);return this.config.startHeight=s,this.placed||(this.state={...this.state,alt:s},this.pinnedStartHeight=s,a!==`off`&&(this.groundSurfaceHeight=s)),s}start(){if(!this.view||this.animId!=null)return;if(this.animId=-1,document.addEventListener(`keydown`,this.boundKeyDown),document.addEventListener(`keyup`,this.boundKeyUp),this.placed){this.resumeLoop(),this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading),this.applyModelVisibility();return}this.placed=!0;let{startLat:e,startLng:n,startHeight:r,startHeading:i}=this.config,a=(0,t.geodeticToVector3)({lat:e,lng:n,height:r});if(this.modelHeading=(0,t.degreeToRadian)(i),this.cameraHeading=this.modelHeading,this.character){let{animation:e,modelRotationOffset:t,modelScale:n,castShadow:r,receiveShadow:i}=this.character;this.handle=this.view.addMesh({gltfModel:{url:this.character.modelUrl,castShadow:r,receiveShadow:i,animationEnabled:!0,animationAutoPlay:!0,animationActiveClip:e.idleClip,animationSpeed:this.clipSpeed(e.idleClip),animationLoop:!0,animationCrossfadeDuration:e.crossfadeDuration},matrixWorld:this.composeCharacterFrame(a,this.modelHeading),rotation:{x:t.x,y:t.y,z:t.z},scale:{x:n,y:n,z:n}}),this.modelRef=this.handle.ref,this.modelRef.on(`load`,()=>{!this.view||this.animId==null||this.resumeLoop()})}else this.resumeLoop();this.applyInitialCamera(),this.applyModelVisibility()}stop(){if(this.animId==null)return;this.releaseControl(),this.view?.cameraFollow(!1),this.view?.cameraFreeLook(!1),this.playAnimation(this.character?.animation.idleClip);let e={...this.state,speed:0,animationState:this.currentAnimState};this.hasStateChanged(this.state,e)&&(this.state=e,this.notify())}releaseControl(){this.animId!=null&&this.animId>=0&&cancelAnimationFrame(this.animId),this.animId=null,document.removeEventListener(`keydown`,this.boundKeyDown),document.removeEventListener(`keyup`,this.boundKeyUp),this.heldActions.clear(),this.orbitKeyHeld=!1,this.orbitLatched=!1,this.dashHeld=!1}resumeLoop(){this.lastTime=performance.now(),this.animId=requestAnimationFrame(this.tick)}teleport(e){if(!this.view)return;this.pinnedStartHeight=void 0;let{lng:n,lat:r}=e,i=e.heading==null?(0,t.radianToDegree)(this.cameraHeading):e.heading,a=(0,t.degreeToRadian)(i),o={lat:r,lng:n},s=this.resolveGroundHeight(o,o,e.alt,0);if(this.updateSlopeTilt(o.lat,o.lng,s,0),this.handle&&this.character){let e=(0,t.geodeticToVector3)({lat:o.lat,lng:o.lng,height:s});this.handle.update({matrixWorld:this.composeCharacterFrame(e,a)})}this.modelHeading=a,this.cameraHeading=a,this.placeChaseCamera(r,n,s,a),this.state={lng:n,lat:r,alt:s,heading:i,speed:0,animationState:this.currentAnimState,mode:this.viewMode},this.notify()}getState(){return this.state}onStateChange(e){return this.listeners.add(e),()=>this.listeners.delete(e)}onAction(e){return this.actionListeners.add(e),()=>this.actionListeners.delete(e)}setViewMode(e){this.viewMode!==e&&(this.viewMode=e,this.state={...this.state,mode:e},this.applyModelVisibility(),this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading),this.notify())}toggleViewMode(){this.setViewMode(this.viewMode===`tpv`?`fpv`:`tpv`)}setAllowCameraControl(e){this.config.allowCameraControl=e}setCollision(e){this.collision={...this.collision,...e}}getCollision(){return this.collision}setHeading(e){if(!this.view)return;let{lat:n,lng:r,alt:i}=this.state,a=(0,t.degreeToRadian)(e);if(this.modelHeading=a,this.cameraHeading=a,this.handle&&this.character){let e=(0,t.geodeticToVector3)({lat:n,lng:r,height:i});this.handle.update({matrixWorld:this.composeCharacterFrame(e,a)})}this.isFreeCamera()||this.placeChaseCamera(n,r,i,a),this.state={...this.state,heading:e},this.notify()}getHeading(){return this.state.heading}setCameraPitch(e){this.config.cameraPitch=e,this.isFreeCamera()||this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading)}getCameraPitch(){return this.config.cameraPitch}setFpvPitch(e){this.config.fpvPitch=e,this.isFreeCamera()||this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading)}getFpvPitch(){return this.config.fpvPitch}setFpvHeightOffset(e){this.config.fpvHeightOffset=e,this.isFreeCamera()||this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading)}getFpvHeightOffset(){return this.config.fpvHeightOffset}setAnimationSpeed(e){this.character&&(this.character.animation.speed=e),this.modelRef?.setAnimationSpeed(this.clipSpeed(this.currentAnimState))}getAnimationSpeed(){return this.character?.animation.speed??1}get model(){return this.handle}clipSpeed(e){let t=this.character?.animation;return t?e===t.dashClip?t.dashSpeed??t.speed??1:t.walkClip!=null&&e===t.walkClip?t.walkSpeed??t.speed??1:t.idleSpeed??t.speed??1:1}dispose(){this.releaseControl(),this.handle&&=(this.handle.delete(),null),this.modelRef=null,this.placed=!1,this.pinnedStartHeight=void 0,this.listeners.clear(),this.actionListeners.clear(),this.view=void 0}playAnimation(e){!e||!this.character||!this.modelRef||e!==this.currentAnimState&&(this.modelRef.crossFadeAnimation(this.currentAnimState??``,e,this.character.animation.crossfadeDuration),this.modelRef.setAnimationSpeed(this.clipSpeed(e)),this.currentAnimState=e)}resolveCharacter(e){return{modelUrl:e.modelUrl,animation:e.animation,modelRotationOffset:e.modelRotationOffset?{...u,...e.modelRotationOffset}:u,modelScale:e.modelScale??y,hideModelInFpv:e.hideModelInFpv??!0,castShadow:e.castShadow??!1,receiveShadow:e.receiveShadow??!1}}buildKeyMap(e){let t=new Map,n=(e,n)=>{for(let r of e)t.set(r,n)};return n(e.forward,`forward`),n(e.backward,`backward`),n(e.turnLeft,`turnLeft`),n(e.turnRight,`turnRight`),n(e.ascend,`ascend`),n(e.descend,`descend`),n(e.dash,`dash`),n(e.orbitCamera,`orbitCamera`),n(e.toggleView,`toggleView`),t}isFreeCamera(){return this.config.allowCameraControl||this.orbitKeyHeld||this.orbitLatched}isMovementAction(e){return e===`forward`||e===`backward`||e===`turnLeft`||e===`turnRight`||e===`ascend`||e===`descend`}applyModelVisibility(){if(!this.handle||!this.character)return;let e=this.viewMode===`fpv`&&this.character.hideModelInFpv;this.handle.visible=!e}applyInitialCamera(){let{startLat:e,startLng:n,startHeight:r,startHeading:i}=this.config;this.placeChaseCamera(e,n,r,(0,t.degreeToRadian)(i))}placeChaseCamera(e,n,r,i,a=0){if(!this.view)return;let{cameraDistance:o,fpvPitch:s,fpvForwardOffset:c}=this.config,l=this.viewMode===`fpv`,u=this.eyeHeightAt(r);if(l){let o=b,l=c+b,d=this.followSlopeWithPitch((0,t.degreeToRadian)(s),e,n,r,i,a),f=Math.tan(d)*o,p=this.advanceLatLng(e,n,i,l);this._offset.set(-Math.sin(i)*o,-Math.cos(i)*o,f),this.view.cameraFollow(!0,{lat:p.lat,lng:p.lng,height:u-f},this._offset);return}let d=this.followSlopeWithPitch((0,t.degreeToRadian)(this.config.cameraPitch),e,n,r,i,a),f=o*Math.cos(d),p=o*Math.sin(d);this._offset.set(-Math.sin(i)*f,-Math.cos(i)*f,p),this.view.cameraFollow(!0,{lat:e,lng:n,height:u},this._offset)}followSlopeWithPitch(e,t,r,i,a,o){let{cameraDistance:s,cameraLerpSpeed:c}=this.config,{mode:l,cameraSlopeFollow:u}=this.collision,d=0;if(l!==`off`&&u>0){let e=this.terrainAlong(t,r,a,s),o=this.terrainAlong(t,r,a+Math.PI,s);e!==void 0&&o!==void 0&&(d=n.MathUtils.clamp(Math.atan2(e-o,2*s)*u*this.groundedFactor(i,s),-h,h))}return this.cameraSlopePitch+=(d-this.cameraSlopePitch)*_(c,o),e-this.cameraSlopePitch}groundedFactor(e,t){let r=this.groundSurfaceHeight;return r===void 0?0:n.MathUtils.clamp(1-(e-r)/t,0,1)}terrainAlong(e,t,n,r){let i=this.advanceLatLng(e,t,n,r);return this.sampleTerrain(i.lat,i.lng)}composeCharacterFrame(e,r){let i=(0,t.eastNorthUpToFixedFrame)(e);this._headingMatrix.makeRotationZ(-r),this._characterFrame.multiplyMatrices(i,this._headingMatrix);let a=this.slopeGradEast,o=this.slopeGradNorth;if(a!==0||o!==0){let e=Math.sin(r),i=Math.cos(r),s=(0,t.degreeToRadian)(this.collision.maxSlopeTilt),c=n.MathUtils.clamp(Math.atan(a*e+o*i),-s,s),l=n.MathUtils.clamp(-Math.atan(a*i-o*e),-s,s);this._tiltMatrix.makeRotationX(c),this._characterFrame.multiply(this._tiltMatrix),this._tiltMatrix.makeRotationY(l),this._characterFrame.multiply(this._tiltMatrix)}return this._characterFrame}eyeHeightAt(e){return e+this.config.fpvHeightOffset}sampleTerrain(e,t){return this.view?.sampleTerrainHeight({lat:e,lng:t})}resolveGroundHeight(e,t,n,r){let{mode:i,groundOffset:a}=this.collision;if(i===`off`)return this.forgetGroundSamples(),n;let o=this.sampleTerrain(e.lat,e.lng);if(o===void 0)return this.forgetGroundSamples(),n;this.groundSurfaceHeight=o+a;let s=this.groundSurfaceHeight-this.absorbGroundDataShift(e,t,o,n,r);return i===`ground`?s:Math.max(n,s)}absorbGroundDataShift(e,t,n,r,i){let a=this.lastGroundSample,o;if(a===void 0)o=(this.sampleTerrain(t.lat,t.lng)??n)+this.collision.groundOffset-r;else{let e=this.sampleTerrain(a.lat,a.lng);o=e===void 0?0:e-a.height}let s=this.unabsorbedGroundShift+o,c=Math.sign(s);i>0&&(c!==Math.sign(this.settleVelocity)&&(this.settleVelocity=0),this.settleVelocity+=c*p*i);let l=i>0?Math.abs(this.settleVelocity)*i:Math.abs(s);return this.unabsorbedGroundShift=c*Math.max(0,Math.abs(s)-l),this.unabsorbedGroundShift===0&&(this.settleVelocity=0),this.lastGroundSample={lat:e.lat,lng:e.lng,height:n},this.unabsorbedGroundShift}forgetGroundSamples(){this.lastGroundSample=void 0,this.unabsorbedGroundShift=0,this.settleVelocity=0,this.groundSurfaceHeight=void 0}updateSlopeTilt(e,t,n,r){let{mode:i,alignToSlope:a,slopeSampleDistance:o}=this.collision,s=0,c=0;if(i!==`off`&&a){let r=this.measureSlopeGradient(e,t);if(!r)return;let i=this.groundedFactor(n,o);s=r.east*i,c=r.north*i}else if(this.slopeGradEast===0&&this.slopeGradNorth===0)return;let l=_(m,r);this.slopeGradEast+=(s-this.slopeGradEast)*l,this.slopeGradNorth+=(c-this.slopeGradNorth)*l}measureSlopeGradient(e,n){let r=this.collision.slopeSampleDistance,i=(0,t.geodeticToVector3)({lat:e,lng:n,height:0}),a=(0,t.eastNorthUpToFixedFrame)(i);this._east.setFromMatrixColumn(a,0).normalize(),this._north.setFromMatrixColumn(a,1).normalize();let o=0,s=0;for(let{sin:e,cos:n}of g){this._probePos.copy(i).addScaledVector(this._east,e*r).addScaledVector(this._north,n*r);let a=(0,t.vector3ToGeodetic)(this._probePos),c=this.sampleTerrain(a.lat,a.lng);if(c===void 0)return;o+=c*e,s+=c*n}let c=2/(g.length*r);return{east:o*c,north:s*c}}advanceLatLng(e,n,r,i){let a=(0,t.geodeticToVector3)({lat:e,lng:n,height:0}),o=(0,t.eastNorthUpToFixedFrame)(a);this._east.setFromMatrixColumn(o,0).normalize(),this._north.setFromMatrixColumn(o,1).normalize(),this._worldForward.copy(this._east).multiplyScalar(Math.sin(r)).addScaledVector(this._north,Math.cos(r)),a.addScaledVector(this._worldForward,i);let s=(0,t.vector3ToGeodetic)(a);return{lat:s.lat,lng:s.lng}}notify(){for(let e of this.listeners)e(this.state)}emitAction(e){for(let t of this.actionListeners)t(e)}onKeyDown(e){let t=e.target;if(t.tagName===`INPUT`||t.tagName===`TEXTAREA`||t.isContentEditable)return;let n=this.keyToAction.get(e.code);if(n!==void 0){if(e.repeat||this.emitAction(n),n===`toggleView`){if(e.repeat)return;this.toggleViewMode();return}if(n===`orbitCamera`){this.orbitKeyHeld=!0,this.orbitLatched=!0;return}if(!this.movementSuppressed){if(n===`dash`){this.dashHeld=!0;return}this.isMovementAction(n)&&(this.orbitLatched=!1,this.pinnedStartHeight=void 0),this.heldActions.add(n)}}}onKeyUp(e){let t=this.keyToAction.get(e.code);if(t!==void 0&&t!==`toggleView`){if(t===`orbitCamera`){this.orbitKeyHeld=!1;return}if(t===`dash`){this.dashHeld=!1;return}this.heldActions.delete(t)}}tick=e=>{if(!this.view||this.animId==null)return;this.animId=requestAnimationFrame(this.tick);let r=Math.min((e-this.lastTime)/1e3,.1);this.lastTime=e;let i=0,a=0,o=0;this.heldActions.has(`forward`)&&(a+=1),this.heldActions.has(`backward`)&&--a,this.heldActions.has(`turnLeft`)&&--i,this.heldActions.has(`turnRight`)&&(i+=1),this.heldActions.has(`ascend`)&&(o+=1),this.heldActions.has(`descend`)&&--o;let{moveSpeed:s,rotationSpeed:c,altSpeed:l,minAlt:u,maxAlt:d,cameraLerpSpeed:f}=this.config,p=this.dashHeld?this.config.dashSpeedMultiplier:1;i!==0&&(this.modelHeading+=(0,t.degreeToRadian)(c*i));let m=this._curPos;if(this.handle&&this.modelRef){let e=this.modelRef.getWorldPosition();if(!e)return;m.copy(e)}else m.copy((0,t.geodeticToVector3)({lat:this.state.lat,lng:this.state.lng,height:this.state.alt}));let h=(0,t.eastNorthUpToFixedFrame)(m);this._east.setFromMatrixColumn(h,0).normalize(),this._north.setFromMatrixColumn(h,1).normalize();let g=(0,t.vector3ToGeodetic)(m);this._worldForward.copy(this._east).multiplyScalar(Math.sin(this.modelHeading)).addScaledVector(this._north,Math.cos(this.modelHeading)),a!==0&&m.addScaledVector(this._worldForward,s*p*r*a);let v=(0,t.vector3ToGeodetic)(m),y=this.collision.mode===`ground`?g.height:n.MathUtils.clamp(g.height+o*l*r,u,d),b=this.pinnedStartHeight??this.resolveGroundHeight(v,g,y,r),x=v.lat,S=v.lng,C=b;if(this.updateSlopeTilt(v.lat,v.lng,b,r),this.handle&&this.character){let e=(0,t.geodeticToVector3)({lat:v.lat,lng:v.lng,height:b});this.handle.update({matrixWorld:this.composeCharacterFrame(e,this.modelHeading)})}let w=this.modelHeading-this.cameraHeading;if(w-=Math.round(w/(Math.PI*2))*(Math.PI*2),this.cameraHeading+=w*_(f,r),this.isFreeCamera()){if(this.viewMode===`fpv`){let e=this.advanceLatLng(x,S,this.cameraHeading,this.config.fpvForwardOffset);this.view.cameraFreeLook(!0,{lat:e.lat,lng:e.lng,height:this.eyeHeightAt(C)})}else this.view.cameraFollow(!0,{lat:x,lng:S,height:this.eyeHeightAt(C)})}else this.placeChaseCamera(x,S,C,this.cameraHeading,r);let T=a!==0||i!==0||o!==0,E=T&&this.dashHeld,D=this.currentAnimState;if(this.character){let{animation:e}=this.character,t=e.walkClip??e.idleClip;D=E?e.dashClip:T?t:e.idleClip,this.playAnimation(D)}let O={lng:S,lat:x,alt:C,heading:(0,t.radianToDegree)(this.modelHeading),speed:s*p,animationState:D,mode:this.viewMode};this.hasStateChanged(this.state,O)&&(this.state=O,this.notify())};hasStateChanged(e,t){return e.lng!==t.lng||e.lat!==t.lat||e.alt!==t.alt||e.heading!==t.heading||e.speed!==t.speed||e.animationState!==t.animationState||e.mode!==t.mode}};function S(e,t,n){e.style.transform=`translate(${t}px, ${n}px)`,e.style.willChange=`transform`}var C=1e5,w=class extends t.Plugin{view;maxDistance;positions=[];projected=new Map;listeners=new Set;boundHandler;constructor(e){super(),this.maxDistance=e?.maxDistance??C,this.boundHandler=this.handlePreRender.bind(this)}async init(e,t){this.view=e,e.on(`preRender`,this.boundHandler)}setPositions(e){this.positions=e}onUpdate(e){return this.listeners.add(e),()=>this.listeners.delete(e)}dispose(){this.view&&this.view.off(`preRender`,this.boundHandler),this.listeners.clear(),this.positions=[],this.projected.clear()}worldToScreen(e,n,r){if(!this.view)return null;let i=(0,t.geodeticToVector3)({lng:e,lat:n,height:r}),a={width:this.view.screenSize.x,height:this.view.screenSize.y,pixelRatio:this.view.pixelRatio},o=(0,t.convertWorldToScreen)(a,this.view.camera.raw,i);return o?{x:o.x,y:o.y}:null}handlePreRender(){if(!this.view)return;let e=this.view.camera.positionGeographic,n=(0,t.geodeticToVector3)({lng:e.lng,lat:e.lat,height:e.height}),r=new Map;for(let e of this.positions){let i=(0,t.geodeticToVector3)({lng:e.lng,lat:e.lat,height:e.alt}),a=n.distanceTo(i);if(a>this.maxDistance)continue;let o=this.worldToScreen(e.lng,e.lat,e.alt);o&&r.set(e.id,{...o,distance:a})}this.projected=r;let i={projected:this.projected};for(let e of this.listeners)e(i)}},T=`https://api.cesium.com/v1/assets`,E=class extends t.Plugin{view;config;endpoint;constructor(e){super(),this.config=e}async init(e,t){this.view=e;let{assetId:n,accessToken:r,endpoint:i=T}=this.config,a=`${i}/${encodeURIComponent(String(n))}/endpoint?access_token=${encodeURIComponent(r)}`,o=await fetch(a);if(!o.ok)throw Error(`CesiumIonPlugin: failed to fetch asset ${n} endpoint (${o.status} ${o.statusText})`);let s=await o.json();this.endpoint={url:s.url,accessToken:s.accessToken}}addTerrain(e={}){if(!this.view||!this.endpoint)throw Error(`CesiumIonPlugin: addTerrain() must be called after view.init().`);let{castShadow:t,receiveShadow:n,show:r,showBoundingBox:i,skirt:a,skirtExaggeration:o,...s}=e,c=this.view.addSource({type:`quantized-mesh`,url:`${this.endpoint.url}{z}/{x}/{y}.terrain`,token:this.endpoint.accessToken,...s}),l=this.view.addLayer({type:`terrain`,source:c,terrain:{castShadow:t,receiveShadow:n,show:r,showBoundingBox:i,skirt:a,skirtExaggeration:o}});return l.once(`deleted`,()=>c.delete()),l}},D=/^[23]\.\d+\.\d+$/,O=512,k=`mapbox`;function A(e){if(typeof e.tilejson!=`string`||!D.test(e.tilejson))throw Error(`TileJsonPlugin: invalid or missing "tilejson" version field (got ${JSON.stringify(e.tilejson)}).`);if(!Array.isArray(e.tiles)||e.tiles.length===0)throw Error(`TileJsonPlugin: "tiles" must be a non-empty array of URL templates.`)}function j(e){switch(e){case`mapbox`:return(0,t.MAPBOX_ELEVATION_DECODER)();case`terrarium`:return(0,t.TERRARIUM_ELEVATION_DECODER)();default:throw Error(`TileJsonPlugin: unsupported raster-dem encoding ${JSON.stringify(e)}. Supported encodings: "mapbox", "terrarium".`)}}var M=class extends t.Plugin{view;events=new t.EventHandler;credits=[];async init(e,t){this.view=e}on(e,t){this.events.on(e,t)}once(e,t){this.events.once(e,t)}off(e,t){this.events.off(e,t)}async addSource(e){let t=this.view;if(!t)throw Error(`TileJsonPlugin: addSource() must be called after view.init().`);let n=await this.fetchTileJson(e.url);A(n);let[r,...i]=n.tiles;i.length>0&&console.warn(`TileJsonPlugin: TileJSON at ${e.url} lists ${n.tiles.length} mirror tile endpoints; Navara sources use a single URL, so only the first is used.`);let a=e.minzoom??n.minzoom,o=e.maxzoom??n.maxzoom,s=e.scheme??n.scheme,c=e.id,l={url:r,...c===void 0?{}:{id:c},...o===void 0?{}:{maxZoom:o}},u={...l,...a===void 0?{}:{minZoom:a},...s===`tms`?{tms:!0}:{}},d;return d=e.type===`raster-tile`?t.addSource({type:`raster-tile`,...u}):e.type===`raster-dem`?t.addSource({type:`raster-dem`,...u,tileSize:e.tileSize??n.tileSize??O,elevationDecoder:j(e.encoding??n.encoding??k)}):t.addSource({type:`vector-tile`,...l}),n.attribution&&this.addCredit(n.attribution),this.events.emit(`loaded`,{source:d,tilejson:n,attribution:n.attribution}),d}dispose(){let e=this.view?.attribution;e&&this.credits.length>0&&e.remove(this.credits.map(e=>({attributionHtml:e}))),this.credits.length=0,this.events.clear(`loaded`),this.view=void 0}async fetchTileJson(e){let t=await fetch(e);if(!t.ok)throw Error(`TileJsonPlugin: failed to fetch TileJSON from ${e} (${t.status} ${t.statusText})`);return await t.json()}addCredit(e){this.credits.includes(e)||(this.credits.push(e),this.view?.attribution?.add([{attributionHtml:e}]))}};e.CesiumIonPlugin=E,e.OverlayPlugin=w,e.PersonViewPlugin=x,e.TileJsonPlugin=M,e.moveOverlayElement=S});
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@navaramap/three"),require("three")):typeof define==`function`&&define.amd?define([`exports`,`@navaramap/three`,`three`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.NavaraThreePlugins={},e._navaramap_three,e.three))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};t=((e,t,n)=>(n=e==null?{}:r(s(e)),l(i(n,`default`,{value:e,enumerable:!0}),e)))(t,1);var u={x:0,y:0,z:0},d={forward:[`KeyW`],backward:[`KeyS`],turnLeft:[`KeyA`],turnRight:[`KeyD`],ascend:[`ArrowUp`,`Space`],descend:[`ArrowDown`,`ControlLeft`,`ControlRight`],dash:[`ShiftLeft`,`ShiftRight`],orbitCamera:[`AltLeft`,`AltRight`],toggleView:[`KeyV`]},f={mode:`off`,groundOffset:0,alignToSlope:!0,slopeSampleDistance:4,maxSlopeTilt:45,cameraSlopeFollow:1},p=10,m=6,h=Math.PI/4,g=[0,1,2,3,4,5,6,7].map(e=>{let t=e*Math.PI/4;return{sin:Math.sin(t),cos:Math.cos(t)}}),_=(e,t)=>e>0&&t>0?1-Math.exp(-e*t):1,v={allowCameraControl:!1,initialView:`tpv`,moveSpeed:50,rotationSpeed:3,altSpeed:30,dashSpeedMultiplier:2.5,minAlt:50,maxAlt:5e3,cameraDistance:50,cameraPitch:0,cameraLerpSpeed:3,fpvForwardOffset:0,fpvHeightOffset:1,fpvPitch:0,startLat:35.6812,startLng:139.7671,startHeight:500,startHeading:234},y=3,b=1e3,x=class extends t.Plugin{view;config;collision;character;keys;keyToAction;handle=null;modelRef=null;animId=null;placed=!1;pinnedStartHeight;lastTime=0;state;listeners=new Set;actionListeners=new Set;heldActions=new Set;dashHeld=!1;orbitKeyHeld=!1;orbitLatched=!1;currentAnimState;modelHeading;cameraHeading;viewMode;movementSuppressed=!1;boundKeyDown;boundKeyUp;_curPos=new n.Vector3;_east=new n.Vector3;_north=new n.Vector3;_worldForward=new n.Vector3;_offset=new n.Vector3;_probePos=new n.Vector3;_headingMatrix=new n.Matrix4;_tiltMatrix=new n.Matrix4;_characterFrame=new n.Matrix4;cameraSlopePitch=0;slopeGradEast=0;slopeGradNorth=0;lastGroundSample;unabsorbedGroundShift=0;settleVelocity=0;groundSurfaceHeight;constructor(e={}){super();let{character:t,keys:r,collision:i,...a}=e;this.config={...v,...a},this.collision={...f,...i},this.character=t?this.resolveCharacter(t):null,this.keys={...d,...r},this.keyToAction=this.buildKeyMap(this.keys),this.modelHeading=n.MathUtils.degToRad(this.config.startHeading),this.cameraHeading=this.modelHeading,this.viewMode=this.config.initialView,this.currentAnimState=this.character?this.character.animation.idleClip:null,this.boundKeyDown=this.onKeyDown.bind(this),this.boundKeyUp=this.onKeyUp.bind(this)}async init(e,t){this.view=e,this.state={lng:this.config.startLng,lat:this.config.startLat,alt:this.config.startHeight,heading:this.config.startHeading,speed:0,animationState:this.currentAnimState,mode:this.viewMode}}async resolveStartHeight(e){let t=this.view;if(!t)return;let{startLat:n,startLng:r}=this.config,[i]=await t.sampleTerrainMostDetailed(e,[{lat:n,lng:r}]);if(i?.height===void 0)return;let{mode:a,groundOffset:o}=this.collision,s=i.height+(a===`off`?0:o);return this.config.startHeight=s,this.placed||(this.state={...this.state,alt:s},this.pinnedStartHeight=s,a!==`off`&&(this.groundSurfaceHeight=s)),s}start(){if(!this.view||this.animId!=null)return;if(this.animId=-1,document.addEventListener(`keydown`,this.boundKeyDown),document.addEventListener(`keyup`,this.boundKeyUp),this.placed){this.resumeLoop(),this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading),this.applyModelVisibility();return}this.placed=!0;let{startLat:e,startLng:n,startHeight:r,startHeading:i}=this.config,a=(0,t.geodeticToVector3)({lat:e,lng:n,height:r});if(this.modelHeading=(0,t.degreeToRadian)(i),this.cameraHeading=this.modelHeading,this.character){let{animation:e,modelRotationOffset:t,modelScale:n,castShadow:r,receiveShadow:i}=this.character;this.handle=this.view.addMesh({gltfModel:{url:this.character.modelUrl,castShadow:r,receiveShadow:i,animationEnabled:!0,animationAutoPlay:!0,animationActiveClip:e.idleClip,animationSpeed:this.clipSpeed(e.idleClip),animationLoop:!0,animationCrossfadeDuration:e.crossfadeDuration},matrixWorld:this.composeCharacterFrame(a,this.modelHeading),rotation:{x:t.x,y:t.y,z:t.z},scale:{x:n,y:n,z:n}}),this.modelRef=this.handle.ref,this.modelRef.on(`load`,()=>{this.view&&this.animId!=null&&this.resumeLoop()})}else this.resumeLoop();this.applyInitialCamera(),this.applyModelVisibility()}stop(){if(this.animId==null)return;this.releaseControl(),this.view?.cameraFollow(!1),this.view?.cameraFreeLook(!1),this.playAnimation(this.character?.animation.idleClip);let e={...this.state,speed:0,animationState:this.currentAnimState};this.hasStateChanged(this.state,e)&&(this.state=e,this.notify())}releaseControl(){this.animId!=null&&this.animId>=0&&cancelAnimationFrame(this.animId),this.animId=null,document.removeEventListener(`keydown`,this.boundKeyDown),document.removeEventListener(`keyup`,this.boundKeyUp),this.heldActions.clear(),this.orbitKeyHeld=!1,this.orbitLatched=!1,this.dashHeld=!1}resumeLoop(){this.lastTime=performance.now(),this.animId=requestAnimationFrame(this.tick)}teleport(e){if(!this.view)return;this.pinnedStartHeight=void 0;let{lng:n,lat:r}=e,i=e.heading==null?(0,t.radianToDegree)(this.cameraHeading):e.heading,a=(0,t.degreeToRadian)(i),o={lat:r,lng:n},s=this.resolveGroundHeight(o,o,e.alt,0);if(this.updateSlopeTilt(o.lat,o.lng,s,0),this.handle&&this.character){let e=(0,t.geodeticToVector3)({lat:o.lat,lng:o.lng,height:s});this.handle.update({matrixWorld:this.composeCharacterFrame(e,a)})}this.modelHeading=a,this.cameraHeading=a,this.placeChaseCamera(r,n,s,a),this.state={lng:n,lat:r,alt:s,heading:i,speed:0,animationState:this.currentAnimState,mode:this.viewMode},this.notify()}getState(){return this.state}onStateChange(e){return this.listeners.add(e),()=>this.listeners.delete(e)}onAction(e){return this.actionListeners.add(e),()=>this.actionListeners.delete(e)}setViewMode(e){this.viewMode!==e&&(this.viewMode=e,this.state={...this.state,mode:e},this.applyModelVisibility(),this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading),this.notify())}toggleViewMode(){this.setViewMode(this.viewMode===`tpv`?`fpv`:`tpv`)}setAllowCameraControl(e){this.config.allowCameraControl=e}setCollision(e){this.collision={...this.collision,...e}}getCollision(){return this.collision}setHeading(e){if(!this.view)return;let{lat:n,lng:r,alt:i}=this.state,a=(0,t.degreeToRadian)(e);if(this.modelHeading=a,this.cameraHeading=a,this.handle&&this.character){let e=(0,t.geodeticToVector3)({lat:n,lng:r,height:i});this.handle.update({matrixWorld:this.composeCharacterFrame(e,a)})}this.isFreeCamera()||this.placeChaseCamera(n,r,i,a),this.state={...this.state,heading:e},this.notify()}getHeading(){return this.state.heading}setCameraPitch(e){this.config.cameraPitch=e,this.isFreeCamera()||this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading)}getCameraPitch(){return this.config.cameraPitch}setFpvPitch(e){this.config.fpvPitch=e,this.isFreeCamera()||this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading)}getFpvPitch(){return this.config.fpvPitch}setFpvHeightOffset(e){this.config.fpvHeightOffset=e,this.isFreeCamera()||this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading)}getFpvHeightOffset(){return this.config.fpvHeightOffset}setAnimationSpeed(e){this.character&&(this.character.animation.speed=e),this.modelRef?.setAnimationSpeed(this.clipSpeed(this.currentAnimState))}getAnimationSpeed(){return this.character?.animation.speed??1}get model(){return this.handle}clipSpeed(e){let t=this.character?.animation;return t?e===t.dashClip?t.dashSpeed??t.speed??1:t.walkClip!=null&&e===t.walkClip?t.walkSpeed??t.speed??1:t.idleSpeed??t.speed??1:1}dispose(){this.releaseControl(),this.handle&&=(this.handle.delete(),null),this.modelRef=null,this.placed=!1,this.pinnedStartHeight=void 0,this.listeners.clear(),this.actionListeners.clear(),this.view=void 0}playAnimation(e){e&&this.character&&this.modelRef&&e!==this.currentAnimState&&(this.modelRef.crossFadeAnimation(this.currentAnimState??``,e,this.character.animation.crossfadeDuration),this.modelRef.setAnimationSpeed(this.clipSpeed(e)),this.currentAnimState=e)}resolveCharacter(e){return{modelUrl:e.modelUrl,animation:e.animation,modelRotationOffset:e.modelRotationOffset?{...u,...e.modelRotationOffset}:u,modelScale:e.modelScale??y,hideModelInFpv:e.hideModelInFpv??!0,castShadow:e.castShadow??!1,receiveShadow:e.receiveShadow??!1}}buildKeyMap(e){let t=new Map,n=(e,n)=>{for(let r of e)t.set(r,n)};return n(e.forward,`forward`),n(e.backward,`backward`),n(e.turnLeft,`turnLeft`),n(e.turnRight,`turnRight`),n(e.ascend,`ascend`),n(e.descend,`descend`),n(e.dash,`dash`),n(e.orbitCamera,`orbitCamera`),n(e.toggleView,`toggleView`),t}isFreeCamera(){return this.config.allowCameraControl||this.orbitKeyHeld||this.orbitLatched}isMovementAction(e){return e===`forward`||e===`backward`||e===`turnLeft`||e===`turnRight`||e===`ascend`||e===`descend`}applyModelVisibility(){if(!this.handle||!this.character)return;let e=this.viewMode===`fpv`&&this.character.hideModelInFpv;this.handle.visible=!e}applyInitialCamera(){let{startLat:e,startLng:n,startHeight:r,startHeading:i}=this.config;this.placeChaseCamera(e,n,r,(0,t.degreeToRadian)(i))}placeChaseCamera(e,n,r,i,a=0){if(!this.view)return;let{cameraDistance:o,fpvPitch:s,fpvForwardOffset:c}=this.config,l=this.viewMode===`fpv`,u=this.eyeHeightAt(r);if(l){let o=b,l=c+b,d=this.followSlopeWithPitch((0,t.degreeToRadian)(s),e,n,r,i,a),f=Math.tan(d)*o,p=this.advanceLatLng(e,n,i,l);this._offset.set(-Math.sin(i)*o,-Math.cos(i)*o,f),this.view.cameraFollow(!0,{lat:p.lat,lng:p.lng,height:u-f},this._offset);return}let d=this.followSlopeWithPitch((0,t.degreeToRadian)(this.config.cameraPitch),e,n,r,i,a),f=o*Math.cos(d),p=o*Math.sin(d);this._offset.set(-Math.sin(i)*f,-Math.cos(i)*f,p),this.view.cameraFollow(!0,{lat:e,lng:n,height:u},this._offset)}followSlopeWithPitch(e,t,r,i,a,o){let{cameraDistance:s,cameraLerpSpeed:c}=this.config,{mode:l,cameraSlopeFollow:u}=this.collision,d=0;if(l!==`off`&&u>0){let e=this.terrainAlong(t,r,a,s),o=this.terrainAlong(t,r,a+Math.PI,s);e!==void 0&&o!==void 0&&(d=n.MathUtils.clamp(Math.atan2(e-o,2*s)*u*this.groundedFactor(i,s),-h,h))}return this.cameraSlopePitch+=(d-this.cameraSlopePitch)*_(c,o),e-this.cameraSlopePitch}groundedFactor(e,t){let r=this.groundSurfaceHeight;return r===void 0?0:n.MathUtils.clamp(1-(e-r)/t,0,1)}terrainAlong(e,t,n,r){let i=this.advanceLatLng(e,t,n,r);return this.sampleTerrain(i.lat,i.lng)}composeCharacterFrame(e,r){let i=(0,t.eastNorthUpToFixedFrame)(e);this._headingMatrix.makeRotationZ(-r),this._characterFrame.multiplyMatrices(i,this._headingMatrix);let a=this.slopeGradEast,o=this.slopeGradNorth;if(a!==0||o!==0){let e=Math.sin(r),i=Math.cos(r),s=(0,t.degreeToRadian)(this.collision.maxSlopeTilt),c=n.MathUtils.clamp(Math.atan(a*e+o*i),-s,s),l=n.MathUtils.clamp(-Math.atan(a*i-o*e),-s,s);this._tiltMatrix.makeRotationX(c),this._characterFrame.multiply(this._tiltMatrix),this._tiltMatrix.makeRotationY(l),this._characterFrame.multiply(this._tiltMatrix)}return this._characterFrame}eyeHeightAt(e){return e+this.config.fpvHeightOffset}sampleTerrain(e,t){return this.view?.sampleTerrainHeight({lat:e,lng:t})}resolveGroundHeight(e,t,n,r){let{mode:i,groundOffset:a}=this.collision;if(i===`off`)return this.forgetGroundSamples(),n;let o=this.sampleTerrain(e.lat,e.lng);if(o===void 0)return this.forgetGroundSamples(),n;this.groundSurfaceHeight=o+a;let s=this.groundSurfaceHeight-this.absorbGroundDataShift(e,t,o,n,r);return i===`ground`?s:Math.max(n,s)}absorbGroundDataShift(e,t,n,r,i){let a=this.lastGroundSample,o;if(a===void 0)o=(this.sampleTerrain(t.lat,t.lng)??n)+this.collision.groundOffset-r;else{let e=this.sampleTerrain(a.lat,a.lng);o=e===void 0?0:e-a.height}let s=this.unabsorbedGroundShift+o,c=Math.sign(s);i>0&&(c!==Math.sign(this.settleVelocity)&&(this.settleVelocity=0),this.settleVelocity+=c*p*i);let l=i>0?Math.abs(this.settleVelocity)*i:Math.abs(s);return this.unabsorbedGroundShift=c*Math.max(0,Math.abs(s)-l),this.unabsorbedGroundShift===0&&(this.settleVelocity=0),this.lastGroundSample={lat:e.lat,lng:e.lng,height:n},this.unabsorbedGroundShift}forgetGroundSamples(){this.lastGroundSample=void 0,this.unabsorbedGroundShift=0,this.settleVelocity=0,this.groundSurfaceHeight=void 0}updateSlopeTilt(e,t,n,r){let{mode:i,alignToSlope:a,slopeSampleDistance:o}=this.collision,s=0,c=0;if(i!==`off`&&a){let r=this.measureSlopeGradient(e,t);if(!r)return;let i=this.groundedFactor(n,o);s=r.east*i,c=r.north*i}else if(this.slopeGradEast===0&&this.slopeGradNorth===0)return;let l=_(m,r);this.slopeGradEast+=(s-this.slopeGradEast)*l,this.slopeGradNorth+=(c-this.slopeGradNorth)*l}measureSlopeGradient(e,n){let r=this.collision.slopeSampleDistance,i=(0,t.geodeticToVector3)({lat:e,lng:n,height:0}),a=(0,t.eastNorthUpToFixedFrame)(i);this._east.setFromMatrixColumn(a,0).normalize(),this._north.setFromMatrixColumn(a,1).normalize();let o=0,s=0;for(let{sin:e,cos:n}of g){this._probePos.copy(i).addScaledVector(this._east,e*r).addScaledVector(this._north,n*r);let a=(0,t.vector3ToGeodetic)(this._probePos),c=this.sampleTerrain(a.lat,a.lng);if(c===void 0)return;o+=c*e,s+=c*n}let c=2/(g.length*r);return{east:o*c,north:s*c}}advanceLatLng(e,n,r,i){let a=(0,t.geodeticToVector3)({lat:e,lng:n,height:0}),o=(0,t.eastNorthUpToFixedFrame)(a);this._east.setFromMatrixColumn(o,0).normalize(),this._north.setFromMatrixColumn(o,1).normalize(),this._worldForward.copy(this._east).multiplyScalar(Math.sin(r)).addScaledVector(this._north,Math.cos(r)),a.addScaledVector(this._worldForward,i);let s=(0,t.vector3ToGeodetic)(a);return{lat:s.lat,lng:s.lng}}notify(){for(let e of this.listeners)e(this.state)}emitAction(e){for(let t of this.actionListeners)t(e)}onKeyDown(e){let t=e.target;if(t.tagName===`INPUT`||t.tagName===`TEXTAREA`||t.isContentEditable)return;let n=this.keyToAction.get(e.code);if(n!==void 0){if(e.repeat||this.emitAction(n),n===`toggleView`){if(e.repeat)return;this.toggleViewMode()}else n===`orbitCamera`?(this.orbitKeyHeld=!0,this.orbitLatched=!0):this.movementSuppressed||(n===`dash`?this.dashHeld=!0:(this.isMovementAction(n)&&(this.orbitLatched=!1,this.pinnedStartHeight=void 0),this.heldActions.add(n)))}}onKeyUp(e){let t=this.keyToAction.get(e.code);t!==void 0&&t!==`toggleView`&&(t===`orbitCamera`?this.orbitKeyHeld=!1:t===`dash`?this.dashHeld=!1:this.heldActions.delete(t))}tick=e=>{if(!this.view||this.animId==null)return;this.animId=requestAnimationFrame(this.tick);let r=Math.min((e-this.lastTime)/1e3,.1);this.lastTime=e;let i=0,a=0,o=0;this.heldActions.has(`forward`)&&(a+=1),this.heldActions.has(`backward`)&&--a,this.heldActions.has(`turnLeft`)&&--i,this.heldActions.has(`turnRight`)&&(i+=1),this.heldActions.has(`ascend`)&&(o+=1),this.heldActions.has(`descend`)&&--o;let{moveSpeed:s,rotationSpeed:c,altSpeed:l,minAlt:u,maxAlt:d,cameraLerpSpeed:f}=this.config,p=this.dashHeld?this.config.dashSpeedMultiplier:1;i!==0&&(this.modelHeading+=(0,t.degreeToRadian)(c*i));let m=this._curPos;if(this.handle&&this.modelRef){let e=this.modelRef.getWorldPosition();if(!e)return;m.copy(e)}else m.copy((0,t.geodeticToVector3)({lat:this.state.lat,lng:this.state.lng,height:this.state.alt}));let h=(0,t.eastNorthUpToFixedFrame)(m);this._east.setFromMatrixColumn(h,0).normalize(),this._north.setFromMatrixColumn(h,1).normalize();let g=(0,t.vector3ToGeodetic)(m);this._worldForward.copy(this._east).multiplyScalar(Math.sin(this.modelHeading)).addScaledVector(this._north,Math.cos(this.modelHeading)),a!==0&&m.addScaledVector(this._worldForward,s*p*r*a);let v=(0,t.vector3ToGeodetic)(m),y=this.collision.mode===`ground`?g.height:n.MathUtils.clamp(g.height+o*l*r,u,d),b=this.pinnedStartHeight??this.resolveGroundHeight(v,g,y,r),x=v.lat,S=v.lng,C=b;if(this.updateSlopeTilt(v.lat,v.lng,b,r),this.handle&&this.character){let e=(0,t.geodeticToVector3)({lat:v.lat,lng:v.lng,height:b});this.handle.update({matrixWorld:this.composeCharacterFrame(e,this.modelHeading)})}let w=this.modelHeading-this.cameraHeading;if(w-=Math.round(w/(Math.PI*2))*(Math.PI*2),this.cameraHeading+=w*_(f,r),this.isFreeCamera()){if(this.viewMode===`fpv`){let e=this.advanceLatLng(x,S,this.cameraHeading,this.config.fpvForwardOffset);this.view.cameraFreeLook(!0,{lat:e.lat,lng:e.lng,height:this.eyeHeightAt(C)})}else this.view.cameraFollow(!0,{lat:x,lng:S,height:this.eyeHeightAt(C)})}else this.placeChaseCamera(x,S,C,this.cameraHeading,r);let T=a!==0||i!==0||o!==0,E=T&&this.dashHeld,D=this.currentAnimState;if(this.character){let{animation:e}=this.character,t=e.walkClip??e.idleClip;D=E?e.dashClip:T?t:e.idleClip,this.playAnimation(D)}let O={lng:S,lat:x,alt:C,heading:(0,t.radianToDegree)(this.modelHeading),speed:s*p,animationState:D,mode:this.viewMode};this.hasStateChanged(this.state,O)&&(this.state=O,this.notify())};hasStateChanged(e,t){return e.lng!==t.lng||e.lat!==t.lat||e.alt!==t.alt||e.heading!==t.heading||e.speed!==t.speed||e.animationState!==t.animationState||e.mode!==t.mode}};function S(e,t,n){e.style.transform=`translate(${t}px, ${n}px)`,e.style.willChange=`transform`}var C=1e5,w=class extends t.Plugin{view;maxDistance;positions=[];projected=new Map;listeners=new Set;boundHandler;constructor(e){super(),this.maxDistance=e?.maxDistance??C,this.boundHandler=this.handlePreRender.bind(this)}async init(e,t){this.view=e,e.on(`preRender`,this.boundHandler)}setPositions(e){this.positions=e}onUpdate(e){return this.listeners.add(e),()=>this.listeners.delete(e)}dispose(){this.view&&this.view.off(`preRender`,this.boundHandler),this.listeners.clear(),this.positions=[],this.projected.clear()}worldToScreen(e,n,r){if(!this.view)return null;let i=(0,t.geodeticToVector3)({lng:e,lat:n,height:r}),a={width:this.view.screenSize.x,height:this.view.screenSize.y,pixelRatio:this.view.pixelRatio},o=(0,t.convertWorldToScreen)(a,this.view.camera.raw,i);return o?{x:o.x,y:o.y}:null}handlePreRender(){if(!this.view)return;let e=this.view.camera.positionGeographic,n=(0,t.geodeticToVector3)({lng:e.lng,lat:e.lat,height:e.height}),r=new Map;for(let e of this.positions){let i=(0,t.geodeticToVector3)({lng:e.lng,lat:e.lat,height:e.alt}),a=n.distanceTo(i);if(a>this.maxDistance)continue;let o=this.worldToScreen(e.lng,e.lat,e.alt);o&&r.set(e.id,{...o,distance:a})}this.projected=r;let i={projected:this.projected};for(let e of this.listeners)e(i)}},T=`https://api.cesium.com/v1/assets`,E=class extends t.Plugin{view;config;endpoint;constructor(e){super(),this.config=e}async init(e,t){this.view=e;let{assetId:n,accessToken:r,endpoint:i=T}=this.config,a=`${i}/${encodeURIComponent(String(n))}/endpoint?access_token=${encodeURIComponent(r)}`,o=await fetch(a);if(!o.ok)throw Error(`CesiumIonPlugin: failed to fetch asset ${n} endpoint (${o.status} ${o.statusText})`);let s=await o.json();this.endpoint={url:s.url,accessToken:s.accessToken}}addTerrain(e={}){if(!this.view||!this.endpoint)throw Error(`CesiumIonPlugin: addTerrain() must be called after view.init().`);let{castShadow:t,receiveShadow:n,show:r,showBoundingBox:i,skirt:a,skirtExaggeration:o,...s}=e,c=this.view.addSource({type:`quantized-mesh`,url:`${this.endpoint.url}{z}/{x}/{y}.terrain`,token:this.endpoint.accessToken,...s}),l=this.view.addLayer({type:`terrain`,source:c,terrain:{castShadow:t,receiveShadow:n,show:r,showBoundingBox:i,skirt:a,skirtExaggeration:o}});return l.once(`deleted`,()=>c.delete()),l}},D=/^[23]\.\d+\.\d+$/,O=512,k=`mapbox`;function A(e){if(e.tilejson!==void 0&&(typeof e.tilejson!=`string`||!D.test(e.tilejson)))throw Error(`TileJsonPlugin: invalid "tilejson" version field (got ${JSON.stringify(e.tilejson)}).`);if(!Array.isArray(e.tiles)||e.tiles.length===0)throw Error(`TileJsonPlugin: "tiles" must be a non-empty array of URL templates.`)}function j(e){switch(e){case`mapbox`:return(0,t.MAPBOX_ELEVATION_DECODER)();case`terrarium`:return(0,t.TERRARIUM_ELEVATION_DECODER)();default:throw Error(`TileJsonPlugin: unsupported raster-dem encoding ${JSON.stringify(e)}. Supported encodings: "mapbox", "terrarium".`)}}var M=class extends t.Plugin{view;events=new t.EventHandler;credits=[];async init(e,t){this.view=e}on(e,t){this.events.on(e,t)}once(e,t){this.events.once(e,t)}off(e,t){this.events.off(e,t)}async addSource(e){let t=this.view;if(!t)throw Error(`TileJsonPlugin: addSource() must be called after view.init().`);let n=await this.fetchTileJson(e.url);A(n);let[r,...i]=n.tiles;i.length>0&&console.warn(`TileJsonPlugin: TileJSON at ${e.url} lists ${n.tiles.length} mirror tile endpoints; Navara sources use a single URL, so only the first is used.`);let a=e.minzoom??n.minzoom,o=e.maxzoom??n.maxzoom,s=e.scheme??n.scheme,c=e.id,l={url:r,...c===void 0?{}:{id:c},...o===void 0?{}:{maxZoom:o}},u={...l,...a===void 0?{}:{minZoom:a},...s===`tms`?{tms:!0}:{}},d;return d=e.type===`raster-tile`?t.addSource({type:`raster-tile`,...u}):e.type===`raster-dem`?t.addSource({type:`raster-dem`,...u,tileSize:e.tileSize??n.tileSize??O,elevationDecoder:j(e.encoding??n.encoding??k)}):t.addSource({type:`vector-tile`,...l}),n.attribution&&this.addCredit(n.attribution),this.events.emit(`loaded`,{source:d,tilejson:n,attribution:n.attribution}),d}dispose(){let e=this.view?.attribution;e&&this.credits.length>0&&e.remove(this.credits.map(e=>({attributionHtml:e}))),this.credits.length=0,this.events.clear(`loaded`),this.view=void 0}async fetchTileJson(e){let t=await fetch(e);if(!t.ok)throw Error(`TileJsonPlugin: failed to fetch TileJSON from ${e} (${t.status} ${t.statusText})`);return await t.json()}addCredit(e){this.credits.includes(e)||(this.credits.push(e),this.view?.attribution?.add([{attributionHtml:e}]))}};e.CesiumIonPlugin=E,e.OverlayPlugin=w,e.PersonViewPlugin=x,e.TileJsonPlugin=M,e.moveOverlayElement=S});
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@navaramap/three-plugins",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "main": "dist/index.umd.cjs",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -19,15 +19,17 @@
19
19
  "access": "public"
20
20
  },
21
21
  "peerDependencies": {
22
- "three": ">=0.183.0",
23
- "@navaramap/three": "0.2.0",
24
- "@navaramap/three-default-plugin": "0.2.0"
22
+ "@navaramap/three": "0.3.0",
23
+ "@navaramap/three-default-descs": "0.3.0",
24
+ "@navaramap/three-default-plugin": "0.3.0",
25
+ "three": ">=0.183.0"
25
26
  },
26
27
  "devDependencies": {
27
- "@types/three": "0.185.4",
28
- "three": "0.185.1",
29
- "@navaramap/three": "0.2.0",
30
- "@navaramap/three-default-plugin": "0.2.0"
28
+ "@navaramap/three": "0.3.0",
29
+ "@navaramap/three-default-descs": "0.3.0",
30
+ "@navaramap/three-default-plugin": "0.3.0",
31
+ "@types/three": "0.186.0",
32
+ "three": "0.186.1"
31
33
  },
32
34
  "scripts": {
33
35
  "type-check": "tsc && tsc -p tsconfig.node.json",
@@ -331,6 +331,16 @@ describe("TileJsonPlugin.addSource", () => {
331
331
  ).rejects.toThrow(/tilejson.*version/i);
332
332
  });
333
333
 
334
+ it("accepts a document without a tilejson version field", async () => {
335
+ const { view } = makeFakeView();
336
+ const plugin = initPlugin(view);
337
+ stubTileJson({ tiles: ["https://a/{z}/{x}/{y}.png"] });
338
+
339
+ await expect(
340
+ plugin.addSource({ type: "raster-tile", url: DOC_URL }),
341
+ ).resolves.toBeDefined();
342
+ });
343
+
334
344
  it("rejects a missing/empty tiles array", async () => {
335
345
  const { view } = makeFakeView();
336
346
  const plugin = initPlugin(view);
@@ -96,8 +96,12 @@ export type TileJsonDemEncoding = "mapbox" | "terrarium";
96
96
  * fields (`bounds`, `center`, `grids`, ...) are ignored.
97
97
  */
98
98
  export type TileJson = {
99
- /** Semver of the TileJSON spec the document conforms to, e.g. `"3.0.0"`. */
100
- tilejson: string;
99
+ /**
100
+ * Semver of the TileJSON spec the document conforms to, e.g. `"3.0.0"`.
101
+ * Optional because many tilesets omit it (MapLibre accepts that); a value
102
+ * that is present must still be a `major.minor.patch` version.
103
+ */
104
+ tilejson?: string;
101
105
  /** Tile URL templates (`{z}/{x}/{y}`). Required and non-empty per the spec. */
102
106
  tiles: string[];
103
107
  /** Attribution/credit HTML shown through the view's attribution UI. */
@@ -212,15 +216,16 @@ export type TileJsonPluginEventMap = {
212
216
 
213
217
  /**
214
218
  * Validates the fields this plugin relies on. Throws with an actionable message
215
- * when the `tilejson` version or the `tiles` array is missing or malformed.
219
+ * when a present `tilejson` version is malformed or the `tiles` array is
220
+ * missing or empty.
216
221
  */
217
222
  function validateTileJson(doc: TileJson): void {
218
223
  if (
219
- typeof doc.tilejson !== "string" ||
220
- !TILEJSON_VERSION.test(doc.tilejson)
224
+ doc.tilejson !== undefined &&
225
+ (typeof doc.tilejson !== "string" || !TILEJSON_VERSION.test(doc.tilejson))
221
226
  ) {
222
227
  throw new Error(
223
- `TileJsonPlugin: invalid or missing "tilejson" version field (got ${JSON.stringify(doc.tilejson)}).`,
228
+ `TileJsonPlugin: invalid "tilejson" version field (got ${JSON.stringify(doc.tilejson)}).`,
224
229
  );
225
230
  }
226
231
  if (!Array.isArray(doc.tiles) || doc.tiles.length === 0) {