UNPKG

@vci/quick-three

Version:

quick three

563 lines (527 loc) 17 kB
import { mergeDeep, release } from "@vci/helper/src/object"; import { Box3, Box3Helper, Vector3 } from "three"; import { Easing, remove, Tween } from "@tweenjs/tween.js"; import { clearScheduledTasks } from "@vci/helper/src/browser"; import { uuid } from "@vci/helper/src/string"; import MaterialPatch from "../helper/MaterialPatch"; import { isEmpty } from "@vci/helper/src/other"; import EventDispatcher from "@vci/helper/src/EventDispatcher"; import O3DisposerHelper from "../helper/O3DisposerHelper"; import { ThingEvents } from "../events/ThingEvents"; // 核心 export default class Thing extends EventDispatcher { // mode-获取中心点的模式 cc-中段中心 cb-底部中心 ct-顶部中心 static CenterMode = { "CenterTop": "ct", "Center": "cc", "CenterBottom": "cb" }; constructor(option) { super(); this.option = mergeDeep({ id: uuid(), name: "物体", // 模型相关 object: null, position: [0, 0, 0], scale: [1, 1, 1], rotation: [0, 0, 0], renderOrder: null, // 功能性配置 enableEvent: true, enableEdit: true, enableUpdateMatrix: true, enableEditUpdateOptionPosition: true, enableUpdateRenderOrderDeep: true, enableBoxHelper: false, enableDisposeO3OnUpdate: true, // 功能性配置|销毁 isImmortal: false, enableDisposeO3OnDestroy: true, enableRemoveO3FromParentOnDestroy: true, // 仅当enableDisposeO3OnDestroy=false时生效 // 功能性配置|qt.updateThings enableRemovedWhenDestroyOnUpdate: true, enableFadeOutWhenDestroyOnUpdate: false, // 初始化后不可变的配置,调用update更新后也无效 qt: null, parentObject: null, enableCheckObject: true, enableAddToThingPool: true, enableAddObjectToParent: true, // 扩展数据 extData: null }, option); this.init(); if (this.option.enableCheckObject && !this.object) throw new Error(this.qt.log.format(`绑定模型不存在,请检查: ${this.object}`)); this.object && this.objectMount(this.object); this.update(); this.afterInstantiation(); } // 初始化 init() { this.isThing = true; this.visible = true; this.isDestroyed = false; this.inter = {}; this.tw = {}; this.alias(); if (!this.qt) throw new Error("请传入QuickThree实例"); if (!this.parentObject) this.parentObject = this.qt.scene; if (this.option.enableAddObjectToParent && !this.parentObject) throw new Error("物体必须有程载容器, Group|Scene|Object3D"); this.box = new Box3(); this.size = new Vector3(); this.center = new Vector3(); this.option.enableAddToThingPool && this.qt.things.push(this); } alias() { Object.defineProperty(this, "id", { get: () => this.option.id, set: id => { this.option.id = id; }, configurable: true, enumerable: true }); Object.defineProperty(this, "name", { get: () => this.option.name, set: name => { this.option.name = name; }, configurable: true, enumerable: true }); Object.defineProperty(this, "object", { get: () => this.option.object, set: object => { this.option.object = object; }, configurable: true, enumerable: true }); Object.defineProperty(this, "objectFlat", { get() { const o3s = []; this.object.traverse(o => (o.isMesh || o.isLine) && o3s.push(o)); return o3s; }, configurable: true, enumerable: true }); Object.defineProperty(this, "position", { get: () => this.option.position, set: position => { this.option.position = position; this.object && this.option.enableUpdateMatrix && this.object.position.fromArray(position); }, configurable: true, enumerable: true }); Object.defineProperty(this, "rotation", { get: () => this.option.rotation, set: rotation => { this.option.rotation = rotation; this.object && this.option.enableUpdateMatrix && this.object.rotation.fromArray(rotation); }, configurable: true, enumerable: true }); Object.defineProperty(this, "scale", { get: () => this.option.scale, set: scale => { this.option.scale = scale; this.object && this.option.enableUpdateMatrix && this.object.scale.fromArray(scale); }, configurable: true, enumerable: true }); Object.defineProperty(this, "parentObject", { get: () => this.option.parentObject, set: parentObject => { this.option.parentObject = parentObject; }, configurable: true, enumerable: true }); Object.defineProperty(this, "extData", { get: () => this.option.extData || {}, set: extData => { this.option.extData = extData; }, configurable: true, enumerable: true }); Object.defineProperty(this, "qt", { get: () => this.option.qt, set: qt => { this.option.qt = qt; }, configurable: true, enumerable: true }); Object.defineProperty(this, "isImmortal", { get: () => this.option.isImmortal, configurable: true, enumerable: true }); Object.defineProperty(this, "enableEdit", { get: () => this.option.enableEdit, configurable: true, enumerable: true }); Object.defineProperty(this, "enableEvent", { get: () => this.option.enableEvent, configurable: true, enumerable: true }); } // 构造函数运行完成后 afterInstantiation() { } // 更新配置 update(option) { option && mergeDeep(this.option, option); // 更新模型基本状态 this.objectUpdate(); // 辅助盒子 this.updateBoxHelper(); // 更新事件事物 this.updateEvents(); // 触发更新事件 this.dispatchEvent(ThingEvents.Update); return this; } // 挂载模型 objectMount(object) { if (!object) throw new Error(this.qt.log.format(`绑定模型不存在,请检查: ${object}`)); if (!object.isObject3D) throw new Error(this.qt.log.format(`绑定模型不是Object3D的实例,请检查: ${object}`)); if (this.object && this.object !== object) this.objectDetach(); this.object = object; // 添加模型到父级 this.option.enableAddObjectToParent && this.parentObject.add(this.object); // 注入属性到模型userData this.objectEmpowerment(); // 如果不能更新模型矩阵,则让position等于模型本身矩阵 !this.option.enableUpdateMatrix && (this.position = object.position.toArray()); return this; } objectDetach() { this.objectDeleteEmpowerment(); this.option.enableDisposeO3OnUpdate && O3DisposerHelper.DisposeObject3D(this.object, this.parentObject); this.object = null; return this; } objectUpdate() { // 定位|缩放|方位 if (this.option.enableUpdateMatrix) { this.position = this.option.position; this.scale = this.option.scale; this.rotation = this.option.rotation; // 设置模型原始位置 !this.positionOrigin && (this.positionOrigin = new Vector3().fromArray(this.position)); } else !this.positionOrigin && (this.positionOrigin = new Vector3().copy(this.object.position)); // 渲染顺序 this.objectUpdateRenderOrder(); // 更新事物中心点 this.updateCenter(); return this; } // 注入属性到模型userData objectEmpowerment() { if (this.object) { this.object.thing = this; this.object.isThing = true; this.object.userData = this.option; } return this; } // 删除注入到模型userData的属性 objectDeleteEmpowerment() { if (this.object) { delete this.object.thing; delete this.object.isThing; delete this.object.userData; } return this; } // 更新模型渲染顺序 objectUpdateRenderOrder() { const { enableUpdateRenderOrderDeep } = this.option; const renderOrder = +this.option.renderOrder; if (this.object && !isNaN(renderOrder) && !isEmpty(this.option.renderOrder)) { this.object.renderOrder = renderOrder; enableUpdateRenderOrderDeep && this.object.traverse(o3 => o3.renderOrder = renderOrder); } return this; } // 更新包围盒 updateBox() { if (this.object) { this.box.setFromObject(this.object); this.box.getSize(this.size); // 更新辅助包围盒 this.boxHelper && this.boxHelper.updateMatrixWorld(); } return this; } // 更新中心点 updateCenter() { if (this.object) { this.updateBox(); this.box.getCenter(this.center); } return this; } // 获取中心点 mode-CenterMode getCenter(mode = Thing.CenterMode.Center) { this.updateCenter(); const center = new Vector3(); switch (mode) { case Thing.CenterMode.CenterBottom: center.set( this.center.x, this.center.y - this.size.y / 2, this.center.z ); break; case Thing.CenterMode.Center: center.copy(this.center); break; case Thing.CenterMode.CenterTop: center.set( this.center.x, this.center.y + this.size.y / 2, this.center.z ); break; } return center; } // 获取尺寸 getSize() { const _rotation = this.object.rotation.clone(); this.object.rotation.set(0, 0, 0); this.updateBox(); const size = this.size.clone(); this.object.rotation.copy(_rotation); this.updateBox(); return size; } // 更新辅助包围盒 updateBoxHelper() { const { enableBoxHelper } = this.option; if (enableBoxHelper) { if (!this.boxHelper) { this.boxHelper = new Box3Helper(this.box, "#ffff00"); this.parentObject.add(this.boxHelper); } this.boxHelper.visible = true; } else this.boxHelper && (this.boxHelper.visible = false); } // 更新事件支持 updateEvents() { this.qt.helper.updateEventO3s(); } // 整个场景发生改变的对应处理 matrixChange(isWindowResize = false, handle) { clearTimeout(this.inter.matrixChange); const matrixChangeHandle = () => handle && handle(); if (isWindowResize) this.inter.matrixChange = setTimeout(() => matrixChangeHandle(), 200); else matrixChangeHandle(); return this; } // 动画 delta 动画帧 animate() { this.updateBox(); // 触发动画事件 this.dispatchEvent(ThingEvents.Animate); } modifyShadowState(enabled = false) { this.object && this.object.traverse(o => o.castShadow = o.receiveShadow = enabled); return this; } /** * 显示模型 * @param enableModifyMaterial 是否只改变模型的材质 * @returns {Thing} */ show(enableModifyMaterial = false) { this.visible = true; this.qt.helper.updateEventO3s(); const o3 = this.object; o3.visible = true; this.modifyShadowState(true); enableModifyMaterial && MaterialPatch.ModifyMtlProperties(o3, { transparent: true, opacity: 1 }); return this; } /** * 隐藏模型 * @param enableModifyMaterial 是否只改变模型的材质 * @returns {Thing} */ hide(enableModifyMaterial = false) { this.visible = false; this.qt.helper.updateEventO3s(); const o3 = this.object; o3.visible = false; this.modifyShadowState(false); enableModifyMaterial && MaterialPatch.ModifyMtlProperties(o3, { transparent: true, opacity: 0 }); return this; } o3Show(object) { const o3 = object || this.object; o3 && (o3.visible = true); return this; } o3Hide(object) { const o3 = object || this.object; o3 && (o3.visible = false); return this; } // 动画显示模型 async fadeIn(tweenOption = {}) { return this.fadeTo(1, tweenOption); } async fadeTo(opacity = .5, tweenOption) { this.visible = true; const o3 = this.object; o3.visible = true; this.modifyShadowState(true); MaterialPatch.ModifyMtlProperties(o3, { transparent: true }); await this.animationModifyMtlProperties({ opacity }, tweenOption); this.qt.helper.updateEventO3s(); return this; } // 动画隐藏模型 async fadeOut(tweenOption = {}) { this.visible = false; this.qt.helper.updateEventO3s(); const o3 = this.object; MaterialPatch.ModifyMtlProperties(o3, { transparent: true }); await this.animationModifyMtlProperties({ opacity: 0 }, tweenOption); this.modifyShadowState(false); o3.visible = false; return this; } resetMtlProps(props = []) { MaterialPatch.ResetMtlProperties(this.object, props); return this; } /** * 实例化的动画更改材质属性 * @param targets 目标属性对象 例: { opacity: 1 } * @param tweenOption Tween配置 Object * @returns {Promise<Thing>} */ async animationModifyMtlProperties(targets, tweenOption = {}) { await MaterialPatch.AnimationModifyMtlProperties( this.object, targets, tweenOption, this.tw ); return this; } // 唤醒 使模型能够触发事件以及编辑 wakeUp() { this.option.enableEdit = true; this.option.enableEvent = true; this.updateEvents(); return this; } // 休眠 使模型不能够触发事件以及编辑 sleep() { this.option.enableEdit = false; this.option.enableEvent = false; this.updateEvents(); return this; } /** * 移动 * @param target 三维距离 * @param twOption Tween配置 * @returns {Promise<Thing>} */ async move(target = new Vector3(), twOption) { const start = new Vector3().copy(this.object.position); const end = new Vector3().copy(start).add(target); return await this._move( start, end, twOption ); } /** * 移动 * @param target 三维位置 * @param twOption Tween配置 * @returns {Promise<Thing>} */ async moveTo(target = new Vector3(), twOption) { const start = new Vector3().copy(this.object.position); const end = new Vector3().copy(target); return await this._move( start, end, twOption ); } async _move(start = new Vector3(), end = new Vector3(), twOption) { const { duration, delay, easing, immediate } = mergeDeep({ duration: 1e3, delay: 0, easing: Easing.Linear.None, immediate: false }, twOption); this.tw.move && remove(this.tw.move); if (immediate) { if (!this.option.enableUpdateMatrix) { this.option.enableUpdateMatrix = true; this._oeu = true; } this.position = end.toArray(); if (this._oeu) { this.option.enableUpdateMatrix = false; delete this._oeu; } } else await new Promise(resolve => { this.tw.move = new Tween(start) .to(end) .onStart(() => { if (!this.option.enableUpdateMatrix) { this.option.enableUpdateMatrix = true; this._oeu = true; } }) .duration(duration) .delay(delay) .easing(easing) .onUpdate(e => this.position = [e.x, e.y, e.z]) .onComplete(() => { if (this._oeu) { this.option.enableUpdateMatrix = false; delete this._oeu; } resolve(); }) .start(); }); return this; } // 销毁 destroy(force = false, enableCr = true) { if (this.isDestroyed || this.isImmortal && !force) return this; // 标记已销毁 this.isDestroyed = true; this.dispatchEvent(ThingEvents.Destroyed); // 清除定时器 clearScheduledTasks(this.inter); // 移除Tween实例 Object.keys(this.tw).forEach(k => { if (this.tw[k]) { remove(this.tw[k]); delete this.tw[k]; } }); // 销毁boxHelper this.boxHelper && O3DisposerHelper.DisposeObject3D(this.boxHelper, this.parentObject); // 销毁当前模型 this.objectDeleteEmpowerment(); if (this.object) if (this.option.enableDisposeO3OnDestroy) O3DisposerHelper.DisposeObject3D(this.object, this.parentObject); else this.option.enableRemoveO3FromParentOnDestroy && this.parentObject.remove(this.object); if (enableCr) { // 清除已经销毁的事物实例 this.qt.clearDestroyedThings(); // 清除对象属性 release(this, /^is\s*/); } } }