UNPKG

@polygonjs/polygonjs

Version:

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

425 lines (387 loc) 13.7 kB
import {Constructor, valueof} from '../../../types/GlobalTypes'; import {BaseEvent, Camera} from 'three'; import {CoreTransform} from '../../../core/Transform'; import {ObjNodeRenderOrder} from './_Base'; import {LayersController, LayerParamConfig} from './utils/LayersController'; import {CameraRenderParamConfig} from './utils/cameras/RenderController'; import {TransformedParamConfig, TransformController} from './utils/TransformController'; import {ObjChildrenDisplayController} from './utils/ObjChildrenDisplayController'; import {DisplayNodeController} from '../utils/DisplayNodeController'; import {NodeContext} from '../../poly/NodeContext'; import {ThreejsViewer} from '../../viewers/Threejs'; import {FlagsControllerD} from '../utils/FlagsController'; import {BaseParamType} from '../../params/_Base'; import {BaseNodeType} from '../_Base'; import {BaseSopNodeType} from '../sop/_Base'; import {TypedObjNode} from './_Base'; import {BaseViewerType} from '../../viewers/_Base'; import {HierarchyController} from './utils/HierarchyController'; import {GeoNodeChildrenMap} from '../../poly/registers/nodes/Sop'; import {CameraHelper} from '../../../core/helpers/CameraHelper'; import {ParamConfig, NodeParamsConfig} from '../utils/params/ParamsConfig'; import {isBooleanTrue} from '../../../core/BooleanValue'; import {NodeCreateOptions} from '../utils/hierarchy/ChildrenController'; import {CameraPostProcessParamConfig} from './utils/cameras/PostProcessParamOptions'; import {Poly} from '../../Poly'; import {CORE_CAMERA_DEFAULT} from '../../../core/camera/CoreCamera'; import {CameraControlsSopOperation} from '../../operations/sop/CameraControls'; import {CameraRendererSopOperation} from '../../operations/sop/CameraRenderer'; import {CameraCSSRendererSopOperation} from '../../operations/sop/CameraCSSRenderer'; import {CameraPostProcessSopOperation} from '../../operations/sop/CameraPostProcess'; import {CameraRenderSceneSopOperation} from '../../operations/sop/CameraRenderScene'; import {CameraFrameModeSopOperation} from '../../operations/sop/CameraFrameMode'; import {CoreCameraFrameParamConfig} from '../../../core/camera/CoreCameraFrameMode'; import {CorePath} from '../../../core/geometry/CorePath'; import {Object3DWithEvent} from '../../../core/geometry/ObjectContent'; export interface OrthoOrPerspCamera extends Camera { near: number; far: number; updateProjectionMatrix: () => void; getFocalLength?: () => void; } const EVENT_CHANGE: BaseEvent<'change'> = {type: 'change'}; export function CameraMainCameraParamConfig<TBase extends Constructor>(Base: TBase) { return class Mixin extends Base { setMainCamera = ParamConfig.BUTTON(null, { callback: (node: BaseNodeType, param: BaseParamType) => { BaseCameraObjNodeClass.PARAM_CALLBACK_setMainCamera(node as BaseCameraObjNodeType); }, }); }; } export function ThreejsCameraTransformParamConfig<TBase extends Constructor>(Base: TBase) { return class Mixin extends Base { camera = ParamConfig.FOLDER(); /** @param controls node to allow the camera to be moved by user input */ controls = ParamConfig.NODE_PATH('', { nodeSelection: { context: NodeContext.EVENT, }, dependentOnFoundNode: true, }); /** @param near */ near = ParamConfig.FLOAT(CORE_CAMERA_DEFAULT.near, { range: [0.1, 100], cook: false, computeOnDirty: true, callback: (node: BaseNodeType, param: BaseParamType) => { BaseThreejsCameraObjNodeClass.PARAM_CALLBACK_update_near_far_from_param( node as BaseThreejsCameraObjNodeType, param ); }, }); /** @param far */ far = ParamConfig.FLOAT(CORE_CAMERA_DEFAULT.far, { range: [0, 100], cook: false, computeOnDirty: true, callback: (node: BaseNodeType, param: BaseParamType) => { BaseThreejsCameraObjNodeClass.PARAM_CALLBACK_update_near_far_from_param( node as BaseThreejsCameraObjNodeType, param ); }, }); // aspect = ParamConfig.FLOAT(1); // lock_width = ParamConfig.BOOLEAN(1); // look_at = ParamConfig.OPERATOR_PATH(''); /** @param display */ display = ParamConfig.BOOLEAN(1); /** @param show helper */ showHelper = ParamConfig.BOOLEAN(0); }; } export class BaseCameraObjParamsConfig extends CameraMainCameraParamConfig(NodeParamsConfig) {} export class BaseThreejsCameraObjParamsConfig extends CameraPostProcessParamConfig( CameraRenderParamConfig( CoreCameraFrameParamConfig( TransformedParamConfig( LayerParamConfig(ThreejsCameraTransformParamConfig(CameraMainCameraParamConfig(NodeParamsConfig))) ) ) ) ) {} export interface BaseViewerOptions { element?: HTMLElement; updateAutoRenderOnIntersectionChange?: boolean; } export abstract class TypedCameraObjNode< O extends OrthoOrPerspCamera, K extends BaseCameraObjParamsConfig > extends TypedObjNode<O, K> { // public readonly flags: FlagsControllerD = new FlagsControllerD(this); public override readonly renderOrder: number = ObjNodeRenderOrder.CAMERA; protected override _object!: O; protected _aspect: number = -1; override get object() { return this._object; } override async cook() { this.updateCamera(); (this._object as Object3DWithEvent<'change'>).dispatchEvent(EVENT_CHANGE); this.cookController.endCook(); } // prepareRaycaster(mouse: Vector2, raycaster: Raycaster) {} camera() { return this._object; } updateCamera() {} static PARAM_CALLBACK_setMainCamera(node: BaseCameraObjNodeType) { node.setAsMainCamera(); } setAsMainCamera() { const path = CorePath.objectPath(this.object); this.scene().camerasController.setMainCameraPath(path); } setupForAspectRatio(aspect: number) {} // protected _updateForAspectRatio(): void {} update_transform_params_from_object() { // CoreTransform.set_params_from_matrix(this._object.matrix, this, {scale: false}) CoreTransform.setParamsFromObject(this._object, this); } abstract createViewer(options?: BaseViewerOptions | HTMLElement): Promise<BaseViewerType | undefined>; static PARAM_CALLBACK_update_from_param(node: BaseCameraObjNodeType, param: BaseParamType) { (node.object as any)[param.name()] = (node.pv as any)[param.name()]; } } // interface ThreejsViewerOptions extends BaseViewerOptions { // viewerProperties?: ThreejsViewerProperties; // } export class TypedThreejsCameraObjNode< O extends OrthoOrPerspCamera, K extends BaseThreejsCameraObjParamsConfig > extends TypedCameraObjNode<O, K> { public override readonly flags: FlagsControllerD = new FlagsControllerD(this); override readonly hierarchyController: HierarchyController = new HierarchyController(this); override readonly transformController: TransformController = new TransformController(this); // protected _controlsController: ThreejsCameraControlsController | undefined; // controlsController(): ThreejsCameraControlsController { // return (this._controlsController = this._controlsController || new ThreejsCameraControlsController(this)); // } protected __layersController__: LayersController | undefined; private _layersController() { return (this.__layersController__ = this.__layersController__ || new LayersController(this)); } // protected _renderController: RenderController | undefined; // renderController(): RenderController { // return (this._renderController = this._renderController || new RenderController(this)); // } // protected _postProcessController: PostProcessController | undefined; // postProcessController(): PostProcessController { // return (this._postProcessController = this._postProcessController || new PostProcessController(this)); // } // display_node and children_display controllers public override readonly childrenDisplayController: ObjChildrenDisplayController = new ObjChildrenDisplayController( this ); public override readonly displayNodeController: DisplayNodeController = new DisplayNodeController( this, this.childrenDisplayController.displayNodeControllerCallbacks() ); // protected override _childrenControllerContext = NodeContext.SOP; override initializeBaseNode() { super.initializeBaseNode(); this.io.outputs.setHasOneOutput(); this.hierarchyController.initializeNode(); this.transformController.initializeNode(); this.childrenDisplayController.initializeNode(); this.initHelperHook(); } override createNode<S extends keyof GeoNodeChildrenMap>( node_class: S, options?: NodeCreateOptions ): GeoNodeChildrenMap[S]; override createNode<K extends valueof<GeoNodeChildrenMap>>( node_class: Constructor<K>, options?: NodeCreateOptions ): K; override createNode<K extends valueof<GeoNodeChildrenMap>>( node_class: Constructor<K>, options?: NodeCreateOptions ): K { return super.createNode(node_class, options) as K; } override children() { return super.children() as BaseSopNodeType[]; } override nodesByType<K extends keyof GeoNodeChildrenMap>(type: K): GeoNodeChildrenMap[K][] { return super.nodesByType(type) as GeoNodeChildrenMap[K][]; } // override prepareRaycaster(mouse: Vector2, raycaster: Raycaster) { // raycaster.setFromCamera(mouse, this._object); // } override async cook() { this.transformController.update(); this._layersController().update(); // await this.background_controller.update(); this.updateNearFar(); // this.renderController().update(); this.updateCamera(); this._updateHelper(); // this.controlsController().update_controls(); const objects = [this._object]; const node = this; const hierachyParams = { group: '', }; CameraControlsSopOperation.updateObject({ objects, params: { node: this.pv.controls, ...hierachyParams, }, node, active: true, errorIfNodeNotFound: false, }); CameraRendererSopOperation.updateObject({ objects, params: { node: this.pv.renderer, ...hierachyParams, }, node, active: this.pv.setRenderer, }); CameraCSSRendererSopOperation.updateObject({ objects, params: { node: this.pv.CSSRenderer, ...hierachyParams, }, node, active: this.pv.setCSSRenderer, }); CameraPostProcessSopOperation.updateObject({ objects, params: { node: this.pv.postProcessNode, useOtherNode: false, ...hierachyParams, }, node, active: this.pv.doPostProcess, }); CameraRenderSceneSopOperation.updateObject({ objects, params: { node: this.pv.scene, ...hierachyParams, }, node, active: this.pv.setScene, }); CameraFrameModeSopOperation.updateObject({ objects, params: { frameMode: this.pv.frameMode, expectedAspectRatio: this.pv.expectedAspectRatio, ...hierachyParams, }, }); // TODO: ideally the update transform and update camera // can both return if the camera has changed // and we can run this here instead of inside the update_transform and update_camera // this._object.dispatchEvent( EVENT_CHANGE ) (this._object as Object3DWithEvent<'change'>).dispatchEvent(EVENT_CHANGE); this.scene().camerasController.updateFromChangeInObject(this.object); this.cookController.endCook(); } static PARAM_CALLBACK_update_near_far_from_param(node: BaseThreejsCameraObjNodeType, param: BaseParamType) { node.updateNearFar(); } updateNearFar() { if (this._object.near != this.pv.near || this._object.far != this.pv.far) { this._object.near = this.pv.near; this._object.far = this.pv.far; this._object.updateProjectionMatrix(); this._updateHelper(); } } // override setupForAspectRatio(aspect: number) { // if (isNaN(aspect)) { // return; // } // if (aspect && this._aspect != aspect) { // this._aspect = aspect; // this._updateForAspectRatio(); // } // } async createViewer(options?: BaseViewerOptions | HTMLElement): Promise<ThreejsViewer<Camera> | undefined> { if (this.isDirty()) { // make sure that every parameter is cooked, // so that the camera object has all the attributes required await this.compute(); } const viewer = Poly.camerasRegister.createViewer({camera: this.object, scene: this.scene()}) as | ThreejsViewer<Camera> | undefined; let element: HTMLElement | undefined; let updateAutoRenderOnIntersectionChange: boolean | undefined; if (options && options instanceof HTMLElement) { element = options; } else { element = options?.element; updateAutoRenderOnIntersectionChange = options?.updateAutoRenderOnIntersectionChange; } if (viewer) { if (element) { viewer.mount(element, { updateAutoRenderOnIntersectionChange, }); } } return viewer; } // // // HELPER // // private _helper: CameraHelper | undefined; initHelperHook() { this.flags.display.onUpdate(() => { this._updateHelper(); }); } helperVisible() { return this.flags.display.active() && isBooleanTrue(this.pv.showHelper); } private _createHelper(): CameraHelper { const helper = new CameraHelper(this.object); helper.update(); return helper; } _updateHelper() { if (this.helperVisible()) { if (!this._helper) { this._helper = this._createHelper(); } if (this._helper) { this.object.add(this._helper); this._helper.update(); } } else { if (this._helper) { this.object.remove(this._helper); } } } } export type BaseCameraObjNodeType = TypedCameraObjNode<OrthoOrPerspCamera, BaseCameraObjParamsConfig>; export abstract class BaseCameraObjNodeClass extends TypedCameraObjNode< OrthoOrPerspCamera, BaseCameraObjParamsConfig > {} export type BaseThreejsCameraObjNodeType = TypedThreejsCameraObjNode< OrthoOrPerspCamera, BaseThreejsCameraObjParamsConfig >; export class BaseThreejsCameraObjNodeClass extends TypedThreejsCameraObjNode< OrthoOrPerspCamera, BaseThreejsCameraObjParamsConfig > { PARAM_CALLBACK_update_effects_composer(node: BaseNodeType) {} }