@navaramap/three-plugins 0.2.1 → 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 +6 -2
- package/dist/index.js +7 -31
- package/dist/index.umd.cjs +1 -1
- package/package.json +10 -10
- package/src/TileJsonPlugin.test.ts +10 -0
- package/src/TileJsonPlugin.ts +11 -6
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
|
-
/**
|
|
638
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 };
|
package/dist/index.umd.cjs
CHANGED
|
@@ -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.
|
|
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,17 +19,17 @@
|
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
|
-
"three": "
|
|
23
|
-
"@navaramap/three": "0.
|
|
24
|
-
"@navaramap/three-default-plugin": "0.
|
|
25
|
-
"
|
|
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"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@
|
|
29
|
-
"three": "0.
|
|
30
|
-
"@navaramap/three": "0.
|
|
31
|
-
"@
|
|
32
|
-
"
|
|
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"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
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);
|
package/src/TileJsonPlugin.ts
CHANGED
|
@@ -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
|
-
/**
|
|
100
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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) {
|