@turbox3d/renderer-core
Version:
Large-scale declarative graphic ui core renderer
497 lines (436 loc) • 17.5 kB
text/typescript
/* 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);
}
}
}