s3m-three 0.3.9 → 0.3.11

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.
Files changed (36) hide show
  1. package/dist/browser/s3m-three.iife.ext-three.js +174 -40
  2. package/dist/browser/s3m-three.iife.js +174 -40
  3. package/dist/core/types.d.ts +16 -0
  4. package/dist/core/types.d.ts.map +1 -1
  5. package/dist/index.d.ts +3 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/render/EdgeLines.d.ts +12 -0
  10. package/dist/render/EdgeLines.d.ts.map +1 -1
  11. package/dist/render/EdgeLines.js +60 -6
  12. package/dist/render/EdgeLines.js.map +1 -1
  13. package/dist/render/FeatureStyleManager.d.ts +33 -0
  14. package/dist/render/FeatureStyleManager.d.ts.map +1 -1
  15. package/dist/render/FeatureStyleManager.js +98 -11
  16. package/dist/render/FeatureStyleManager.js.map +1 -1
  17. package/dist/render/GPUPathManager.d.ts +95 -0
  18. package/dist/render/GPUPathManager.d.ts.map +1 -0
  19. package/dist/render/GPUPathManager.js +265 -0
  20. package/dist/render/GPUPathManager.js.map +1 -0
  21. package/dist/render/S3MViewer.d.ts +47 -0
  22. package/dist/render/S3MViewer.d.ts.map +1 -1
  23. package/dist/render/S3MViewer.js +104 -3
  24. package/dist/render/S3MViewer.js.map +1 -1
  25. package/dist/render/StyleMaterial.d.ts +12 -0
  26. package/dist/render/StyleMaterial.d.ts.map +1 -1
  27. package/dist/render/StyleMaterial.js +117 -14
  28. package/dist/render/StyleMaterial.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/core/types.ts +8 -0
  31. package/src/index.ts +3 -1
  32. package/src/render/EdgeLines.ts +64 -6
  33. package/src/render/FeatureStyleManager.ts +130 -11
  34. package/src/render/GPUPathManager.ts +306 -0
  35. package/src/render/S3MViewer.ts +119 -2
  36. package/src/render/StyleMaterial.ts +121 -14
@@ -0,0 +1,306 @@
1
+ /**
2
+ * GPU 路径动画管理器
3
+ *
4
+ * 负责:
5
+ * 1. 注册与管理多条三维离散路径(Path)
6
+ * 2. 将空间折线等弧长采样并烘焙为 Float32Array 格式的 2D 数据纹理 (uPathTex):
7
+ * - Width: SAMPLES_PER_PATH (默认 256)
8
+ * - Height: max(pathCount, 1)
9
+ * - Format: RGBAFormat, FloatType
10
+ * - 像素通道:
11
+ * - R: X 坐标 (米)
12
+ * - G: Y 坐标 (米)
13
+ * - B: Z 坐标 (米)
14
+ * - A: 切线偏航角 yaw (弧度, atan2(dy, dx), 经过连续角度展开 unwrap)
15
+ * 3. 硬件双线性插值 (LinearFilter) 支持:
16
+ * GPU 在顶点着色器中按归一化进度 t (0~1) 采样,硬件自动插值获得平滑轨迹与朝向
17
+ */
18
+ import * as THREE from 'three';
19
+ import { Path } from '../anim/Path.js';
20
+
21
+ export const SAMPLES_PER_PATH = 256;
22
+
23
+ export interface GPUPathInfo {
24
+ id: number;
25
+ name?: string;
26
+ path: Path;
27
+ }
28
+
29
+ export class GPUPathManager {
30
+ private _paths: Map<number, GPUPathInfo> = new Map();
31
+ private _nextId = 0;
32
+ private _texture: THREE.DataTexture | null = null;
33
+ private _data: Float32Array | null = null;
34
+ private _dirty = false;
35
+
36
+ /** 默认空纹理,防止 WebGL 未绑定报错 */
37
+ static readonly DUMMY_PATH_TEX: THREE.DataTexture = (() => {
38
+ const data = new Float32Array(SAMPLES_PER_PATH * 1 * 4);
39
+ const tex = new THREE.DataTexture(data, SAMPLES_PER_PATH, 1, THREE.RGBAFormat, THREE.FloatType);
40
+ tex.minFilter = THREE.LinearFilter;
41
+ tex.magFilter = THREE.LinearFilter;
42
+ tex.wrapS = THREE.ClampToEdgeWrapping;
43
+ tex.wrapT = THREE.ClampToEdgeWrapping;
44
+ tex.needsUpdate = true;
45
+ return tex;
46
+ })();
47
+
48
+ constructor() {}
49
+
50
+ /**
51
+ * 注册一条路径到 GPU 路径库
52
+ * @param pointsOrPath 折线点数组或现有的 Path 实例
53
+ * @param opts 注册选项(可指定固定 id 或 name)
54
+ * @returns 分配的路径 ID (从 0 开始的连续或离散索引)
55
+ */
56
+ registerPath(
57
+ pointsOrPath: Path | ArrayLike<number> | Array<[number, number, number]>,
58
+ opts: { id?: number; name?: string; closed?: boolean } = {}
59
+ ): number {
60
+ const path =
61
+ pointsOrPath instanceof Path
62
+ ? pointsOrPath
63
+ : new Path(pointsOrPath, { closed: opts.closed });
64
+
65
+ let id = opts.id;
66
+ if (id == null) {
67
+ id = this._nextId++;
68
+ while (this._paths.has(id)) {
69
+ id = this._nextId++;
70
+ }
71
+ } else {
72
+ if (id >= this._nextId) {
73
+ this._nextId = id + 1;
74
+ }
75
+ }
76
+
77
+ this._paths.set(id, { id, name: opts.name, path });
78
+ this._dirty = true;
79
+ return id;
80
+ }
81
+
82
+ /**
83
+ * 注销一条路径
84
+ */
85
+ unregisterPath(id: number): boolean {
86
+ const removed = this._paths.delete(id);
87
+ if (removed) {
88
+ this._dirty = true;
89
+ }
90
+ return removed;
91
+ }
92
+
93
+ /**
94
+ * 获取指定 ID 的路径对象
95
+ */
96
+ getPath(id: number): Path | undefined {
97
+ return this._paths.get(id)?.path;
98
+ }
99
+
100
+ /**
101
+ * 是否存在指定 ID 的路径
102
+ */
103
+ hasPath(id: number): boolean {
104
+ return this._paths.has(id);
105
+ }
106
+
107
+ /**
108
+ * 获取所有已注册的路径 ID
109
+ */
110
+ getPathIds(): number[] {
111
+ return Array.from(this._paths.keys()).sort((a, b) => a - b);
112
+ }
113
+
114
+ /**
115
+ * 清空所有已注册的路径
116
+ */
117
+ clear(): void {
118
+ if (this._paths.size > 0) {
119
+ this._paths.clear();
120
+ this._nextId = 0;
121
+ this._dirty = true;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * 当前注册的有效路径数量
127
+ */
128
+ get count(): number {
129
+ return this._paths.size;
130
+ }
131
+
132
+ /**
133
+ * 获取 GPU 路径数据纹理,若有变动会自动触发重新烘焙
134
+ */
135
+ getTexture(): THREE.DataTexture {
136
+ if (this._dirty || !this._texture) {
137
+ this._bake();
138
+ }
139
+ return this._texture ?? GPUPathManager.DUMMY_PATH_TEX;
140
+ }
141
+
142
+ /**
143
+ * 将当前路径纹理与 uniform 状态同步到材质 uniform 字典
144
+ */
145
+ updateUniforms(uniforms: Record<string, any>): void {
146
+ const tex = this.getTexture();
147
+ const active = this._paths.size > 0 ? 1 : 0;
148
+ const maxId = this._getMaxPathId();
149
+ const height = Math.max(maxId + 1, 1);
150
+
151
+ if (uniforms.uPathTex) uniforms.uPathTex.value = tex;
152
+ if (uniforms.uPathCount) uniforms.uPathCount.value = height;
153
+ if (uniforms.uPathTexActive) uniforms.uPathTexActive.value = active;
154
+ }
155
+
156
+ /**
157
+ * 烘焙路径数据到纹理
158
+ */
159
+ private _bake(): void {
160
+ this._dirty = false;
161
+ if (this._paths.size === 0) {
162
+ if (this._texture && this._texture !== GPUPathManager.DUMMY_PATH_TEX) {
163
+ this._texture.dispose();
164
+ }
165
+ this._texture = GPUPathManager.DUMMY_PATH_TEX;
166
+ this._data = null;
167
+ return;
168
+ }
169
+
170
+ const maxId = this._getMaxPathId();
171
+ const height = Math.max(maxId + 1, 1);
172
+ const totalPixels = SAMPLES_PER_PATH * height;
173
+ const bufferLength = totalPixels * 4;
174
+
175
+ if (!this._data || this._data.length !== bufferLength) {
176
+ if (this._texture && this._texture !== GPUPathManager.DUMMY_PATH_TEX) {
177
+ this._texture.dispose();
178
+ }
179
+ this._data = new Float32Array(bufferLength);
180
+ this._texture = new THREE.DataTexture(
181
+ this._data,
182
+ SAMPLES_PER_PATH,
183
+ height,
184
+ THREE.RGBAFormat,
185
+ THREE.FloatType
186
+ );
187
+ this._texture.minFilter = THREE.LinearFilter;
188
+ this._texture.magFilter = THREE.LinearFilter;
189
+ this._texture.wrapS = THREE.ClampToEdgeWrapping;
190
+ this._texture.wrapT = THREE.ClampToEdgeWrapping;
191
+ } else {
192
+ this._data.fill(0);
193
+ }
194
+
195
+ const scratchA = [0, 0, 0];
196
+ const scratchB = [0, 0, 0];
197
+ const N = SAMPLES_PER_PATH;
198
+
199
+ for (const [id, info] of this._paths) {
200
+ if (id < 0 || id >= height) continue;
201
+ const path = info.path;
202
+ const rowOffset = id * N * 4;
203
+
204
+ // 提取 N 个等弧长采样点位置与航向切线角
205
+ let lastYaw = 0;
206
+ for (let i = 0; i < N; i++) {
207
+ const t = i / (N - 1);
208
+ path.sampleAt(t, scratchA);
209
+
210
+ // 计算切线方向:取一个极小的前向步长
211
+ let yaw = lastYaw;
212
+ if (path.length > 1e-4) {
213
+ const deltaT = Math.min(1.0, t + 1e-3);
214
+ path.sampleAt(deltaT, scratchB);
215
+ let dx = scratchB[0] - scratchA[0];
216
+ let dy = scratchB[1] - scratchA[1];
217
+ if (dx * dx + dy * dy < 1e-8 && t > 0) {
218
+ // 前向位移过小则尝试后向差分
219
+ const prevT = Math.max(0.0, t - 1e-3);
220
+ path.sampleAt(prevT, scratchB);
221
+ dx = scratchA[0] - scratchB[0];
222
+ dy = scratchA[1] - scratchB[1];
223
+ }
224
+ if (dx * dx + dy * dy >= 1e-8) {
225
+ yaw = Math.atan2(dy, dx);
226
+ }
227
+ }
228
+
229
+ // 连续角度展开 (Angle Unwrap):避免在 ±PI 处发生 360° 线性插值回旋
230
+ if (i > 0) {
231
+ let diff = yaw - lastYaw;
232
+ while (diff > Math.PI) {
233
+ yaw -= 2 * Math.PI;
234
+ diff = yaw - lastYaw;
235
+ }
236
+ while (diff < -Math.PI) {
237
+ yaw += 2 * Math.PI;
238
+ diff = yaw - lastYaw;
239
+ }
240
+ }
241
+ lastYaw = yaw;
242
+
243
+ const pIdx = rowOffset + i * 4;
244
+ this._data[pIdx + 0] = scratchA[0];
245
+ this._data[pIdx + 1] = scratchA[1];
246
+ this._data[pIdx + 2] = scratchA[2];
247
+ this._data[pIdx + 3] = yaw;
248
+ }
249
+ }
250
+
251
+ if (this._texture) {
252
+ this._texture.needsUpdate = true;
253
+ }
254
+ }
255
+
256
+ private _getMaxPathId(): number {
257
+ let max = 0;
258
+ for (const id of this._paths.keys()) {
259
+ if (id > max) max = id;
260
+ }
261
+ return max;
262
+ }
263
+
264
+ /**
265
+ * 纯数学模拟采样烘焙纹理(用于单测验证硬件线性插值精度)
266
+ */
267
+ sampleBaked(pathId: number, t: number): { position: [number, number, number]; yaw: number } {
268
+ if (!this._data || !this._paths.has(pathId)) {
269
+ return { position: [0, 0, 0], yaw: 0 };
270
+ }
271
+ const maxId = this._getMaxPathId();
272
+ if (pathId < 0 || pathId > maxId) {
273
+ return { position: [0, 0, 0], yaw: 0 };
274
+ }
275
+
276
+ const N = SAMPLES_PER_PATH;
277
+ const clampedT = Math.min(1.0, Math.max(0.0, t));
278
+ const sampleCoord = clampedT * (N - 1);
279
+ const i0 = Math.floor(sampleCoord);
280
+ const i1 = Math.min(N - 1, i0 + 1);
281
+ const frac = sampleCoord - i0;
282
+
283
+ const rowOffset = pathId * N * 4;
284
+ const idx0 = rowOffset + i0 * 4;
285
+ const idx1 = rowOffset + i1 * 4;
286
+
287
+ const x = this._data[idx0 + 0] * (1 - frac) + this._data[idx1 + 0] * frac;
288
+ const y = this._data[idx0 + 1] * (1 - frac) + this._data[idx1 + 1] * frac;
289
+ const z = this._data[idx0 + 2] * (1 - frac) + this._data[idx1 + 2] * frac;
290
+ const yaw = this._data[idx0 + 3] * (1 - frac) + this._data[idx1 + 3] * frac;
291
+
292
+ return { position: [x, y, z], yaw };
293
+ }
294
+
295
+ /**
296
+ * 释放纹理与显存
297
+ */
298
+ dispose(): void {
299
+ if (this._texture && this._texture !== GPUPathManager.DUMMY_PATH_TEX) {
300
+ this._texture.dispose();
301
+ this._texture = null;
302
+ }
303
+ this._data = null;
304
+ this._paths.clear();
305
+ }
306
+ }
@@ -41,6 +41,8 @@ import type { Channel, TransitionOptions } from '../anim/Animator.js';
41
41
  import { Path } from '../anim/Path.js';
42
42
  import type { EasingFn, EasingName } from '../anim/easing.js';
43
43
  import type { LineSharedUniforms, ThickLineMaterial, ThickLineStyleUniforms } from './EdgeLines.js';
44
+ import { GPUPathManager } from './GPUPathManager.js';
45
+ import type { GPUPathOptions } from './FeatureStyleManager.js';
44
46
  import {
45
47
  buildFeatureEdgeSegments,
46
48
  buildPositionBatchMap,
@@ -370,6 +372,9 @@ export class S3MViewer {
370
372
  private readonly _datasetManager = new DatasetManager();
371
373
  /** 单体样式、专题着色与状态纹理管理器 */
372
374
  private readonly _featureStyleManager: FeatureStyleManager;
375
+ /** GPU 路径动画管理器 */
376
+ readonly gpuPathManager: GPUPathManager = new GPUPathManager();
377
+ private _customPathTime = false;
373
378
 
374
379
  /** 兼容 datasets 映射表暴露 */
375
380
  get datasets(): Map<string, S3MDatasetEntry> {
@@ -540,6 +545,9 @@ export class S3MViewer {
540
545
  // 这里按 style 实际值调一次 setEdges,让开关真正生效(与 setEdges 后来的手工调用一致)。
541
546
  this.setEdges(!!this.style.edges);
542
547
 
548
+ // 初始化图层整体不透明度:确保共享 uniform uOpacity、材质混合开关与棱线外观与初始 style.opacity 严格一致
549
+ this._applyOpacity();
550
+
543
551
  this._fps.last = performance.now();
544
552
  this._datasetQueue = this._normalizeDatasets(options);
545
553
  if (options.attributes) this.attributeQuery = new AttributeQuery(options.attributes);
@@ -1132,6 +1140,96 @@ export class S3MViewer {
1132
1140
  this.animator.unregister(key);
1133
1141
  this._pathSpecs.delete(key);
1134
1142
  }
1143
+
1144
+ /**
1145
+ * 注册 GPU 路径到路径库
1146
+ * @param points 折线点数组或 Path 实例
1147
+ * @param opts 选项(可指定路径 ID 或闭合)
1148
+ * @returns 路径 ID
1149
+ */
1150
+ registerGPUPath(
1151
+ points: Path | ArrayLike<number> | Array<[number, number, number]>,
1152
+ opts: { id?: number; name?: string; closed?: boolean } = {}
1153
+ ): number {
1154
+ const id = this.gpuPathManager.registerPath(points, opts);
1155
+ this.gpuPathManager.updateUniforms(this.uniforms);
1156
+ return id;
1157
+ }
1158
+
1159
+ /**
1160
+ * 注销 GPU 路径
1161
+ */
1162
+ unregisterGPUPath(id: number): boolean {
1163
+ const res = this.gpuPathManager.unregisterPath(id);
1164
+ this.gpuPathManager.updateUniforms(this.uniforms);
1165
+ return res;
1166
+ }
1167
+
1168
+ /**
1169
+ * 获取 GPUPathManager 实例
1170
+ */
1171
+ getGPUPathManager(): GPUPathManager {
1172
+ return this.gpuPathManager;
1173
+ }
1174
+
1175
+ /**
1176
+ * 将单体绑定到 GPU 路径动画(智慧交通车流 / 大批量巡检无人机)
1177
+ * @param smid 单体 ID
1178
+ * @param path 路径 ID 或新的路径折线
1179
+ * @param options 动画配置(速度、相位、循环模式、自动朝向等)
1180
+ */
1181
+ bindGPUPath(
1182
+ smid: number,
1183
+ path: number | Path | ArrayLike<number> | Array<[number, number, number]>,
1184
+ options: Omit<GPUPathOptions, 'pathId'> = {}
1185
+ ): number {
1186
+ let pathId: number;
1187
+ if (typeof path === 'number') {
1188
+ pathId = path;
1189
+ } else {
1190
+ pathId = this.registerGPUPath(path);
1191
+ }
1192
+ const meshes = this._meshesOf([smid]);
1193
+ this._featureStyleManager.bindGPUPath(smid, pathId, options, meshes);
1194
+ for (const mesh of meshes) mesh.frustumCulled = false;
1195
+ this.gpuPathManager.updateUniforms(this.uniforms);
1196
+ return pathId;
1197
+ }
1198
+
1199
+ /**
1200
+ * 解绑单体的 GPU 路径动画
1201
+ */
1202
+ unbindGPUPath(smid: number): void {
1203
+ const meshes = this._meshesOf([smid]);
1204
+ this._featureStyleManager.unbindGPUPath(smid, meshes);
1205
+ this.gpuPathManager.updateUniforms(this.uniforms);
1206
+ }
1207
+
1208
+ /**
1209
+ * 手动设置单体在 GPU 路径上的归一化进度(0.0 ~ 1.0)
1210
+ */
1211
+ setGPUPathProgress(smid: number, progress: number): void {
1212
+ const meshes = this._meshesOf([smid]);
1213
+ this._featureStyleManager.setGPUPathProgress(smid, progress, meshes);
1214
+ for (const mesh of meshes) mesh.frustumCulled = false;
1215
+ }
1216
+
1217
+ /**
1218
+ * 设置全局 GPU 路径时间(秒)
1219
+ */
1220
+ setGPUPathTime(time: number, autoAdvance = false): void {
1221
+ if (this.uniforms.uPathTime) {
1222
+ this.uniforms.uPathTime.value = time;
1223
+ }
1224
+ this._customPathTime = !autoAdvance;
1225
+ }
1226
+
1227
+ /**
1228
+ * 获取全局 GPU 路径时间(秒)
1229
+ */
1230
+ getGPUPathTime(): number {
1231
+ return this.uniforms.uPathTime?.value ?? 0;
1232
+ }
1135
1233
  /**
1136
1234
  * 单体样式过渡:颜色 / 抬高 / 透明度 / 平移可以任意组合。
1137
1235
  *
@@ -1584,7 +1682,8 @@ export class S3MViewer {
1584
1682
  const o = Math.min(1, Math.max(0.05, this.style.opacity ?? 1));
1585
1683
  // 不写 mat.opacity:全局透明度走共享 uOpacity,材质自身 opacity 保留模型自带 alpha
1586
1684
  mat.transparent = o < 0.999;
1587
- mat.depthWrite = o >= 0.999;
1685
+ const keepDepth = this.style.depthWrite !== false;
1686
+ mat.depthWrite = mat.transparent ? keepDepth : (this.style.depthWrite !== false);
1588
1687
  mat.flatShading = this.style.shading !== 'smooth';
1589
1688
  mat.needsUpdate = true;
1590
1689
  this._featureStyleManager.applyOverridesToMesh(mesh);
@@ -1639,7 +1738,7 @@ export class S3MViewer {
1639
1738
  // 淡入期间强制开了 transparent + depthWrite,结束时不能自己拍板关掉——
1640
1739
  // 图层可能本来就半透明、单体也可能还有淡化。交给淡化流程重新裁决一次。
1641
1740
  this._featureStyleManager.applyOverridesToMesh(mesh);
1642
- if (!mat.transparent) mat.depthWrite = true;
1741
+ mat.depthWrite = this.style.depthWrite !== false;
1643
1742
  this._requestRepaint();
1644
1743
  },
1645
1744
  });
@@ -2010,6 +2109,9 @@ export class S3MViewer {
2010
2109
  if (u.uAmbientGain && s.ambientGain != null) {
2011
2110
  u.uAmbientGain.value = s.ambientGain;
2012
2111
  }
2112
+ if (u.uOpacity && s.opacity != null) {
2113
+ u.uOpacity.value = Math.min(1, Math.max(0.05, s.opacity));
2114
+ }
2013
2115
  if (this.outlineMaterial) {
2014
2116
  if (this.outlineMaterial.uniforms.uElevation && s.selectElevation != null) {
2015
2117
  this.outlineMaterial.uniforms.uElevation.value = s.selectElevation;
@@ -2452,6 +2554,10 @@ export class S3MViewer {
2452
2554
  uBatchMix: this.uniforms.uBatchMix,
2453
2555
  uBatchTexWidth: styled.userData.uBatchTexWidth,
2454
2556
  uBatchTexActive: styled.userData.uBatchTexActive,
2557
+ uPathTex: this.uniforms.uPathTex,
2558
+ uPathCount: this.uniforms.uPathCount,
2559
+ uPathTexActive: this.uniforms.uPathTexActive,
2560
+ uPathTime: this.uniforms.uPathTime,
2455
2561
  }
2456
2562
  : undefined;
2457
2563
  return createThickLineMaterial(this.lineShared, {}, this._edgeStyleUniforms, batch);
@@ -2467,6 +2573,10 @@ export class S3MViewer {
2467
2573
  uBatchMix: this.uniforms.uBatchMix,
2468
2574
  uBatchTexWidth: styled.userData.uBatchTexWidth,
2469
2575
  uBatchTexActive: styled.userData.uBatchTexActive,
2576
+ uPathTex: this.uniforms.uPathTex,
2577
+ uPathCount: this.uniforms.uPathCount,
2578
+ uPathTexActive: this.uniforms.uPathTexActive,
2579
+ uPathTime: this.uniforms.uPathTime,
2470
2580
  }
2471
2581
  : undefined;
2472
2582
  return createThickLineMaterial(this.lineShared, {}, this._selEdgeStyleUniforms, batch);
@@ -3284,6 +3394,12 @@ export class S3MViewer {
3284
3394
  this.uniforms.uTime.value += 0.016 * (this.style.speed ?? 1);
3285
3395
  }
3286
3396
 
3397
+ this.gpuPathManager.updateUniforms(this.uniforms);
3398
+ const gpuPathActive = (this.uniforms.uPathTexActive?.value ?? 0) > 0;
3399
+ if (gpuPathActive && !this._customPathTime && this.uniforms.uPathTime) {
3400
+ this.uniforms.uPathTime.value += 0.016;
3401
+ }
3402
+
3287
3403
  this._updateLights();
3288
3404
 
3289
3405
  // 等宽棱线按设备像素撑开,分辨率每帧同步一次
@@ -3306,6 +3422,7 @@ export class S3MViewer {
3306
3422
  return (
3307
3423
  animating ||
3308
3424
  this.style.mode === STYLE.DYNAMIC ||
3425
+ gpuPathActive ||
3309
3426
  this.stats.loading > 0 ||
3310
3427
  tilesetChanged
3311
3428
  );