Skip to content

Commit

Permalink
Merge pull request #109 from pattern-x/feature/hotpoint-plugin-src-code
Browse files Browse the repository at this point in the history
Upload hotpoint plugin code as an example
  • Loading branch information
pattern-x authored Jul 25, 2023
2 parents 3d21be8 + 127ccc1 commit cec0a5c
Showing 1 changed file with 171 additions and 0 deletions.
171 changes: 171 additions & 0 deletions sdk-src/plugins/hotpoint/HotpointPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import * as THREE from "three";
import { CSS2DObject, CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer.js";

import { BaseViewer, CSS2DObjectUtils, log, Hotpoint, matrixAutoUpdate, Plugin, ViewerEvent, Vector2, Vector3 } from "src/core";

/**
* Hotpoint plugin manages hotpoints in a viewer.
* It can be used by DxfViewer and BimViewer.
* The hotpoint feature in VRViewer is more complex, that one has nothing to do with this plugin.
* VRViewer is able to use this plugin though.
* - A hotpoint is created and stored by user. User define its html and css.
* - A hotpoint can be added to, and removed from viewer.
* - Caller should set a hotpointId that is unique in the session of current viewer.
* - DxfViewer doesn't maintain the relationship between hotpoint and layout.
*/
export class HotpointPlugin extends Plugin {
protected hotpointRoot?: THREE.Group;
protected css2dRenderer: CSS2DRenderer;

constructor(viewer: BaseViewer) {
super(viewer, { id: "HotpointPlugin" });

// eslint-disable-next-line
this.css2dRenderer = (this.viewer as any).css2dRenderer;

this.viewer.addEventListener(ViewerEvent.AfterRender, this.onAfterRender);
}

/**
* @description {en} Adds a hotpoint.
* Caller should set a hotpointId that is unique in the session of current viewer.
* @description {zh} 添加热点。
* 调用者应该设置一个在当前Viewer会话中唯一的热点id。
* @param hotpoint
* - {en} hotpoint data.
* - {zh} 热点数据。
* @example
* ``` typescript
* const hotpoint = {
* hotpointId: "c6ea70a3-ddb0-4dd0-87c8-bd2491936428",
* anchorPosition: [0, 0, 0],
* html: "<div>hotpoint</div>",
* visible: true,
* };
* const plugin = new HotpointPlugin(viewer);
* plugin.add(hotpoint);
* ```
*/
add(hotpoint: Hotpoint): void {
const exists = this.has(hotpoint.hotpointId);
if (exists) {
log.warn(`[Hotpoint] Hotpoint with id '${hotpoint.hotpointId}' already exist!`);
return;
}
const p = hotpoint.anchorPosition;
const object = CSS2DObjectUtils.createHotpoint(hotpoint.html);
object.position.set(p[0] || 0, p[1] || 0, p[2] || 0);
object.visible = hotpoint.visible !== false;
object.userData.hotpoint = hotpoint;

if (!this.hotpointRoot) {
this.hotpointRoot = new THREE.Group();
this.hotpointRoot.matrixAutoUpdate = matrixAutoUpdate;
this.hotpointRoot.matrixWorldAutoUpdate = false;
this.hotpointRoot.name = "HotpointRoot";
this.viewer.scene?.add(this.hotpointRoot);
}
this.hotpointRoot.add(object);
object.updateWorldMatrix(false, false);
this.viewer.enableRender();
}

/**
* @description {en} Removes a hotpoint by given hotpointId.
* @description {zh} 根据热点id删除热点。
* @param {string} hotpointId
* - {en} hotpoint id.
* - {zh} 热点id。
* @example
* ``` typescript
* const hotpointId = "c6ea70a3-ddb0-4dd0-87c8-bd2491936428";
* const plugin = new HotpointPlugin(viewer);
* plugin.remove(hotpointId);
* ```
*/
remove(hotpointId: string): void {
const objects = this.hotpointRoot?.children || [];
for (let i = 0; i < objects.length; ++i) {
const obj = objects[i];
if (obj.userData.hotpoint?.hotpointId === hotpointId) {
obj.removeFromParent();
}
}
}

/**
* @description {en} Clears all hotpoints.
* @description {zh} 清除所有热点。
* @example
* ``` typescript
* const plugin = new HotpointPlugin(viewer);
* plugin.clear();
* ```
*/
clear(): void {
this.hotpointRoot?.clear();
}

/**
* Checks if hotpoint with specific id already exist
* Caller should set a hotpointId that is unique in the session of current DxfViewer.
* @internal
*/
has(hotpointId: string): boolean {
return !!this.findHotpointObject(hotpointId);
}

/**
* @description {en} Moves a hotpoint.
* @description {zh} 移动热点的位置。
* @example
* ``` typescript
* const hotpointId = "c6ea70a3-ddb0-4dd0-87c8-bd2491936428";
* const plugin = new HotpointPlugin(viewer);
* plugin.move(hotpointId, [10, 10, 0]);
* ```
*/
move(hotpointId: string, position: Vector2 | Vector3): void {
const object = this.findHotpointObject(hotpointId);
if (object) {
const z = (position as Vector3).z || 0;
object.position.set(position.x, position.y, z);
object.updateWorldMatrix(false, false);
this.viewer.enableRender();
// we probably don't need to update the anchorPosition
}
}

/**
* @description {en} Hides or show a hotpoint.
* @description {zh} 显示或隐藏一个热点。
* @example
* ``` typescript
* const hotpointId = "c6ea70a3-ddb0-4dd0-87c8-bd2491936428";
* const plugin = new HotpointPlugin(viewer);
* plugin.setVisible(hotpointId, false);
* ```
*/
setVisible(hotpointId: string, visible: boolean): void {
const object = this.findHotpointObject(hotpointId);
if (object) {
object.visible = visible;
}
}

protected findHotpointObject(hotpointId: string): CSS2DObject | undefined {
const objects = this.hotpointRoot?.children || [];
const object = objects.find((obj) => obj.userData.hotpoint?.hotpointId === hotpointId);
return object as CSS2DObject;
}

protected onAfterRender = () => {
const scene = this.viewer.scene;
const camera = this.viewer.camera;
if (!scene || !camera || !this.hotpointRoot || this.hotpointRoot.children.length === 0) {
return;
}
// TODO: if css2dRenderer.render() is already called in viewer, then we don't need to call it again.
this.css2dRenderer?.render(scene, camera);
};
}

0 comments on commit cec0a5c

Please sign in to comment.