UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

255 lines (237 loc) 7.91 kB
import {BaseObjNodeClass} from '../_Base'; import {Object3D} from 'three'; import {DisplayNodeController, DisplayNodeControllerCallbacks} from '../../utils/DisplayNodeController'; import {Group} from 'three'; import {BaseSopNodeType} from '../../sop/_Base'; import {Poly} from '../../../Poly'; import {CoreGroup} from '../../../../core/geometry/Group'; import {PolyScene} from '../../../scene/PolyScene'; // import {TesselationParams} from '../../../../core/geometry/modules/cad/CadCommon'; const DISPLAY_PARAM_NAME = 'display'; export type OnSopGroupUpdatedHook = () => void; interface BaseObjNodeClassWithDisplayNode extends BaseObjNodeClass { displayNodeController: DisplayNodeController; // pv: TesselationParams; } export class ObjChildrenDisplayController { protected _childrenUuids: Set<string> = new Set(); protected _sopGroup = this._createSopGroup(); protected _newSpecializedObjects: Object3D[] = []; protected _newObjectsAreDifferent: boolean = false; private _scene: PolyScene; constructor(protected node: BaseObjNodeClassWithDisplayNode) { this._scene = this.node.scene(); } private _createSopGroup() { // This may need to be a Mesh for the rivet to update correctly // But when it is not used for a rivet, there is a place where a MeshBasicMaterial // is added to it, making it an additional webgl program for the renderer. // const mesh = new Mesh(); const group = new Group(); group.matrixAutoUpdate = false; return group; } sopGroup() { return this._sopGroup; } setSopGroupName() { this._sopGroup.name = `${this.node.name()}:sopGroup`; } dispose() { this._clearHooks(); } displayNodeControllerCallbacks(): DisplayNodeControllerCallbacks { return { onDisplayNodeRemove: () => { this.removeChildren(); }, onDisplayNodeSet: () => { // use a timeout here, so that the node isn't cooked too early when being copy/pasted, if it had the display flag on. // This would make nodes error setTimeout(() => { this.requestDisplayNodeContainer(); }, 0); }, onDisplayNodeUpdate: () => { if (!this.node.scene().loadingController.loaded()) { return; } this.requestDisplayNodeContainer(); }, }; } initializeNode() { this.node.object.add(this.sopGroup()); this.node.nameController.add_post_set_fullPath_hook(this.setSopGroupName.bind(this)); this._createSopGroup(); const displayFlag = this.node.flags?.display; if (displayFlag) { displayFlag.onUpdate(() => { this._updateSopGroupHierarchy(); if (displayFlag.active()) { this.requestDisplayNodeContainer(); } }); } } private _updateSopGroupHierarchy() { const displayFlag = this.node.flags?.display; if (displayFlag) { const sopGroup = this.sopGroup(); if (this.usedInScene()) { sopGroup.visible = true; this.node.object.add(sopGroup); sopGroup.updateMatrix(); } else { sopGroup.visible = false; this.node.object.remove(sopGroup); } Poly.onSceneUpdatedHooks.runHooks(); } } usedInScene(): boolean { const node = this.node; if (node.disposed() == true) { return false; } const usedInScene = node.usedInScene(); if (!usedInScene) { return false; } const displayFlagOn = node.flags?.display?.active() || false; if (!displayFlagOn) { return false; } const params = node.params; const hasActiveParam = params.has(DISPLAY_PARAM_NAME); const isActiveParamOn = node.params.boolean(DISPLAY_PARAM_NAME); const paramActiveOn = !hasActiveParam || isActiveParamOn; return paramActiveOn; } async requestDisplayNodeContainer() { if (!this._scene.loadingController.loaded()) { return; } if (this.usedInScene()) { await this._setContentUnderSopGroup(); } } private removeChildren() { if (this._sopGroup.children.length == 0) { return; } let child: Object3D | undefined; Poly.onObjectsAddRemoveHooks.runOnRemoveHooks(this._scene, this._sopGroup); while ((child = this._sopGroup.children[0])) { this._sopGroup.remove(child); } this._childrenUuids.clear(); this._notifyCamerasController(); } async _setContentUnderSopGroup() { // we also check that the parent are the same, in case the node has been deleted // TODO: there should be a wider refactor where deleted node cannot raise callbacks such as flags update const displayNode = this.node.displayNodeController.displayNode() as BaseSopNodeType; if (displayNode && displayNode.parent()?.graphNodeId() == this.node.graphNodeId()) { const container = await displayNode.compute(); const coreGroup = container.coreContent(); if (coreGroup) { // check if the new objects are different const newObjects = coreGroup.threejsObjects(); const _checkObjectsHaveChanged = () => { const objectsCountChanged = newObjects.length != this._childrenUuids.size; if (objectsCountChanged) { return true; } for (const object of newObjects) { if (!this._childrenUuids.has(object.uuid)) { return true; } } return false; }; this._newObjectsAreDifferent = _checkObjectsHaveChanged(); this._newSpecializedObjects.length = 0; this._addSpecializedObjects(displayNode, coreGroup, this._newSpecializedObjects); // update hierarchy if different if (this._newObjectsAreDifferent) { this.removeChildren(); const addObject = (object: Object3D) => { this._sopGroup.add(object); // ensure the matrix of the parent is used object.updateMatrix(); this._childrenUuids.add(object.uuid); }; for (const object of newObjects) { addObject(object); } for (const object of this._newSpecializedObjects) { addObject(object); } } this._notifyCamerasController(); this._runOnSopGroupUpdatedHooks(); if (this._scene.loadingController.loaded()) { Poly.onObjectsAddRemoveHooks.runOnAddHooks(this._scene, this._sopGroup); Poly.onSceneUpdatedHooks.runHooks(); } return; } } this.removeChildren(); this._runOnSopGroupUpdatedHooks(); if (this._scene.loadingController.loaded()) { Poly.onSceneUpdatedHooks.runHooks(); } } private _notifyCamerasController() { this._scene.camerasController.updateFromChangeInObject(this._sopGroup); } protected _addSpecializedObjects(displayNode: BaseSopNodeType, coreGroup: CoreGroup, newObjects: Object3D[]) {} // // // CALLBACKS // // private _onSopGroupUpdatedHookNames: string[] | undefined; private _onSopGroupUpdatedHooks: OnSopGroupUpdatedHook[] | undefined; registerOnSopGroupUpdated(callbackName: string, callback: OnSopGroupUpdatedHook) { this._onSopGroupUpdatedHookNames = this._onSopGroupUpdatedHookNames || []; this._onSopGroupUpdatedHooks = this._onSopGroupUpdatedHooks || []; this._onSopGroupUpdatedHookNames.push(callbackName); this._onSopGroupUpdatedHooks.push(callback); } private _clearHooks() { if (!this._onSopGroupUpdatedHookNames || !this._onSopGroupUpdatedHooks) { return; } for (const hookName of this._onSopGroupUpdatedHookNames) { this.deregisterOnSopGroupUpdated(hookName); } } deregisterOnSopGroupUpdated(callbackName: string) { if (!this._onSopGroupUpdatedHookNames || !this._onSopGroupUpdatedHooks) { return; } const index = this._onSopGroupUpdatedHookNames?.indexOf(callbackName); this._onSopGroupUpdatedHookNames.splice(index, 1); this._onSopGroupUpdatedHooks.splice(index, 1); if (this._onSopGroupUpdatedHookNames.length == 0) { this._onSopGroupUpdatedHookNames = undefined; } if (this._onSopGroupUpdatedHooks.length == 0) { this._onSopGroupUpdatedHooks = undefined; } } private _runOnSopGroupUpdatedHooks() { if (this._onSopGroupUpdatedHooks) { const hooks = [...this._onSopGroupUpdatedHooks]; for (const hook of hooks) { hook(); } } } onSopGroupUpdatedCallbackNames() { return this._onSopGroupUpdatedHookNames; } }