react-xrplayer
Version:
An excellent xr player for react
647 lines (552 loc) • 21.5 kB
JavaScript
/**
* XR对外的交互通过Manager来提供
*/
import * as THREE from 'three';
import InnerViewControls from '../controls/InnerViewControls';
import SpriteShapeHelper from '../display/SpriteShapeHelper';
import CenterModelHelper from '../display/CenterModelHelper';
import TWEEN from '@tweenjs/tween.js';
import ViewConvertHelper from '../action/ViewConvertHelper';
import TextureHelper from '../texture/TextureHelper';
import SpriteParticleHelper from '../display/SpriteParticleHelper';
import VRHelper from "./VRHelper";
import TextHelper from "./content_Insert_Helper/TextHelper";
import CameraMoveAction from "../action/CameraMoveAction";
import HotSpotHelper from '../display/HotSpotHelper';
import { CameraTween, CameraTweenGroup } from "../controls/CameraTween";
class XRPlayerManager {
constructor(mount, initProps, handler) {
this.mount = mount; // Threejs渲染挂载节点
this.props = initProps; // 初始化参数
this.handler = handler;
this.scene = null;
this.sceneMesh = null;
this.camera = null;
this.renderer = null;
this.controls = null;
this.sceneContainer = null; // 全景背景挂载节点
this.sceneTextureHelper = null; //全景场景纹理加载控制器
this.innerViewControls = null;
this.spriteShapeHelper = null;
this.spriteParticleHelper = null; // 粒子展示
this.centerModelHelper = null;
this.viewConvertHelper = null;
this.spriteEventList = null;
this.hotSpotHelper = null;
this.vrHelper = null;
// audio related
this.audio = document.createElement("audio");
this.audio.preload = "metadata";
document.body.appendChild(this.audio);
// camera animation related
this.cameraTweenStatus = {
num: 0,
paused: false
};
this.cameraTweenGroup = null;
this.onCameraAnimationEnded = null;
this.init();
}
init = () => {
this.initCamera();
this.initScene();
this.initRenderer();
this.initVR();
this.animate(0);
}
initCamera = () => {
const {
camera_fov, camera_far, camera_near,
camera_position: position, camera_target: target
} = this.props;
const camera = new THREE.PerspectiveCamera(
camera_fov, this.mount.clientWidth / this.mount.clientHeight,
camera_near, camera_far);
const renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer = renderer;
camera.position.set(position.x, position.y, position.z);
camera.target = new THREE.Vector3(target.x, target.y, target.z);
this.camera = camera;
this.innerViewControls = new InnerViewControls(this.camera);
}
initScene = () => {
const {
scene_texture_resource: textureResource,
axes_helper_display: isAxesHelperDisplay,
} = this.props;
const { panoramic_type = '360', radius = 500, height = 1000 } = textureResource;
this.sceneContainer = document.getElementById('video');
let geometry;
if (panoramic_type === '180') {
geometry = new THREE.CylinderGeometry(radius, radius, height, 40, 40, true); // 球体
} else {
geometry = new THREE.SphereBufferGeometry(radius, 80, 40); // 球体
}
geometry.scale(-1, 1, 1);
this.sceneTextureHelper = new TextureHelper(this.sceneContainer);
this.sceneTextureHelper.onCanPlayHandler = (resUrl) => this.handler('sence_res_ready', { resUrl: resUrl });
let texture = this.sceneTextureHelper.loadTexture(textureResource);
let material = new THREE.MeshBasicMaterial({ map: texture });
this.sceneMesh = new THREE.Mesh(geometry, material);
this.scene = new THREE.Scene();
this.scene.add(this.sceneMesh);
if (isAxesHelperDisplay) {
let axisHelper = new THREE.AxesHelper(1000)//每个轴的长度
this.scene.add(axisHelper);
}
this.scene.add(this.camera);
}
initRenderer = () => {
const renderer = this.renderer;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(this.mount.clientWidth, this.mount.clientHeight);
renderer.sortObjects = false;
renderer.autoClear = false;
this.mount.appendChild(renderer.domElement);
}
initVR = () => {
this.vrHelper = new VRHelper(this.renderer, this.camera);
this.vrHelper.setObjectInteractionHandler((pickedObject) => {
if (!!pickedObject) {
const key = pickedObject.name;
this.emitEvent(key, () => {
this.closeEffectContainer();
});
}
})
}
animate = (time) => {
requestAnimationFrame(this.animate);
if (this.cameraTweenStatus.num === 0)
this.innerViewControls && this.innerViewControls.update();
if (this.centerModelHelper) {
this.centerModelHelper.update();
}
if (this.spriteParticleHelper) {
this.spriteParticleHelper.update();
}
if (this.cameraTweenStatus.paused === false)
TWEEN.update(); // 不要轻易去掉,渐变动画依赖该库
if (this.vrHelper.vrStatus) {
time *= 0.001;
if (this.spriteShapeHelper) {
let objects = this.spriteShapeHelper.getPointObjects();
this.vrHelper.updateInteractionObjects(objects, time);
}
this.vrHelper.render(this.scene, this.camera);
} else {
this.renderer.render(this.scene, this.camera);
}
if (this.hotSpotHelper) {
this.hotSpotHelper.update();
}
if (this.spriteShapeHelper) {
this.spriteShapeHelper.update();
}
}
/*****************************全局接口************************************ */
setGlobalMuted = (muted) => {
this.handler('global_muted', { muted: muted });
}
setGlobalVolume = (volume) => {
this.handler('global_volume', { volume: volume });
}
/****************************全景场景相关控制接口************************* */
setSenceResource = (res) => {
this.sceneTextureHelper && this.sceneTextureHelper.unloadResource();
this.sceneTextureHelper = new TextureHelper(this.sceneContainer);
let texture = this.sceneTextureHelper.loadTexture(res);
let material = new THREE.MeshBasicMaterial({ map: texture });
this.sceneMesh.material = material;
}
// 背景全景视频播放控制
startDisplaySenceResource = () => {
if (this.sceneTextureHelper) {
this.sceneTextureHelper.startDisplay();
}
}
pauseDisplaySenceResource = () => {
if (this.sceneTextureHelper) {
this.sceneTextureHelper.pauseDisplay();
}
}
// 自动旋转相关接口
getEnableAutoRotate = () => {
return this.innerViewControls.getEnableAutoRotate();
}
setEnableAutoRotate = (enable) => {
this.innerViewControls.setEnableAutoRotate(enable)
}
setAutoRotateSpeed = (speed) => {
this.innerViewControls.setAutoRotateSpeed(speed);
}
setAutoRotateDirection = (direction) => {
this.innerViewControls.setAutoRotateDirection(direction);
}
/****************************热点标签相关控制接口************************* */
resetHotSpotsData = () => {
if (!this.spriteShapeHelper) {
this.spriteEventList = new Map();
this.spriteShapeHelper = new SpriteShapeHelper(this.scene,
this.camera, this.renderer, this.mount);
} else {
this.spriteEventList.clear();
}
}
setHotSpots = (hot_spot_list, event_list) => {
this.resetHotSpotsData();
this.spriteEventList = new Map(event_list);
this.spriteShapeHelper.setHotSpotList(hot_spot_list);
this.spriteShapeHelper.objectClickHandler = (intersects) => {
const key = intersects[0].object.name;
this.emitEvent(key, () => {
this.closeEffectContainer();
})
}
this.spriteShapeHelper.tagClickHandler = (key) => {
this.emitEvent(key, () => {
this.closeEffectContainer();
})
}
}
addHotSpot = (hot_spot, event) => {
this.spriteShapeHelper.addHotSpot(hot_spot);
if (event != null && !this.spriteEventList.has(event.key)) {
this.spriteEventList.set(event.key, event.value);
}
}
removeHotSpot = (hot_spot_key) => {
this.spriteShapeHelper.removeHotSpot(hot_spot_key);
}
setIsTipVisible = (enable) => {
this.spriteShapeHelper.setIsTipVisible(enable);
}
setHotSpotClickable = (enable) => {
this.spriteShapeHelper.setHotSpotClickable(enable);
}
/*****************************模型控制相关接口**************************** */
resetModels = () => {
if (!this.centerModelHelper) {
this.centerModelHelper = new CenterModelHelper(this.scene);
}
}
setModels = (model_list) => {
this.resetModels();
this.centerModelHelper.loadModelList(model_list);
}
addModel = (model_key, model) => {
this.centerModelHelper.loadModel(model_key, model);
}
removeModel = (model_key) => {
this.centerModelHelper.removeModel(model_key);
}
removeAllModel = () => {
this.centerModelHelper.removeAllModel();
}
/**************************相机移动相关接口************************* */
toNormalView = (durtime = 8000, delay = 0) => {
if (!this.viewConvertHelper) {
this.viewConvertHelper = new ViewConvertHelper(this.camera, this.innerViewControls);
}
this.innerViewControls.disConnect();
this.viewConvertHelper.toNormalView(durtime, delay);
}
toPlanetView = (durtime = 8000, delay = 0) => {
if (!this.viewConvertHelper) {
this.viewConvertHelper = new ViewConvertHelper(this.camera, this.innerViewControls);
}
this.innerViewControls.disConnect();
this.viewConvertHelper.toPlanetView(durtime, delay);
}
moveCameraTo = (descPos, onStart, onEnd, duration = 5000) => {
var cameraMoveAction = new CameraMoveAction(this.camera, descPos, duration, 0);
cameraMoveAction.onStartHandler = () => {
this.innerViewControls && this.innerViewControls.disConnect();
onStart && onStart();
}
cameraMoveAction.onCompleteHandler = () => {
this.innerViewControls && this.innerViewControls.connect();
onEnd && onEnd();
}
cameraMoveAction.start();
}
/**************************相机控制相关接口************************* */
// 相机控制器开关
connectCameraControl = () => {
this.innerViewControls.connect();
}
disConnectCameraControl = () => {
this.innerViewControls.disConnect();
}
// 方向传感器控制开关
getEnableOrientationControls = () => {
return this.innerViewControls.getEnableOrientationControls();
}
enableOrientationControls = () => {
this.innerViewControls.enableOrientationControls();
}
disableOrientationControls = () => {
this.innerViewControls.disableOrientationControls();
}
// 相机位置接口
getCameraPosition = () => {
return this.innerViewControls.getCameraPosition();
}
setCameraPosition = (x, y, z) => {
this.innerViewControls.setCameraPosition(x, y, z);
}
// 相机当前fov接口
setCameraFov = (fov) => {
this.innerViewControls.setCameraFov(fov);
}
getCameraFov = () => {
return this.innerViewControls.getCameraFov();
}
enableChangeFov = (enable) => {
this.innerViewControls.enableChangeFov(enable);
}
// FOV上下范围设置接口
setFovVerticalScope = (bottom, top) => {
this.innerViewControls.setFovVerticalScope(bottom, top);
}
getFovVerticalScope = () => {
return this.innerViewControls.getFovVerticalScope();
}
// FOV左右范围设置接口
setFovHorizontalScope = (left, right) => {
this.innerViewControls.setFovHorizontalScope(left, right);
}
getFovHorizontalScope = () => {
return this.innerViewControls.getFovHorizontalScope();
}
/*******************************粒子特效接口********************************** */
setParticleEffectRes = (res) => {
if (!this.spriteParticleHelper) {
this.spriteParticleHelper = new SpriteParticleHelper(this.scene);
}
this.spriteParticleHelper.setResource(res);
}
getEnableParticleDisplay = () => {
return this.spriteParticleHelper.getEnableDisplay();
}
enableParticleDisplay = (enable) => {
if (enable) {
this.spriteParticleHelper.enableDisplay();
} else {
this.spriteParticleHelper.disableDisplay();
}
}
/*******************************VR接口********************************** */
changeVRStatus = () => {
if (this.vrHelper.vrStatus) {
this.vrHelper.disable();
this.renderer.setViewport(0, 0, this.mount.clientWidth, this.mount.clientHeight);
}
else {
this.vrHelper.enable();
}
}
/*******************************文本框接口********************************** */
createTextBox = (params) => {
var TextBox = new TextHelper(params);
TextBox.addTo(this.scene);
return TextBox;
}
showTextBox = (TextBox) => {
if (!!!TextBox) return;
TextBox.show();
}
hideTextBox = (TextBox) => {
if (!!!TextBox) return;
TextBox.hide();
}
changeTextBox = (TextBox, params) => {
if (!!!TextBox) return;
TextBox.removeFrom(this.scene);
TextBox.setMessage(params);
TextBox.addTo(this.scene);
}
//使用remove后记得将TextBox设为null,防止内存泄漏
removeTextBox = (TextBox) => {
if (TextBox === undefined) return;
TextBox.removeFrom(this.scene);
}
addIcon = (img, position, name, title, width, height) => {
if (!this.hotSpotHelper) {
this.hotSpotHelper = new HotSpotHelper(this.scene, this.mount, this.camera);
}
const { x, y, z } = position;
this.hotSpotHelper.markIcon(img, new THREE.Vector3(x, y, z), name, title, width, height);
}
removeIcon = (name) => {
this.hotSpotHelper.removeIcon(name);
}
addIcons = (iconList) => {
this.hotSpotHelper.addIcons(iconList);
}
removeAllIcons = () => {
this.hotSpotHelper.removeAllIcons();
}
/********************************音频接口************************************/
initAudio = () => {
if (!this.audio) {
this.audio = document.createElement("audio");
this.audio.preload = "metadata";
document.body.appendChild(this.audio);
this.audio.onended = () => {
console.log('audio', "播放结束");
}
}
}
setAudioSrc = (src) => {
this.audio.setAttribute("src", src);
}
getAudioSrc = () => {
return this.audio.currentSrc;
}
setAudioVolume = (volume) => { // 0 到 1
this.audio.volume = volume;
}
getAudioVolume = () => {
return this.audio.volume;
}
setAudioMuted = (muted) => { // true 或 false
this.audio.muted = muted;
}
getAudioMuted = () => {
return this.audio.muted;
}
getAudioPaused = () => {
return this.audio.paused;
}
pauseAudio = () => {
this.audio.pause();
}
playAudio = () => {
this.audio.play();
}
playAudioRes = (src) => {
this.setAudioSrc(src);
this.audio.play();
}
replayAudio = () => {
this.audio.currentTime = 0;
}
endAudio = () => {
this.audio.currentTime = this.audio.duration;
}
/****************************相机动画接口***********************************/
/*
设置动画流程(示例见app.js):
1. 通过createCameraAnimation获取动画各部分的cameraTween
2. 通过setCameraAnimationGroup连接各动画
为防止不必要的bug,请遵循以下播放注意事项:(可以在以后设计UI时通过隐藏button或使button失效防止这一类问题产生)
1. startCameraAnimationGroup后才可调用stop,pause,play等功能
2. stop或自动结束之后再调用start重播
params的格式:
{
pos0, pos1, duration, 必需
easing, callback 非必需(easing是速度变化的方式,详见https://www.createjs.com/docs/tweenjs/classes/Ease.html)
}
pos0、pos1的格式
{
lat, lon, 必需
fov 非必需
}或
{
x, y, z, 必需
fov 非必需
}
*/
createCameraTweenGroup = (animationList, loop) => {
if (!!!loop) {
loop = false;
}
let cameraTweens = [];
animationList.forEach((item, index) => {
var animation = this.createCameraAnimation(item);
cameraTweens.push(animation);
});
var cameraTweenGroup = new CameraTweenGroup(cameraTweens,
100, this.innerViewControls);
cameraTweenGroup.onCameraAnimationEnded = (key) => {
this.onCameraAnimationEnded &&
this.onCameraAnimationEnded(key);
}
cameraTweenGroup.onCameraAnimationStart = (key) => {
this.onCameraAnimationStart &&
this.onCameraAnimationStart(key);
}
cameraTweenGroup.onCameraAnimationStop = (key) => {
this.onCameraAnimationStop &&
this.onCameraAnimationStop(key);
}
this.cameraTweenGroup = cameraTweenGroup;
return cameraTweenGroup;
}
createCameraAnimation = (params) => { //因为存在入场动画,导致设置相机动画时distance是450,这里直接改为100
var cameraTween = new CameraTween(params, this.camera, 100,
this.innerViewControls, this.cameraTweenStatus);
cameraTween.key = params.key;
return cameraTween;
}
setCameraTweenGroup = (cameraTweenGroup) => {
this.cameraTweenGroup = cameraTweenGroup;
}
getCameraTweenGroup = () => {
return this.cameraTweenGroup;
}
startCameraTweenGroup = (time) => {
if (!this.cameraTweenGroup) {
return;
}
if (!!!time) {
this.cameraTweenGroup.start();
}
else {
this.cameraTweenGroup.start(time);
}
}
stopCameraTweenGroup = () => {
this.cameraTweenGroup && this.cameraTweenGroup.stop();
}
pauseCameraTweenGroup = () => {
this.cameraTweenGroup && this.cameraTweenGroup.pause();
}
playCameraTweenGroup = () => {
this.cameraTweenGroup && this.cameraTweenGroup.play();
}
nextCameraTween = () => {
this.cameraTweenGroup && this.cameraTweenGroup.next();
}
enableCameraTweenGroupAutoNext = (enable) => {
this.cameraTweenGroup.enableAutoNext(enable);
}
enableCameraTweenGroupLoop = (enable) => {
this.cameraTweenGroup.enableLoop(enable);
}
emitEvent = (eventKey, callback = () => { }) => {
if (this.spriteEventList && this.spriteEventList.has(eventKey)) {
const data = this.spriteEventList.get(eventKey);
this.handler(data.type, { data }, () => {
callback();
})
} else {
callback();
}
}
closeEffectContainer = () => {
this.handler('close_effect_container');
}
/*******************************其他接口********************************** */
onWindowResize = (mountWidth, mountHeight) => {
this.camera.aspect = mountWidth / mountHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(mountWidth, mountHeight);
}
destroy = () => {
this.mount.removeChild(this.renderer.domElement)
this.sceneTextureHelper && this.sceneTextureHelper.unloadResource();
}
}
export default XRPlayerManager;