@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
255 lines (237 loc) • 7.91 kB
text/typescript
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;
}
}