@d5techs/3dgs-lib 1.3.0 → 1.4.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.
package/dist/App.d.ts CHANGED
@@ -19,7 +19,7 @@ import { GSSplatRenderer } from "./gs/GSSplatRenderer";
19
19
  import { GSSplatRendererMobile } from "./gs/GSSplatRendererMobile";
20
20
  import type { BoundingBox, SimpleBoundingBox } from "./types";
21
21
  import { HotspotManager } from "./interaction/HotspotManager";
22
- import type { HotspotInfo } from "./interaction/HotspotManager";
22
+ import type { HotspotInfo, HotspotLabelPosition, HotspotLabelConfig } from "./interaction/HotspotManager";
23
23
  import { SplatTransformProxy, MeshGroupProxy, SplatBoundingBoxProvider } from "./scene/proxies";
24
24
  import { TransformableObject, GizmoMode } from "./core/gizmo/TransformGizmo";
25
25
  import type { BoundingBoxProvider } from "./types";
@@ -49,6 +49,7 @@ export declare class App {
49
49
  private isRunning;
50
50
  private animationId;
51
51
  private useMobileRenderer;
52
+ private lastCompactData;
52
53
  private boundOnResize;
53
54
  constructor(canvas: HTMLCanvasElement);
54
55
  /**
@@ -73,12 +74,14 @@ export declare class App {
73
74
  addPLY(urlOrBuffer: string | ArrayBuffer, onProgress?: ProgressCallback, isLocalFile?: boolean, coordinateSystem?: CoordinateSystem): Promise<number>;
74
75
  /**
75
76
  * 加载 Splat 文件
77
+ * @param coordinateSystem 源数据坐标系,默认 'blender'(Z-up → Y-up 自动转换)
76
78
  */
77
- addSplat(urlOrBuffer: string | ArrayBuffer, onProgress?: ProgressCallback, isLocalFile?: boolean): Promise<number>;
79
+ addSplat(urlOrBuffer: string | ArrayBuffer, onProgress?: ProgressCallback, isLocalFile?: boolean, coordinateSystem?: CoordinateSystem): Promise<number>;
78
80
  /**
79
81
  * 加载 SOG 文件 (Spatially Ordered Gaussians)
82
+ * @param coordinateSystem 源数据坐标系,默认 'blender'(Z-up → Y-up 自动转换)
80
83
  */
81
- addSOG(urlOrBuffer: string | ArrayBuffer, onProgress?: ProgressCallback, isLocalFile?: boolean): Promise<number>;
84
+ addSOG(urlOrBuffer: string | ArrayBuffer, onProgress?: ProgressCallback, isLocalFile?: boolean, coordinateSystem?: CoordinateSystem): Promise<number>;
82
85
  /**
83
86
  * 添加测试立方体
84
87
  */
@@ -128,6 +131,7 @@ export declare class App {
128
131
  getViewportGizmo(): import(".").ViewportGizmo;
129
132
  getBoundingBoxRenderer(): import(".").BoundingBoxRenderer;
130
133
  setGizmoMode(mode: GizmoMode): void;
134
+ setUniformScaleOnly(enabled: boolean): void;
131
135
  setGizmoTarget(object: TransformableObject | null): void;
132
136
  setSelectionBoundingBox(box: SimpleBoundingBox | null): void;
133
137
  setSelectionBoundingBoxProvider(provider: BoundingBoxProvider | null): void;
@@ -151,6 +155,8 @@ export declare class App {
151
155
  getGSRenderer(): GSSplatRenderer | undefined;
152
156
  getGSRendererMobile(): GSSplatRendererMobile | undefined;
153
157
  isUsingMobileRenderer(): boolean;
158
+ getLastCompactData(): import('./gs/PLYLoaderMobile').CompactSplatData | null;
159
+ setLastCompactData(data: import('./gs/PLYLoaderMobile').CompactSplatData): void;
154
160
  getHotspotManager(): HotspotManager;
155
161
  enterHotspotMode(objUrl: string): void;
156
162
  exitHotspotMode(): void;
@@ -160,7 +166,33 @@ export declare class App {
160
166
  getHotspotBillboard(hotspotIndex: number): boolean;
161
167
  getHotspotCount(): number;
162
168
  findHotspotIndexByMeshStart(overlayMeshStartIndex: number): number;
169
+ placeHotspotAt(objUrl: string, position: [number, number, number], normal: [number, number, number], visualDiameter?: number, normalOffset?: number, overrideScale?: number): Promise<HotspotInfo | null>;
163
170
  getOverlayMeshByIndex(index: number): import("./mesh/Mesh").Mesh | null;
171
+ /**
172
+ * 检测给定屏幕坐标命中了哪个热点
173
+ * @returns 热点索引,-1 表示未命中
174
+ */
175
+ hitTestHotspot(clientX: number, clientY: number): number;
176
+ /**
177
+ * 设置热点点击回调(非放置模式下生效)
178
+ */
179
+ setOnHotspotClicked(cb: ((hotspotIndex: number, info: HotspotInfo) => void) | null): void;
180
+ /**
181
+ * 设置热点点击命中半径(NDC 空间,默认 0.05)
182
+ */
183
+ setHotspotHitRadius(radius: number): void;
184
+ /**
185
+ * 设置热点文字标签
186
+ */
187
+ setHotspotLabel(hotspotIndex: number, text: string, position?: HotspotLabelPosition, fontSize?: number, visible?: boolean): boolean;
188
+ /**
189
+ * 设置热点标签可见性
190
+ */
191
+ setHotspotLabelVisible(hotspotIndex: number, visible: boolean): boolean;
192
+ /**
193
+ * 获取热点标签配置
194
+ */
195
+ getHotspotLabel(hotspotIndex: number): HotspotLabelConfig | null;
164
196
  private fetchWithProgress;
165
197
  private parsePLYBuffer;
166
198
  setGridVisible(visible: boolean): void;
@@ -81,6 +81,8 @@ export declare class TransformGizmo {
81
81
  snapIncrement: number;
82
82
  dragMode: GizmoDragMode;
83
83
  flipPlanes: boolean;
84
+ /** 强制等比缩放:开启后无论拖哪个轴,缩放都作用于 xyz */
85
+ uniformScaleOnly: boolean;
84
86
  private _target;
85
87
  private _shapes;
86
88
  private _hoverAxis;
@@ -0,0 +1,13 @@
1
+ import type { EditOp } from './EditOps';
2
+ export declare class EditHistory {
3
+ private history;
4
+ private cursor;
5
+ private onChange?;
6
+ constructor(onChange?: () => void);
7
+ add(op: EditOp): void;
8
+ canUndo(): boolean;
9
+ canRedo(): boolean;
10
+ undo(): void;
11
+ redo(): void;
12
+ clear(): void;
13
+ }
@@ -0,0 +1,56 @@
1
+ import { SplatState } from './SplatState';
2
+ export interface EditOp {
3
+ name: string;
4
+ do(): void;
5
+ undo(): void;
6
+ destroy?(): void;
7
+ }
8
+ export declare class SelectOp implements EditOp {
9
+ name: string;
10
+ private inner;
11
+ constructor(state: SplatState, op: 'add' | 'remove' | 'set', predicate: (i: number) => boolean);
12
+ do(): void;
13
+ undo(): void;
14
+ }
15
+ export declare class SelectAllOp implements EditOp {
16
+ name: string;
17
+ private inner;
18
+ constructor(state: SplatState);
19
+ do(): void;
20
+ undo(): void;
21
+ }
22
+ export declare class SelectNoneOp implements EditOp {
23
+ name: string;
24
+ private inner;
25
+ constructor(state: SplatState);
26
+ do(): void;
27
+ undo(): void;
28
+ }
29
+ export declare class SelectInvertOp implements EditOp {
30
+ name: string;
31
+ private inner;
32
+ constructor(state: SplatState);
33
+ do(): void;
34
+ undo(): void;
35
+ }
36
+ export declare class DeleteSelectionOp implements EditOp {
37
+ name: string;
38
+ private inner;
39
+ constructor(state: SplatState);
40
+ do(): void;
41
+ undo(): void;
42
+ }
43
+ export declare class HideSelectionOp implements EditOp {
44
+ name: string;
45
+ private inner;
46
+ constructor(state: SplatState);
47
+ do(): void;
48
+ undo(): void;
49
+ }
50
+ export declare class UnhideAllOp implements EditOp {
51
+ name: string;
52
+ private inner;
53
+ constructor(state: SplatState);
54
+ do(): void;
55
+ undo(): void;
56
+ }
@@ -0,0 +1,13 @@
1
+ type Listener = (...args: any[]) => void;
2
+ type AsyncListener = (...args: any[]) => Promise<any>;
3
+ export declare class Events {
4
+ private listeners;
5
+ private asyncHandlers;
6
+ on(event: string, fn: Listener): () => void;
7
+ off(event: string, fn: Listener): void;
8
+ fire(event: string, ...args: any[]): void;
9
+ handler(event: string, fn: AsyncListener): void;
10
+ invoke(event: string, ...args: any[]): Promise<any>;
11
+ destroy(): void;
12
+ }
13
+ export {};
@@ -0,0 +1,80 @@
1
+ /**
2
+ * SplatEditor - 3DGS 模型编辑器核心类
3
+ *
4
+ * 协调选择工具、编辑历史、状态管理和渲染器集成。
5
+ * 将 splat 中心投影到屏幕空间来判断哪些 splat 在选区内。
6
+ */
7
+ import type { Camera } from '../core/Camera';
8
+ import type { GSSplatRenderer } from '../gs/GSSplatRenderer';
9
+ import type { CompactSplatData } from '../gs/PLYLoaderMobile';
10
+ import { SplatState } from './SplatState';
11
+ import { EditHistory } from './EditHistory';
12
+ import { ToolManager } from './tools/ToolManager';
13
+ export interface SplatEditorCallbacks {
14
+ onStateChanged?: () => void;
15
+ onToolChanged?: (tool: string | null) => void;
16
+ onHistoryChanged?: (canUndo: boolean, canRedo: boolean) => void;
17
+ /** 退出编辑模式时调用,传回精简后的 CompactSplatData(已剔除删除的 splat) */
18
+ onApplyEdits?: (newData: CompactSplatData) => void;
19
+ }
20
+ export declare class SplatEditor {
21
+ private camera;
22
+ private gsRenderer;
23
+ private container;
24
+ private splatState;
25
+ private editHistory;
26
+ private toolManager;
27
+ private compactData;
28
+ private callbacks;
29
+ private maskCanvas;
30
+ private maskCtx;
31
+ private toolOverlay;
32
+ private projectedPositions;
33
+ private projDirty;
34
+ private _active;
35
+ private overlayCleanup;
36
+ constructor(camera: Camera, gsRenderer: GSSplatRenderer, container: HTMLElement, callbacks?: SplatEditorCallbacks);
37
+ get active(): boolean;
38
+ get state(): SplatState;
39
+ get history(): EditHistory;
40
+ get tools(): ToolManager;
41
+ /**
42
+ * 设置 compact 数据引用(用于导出和颜色匹配)
43
+ */
44
+ setCompactData(data: CompactSplatData): void;
45
+ /**
46
+ * 进入编辑模式
47
+ */
48
+ enter(): void;
49
+ /**
50
+ * 退出编辑模式,将编辑结果永久写入渲染数据
51
+ */
52
+ exit(): void;
53
+ /**
54
+ * 将编辑结果写回渲染器:从 CompactSplatData 中剔除被标记为 deleted 的 splat,
55
+ * 用精简数据重建 GPU 缓冲区。
56
+ */
57
+ private applyEditsToRenderer;
58
+ selectAll(): void;
59
+ selectNone(): void;
60
+ selectInvert(): void;
61
+ deleteSelection(): void;
62
+ hideSelection(): void;
63
+ unhideAll(): void;
64
+ undo(): void;
65
+ redo(): void;
66
+ exportPLY(): ArrayBuffer | null;
67
+ downloadPLY(filename?: string): void;
68
+ private selectByRect;
69
+ private selectByMask;
70
+ private selectByColor;
71
+ /**
72
+ * 每帧调用,标记投影需要更新
73
+ */
74
+ markProjectionDirty(): void;
75
+ private ensureProjection;
76
+ private onStateChanged;
77
+ private setupOverlayEventForwarding;
78
+ private _keyHandler;
79
+ private _onKeyDown;
80
+ }
@@ -0,0 +1,6 @@
1
+ import { SplatState } from './SplatState';
2
+ /**
3
+ * 将编辑后的 splat 数据导出为 PLY 文件
4
+ * 仅包含未删除的 splats
5
+ */
6
+ export declare function exportEditedPLY(positions: Float32Array, scales: Float32Array, rotations: Float32Array, colors: Float32Array, opacities: Float32Array, shCoeffs: Float32Array | null, state: SplatState): ArrayBuffer;
@@ -0,0 +1,18 @@
1
+ export declare enum State {
2
+ selected = 1,
3
+ hidden = 2,
4
+ deleted = 4
5
+ }
6
+ export declare class SplatState {
7
+ readonly count: number;
8
+ readonly data: Uint8Array;
9
+ private _numSelected;
10
+ private _numHidden;
11
+ private _numDeleted;
12
+ constructor(count: number);
13
+ get numSelected(): number;
14
+ get numHidden(): number;
15
+ get numDeleted(): number;
16
+ recalcCounts(): void;
17
+ clear(): void;
18
+ }
@@ -0,0 +1,7 @@
1
+ export { SplatEditor } from './SplatEditor';
2
+ export type { SplatEditorCallbacks } from './SplatEditor';
3
+ export { SplatState, State } from './SplatState';
4
+ export { EditHistory } from './EditHistory';
5
+ export { ToolManager } from './tools/ToolManager';
6
+ export type { Tool } from './tools/ToolManager';
7
+ export { exportEditedPLY } from './SplatExporter';
@@ -0,0 +1,21 @@
1
+ import type { Tool } from './ToolManager';
2
+ export declare class BrushSelection implements Tool {
3
+ private parent;
4
+ private svg;
5
+ private circle;
6
+ private maskCanvas;
7
+ private maskCtx;
8
+ private onMaskSelect;
9
+ private radius;
10
+ private prev;
11
+ private dragId;
12
+ constructor(parent: HTMLElement, maskCanvas: HTMLCanvasElement, maskCtx: CanvasRenderingContext2D, onMaskSelect: (op: 'add' | 'remove' | 'set', canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) => void);
13
+ activate(): void;
14
+ deactivate(): void;
15
+ private update;
16
+ private pointerdown;
17
+ private pointermove;
18
+ private dragEnd;
19
+ private pointerup;
20
+ private wheel;
21
+ }
@@ -0,0 +1,21 @@
1
+ import type { Tool } from './ToolManager';
2
+ /**
3
+ * EyedropperSelection - 吸管/颜色匹配选择工具
4
+ * 点击拾取一个 splat 的颜色,然后选择所有颜色相似的 splats
5
+ */
6
+ export declare class EyedropperSelection implements Tool {
7
+ private parent;
8
+ private onColorMatch;
9
+ private thresholdEl;
10
+ private threshold;
11
+ private pointerId;
12
+ constructor(parent: HTMLElement, onColorMatch: (op: 'add' | 'remove' | 'set', normalizedPoint: {
13
+ x: number;
14
+ y: number;
15
+ }, threshold: number) => void);
16
+ activate(): void;
17
+ deactivate(): void;
18
+ private pointerdown;
19
+ private pointermove;
20
+ private pointerup;
21
+ }
@@ -0,0 +1,22 @@
1
+ import type { Tool } from './ToolManager';
2
+ /**
3
+ * FloodSelection - 基于不透明度的洪水填充选择工具
4
+ * 从点击位置开始,向相邻像素扩展,选择不透明度相近的区域
5
+ */
6
+ export declare class FloodSelection implements Tool {
7
+ private parent;
8
+ private maskCanvas;
9
+ private maskCtx;
10
+ private onMaskSelect;
11
+ private getOffscreenImage;
12
+ private thresholdEl;
13
+ private threshold;
14
+ private clicked;
15
+ private imageData;
16
+ constructor(parent: HTMLElement, maskCanvas: HTMLCanvasElement, maskCtx: CanvasRenderingContext2D, onMaskSelect: (op: 'add' | 'remove' | 'set', canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) => void, getOffscreenImage: (width: number, height: number) => Uint8Array | null);
17
+ activate(): void;
18
+ deactivate(): void;
19
+ private pointerdown;
20
+ private pointermove;
21
+ private pointerup;
22
+ }
@@ -0,0 +1,24 @@
1
+ import type { Tool } from './ToolManager';
2
+ export declare class LassoSelection implements Tool {
3
+ private parent;
4
+ private svg;
5
+ private polygon;
6
+ private maskCanvas;
7
+ private maskCtx;
8
+ private onMaskSelect;
9
+ private points;
10
+ private currentPoint;
11
+ private lastPointTime;
12
+ private dragId;
13
+ constructor(parent: HTMLElement, maskCanvas: HTMLCanvasElement, maskCtx: CanvasRenderingContext2D, onMaskSelect: (op: 'add' | 'remove' | 'set', canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) => void);
14
+ activate(): void;
15
+ deactivate(): void;
16
+ private dist;
17
+ private paint;
18
+ private update;
19
+ private commitSelection;
20
+ private pointerdown;
21
+ private pointermove;
22
+ private dragEnd;
23
+ private pointerup;
24
+ }
@@ -0,0 +1,22 @@
1
+ import type { Tool } from './ToolManager';
2
+ export declare class PolygonSelection implements Tool {
3
+ private parent;
4
+ private svg;
5
+ private polyline;
6
+ private maskCanvas;
7
+ private maskCtx;
8
+ private onMaskSelect;
9
+ private points;
10
+ private currentPoint;
11
+ constructor(parent: HTMLElement, maskCanvas: HTMLCanvasElement, maskCtx: CanvasRenderingContext2D, onMaskSelect: (op: 'add' | 'remove' | 'set', canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) => void);
12
+ activate(): void;
13
+ deactivate(): void;
14
+ private dist;
15
+ private isClosed;
16
+ private paint;
17
+ private commitSelection;
18
+ private pointerdown;
19
+ private pointermove;
20
+ private pointerup;
21
+ private dblclick;
22
+ }
@@ -0,0 +1,24 @@
1
+ import type { Tool } from './ToolManager';
2
+ export declare class RectSelection implements Tool {
3
+ private parent;
4
+ private svg;
5
+ private rect;
6
+ private onSelect;
7
+ private start;
8
+ private end;
9
+ private dragId;
10
+ private dragMoved;
11
+ constructor(parent: HTMLElement, onSelect: (op: 'add' | 'remove' | 'set', rect: {
12
+ startX: number;
13
+ startY: number;
14
+ endX: number;
15
+ endY: number;
16
+ }) => void);
17
+ activate(): void;
18
+ deactivate(): void;
19
+ private updateRect;
20
+ private pointerdown;
21
+ private pointermove;
22
+ private dragEnd;
23
+ private pointerup;
24
+ }
@@ -0,0 +1,14 @@
1
+ export interface Tool {
2
+ activate(): void;
3
+ deactivate(): void;
4
+ }
5
+ export declare class ToolManager {
6
+ private tools;
7
+ private _active;
8
+ private onToolChanged?;
9
+ constructor(onToolChanged?: (name: string | null) => void);
10
+ get active(): string | null;
11
+ register(name: string, tool: Tool): void;
12
+ activate(name: string | null): void;
13
+ deactivateAll(): void;
14
+ }
@@ -48,6 +48,11 @@ export declare class GSSplatRenderer implements IGSSplatRendererWithCapabilities
48
48
  private sortStateInitialized;
49
49
  private sortFrequency;
50
50
  private frameCounter;
51
+ private editorStateBuffer;
52
+ private editorPipeline;
53
+ private editorBindGroupLayout;
54
+ private editorBindGroup;
55
+ private editorEnabled;
51
56
  private depthNormalPipeline;
52
57
  private depthRT;
53
58
  private depthRTView;
@@ -109,5 +114,21 @@ export declare class GSSplatRenderer implements IGSSplatRendererWithCapabilities
109
114
  } | null;
110
115
  supportsSHMode(mode: SHMode): boolean;
111
116
  getCapabilities(): RendererCapabilities;
117
+ /**
118
+ * 启用编辑器模式,传入每个 splat 的状态数据
119
+ */
120
+ setEditorState(stateData: Uint8Array): void;
121
+ /**
122
+ * 更新编辑器状态数据
123
+ */
124
+ updateEditorState(stateData: Uint8Array): void;
125
+ private alignStateData;
126
+ /**
127
+ * 禁用编辑器模式
128
+ */
129
+ clearEditorState(): void;
130
+ private createEditorPipeline;
131
+ private rebuildEditorBindGroup;
132
+ private buildEditorShader;
112
133
  destroy(): void;
113
134
  }
package/dist/index.d.ts CHANGED
@@ -41,9 +41,16 @@ export type { SceneObjectType, SceneObjectInfo } from './scene/SceneManager';
41
41
  export { SplatTransformProxy, MeshGroupProxy, SplatBoundingBoxProvider, } from './scene/proxies';
42
42
  export { GizmoManager } from './interaction/GizmoManager';
43
43
  export { HotspotManager } from './interaction/HotspotManager';
44
- export type { HotspotInfo } from './interaction/HotspotManager';
44
+ export type { HotspotInfo, HotspotLabelConfig, HotspotLabelPosition } from './interaction/HotspotManager';
45
45
  export type { TransformableObject } from './core/gizmo/TransformGizmo';
46
46
  export { TransformGizmo, GizmoMode } from './core/gizmo/TransformGizmo';
47
47
  export type { GizmoTheme, TransformGizmoConfig, GizmoSpace } from './core/gizmo/TransformGizmo';
48
+ export { SplatEditor } from './editor/SplatEditor';
49
+ export type { SplatEditorCallbacks } from './editor/SplatEditor';
50
+ export { SplatState, State } from './editor/SplatState';
51
+ export { EditHistory } from './editor/EditHistory';
52
+ export { ToolManager } from './editor/tools/ToolManager';
53
+ export type { Tool } from './editor/tools/ToolManager';
54
+ export { exportEditedPLY } from './editor/SplatExporter';
48
55
  export { App } from './App';
49
56
  export type { ProgressCallback } from './App';
@@ -52,6 +52,7 @@ export declare class GizmoManager {
52
52
  * 设置 Gizmo 模式
53
53
  */
54
54
  setGizmoMode(mode: GizmoMode): void;
55
+ setUniformScaleOnly(enabled: boolean): void;
55
56
  /**
56
57
  * 设置 Gizmo 目标对象
57
58
  */
@@ -10,6 +10,15 @@ import { OrbitControls } from "../core/OrbitControls";
10
10
  import { MeshRenderer } from "../mesh/MeshRenderer";
11
11
  import type { IGSSplatRenderer } from "../gs/IGSSplatRenderer";
12
12
  import type { Vec3Tuple } from "../types";
13
+ /** 热点标签位置 */
14
+ export type HotspotLabelPosition = 'top' | 'left' | 'right';
15
+ /** 热点标签配置 */
16
+ export interface HotspotLabelConfig {
17
+ text: string;
18
+ position: HotspotLabelPosition;
19
+ fontSize: number;
20
+ visible: boolean;
21
+ }
13
22
  /** 热点信息 */
14
23
  export interface HotspotInfo {
15
24
  position: Vec3Tuple;
@@ -24,6 +33,10 @@ export interface HotspotInfo {
24
33
  placedNormalOffset: number;
25
34
  /** 放置时的本地中心偏移 (本地空间) */
26
35
  placedLocalCenter: Vec3Tuple;
36
+ /** 文字标签配置 */
37
+ label?: HotspotLabelConfig;
38
+ /** 标签 DOM 元素(内部使用) */
39
+ _labelElement?: HTMLDivElement;
27
40
  }
28
41
  /**
29
42
  * HotspotManager
@@ -58,6 +71,9 @@ export declare class HotspotManager {
58
71
  private lastNormal;
59
72
  private indicatorBaseRadius;
60
73
  private pickRadiusPx;
74
+ private labelContainer;
75
+ private onHotspotClicked;
76
+ private hotspotHitRadius;
61
77
  private onHotspotPlaced;
62
78
  private onModeChanged;
63
79
  constructor(renderer: Renderer, camera: Camera, canvas: HTMLCanvasElement, controls: OrbitControls, meshRenderer: MeshRenderer);
@@ -69,6 +85,41 @@ export declare class HotspotManager {
69
85
  getHotspots(): HotspotInfo[];
70
86
  setOnHotspotPlaced(cb: ((info: HotspotInfo) => void) | null): void;
71
87
  setOnModeChanged(cb: ((active: boolean) => void) | null): void;
88
+ private initLabelContainer;
89
+ /**
90
+ * 设置热点标签
91
+ */
92
+ setHotspotLabel(hotspotIndex: number, text: string, position?: HotspotLabelPosition, fontSize?: number, visible?: boolean): boolean;
93
+ /**
94
+ * 设置热点标签可见性
95
+ */
96
+ setHotspotLabelVisible(hotspotIndex: number, visible: boolean): boolean;
97
+ /**
98
+ * 获取热点标签配置
99
+ */
100
+ getHotspotLabel(hotspotIndex: number): HotspotLabelConfig | null;
101
+ private ensureLabelElement;
102
+ private applyLabelStyle;
103
+ /**
104
+ * 每帧更新所有标签的屏幕位置
105
+ */
106
+ updateLabels(): void;
107
+ /**
108
+ * 设置热点点击回调
109
+ */
110
+ setOnHotspotClicked(cb: ((hotspotIndex: number, info: HotspotInfo) => void) | null): void;
111
+ private _clickListenerBound;
112
+ private _boundOnCanvasClick;
113
+ private _onCanvasClick;
114
+ /**
115
+ * 检测给定屏幕坐标命中了哪个热点
116
+ * @returns 热点索引,-1 表示未命中
117
+ */
118
+ hitTestHotspot(clientX: number, clientY: number): number;
119
+ /**
120
+ * 设置热点点击命中半径(NDC 空间,默认 0.05)
121
+ */
122
+ setHotspotHitRadius(radius: number): void;
72
123
  private pickPixelX;
73
124
  private pickPixelY;
74
125
  private lastClientX;
@@ -130,6 +181,12 @@ export declare class HotspotManager {
130
181
  * 通过 overlay mesh 起始索引找到对应的热点索引
131
182
  */
132
183
  findHotspotIndexByMeshStart(overlayMeshStartIndex: number): number;
184
+ /**
185
+ * 在指定位置和法线方向程序化放置一个热点(用于从保存数据恢复)。
186
+ * 逻辑与交互放置一致:加载 OBJ → 计算朝向矩阵 → addOverlayMesh → 注册到内部列表。
187
+ * @returns 放置后的 HotspotInfo,失败返回 null
188
+ */
189
+ placeHotspotAt(objUrl: string, position: Vec3Tuple, normal: Vec3Tuple, visualDiameter?: number, normalOffset?: number, overrideScale?: number): Promise<HotspotInfo | null>;
133
190
  /**
134
191
  * 每帧调用:更新所有 billboard 热点朝向相机
135
192
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d5techs/3dgs-lib",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "可扩展的 WebGPU 3D 渲染引擎",
5
5
  "type": "module",
6
6
  "main": "./dist/3dgs-lib.cjs",