Skip to content

Commit cec0a5c

Browse files
authored
Merge pull request #109 from pattern-x/feature/hotpoint-plugin-src-code
Upload hotpoint plugin code as an example
2 parents 3d21be8 + 127ccc1 commit cec0a5c

File tree

1 file changed

+171
-0
lines changed

1 file changed

+171
-0
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import * as THREE from "three";
2+
import { CSS2DObject, CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer.js";
3+
4+
import { BaseViewer, CSS2DObjectUtils, log, Hotpoint, matrixAutoUpdate, Plugin, ViewerEvent, Vector2, Vector3 } from "src/core";
5+
6+
/**
7+
* Hotpoint plugin manages hotpoints in a viewer.
8+
* It can be used by DxfViewer and BimViewer.
9+
* The hotpoint feature in VRViewer is more complex, that one has nothing to do with this plugin.
10+
* VRViewer is able to use this plugin though.
11+
* - A hotpoint is created and stored by user. User define its html and css.
12+
* - A hotpoint can be added to, and removed from viewer.
13+
* - Caller should set a hotpointId that is unique in the session of current viewer.
14+
* - DxfViewer doesn't maintain the relationship between hotpoint and layout.
15+
*/
16+
export class HotpointPlugin extends Plugin {
17+
protected hotpointRoot?: THREE.Group;
18+
protected css2dRenderer: CSS2DRenderer;
19+
20+
constructor(viewer: BaseViewer) {
21+
super(viewer, { id: "HotpointPlugin" });
22+
23+
// eslint-disable-next-line
24+
this.css2dRenderer = (this.viewer as any).css2dRenderer;
25+
26+
this.viewer.addEventListener(ViewerEvent.AfterRender, this.onAfterRender);
27+
}
28+
29+
/**
30+
* @description {en} Adds a hotpoint.
31+
* Caller should set a hotpointId that is unique in the session of current viewer.
32+
* @description {zh} 添加热点。
33+
* 调用者应该设置一个在当前Viewer会话中唯一的热点id。
34+
* @param hotpoint
35+
* - {en} hotpoint data.
36+
* - {zh} 热点数据。
37+
* @example
38+
* ``` typescript
39+
* const hotpoint = {
40+
* hotpointId: "c6ea70a3-ddb0-4dd0-87c8-bd2491936428",
41+
* anchorPosition: [0, 0, 0],
42+
* html: "<div>hotpoint</div>",
43+
* visible: true,
44+
* };
45+
* const plugin = new HotpointPlugin(viewer);
46+
* plugin.add(hotpoint);
47+
* ```
48+
*/
49+
add(hotpoint: Hotpoint): void {
50+
const exists = this.has(hotpoint.hotpointId);
51+
if (exists) {
52+
log.warn(`[Hotpoint] Hotpoint with id '${hotpoint.hotpointId}' already exist!`);
53+
return;
54+
}
55+
const p = hotpoint.anchorPosition;
56+
const object = CSS2DObjectUtils.createHotpoint(hotpoint.html);
57+
object.position.set(p[0] || 0, p[1] || 0, p[2] || 0);
58+
object.visible = hotpoint.visible !== false;
59+
object.userData.hotpoint = hotpoint;
60+
61+
if (!this.hotpointRoot) {
62+
this.hotpointRoot = new THREE.Group();
63+
this.hotpointRoot.matrixAutoUpdate = matrixAutoUpdate;
64+
this.hotpointRoot.matrixWorldAutoUpdate = false;
65+
this.hotpointRoot.name = "HotpointRoot";
66+
this.viewer.scene?.add(this.hotpointRoot);
67+
}
68+
this.hotpointRoot.add(object);
69+
object.updateWorldMatrix(false, false);
70+
this.viewer.enableRender();
71+
}
72+
73+
/**
74+
* @description {en} Removes a hotpoint by given hotpointId.
75+
* @description {zh} 根据热点id删除热点。
76+
* @param {string} hotpointId
77+
* - {en} hotpoint id.
78+
* - {zh} 热点id。
79+
* @example
80+
* ``` typescript
81+
* const hotpointId = "c6ea70a3-ddb0-4dd0-87c8-bd2491936428";
82+
* const plugin = new HotpointPlugin(viewer);
83+
* plugin.remove(hotpointId);
84+
* ```
85+
*/
86+
remove(hotpointId: string): void {
87+
const objects = this.hotpointRoot?.children || [];
88+
for (let i = 0; i < objects.length; ++i) {
89+
const obj = objects[i];
90+
if (obj.userData.hotpoint?.hotpointId === hotpointId) {
91+
obj.removeFromParent();
92+
}
93+
}
94+
}
95+
96+
/**
97+
* @description {en} Clears all hotpoints.
98+
* @description {zh} 清除所有热点。
99+
* @example
100+
* ``` typescript
101+
* const plugin = new HotpointPlugin(viewer);
102+
* plugin.clear();
103+
* ```
104+
*/
105+
clear(): void {
106+
this.hotpointRoot?.clear();
107+
}
108+
109+
/**
110+
* Checks if hotpoint with specific id already exist
111+
* Caller should set a hotpointId that is unique in the session of current DxfViewer.
112+
* @internal
113+
*/
114+
has(hotpointId: string): boolean {
115+
return !!this.findHotpointObject(hotpointId);
116+
}
117+
118+
/**
119+
* @description {en} Moves a hotpoint.
120+
* @description {zh} 移动热点的位置。
121+
* @example
122+
* ``` typescript
123+
* const hotpointId = "c6ea70a3-ddb0-4dd0-87c8-bd2491936428";
124+
* const plugin = new HotpointPlugin(viewer);
125+
* plugin.move(hotpointId, [10, 10, 0]);
126+
* ```
127+
*/
128+
move(hotpointId: string, position: Vector2 | Vector3): void {
129+
const object = this.findHotpointObject(hotpointId);
130+
if (object) {
131+
const z = (position as Vector3).z || 0;
132+
object.position.set(position.x, position.y, z);
133+
object.updateWorldMatrix(false, false);
134+
this.viewer.enableRender();
135+
// we probably don't need to update the anchorPosition
136+
}
137+
}
138+
139+
/**
140+
* @description {en} Hides or show a hotpoint.
141+
* @description {zh} 显示或隐藏一个热点。
142+
* @example
143+
* ``` typescript
144+
* const hotpointId = "c6ea70a3-ddb0-4dd0-87c8-bd2491936428";
145+
* const plugin = new HotpointPlugin(viewer);
146+
* plugin.setVisible(hotpointId, false);
147+
* ```
148+
*/
149+
setVisible(hotpointId: string, visible: boolean): void {
150+
const object = this.findHotpointObject(hotpointId);
151+
if (object) {
152+
object.visible = visible;
153+
}
154+
}
155+
156+
protected findHotpointObject(hotpointId: string): CSS2DObject | undefined {
157+
const objects = this.hotpointRoot?.children || [];
158+
const object = objects.find((obj) => obj.userData.hotpoint?.hotpointId === hotpointId);
159+
return object as CSS2DObject;
160+
}
161+
162+
protected onAfterRender = () => {
163+
const scene = this.viewer.scene;
164+
const camera = this.viewer.camera;
165+
if (!scene || !camera || !this.hotpointRoot || this.hotpointRoot.children.length === 0) {
166+
return;
167+
}
168+
// TODO: if css2dRenderer.render() is already called in viewer, then we don't need to call it again.
169+
this.css2dRenderer?.render(scene, camera);
170+
};
171+
}

0 commit comments

Comments
 (0)