s3m-three 0.4.23 → 0.4.25

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 (67) 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/assets/{s3mWorker-DvAUA8NJ.js → s3mWorker-jc9Oly5K.js} +1 -1
  30. package/dist/browser/s3m-three.iife.ext-three.js +21 -4
  31. package/dist/browser/s3m-three.iife.js +21 -4
  32. package/dist/core/DXTTextureDecode.d.ts +5 -2
  33. package/dist/core/DXTTextureDecode.d.ts.map +1 -1
  34. package/dist/core/DXTTextureDecode.js +17 -7
  35. package/dist/core/DXTTextureDecode.js.map +1 -1
  36. package/dist/index.d.ts +3 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +3 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/render/S3MViewer.d.ts +106 -0
  41. package/dist/render/S3MViewer.d.ts.map +1 -1
  42. package/dist/render/S3MViewer.js +174 -1
  43. package/dist/render/S3MViewer.js.map +1 -1
  44. package/dist/render/analysis/DepthPass.d.ts +78 -0
  45. package/dist/render/analysis/DepthPass.d.ts.map +1 -0
  46. package/dist/render/analysis/DepthPass.js +209 -0
  47. package/dist/render/analysis/DepthPass.js.map +1 -0
  48. package/dist/render/viewer/PickService.d.ts +17 -0
  49. package/dist/render/viewer/PickService.d.ts.map +1 -1
  50. package/dist/render/viewer/PickService.js +42 -1
  51. package/dist/render/viewer/PickService.js.map +1 -1
  52. package/dist/worker/s3mWorker.js +1 -1
  53. package/dist/worker/s3mWorker.js.map +1 -1
  54. package/docs/API.md +100 -0
  55. package/package.json +1 -1
  56. package/src/analysis/clipping.ts +135 -0
  57. package/src/analysis/depth.ts +126 -0
  58. package/src/analysis/index.ts +47 -0
  59. package/src/analysis/lineOfSight.ts +110 -0
  60. package/src/analysis/measure.ts +119 -0
  61. package/src/analysis/skyline.ts +125 -0
  62. package/src/analysis/viewshed.ts +160 -0
  63. package/src/core/DXTTextureDecode.ts +17 -7
  64. package/src/index.ts +5 -0
  65. package/src/render/S3MViewer.ts +233 -2
  66. package/src/render/analysis/DepthPass.ts +278 -0
  67. package/src/render/viewer/PickService.ts +48 -1
@@ -0,0 +1,63 @@
1
+ /**
2
+ * 裁剪 / 剖切的纯数学部分:把「平面 / Box / 十字」三种交互输入翻译成一组半空间。
3
+ *
4
+ * 与 three 的 `THREE.Plane` 同构(`normal` 单位化,`constant = -normal·point`),
5
+ * 判据是 three 的口径:`normal·p + constant >= 0` 的片元**保留**,否则丢弃。
6
+ * 所以这里的法线一律朝「保留侧」。
7
+ *
8
+ * 不依赖 three,`S3MViewer.setClipping` 再把结果转成 `THREE.Plane` 挂到
9
+ * `renderer.clippingPlanes`(每个图层一个 renderer,天然图层级)。
10
+ *
11
+ * 已知边界:
12
+ * - 半空间求交只能表达**凸**区域,凹多边形要多组并集(three 的 clippingPlanes
13
+ * 是「全部满足才保留」,做不到并集),见 `docs/API.md` 的说明;
14
+ * - 不做封口:剖切后看到的是背面/空壳,封口要靠 stencil 或背面重绘,不在这一层。
15
+ */
16
+ import type { Vec3 } from './measure.js';
17
+ /** 与 `THREE.Plane` 同构:`normal` 必须单位化 */
18
+ export interface PlaneLike {
19
+ normal: Vec3;
20
+ constant: number;
21
+ }
22
+ /** 过一个点、法线指向**保留侧**的平面 */
23
+ export declare function planeFromPointNormal(normal: Vec3, point: Vec3): PlaneLike;
24
+ /** 点是否在保留侧(含边界);用于调试与单测 */
25
+ export declare function isPointKept(planes: readonly PlaneLike[], p: Vec3): boolean;
26
+ /** 平面裁剪:法线指向保留的一侧,切掉另一侧 */
27
+ export interface ClipPlaneOptions {
28
+ type: 'plane';
29
+ /** 指向保留侧的法线(不必单位化) */
30
+ normal: Vec3;
31
+ /** 平面过的一个点 */
32
+ point: Vec3;
33
+ }
34
+ /** Box 裁剪:保留盒内的部分 */
35
+ export interface ClipBoxOptions {
36
+ type: 'box';
37
+ /** 对角点(顺序无所谓,内部会取 min/max) */
38
+ a: Vec3;
39
+ b: Vec3;
40
+ }
41
+ /**
42
+ * 十字剖切:用两个竖直面切掉一个象限,露出两个互相垂直的剖面。
43
+ *
44
+ * `keepX` / `keepY` 指保留哪个方向:`1` 保留 +x 侧,`-1` 保留 -x 侧。
45
+ * 加 `horizontal: true` 再多一个水平面,变成「切掉一个角」的三面剖切。
46
+ */
47
+ export interface ClipCrossOptions {
48
+ type: 'cross';
49
+ center: Vec3;
50
+ keepX: 1 | -1;
51
+ keepY: 1 | -1;
52
+ /** 是否再叠一个水平面(在 center.z 处切掉上方),默认 false */
53
+ horizontal?: boolean;
54
+ /** 水平面保留哪一侧:`1` 保留上方,`-1` 保留下方,默认 -1 */
55
+ keepZ?: 1 | -1;
56
+ }
57
+ export type ClipOptions = ClipPlaneOptions | ClipBoxOptions | ClipCrossOptions;
58
+ export declare function planeClipping(opt: Omit<ClipPlaneOptions, 'type'>): PlaneLike[];
59
+ export declare function boxClipping(opt: Omit<ClipBoxOptions, 'type'>): PlaneLike[];
60
+ export declare function crossClipping(opt: Omit<ClipCrossOptions, 'type'>): PlaneLike[];
61
+ /** 统一入口:把交互输入翻成半空间列表 */
62
+ export declare function buildClipPlanes(opt: ClipOptions): PlaneLike[];
63
+ //# sourceMappingURL=clipping.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipping.d.ts","sourceRoot":"","sources":["../../src/analysis/clipping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,wCAAwC;AACxC,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,IAAI,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAYD,0BAA0B;AAC1B,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,SAAS,CAGzE;AAED,2BAA2B;AAC3B,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,EAAE,CAAC,EAAE,IAAI,GAAG,OAAO,CAK1E;AAID,2BAA2B;AAC3B,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,sBAAsB;IACtB,MAAM,EAAE,IAAI,CAAC;IACb,cAAc;IACd,KAAK,EAAE,IAAI,CAAC;CACb;AAED,qBAAqB;AACrB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,KAAK,CAAC;IACZ,8BAA8B;IAC9B,CAAC,EAAE,IAAI,CAAC;IACR,CAAC,EAAE,IAAI,CAAC;CACT;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACd,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACd,2CAA2C;IAC3C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,wCAAwC;IACxC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAChB;AAED,MAAM,MAAM,WAAW,GAAG,gBAAgB,GAAG,cAAc,GAAG,gBAAgB,CAAC;AAI/E,wBAAgB,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,GAAG,SAAS,EAAE,CAE9E;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,GAAG,SAAS,EAAE,CAoB1E;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,GAAG,SAAS,EAAE,CAS9E;AAED,wBAAwB;AACxB,wBAAgB,eAAe,CAAC,GAAG,EAAE,WAAW,GAAG,SAAS,EAAE,CAS7D"}
@@ -0,0 +1,69 @@
1
+ function normalize(v) {
2
+ const len = Math.hypot(v[0], v[1], v[2]);
3
+ if (len < 1e-9)
4
+ return [0, 0, 1];
5
+ return [v[0] / len, v[1] / len, v[2] / len];
6
+ }
7
+ function dot(a, b) {
8
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
9
+ }
10
+ /** 过一个点、法线指向**保留侧**的平面 */
11
+ export function planeFromPointNormal(normal, point) {
12
+ const n = normalize(normal);
13
+ return { normal: n, constant: -dot(n, point) };
14
+ }
15
+ /** 点是否在保留侧(含边界);用于调试与单测 */
16
+ export function isPointKept(planes, p) {
17
+ for (const pl of planes) {
18
+ if (dot(pl.normal, p) + pl.constant < 0)
19
+ return false;
20
+ }
21
+ return true;
22
+ }
23
+ /* ------------------------------------------------------------------ 生成 */
24
+ export function planeClipping(opt) {
25
+ return [planeFromPointNormal(opt.normal, opt.point)];
26
+ }
27
+ export function boxClipping(opt) {
28
+ const min = [
29
+ Math.min(opt.a[0], opt.b[0]),
30
+ Math.min(opt.a[1], opt.b[1]),
31
+ Math.min(opt.a[2], opt.b[2]),
32
+ ];
33
+ const max = [
34
+ Math.max(opt.a[0], opt.b[0]),
35
+ Math.max(opt.a[1], opt.b[1]),
36
+ Math.max(opt.a[2], opt.b[2]),
37
+ ];
38
+ // 六个面,法线一律朝盒内 —— 六个半空间求交就是「盒内」
39
+ return [
40
+ { normal: [1, 0, 0], constant: -min[0] },
41
+ { normal: [-1, 0, 0], constant: max[0] },
42
+ { normal: [0, 1, 0], constant: -min[1] },
43
+ { normal: [0, -1, 0], constant: max[1] },
44
+ { normal: [0, 0, 1], constant: -min[2] },
45
+ { normal: [0, 0, -1], constant: max[2] },
46
+ ];
47
+ }
48
+ export function crossClipping(opt) {
49
+ const planes = [
50
+ planeFromPointNormal([opt.keepX, 0, 0], opt.center),
51
+ planeFromPointNormal([0, opt.keepY, 0], opt.center),
52
+ ];
53
+ if (opt.horizontal) {
54
+ planes.push(planeFromPointNormal([0, 0, opt.keepZ ?? -1], opt.center));
55
+ }
56
+ return planes;
57
+ }
58
+ /** 统一入口:把交互输入翻成半空间列表 */
59
+ export function buildClipPlanes(opt) {
60
+ switch (opt.type) {
61
+ case 'plane':
62
+ return planeClipping(opt);
63
+ case 'box':
64
+ return boxClipping(opt);
65
+ case 'cross':
66
+ return crossClipping(opt);
67
+ }
68
+ }
69
+ //# sourceMappingURL=clipping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipping.js","sourceRoot":"","sources":["../../src/analysis/clipping.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * 深度图回读的数学部分:解包 + 反投影。
3
+ *
4
+ * GPU 那边只能把深度渲染成 RGBA8(three 的 `RGBADepthPacking`),
5
+ * 回读到 CPU 后要还原成 0~1 的窗口深度,再反投影成世界坐标 —— 这两步都在这里,
6
+ * 与 WebGL 无关,可以用构造出来的矩阵直接单测。
7
+ *
8
+ * 打包/解包的常数照抄 three 的 `packing.glsl.js`,不要自己改:
9
+ * 改一个常数,回读出来的深度就会整体偏移,而画面看起来完全正常。
10
+ */
11
+ import type { Vec3 } from './measure.js';
12
+ /**
13
+ * 把 0~1 的深度按 three 的 `RGBADepthPacking` 打包成 4 个字节(0~255)。
14
+ *
15
+ * 严格照抄 `packing.glsl.js` 的 `packDepthToRGBA`,**不要凭直觉改**:
16
+ * - `v <= 0` 返回全黑、`v >= 1` 返回**全白**(这两条特判是精度链条的一部分,
17
+ * 也正好让「远平面」和「清屏白」解包出来都是 1.0,背景判定因此简单);
18
+ * - 中间值是 `modf` 链拆出来的 32 位定点,通道顺序是 (粗 → 细) 的 r/g/b/a。
19
+ *
20
+ * 精度:解包误差约 1e-5(受 g 通道 8 位量化限制),换算成 141m 处的位置误差约 0.2m。
21
+ */
22
+ export declare function packRGBADepth(depth: number): [number, number, number, number];
23
+ /**
24
+ * 解包:RGBA 四个字节 → 0~1 的窗口深度(0 = 近平面,1 = 远平面 / 背景)。
25
+ *
26
+ * 与 `packing.glsl.js` 的 `unpackRGBAToDepth` 同式:
27
+ * `dot(rgba, (UnpackDownscale/1, UnpackDownscale/256, UnpackDownscale/65536, 1/2^24))`。
28
+ *
29
+ * @param rgba 回读出来的像素缓冲
30
+ * @param offset 字节偏移(第 i 个像素就是 i*4)
31
+ */
32
+ export declare function unpackRGBADepth(rgba: ArrayLike<number>, offset?: number): number;
33
+ /**
34
+ * NDC + 窗口深度 → 世界坐标。
35
+ *
36
+ * @param ndcX -1~1(屏幕左侧到右侧)
37
+ * @param ndcY -1~1(屏幕底部到顶部;注意与像素行号反向)
38
+ * @param depth01 0~1 的窗口深度
39
+ * @param inverseViewProj 视图投影矩阵的逆(列主序 16 元,与 `THREE.Matrix4.elements` 同序)
40
+ */
41
+ export declare function unprojectNdc(ndcX: number, ndcY: number, depth01: number, inverseViewProj: ArrayLike<number>): Vec3;
42
+ /** 像素坐标(左上角为 0,0)→ NDC */
43
+ export declare function pixelToNdc(px: number, py: number, width: number, height: number): [number, number];
44
+ /**
45
+ * 把「观察点 → 目标点」拆成方位角 / 仰角(度)。
46
+ *
47
+ * 方位角:0 = 正北,顺时针增大(与地图 bearing 一致),ENU 下 x 向东、y 向北。
48
+ * 仰角:0 = 水平,正值向上,负值向下。
49
+ */
50
+ export declare function azimuthElevation(from: Vec3, to: Vec3): {
51
+ azimuth: number;
52
+ elevation: number;
53
+ distance: number;
54
+ };
55
+ //# sourceMappingURL=depth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"depth.d.ts","sourceRoot":"","sources":["../../src/analysis/depth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAWzC;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAiB7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,SAAI,GAAG,MAAM,CAW3E;AAQD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,SAAS,CAAC,MAAM,CAAC,GACjC,IAAI,CASN;AAED,0BAA0B;AAC1B,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAElG;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,IAAI,EACV,EAAE,EAAE,IAAI,GACP;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAS1D"}
@@ -0,0 +1,99 @@
1
+ /** three: `PackUpscale = 256 / 255` */
2
+ const PACK_UPSCALE = 256 / 255;
3
+ /** three: `UnpackDownscale = 255 / 256` */
4
+ const UNPACK_DOWNSCALE = 255 / 256;
5
+ /** three: `ShiftRight8 = 1 / 256` */
6
+ const SHIFT_RIGHT_8 = 1 / 256;
7
+ /** three: `PackFactors = (1, 256, 65536, 16777216)` */
8
+ const PACK_FACTOR_A = 16777216;
9
+ /**
10
+ * 把 0~1 的深度按 three 的 `RGBADepthPacking` 打包成 4 个字节(0~255)。
11
+ *
12
+ * 严格照抄 `packing.glsl.js` 的 `packDepthToRGBA`,**不要凭直觉改**:
13
+ * - `v <= 0` 返回全黑、`v >= 1` 返回**全白**(这两条特判是精度链条的一部分,
14
+ * 也正好让「远平面」和「清屏白」解包出来都是 1.0,背景判定因此简单);
15
+ * - 中间值是 `modf` 链拆出来的 32 位定点,通道顺序是 (粗 → 细) 的 r/g/b/a。
16
+ *
17
+ * 精度:解包误差约 1e-5(受 g 通道 8 位量化限制),换算成 141m 处的位置误差约 0.2m。
18
+ */
19
+ export function packRGBADepth(depth) {
20
+ const v = Math.min(1, Math.max(0, depth));
21
+ if (v <= 0)
22
+ return [0, 0, 0, 0];
23
+ if (v >= 1)
24
+ return [255, 255, 255, 255];
25
+ const af = modf(v * PACK_FACTOR_A);
26
+ const b = modf(af.int * SHIFT_RIGHT_8);
27
+ const g = modf(b.int * SHIFT_RIGHT_8);
28
+ const toByte = (x) => Math.max(0, Math.min(255, Math.round(x * 255)));
29
+ return [
30
+ // r = floor(v*256)/255 —— 乘回 255 就是整数本身
31
+ toByte(g.int / 255),
32
+ toByte(g.frac * PACK_UPSCALE),
33
+ toByte(b.frac * PACK_UPSCALE),
34
+ toByte(af.frac),
35
+ ];
36
+ }
37
+ /**
38
+ * 解包:RGBA 四个字节 → 0~1 的窗口深度(0 = 近平面,1 = 远平面 / 背景)。
39
+ *
40
+ * 与 `packing.glsl.js` 的 `unpackRGBAToDepth` 同式:
41
+ * `dot(rgba, (UnpackDownscale/1, UnpackDownscale/256, UnpackDownscale/65536, 1/2^24))`。
42
+ *
43
+ * @param rgba 回读出来的像素缓冲
44
+ * @param offset 字节偏移(第 i 个像素就是 i*4)
45
+ */
46
+ export function unpackRGBADepth(rgba, offset = 0) {
47
+ const r = rgba[offset] / 255;
48
+ const g = rgba[offset + 1] / 255;
49
+ const b = rgba[offset + 2] / 255;
50
+ const a = rgba[offset + 3] / 255;
51
+ return (r * UNPACK_DOWNSCALE +
52
+ g * (UNPACK_DOWNSCALE / 256) +
53
+ b * (UNPACK_DOWNSCALE / 65536) +
54
+ a / PACK_FACTOR_A);
55
+ }
56
+ /** GLSL `modf`:返回小数部分,整数部分写进 `int` */
57
+ function modf(x) {
58
+ const int = Math.floor(x);
59
+ return { frac: x - int, int };
60
+ }
61
+ /**
62
+ * NDC + 窗口深度 → 世界坐标。
63
+ *
64
+ * @param ndcX -1~1(屏幕左侧到右侧)
65
+ * @param ndcY -1~1(屏幕底部到顶部;注意与像素行号反向)
66
+ * @param depth01 0~1 的窗口深度
67
+ * @param inverseViewProj 视图投影矩阵的逆(列主序 16 元,与 `THREE.Matrix4.elements` 同序)
68
+ */
69
+ export function unprojectNdc(ndcX, ndcY, depth01, inverseViewProj) {
70
+ const z = depth01 * 2 - 1;
71
+ const m = inverseViewProj;
72
+ const x = m[0] * ndcX + m[4] * ndcY + m[8] * z + m[12];
73
+ const y = m[1] * ndcX + m[5] * ndcY + m[9] * z + m[13];
74
+ const zz = m[2] * ndcX + m[6] * ndcY + m[10] * z + m[14];
75
+ const w = m[3] * ndcX + m[7] * ndcY + m[11] * z + m[15];
76
+ const iw = Math.abs(w) > 1e-12 ? 1 / w : 1;
77
+ return [x * iw, y * iw, zz * iw];
78
+ }
79
+ /** 像素坐标(左上角为 0,0)→ NDC */
80
+ export function pixelToNdc(px, py, width, height) {
81
+ return [((px + 0.5) / width) * 2 - 1, 1 - ((py + 0.5) / height) * 2];
82
+ }
83
+ /**
84
+ * 把「观察点 → 目标点」拆成方位角 / 仰角(度)。
85
+ *
86
+ * 方位角:0 = 正北,顺时针增大(与地图 bearing 一致),ENU 下 x 向东、y 向北。
87
+ * 仰角:0 = 水平,正值向上,负值向下。
88
+ */
89
+ export function azimuthElevation(from, to) {
90
+ const dx = to[0] - from[0];
91
+ const dy = to[1] - from[1];
92
+ const dz = to[2] - from[2];
93
+ const horizontal = Math.hypot(dx, dy);
94
+ const distance = Math.hypot(horizontal, dz);
95
+ const azimuth = ((Math.atan2(dx, dy) * 180) / Math.PI + 360) % 360;
96
+ const elevation = (Math.atan2(dz, horizontal) * 180) / Math.PI;
97
+ return { azimuth, elevation, distance };
98
+ }
99
+ //# sourceMappingURL=depth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"depth.js","sourceRoot":"","sources":["../../src/analysis/depth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 三维空间分析内核(宿主无关、不依赖 WebGL)。
3
+ *
4
+ * 分工:
5
+ * - 本目录只做**纯数学**:量算、通视判定、裁剪半空间生成。可以直接单测。
6
+ * - 需要 GPU 的部分(贴地采样、天际线、可视域、阴影)在 `render/` 下,
7
+ * 复用 `Picker` 的离屏 pass 与状态恢复。
8
+ * - 交互(取点、拖拽、标签绘制)属于应用层,不进这个包。
9
+ */
10
+ export { distance3D, distanceHorizontal, heightDifference, polylineLength, polylineLengthHorizontal, polygonPerimeter, polygonArea, polygonAreaHorizontal, measureSegment, } from './measure.js';
11
+ export type { Vec3, LngLatAlt, MeasureResult } from './measure.js';
12
+ export { lineOfSight } from './lineOfSight.js';
13
+ export type { RayHit, RayCast, LineOfSightOptions, LineOfSightResult } from './lineOfSight.js';
14
+ export { planeFromPointNormal, isPointKept, planeClipping, boxClipping, crossClipping, buildClipPlanes, } from './clipping.js';
15
+ export type { PlaneLike, ClipPlaneOptions, ClipBoxOptions, ClipCrossOptions, ClipOptions, } from './clipping.js';
16
+ export { packRGBADepth, unpackRGBADepth, unprojectNdc, pixelToNdc, azimuthElevation } from './depth.js';
17
+ export { skylineFromDepth } from './skyline.js';
18
+ export type { SkylineArgs, SkylineResult } from './skyline.js';
19
+ export { computeViewshed, makeCubeDistanceSampler } from './viewshed.js';
20
+ export type { ViewshedOptions, ViewshedResult } from './viewshed.js';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,wBAAwB,EACxB,gBAAgB,EAChB,WAAW,EACX,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEnE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE/F,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,WAAW,EACX,aAAa,EACb,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,WAAW,GACZ,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACxG,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACzE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 三维空间分析内核(宿主无关、不依赖 WebGL)。
3
+ *
4
+ * 分工:
5
+ * - 本目录只做**纯数学**:量算、通视判定、裁剪半空间生成。可以直接单测。
6
+ * - 需要 GPU 的部分(贴地采样、天际线、可视域、阴影)在 `render/` 下,
7
+ * 复用 `Picker` 的离屏 pass 与状态恢复。
8
+ * - 交互(取点、拖拽、标签绘制)属于应用层,不进这个包。
9
+ */
10
+ export { distance3D, distanceHorizontal, heightDifference, polylineLength, polylineLengthHorizontal, polygonPerimeter, polygonArea, polygonAreaHorizontal, measureSegment, } from './measure.js';
11
+ export { lineOfSight } from './lineOfSight.js';
12
+ export { planeFromPointNormal, isPointKept, planeClipping, boxClipping, crossClipping, buildClipPlanes, } from './clipping.js';
13
+ /* 深度图回读(P1:天际线 / 可视域的数学部分,GL 在 render/analysis/DepthPass) */
14
+ export { packRGBADepth, unpackRGBADepth, unprojectNdc, pixelToNdc, azimuthElevation } from './depth.js';
15
+ export { skylineFromDepth } from './skyline.js';
16
+ export { computeViewshed, makeCubeDistanceSampler } from './viewshed.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,wBAAwB,EACxB,gBAAgB,EAChB,WAAW,EACX,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,WAAW,EACX,aAAa,EACb,eAAe,GAChB,MAAM,eAAe,CAAC;AASvB,6DAA6D;AAC7D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACxG,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * 通视分析(单条视线)。
3
+ *
4
+ * 求交本体由调用方注入 —— 内核只负责「沿视线找第一个遮挡、算可见比例」,
5
+ * 这样不依赖 three,也能在单测里用解析几何伪造求交结果。
6
+ *
7
+ * 场景里实际用的求交是 `THREE.Raycaster` 打全部 pickable mesh(与
8
+ * `PickService.drillPick` 同一条路径),由 `S3MViewer.lineOfSight` 装配。
9
+ */
10
+ import { type Vec3 } from './measure.js';
11
+ /** 一次射线求交的结果:只关心「最近的交点距离」 */
12
+ export interface RayHit {
13
+ /** 沿射线的距离(米) */
14
+ distance: number;
15
+ /** 交点位置(可选,用于画遮挡点标记) */
16
+ point?: Vec3;
17
+ }
18
+ /**
19
+ * 射线求交函数。
20
+ *
21
+ * @param origin 起点(场景 ENU 米)
22
+ * @param dir 单位方向
23
+ * @param maxDistance 只关心这个距离内的交点,超出可以返回 null(实现可据此提前剪枝)
24
+ */
25
+ export type RayCast = (origin: Vec3, dir: Vec3, maxDistance: number) => RayHit | null;
26
+ export interface LineOfSightOptions {
27
+ /**
28
+ * 起点外推距离(米,默认 0.05)。
29
+ *
30
+ * 从建筑表面起步时,起点就落在三角面上,浮点误差会让射线立刻打中自己所在的
31
+ * 那个三角面,于是「永远不可见」。沿视线方向先推一小段再求交即可避开。
32
+ */
33
+ originOffset?: number;
34
+ /**
35
+ * 终点内缩距离(米,默认 0.05)。同理:终点若贴在目标表面,不要把目标自身
36
+ * 当成遮挡物。
37
+ */
38
+ targetOffset?: number;
39
+ }
40
+ export interface LineOfSightResult {
41
+ /** 起终点之间是否通视 */
42
+ visible: boolean;
43
+ /** 起点到终点的直线距离(米) */
44
+ distance: number;
45
+ /** 第一个遮挡点距起点的距离(米);通视时为 null */
46
+ blockedDistance: number | null;
47
+ /** 第一个遮挡点的位置;通视时为 null */
48
+ blockedPoint: Vec3 | null;
49
+ /** 可见段占全长的比例(0~1,1 = 完全通视)—— 直接用来画「绿段 / 红段」 */
50
+ visibleRatio: number;
51
+ }
52
+ /**
53
+ * 计算两点之间的通视情况。
54
+ *
55
+ * @param from 起点(场景 ENU 米)
56
+ * @param to 终点
57
+ * @param cast 射线求交函数
58
+ */
59
+ export declare function lineOfSight(from: Vec3, to: Vec3, cast: RayCast, opts?: LineOfSightOptions): LineOfSightResult;
60
+ //# sourceMappingURL=lineOfSight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lineOfSight.d.ts","sourceRoot":"","sources":["../../src/analysis/lineOfSight.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,cAAc,CAAC;AAErD,6BAA6B;AAC7B,MAAM,WAAW,MAAM;IACrB,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,wBAAwB;IACxB,KAAK,CAAC,EAAE,IAAI,CAAC;CACd;AAED;;;;;;GAMG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAEtF,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,gBAAgB;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,oBAAoB;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,0BAA0B;IAC1B,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,IAAI,EACV,EAAE,EAAE,IAAI,EACR,IAAI,EAAE,OAAO,EACb,IAAI,GAAE,kBAAuB,GAC5B,iBAAiB,CAyCnB"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * 通视分析(单条视线)。
3
+ *
4
+ * 求交本体由调用方注入 —— 内核只负责「沿视线找第一个遮挡、算可见比例」,
5
+ * 这样不依赖 three,也能在单测里用解析几何伪造求交结果。
6
+ *
7
+ * 场景里实际用的求交是 `THREE.Raycaster` 打全部 pickable mesh(与
8
+ * `PickService.drillPick` 同一条路径),由 `S3MViewer.lineOfSight` 装配。
9
+ */
10
+ import { distance3D } from './measure.js';
11
+ /**
12
+ * 计算两点之间的通视情况。
13
+ *
14
+ * @param from 起点(场景 ENU 米)
15
+ * @param to 终点
16
+ * @param cast 射线求交函数
17
+ */
18
+ export function lineOfSight(from, to, cast, opts = {}) {
19
+ const total = distance3D(from, to);
20
+ if (total <= 1e-6) {
21
+ return { visible: true, distance: 0, blockedDistance: null, blockedPoint: null, visibleRatio: 1 };
22
+ }
23
+ const dir = [
24
+ (to[0] - from[0]) / total,
25
+ (to[1] - from[1]) / total,
26
+ (to[2] - from[2]) / total,
27
+ ];
28
+ const startOffset = Math.max(0, opts.originOffset ?? 0.05);
29
+ const endOffset = Math.max(0, opts.targetOffset ?? 0.05);
30
+ // 两端各缩一点;太短的视线就不缩了,免得区间翻负
31
+ const shrink = Math.min(startOffset + endOffset, total * 0.5);
32
+ const origin = [
33
+ from[0] + dir[0] * startOffset,
34
+ from[1] + dir[1] * startOffset,
35
+ from[2] + dir[2] * startOffset,
36
+ ];
37
+ const limit = Math.max(0, total - shrink);
38
+ const hit = cast(origin, dir, limit);
39
+ if (hit && hit.distance < limit) {
40
+ // 距离要把起点外推量加回去,才是「从真实起点算起」
41
+ const blockedDistance = Math.min(total, hit.distance + startOffset);
42
+ return {
43
+ visible: false,
44
+ distance: total,
45
+ blockedDistance,
46
+ blockedPoint: hit.point ?? [
47
+ from[0] + dir[0] * blockedDistance,
48
+ from[1] + dir[1] * blockedDistance,
49
+ from[2] + dir[2] * blockedDistance,
50
+ ],
51
+ visibleRatio: Math.max(0, Math.min(1, blockedDistance / total)),
52
+ };
53
+ }
54
+ return { visible: true, distance: total, blockedDistance: null, blockedPoint: null, visibleRatio: 1 };
55
+ }
56
+ //# sourceMappingURL=lineOfSight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lineOfSight.js","sourceRoot":"","sources":["../../src/analysis/lineOfSight.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,EAAa,MAAM,cAAc,CAAC;AA+CrD;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CACzB,IAAU,EACV,EAAQ,EACR,IAAa,EACb,OAA2B,EAAE;IAE7B,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACnC,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;QAClB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;IACpG,CAAC;IAED,MAAM,GAAG,GAAS;QAChB,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK;QACzB,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK;QACzB,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK;KAC1B,CAAC;IAEF,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC;IACzD,0BAA0B;IAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,GAAG,SAAS,EAAE,KAAK,GAAG,GAAG,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAS;QACnB,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,WAAW;QAC9B,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,WAAW;QAC9B,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,WAAW;KAC/B,CAAC;IACF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC;IAE1C,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IACrC,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,GAAG,KAAK,EAAE,CAAC;QAChC,2BAA2B;QAC3B,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,QAAQ,GAAG,WAAW,CAAC,CAAC;QACpE,OAAO;YACL,OAAO,EAAE,KAAK;YACd,QAAQ,EAAE,KAAK;YACf,eAAe;YACf,YAAY,EAAE,GAAG,CAAC,KAAK,IAAI;gBACzB,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,eAAe;gBAClC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,eAAe;gBAClC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,eAAe;aACnC;YACD,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,GAAG,KAAK,CAAC,CAAC;SAChE,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;AACxG,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * 量算:距离 / 折线长 / 面积 / 高差。
3
+ *
4
+ * 全部是纯函数,输入输出都是**场景 ENU 米制坐标**(three 世界坐标),
5
+ * 不依赖 three、不依赖 WebGL,可以直接单测。
6
+ *
7
+ * 经纬度 ↔ ENU 的换算在 `S3MViewer` 里做(需要 `sceneAnchor` 与 `GeoAdapter`),
8
+ * 这一层只管算数 —— 这样投影口径变化不会污染数学。
9
+ */
10
+ /** 场景 ENU 米制坐标(three 世界坐标) */
11
+ export type Vec3 = [number, number, number];
12
+ /** 经纬度 + 绝对高程(米) */
13
+ export interface LngLatAlt {
14
+ lng: number;
15
+ lat: number;
16
+ /** 绝对高程(米),不是相对场景锚点的高度 */
17
+ altitude: number;
18
+ }
19
+ export declare function distance3D(a: Vec3, b: Vec3): number;
20
+ /** 水平距离(忽略高程差)—— 规划类口径常用 */
21
+ export declare function distanceHorizontal(a: Vec3, b: Vec3): number;
22
+ /** 高差(有符号:b 比 a 高为正) */
23
+ export declare function heightDifference(a: Vec3, b: Vec3): number;
24
+ /** 折线长度(逐段求和);点少于 2 个返回 0 */
25
+ export declare function polylineLength(points: readonly Vec3[]): number;
26
+ /** 折线水平投影长度 */
27
+ export declare function polylineLengthHorizontal(points: readonly Vec3[]): number;
28
+ /** 多边形周长(首尾自动闭合,不要重复传首点) */
29
+ export declare function polygonPerimeter(points: readonly Vec3[]): number;
30
+ /**
31
+ * 多边形面积(Newell 法)。
32
+ *
33
+ * 对**共面**点给出真实面积;对起伏/不共面的点给出「投影到最佳拟合平面」的面积,
34
+ * 这也是绝大多数量算工具的口径。要水平投影面积用 `polygonAreaHorizontal`。
35
+ *
36
+ * 与点的绕向无关(取向量模长),所以不用担心用户顺时针还是逆时针点。
37
+ */
38
+ export declare function polygonArea(points: readonly Vec3[]): number;
39
+ /**
40
+ * 水平投影面积(鞋带公式,忽略高程)。
41
+ *
42
+ * 与 `polygonArea` 的区别:一个坡面上的矩形,`polygonArea` 给坡面真实面积,
43
+ * 这里给它在水平面上的投影面积。建筑面积/用地面积按后者算。
44
+ */
45
+ export declare function polygonAreaHorizontal(points: readonly Vec3[]): number;
46
+ /** 量算结果:一次给全三种口径,避免调用方自己再算一遍 */
47
+ export interface MeasureResult {
48
+ /** 三维直线距离(米) */
49
+ distance3D: number;
50
+ /** 水平距离(米) */
51
+ distanceHorizontal: number;
52
+ /** 高差(米,有符号) */
53
+ heightDiff: number;
54
+ }
55
+ export declare function measureSegment(a: Vec3, b: Vec3): MeasureResult;
56
+ //# sourceMappingURL=measure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"measure.d.ts","sourceRoot":"","sources":["../../src/analysis/measure.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,8BAA8B;AAC9B,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAE5C,oBAAoB;AACpB,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM,CAKnD;AAED,4BAA4B;AAC5B,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM,CAI3D;AAED,wBAAwB;AACxB,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM,CAEzD;AAED,6BAA6B;AAC7B,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,GAAG,MAAM,CAI9D;AAED,eAAe;AACf,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,GAAG,MAAM,CAIxE;AAED,4BAA4B;AAC5B,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,GAAG,MAAM,CAGhE;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,GAAG,MAAM,CAc3D;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,GAAG,MAAM,CAUrE;AAED,gCAAgC;AAChC,MAAM,WAAW,aAAa;IAC5B,gBAAgB;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc;IACd,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,aAAa,CAM9D"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * 量算:距离 / 折线长 / 面积 / 高差。
3
+ *
4
+ * 全部是纯函数,输入输出都是**场景 ENU 米制坐标**(three 世界坐标),
5
+ * 不依赖 three、不依赖 WebGL,可以直接单测。
6
+ *
7
+ * 经纬度 ↔ ENU 的换算在 `S3MViewer` 里做(需要 `sceneAnchor` 与 `GeoAdapter`),
8
+ * 这一层只管算数 —— 这样投影口径变化不会污染数学。
9
+ */
10
+ export function distance3D(a, b) {
11
+ const dx = b[0] - a[0];
12
+ const dy = b[1] - a[1];
13
+ const dz = b[2] - a[2];
14
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
15
+ }
16
+ /** 水平距离(忽略高程差)—— 规划类口径常用 */
17
+ export function distanceHorizontal(a, b) {
18
+ const dx = b[0] - a[0];
19
+ const dy = b[1] - a[1];
20
+ return Math.sqrt(dx * dx + dy * dy);
21
+ }
22
+ /** 高差(有符号:b 比 a 高为正) */
23
+ export function heightDifference(a, b) {
24
+ return b[2] - a[2];
25
+ }
26
+ /** 折线长度(逐段求和);点少于 2 个返回 0 */
27
+ export function polylineLength(points) {
28
+ let total = 0;
29
+ for (let i = 1; i < points.length; i++)
30
+ total += distance3D(points[i - 1], points[i]);
31
+ return total;
32
+ }
33
+ /** 折线水平投影长度 */
34
+ export function polylineLengthHorizontal(points) {
35
+ let total = 0;
36
+ for (let i = 1; i < points.length; i++)
37
+ total += distanceHorizontal(points[i - 1], points[i]);
38
+ return total;
39
+ }
40
+ /** 多边形周长(首尾自动闭合,不要重复传首点) */
41
+ export function polygonPerimeter(points) {
42
+ if (points.length < 2)
43
+ return 0;
44
+ return polylineLength(points) + distance3D(points[points.length - 1], points[0]);
45
+ }
46
+ /**
47
+ * 多边形面积(Newell 法)。
48
+ *
49
+ * 对**共面**点给出真实面积;对起伏/不共面的点给出「投影到最佳拟合平面」的面积,
50
+ * 这也是绝大多数量算工具的口径。要水平投影面积用 `polygonAreaHorizontal`。
51
+ *
52
+ * 与点的绕向无关(取向量模长),所以不用担心用户顺时针还是逆时针点。
53
+ */
54
+ export function polygonArea(points) {
55
+ const n = points.length;
56
+ if (n < 3)
57
+ return 0;
58
+ let nx = 0;
59
+ let ny = 0;
60
+ let nz = 0;
61
+ for (let i = 0; i < n; i++) {
62
+ const a = points[i];
63
+ const b = points[(i + 1) % n];
64
+ nx += (a[1] - b[1]) * (a[2] + b[2]);
65
+ ny += (a[2] - b[2]) * (a[0] + b[0]);
66
+ nz += (a[0] - b[0]) * (a[1] + b[1]);
67
+ }
68
+ return Math.sqrt(nx * nx + ny * ny + nz * nz) / 2;
69
+ }
70
+ /**
71
+ * 水平投影面积(鞋带公式,忽略高程)。
72
+ *
73
+ * 与 `polygonArea` 的区别:一个坡面上的矩形,`polygonArea` 给坡面真实面积,
74
+ * 这里给它在水平面上的投影面积。建筑面积/用地面积按后者算。
75
+ */
76
+ export function polygonAreaHorizontal(points) {
77
+ const n = points.length;
78
+ if (n < 3)
79
+ return 0;
80
+ let sum = 0;
81
+ for (let i = 0; i < n; i++) {
82
+ const a = points[i];
83
+ const b = points[(i + 1) % n];
84
+ sum += a[0] * b[1] - b[0] * a[1];
85
+ }
86
+ return Math.abs(sum) / 2;
87
+ }
88
+ export function measureSegment(a, b) {
89
+ return {
90
+ distance3D: distance3D(a, b),
91
+ distanceHorizontal: distanceHorizontal(a, b),
92
+ heightDiff: heightDifference(a, b),
93
+ };
94
+ }
95
+ //# sourceMappingURL=measure.js.map