s3m-three 0.4.22 → 0.4.24

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 (93) hide show
  1. package/dist/analysis/clipping.d.ts +63 -0
  2. package/dist/analysis/clipping.d.ts.map +1 -0
  3. package/dist/analysis/clipping.js +69 -0
  4. package/dist/analysis/clipping.js.map +1 -0
  5. package/dist/analysis/depth.d.ts +55 -0
  6. package/dist/analysis/depth.d.ts.map +1 -0
  7. package/dist/analysis/depth.js +99 -0
  8. package/dist/analysis/depth.js.map +1 -0
  9. package/dist/analysis/index.d.ts +21 -0
  10. package/dist/analysis/index.d.ts.map +1 -0
  11. package/dist/analysis/index.js +17 -0
  12. package/dist/analysis/index.js.map +1 -0
  13. package/dist/analysis/lineOfSight.d.ts +60 -0
  14. package/dist/analysis/lineOfSight.d.ts.map +1 -0
  15. package/dist/analysis/lineOfSight.js +56 -0
  16. package/dist/analysis/lineOfSight.js.map +1 -0
  17. package/dist/analysis/measure.d.ts +56 -0
  18. package/dist/analysis/measure.d.ts.map +1 -0
  19. package/dist/analysis/measure.js +95 -0
  20. package/dist/analysis/measure.js.map +1 -0
  21. package/dist/analysis/skyline.d.ts +55 -0
  22. package/dist/analysis/skyline.d.ts.map +1 -0
  23. package/dist/analysis/skyline.js +78 -0
  24. package/dist/analysis/skyline.js.map +1 -0
  25. package/dist/analysis/viewshed.d.ts +48 -0
  26. package/dist/analysis/viewshed.d.ts.map +1 -0
  27. package/dist/analysis/viewshed.js +115 -0
  28. package/dist/analysis/viewshed.js.map +1 -0
  29. package/dist/browser/s3m-three.iife.ext-three.js +52 -35
  30. package/dist/browser/s3m-three.iife.js +47 -30
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +3 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/render/DatasetManager.d.ts +13 -0
  36. package/dist/render/DatasetManager.d.ts.map +1 -1
  37. package/dist/render/DatasetManager.js +20 -0
  38. package/dist/render/DatasetManager.js.map +1 -1
  39. package/dist/render/GeometryBuilder.d.ts +11 -0
  40. package/dist/render/GeometryBuilder.d.ts.map +1 -1
  41. package/dist/render/GeometryBuilder.js +2 -1
  42. package/dist/render/GeometryBuilder.js.map +1 -1
  43. package/dist/render/S3MViewer.d.ts +159 -5
  44. package/dist/render/S3MViewer.d.ts.map +1 -1
  45. package/dist/render/S3MViewer.js +331 -13
  46. package/dist/render/S3MViewer.js.map +1 -1
  47. package/dist/render/StyleMaterial.d.ts +10 -1
  48. package/dist/render/StyleMaterial.d.ts.map +1 -1
  49. package/dist/render/StyleMaterial.js +9 -1
  50. package/dist/render/StyleMaterial.js.map +1 -1
  51. package/dist/render/TextureFactory.d.ts +23 -1
  52. package/dist/render/TextureFactory.d.ts.map +1 -1
  53. package/dist/render/TextureFactory.js +32 -8
  54. package/dist/render/TextureFactory.js.map +1 -1
  55. package/dist/render/analysis/DepthPass.d.ts +78 -0
  56. package/dist/render/analysis/DepthPass.d.ts.map +1 -0
  57. package/dist/render/analysis/DepthPass.js +209 -0
  58. package/dist/render/analysis/DepthPass.js.map +1 -0
  59. package/dist/render/viewer/LightRig.d.ts +21 -7
  60. package/dist/render/viewer/LightRig.d.ts.map +1 -1
  61. package/dist/render/viewer/LightRig.js +11 -13
  62. package/dist/render/viewer/LightRig.js.map +1 -1
  63. package/dist/render/viewer/PickService.d.ts +15 -0
  64. package/dist/render/viewer/PickService.d.ts.map +1 -1
  65. package/dist/render/viewer/PickService.js +41 -0
  66. package/dist/render/viewer/PickService.js.map +1 -1
  67. package/dist/render/viewer/types.d.ts +12 -0
  68. package/dist/render/viewer/types.d.ts.map +1 -1
  69. package/dist/render/viewer/types.js.map +1 -1
  70. package/dist/tileset/S3MTileset.d.ts +19 -0
  71. package/dist/tileset/S3MTileset.d.ts.map +1 -1
  72. package/dist/tileset/S3MTileset.js +16 -0
  73. package/dist/tileset/S3MTileset.js.map +1 -1
  74. package/docs/API.md +124 -3
  75. package/package.json +1 -1
  76. package/src/analysis/clipping.ts +135 -0
  77. package/src/analysis/depth.ts +126 -0
  78. package/src/analysis/index.ts +47 -0
  79. package/src/analysis/lineOfSight.ts +110 -0
  80. package/src/analysis/measure.ts +119 -0
  81. package/src/analysis/skyline.ts +125 -0
  82. package/src/analysis/viewshed.ts +160 -0
  83. package/src/index.ts +5 -0
  84. package/src/render/DatasetManager.ts +21 -0
  85. package/src/render/GeometryBuilder.ts +13 -1
  86. package/src/render/S3MViewer.ts +401 -14
  87. package/src/render/StyleMaterial.ts +16 -2
  88. package/src/render/TextureFactory.ts +50 -8
  89. package/src/render/analysis/DepthPass.ts +278 -0
  90. package/src/render/viewer/LightRig.ts +23 -13
  91. package/src/render/viewer/PickService.ts +47 -0
  92. package/src/render/viewer/types.ts +12 -0
  93. package/src/tileset/S3MTileset.ts +24 -0
@@ -0,0 +1,119 @@
1
+ /**
2
+ * 量算:距离 / 折线长 / 面积 / 高差。
3
+ *
4
+ * 全部是纯函数,输入输出都是**场景 ENU 米制坐标**(three 世界坐标),
5
+ * 不依赖 three、不依赖 WebGL,可以直接单测。
6
+ *
7
+ * 经纬度 ↔ ENU 的换算在 `S3MViewer` 里做(需要 `sceneAnchor` 与 `GeoAdapter`),
8
+ * 这一层只管算数 —— 这样投影口径变化不会污染数学。
9
+ */
10
+
11
+ /** 场景 ENU 米制坐标(three 世界坐标) */
12
+ export type Vec3 = [number, number, number];
13
+
14
+ /** 经纬度 + 绝对高程(米) */
15
+ export interface LngLatAlt {
16
+ lng: number;
17
+ lat: number;
18
+ /** 绝对高程(米),不是相对场景锚点的高度 */
19
+ altitude: number;
20
+ }
21
+
22
+ export function distance3D(a: Vec3, b: Vec3): number {
23
+ const dx = b[0] - a[0];
24
+ const dy = b[1] - a[1];
25
+ const dz = b[2] - a[2];
26
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
27
+ }
28
+
29
+ /** 水平距离(忽略高程差)—— 规划类口径常用 */
30
+ export function distanceHorizontal(a: Vec3, b: Vec3): number {
31
+ const dx = b[0] - a[0];
32
+ const dy = b[1] - a[1];
33
+ return Math.sqrt(dx * dx + dy * dy);
34
+ }
35
+
36
+ /** 高差(有符号:b 比 a 高为正) */
37
+ export function heightDifference(a: Vec3, b: Vec3): number {
38
+ return b[2] - a[2];
39
+ }
40
+
41
+ /** 折线长度(逐段求和);点少于 2 个返回 0 */
42
+ export function polylineLength(points: readonly Vec3[]): number {
43
+ let total = 0;
44
+ for (let i = 1; i < points.length; i++) total += distance3D(points[i - 1], points[i]);
45
+ return total;
46
+ }
47
+
48
+ /** 折线水平投影长度 */
49
+ export function polylineLengthHorizontal(points: readonly Vec3[]): number {
50
+ let total = 0;
51
+ for (let i = 1; i < points.length; i++) total += distanceHorizontal(points[i - 1], points[i]);
52
+ return total;
53
+ }
54
+
55
+ /** 多边形周长(首尾自动闭合,不要重复传首点) */
56
+ export function polygonPerimeter(points: readonly Vec3[]): number {
57
+ if (points.length < 2) return 0;
58
+ return polylineLength(points) + distance3D(points[points.length - 1], points[0]);
59
+ }
60
+
61
+ /**
62
+ * 多边形面积(Newell 法)。
63
+ *
64
+ * 对**共面**点给出真实面积;对起伏/不共面的点给出「投影到最佳拟合平面」的面积,
65
+ * 这也是绝大多数量算工具的口径。要水平投影面积用 `polygonAreaHorizontal`。
66
+ *
67
+ * 与点的绕向无关(取向量模长),所以不用担心用户顺时针还是逆时针点。
68
+ */
69
+ export function polygonArea(points: readonly Vec3[]): number {
70
+ const n = points.length;
71
+ if (n < 3) return 0;
72
+ let nx = 0;
73
+ let ny = 0;
74
+ let nz = 0;
75
+ for (let i = 0; i < n; i++) {
76
+ const a = points[i];
77
+ const b = points[(i + 1) % n];
78
+ nx += (a[1] - b[1]) * (a[2] + b[2]);
79
+ ny += (a[2] - b[2]) * (a[0] + b[0]);
80
+ nz += (a[0] - b[0]) * (a[1] + b[1]);
81
+ }
82
+ return Math.sqrt(nx * nx + ny * ny + nz * nz) / 2;
83
+ }
84
+
85
+ /**
86
+ * 水平投影面积(鞋带公式,忽略高程)。
87
+ *
88
+ * 与 `polygonArea` 的区别:一个坡面上的矩形,`polygonArea` 给坡面真实面积,
89
+ * 这里给它在水平面上的投影面积。建筑面积/用地面积按后者算。
90
+ */
91
+ export function polygonAreaHorizontal(points: readonly Vec3[]): number {
92
+ const n = points.length;
93
+ if (n < 3) return 0;
94
+ let sum = 0;
95
+ for (let i = 0; i < n; i++) {
96
+ const a = points[i];
97
+ const b = points[(i + 1) % n];
98
+ sum += a[0] * b[1] - b[0] * a[1];
99
+ }
100
+ return Math.abs(sum) / 2;
101
+ }
102
+
103
+ /** 量算结果:一次给全三种口径,避免调用方自己再算一遍 */
104
+ export interface MeasureResult {
105
+ /** 三维直线距离(米) */
106
+ distance3D: number;
107
+ /** 水平距离(米) */
108
+ distanceHorizontal: number;
109
+ /** 高差(米,有符号) */
110
+ heightDiff: number;
111
+ }
112
+
113
+ export function measureSegment(a: Vec3, b: Vec3): MeasureResult {
114
+ return {
115
+ distance3D: distance3D(a, b),
116
+ distanceHorizontal: distanceHorizontal(a, b),
117
+ heightDiff: heightDifference(a, b),
118
+ };
119
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * 天际线:从一张「从相机看出去」的深度图,按列抽出天际线曲线。
3
+ *
4
+ * 为什么是「按列」:天际线在屏幕上就是每列最上面那个几何像素的轮廓。
5
+ * 所以不需要额外的几何体 —— 一次深度 pass + 一次逐列扫描就够了,
6
+ * 结果直接是一串(方位角,仰角),画成 2D 曲线比生成 3D 多边形便宜得多。
7
+ *
8
+ * 只做数学,不碰 WebGL:深度图由 `render/analysis/DepthPass` 产出。
9
+ */
10
+ import { azimuthElevation, pixelToNdc, unpackRGBADepth, unprojectNdc } from './depth.js';
11
+ import type { Vec3 } from './measure.js';
12
+
13
+ export interface SkylineArgs {
14
+ /** RGBA8 深度图(three 的 RGBADepthPacking) */
15
+ rgba: ArrayLike<number>;
16
+ width: number;
17
+ height: number;
18
+ /** 视图投影矩阵的逆(列主序,与 `THREE.Matrix4.elements` 同序) */
19
+ inverseViewProj: ArrayLike<number>;
20
+ /** 相机在场景 ENU 里的位置 */
21
+ cameraPos: Vec3;
22
+ /**
23
+ * 背景判定阈值:深度 >= 它的像素视为天空(默认 0.9999)。
24
+ * 用 0.9999 而不是 1:远处地平线可能因为精度落在 0.99999 上。
25
+ */
26
+ skyDepth?: number;
27
+ /** 每隔多少列取一次(默认 1,即逐列) */
28
+ step?: number;
29
+ }
30
+
31
+ export interface SkylineResult {
32
+ /** 方位角(度,0 = 正北,顺时针),长度 = ceil(width / step) */
33
+ azimuths: Float32Array;
34
+ /** 对应列的仰角(度,0 = 水平) */
35
+ elevations: Float32Array;
36
+ /** 整条天际线的最大仰角(度) */
37
+ maxElevation: number;
38
+ /** 最小仰角(度);全是天空时为 0 */
39
+ minElevation: number;
40
+ /** 没有几何、直通天空的列占比(0~1)—— 开敞度的最粗口径 */
41
+ openRatio: number;
42
+ /**
43
+ * 每列天际线在**屏幕上的位置**(归一化 0~1,左上角为原点),
44
+ * 布局是 `[x0, y0, x1, y1, …]`,长度 = `cols * 2`。
45
+ *
46
+ * 这是画「地图上的天际线」最直接的输入:深度图本来就是屏幕空间的东西,
47
+ * 换算成角度只是为了给人看,换算回去还会多一次投影误差。
48
+ * 归一化是为了让调用方按自己的画布尺寸缩放(捕获分辨率可以比画布小)。
49
+ */
50
+ screen: Float32Array;
51
+ /**
52
+ * 每列是否有几何(1 = 有,0 = 纯天空)。
53
+ *
54
+ * 画线时要在这里断开:把纯天空的列也连进去,线会横穿天空,
55
+ * 看起来像天际线突然掉到画面底部。
56
+ */
57
+ hasGeometry: Uint8Array;
58
+ }
59
+
60
+ /**
61
+ * 逐列取「最上面的几何像素」,反投影成世界坐标后换算成仰角,
62
+ * 同时记下该像素的归一化屏幕位置(给「在地图上描线」用)。
63
+ *
64
+ * 没有几何的列记 0°(地平线)并计入 `openRatio`,而不是记 -90°:
65
+ * 天际线曲线在地平线处收口,画图时不会突然掉到画面底部。
66
+ */
67
+ export function skylineFromDepth(args: SkylineArgs): SkylineResult {
68
+ const { rgba, width, height, inverseViewProj, cameraPos } = args;
69
+ const skyDepth = args.skyDepth ?? 0.9999;
70
+ const step = Math.max(1, Math.floor(args.step ?? 1));
71
+ const cols = Math.ceil(width / step);
72
+
73
+ const azimuths = new Float32Array(cols);
74
+ const elevations = new Float32Array(cols);
75
+ const screen = new Float32Array(cols * 2);
76
+ const hasGeometry = new Uint8Array(cols);
77
+ let maxElevation = -90;
78
+ let minElevation = 90;
79
+ let open = 0;
80
+
81
+ for (let c = 0; c < cols; c++) {
82
+ const x = Math.min(width - 1, c * step);
83
+ let found = false;
84
+ for (let y = 0; y < height; y++) {
85
+ const off = (y * width + x) * 4;
86
+ const depth = unpackRGBADepth(rgba, off);
87
+ if (depth >= skyDepth) continue;
88
+
89
+ const [ndcX, ndcY] = pixelToNdc(x, y, width, height);
90
+ const p = unprojectNdc(ndcX, ndcY, depth, inverseViewProj);
91
+ const { azimuth, elevation } = azimuthElevation(cameraPos, p);
92
+ azimuths[c] = azimuth;
93
+ elevations[c] = elevation;
94
+ // 像素中心归一化:与捕获图同尺度,调用方乘自己的画布尺寸即可
95
+ screen[c * 2] = (x + 0.5) / width;
96
+ screen[c * 2 + 1] = (y + 0.5) / height;
97
+ hasGeometry[c] = 1;
98
+ if (elevation > maxElevation) maxElevation = elevation;
99
+ if (elevation < minElevation) minElevation = elevation;
100
+ found = true;
101
+ break;
102
+ }
103
+ if (!found) {
104
+ // 这一列没有几何:天际线落在水平线上
105
+ open++;
106
+ azimuths[c] = 0;
107
+ elevations[c] = 0;
108
+ screen[c * 2] = (x + 0.5) / width;
109
+ screen[c * 2 + 1] = 1; // 贴到画面底部,画线时按 hasGeometry 断开
110
+ hasGeometry[c] = 0;
111
+ if (0 > maxElevation) maxElevation = 0;
112
+ if (0 < minElevation) minElevation = 0;
113
+ }
114
+ }
115
+
116
+ return {
117
+ azimuths,
118
+ elevations,
119
+ maxElevation,
120
+ minElevation,
121
+ openRatio: cols > 0 ? open / cols : 0,
122
+ screen,
123
+ hasGeometry,
124
+ };
125
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * 360° 可视域:地面栅格版。
3
+ *
4
+ * 思路与「从观察点做阴影贴图」一致,但**判定在 CPU 上做**:
5
+ * GPU 只负责产出一个 `方向 → 最近遮挡距离` 的采样函数(6 面立方体深度图),
6
+ * 这里拿它在观察点周围的地面网格上逐格比较距离。
7
+ *
8
+ * 为什么不在片元里逐像素判定(建筑立面着色版):
9
+ * 那要把立方体深度注入到每种材质(pbr / lambert / 棱线 / 点云 / 泼溅)的 shader 里,
10
+ * 成本和风险高一个量级;地面栅格版能覆盖绝大多数规划类需求,且结果可以直接
11
+ * 当成一张贴图/热力图叠在地面上。
12
+ *
13
+ * 纯数学,不碰 WebGL:采样函数由 `render/analysis/DepthPass` 提供,单测里用假的。
14
+ */
15
+ import { unpackRGBADepth } from './depth.js';
16
+ import type { Vec3 } from './measure.js';
17
+
18
+ export interface ViewshedOptions {
19
+ /** 分析半径(米) */
20
+ radius: number;
21
+ /** 栅格边长(格数,默认 96;96² = 9216 个采样点,CPU 侧约 1~3 ms) */
22
+ grid?: number;
23
+ /**
24
+ * 遮挡容差(米,默认 0.5)。
25
+ *
26
+ * 采样点到观察点的距离要**小于等于**该方向上的遮挡距离才算可见;
27
+ * 留一点容差是为了吸收深度图的离散化误差与地面自身的起伏。
28
+ */
29
+ bias?: number;
30
+ /** 地面高程(场景 ENU 的 z,默认 0) */
31
+ groundZ?: number;
32
+ }
33
+
34
+ export interface ViewshedResult {
35
+ /**
36
+ * `grid × grid` 的可见性,行优先(`y * grid + x`):
37
+ * `1` = 可见,`0` = 被遮挡,`-1` = 超出半径或没有数据。
38
+ */
39
+ raster: Float32Array;
40
+ grid: number;
41
+ /** 栅格左下角(场景 ENU 米) */
42
+ minX: number;
43
+ minY: number;
44
+ /** 每格边长(米) */
45
+ cellSize: number;
46
+ /** 有效格子里可见的比例(0~1) */
47
+ visibleRatio: number;
48
+ }
49
+
50
+ /**
51
+ * @param origin 观察点(场景 ENU 米,已含眼高)
52
+ * @param sampleDistance 沿单位方向取「最近的遮挡距离」;该方向没有几何时返回 `Infinity`
53
+ */
54
+ export function computeViewshed(
55
+ origin: Vec3,
56
+ sampleDistance: (dir: Vec3) => number,
57
+ opts: ViewshedOptions
58
+ ): ViewshedResult {
59
+ const radius = Math.max(1e-3, opts.radius);
60
+ const grid = Math.max(2, Math.floor(opts.grid ?? 96));
61
+ const bias = Math.max(0, opts.bias ?? 0.5);
62
+ const groundZ = opts.groundZ ?? 0;
63
+
64
+ const cellSize = (radius * 2) / grid;
65
+ const minX = origin[0] - radius;
66
+ const minY = origin[1] - radius;
67
+ const raster = new Float32Array(grid * grid);
68
+
69
+ let visible = 0;
70
+ let counted = 0;
71
+
72
+ for (let iy = 0; iy < grid; iy++) {
73
+ const wy = minY + (iy + 0.5) * cellSize;
74
+ for (let ix = 0; ix < grid; ix++) {
75
+ const wx = minX + (ix + 0.5) * cellSize;
76
+ const dx = wx - origin[0];
77
+ const dy = wy - origin[1];
78
+ const dz = groundZ - origin[2];
79
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
80
+ const idx = iy * grid + ix;
81
+
82
+ if (dist > radius || dist < 1e-6) {
83
+ raster[idx] = -1;
84
+ continue;
85
+ }
86
+
87
+ const dir: Vec3 = [dx / dist, dy / dist, dz / dist];
88
+ const blockedAt = sampleDistance(dir);
89
+ const ok = dist <= blockedAt + bias;
90
+ raster[idx] = ok ? 1 : 0;
91
+ counted++;
92
+ if (ok) visible++;
93
+ }
94
+ }
95
+
96
+ return {
97
+ raster,
98
+ grid,
99
+ minX,
100
+ minY,
101
+ cellSize,
102
+ visibleRatio: counted > 0 ? visible / counted : 0,
103
+ };
104
+ }
105
+
106
+ /**
107
+ * 从 6 面立方体深度图构造 `sampleDistance`。
108
+ *
109
+ * 立方体每个面是 `size × size` 的 RGBA8,存的是 `距离 / maxDist` 的打包值
110
+ * (打包方式同 three 的 `packDepthToRGBA`,与 `RGBADepthPacking` 一致)。
111
+ * 采样用最近邻 —— 可视域本身是栅格化的,插值不会让结果更准。
112
+ *
113
+ * 面序与 three 的 `WebGLCubeRenderTarget` / `CubeCamera` 一致:
114
+ * `+x, -x, +y, -y, +z, -z`。
115
+ */
116
+ export function makeCubeDistanceSampler(
117
+ faces: readonly (ArrayLike<number> | null | undefined)[],
118
+ size: number,
119
+ maxDist: number
120
+ ): (dir: Vec3) => number {
121
+ return (dir) => {
122
+ const [x, y, z] = dir;
123
+ const ax = Math.abs(x);
124
+ const ay = Math.abs(y);
125
+ const az = Math.abs(z);
126
+
127
+ // 主轴决定落在哪个面:|x| 最大 → ±x 面,依此类推
128
+ let faceIndex: number;
129
+ let u: number;
130
+ let v: number;
131
+ if (ax >= ay && ax >= az) {
132
+ faceIndex = x >= 0 ? 0 : 1;
133
+ const d = ax;
134
+ // three 的立方体面约定:+x 面 u = -z/d, v = -y/d
135
+ u = x >= 0 ? -z / d : z / d;
136
+ v = -y / d;
137
+ } else if (ay >= az) {
138
+ faceIndex = y >= 0 ? 2 : 3;
139
+ const d = ay;
140
+ u = x / d;
141
+ v = y >= 0 ? z / d : -z / d;
142
+ } else {
143
+ faceIndex = z >= 0 ? 4 : 5;
144
+ const d = az;
145
+ // 面朝向相反时 u 轴也翻向,否则 -z 面会左右镜像(可视域会镜像到错误一侧)
146
+ u = z >= 0 ? x / d : -x / d;
147
+ v = -y / d;
148
+ }
149
+
150
+ const face = faces[faceIndex];
151
+ if (!face) return Infinity;
152
+ const px = Math.min(size - 1, Math.max(0, Math.floor(((u + 1) / 2) * size)));
153
+ const py = Math.min(size - 1, Math.max(0, Math.floor(((v + 1) / 2) * size)));
154
+ const d = unpackRGBADepth(face, (py * size + px) * 4);
155
+ // 打包值是归一化距离:1 = 远平面(该方向没有几何)。three 的 pack 对 v>=1
156
+ // 直接返回全白,解包正好是 1.0,所以阈值取 0.9999 就够。
157
+ if (d >= 0.9999) return Infinity;
158
+ return d * maxDist;
159
+ };
160
+ }
package/src/index.ts CHANGED
@@ -138,6 +138,11 @@ export { setMaterialQuality, getMaterialQuality } from './render/StyleMaterial.j
138
138
 
139
139
  export { Picker } from './render/Picker.js';
140
140
  export { GpuEdgePass } from './render/GpuEdgePass.js';
141
+
142
+ /* 三维空间分析内核:纯数学,不依赖 WebGL,可单独用于服务端 / Worker */
143
+ export * from './analysis/index.js';
144
+ export { captureSkylineDepth, captureRadialCube, buildCubeCameras, CUBE_FACES } from './render/analysis/DepthPass.js';
145
+ export type { DepthCaptureContext, SkylineCapture } from './render/analysis/DepthPass.js';
141
146
  export type { GpuEdgeParams } from './render/GpuEdgePass.js';
142
147
  export type { PickHit } from './render/Picker.js';
143
148
 
@@ -13,6 +13,7 @@ import type { AuthenticateFn } from '../core/AuthFetch.js';
13
13
  import type { Anchor } from '../core/types.js';
14
14
  import type { UnsupportedPolicy } from '../core/degradations.js';
15
15
  import { worldFromLngLat, type GeoAdapter } from './GeoAdapter.js';
16
+ import type { TextureCreateOptions } from './TextureFactory.js';
16
17
 
17
18
  export type DatasetAlignment = 'origin' | 'bottom' | 'top' | 'center';
18
19
 
@@ -118,6 +119,23 @@ export class DatasetManager {
118
119
  }
119
120
  }
120
121
 
122
+ /**
123
+ * 图层级渲染 profile 默认值(颜色管线 / 纹理参数)。
124
+ * 与 `defaultMaterialQuality` 不同,它没有数据集级覆盖 —— 管线和纹理解码是
125
+ * 图层级的观感设定,逐数据集混着来只会让同一屏出现两套色彩空间。
126
+ */
127
+ defaultColorPipeline: 'linear' | 'direct' | undefined = undefined;
128
+ defaultTextureOptions: TextureCreateOptions | undefined = undefined;
129
+
130
+ /** 图层 profile 变更:同步给已存在的数据集(之后加载的瓦片才用新参数) */
131
+ setRenderProfile(p: { colorPipeline?: 'linear' | 'direct'; textureOptions?: TextureCreateOptions }): void {
132
+ if (p.colorPipeline !== undefined) this.defaultColorPipeline = p.colorPipeline;
133
+ if (p.textureOptions !== undefined) this.defaultTextureOptions = p.textureOptions;
134
+ for (const entry of this.datasets.values()) {
135
+ entry.tileset?.setRenderProfile(p);
136
+ }
137
+ }
138
+
121
139
  get(id: string): S3MDatasetEntry | undefined {
122
140
  return this.datasets.get(id);
123
141
  }
@@ -250,6 +268,9 @@ export class DatasetManager {
250
268
  polygonOffset: ds.polygonOffset,
251
269
  // 数据集 > 图层 > 模块级默认(undefined 一路传下去,由 createStyledMaterial 回落)
252
270
  materialQuality: ds.materialQuality ?? this.defaultMaterialQuality,
271
+ // 颜色管线 / 纹理参数只有图层级(见 setRenderProfile 注释)
272
+ colorPipeline: this.defaultColorPipeline,
273
+ textureOptions: this.defaultTextureOptions,
253
274
  useWorker: ds.useWorker,
254
275
  authenticate: ds.authenticate ?? authenticate,
255
276
  unsupported: ds.unsupported,
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import * as THREE from 'three';
15
15
  import { createTexture } from './TextureFactory.js';
16
+ import type { TextureCreateOptions } from './TextureFactory.js';
16
17
  import { createSharedUniforms, createStyledMaterial } from './StyleMaterial.js';
17
18
  import { isTransparentMaterial, minVertexAlpha } from './materialAlpha.js';
18
19
  import type { StyledMaterial } from './StyleMaterial.js';
@@ -367,6 +368,16 @@ export interface BuildTileOptions {
367
368
  * 由 `S3MTileset` 从图层 / 数据集一路带下来,所以同页多个图层可以各用各的。
368
369
  */
369
370
  materialQuality?: 'pbr' | 'lambert';
371
+ /**
372
+ * 颜色管线,透传给 `createStyledMaterial`。不传 = 读模块级默认。
373
+ * 由 `S3MTileset` 从图层的渲染 profile 带下来。
374
+ */
375
+ colorPipeline?: 'linear' | 'direct';
376
+ /**
377
+ * 纹理创建参数(sRGB 解码 / 尺寸上限 / 各向异性上限),透传给 `createTexture`。
378
+ * 不传 = 各自读模块级默认。同样由图层的渲染 profile 带下来。
379
+ */
380
+ textureOptions?: TextureCreateOptions;
370
381
  }
371
382
 
372
383
  export interface BuildTileResult {
@@ -426,7 +437,7 @@ export async function buildTileObjects(
426
437
  if (prevPixels >= (tex.width ?? 0) * (tex.height ?? 0)) continue;
427
438
  textureByCode.delete(key);
428
439
  }
429
- const { texture, decoded } = await createTexture(tex);
440
+ const { texture, decoded } = await createTexture(tex, options.textureOptions);
430
441
  if (texture) {
431
442
  textureByCode.set(key, texture);
432
443
  usedTextureKeys.push(key);
@@ -550,6 +561,7 @@ export async function buildTileObjects(
550
561
  secMap,
551
562
  // 图层 / 数据集的档位;undefined 时 createStyledMaterial 自己回落到模块级默认
552
563
  materialQuality: options.materialQuality,
564
+ colorPipeline: options.colorPipeline,
553
565
  });
554
566
  // 用户配置的深度偏移(共面摩尔纹 / 重合面归属的人工定序,见 BuildTileOptions 注释)
555
567
  if (options.polygonOffset) {