import { BaseObject, ImageTexture, Math, ImageColorFormat, ImageFilterType, ImageWrapType } from '@uino/thing';
import { PanoObject } from "./PanoObject";
import { PanoGraph } from "./PanoGraph";
import { FadeSwitcher } from "./switchers/FadeSwitcher";
import { StretchSwitcher } from "./switchers/StretchSwitcher";
import { PanoramaAnimationType } from "../../const";
import { Utils } from '../../common/Utils';
/**
* @class Panorama
* @summary 全景图类,用于在三维场景中创建可交互漫游的全景图系统。
* 支持多张全景图以图结构组织,通过位置坐标定义全景节点之间的空间关系,
* 用户可在不同全景节点间切换浏览,支持淡入淡出(Fade)和拉伸过渡(Stretch)两种切换动画。
* 适用于室内漫游、街景浏览、虚拟看房、景区导览等场景。
* @extends THING.BaseObject
* @memberof THING
* @public
* @example
* // 创建多张全景图构成的漫游链路
* const datas = [
* {uri: './pano/room1.jpg', rotationY: 0, position: [-30, 0, 0], link: [1]},
* {uri: './pano/room2.jpg', rotationY: 0, position: [-20, 0, 0], link: [0, 2]},
* {uri: './pano/room3.jpg', rotationY: 0, position: [-10, 0, 0], link: [1, 3]},
* {uri: './pano/room4.jpg', rotationY: 0, position: [0, 0, 0], link: [2]}
* ];
* const panorama = new THING.EXTEND.Panorama({ data: datas, firstIndex: 0 });
*
* // 切换到索引 2 的全景图
* panorama.changePano({
* link: { from: 0, to: 2 },
* time: 500,
* animationType: THING.EXTEND.PanoramaAnimationType.Fade,
* onComplete: function() {
* console.log('全景切换完成');
* }
* });
*/
class Panorama extends BaseObject {
/**
* 构造函数
* @param {object} param
* @param {Array<Object>} param.data 全景图的所有数据
* @param {number} param.firstIndex 初始化时加载的全景图数据索引,默认为0
* @param {Function} param.onComplete 创建全景图后的完成回调函数,默认为null
*/
constructor(param = {}) {
super(param);
this._data = param['data'];
this._currentIndex = param['firstIndex'] || 0;
this._radius = param['radius'];
this._completeCallback = Utils.parseValue(param['onComplete'], param['complete']);
this._graph = new PanoGraph();
this._panoArray = [];
this._createPanos();
this._initData();
this._fadeSwitcher = new FadeSwitcher();
this._stretchSwitcher = new StretchSwitcher();
this._currentSwitcher = this._stretchSwitcher;
this._animationType = PanoramaAnimationType.Stretch;
this.onAddTickableObject();
}
_createPanos() {
this._data.forEach((info, index) => {
const texture = new ImageTexture({
colorFormat: ImageColorFormat.RGB,
generateMipmaps: false,
minFilterType: ImageFilterType.LinearFilter,
magFilterType: ImageFilterType.LinearFilter,
anisotropy: 1,
wrapType: ImageWrapType.ClampToEdge
});
const pano = new PanoObject({ imageUrl: info.uri, radius: this._radius });
pano._index = index;
pano.angles = [pano.angles[0], info.rotationY, pano.angles[2]];
pano.style.image = texture;
pano.style.opacity = 0;
pano.visible = false;
if (index === this._currentIndex) {
pano.loadTexture().then(() => {
if (this._completeCallback) {
this._completeCallback();
}
});
pano.style.opacity = 1;
pano.visible = true;
}
this.add(pano);
this._panoArray.push(pano);
});
}
_initData() {
let direction = [0, 0, 0];
this._data.forEach((info, index) => {
info.link.forEach(_index => {
const _info = this._data[_index];
direction[0] = _info.position[0] - info.position[0];
direction[1] = _info.position[1] - info.position[1];
direction[2] = _info.position[2] - info.position[2];
this._graph.link(this._panoArray[index], this._panoArray[_index], Math.normalizeVector(direction));
});
});
}
_setAnimationType(value) {
switch (value) {
case PanoramaAnimationType.Fade:
this._currentSwitcher = this._fadeSwitcher;
this._animationType = value;
break;
case PanoramaAnimationType.Stretch:
this._currentSwitcher = this._stretchSwitcher;
this._animationType = value;
break;
default:
break;
}
}
getType() {
return 'Pano';
}
onUpdate(deltaTime) {
this._currentSwitcher.update(deltaTime);
}
dispose() {
this._node.dispose();
}
/**
* 切换全景图
* @param {object} param
* @param {object} param.link 全景图数据,例如 { uri: './pano/uino/IMG_20220909_102355_00_003.jpg', rotationY: 0, position: [-30, 0, 0], link: [1] }
* @param {number} param.time 全景图动画的时间
* @param {PanoramaAnimationType} param.animationType 全景图动画类型
* @param {Function} param.onUpdate 播放全景图动画时的更新回调函数
* @param {Function} param.onComplete 全景图动画完成后的回调函数
* @public
*/
changePano(param) {
const { link, time, animationType } = param;
const onUpdate = Utils.parseValue(param['onUpdate'], param['update']);
const onComplete = Utils.parseValue(param['onComplete'], param['complete']);
const that = this;
const linkObject = this._graph.getLink(link.from, link.to);
if (animationType) {
this._setAnimationType(animationType);
}
linkObject.to.loadTexture().then((ev) => {
linkObject.to.visible = true;
this._currentSwitcher.run(linkObject, {
time: time || 400
}).onUpdate(function (object, elapsed) {
if (onUpdate) {
onUpdate(elapsed);
}
}).onComplete(function () {
linkObject.from.visible = false;
that._currentIndex = link.to;
if (onComplete) {
onComplete();
}
});
});
}
/**
* 获取可以从指定全景图索引直接切换的所有全景图信息
* @param {number} index 我们想要获取信息的全景图索引,默认为this.currentIndex
* @return {Array<Object>} 例如 [{ from: 3, to: 2, direction: [-1,0,0] }]
* @public
*/
getLinks(index) {
let result = [];
index = index === undefined ? this.currentIndex : index;
this._graph.getLinks(this._panoArray[index]).forEach(cur => {
result.push({ from: cur.from._index, to: cur.to._index, direction: cur.direction });
});
return result;
}
/**
* 获取当前全景图的索引
* @type {number}
* @public
*/
get currentIndex() {
return this._currentIndex;
}
/**
* 获取当前全景图动画的切换器
* @type {THING.EXTEND.PanoSwitcher}
* @public
*/
get switcher() {
return this._currentSwitcher;
}
/**
* 获取所有PanoObject对象
* @type {Array<THING.EXTEND.PanoObejct>}
* @public
*/
get panoArray() {
return this._panoArray;
}
}
export { Panorama }