@ztgkzhaohao/geo-effect-kit 0.3.0 → 0.4.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/README.md +23 -1
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +417 -25
- package/dist/index.js.map +1 -1
- package/dist/scan-cone-expansion.d.ts +29 -0
- package/dist/scan-cone-expansion.d.ts.map +1 -0
- package/dist/scan-cone-expansion.js +47 -0
- package/dist/scan-cone-expansion.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { BoundingSphere, BillboardGraphics, CallbackPositionProperty, CallbackProperty, Cartesian2, Cartesian3, Cartesian4, CircleGeometry, Color, ColorMaterialProperty, ConstantPositionProperty, ConstantProperty, CustomDataSource, EllipsoidSurfaceAppearance, Event, GeometryInstance, GroundPrimitive, HeadingPitchRoll, HeadingPitchRange, HeightReference, HorizontalOrigin, Material, Math as CesiumMath, PointGraphics, PolylineArrowMaterialProperty, PolylineDashMaterialProperty, PolylineGlowMaterialProperty, PolylineOutlineMaterialProperty, PolygonGeometry, PolygonHierarchy, PostProcessStage, Primitive, Rectangle, Transforms, VerticalOrigin, } from 'cesium';
|
|
1
|
+
import { BoundingSphere, BillboardGraphics, CallbackPositionProperty, CallbackProperty, Cartesian2, Cartesian3, Cartesian4, CircleGeometry, Color, ColorMaterialProperty, ConstantPositionProperty, ConstantProperty, CustomDataSource, CylinderGeometry, EllipsoidSurfaceAppearance, Event, GeometryInstance, GroundPrimitive, HeadingPitchRoll, HeadingPitchRange, HeightReference, HorizontalOrigin, Intersect, Material, MaterialAppearance, Math as CesiumMath, Matrix4, PointGraphics, PolylineArrowMaterialProperty, PolylineDashMaterialProperty, PolylineGlowMaterialProperty, PolylineOutlineMaterialProperty, PolygonGeometry, PolygonHierarchy, PostProcessStage, Primitive, Rectangle, Transforms, VerticalOrigin, } from 'cesium';
|
|
2
2
|
import { decompressFrames, parseGIF } from 'gifuct-js';
|
|
3
|
+
import { normalizeScanConeExpansionOptions, sampleScanConeExpansionFrame } from './scan-cone-expansion.js';
|
|
3
4
|
export const GEO_RADAR_SCAN_MATERIAL_TYPE = 'GeoRadarScanMaterial';
|
|
4
5
|
export const GEO_RIPPLE_SPREAD_MATERIAL_TYPE = 'GeoRippleSpreadMaterial';
|
|
5
6
|
export const GEO_LIGHT_WALL_MATERIAL_TYPE = 'GeoLightWallMaterial';
|
|
@@ -233,6 +234,7 @@ export function normalizeScanConeOptions(options) {
|
|
|
233
234
|
pitch: finiteOr(options.pitch ?? 0, 0),
|
|
234
235
|
showOrigin: options.showOrigin ?? true,
|
|
235
236
|
visible: options.visible ?? true,
|
|
237
|
+
...(options.expansion ? { expansion: normalizeScanConeExpansionOptions(options.expansion) } : {}),
|
|
236
238
|
};
|
|
237
239
|
}
|
|
238
240
|
export function normalizeShieldDomeOptions(options) {
|
|
@@ -3120,17 +3122,42 @@ export class ScanConeEffect {
|
|
|
3120
3122
|
dataSource;
|
|
3121
3123
|
options;
|
|
3122
3124
|
coneEntity = null;
|
|
3125
|
+
conePrimitive = null;
|
|
3123
3126
|
originEntity = null;
|
|
3124
3127
|
material = null;
|
|
3128
|
+
primitiveMaterial = null;
|
|
3129
|
+
primitiveOriginScratch = new Cartesian3();
|
|
3130
|
+
primitiveHeadingPitchRollScratch = new HeadingPitchRoll();
|
|
3131
|
+
primitiveModelMatrixScratch = new Matrix4();
|
|
3132
|
+
primitiveScaleScratch = new Cartesian3();
|
|
3133
|
+
primitiveTranslationScratch = new Cartesian3();
|
|
3134
|
+
expansionState = createInitialScanConeExpansionState('idle');
|
|
3135
|
+
expansionPreviousTimestamp = null;
|
|
3136
|
+
pausedByVisibility = false;
|
|
3137
|
+
completionNotified = false;
|
|
3138
|
+
cameraFollowPlanned = false;
|
|
3139
|
+
cameraFollowCancelledByUser = false;
|
|
3140
|
+
cameraFollowListenersAttached = false;
|
|
3141
|
+
cameraFollowGeneration = 0;
|
|
3142
|
+
activeCameraFollowGeneration = null;
|
|
3143
|
+
internallyCancellingCameraFollowGeneration = null;
|
|
3125
3144
|
renderFrame = 0;
|
|
3126
3145
|
destroyed = false;
|
|
3146
|
+
handleCameraFollowUserInput = () => {
|
|
3147
|
+
if (this.activeCameraFollowGeneration === null)
|
|
3148
|
+
return;
|
|
3149
|
+
this.cameraFollowCancelledByUser = true;
|
|
3150
|
+
this.stopExpansionCameraFollow(true);
|
|
3151
|
+
};
|
|
3127
3152
|
constructor(viewer, options) {
|
|
3128
3153
|
this.viewer = viewer;
|
|
3129
3154
|
this.options = normalizeScanConeOptions(options);
|
|
3130
3155
|
this.dataSource = new CustomDataSource('geo-effect-kit-scan-cone');
|
|
3131
3156
|
this.dataSource.show = this.options.visible;
|
|
3132
3157
|
this.viewer.dataSources.add(this.dataSource);
|
|
3133
|
-
this.
|
|
3158
|
+
this.resetExpansionState();
|
|
3159
|
+
this.renderCurrentPath();
|
|
3160
|
+
this.startExpansionCameraFollow(this.getExpansionRemainingDurationMs());
|
|
3134
3161
|
this.startRenderLoop();
|
|
3135
3162
|
this.viewer.scene.requestRender();
|
|
3136
3163
|
}
|
|
@@ -3144,27 +3171,107 @@ export class ScanConeEffect {
|
|
|
3144
3171
|
showOrigin: options.showOrigin ?? this.options.showOrigin,
|
|
3145
3172
|
visible: options.visible ?? this.options.visible,
|
|
3146
3173
|
});
|
|
3147
|
-
const
|
|
3174
|
+
const previous = this.options;
|
|
3175
|
+
const modeChanged = Boolean(previous.expansion) !== Boolean(next.expansion);
|
|
3176
|
+
const restartExpansion = Boolean(previous.expansion && next.expansion) && (previous.lengthMeters !== next.lengthMeters ||
|
|
3177
|
+
previous.expansion?.maxRadiusMeters !== next.expansion?.maxRadiusMeters ||
|
|
3178
|
+
previous.expansion?.durationMs !== next.expansion?.durationMs);
|
|
3179
|
+
const cameraFollowChanged = previous.expansion?.cameraFollow !== next.expansion?.cameraFollow;
|
|
3180
|
+
const cameraTargetChanged = Boolean(previous.expansion && next.expansion) && (!positionEqual(previous.center, next.center) ||
|
|
3181
|
+
previous.heading !== next.heading ||
|
|
3182
|
+
previous.pitch !== next.pitch ||
|
|
3183
|
+
previous.speed !== next.speed);
|
|
3184
|
+
const rebuildEntities = !next.expansion && shouldRebuildScanCone(previous, next);
|
|
3185
|
+
if (modeChanged)
|
|
3186
|
+
this.stopExpansionCameraFollow(true);
|
|
3148
3187
|
this.options = next;
|
|
3149
|
-
if (
|
|
3188
|
+
if (modeChanged) {
|
|
3189
|
+
this.cameraFollowPlanned = false;
|
|
3190
|
+
this.cameraFollowCancelledByUser = false;
|
|
3191
|
+
this.resetExpansionState();
|
|
3192
|
+
this.renderCurrentPath();
|
|
3193
|
+
this.startExpansionCameraFollow(this.getExpansionRemainingDurationMs());
|
|
3194
|
+
}
|
|
3195
|
+
else if (rebuildEntities) {
|
|
3150
3196
|
this.renderEntities();
|
|
3151
3197
|
}
|
|
3152
3198
|
else {
|
|
3153
3199
|
this.applyMaterialOptions();
|
|
3154
3200
|
this.syncOriginEntity();
|
|
3201
|
+
if (restartExpansion)
|
|
3202
|
+
this.restartExpansion();
|
|
3203
|
+
else if (this.options.expansion)
|
|
3204
|
+
this.updatePrimitiveModelMatrix(getAnimationSeconds());
|
|
3205
|
+
}
|
|
3206
|
+
if (!modeChanged && !restartExpansion && cameraFollowChanged) {
|
|
3207
|
+
this.stopExpansionCameraFollow(true);
|
|
3208
|
+
this.cameraFollowPlanned = false;
|
|
3209
|
+
this.startExpansionCameraFollow(this.getExpansionRemainingDurationMs());
|
|
3210
|
+
}
|
|
3211
|
+
if (!modeChanged &&
|
|
3212
|
+
!restartExpansion &&
|
|
3213
|
+
!cameraFollowChanged &&
|
|
3214
|
+
cameraTargetChanged &&
|
|
3215
|
+
!this.cameraFollowCancelledByUser) {
|
|
3216
|
+
this.stopExpansionCameraFollow(true);
|
|
3217
|
+
this.cameraFollowPlanned = false;
|
|
3218
|
+
this.startExpansionCameraFollow(this.getExpansionRemainingDurationMs());
|
|
3155
3219
|
}
|
|
3156
3220
|
this.dataSource.show = this.options.visible;
|
|
3221
|
+
if (this.conePrimitive)
|
|
3222
|
+
this.conePrimitive.show = this.options.visible;
|
|
3223
|
+
if (!modeChanged && !restartExpansion && previous.visible !== this.options.visible) {
|
|
3224
|
+
if (this.options.visible)
|
|
3225
|
+
this.resumeExpansionAfterVisibilityPause();
|
|
3226
|
+
else
|
|
3227
|
+
this.pauseExpansionForVisibility();
|
|
3228
|
+
}
|
|
3157
3229
|
if (this.options.visible)
|
|
3158
3230
|
this.startRenderLoop();
|
|
3159
3231
|
else
|
|
3160
3232
|
this.stopRenderLoop();
|
|
3161
3233
|
this.viewer.scene.requestRender();
|
|
3162
3234
|
}
|
|
3235
|
+
restartExpansion() {
|
|
3236
|
+
if (this.destroyed || !this.options.expansion)
|
|
3237
|
+
return;
|
|
3238
|
+
this.stopExpansionCameraFollow(true);
|
|
3239
|
+
this.cameraFollowPlanned = false;
|
|
3240
|
+
this.cameraFollowCancelledByUser = false;
|
|
3241
|
+
this.expansionState = createInitialScanConeExpansionState(this.options.visible ? 'running' : 'paused');
|
|
3242
|
+
this.expansionPreviousTimestamp = null;
|
|
3243
|
+
this.pausedByVisibility = !this.options.visible;
|
|
3244
|
+
this.completionNotified = false;
|
|
3245
|
+
this.updatePrimitiveModelMatrix(getAnimationSeconds());
|
|
3246
|
+
this.startExpansionCameraFollow(this.getExpansionRemainingDurationMs());
|
|
3247
|
+
if (this.options.visible)
|
|
3248
|
+
this.startRenderLoop();
|
|
3249
|
+
this.viewer.scene.requestRender();
|
|
3250
|
+
}
|
|
3251
|
+
cancelExpansion() {
|
|
3252
|
+
if (this.destroyed || !this.options.expansion)
|
|
3253
|
+
return;
|
|
3254
|
+
if (this.expansionState.status === 'completed' || this.expansionState.status === 'cancelled')
|
|
3255
|
+
return;
|
|
3256
|
+
this.expansionState = { ...this.expansionState, status: 'cancelled' };
|
|
3257
|
+
this.expansionPreviousTimestamp = null;
|
|
3258
|
+
this.pausedByVisibility = false;
|
|
3259
|
+
this.stopExpansionCameraFollow(true);
|
|
3260
|
+
}
|
|
3261
|
+
isExpanding() {
|
|
3262
|
+
return !this.destroyed && this.expansionState.status === 'running';
|
|
3263
|
+
}
|
|
3264
|
+
getExpansionState() {
|
|
3265
|
+
return { ...this.expansionState };
|
|
3266
|
+
}
|
|
3163
3267
|
show() {
|
|
3164
3268
|
if (this.destroyed)
|
|
3165
3269
|
return;
|
|
3166
3270
|
this.options = { ...this.options, visible: true };
|
|
3167
3271
|
this.dataSource.show = true;
|
|
3272
|
+
if (this.conePrimitive)
|
|
3273
|
+
this.conePrimitive.show = true;
|
|
3274
|
+
this.resumeExpansionAfterVisibilityPause();
|
|
3168
3275
|
this.startRenderLoop();
|
|
3169
3276
|
this.viewer.scene.requestRender();
|
|
3170
3277
|
}
|
|
@@ -3173,12 +3280,19 @@ export class ScanConeEffect {
|
|
|
3173
3280
|
return;
|
|
3174
3281
|
this.options = { ...this.options, visible: false };
|
|
3175
3282
|
this.dataSource.show = false;
|
|
3283
|
+
if (this.conePrimitive)
|
|
3284
|
+
this.conePrimitive.show = false;
|
|
3285
|
+
this.pauseExpansionForVisibility();
|
|
3176
3286
|
this.stopRenderLoop();
|
|
3177
3287
|
this.viewer.scene.requestRender();
|
|
3178
3288
|
}
|
|
3179
3289
|
flyTo(options = {}) {
|
|
3180
3290
|
if (this.destroyed)
|
|
3181
3291
|
return;
|
|
3292
|
+
if (this.activeCameraFollowGeneration !== null) {
|
|
3293
|
+
this.cameraFollowCancelledByUser = true;
|
|
3294
|
+
this.stopExpansionCameraFollow(true);
|
|
3295
|
+
}
|
|
3182
3296
|
const center = this.getOriginCartesian();
|
|
3183
3297
|
const radius = Math.max(this.options.radiusMeters, this.options.lengthMeters);
|
|
3184
3298
|
this.viewer.camera.flyToBoundingSphere(new BoundingSphere(center, radius), {
|
|
@@ -3189,12 +3303,15 @@ export class ScanConeEffect {
|
|
|
3189
3303
|
destroy() {
|
|
3190
3304
|
if (this.destroyed)
|
|
3191
3305
|
return;
|
|
3306
|
+
this.stopExpansionCameraFollow(true);
|
|
3192
3307
|
this.destroyed = true;
|
|
3193
3308
|
this.stopRenderLoop();
|
|
3309
|
+
this.removeConePrimitive();
|
|
3194
3310
|
this.dataSource.entities.removeAll();
|
|
3195
3311
|
this.coneEntity = null;
|
|
3196
3312
|
this.originEntity = null;
|
|
3197
3313
|
this.material = null;
|
|
3314
|
+
this.primitiveMaterial = null;
|
|
3198
3315
|
this.viewer.dataSources.remove(this.dataSource, true);
|
|
3199
3316
|
this.viewer.scene.requestRender();
|
|
3200
3317
|
}
|
|
@@ -3208,9 +3325,11 @@ export class ScanConeEffect {
|
|
|
3208
3325
|
return {
|
|
3209
3326
|
...this.options,
|
|
3210
3327
|
center: { ...this.options.center },
|
|
3328
|
+
...(this.options.expansion ? { expansion: { ...this.options.expansion } } : {}),
|
|
3211
3329
|
};
|
|
3212
3330
|
}
|
|
3213
3331
|
renderEntities() {
|
|
3332
|
+
this.removeConePrimitive();
|
|
3214
3333
|
clearEntities(this.dataSource);
|
|
3215
3334
|
this.originEntity = null;
|
|
3216
3335
|
this.material = createScanConeMaterialProperty(this.options);
|
|
@@ -3230,14 +3349,48 @@ export class ScanConeEffect {
|
|
|
3230
3349
|
});
|
|
3231
3350
|
this.syncOriginEntity();
|
|
3232
3351
|
}
|
|
3352
|
+
renderCurrentPath() {
|
|
3353
|
+
if (this.options.expansion)
|
|
3354
|
+
this.renderPrimitive();
|
|
3355
|
+
else
|
|
3356
|
+
this.renderEntities();
|
|
3357
|
+
}
|
|
3358
|
+
renderPrimitive() {
|
|
3359
|
+
clearEntities(this.dataSource);
|
|
3360
|
+
this.coneEntity = null;
|
|
3361
|
+
this.originEntity = null;
|
|
3362
|
+
this.material = null;
|
|
3363
|
+
this.removeConePrimitive();
|
|
3364
|
+
registerScanConeMaterial();
|
|
3365
|
+
this.primitiveMaterial = Material.fromType(GEO_SCAN_CONE_MATERIAL_TYPE, createScanConeMaterialUniforms(this.options));
|
|
3366
|
+
this.conePrimitive = this.viewer.scene.primitives.add(new Primitive({
|
|
3367
|
+
geometryInstances: new GeometryInstance({
|
|
3368
|
+
id: 'geo-effect-kit-scan-cone-volume',
|
|
3369
|
+
geometry: new CylinderGeometry({
|
|
3370
|
+
length: 1,
|
|
3371
|
+
topRadius: 0,
|
|
3372
|
+
bottomRadius: 1,
|
|
3373
|
+
slices: 128,
|
|
3374
|
+
vertexFormat: MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat,
|
|
3375
|
+
}),
|
|
3376
|
+
}),
|
|
3377
|
+
appearance: new MaterialAppearance({
|
|
3378
|
+
material: this.primitiveMaterial,
|
|
3379
|
+
translucent: true,
|
|
3380
|
+
closed: false,
|
|
3381
|
+
faceForward: true,
|
|
3382
|
+
}),
|
|
3383
|
+
asynchronous: false,
|
|
3384
|
+
modelMatrix: this.createPrimitiveModelMatrix(getAnimationSeconds()),
|
|
3385
|
+
show: this.options.visible,
|
|
3386
|
+
}));
|
|
3387
|
+
this.syncOriginEntity();
|
|
3388
|
+
}
|
|
3233
3389
|
applyMaterialOptions() {
|
|
3234
|
-
if (
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
this.material.uniforms.speed = this.options.speed;
|
|
3239
|
-
this.material.uniforms.coneType = getScanConeTypeUniform(this.options.type);
|
|
3240
|
-
this.material.uniforms.aperture = this.options.aperture;
|
|
3390
|
+
if (this.material)
|
|
3391
|
+
applyScanConeMaterialUniforms(this.material.uniforms, this.options);
|
|
3392
|
+
if (this.primitiveMaterial)
|
|
3393
|
+
applyScanConeMaterialUniforms(this.primitiveMaterial.uniforms, this.options);
|
|
3241
3394
|
}
|
|
3242
3395
|
syncOriginEntity() {
|
|
3243
3396
|
if (!this.options.showOrigin) {
|
|
@@ -3267,8 +3420,8 @@ export class ScanConeEffect {
|
|
|
3267
3420
|
if (this.originEntity.point)
|
|
3268
3421
|
this.originEntity.point.color = new ConstantProperty(color);
|
|
3269
3422
|
}
|
|
3270
|
-
getOriginCartesian() {
|
|
3271
|
-
return Cartesian3.fromDegrees(this.options.center.longitude, this.options.center.latitude, this.options.center.height ?? 0);
|
|
3423
|
+
getOriginCartesian(result) {
|
|
3424
|
+
return Cartesian3.fromDegrees(this.options.center.longitude, this.options.center.latitude, this.options.center.height ?? 0, undefined, result);
|
|
3272
3425
|
}
|
|
3273
3426
|
getConeCenterCartesian() {
|
|
3274
3427
|
return Cartesian3.fromDegrees(this.options.center.longitude, this.options.center.latitude, (this.options.center.height ?? 0) + this.options.lengthMeters / 2);
|
|
@@ -3281,13 +3434,32 @@ export class ScanConeEffect {
|
|
|
3281
3434
|
startRenderLoop() {
|
|
3282
3435
|
if (this.renderFrame || !this.options.visible || typeof window === 'undefined')
|
|
3283
3436
|
return;
|
|
3284
|
-
const tick = () => {
|
|
3437
|
+
const tick = (timestamp) => {
|
|
3438
|
+
this.renderFrame = 0;
|
|
3285
3439
|
if (this.destroyed || !this.options.visible) {
|
|
3286
|
-
this.renderFrame = 0;
|
|
3287
3440
|
return;
|
|
3288
3441
|
}
|
|
3289
|
-
|
|
3290
|
-
|
|
3442
|
+
try {
|
|
3443
|
+
const animationSeconds = Number.isFinite(timestamp) ? timestamp / 1000 : 0;
|
|
3444
|
+
if (this.primitiveMaterial)
|
|
3445
|
+
this.primitiveMaterial.uniforms.timeSeconds = animationSeconds;
|
|
3446
|
+
const modelMatrixUpdated = this.advanceExpansion(timestamp);
|
|
3447
|
+
if (this.destroyed || !this.options.visible)
|
|
3448
|
+
return;
|
|
3449
|
+
if (this.conePrimitive && !modelMatrixUpdated)
|
|
3450
|
+
this.updatePrimitiveModelMatrix(animationSeconds);
|
|
3451
|
+
}
|
|
3452
|
+
finally {
|
|
3453
|
+
if (!this.destroyed && this.options.visible) {
|
|
3454
|
+
try {
|
|
3455
|
+
this.viewer.scene.requestRender();
|
|
3456
|
+
}
|
|
3457
|
+
finally {
|
|
3458
|
+
if (!this.renderFrame)
|
|
3459
|
+
this.renderFrame = window.requestAnimationFrame(tick);
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3291
3463
|
};
|
|
3292
3464
|
this.renderFrame = window.requestAnimationFrame(tick);
|
|
3293
3465
|
}
|
|
@@ -3299,6 +3471,210 @@ export class ScanConeEffect {
|
|
|
3299
3471
|
window.cancelAnimationFrame(this.renderFrame);
|
|
3300
3472
|
this.renderFrame = 0;
|
|
3301
3473
|
}
|
|
3474
|
+
resetExpansionState() {
|
|
3475
|
+
if (!this.options.expansion) {
|
|
3476
|
+
this.expansionState = createInitialScanConeExpansionState('idle');
|
|
3477
|
+
this.expansionPreviousTimestamp = null;
|
|
3478
|
+
this.pausedByVisibility = false;
|
|
3479
|
+
this.completionNotified = false;
|
|
3480
|
+
return;
|
|
3481
|
+
}
|
|
3482
|
+
const shouldRun = this.options.expansion.autoStart;
|
|
3483
|
+
this.expansionState = createInitialScanConeExpansionState(shouldRun ? (this.options.visible ? 'running' : 'paused') : 'idle');
|
|
3484
|
+
this.expansionPreviousTimestamp = null;
|
|
3485
|
+
this.pausedByVisibility = shouldRun && !this.options.visible;
|
|
3486
|
+
this.completionNotified = false;
|
|
3487
|
+
}
|
|
3488
|
+
advanceExpansion(timestamp) {
|
|
3489
|
+
const expansion = this.options.expansion;
|
|
3490
|
+
if (!expansion || this.expansionState.status !== 'running')
|
|
3491
|
+
return false;
|
|
3492
|
+
const safeTimestamp = Number.isFinite(timestamp) ? timestamp : 0;
|
|
3493
|
+
const previousTimestamp = this.expansionPreviousTimestamp;
|
|
3494
|
+
const elapsedDelta = previousTimestamp === null ? 0 : Math.max(0, safeTimestamp - previousTimestamp);
|
|
3495
|
+
this.expansionPreviousTimestamp = previousTimestamp === null
|
|
3496
|
+
? safeTimestamp
|
|
3497
|
+
: Math.max(previousTimestamp, safeTimestamp);
|
|
3498
|
+
const frame = sampleScanConeExpansionFrame(expansion, this.options.lengthMeters, this.expansionState.elapsedMs + elapsedDelta);
|
|
3499
|
+
const completed = frame.elapsedMs >= expansion.durationMs;
|
|
3500
|
+
this.expansionState = {
|
|
3501
|
+
...frame,
|
|
3502
|
+
status: completed ? 'completed' : 'running',
|
|
3503
|
+
};
|
|
3504
|
+
this.updatePrimitiveModelMatrix(safeTimestamp / 1000);
|
|
3505
|
+
const callbackState = { ...this.expansionState };
|
|
3506
|
+
const notifyCompletion = completed && !this.completionNotified;
|
|
3507
|
+
if (notifyCompletion)
|
|
3508
|
+
this.completionNotified = true;
|
|
3509
|
+
if (completed)
|
|
3510
|
+
this.detachCameraFollowListeners();
|
|
3511
|
+
try {
|
|
3512
|
+
expansion.onFrame?.(callbackState);
|
|
3513
|
+
}
|
|
3514
|
+
finally {
|
|
3515
|
+
if (!this.destroyed && notifyCompletion)
|
|
3516
|
+
expansion.onComplete?.({ ...callbackState });
|
|
3517
|
+
}
|
|
3518
|
+
return true;
|
|
3519
|
+
}
|
|
3520
|
+
pauseExpansionForVisibility() {
|
|
3521
|
+
if (!this.options.expansion || this.expansionState.status !== 'running')
|
|
3522
|
+
return;
|
|
3523
|
+
this.expansionState = { ...this.expansionState, status: 'paused' };
|
|
3524
|
+
this.expansionPreviousTimestamp = null;
|
|
3525
|
+
this.pausedByVisibility = true;
|
|
3526
|
+
this.stopExpansionCameraFollow(true);
|
|
3527
|
+
this.cameraFollowPlanned = false;
|
|
3528
|
+
}
|
|
3529
|
+
resumeExpansionAfterVisibilityPause() {
|
|
3530
|
+
if (!this.options.expansion || this.expansionState.status !== 'paused' || !this.pausedByVisibility)
|
|
3531
|
+
return;
|
|
3532
|
+
this.expansionState = { ...this.expansionState, status: 'running' };
|
|
3533
|
+
this.expansionPreviousTimestamp = null;
|
|
3534
|
+
this.pausedByVisibility = false;
|
|
3535
|
+
this.startExpansionCameraFollow(this.getExpansionRemainingDurationMs());
|
|
3536
|
+
}
|
|
3537
|
+
getExpansionRemainingDurationMs() {
|
|
3538
|
+
if (!this.options.expansion)
|
|
3539
|
+
return 0;
|
|
3540
|
+
return Math.max(0, this.options.expansion.durationMs - this.expansionState.elapsedMs);
|
|
3541
|
+
}
|
|
3542
|
+
startExpansionCameraFollow(durationMs) {
|
|
3543
|
+
const expansion = this.options.expansion;
|
|
3544
|
+
if (!expansion?.cameraFollow ||
|
|
3545
|
+
!this.options.visible ||
|
|
3546
|
+
this.expansionState.status !== 'running' ||
|
|
3547
|
+
this.cameraFollowPlanned ||
|
|
3548
|
+
this.cameraFollowCancelledByUser ||
|
|
3549
|
+
durationMs <= 0)
|
|
3550
|
+
return;
|
|
3551
|
+
this.cameraFollowPlanned = true;
|
|
3552
|
+
const camera = this.viewer.camera;
|
|
3553
|
+
let sphere;
|
|
3554
|
+
let visibility;
|
|
3555
|
+
try {
|
|
3556
|
+
sphere = this.createFinalExpansionBoundingSphere(durationMs);
|
|
3557
|
+
const position = camera.positionWC ?? camera.position;
|
|
3558
|
+
const direction = camera.directionWC ?? camera.direction;
|
|
3559
|
+
const up = camera.upWC ?? camera.up;
|
|
3560
|
+
visibility = camera.frustum
|
|
3561
|
+
.computeCullingVolume(position, direction, up)
|
|
3562
|
+
.computeVisibility(sphere);
|
|
3563
|
+
}
|
|
3564
|
+
catch {
|
|
3565
|
+
return;
|
|
3566
|
+
}
|
|
3567
|
+
if (visibility === Intersect.INSIDE)
|
|
3568
|
+
return;
|
|
3569
|
+
const heading = Number.isFinite(camera.heading) ? camera.heading : 0;
|
|
3570
|
+
const pitch = Number.isFinite(camera.pitch)
|
|
3571
|
+
? clamp(camera.pitch, -CesiumMath.PI_OVER_TWO + 0.01, CesiumMath.PI_OVER_TWO - 0.01)
|
|
3572
|
+
: -CesiumMath.PI_OVER_FOUR;
|
|
3573
|
+
const framedSphere = new BoundingSphere(sphere.center, sphere.radius * 1.18);
|
|
3574
|
+
const generation = ++this.cameraFollowGeneration;
|
|
3575
|
+
this.activeCameraFollowGeneration = generation;
|
|
3576
|
+
this.attachCameraFollowListeners();
|
|
3577
|
+
try {
|
|
3578
|
+
camera.flyToBoundingSphere(framedSphere, {
|
|
3579
|
+
duration: durationMs / 1000,
|
|
3580
|
+
offset: new HeadingPitchRange(heading, pitch, 0),
|
|
3581
|
+
complete: () => this.releaseExpansionCameraFollow(generation),
|
|
3582
|
+
cancel: () => this.handleExpansionCameraFollowCancelled(generation),
|
|
3583
|
+
});
|
|
3584
|
+
}
|
|
3585
|
+
catch {
|
|
3586
|
+
this.releaseExpansionCameraFollow(generation);
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
createFinalExpansionBoundingSphere(durationMs) {
|
|
3590
|
+
const expansion = this.options.expansion;
|
|
3591
|
+
const radius = expansion?.maxRadiusMeters ?? this.options.radiusMeters;
|
|
3592
|
+
const length = this.options.lengthMeters;
|
|
3593
|
+
const finalHeading = this.options.heading +
|
|
3594
|
+
(getAnimationSeconds() + durationMs / 1000) * this.options.speed * 36;
|
|
3595
|
+
const headingPitchRoll = HeadingPitchRoll.fromDegrees(finalHeading, this.options.pitch, 0);
|
|
3596
|
+
const transform = Transforms.headingPitchRollToFixedFrame(this.getOriginCartesian(), headingPitchRoll);
|
|
3597
|
+
const localCenter = new Cartesian3(0, 0, length / 2);
|
|
3598
|
+
const center = Matrix4.multiplyByPoint(transform, localCenter, new Cartesian3());
|
|
3599
|
+
return new BoundingSphere(center, Math.hypot(radius, length / 2));
|
|
3600
|
+
}
|
|
3601
|
+
attachCameraFollowListeners() {
|
|
3602
|
+
if (this.cameraFollowListenersAttached)
|
|
3603
|
+
return;
|
|
3604
|
+
this.cameraFollowListenersAttached = true;
|
|
3605
|
+
this.viewer.canvas.addEventListener('pointerdown', this.handleCameraFollowUserInput);
|
|
3606
|
+
this.viewer.canvas.addEventListener('wheel', this.handleCameraFollowUserInput);
|
|
3607
|
+
this.viewer.canvas.addEventListener('touchstart', this.handleCameraFollowUserInput);
|
|
3608
|
+
}
|
|
3609
|
+
stopExpansionCameraFollow(cancelFlight) {
|
|
3610
|
+
const generation = this.activeCameraFollowGeneration;
|
|
3611
|
+
if (generation === null) {
|
|
3612
|
+
this.detachCameraFollowListeners();
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3615
|
+
if (!cancelFlight) {
|
|
3616
|
+
this.releaseExpansionCameraFollow(generation);
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3619
|
+
this.internallyCancellingCameraFollowGeneration = generation;
|
|
3620
|
+
try {
|
|
3621
|
+
this.viewer.camera.cancelFlight();
|
|
3622
|
+
}
|
|
3623
|
+
catch {
|
|
3624
|
+
// Camera follow is optional; camera failures must not stop the effect lifecycle.
|
|
3625
|
+
}
|
|
3626
|
+
finally {
|
|
3627
|
+
this.releaseExpansionCameraFollow(generation);
|
|
3628
|
+
if (this.internallyCancellingCameraFollowGeneration === generation) {
|
|
3629
|
+
this.internallyCancellingCameraFollowGeneration = null;
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
handleExpansionCameraFollowCancelled(generation) {
|
|
3634
|
+
if (this.activeCameraFollowGeneration !== generation)
|
|
3635
|
+
return;
|
|
3636
|
+
if (this.internallyCancellingCameraFollowGeneration !== generation) {
|
|
3637
|
+
this.cameraFollowCancelledByUser = true;
|
|
3638
|
+
}
|
|
3639
|
+
this.releaseExpansionCameraFollow(generation);
|
|
3640
|
+
}
|
|
3641
|
+
releaseExpansionCameraFollow(generation) {
|
|
3642
|
+
if (this.activeCameraFollowGeneration !== generation)
|
|
3643
|
+
return;
|
|
3644
|
+
this.activeCameraFollowGeneration = null;
|
|
3645
|
+
this.detachCameraFollowListeners();
|
|
3646
|
+
}
|
|
3647
|
+
detachCameraFollowListeners() {
|
|
3648
|
+
if (!this.cameraFollowListenersAttached)
|
|
3649
|
+
return;
|
|
3650
|
+
this.viewer.canvas.removeEventListener('pointerdown', this.handleCameraFollowUserInput);
|
|
3651
|
+
this.viewer.canvas.removeEventListener('wheel', this.handleCameraFollowUserInput);
|
|
3652
|
+
this.viewer.canvas.removeEventListener('touchstart', this.handleCameraFollowUserInput);
|
|
3653
|
+
this.cameraFollowListenersAttached = false;
|
|
3654
|
+
}
|
|
3655
|
+
createPrimitiveModelMatrix(animationSeconds, result = this.primitiveModelMatrixScratch) {
|
|
3656
|
+
const heading = this.options.heading + animationSeconds * this.options.speed * 36;
|
|
3657
|
+
HeadingPitchRoll.fromDegrees(heading, this.options.pitch, 0, this.primitiveHeadingPitchRollScratch);
|
|
3658
|
+
Transforms.headingPitchRollToFixedFrame(this.getOriginCartesian(this.primitiveOriginScratch), this.primitiveHeadingPitchRollScratch, undefined, undefined, result);
|
|
3659
|
+
const radius = Math.max(0.000001, this.expansionState.radiusMeters);
|
|
3660
|
+
const length = Math.max(0.000001, this.expansionState.lengthMeters);
|
|
3661
|
+
Cartesian3.fromElements(0, 0, length / 2, this.primitiveTranslationScratch);
|
|
3662
|
+
Cartesian3.fromElements(radius, radius, length, this.primitiveScaleScratch);
|
|
3663
|
+
Matrix4.multiplyByTranslation(result, this.primitiveTranslationScratch, result);
|
|
3664
|
+
return Matrix4.multiplyByScale(result, this.primitiveScaleScratch, result);
|
|
3665
|
+
}
|
|
3666
|
+
updatePrimitiveModelMatrix(animationSeconds) {
|
|
3667
|
+
if (!this.conePrimitive)
|
|
3668
|
+
return;
|
|
3669
|
+
this.createPrimitiveModelMatrix(animationSeconds, this.conePrimitive.modelMatrix);
|
|
3670
|
+
}
|
|
3671
|
+
removeConePrimitive() {
|
|
3672
|
+
if (!this.conePrimitive)
|
|
3673
|
+
return;
|
|
3674
|
+
this.viewer.scene.primitives.remove(this.conePrimitive);
|
|
3675
|
+
this.conePrimitive = null;
|
|
3676
|
+
this.primitiveMaterial = null;
|
|
3677
|
+
}
|
|
3302
3678
|
}
|
|
3303
3679
|
export class ShieldDomeEffect {
|
|
3304
3680
|
viewer;
|
|
@@ -3635,14 +4011,21 @@ function createLightWallMaterialProperty(options) {
|
|
|
3635
4011
|
export function createScanConeMaterialProperty(options) {
|
|
3636
4012
|
const normalized = normalizeScanConeOptions(options);
|
|
3637
4013
|
registerScanConeMaterial();
|
|
3638
|
-
return new DynamicCesiumMaterialProperty(GEO_SCAN_CONE_MATERIAL_TYPE,
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
4014
|
+
return new DynamicCesiumMaterialProperty(GEO_SCAN_CONE_MATERIAL_TYPE, createScanConeMaterialUniforms(normalized));
|
|
4015
|
+
}
|
|
4016
|
+
function createScanConeMaterialUniforms(options, timeSeconds = -1) {
|
|
4017
|
+
return {
|
|
4018
|
+
color: Color.fromCssColorString(options.color).withAlpha(1),
|
|
4019
|
+
opacity: options.opacity,
|
|
4020
|
+
speed: options.speed,
|
|
4021
|
+
timeSeconds,
|
|
4022
|
+
coneType: getScanConeTypeUniform(options.type),
|
|
4023
|
+
aperture: options.aperture,
|
|
4024
|
+
};
|
|
4025
|
+
}
|
|
4026
|
+
function applyScanConeMaterialUniforms(uniforms, options) {
|
|
4027
|
+
const timeSeconds = typeof uniforms.timeSeconds === 'number' ? uniforms.timeSeconds : -1;
|
|
4028
|
+
Object.assign(uniforms, createScanConeMaterialUniforms(options, timeSeconds));
|
|
3646
4029
|
}
|
|
3647
4030
|
function createShieldDomeMaterialProperty(options) {
|
|
3648
4031
|
registerShieldDomeMaterial();
|
|
@@ -3997,6 +4380,15 @@ function getConeBottomRadius(options) {
|
|
|
3997
4380
|
const apertureRadius = Math.tan(CesiumMath.toRadians(options.aperture) / 2) * options.lengthMeters;
|
|
3998
4381
|
return Math.max(options.radiusMeters, apertureRadius);
|
|
3999
4382
|
}
|
|
4383
|
+
function createInitialScanConeExpansionState(status) {
|
|
4384
|
+
return {
|
|
4385
|
+
status,
|
|
4386
|
+
progress: 0,
|
|
4387
|
+
radiusMeters: 0,
|
|
4388
|
+
lengthMeters: 0,
|
|
4389
|
+
elapsedMs: 0,
|
|
4390
|
+
};
|
|
4391
|
+
}
|
|
4000
4392
|
function normalizePositions(positions, close) {
|
|
4001
4393
|
const normalized = (positions.length ? positions : [{ longitude: 0, latitude: 0 }]).map(normalizePosition);
|
|
4002
4394
|
const onlyPosition = normalized[0];
|