UNPKG

@turbox3d/renderer-core

Version:

Large-scale declarative graphic ui core renderer

497 lines (436 loc) 17.5 kB
/* eslint-disable react/no-unused-class-component-methods */ import { reactive, Reaction } from '@turbox3d/reactivity'; import { InteractiveConfig, SceneEvent, ViewEntity, EventType } from '@turbox3d/event-manager'; import { CommandManager, SceneTool } from '@turbox3d/command-manager'; import { warn } from '@turbox3d/shared'; import { BaseScene, SceneType } from './scene'; import { PureComponent, ComponentProps } from './component'; import { getMeshParent } from './utils'; type CommitType = 'update' | 'create' | 'delete'; const WARN_TXT = 'Passing the {id} and {type} props or rewrite getViewEntity() is recommended while mesh component is interactive.'; export abstract class BaseMesh< Props extends object, ApplicationContext, Renderer, Scene, Camera, Raycaster, Container extends DisplayObject, DisplayObject, Viewport, Point > extends PureComponent<Props> { /** 当前组件的视图对象 */ protected view: DisplayObject; /** 视图对象的类型(有相机、灯光、模型三类,默认为 model) */ protected viewType: 'camera' | 'light' | 'model' = 'model'; /** 响应式的渲染任务管线 */ protected reactivePipeLine: Array<() => void> = []; /** 是否默认添加到场景中。默认:true */ protected autoAppendToWorld = true; private reactions: Reaction[] = []; private interactiveTask: Reaction; constructor(props = {} as ComponentProps<Props>) { super(props); this.view = this.createDefaultView(); this.interactiveTask = reactive(() => { this.applyInteractive(); }); } commit(type: CommitType, updateRange?: { interactive: boolean; customProps: boolean; }): void { // 删除操作 if (type === 'delete') { if (this.reactions.length) { this.reactions.forEach(reaction => reaction.dispose()); } this.interactiveTask.dispose(); // 删除交互配置 this.context.getSceneTools().updateInteractiveObject(this.view); // 移除视图 this.removeFromWorld(); return; } // 创建操作 if (type === 'create') { this.clearView(); this.draw(); if (this.reactivePipeLine.length) { this.reactions = this.reactivePipeLine.map(task => reactive(() => task.call(this), { name: 'baseMeshReactivePipeLine', immediately: false, })); } // 将视图添加到场景中 this.appendToWorld(); // 配置交互能力 this.applyInteractive(); return; } // 更新操作 if (this._vNode.committing) { return; } this._vNode.committing = true; const { interactive, customProps } = updateRange || {}; if (interactive) { this.applyInteractive(); } if (!customProps) { return; } this.clearView(); this.draw(); if (this.reactivePipeLine.length) { /* * props上的属性值变了,会强制全部重新执行(因为可能存在非响应式属性传入的情况,导致pipeLine不会重新执行) * @todo props 和 pipeLine 同时都执行,需要合并 */ this.reactivePipeLine.forEach(task => task.call(this)); } } abstract createDefaultView(): DisplayObject; /** * 提供接口用以给子组件添加 view */ abstract addChildView(view: DisplayObject): void; /** * 绘制图形图像,默认在 componentDidMount 时调用 */ protected draw(): void { // } /** * 设置当前 cursor 状态 * * @param cursor */ protected updateCursor(cursor: string) { this.context.getSceneTools().updateCursor(cursor); } /** * 恢复默认 cursor 状态 */ protected resetCursor() { this.context.getSceneTools().updateCursor(); } /** * 标记当前对象是否可点击 */ protected onClickable() { return !!this.props.clickable; } /** * 标记当前对象是否可 Hover */ protected onHoverable() { return !!this.props.hoverable; } /** * 标记当前对象是否可拖拽 */ protected onDraggable() { return !!this.props.draggable; } protected onPinchable() { return !!this.props.pinchable; } protected onRotatable() { return !!this.props.rotatable; } protected onPressable() { return !!this.props.pressable; } protected get interactiveConfig(): InteractiveConfig { const isClickable = this.onClickable(); const isDraggable = this.onDraggable(); const isHoverable = this.onHoverable(); const isPinchable = this.onPinchable(); const isRotatable = this.onRotatable(); const isPressable = this.onPressable(); return { getViewEntity: this.getViewEntity.bind(this), onClick: this._on$Click, onDBClick: this._on$DBClick, onRightClick: this._on$RightClick, onDragStart: this._on$DragStart, onDragMove: this._on$DragMove, onDragEnd: this._on$DragEnd, onPinchStart: this._on$PinchStart, onPinch: this._on$Pinch, onPinchEnd: this._on$PinchEnd, onRotateStart: this._on$RotateStart, onRotate: this._on$Rotate, onRotateEnd: this._on$RotateEnd, onPress: this._on$Press, onPressUp: this._on$PressUp, onHoverIn: this._on$HoverIn, onHoverOut: this._on$HoverOut, isClickable, isDraggable, isHoverable, isPinchable, isRotatable, isPressable, }; } protected onClick(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onClick && this.props.onClick(viewEntity, event, tools); } protected onDBClick(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onDBClick && this.props.onDBClick(viewEntity, event, tools); } protected onRightClick(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onRightClick && this.props.onRightClick(viewEntity, event, tools); } protected onDragStart(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onDragStart && this.props.onDragStart(viewEntity, event, tools); } protected onDragMove(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onDragMove && this.props.onDragMove(viewEntity, event, tools); } protected onDragEnd(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onDragEnd && this.props.onDragEnd(viewEntity, event, tools); } protected onPinchStart(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onPinchStart && this.props.onPinchStart(viewEntity, event, tools); } protected onPinch(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onPinch && this.props.onPinch(viewEntity, event, tools); } protected onPinchEnd(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onPinchEnd && this.props.onPinchEnd(viewEntity, event, tools); } protected onRotateStart(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onRotateStart && this.props.onRotateStart(viewEntity, event, tools); } protected onRotate(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onRotate && this.props.onRotate(viewEntity, event, tools); } protected onRotateEnd(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onRotateEnd && this.props.onRotateEnd(viewEntity, event, tools); } protected onPress(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onPress && this.props.onPress(viewEntity, event, tools); } protected onPressUp(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onPressUp && this.props.onPressUp(viewEntity, event, tools); } protected onHoverIn(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onHoverIn && this.props.onHoverIn(viewEntity, event, tools); } protected onHoverOut(viewEntity: Partial<ViewEntity>, event: SceneEvent, tools: SceneTool) { this.props.onHoverOut && this.props.onHoverOut(viewEntity, event, tools); } protected getViewEntity(): Partial<ViewEntity> { const { id, type } = this.props; const isClickable = this.onClickable(); const isDraggable = this.onDraggable(); const isHoverable = this.onHoverable(); const isPinchable = this.onPinchable(); const isRotatable = this.onRotatable(); const isPressable = this.onPressable(); if ((isClickable || isHoverable || isDraggable || isPinchable || isRotatable || isPressable) && (id === void 0 || type === void 0)) { warn(WARN_TXT); } return { id, type }; } private _on$Click = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onClick, viewEntity as ViewEntity, event, tools); } return this.onClick(viewEntity, event, tools); }; private _on$DBClick = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onDBClick, viewEntity as ViewEntity, event, tools); } return this.onDBClick(viewEntity, event, tools); }; private _on$RightClick = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onRightClick, viewEntity as ViewEntity, event, tools); } return this.onRightClick(viewEntity, event, tools); }; private _on$DragStart = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onDragStart, viewEntity as ViewEntity, event, tools); } return this.onDragStart(viewEntity, event, tools); }; private _on$DragMove = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onDragMove, viewEntity as ViewEntity, event, tools); } return this.onDragMove(viewEntity, event, tools); }; private _on$DragEnd = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onDragEnd, viewEntity as ViewEntity, event, tools); } return this.onDragEnd(viewEntity, event, tools); }; private _on$PinchStart = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onPinchStart, viewEntity as ViewEntity, event, tools); } return this.onPinchStart(viewEntity, event, tools); }; private _on$Pinch = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onPinch, viewEntity as ViewEntity, event, tools); } return this.onPinch(viewEntity, event, tools); }; private _on$PinchEnd = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onPinchEnd, viewEntity as ViewEntity, event, tools); } return this.onPinchEnd(viewEntity, event, tools); }; private _on$RotateStart = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onRotateStart, viewEntity as ViewEntity, event, tools); } return this.onRotateStart(viewEntity, event, tools); }; private _on$Rotate = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onRotate, viewEntity as ViewEntity, event, tools); } return this.onRotate(viewEntity, event, tools); }; private _on$RotateEnd = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onRotateEnd, viewEntity as ViewEntity, event, tools); } return this.onRotateEnd(viewEntity, event, tools); }; private _on$Press = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onPress, viewEntity as ViewEntity, event, tools); } return this.onPress(viewEntity, event, tools); }; private _on$PressUp = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onPressUp, viewEntity as ViewEntity, event, tools); } return this.onPressUp(viewEntity, event, tools); }; private _on$HoverIn = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onHoverIn, viewEntity as ViewEntity, event, tools); } return this.onHoverIn(viewEntity, event, tools); }; private _on$HoverOut = (event: SceneEvent) => { const tools = this.context.getSceneTools(); const viewEntity = this.getViewEntity(); if (viewEntity.id !== void 0 && viewEntity.type !== void 0) { this.forwardToCommand(EventType.onHoverOut, viewEntity as ViewEntity, event, tools); } return this.onHoverOut(viewEntity, event, tools); }; /** 转发事件给CommandManager */ private forwardToCommand(eventType: EventType, viewEntity: ViewEntity, event: SceneEvent, tools: SceneTool) { const commandMgr = this.context.getCommandManager() as CommandManager; if (commandMgr) { commandMgr.distributeEvent(eventType, viewEntity, event, tools); } } /** * 将当前视图对象添加到视图中 */ private appendToWorld() { if (this.autoAppendToWorld) { let parent = getMeshParent(this) as | BaseMesh<Props, ApplicationContext, Renderer, Scene, Camera, Raycaster, Container, DisplayObject, Viewport, Point> | BaseScene<ApplicationContext, Renderer, Scene, Camera, Raycaster, Container, DisplayObject, Viewport> | undefined; if (parent) { const isCameraOrLight = this.viewType === 'camera' || this.viewType === 'light'; if (isCameraOrLight) { while (parent && !(parent instanceof BaseScene && parent.sceneType === SceneType.Scene3D)) { parent = getMeshParent(parent) as | BaseMesh<Props, ApplicationContext, Renderer, Scene, Camera, Raycaster, Container, DisplayObject, Viewport, Point> | BaseScene<ApplicationContext, Renderer, Scene, Camera, Raycaster, Container, DisplayObject, Viewport> | undefined; } if (parent && parent.scene) { this.addViewToScene(parent, this.view); } } else { parent.addChildView(this.view); } } else { warn('Cannot retrieve parent Mesh or Scene.'); } } } /** 往场景中添加对象 */ abstract addViewToScene( scene: BaseScene<ApplicationContext, Renderer, Scene, Camera, Raycaster, Container, DisplayObject, Viewport>, view: DisplayObject ): void; /** * 清除当前视图 */ abstract clearView(): void; /** * 从世界中移除当前对象 */ abstract removeFromWorld(): void; /** 设置视图是否可交互 */ abstract setViewInteractive(interactive: boolean): void; /** * 激活视图对象的交互状态,并更新交互配置 */ private applyInteractive() { const isClickable = this.onClickable(); const isDraggable = this.onDraggable(); const isHoverable = this.onHoverable(); const isPinchable = this.onPinchable(); const isRotatable = this.onRotatable(); const isPressable = this.onPressable(); this.setViewInteractive(isClickable || isHoverable || isDraggable || isPinchable || isRotatable || isPressable); if (!this._vNode) { return; } if (isClickable || isHoverable || isDraggable || isPinchable || isRotatable || isPressable) { this.context && this.context.getSceneTools().updateInteractiveObject(this.view, this.interactiveConfig); } else { this.context && this.context.getSceneTools().updateInteractiveObject(this.view); } } }