@d5techs/3dgs-lib 1.0.1 → 1.1.1

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 (38) hide show
  1. package/README.md +311 -136
  2. package/dist/3dgs-lib.cjs +4215 -2150
  3. package/dist/3dgs-lib.cjs.map +1 -1
  4. package/dist/3dgs-lib.js +4216 -2151
  5. package/dist/3dgs-lib.js.map +1 -1
  6. package/dist/App.d.ts +28 -7
  7. package/dist/core/BoundingBoxRenderer.d.ts +2 -15
  8. package/dist/core/OrbitControls.d.ts +21 -41
  9. package/dist/core/gizmo/{TransformGizmoV2.d.ts → TransformGizmo.d.ts} +2 -2
  10. package/dist/core/{ViewportGizmo.d.ts → gizmo/ViewportGizmo.d.ts} +2 -2
  11. package/dist/core/gizmo/index.d.ts +3 -2
  12. package/dist/core/index.d.ts +7 -0
  13. package/dist/gs/GSSplatRenderer.d.ts +68 -172
  14. package/dist/gs/GSSplatRendererMobile.d.ts +9 -6
  15. package/dist/gs/GSSplatSorter.d.ts +44 -37
  16. package/dist/gs/IGSSplatRenderer.d.ts +39 -37
  17. package/dist/gs/SOGLoader.d.ts +25 -0
  18. package/dist/index.d.ts +17 -13
  19. package/dist/interaction/GizmoManager.d.ts +9 -46
  20. package/dist/interaction/HotspotManager.d.ts +142 -0
  21. package/dist/loaders/GLBLoader.d.ts +3 -11
  22. package/dist/loaders/OBJLoader.d.ts +1 -1
  23. package/dist/mesh/Mesh.d.ts +8 -16
  24. package/dist/mesh/MeshRenderer.d.ts +30 -14
  25. package/dist/scene/SceneManager.d.ts +12 -6
  26. package/dist/scene/proxies/MeshGroupProxy.d.ts +20 -0
  27. package/dist/scene/proxies/SplatBoundingBoxProvider.d.ts +11 -0
  28. package/dist/scene/proxies/SplatTransformProxy.d.ts +17 -0
  29. package/dist/scene/proxies/index.d.ts +6 -0
  30. package/dist/types/geometry.d.ts +60 -0
  31. package/dist/types/index.d.ts +8 -0
  32. package/dist/types/material.d.ts +27 -0
  33. package/dist/types/splat.d.ts +25 -0
  34. package/dist/utils/device.d.ts +17 -0
  35. package/dist/utils/geometry.d.ts +26 -0
  36. package/dist/utils/index.d.ts +6 -0
  37. package/dist/utils/texture.d.ts +64 -0
  38. package/package.json +4 -1
@@ -1,18 +1,12 @@
1
1
  /**
2
- * GSSplatSorter - GPU Counting Sort 深度排序器
2
+ * GSSplatSorter - GPU Radix Sort 深度排序器
3
3
  *
4
- * 基于 PlayCanvas 的排序算法实现,使用 Counting Sort
5
- * 时间复杂度 O(n),远比 Bitonic Sort O(n log²n) 更快
4
+ * 基于 rfs-gsplat-render 3-Pass Radix Sort 架构实现:
5
+ * - 4 pass (8-bit 增量,总共 32 位)
6
+ * - 每个 pass 包含: Upsweep -> Spine -> Downsweep
7
+ * - 稳定排序,解决远距离闪烁问题
6
8
  *
7
- * 流程:
8
- * 1. 剔除 + 计算深度 + 统计计数
9
- * 2. 前缀和计算偏移
10
- * 3. 散射到最终位置
11
- *
12
- * iOS 兼容性:
13
- * - iOS WebGPU 对大型原子数组支持有限
14
- * - 默认使用 65536 个桶(桌面/Android)
15
- * - iOS 使用 4096 个桶(减少原子操作压力)
9
+ * 参考: rfs-gsplat-render/assets/shaders/radix_sort.wgsl
16
10
  */
17
11
  /**
18
12
  * 屏幕尺寸信息
@@ -28,48 +22,64 @@ export interface CullingOptions {
28
22
  nearPlane: number;
29
23
  farPlane: number;
30
24
  pixelThreshold: number;
25
+ frustumDilation?: number;
26
+ /** 最大可见 splat 数量(排序后截断),0 = 不限制 */
27
+ maxVisibleCount?: number;
31
28
  }
32
29
  /**
33
30
  * 排序器配置选项
34
31
  */
35
32
  export interface SorterOptions {
36
- /** 深度桶数量(必须是 2 的幂次),默认根据平台自动选择 */
33
+ /** 暂时保留,Radix Sort 不使用桶配置 */
37
34
  numBuckets?: number;
38
35
  }
39
36
  /**
40
- * GSSplatSorter - GPU Counting Sort 排序器
37
+ * GSSplatSorter - GPU Radix Sort 排序器
38
+ * 基于 rfs-gsplat-render 的 3-Pass Radix Sort 实现
41
39
  */
42
40
  export declare class GSSplatSorter {
43
41
  private device;
44
42
  private splatCount;
45
43
  private cullingParamsBuffer;
46
- private countersBuffer;
47
- private visibleIndicesBuffer;
48
44
  private depthKeysBuffer;
49
- private bucketCountsBuffer;
50
- private bucketOffsetsBuffer;
51
- private bucketPositionsBuffer;
45
+ private visibleIndicesBuffer;
46
+ private indirectBuffer;
47
+ private globalHistogramBuffer;
48
+ private partitionHistogramBuffer;
49
+ private keysTempBuffer;
50
+ private valuesTempBuffer;
51
+ private sortParamsBuffers;
52
52
  private sortedIndicesBuffer;
53
- private drawIndirectBuffer;
54
- private resetCountersPipeline;
55
- private resetBucketCountsPipeline;
56
- private cullAndCountPipeline;
57
- private updateDrawIndirectPipeline;
58
- private prefixSumPipeline;
59
- private resetBucketPositionsPipeline;
60
- private scatterPipeline;
53
+ private initIndirectPipeline;
54
+ private projectCullPipeline;
55
+ private clampDrawCountPipeline;
61
56
  private cullingBindGroupLayout;
62
57
  private cullingBindGroup;
63
- private prefixSumBindGroupLayout;
64
- private prefixSumBindGroup;
65
- private scatterBindGroupLayout;
66
- private scatterBindGroup;
67
- private readonly WORKGROUP_SIZE;
68
- private readonly numBuckets;
58
+ private upsweepPipeline;
59
+ private spinePipeline;
60
+ private downsweepPipeline;
61
+ private upsweepBindGroupLayout;
62
+ private spineBindGroupLayout;
63
+ private downsweepBindGroupLayout;
64
+ private upsweepBindGroups;
65
+ private spineBindGroups;
66
+ private downsweepBindGroups;
67
+ private numPartitions;
69
68
  private screenWidth;
70
69
  private screenHeight;
71
70
  private cullingOptions;
72
- constructor(device: GPUDevice, splatCount: number, splatBuffer: GPUBuffer, cameraBuffer: GPUBuffer, options?: SorterOptions);
71
+ constructor(device: GPUDevice, splatCount: number, splatBuffer: GPUBuffer, cameraBuffer: GPUBuffer, _options?: SorterOptions);
72
+ /**
73
+ * 创建 Radix Sort 的 bind groups
74
+ * 4 个 pass,使用 ping-pong buffers
75
+ *
76
+ * Ping-pong 模式:
77
+ * - Pass 0: depthKeys/visibleIndices -> keysTempBuffer/valuesTempBuffer
78
+ * - Pass 1: keysTempBuffer/valuesTempBuffer -> depthKeys/visibleIndices
79
+ * - Pass 2: depthKeys/visibleIndices -> keysTempBuffer/valuesTempBuffer
80
+ * - Pass 3: keysTempBuffer/valuesTempBuffer -> (depthKeys)/sortedIndicesBuffer
81
+ */
82
+ private createRadixSortBindGroups;
73
83
  /**
74
84
  * 设置屏幕尺寸
75
85
  */
@@ -81,9 +91,6 @@ export declare class GSSplatSorter {
81
91
  /**
82
92
  * 执行剔除和排序
83
93
  * 每帧调用
84
- *
85
- * 优化:合并所有 compute pass 到单次 GPU 提交
86
- * WebGPU 保证同一 command buffer 中的命令按顺序执行
87
94
  */
88
95
  sort(): void;
89
96
  /**
@@ -3,26 +3,10 @@
3
3
  *
4
4
  * 桌面端和移动端渲染器都实现此接口,消除平台判断代码
5
5
  */
6
- import { CompactSplatData } from "./PLYLoaderMobile";
7
- import { SplatCPU } from "./PLYLoader";
8
- /**
9
- * Bounding Box 结构
10
- */
11
- export interface BoundingBox {
12
- min: [number, number, number];
13
- max: [number, number, number];
14
- center: [number, number, number];
15
- radius: number;
16
- }
17
- /**
18
- * SH 模式枚举
19
- */
20
- export declare enum SHMode {
21
- L0 = 0,// 仅 DC 颜色(最快)
22
- L1 = 1,// DC + L1 SH
23
- L2 = 2,// DC + L1 + L2 SH
24
- L3 = 3
25
- }
6
+ import type { CompactSplatData } from "./PLYLoaderMobile";
7
+ import type { SplatCPU } from "./PLYLoader";
8
+ import type { BoundingBox, Vec3Tuple } from "../types";
9
+ import { SHMode, RendererCapabilities } from "../types";
26
10
  /**
27
11
  * 3D Gaussian Splatting 渲染器接口
28
12
  */
@@ -47,7 +31,7 @@ export interface IGSSplatRenderer {
47
31
  /**
48
32
  * 获取位置
49
33
  */
50
- getPosition(): [number, number, number];
34
+ getPosition(): Vec3Tuple;
51
35
  /**
52
36
  * 设置旋转(欧拉角,弧度)
53
37
  */
@@ -55,7 +39,7 @@ export interface IGSSplatRenderer {
55
39
  /**
56
40
  * 获取旋转
57
41
  */
58
- getRotation(): [number, number, number];
42
+ getRotation(): Vec3Tuple;
59
43
  /**
60
44
  * 设置缩放
61
45
  */
@@ -63,7 +47,7 @@ export interface IGSSplatRenderer {
63
47
  /**
64
48
  * 获取缩放
65
49
  */
66
- getScale(): [number, number, number];
50
+ getScale(): Vec3Tuple;
67
51
  /**
68
52
  * 设置旋转/缩放中心点(pivot)
69
53
  */
@@ -71,7 +55,7 @@ export interface IGSSplatRenderer {
71
55
  /**
72
56
  * 获取旋转/缩放中心点(pivot)
73
57
  */
74
- getPivot(): [number, number, number];
58
+ getPivot(): Vec3Tuple;
75
59
  /**
76
60
  * 获取模型矩阵
77
61
  */
@@ -84,6 +68,11 @@ export interface IGSSplatRenderer {
84
68
  * 获取 bounding box
85
69
  */
86
70
  getBoundingBox(): BoundingBox | null;
71
+ /**
72
+ * 获取 CPU 端 splat 位置数据(用于射线拾取等交互)
73
+ * 返回 Float32Array,每 3 个值为一个点的 x, y, z
74
+ */
75
+ getCPUPositions(): Float32Array | null;
87
76
  /**
88
77
  * 设置 SH 模式
89
78
  * 移动端可能只支持 L0
@@ -97,24 +86,37 @@ export interface IGSSplatRenderer {
97
86
  * 是否支持指定的 SH 模式
98
87
  */
99
88
  supportsSHMode?(mode: SHMode): boolean;
89
+ /**
90
+ * 设置排序频率
91
+ * 1 = 每帧排序(默认),N = 每 N 帧排序一次
92
+ * 降低排序频率可显著提升中距离场景的帧率,代价是移动时短暂的排序瑕疵
93
+ */
94
+ setSortFrequency?(frequency: number): void;
95
+ /**
96
+ * 获取排序频率
97
+ */
98
+ getSortFrequency?(): number;
99
+ /**
100
+ * 渲染深度+法线到 offscreen RT,并将指定像素 copy 到 staging buffer 发起异步回读。
101
+ * 在主 render 之后每帧调用,复用排序结果。
102
+ * @param px 像素 x 坐标(-1 表示不回读)
103
+ * @param py 像素 y 坐标(-1 表示不回读)
104
+ */
105
+ prepareDepthNormalPass?(px: number, py: number): void;
106
+ /**
107
+ * 同步读取上一帧 prepareDepthNormalPass 回读完成的结果。
108
+ * @returns null 表示上一帧无命中、回读未完成、或未请求过回读
109
+ */
110
+ getDepthNormal?(): {
111
+ depth: number;
112
+ normal: Vec3Tuple;
113
+ worldPos: Vec3Tuple;
114
+ } | null;
100
115
  /**
101
116
  * 销毁资源
102
117
  */
103
118
  destroy(): void;
104
119
  }
105
- /**
106
- * 渲染器能力描述
107
- */
108
- export interface RendererCapabilities {
109
- /** 支持的最高 SH 模式 */
110
- maxSHMode: SHMode;
111
- /** 是否支持原始 SplatCPU 数据 */
112
- supportsRawData: boolean;
113
- /** 是否为移动端优化版本 */
114
- isMobileOptimized: boolean;
115
- /** 最大支持的 splat 数量(0 表示无限制) */
116
- maxSplatCount: number;
117
- }
118
120
  /**
119
121
  * 获取渲染器能力(可选方法)
120
122
  */
@@ -0,0 +1,25 @@
1
+ /**
2
+ * SOGLoader - 加载 SOG (Spatially Ordered Gaussians) 格式的 3DGS 文件
3
+ *
4
+ * SOG 是 PlayCanvas 开源的压缩容器格式,约 15-20x 压缩比 (vs PLY)
5
+ * .sog 文件本质是 ZIP 包,内含 meta.json + 多个无损 WebP 图像
6
+ *
7
+ * 支持:
8
+ * - 完整位置解码(16-bit 对称对数量化)
9
+ * - Codebook 缩放解码
10
+ * - Smallest-three 四元数解码 (26-bit)
11
+ * - DC 颜色 + 透明度 (SH L0)
12
+ * - 高阶球谐系数 L1-L3(palette + codebook)
13
+ *
14
+ * 规范 v2: https://developer.playcanvas.com/user-manual/gaussian-splatting/formats/sog/
15
+ */
16
+ import type { CompactSplatData } from './PLYLoaderMobile';
17
+ export type SOGProgressCallback = (progress: number, stage: 'download' | 'parse' | 'upload') => void;
18
+ /**
19
+ * 从 URL 加载 .sog 文件
20
+ */
21
+ export declare function loadSOG(url: string, onProgress?: SOGProgressCallback): Promise<CompactSplatData>;
22
+ /**
23
+ * 从 ArrayBuffer 解析 SOG 数据
24
+ */
25
+ export declare function deserializeSOG(data: ArrayBuffer, onProgress?: SOGProgressCallback): Promise<CompactSplatData>;
package/dist/index.d.ts CHANGED
@@ -2,31 +2,32 @@
2
2
  * WebGPU 3D 渲染引擎
3
3
  * 库入口文件 - 导出所有公共 API
4
4
  */
5
+ export type { Vec3Tuple, Vec4Tuple, BoundingBox, SimpleBoundingBox, Transform, TransformableObject as ITransformableObject, BoundingBoxProvider as IBoundingBoxProvider, MaterialData, RendererCapabilities, } from './types';
6
+ export { SHMode, DEFAULT_MATERIAL, DEFAULT_OBJ_MATERIAL } from './types';
7
+ export { isMobileDevice, getRecommendedDPR, isWebGPUSupported, computeBoundingBox, mergeBoundingBoxes, createBoundingBoxFromMinMax, transformBoundingBox, loadTextureFromURL, loadTextureFromBlob, loadTextureFromBuffer, createTextureFromImageBitmap, TextureCache, } from './utils';
5
8
  export { Renderer } from './core/Renderer';
6
9
  export { Camera } from './core/Camera';
7
10
  export { OrbitControls } from './core/OrbitControls';
8
- export { ViewportGizmo } from './core/ViewportGizmo';
11
+ export { ViewportGizmo } from './core/gizmo/ViewportGizmo';
9
12
  export { BoundingBoxRenderer } from './core/BoundingBoxRenderer';
10
- export type { BoundingBox as SelectionBoundingBox, BoundingBoxProvider } from './core/BoundingBoxRenderer';
11
13
  export { Mesh } from './mesh/Mesh';
12
- export type { MeshBoundingBox } from './mesh/Mesh';
13
14
  export { MeshRenderer } from './mesh/MeshRenderer';
14
15
  export { GLBLoader } from './loaders/GLBLoader';
15
- export type { MaterialData, LoadedMesh } from './loaders/GLBLoader';
16
+ export type { LoadedMesh } from './loaders/GLBLoader';
16
17
  export { OBJLoader } from './loaders/OBJLoader';
17
18
  export { OBJParser } from './loaders/OBJParser';
18
19
  export type { ParsedOBJData, ParsedObject } from './loaders/OBJParser';
19
20
  export { MTLParser } from './loaders/MTLParser';
20
21
  export type { ParsedMaterial } from './loaders/MTLParser';
21
- export type { IGSSplatRenderer, IGSSplatRendererWithCapabilities, RendererCapabilities, BoundingBox, SHMode } from './gs/IGSSplatRenderer';
22
+ export type { IGSSplatRenderer, IGSSplatRendererWithCapabilities, } from './gs/IGSSplatRenderer';
22
23
  export { loadPLY } from './gs/PLYLoader';
23
24
  export type { SplatCPU } from './gs/PLYLoader';
24
25
  export { loadPLYMobile, parsePLYBuffer, compactDataToGPUBuffer } from './gs/PLYLoaderMobile';
25
26
  export type { MobileLoadOptions, CompactSplatData } from './gs/PLYLoaderMobile';
26
27
  export { loadSplat, deserializeSplat } from './gs/SplatLoader';
27
- export { GSSplatRenderer, PerformanceTier } from './gs/GSSplatRenderer';
28
- export { SHMode as GSSHMode } from './gs/GSSplatRenderer';
29
- export type { MobileOptimizationConfig } from './gs/GSSplatRenderer';
28
+ export { loadSOG, deserializeSOG } from './gs/SOGLoader';
29
+ export type { SOGProgressCallback } from './gs/SOGLoader';
30
+ export { GSSplatRenderer } from './gs/GSSplatRenderer';
30
31
  export { GSSplatSorter } from './gs/GSSplatSorter';
31
32
  export type { SorterOptions, CullingOptions, ScreenInfo } from './gs/GSSplatSorter';
32
33
  export { GSSplatRendererMobile } from './gs/GSSplatRendererMobile';
@@ -36,9 +37,12 @@ export { compressSplatsToTextures, destroyCompressedTextures, calculateTextureDi
36
37
  export type { CompressedSplatTextures } from './gs/TextureCompressor';
37
38
  export { SceneManager } from './scene/SceneManager';
38
39
  export type { SceneObjectType, SceneObjectInfo } from './scene/SceneManager';
39
- export { GizmoManager, SplatTransformProxy as GizmoSplatTransformProxy, MeshGroupProxy as GizmoMeshGroupProxy, SplatBoundingBoxProvider as GizmoSplatBoundingBoxProvider } from './interaction/GizmoManager';
40
- export { App, SplatTransformProxy, MeshGroupProxy, SplatBoundingBoxProvider } from './App';
40
+ export { SplatTransformProxy, MeshGroupProxy, SplatBoundingBoxProvider, } from './scene/proxies';
41
+ export { GizmoManager } from './interaction/GizmoManager';
42
+ export { HotspotManager } from './interaction/HotspotManager';
43
+ export type { HotspotInfo } from './interaction/HotspotManager';
44
+ export type { TransformableObject } from './core/gizmo/TransformGizmo';
45
+ export { TransformGizmo, GizmoMode } from './core/gizmo/TransformGizmo';
46
+ export type { GizmoTheme, TransformGizmoConfig, GizmoSpace } from './core/gizmo/TransformGizmo';
47
+ export { App } from './App';
41
48
  export type { ProgressCallback } from './App';
42
- export type { TransformableObject } from './core/gizmo/TransformGizmoV2';
43
- export { TransformGizmoV2, GizmoMode } from './core/gizmo/TransformGizmoV2';
44
- export type { GizmoTheme, TransformGizmoConfig, GizmoSpace } from './core/gizmo/TransformGizmoV2';
@@ -9,50 +9,13 @@
9
9
  import { Renderer } from "../core/Renderer";
10
10
  import { Camera } from "../core/Camera";
11
11
  import { OrbitControls } from "../core/OrbitControls";
12
- import { ViewportGizmo } from "../core/ViewportGizmo";
13
- import { TransformGizmoV2, TransformableObject, GizmoMode } from "../core/gizmo/TransformGizmoV2";
14
- import { BoundingBoxRenderer, BoundingBox as RendererBoundingBox, BoundingBoxProvider } from "../core/BoundingBoxRenderer";
15
- import { IGSSplatRenderer } from "../gs/IGSSplatRenderer";
16
- import { Mesh } from "../mesh/Mesh";
17
- /**
18
- * SplatTransformProxy - PLY/Splat 变换代理对象
19
- * 实现类似 Mesh 的接口,让 TransformGizmo 可以操作 PLY 模型
20
- */
21
- export declare class SplatTransformProxy implements TransformableObject {
22
- position: [number, number, number];
23
- rotation: [number, number, number];
24
- scale: [number, number, number];
25
- private renderer;
26
- private center;
27
- constructor(renderer: IGSSplatRenderer, center: [number, number, number]);
28
- setPosition(x: number, y: number, z: number): void;
29
- setRotation(x: number, y: number, z: number): void;
30
- setScale(x: number, y: number, z: number): void;
31
- }
32
- /**
33
- * MeshGroupProxy - 多 Mesh 组变换代理对象
34
- * 让 TransformGizmo 可以同时操作多个 Mesh
35
- */
36
- export declare class MeshGroupProxy implements TransformableObject {
37
- position: [number, number, number];
38
- rotation: [number, number, number];
39
- scale: [number, number, number];
40
- private meshes;
41
- constructor(meshes: Mesh[]);
42
- setPosition(x: number, y: number, z: number): void;
43
- setRotation(x: number, y: number, z: number): void;
44
- setScale(x: number, y: number, z: number): void;
45
- getBoundingBox(): RendererBoundingBox | null;
46
- }
47
- /**
48
- * SplatBoundingBoxProvider - PLY/Splat 包围盒提供者
49
- * 动态获取 PLY 的包围盒(考虑变换)
50
- */
51
- export declare class SplatBoundingBoxProvider implements BoundingBoxProvider {
52
- private renderer;
53
- constructor(renderer: IGSSplatRenderer);
54
- getBoundingBox(): RendererBoundingBox | null;
55
- }
12
+ import { ViewportGizmo } from "../core/gizmo/ViewportGizmo";
13
+ import { TransformGizmo, TransformableObject, GizmoMode } from "../core/gizmo/TransformGizmo";
14
+ import { BoundingBoxRenderer } from "../core/BoundingBoxRenderer";
15
+ import type { SimpleBoundingBox, BoundingBoxProvider } from "../types";
16
+ import type { IGSSplatRenderer } from "../gs/IGSSplatRenderer";
17
+ import type { Mesh } from "../mesh/Mesh";
18
+ import { SplatTransformProxy, MeshGroupProxy, SplatBoundingBoxProvider } from "../scene/proxies";
56
19
  /**
57
20
  * GizmoManager - Gizmo 交互管理器
58
21
  */
@@ -84,7 +47,7 @@ export declare class GizmoManager {
84
47
  /**
85
48
  * 获取变换 Gizmo
86
49
  */
87
- getTransformGizmo(): TransformGizmoV2;
50
+ getTransformGizmo(): TransformGizmo;
88
51
  /**
89
52
  * 设置 Gizmo 模式
90
53
  */
@@ -104,7 +67,7 @@ export declare class GizmoManager {
104
67
  /**
105
68
  * 设置选中对象的包围盒(静态模式)
106
69
  */
107
- setSelectionBoundingBox(box: RendererBoundingBox | null): void;
70
+ setSelectionBoundingBox(box: SimpleBoundingBox | null): void;
108
71
  /**
109
72
  * 设置选中对象的包围盒提供者(动态模式)
110
73
  */
@@ -0,0 +1,142 @@
1
+ /**
2
+ * HotspotManager - 热点放置管理器
3
+ *
4
+ * 在 3DGS 场景中通过射线拾取表面点,计算法线,
5
+ * 显示吸附圆圈指示器,并放置 OBJ 模型热点。
6
+ */
7
+ import { Camera } from "../core/Camera";
8
+ import { Renderer } from "../core/Renderer";
9
+ import { OrbitControls } from "../core/OrbitControls";
10
+ import { MeshRenderer } from "../mesh/MeshRenderer";
11
+ import type { IGSSplatRenderer } from "../gs/IGSSplatRenderer";
12
+ import type { Vec3Tuple } from "../types";
13
+ /** 热点信息 */
14
+ export interface HotspotInfo {
15
+ position: Vec3Tuple;
16
+ normal: Vec3Tuple;
17
+ meshStartIndex: number;
18
+ meshCount: number;
19
+ /** 是否始终面向屏幕(billboard 模式),默认 false */
20
+ billboard: boolean;
21
+ /** 放置时的缩放值(billboard 旋转时需要保持) */
22
+ placedScale: number;
23
+ /** 放置时的法线偏移量 */
24
+ placedNormalOffset: number;
25
+ /** 放置时的本地中心偏移 (本地空间) */
26
+ placedLocalCenter: Vec3Tuple;
27
+ }
28
+ /**
29
+ * HotspotManager
30
+ */
31
+ export declare class HotspotManager {
32
+ private camera;
33
+ private renderer;
34
+ private controls;
35
+ private meshRenderer;
36
+ private objLoader;
37
+ private canvas;
38
+ private active;
39
+ private gsRenderer;
40
+ private indicatorMesh;
41
+ private indicatorAdded;
42
+ private hotspots;
43
+ private currentHit;
44
+ private indicatorTransform;
45
+ private hotspotOBJUrl;
46
+ private boundOnMouseMove;
47
+ private boundOnMouseDown;
48
+ private boundOnMouseUp;
49
+ private boundOnKeyDown;
50
+ private mouseDownPos;
51
+ private isDragging;
52
+ private readonly DRAG_THRESHOLD;
53
+ private pendingMouseX;
54
+ private pendingMouseY;
55
+ private hasPendingMove;
56
+ private rafId;
57
+ private lastHitIdx;
58
+ private lastNormal;
59
+ private indicatorBaseRadius;
60
+ private pickRadiusPx;
61
+ private onHotspotPlaced;
62
+ private onModeChanged;
63
+ constructor(renderer: Renderer, camera: Camera, canvas: HTMLCanvasElement, controls: OrbitControls, meshRenderer: MeshRenderer);
64
+ setGSRenderer(gsRenderer: IGSSplatRenderer | null): void;
65
+ setHotspotOBJUrl(url: string): void;
66
+ enter(): void;
67
+ exit(): void;
68
+ isActive(): boolean;
69
+ getHotspots(): HotspotInfo[];
70
+ setOnHotspotPlaced(cb: ((info: HotspotInfo) => void) | null): void;
71
+ setOnModeChanged(cb: ((active: boolean) => void) | null): void;
72
+ private pickPixelX;
73
+ private pickPixelY;
74
+ private lastClientX;
75
+ private lastClientY;
76
+ private pickDirty;
77
+ /**
78
+ * Returns the current pick pixel coordinates in device pixels.
79
+ * Called by the render loop to pass to prepareDepthNormalPass.
80
+ * Returns [-1, -1] when no pick is needed.
81
+ */
82
+ getPickPixel(): [number, number];
83
+ private onMouseMove;
84
+ private processMouseMove;
85
+ /**
86
+ * 读取上一帧的深度法线GPU回读结果,更新指示器
87
+ * 每帧在App.Render()中调用
88
+ * 如果GPU没有回读结果,则使用CPU拾取
89
+ */
90
+ consumeGPUResult(): void;
91
+ private onMouseDown;
92
+ private onMouseUp;
93
+ private onKeyDown;
94
+ private raycastSplatsCPU;
95
+ private estimateNormal;
96
+ /**
97
+ * 距离加权 PCA:离中心越近的点权重越高
98
+ */
99
+ private weightedPCA;
100
+ /**
101
+ * 求 3x3 对称矩阵最小特征值对应的特征向量(偏移幂迭代)
102
+ */
103
+ private smallestEigenvector;
104
+ private createIndicatorMesh;
105
+ private showIndicator;
106
+ private hideIndicator;
107
+ private updateIndicator;
108
+ private placeHotspot;
109
+ /**
110
+ * 将 OBJ mesh 的变换设置为与指示器完全一致的大小、位置、朝向。
111
+ *
112
+ * 使用指示器缓存的基向量 (right, forward, normal) 和 visualDiameter,
113
+ * 将 OBJ 缩放到与指示器圆圈等大,居中放置在相同位置。
114
+ */
115
+ private applyIndicatorTransform;
116
+ /**
117
+ * 从 modelMatrix 反向提取 position / rotation / scale,
118
+ * 使 Mesh 的分离属性与矩阵保持同步(Gizmo 需要)。
119
+ */
120
+ private decomposeModelMatrix;
121
+ /**
122
+ * 设置指定热点的 billboard 模式
123
+ * @param hotspotIndex 热点索引(在 hotspots 数组中的位置)
124
+ * @param enabled 是否启用 billboard
125
+ */
126
+ setHotspotBillboard(hotspotIndex: number, enabled: boolean): boolean;
127
+ getHotspotBillboard(hotspotIndex: number): boolean;
128
+ getHotspotCount(): number;
129
+ /**
130
+ * 通过 overlay mesh 起始索引找到对应的热点索引
131
+ */
132
+ findHotspotIndexByMeshStart(overlayMeshStartIndex: number): number;
133
+ /**
134
+ * 每帧调用:更新所有 billboard 热点朝向相机
135
+ */
136
+ updateBillboards(): void;
137
+ /**
138
+ * 关闭 billboard 时恢复到放置时的法线朝向
139
+ */
140
+ private restoreHotspotOrientation;
141
+ destroy(): void;
142
+ }
@@ -1,14 +1,6 @@
1
1
  import { Mesh } from '../mesh/Mesh';
2
- /**
3
- * 材质数据
4
- */
5
- export interface MaterialData {
6
- baseColorFactor: [number, number, number, number];
7
- baseColorTexture: GPUTexture | null;
8
- metallicFactor: number;
9
- roughnessFactor: number;
10
- doubleSided: boolean;
11
- }
2
+ import type { MaterialData } from '../types';
3
+ export type { MaterialData };
12
4
  /**
13
5
  * 加载后的 Mesh 数据(包含材质)
14
6
  */
@@ -51,7 +43,7 @@ export declare class GLBLoader {
51
43
  /**
52
44
  * 计算顶点数据的 bounding box
53
45
  */
54
- private computeBoundingBox;
46
+ private computeBoundingBoxFromPositions;
55
47
  /**
56
48
  * 获取访问器数据 - 修复字节对齐问题
57
49
  */
@@ -54,7 +54,7 @@ export declare class OBJLoader {
54
54
  * 计算顶点数据的 bounding box
55
55
  * Requirement 3.5: 计算并存储 bounding box 信息
56
56
  */
57
- private computeBoundingBox;
57
+ private computeBoundingBoxFromPositions;
58
58
  /**
59
59
  * 创建材质数据
60
60
  * Requirement 4.3: 将材质关联到网格
@@ -1,12 +1,4 @@
1
- /**
2
- * Bounding Box 结构(与 GSSplatRenderer 共享接口)
3
- */
4
- export interface MeshBoundingBox {
5
- min: [number, number, number];
6
- max: [number, number, number];
7
- center: [number, number, number];
8
- radius: number;
9
- }
1
+ import type { BoundingBox, Vec3Tuple } from "../types";
10
2
  /**
11
3
  * Mesh - 网格数据结构
12
4
  * 存储 GPUBuffer + 变换属性
@@ -23,7 +15,7 @@ export declare class Mesh {
23
15
  rotation: Float32Array;
24
16
  scale: Float32Array;
25
17
  private localBoundingBox;
26
- constructor(vertexBuffer: GPUBuffer, vertexCount: number, indexBuffer?: GPUBuffer | null, indexCount?: number, boundingBox?: MeshBoundingBox);
18
+ constructor(vertexBuffer: GPUBuffer, vertexCount: number, indexBuffer?: GPUBuffer | null, indexCount?: number, boundingBox?: BoundingBox);
27
19
  /**
28
20
  * 获取顶点 stride(字节数)
29
21
  */
@@ -31,21 +23,21 @@ export declare class Mesh {
31
23
  /**
32
24
  * 设置本地 bounding box
33
25
  */
34
- setBoundingBox(bbox: MeshBoundingBox): void;
26
+ setBoundingBox(bbox: BoundingBox): void;
35
27
  /**
36
28
  * 获取本地 bounding box
37
29
  */
38
- getLocalBoundingBox(): MeshBoundingBox | null;
30
+ getLocalBoundingBox(): BoundingBox | null;
39
31
  /**
40
32
  * 获取世界空间的 bounding box(考虑完整变换:缩放、旋转、平移)
41
33
  */
42
- getWorldBoundingBox(): MeshBoundingBox | null;
34
+ getWorldBoundingBox(): BoundingBox | null;
43
35
  setPosition(x: number, y: number, z: number): void;
44
- getPosition(): [number, number, number];
36
+ getPosition(): Vec3Tuple;
45
37
  setRotation(rx: number, ry: number, rz: number): void;
46
- getRotation(): [number, number, number];
38
+ getRotation(): Vec3Tuple;
47
39
  setScale(sx: number, sy: number, sz: number): void;
48
- getScale(): [number, number, number];
40
+ getScale(): Vec3Tuple;
49
41
  updateModelMatrix(): void;
50
42
  resetTransform(): void;
51
43
  destroy(): void;