s3m-three 0.3.8 → 0.3.9
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/browser/s3m-three.iife.ext-three.js +187 -46
- package/dist/browser/s3m-three.iife.js +187 -46
- package/dist/core/types.d.ts +4 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/render/EdgeLines.d.ts +6 -0
- package/dist/render/EdgeLines.d.ts.map +1 -1
- package/dist/render/EdgeLines.js +41 -13
- package/dist/render/EdgeLines.js.map +1 -1
- package/dist/render/FeatureStyleManager.d.ts +45 -0
- package/dist/render/FeatureStyleManager.d.ts.map +1 -1
- package/dist/render/FeatureStyleManager.js +320 -59
- package/dist/render/FeatureStyleManager.js.map +1 -1
- package/dist/render/OrthoProjection.d.ts +6 -0
- package/dist/render/OrthoProjection.d.ts.map +1 -1
- package/dist/render/OrthoProjection.js +48 -0
- package/dist/render/OrthoProjection.js.map +1 -1
- package/dist/render/S3MViewer.d.ts +19 -0
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +122 -6
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/StyleMaterial.d.ts +9 -0
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +153 -21
- package/dist/render/StyleMaterial.js.map +1 -1
- package/package.json +1 -1
- package/src/core/types.ts +2 -0
- package/src/index.ts +6 -0
- package/src/render/EdgeLines.ts +43 -13
- package/src/render/FeatureStyleManager.ts +405 -64
- package/src/render/OrthoProjection.ts +52 -0
- package/src/render/S3MViewer.ts +162 -5
- package/src/render/StyleMaterial.ts +157 -21
package/src/render/S3MViewer.ts
CHANGED
|
@@ -94,6 +94,8 @@ import {
|
|
|
94
94
|
type FieldElevationOptions,
|
|
95
95
|
type FieldElevationResult,
|
|
96
96
|
type S3MFilter,
|
|
97
|
+
resolveRotationMatrix,
|
|
98
|
+
extractEulerAngles,
|
|
97
99
|
} from './FeatureStyleManager.js';
|
|
98
100
|
|
|
99
101
|
import {
|
|
@@ -304,7 +306,7 @@ const STYLE_SCALAR_KEYS = [
|
|
|
304
306
|
const STYLE_COLOR_KEYS = ['colorA', 'colorB', 'colorC', 'selectColor', 'edgeColor'] as const;
|
|
305
307
|
|
|
306
308
|
/** 可动画的单体属性 */
|
|
307
|
-
const FEATURE_PROPS = ['translation', 'elevation', 'opacity', 'color'] as const;
|
|
309
|
+
const FEATURE_PROPS = ['translation', 'elevation', 'opacity', 'color', 'rotation'] as const;
|
|
308
310
|
type FeatureProp = (typeof FEATURE_PROPS)[number];
|
|
309
311
|
|
|
310
312
|
/** 单体沿折线运动的参数 */
|
|
@@ -948,6 +950,111 @@ export class S3MViewer {
|
|
|
948
950
|
this.moveFeature(smid, [cur[0] + delta[0], cur[1] + delta[1], cur[2] + delta[2]], transition);
|
|
949
951
|
}
|
|
950
952
|
|
|
953
|
+
/**
|
|
954
|
+
* 让单体进行刚体姿态旋转(在 GPU 顶点着色器中以 pivot 轴心进行 3x3 矩阵旋转变换)。
|
|
955
|
+
*
|
|
956
|
+
* @param smid 单体 SMID
|
|
957
|
+
* @param rotation 旋转弧度或姿态:数字(绕 Z 轴 yaw 弧度)| [rx, ry, rz] 欧拉角 | [x,y,z,w] 四元数 | Euler/Quaternion/Matrix
|
|
958
|
+
* @param pivot 旋转中心轴心点 [px, py, pz](局部坐标,默认 [0, 0, 0])
|
|
959
|
+
* @param transition 动画过渡参数
|
|
960
|
+
*/
|
|
961
|
+
rotateFeature(
|
|
962
|
+
smid: number,
|
|
963
|
+
rotation:
|
|
964
|
+
| number
|
|
965
|
+
| [number, number, number]
|
|
966
|
+
| [number, number, number, number]
|
|
967
|
+
| THREE.Euler
|
|
968
|
+
| THREE.Quaternion
|
|
969
|
+
| THREE.Matrix3
|
|
970
|
+
| THREE.Matrix4,
|
|
971
|
+
pivot?: [number, number, number],
|
|
972
|
+
transition?: TransitionOptions
|
|
973
|
+
): void {
|
|
974
|
+
const cur = this._featureStyleManager.getFeatureStyle(smid) ?? {};
|
|
975
|
+
if (pivot) {
|
|
976
|
+
this._featureStyleManager.setFeatureStyle(smid, { ...cur, pivot }, this._meshesOf([smid]));
|
|
977
|
+
}
|
|
978
|
+
const targetEuler = extractEulerAngles(rotation);
|
|
979
|
+
const key = this._ensureFeatureChannel(smid, 'rotation');
|
|
980
|
+
this.animator.to(key, targetEuler, transition);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/** 让单体增量旋转的便捷写法 */
|
|
984
|
+
rotateFeatureBy(
|
|
985
|
+
smid: number,
|
|
986
|
+
deltaRotation: number | [number, number, number],
|
|
987
|
+
pivot?: [number, number, number],
|
|
988
|
+
transition?: TransitionOptions
|
|
989
|
+
): void {
|
|
990
|
+
const curEuler = extractEulerAngles(this._featureStyleManager.getFeatureStyle(smid)?.rotation);
|
|
991
|
+
const d = typeof deltaRotation === 'number' ? [0, 0, deltaRotation] : deltaRotation;
|
|
992
|
+
this.rotateFeature(
|
|
993
|
+
smid,
|
|
994
|
+
[curEuler[0] + d[0], curEuler[1] + d[1], curEuler[2] + d[2]],
|
|
995
|
+
pivot,
|
|
996
|
+
transition
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* 双纹理 GPU 交叉淡化(跨状态 / 专题图平滑过渡)。
|
|
1002
|
+
*
|
|
1003
|
+
* 采用双纹理 (uBatchTex + uBatchTex2 + uBatchMix) 硬件混合,
|
|
1004
|
+
* 即使几千上万栋建筑全量切换配色/高度/显隐/姿态,也无需在 CPU 逐单体创建通道,
|
|
1005
|
+
* 由 GPU 顶点与片元着色器以 60FPS 零成本硬件插值完成过渡。
|
|
1006
|
+
*/
|
|
1007
|
+
crossFadeStyles(
|
|
1008
|
+
targetStyles: Map<number, FeatureStyle> | Record<string | number, FeatureStyle>,
|
|
1009
|
+
transition?: TransitionOptions
|
|
1010
|
+
): void {
|
|
1011
|
+
const targetMap =
|
|
1012
|
+
targetStyles instanceof Map
|
|
1013
|
+
? targetStyles
|
|
1014
|
+
: new Map(Object.entries(targetStyles).map(([k, v]) => [Number(k), v]));
|
|
1015
|
+
|
|
1016
|
+
const meshes = this.getAllMeshes();
|
|
1017
|
+
this._featureStyleManager.prepareCrossFade(targetMap, meshes);
|
|
1018
|
+
|
|
1019
|
+
const key = 'batch.mix';
|
|
1020
|
+
if (!this.animator.has(key)) {
|
|
1021
|
+
this.animator.register(key, {
|
|
1022
|
+
dim: 1,
|
|
1023
|
+
read: (out) => {
|
|
1024
|
+
out[0] = this.uniforms.uBatchMix?.value ?? 0;
|
|
1025
|
+
},
|
|
1026
|
+
write: (v) => {
|
|
1027
|
+
if (this.uniforms.uBatchMix) {
|
|
1028
|
+
this.uniforms.uBatchMix.value = v[0];
|
|
1029
|
+
}
|
|
1030
|
+
this.triggerRepaint();
|
|
1031
|
+
},
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const duration = transition?.duration ?? 0;
|
|
1036
|
+
const wantAnim = duration > 0 || !!transition?.spring;
|
|
1037
|
+
if (!wantAnim) {
|
|
1038
|
+
if (this.animator.has(key)) this.animator.cancel(key);
|
|
1039
|
+
if (this.uniforms.uBatchMix) this.uniforms.uBatchMix.value = 0;
|
|
1040
|
+
this._featureStyleManager.commitCrossFade(targetMap, meshes);
|
|
1041
|
+
this.triggerRepaint();
|
|
1042
|
+
transition?.onComplete?.();
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
if (this.uniforms.uBatchMix) this.uniforms.uBatchMix.value = 0;
|
|
1047
|
+
this.animator.to(key, 1, {
|
|
1048
|
+
...transition,
|
|
1049
|
+
onComplete: () => {
|
|
1050
|
+
if (this.uniforms.uBatchMix) this.uniforms.uBatchMix.value = 0;
|
|
1051
|
+
this._featureStyleManager.commitCrossFade(targetMap, meshes);
|
|
1052
|
+
this.triggerRepaint();
|
|
1053
|
+
transition?.onComplete?.();
|
|
1054
|
+
},
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
|
|
951
1058
|
/**
|
|
952
1059
|
* 让单体沿折线运动。
|
|
953
1060
|
*
|
|
@@ -1034,6 +1141,7 @@ export class S3MViewer {
|
|
|
1034
1141
|
*/
|
|
1035
1142
|
animateFeature(smid: number, style: Partial<FeatureStyle>, transition?: TransitionOptions): void {
|
|
1036
1143
|
if (style.translation) this.moveFeature(smid, style.translation, transition);
|
|
1144
|
+
if (style.rotation != null) this.rotateFeature(smid, style.rotation, style.pivot, transition);
|
|
1037
1145
|
if (style.elevation != null) {
|
|
1038
1146
|
this.animator.to(this._ensureFeatureChannel(smid, 'elevation'), style.elevation, transition);
|
|
1039
1147
|
}
|
|
@@ -1127,7 +1235,7 @@ export class S3MViewer {
|
|
|
1127
1235
|
const key = `feature.${smid}.${prop}`;
|
|
1128
1236
|
if (this.animator.has(key)) return key;
|
|
1129
1237
|
|
|
1130
|
-
const channel: Channel = prop === 'translation' || prop === 'color'
|
|
1238
|
+
const channel: Channel = prop === 'translation' || prop === 'color' || prop === 'rotation'
|
|
1131
1239
|
? {
|
|
1132
1240
|
dim: 3,
|
|
1133
1241
|
read: (out) => {
|
|
@@ -1136,6 +1244,12 @@ export class S3MViewer {
|
|
|
1136
1244
|
out[0] = t?.[0] ?? 0;
|
|
1137
1245
|
out[1] = t?.[1] ?? 0;
|
|
1138
1246
|
out[2] = t?.[2] ?? 0;
|
|
1247
|
+
} else if (prop === 'rotation') {
|
|
1248
|
+
const r = this._featureStyleManager.getFeatureStyle(smid)?.rotation;
|
|
1249
|
+
const euler = extractEulerAngles(r);
|
|
1250
|
+
out[0] = euler[0];
|
|
1251
|
+
out[1] = euler[1];
|
|
1252
|
+
out[2] = euler[2];
|
|
1139
1253
|
} else {
|
|
1140
1254
|
// 没有自定义色时以图层主色为起点,插值才连续
|
|
1141
1255
|
const hex = this._featureStyleManager.getFeatureStyle(smid)?.color ?? this.style.colorA;
|
|
@@ -1154,9 +1268,21 @@ export class S3MViewer {
|
|
|
1154
1268
|
{ ...cur, translation: [v[0], v[1], v[2]] },
|
|
1155
1269
|
meshes
|
|
1156
1270
|
);
|
|
1157
|
-
//
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1271
|
+
// 有位移或旋转就关掉视锥剔除,否则移出原始包围球后整块瓦片会凭空消失
|
|
1272
|
+
const transMoving = Math.abs(v[0]) > 1e-6 || Math.abs(v[1]) > 1e-6 || Math.abs(v[2]) > 1e-6;
|
|
1273
|
+
const rotEuler = extractEulerAngles(cur.rotation);
|
|
1274
|
+
const rotMoving = Math.abs(rotEuler[0]) > 1e-6 || Math.abs(rotEuler[1]) > 1e-6 || Math.abs(rotEuler[2]) > 1e-6;
|
|
1275
|
+
for (const mesh of meshes) mesh.frustumCulled = !(transMoving || rotMoving);
|
|
1276
|
+
} else if (prop === 'rotation') {
|
|
1277
|
+
this._featureStyleManager.setFeatureStyle(
|
|
1278
|
+
smid,
|
|
1279
|
+
{ ...cur, rotation: [v[0], v[1], v[2]] },
|
|
1280
|
+
meshes
|
|
1281
|
+
);
|
|
1282
|
+
const rotMoving = Math.abs(v[0]) > 1e-6 || Math.abs(v[1]) > 1e-6 || Math.abs(v[2]) > 1e-6;
|
|
1283
|
+
const t = cur.translation ?? [0, 0, 0];
|
|
1284
|
+
const transMoving = Math.abs(t[0]) > 1e-6 || Math.abs(t[1]) > 1e-6 || Math.abs(t[2]) > 1e-6;
|
|
1285
|
+
for (const mesh of meshes) mesh.frustumCulled = !(rotMoving || transMoving);
|
|
1160
1286
|
} else {
|
|
1161
1287
|
const c = this._colorScratch.setRGB(v[0], v[1], v[2]);
|
|
1162
1288
|
this._featureStyleManager.setFeatureStyle(
|
|
@@ -2322,6 +2448,8 @@ export class S3MViewer {
|
|
|
2322
2448
|
const batch = styled?.userData?.uBatchTex
|
|
2323
2449
|
? {
|
|
2324
2450
|
uBatchTex: styled.userData.uBatchTex,
|
|
2451
|
+
uBatchTex2: styled.userData.uBatchTex2,
|
|
2452
|
+
uBatchMix: this.uniforms.uBatchMix,
|
|
2325
2453
|
uBatchTexWidth: styled.userData.uBatchTexWidth,
|
|
2326
2454
|
uBatchTexActive: styled.userData.uBatchTexActive,
|
|
2327
2455
|
}
|
|
@@ -2335,6 +2463,8 @@ export class S3MViewer {
|
|
|
2335
2463
|
const batch = styled?.userData?.uBatchTex
|
|
2336
2464
|
? {
|
|
2337
2465
|
uBatchTex: styled.userData.uBatchTex,
|
|
2466
|
+
uBatchTex2: styled.userData.uBatchTex2,
|
|
2467
|
+
uBatchMix: this.uniforms.uBatchMix,
|
|
2338
2468
|
uBatchTexWidth: styled.userData.uBatchTexWidth,
|
|
2339
2469
|
uBatchTexActive: styled.userData.uBatchTexActive,
|
|
2340
2470
|
}
|
|
@@ -2501,6 +2631,20 @@ export class S3MViewer {
|
|
|
2501
2631
|
const trans = off.translation;
|
|
2502
2632
|
const elev = off.elevation;
|
|
2503
2633
|
const finalPos = hitPoint.clone();
|
|
2634
|
+
|
|
2635
|
+
if (off.rotation) {
|
|
2636
|
+
const rotMat = resolveRotationMatrix(off.rotation);
|
|
2637
|
+
if (rotMat) {
|
|
2638
|
+
const m3 = new THREE.Matrix3().set(
|
|
2639
|
+
rotMat[0], rotMat[1], rotMat[2],
|
|
2640
|
+
rotMat[3], rotMat[4], rotMat[5],
|
|
2641
|
+
rotMat[6], rotMat[7], rotMat[8]
|
|
2642
|
+
);
|
|
2643
|
+
const piv = off.pivot ? new THREE.Vector3(off.pivot[0], off.pivot[1], off.pivot[2]) : new THREE.Vector3(0, 0, 0);
|
|
2644
|
+
finalPos.sub(piv).applyMatrix3(m3).add(piv);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2504
2648
|
if (trans) finalPos.add(new THREE.Vector3(trans[0], trans[1], trans[2]));
|
|
2505
2649
|
else if (elev) finalPos.z += elev;
|
|
2506
2650
|
|
|
@@ -2731,7 +2875,20 @@ export class S3MViewer {
|
|
|
2731
2875
|
const trans = off.translation;
|
|
2732
2876
|
const elev = off.elevation;
|
|
2733
2877
|
|
|
2878
|
+
const rotMat = off.rotation ? resolveRotationMatrix(off.rotation) : null;
|
|
2879
|
+
let rotM3: THREE.Matrix3 | null = null;
|
|
2880
|
+
let rotPiv: THREE.Vector3 | null = null;
|
|
2881
|
+
if (rotMat) {
|
|
2882
|
+
rotM3 = new THREE.Matrix3().set(
|
|
2883
|
+
rotMat[0], rotMat[1], rotMat[2],
|
|
2884
|
+
rotMat[3], rotMat[4], rotMat[5],
|
|
2885
|
+
rotMat[6], rotMat[7], rotMat[8]
|
|
2886
|
+
);
|
|
2887
|
+
rotPiv = off.pivot ? new THREE.Vector3(off.pivot[0], off.pivot[1], off.pivot[2]) : new THREE.Vector3(0, 0, 0);
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2734
2890
|
for (const c of corners) {
|
|
2891
|
+
if (rotM3 && rotPiv) c.sub(rotPiv).applyMatrix3(rotM3).add(rotPiv);
|
|
2735
2892
|
if (trans) c.add(new THREE.Vector3(trans[0], trans[1], trans[2]));
|
|
2736
2893
|
else if (elev) c.z += elev;
|
|
2737
2894
|
|
|
@@ -172,6 +172,7 @@ export interface StyledMaterial extends THREE.MeshStandardMaterial {
|
|
|
172
172
|
uSelBatch: { value: number };
|
|
173
173
|
uSelWhole: { value: number };
|
|
174
174
|
uBatchTex: { value: THREE.Texture };
|
|
175
|
+
uBatchTex2: { value: THREE.Texture };
|
|
175
176
|
uBatchTexWidth: { value: number };
|
|
176
177
|
uBatchTexActive: { value: number };
|
|
177
178
|
/** 单块瓦片的淡入系数(1 = 完全显示) */
|
|
@@ -181,8 +182,14 @@ export interface StyledMaterial extends THREE.MeshStandardMaterial {
|
|
|
181
182
|
};
|
|
182
183
|
}
|
|
183
184
|
|
|
184
|
-
const DUMMY_DATA = new Float32Array([
|
|
185
|
-
|
|
185
|
+
const DUMMY_DATA = new Float32Array([
|
|
186
|
+
-1, -1, -1, 0, // Row 0: color rgb, elevation/hidden
|
|
187
|
+
0, 0, 0, 0, // Row 1: translation xyz, opacity
|
|
188
|
+
1, 0, 0, 0, // Row 2: rot row 0, pivot.x
|
|
189
|
+
0, 1, 0, 0, // Row 3: rot row 1, pivot.y
|
|
190
|
+
0, 0, 1, 0, // Row 4: rot row 2, pivot.z
|
|
191
|
+
]);
|
|
192
|
+
export const DUMMY_BATCH_TEX = new THREE.DataTexture(DUMMY_DATA, 1, 5, THREE.RGBAFormat, THREE.FloatType);
|
|
186
193
|
DUMMY_BATCH_TEX.minFilter = THREE.NearestFilter;
|
|
187
194
|
DUMMY_BATCH_TEX.magFilter = THREE.NearestFilter;
|
|
188
195
|
DUMMY_BATCH_TEX.needsUpdate = true;
|
|
@@ -226,6 +233,8 @@ export function createSharedUniforms(): StyleUniforms {
|
|
|
226
233
|
uTexMix: { value: 0 },
|
|
227
234
|
/** 基础底色增益 / 暗部补偿(0~1,默认 0.0) */
|
|
228
235
|
uAmbientGain: { value: 0 },
|
|
236
|
+
/** 双纹理交叉淡化插值系数(0 = 纯 uBatchTex, 1 = 纯 uBatchTex2) */
|
|
237
|
+
uBatchMix: { value: 0 },
|
|
229
238
|
/**
|
|
230
239
|
* 图层整体透明度:在片元里乘进 diffuseColor.a。
|
|
231
240
|
*
|
|
@@ -264,6 +273,8 @@ const VERT_PARS = /* glsl */ `
|
|
|
264
273
|
uniform float uSelWhole;
|
|
265
274
|
uniform float uSelElevation;
|
|
266
275
|
uniform sampler2D uBatchTex;
|
|
276
|
+
uniform sampler2D uBatchTex2;
|
|
277
|
+
uniform float uBatchMix;
|
|
267
278
|
uniform float uBatchTexWidth;
|
|
268
279
|
uniform int uBatchTexActive;
|
|
269
280
|
varying float vBatchId;
|
|
@@ -272,6 +283,11 @@ const VERT_PARS = /* glsl */ `
|
|
|
272
283
|
varying vec3 vWorldNormal;
|
|
273
284
|
varying vec3 vWorldPos;
|
|
274
285
|
|
|
286
|
+
// 跨 chunk 共享的顶点姿态变量(在 beginnormal_vertex 建立,begin_vertex / worldpos 使用)
|
|
287
|
+
vec3 s3mCustomOffset;
|
|
288
|
+
mat3 s3mRot;
|
|
289
|
+
vec3 s3mPivot;
|
|
290
|
+
|
|
275
291
|
/** 当前图元所属单体的控制位:实例化取实例属性,其余取逐顶点属性 */
|
|
276
292
|
float s3mBatchRaw() {
|
|
277
293
|
#ifdef USE_INSTANCING
|
|
@@ -280,27 +296,85 @@ const VERT_PARS = /* glsl */ `
|
|
|
280
296
|
return aBatchId;
|
|
281
297
|
#endif
|
|
282
298
|
}
|
|
299
|
+
|
|
300
|
+
vec4 s3mSampleBatch( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
|
|
301
|
+
vec4 vA = texture2D( texA, uv );
|
|
302
|
+
if ( mixVal <= 0.0001 ) return vA;
|
|
303
|
+
vec4 vB = texture2D( texB, uv );
|
|
304
|
+
return mix( vA, vB, mixVal );
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
vec4 s3mSampleBatchRow0( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
|
|
308
|
+
vec4 vA = texture2D( texA, uv );
|
|
309
|
+
if ( mixVal <= 0.0001 ) return vA;
|
|
310
|
+
vec4 vB = texture2D( texB, uv );
|
|
311
|
+
vec4 res = mix( vA, vB, mixVal );
|
|
312
|
+
if ( vA.r < 0.0 && vB.r >= 0.0 ) {
|
|
313
|
+
res.rgb = vB.rgb;
|
|
314
|
+
} else if ( vA.r >= 0.0 && vB.r < 0.0 ) {
|
|
315
|
+
res.rgb = vA.rgb;
|
|
316
|
+
}
|
|
317
|
+
if ( vA.a < -0.5 || vB.a < -0.5 ) {
|
|
318
|
+
res.a = ( mixVal >= 0.5 ) ? vB.a : vA.a;
|
|
319
|
+
}
|
|
320
|
+
return res;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
vec4 s3mSampleBatchRow1( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
|
|
324
|
+
vec4 vA = texture2D( texA, uv );
|
|
325
|
+
if ( mixVal <= 0.0001 ) return vA;
|
|
326
|
+
vec4 vB = texture2D( texB, uv );
|
|
327
|
+
vec4 res = mix( vA, vB, mixVal );
|
|
328
|
+
if ( vA.a == 0.0 && vB.a > 0.0 ) {
|
|
329
|
+
res.a = mix( 1.0, vB.a, mixVal );
|
|
330
|
+
} else if ( vA.a > 0.0 && vB.a == 0.0 ) {
|
|
331
|
+
res.a = mix( vA.a, 1.0, mixVal );
|
|
332
|
+
}
|
|
333
|
+
return res;
|
|
334
|
+
}
|
|
283
335
|
`;
|
|
284
336
|
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
337
|
+
const VERT_SETUP = /* glsl */ `
|
|
338
|
+
s3mCustomOffset = vec3( 0.0 );
|
|
339
|
+
s3mRot = mat3( 1.0 );
|
|
340
|
+
s3mPivot = vec3( 0.0 );
|
|
341
|
+
float s3mBatchSetup = s3mBatchRaw();
|
|
342
|
+
if ( uBatchTexActive > 0 && s3mBatchSetup >= 0.0 ) {
|
|
343
|
+
float s3mU = ( s3mBatchSetup + 0.5 ) / uBatchTexWidth;
|
|
344
|
+
vec4 bState = s3mSampleBatchRow0( uBatchTex, uBatchTex2, vec2( s3mU, 0.1 ), uBatchMix );
|
|
345
|
+
if ( bState.a >= -0.5 ) {
|
|
346
|
+
vec4 bOffset = s3mSampleBatchRow1( uBatchTex, uBatchTex2, vec2( s3mU, 0.3 ), uBatchMix );
|
|
294
347
|
s3mCustomOffset = bOffset.xyz;
|
|
295
348
|
if ( s3mCustomOffset.z == 0.0 && bState.a > 0.0 ) {
|
|
296
349
|
s3mCustomOffset.z = bState.a;
|
|
297
350
|
}
|
|
351
|
+
vec4 r2 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.5 ), uBatchMix );
|
|
352
|
+
vec4 r3 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.7 ), uBatchMix );
|
|
353
|
+
vec4 r4 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.9 ), uBatchMix );
|
|
354
|
+
s3mRot = mat3(
|
|
355
|
+
vec3( r2.x, r3.x, r4.x ),
|
|
356
|
+
vec3( r2.y, r3.y, r4.y ),
|
|
357
|
+
vec3( r2.z, r3.z, r4.z )
|
|
358
|
+
);
|
|
359
|
+
s3mPivot = vec3( r2.w, r3.w, r4.w );
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
objectNormal = normalize( s3mRot * objectNormal );
|
|
363
|
+
`;
|
|
364
|
+
|
|
365
|
+
const VERT_ELEVATION = /* glsl */ `
|
|
366
|
+
float s3mBatchElev = s3mBatchRaw();
|
|
367
|
+
if ( uBatchTexActive > 0 && s3mBatchElev >= 0.0 ) {
|
|
368
|
+
float s3mU = ( s3mBatchElev + 0.5 ) / uBatchTexWidth;
|
|
369
|
+
vec4 bState = s3mSampleBatchRow0( uBatchTex, uBatchTex2, vec2( s3mU, 0.1 ), uBatchMix );
|
|
370
|
+
if ( bState.a < -0.5 ) {
|
|
371
|
+
transformed = vec3( 0.0 );
|
|
298
372
|
}
|
|
299
373
|
}
|
|
300
374
|
|
|
301
|
-
float s3mSelElev = max( step( 0.0, uSelBatch ) * step( abs(
|
|
302
|
-
//
|
|
303
|
-
transformed
|
|
375
|
+
float s3mSelElev = max( step( 0.0, uSelBatch ) * step( abs( s3mBatchElev - uSelBatch ), 0.5 ), uSelWhole );
|
|
376
|
+
// 刚体旋转以 s3mPivot 为中心,再叠加自定义平移与选中拔高
|
|
377
|
+
transformed = s3mRot * ( transformed - s3mPivot ) + s3mPivot + s3mCustomOffset;
|
|
304
378
|
transformed.z += s3mSelElev * uSelElevation;
|
|
305
379
|
`;
|
|
306
380
|
|
|
@@ -328,7 +402,7 @@ const VERT_WORLD = /* glsl */ `
|
|
|
328
402
|
#else
|
|
329
403
|
mat3 s3mNormMat = mat3( modelMatrix );
|
|
330
404
|
#endif
|
|
331
|
-
vWorldNormal = normalize( s3mNormMat * normal );
|
|
405
|
+
vWorldNormal = normalize( s3mNormMat * ( s3mRot * normal ) );
|
|
332
406
|
`;
|
|
333
407
|
|
|
334
408
|
const FRAG_PARS = /* glsl */ `
|
|
@@ -411,6 +485,8 @@ const FRAG_PARS = /* glsl */ `
|
|
|
411
485
|
uniform float uSelEmissive;
|
|
412
486
|
uniform float uDimFactor;
|
|
413
487
|
uniform sampler2D uBatchTex;
|
|
488
|
+
uniform sampler2D uBatchTex2;
|
|
489
|
+
uniform float uBatchMix;
|
|
414
490
|
uniform float uBatchTexWidth;
|
|
415
491
|
uniform int uBatchTexActive;
|
|
416
492
|
uniform float uFilterMinZ;
|
|
@@ -436,6 +512,35 @@ const FRAG_PARS = /* glsl */ `
|
|
|
436
512
|
varying vec3 vWorldNormal;
|
|
437
513
|
varying vec3 vWorldPos;
|
|
438
514
|
|
|
515
|
+
vec4 s3mSampleBatchRow0( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
|
|
516
|
+
vec4 vA = texture2D( texA, uv );
|
|
517
|
+
if ( mixVal <= 0.0001 ) return vA;
|
|
518
|
+
vec4 vB = texture2D( texB, uv );
|
|
519
|
+
vec4 res = mix( vA, vB, mixVal );
|
|
520
|
+
if ( vA.r < 0.0 && vB.r >= 0.0 ) {
|
|
521
|
+
res.rgb = vB.rgb;
|
|
522
|
+
} else if ( vA.r >= 0.0 && vB.r < 0.0 ) {
|
|
523
|
+
res.rgb = vA.rgb;
|
|
524
|
+
}
|
|
525
|
+
if ( vA.a < -0.5 || vB.a < -0.5 ) {
|
|
526
|
+
res.a = ( mixVal >= 0.5 ) ? vB.a : vA.a;
|
|
527
|
+
}
|
|
528
|
+
return res;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
vec4 s3mSampleBatchRow1( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
|
|
532
|
+
vec4 vA = texture2D( texA, uv );
|
|
533
|
+
if ( mixVal <= 0.0001 ) return vA;
|
|
534
|
+
vec4 vB = texture2D( texB, uv );
|
|
535
|
+
vec4 res = mix( vA, vB, mixVal );
|
|
536
|
+
if ( vA.a == 0.0 && vB.a > 0.0 ) {
|
|
537
|
+
res.a = mix( 1.0, vB.a, mixVal );
|
|
538
|
+
} else if ( vA.a > 0.0 && vB.a == 0.0 ) {
|
|
539
|
+
res.a = mix( vA.a, 1.0, mixVal );
|
|
540
|
+
}
|
|
541
|
+
return res;
|
|
542
|
+
}
|
|
543
|
+
|
|
439
544
|
float s3mHash( float p ) {
|
|
440
545
|
p = fract( p * 0.1031 );
|
|
441
546
|
p *= p + 33.33;
|
|
@@ -531,7 +636,8 @@ const FRAG_COLOR = /* glsl */ `
|
|
|
531
636
|
bool hasCustomColor = false;
|
|
532
637
|
if ( uBatchTexActive > 0 && s3mBid >= 0.0 ) {
|
|
533
638
|
// 用取整后的 id 采样:否则逐像素的插值误差会让纹素在边界上来回跳,同样出彩色噪点
|
|
534
|
-
|
|
639
|
+
float s3mU = ( s3mBid + 0.5 ) / uBatchTexWidth;
|
|
640
|
+
vec4 bState = s3mSampleBatchRow0( uBatchTex, uBatchTex2, vec2( s3mU, 0.1 ), uBatchMix );
|
|
535
641
|
if ( bState.a < -0.5 ) {
|
|
536
642
|
discard; // 属于被过滤隐藏的单体
|
|
537
643
|
}
|
|
@@ -542,7 +648,7 @@ const FRAG_COLOR = /* glsl */ `
|
|
|
542
648
|
}
|
|
543
649
|
// 单体独立透明度(状态纹理第 2 行 alpha,0 = 无覆盖)。
|
|
544
650
|
// 与全局 uOpacity(diffuseColor.a 初值)和图集贴图自带 alpha 逐级叠乘
|
|
545
|
-
vec4 bOffset =
|
|
651
|
+
vec4 bOffset = s3mSampleBatchRow1( uBatchTex, uBatchTex2, vec2( s3mU, 0.3 ), uBatchMix );
|
|
546
652
|
if ( bOffset.a > 0.0 ) {
|
|
547
653
|
diffuseColor.a *= bOffset.a;
|
|
548
654
|
}
|
|
@@ -643,6 +749,7 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
643
749
|
// 没有单体化信息的几何(batchId = -1)只能整块选中
|
|
644
750
|
const uSelWhole = { value: 0 };
|
|
645
751
|
const uBatchTex = { value: DUMMY_BATCH_TEX };
|
|
752
|
+
const uBatchTex2 = { value: DUMMY_BATCH_TEX };
|
|
646
753
|
const uBatchTexWidth = { value: 1.0 };
|
|
647
754
|
const uBatchTexActive = { value: 0 };
|
|
648
755
|
/** 单块瓦片的淡入系数:逐材质独享(每块网格一份材质),默认 1 = 完全显示 */
|
|
@@ -662,6 +769,7 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
662
769
|
uSelBatch,
|
|
663
770
|
uSelWhole,
|
|
664
771
|
uBatchTex,
|
|
772
|
+
uBatchTex2,
|
|
665
773
|
uBatchTexWidth,
|
|
666
774
|
uBatchTexActive,
|
|
667
775
|
uTileFade,
|
|
@@ -672,6 +780,7 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
672
780
|
|
|
673
781
|
shader.vertexShader = shader.vertexShader
|
|
674
782
|
.replace('#include <common>', `#include <common>\n${VERT_PARS}`)
|
|
783
|
+
.replace('#include <beginnormal_vertex>', `#include <beginnormal_vertex>\n${VERT_SETUP}`)
|
|
675
784
|
.replace('#include <begin_vertex>', `#include <begin_vertex>\n${VERT_ELEVATION}`)
|
|
676
785
|
.replace('#include <worldpos_vertex>', VERT_WORLD);
|
|
677
786
|
|
|
@@ -706,6 +815,7 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
706
815
|
mat.userData.uSelBatch = uSelBatch;
|
|
707
816
|
mat.userData.uSelWhole = uSelWhole;
|
|
708
817
|
mat.userData.uBatchTex = uBatchTex;
|
|
818
|
+
mat.userData.uBatchTex2 = uBatchTex2;
|
|
709
819
|
mat.userData.uBatchTexWidth = uBatchTexWidth;
|
|
710
820
|
mat.userData.uBatchTexActive = uBatchTexActive;
|
|
711
821
|
mat.userData.uTileFade = uTileFade;
|
|
@@ -722,26 +832,48 @@ const OUTLINE_VERT = /* glsl */ `
|
|
|
722
832
|
uniform float uWidth;
|
|
723
833
|
uniform float uElevation;
|
|
724
834
|
uniform sampler2D uBatchTex;
|
|
835
|
+
uniform sampler2D uBatchTex2;
|
|
836
|
+
uniform float uBatchMix;
|
|
725
837
|
uniform float uBatchTexWidth;
|
|
726
838
|
uniform int uBatchTexActive;
|
|
727
839
|
varying float vSel;
|
|
840
|
+
|
|
841
|
+
vec4 s3mSampleBatch( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
|
|
842
|
+
vec4 vA = texture2D( texA, uv );
|
|
843
|
+
if ( mixVal <= 0.0001 ) return vA;
|
|
844
|
+
vec4 vB = texture2D( texB, uv );
|
|
845
|
+
return mix( vA, vB, mixVal );
|
|
846
|
+
}
|
|
847
|
+
|
|
728
848
|
void main() {
|
|
729
849
|
vSel = max( step( 0.0, uSelBatch ) * step( abs( aBatchId - uSelBatch ), 0.5 ), uSelWhole );
|
|
730
850
|
vec3 s3mCustomOffset = vec3( 0.0 );
|
|
851
|
+
mat3 s3mRot = mat3( 1.0 );
|
|
852
|
+
vec3 s3mPivot = vec3( 0.0 );
|
|
731
853
|
if ( uBatchTexActive > 0 && aBatchId >= 0.0 ) {
|
|
732
|
-
|
|
854
|
+
float s3mU = ( aBatchId + 0.5 ) / uBatchTexWidth;
|
|
855
|
+
vec4 bState = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.1 ), uBatchMix );
|
|
733
856
|
if ( bState.a < -0.5 ) {
|
|
734
857
|
vSel = 0.0; // 过滤隐藏的单体不显示描边
|
|
735
858
|
} else {
|
|
736
|
-
vec4 bOffset =
|
|
859
|
+
vec4 bOffset = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.3 ), uBatchMix );
|
|
737
860
|
s3mCustomOffset = bOffset.xyz;
|
|
738
861
|
if ( s3mCustomOffset.z == 0.0 && bState.a > 0.0 ) {
|
|
739
862
|
s3mCustomOffset.z = bState.a;
|
|
740
863
|
}
|
|
864
|
+
vec4 r2 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.5 ), uBatchMix );
|
|
865
|
+
vec4 r3 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.7 ), uBatchMix );
|
|
866
|
+
vec4 r4 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.9 ), uBatchMix );
|
|
867
|
+
s3mRot = mat3(
|
|
868
|
+
vec3( r2.x, r3.x, r4.x ),
|
|
869
|
+
vec3( r2.y, r3.y, r4.y ),
|
|
870
|
+
vec3( r2.z, r3.z, r4.z )
|
|
871
|
+
);
|
|
872
|
+
s3mPivot = vec3( r2.w, r3.w, r4.w );
|
|
741
873
|
}
|
|
742
874
|
}
|
|
743
|
-
vec3 p = position + normal * uWidth * vSel;
|
|
744
|
-
p
|
|
875
|
+
vec3 p = position + ( s3mRot * normal ) * uWidth * vSel;
|
|
876
|
+
p = s3mRot * ( p - s3mPivot ) + s3mPivot + s3mCustomOffset;
|
|
745
877
|
p.z += vSel * uElevation;
|
|
746
878
|
gl_Position = projectionMatrix * modelViewMatrix * vec4( p, 1.0 );
|
|
747
879
|
}
|
|
@@ -763,6 +895,8 @@ export interface OutlineMaterial extends THREE.ShaderMaterial {
|
|
|
763
895
|
uWidth: { value: number };
|
|
764
896
|
uElevation: { value: number };
|
|
765
897
|
uBatchTex: { value: THREE.Texture };
|
|
898
|
+
uBatchTex2: { value: THREE.Texture };
|
|
899
|
+
uBatchMix: { value: number };
|
|
766
900
|
uBatchTexWidth: { value: number };
|
|
767
901
|
uBatchTexActive: { value: number };
|
|
768
902
|
uColor: { value: THREE.Color };
|
|
@@ -778,6 +912,8 @@ export function createOutlineMaterial(shared: StyleUniforms, initialWidth = 0.9)
|
|
|
778
912
|
uWidth: { value: initialWidth },
|
|
779
913
|
uElevation: { value: 0 },
|
|
780
914
|
uBatchTex: { value: DUMMY_BATCH_TEX },
|
|
915
|
+
uBatchTex2: { value: DUMMY_BATCH_TEX },
|
|
916
|
+
uBatchMix: shared.uBatchMix ?? { value: 0 },
|
|
781
917
|
uBatchTexWidth: { value: 1.0 },
|
|
782
918
|
uBatchTexActive: { value: 0 },
|
|
783
919
|
uColor: { value: shared.uSelColor.value.clone() },
|