import XverseAvatarManager from "./XverseAvatarManager.js" import Codes from "./enum/Codes.js" import PathManager from "./PathManager.js" import Camera from "./Camera.js" import Stats from "./Stats.js" import ActionsHandler from "./ActionsHandler.js" import Signal from "./Signal.js" import ModelManager from "./ModelManager.js" import {reporter} from "./Reporter.js" import util from "./util.js" import XverseEffectManager from "./XverseEffectManager.js" import TimeoutError from "./error/TimeoutError.js" import ParamError from "./error/ParamError.js" import MotionType from "./enum/MotionType.js" import NetworkController from "./NetworkController.js" import InitNetworkTimeoutError from "./error/InitNetworkTimeoutError.js" import InitConfigTimeoutError from "./error/InitConfigTimeoutError.js" import InitDecoderTimeoutError from "./error/InitDecoderTimeoutError.js" import InitEngineError from "./error/InitEngineError.js" import {eventsManager} from "./EventsManager.js" import EngineProxy from "./EngineProxy.js" import EventsController from "./EventsController.js" import EImageQuality from "./enum/EImageQuality.js" import Panorama from "./Panorama.js" import Debug from "./Debug.js" import Logger from "./Logger.js" import Response from "./Response.js" const logger = new Logger('xverse-room') export default class Xverse_Room extends EventEmitter { constructor(e) { super(); E(this, "disableAutoTurn", !1); E(this, "options"); E(this, "_currentNetworkOptions"); E(this, "lastSkinId"); E(this, "debug"); E(this, "isFirstDataUsed", !1); E(this, "userId", null); E(this, "pathManager", new PathManager); E(this, "networkController"); E(this, "_startTime", Date.now()); E(this, "canvas"); E(this, "modelManager"); E(this, "eventsController"); E(this, "panorama"); E(this, "engineProxy"); E(this, "_id"); E(this, "skinList", []); E(this, "isHost", !1); E(this, "avatarManager", new XverseAvatarManager(this)); E(this, "effectManager", new XverseEffectManager(this)); E(this, "sceneManager"); E(this, "scene"); E(this, "breathPointManager"); E(this, "_currentState"); E(this, "joined", !1); E(this, "disableRotate", !1); E(this, "isPano", !1); E(this, "movingByClick", !0); E(this, "camera", new Camera(this)); E(this, "stats", new Stats(this)); E(this, "isUpdatedRawYUVData", !1); E(this, "actionsHandler", new ActionsHandler(this)); E(this, "_currentClickingState", null); E(this, "signal", new Signal(this)); E(this, "firstFrameTimestamp"); E(this, "receiveRtcData", async()=>{ logger.info("Invoke receiveRtcData"); let e = !1 , t = !1 , r = !1 , n = !1; return this.viewMode === "serverless" ? (logger.warn("set view mode to serverless"), this.setViewMode("observer").then(()=>this, ()=>this)) : new Promise(o=>{ const a = this.networkController.rtcp.workers; a.registerFunction("signal", s=>{ this.signal.handleSignal(s) }); a.registerFunction("stream", s=>{ // var l; // this.emit("streamTimestamp", { // timestamp: Date.now() // }); // t || (t = !0,logger.info("Invoke stream event")); if (s.stream) { // r || (r = !0,logger.info("Invoke updateRawYUVData")); // this.isUpdatedRawYUVData = !1; // const fov = (l = this._currentState.skin) == null ? void 0 : l.fov;0 // this.sceneManager.materialComponent.updateRawYUVData(s.stream, s.width, s.height, fov); this.isUpdatedRawYUVData = !0 } if(!e){ logger.info("Invoke isAfterRenderRegistered"); e = !0; this.scene.registerAfterRender(()=>{ if(this.engineProxy.frameRenderNumber >= 2){ n || (n = !0,logger.info("Invoke registerAfterRender")); if(!this.isFirstDataUsed){ logger.info("Invoke isStreamAvailable"); this.isFirstDataUsed = !0; this.firstFrameTimestamp = Date.now(); o(this); this.afterJoinRoom(); } } }) } }); this.panorama.bindListener(()=>{ o(this), this.afterJoinRoom() }); // a.registerFunction("reconnectedFrame", ()=>{}); // logger.info("Invoke decoderWorker.postMessage"); // a.decoderWorker.postMessage({ // t: 5 // }) } ) } ); E(this, "moveToExtra", ""); this.options = e; this.options.wsServerUrl || (this.options.wsServerUrl = SERVER_URLS.DEV); this.modelManager = ModelManager.getInstance(e.appId, e.releaseId); // this.updateReporter(); // const n = e // , {canvas: t} = n // , r = Oe(n, ["canvas"]); // logger.infoAndReportMeasurement({ // metric: "startJoinRoomAt", // startTime: Date.now(), // group: "joinRoom", // extra: r, // value: 0 // }) } doRotate(angle){ //this.sceneManager.materialComponent.initreceveFrames() // 获得相机旋转后的位置 let cameraPostion0 = util.xversePosition2Ue4(this.sceneManager.cameraComponent.mainCamera.position.clone()) let playerPosition = this.sceneManager.avatarComponent._mainUser.position let rotationQuaternion = BABYLON.Quaternion.RotationAxis( new BABYLON.Vector3(0, 0, 1), angle ) let cameraPos = new BABYLON.Vector3( cameraPostion0.x, cameraPostion0.y, cameraPostion0.z ) let cameraCenter = new BABYLON.Vector3( playerPosition.x, playerPosition.y, playerPosition.z ) // console.error(cameraPos, cameraCenter) cameraPos.rotateByQuaternionAroundPointToRef(rotationQuaternion, cameraCenter, cameraPos) // 相机碰撞检测 let cameraPosXverse = util.ue4Position2Xverse(cameraPos) let cameraCenterXverse = util.ue4Position2Xverse(playerPosition) cameraCenterXverse.y = cameraPosXverse.y if(!this.ray) this.ray = new BABYLON.Ray(new BABYLON.Vector3(0,0,0), new BABYLON.Vector3(0,0,1), 50); this.ray.origin = cameraCenterXverse this.ray.direction = BABYLON.Vector3.Normalize( cameraPosXverse.clone().subtract(cameraCenterXverse) ) let info = this.ray.intersectsMeshes(this.sceneManager.getGround())[0]; // todo 矫正 const offset = 0.09 if(info) { info.distance = Math.min(info.distance, 4.5 + offset) cameraPosXverse = cameraCenterXverse.add(this.ray.direction.clone().scale(Math.max( info.distance - offset, 0.1 ))) cameraPos = util.xversePosition2Ue4(cameraPosXverse) } let cameraState = { "position": { "x": cameraPos.x, "y": cameraPos.y, "z": cameraPos.z }, "angle": { "pitch": 0, "yaw": window.camera_endRotation, "roll": 0 } } if(this.avatarManager.avatars.get(nickname).position){ let response = JSON.parse(JSON.stringify(Response)) response.signal.newUserStates[0].userId = nickname response.signal.newUserStates[0].playerState.player.position.x = this.avatarManager.avatars.get(nickname).position.x response.signal.newUserStates[0].playerState.player.position.y = this.avatarManager.avatars.get(nickname).position.y response.signal.newUserStates[0].playerState.player.position.z = this.avatarManager.avatars.get(nickname).position.z response.signal.newUserStates[0].playerState.player.angle.pitch = this.avatarManager.avatars.get(nickname).rotation.pitch response.signal.newUserStates[0].playerState.player.angle.yaw = this.avatarManager.avatars.get(nickname).rotation.yaw response.signal.newUserStates[0].playerState.player.angle.roll = this.avatarManager.avatars.get(nickname).rotation.roll response.signal.newUserStates[0].playerState.camera.position.x = cameraState.position.x response.signal.newUserStates[0].playerState.camera.position.y = cameraState.position.y response.signal.newUserStates[0].playerState.camera.position.z = cameraState.position.z response.signal.newUserStates[0].playerState.camera.angle.pitch = cameraState.angle.pitch response.signal.newUserStates[0].playerState.camera.angle.yaw = cameraState.angle.yaw response.signal.newUserStates[0].playerState.camera.angle.roll = cameraState.angle.roll response.signal.newUserStates[0].playerState.cameraCenter.x = this.avatarManager.avatars.get(nickname).position.x response.signal.newUserStates[0].playerState.cameraCenter.y = this.avatarManager.avatars.get(nickname).position.y response.signal.newUserStates[0].playerState.cameraCenter.z = this.avatarManager.avatars.get(nickname).position.z console.log('更新相机角度') this.signal.handleSignal(response) } } moveTo({ position, needTurnAround, isEnd }) { // console.error(position) let player = this.sceneManager.avatarComponent._mainUser let playerPos = new BABYLON.Vector3( player.position.x, player.position.y, player.position.z ) let playerPosNew = new BABYLON.Vector3(position.x, position.y, position.z) let walkDistanceVec = playerPosNew.clone().subtract(playerPos) let angle = BABYLON.Vector3.GetAngleBetweenVectors(new BABYLON.Vector3(0, -1, 0), walkDistanceVec, new BABYLON.Vector3(0, 0, 1)) let rotationQuaternion = BABYLON.Quaternion.RotationAxis( new BABYLON.Vector3(0, 0, 1), angle ) let playerRotaTemp = rotationQuaternion.toEulerAngles() playerRotaTemp = new BABYLON.Vector3(playerRotaTemp.x, playerRotaTemp.z, playerRotaTemp.y) // 因为是用ue4pos算的,需要矫正一下 let playerRotaNew = util.xverseRotation2Ue4(playerRotaTemp) let cameraPosTemp = util.xversePosition2Ue4(this.sceneManager.cameraComponent.mainCamera.position) let cameraPos = new BABYLON.Vector3( cameraPosTemp.x, cameraPosTemp.y, cameraPosTemp.z ) let cameraPosNew = cameraPos.add(walkDistanceVec) let cameraRota = util.xverseRotation2Ue4(this.sceneManager.cameraComponent.mainCamera.rotation) if(player.position){ let response = JSON.parse(JSON.stringify(Response)) response.signal.newUserStates[0].userId = player.id response.signal.newUserStates[0].playerState.player.position.x = playerPosNew.x response.signal.newUserStates[0].playerState.player.position.y = playerPosNew.y response.signal.newUserStates[0].playerState.player.position.z = playerPosNew.z response.signal.newUserStates[0].playerState.player.angle.pitch = player.rotation.pitch response.signal.newUserStates[0].playerState.player.angle.yaw = needTurnAround ? playerRotaNew.yaw : player.rotation.yaw response.signal.newUserStates[0].playerState.player.angle.roll = player.rotation.roll response.signal.newUserStates[0].playerState.camera.position.x = cameraPosNew.x response.signal.newUserStates[0].playerState.camera.position.y = cameraPosNew.y response.signal.newUserStates[0].playerState.camera.position.z = cameraPosNew.z response.signal.newUserStates[0].playerState.camera.angle.pitch = cameraRota.pitch response.signal.newUserStates[0].playerState.camera.angle.yaw = cameraRota.yaw response.signal.newUserStates[0].playerState.camera.angle.roll = cameraRota.roll response.signal.newUserStates[0].playerState.cameraCenter.x = playerPosNew.x response.signal.newUserStates[0].playerState.cameraCenter.y = playerPosNew.y response.signal.newUserStates[0].playerState.cameraCenter.z = playerPosNew.z // 用于人物动画更新 response.signal.newUserStates[0].renderInfo.isMoving = !isEnd console.log('角色行走') this.signal.handleSignal(response) } } get currentNetworkOptions() { return this._currentNetworkOptions } get viewMode() { var e; return ((e = this._currentState) == null ? void 0 : e.viewMode) || "full" } get id() { return this._id } get skinId() { return this._currentState.skinId } get skin() { return this._currentState.skin } get sessionId() { return this.currentNetworkOptions.sessionId } get pictureQualityLevel() { return this.currentState.pictureQualityLevel } get avatars() { return Array.from(this.avatarManager.avatars.values()) } get currentState() { var e; return le(oe({}, this._currentState), { state: (e = this.networkController) == null ? void 0 : e._state }) } get _userAvatar() { return this.avatars.find(e=>e.userId === this.userId) } get tvs() { return this.engineProxy._tvs } get tv() { return this.tvs[0] } get currentClickingState() { return this._currentClickingState } afterJoinRoomHook() {} beforeJoinRoomResolveHook() {} afterReconnectedHook() {} handleSignalHook(e) {} skinChangedHook() {} async beforeStartGameHook(e) {} loadAssetsHook() {} afterUserAvatarLoadedHook() {} audienceViewModeHook() {} setViewModeToObserver() {} handleVehicleHook(e) {} updateReporter() { const {avatarId: avatarId, skinId: skinId, userId: userId, roomId: roomId, role: role, appId: appId, wsServerUrl: wsServerUrl} = this.options; reporter.updateHeader({ userId: userId }), reporter.updateBody({ roomId: roomId, role: role, skinId: skinId, avatarId: avatarId, appId: appId, wsServerUrl: wsServerUrl }) } async initRoom() { const {timeout: e=DEFAULT_JOINROOM_TIMEOUT} = this.options; if(util.isSupported()){ //return this._initRoom()._timeout(e, new TimeoutError("initRoom timeout")) return this._initRoom() } else{ return Promise.reject(new UnsupportedError) } } async _initRoom() { const e = this.validateOptions(this.options); if(e) { return logger.error("initRoom param error", e), Promise.reject(e); } const {canvas: canvas, avatarId: avatarId, skinId: skinId, userId: userId, wsServerUrl: wsServerUrl, role: role, token: token, pageSession: pageSession, rotationRenderType: rotationRenderType, isAllSync: isAllSync=!1, appId: f, camera: d, player: _, avatarComponents: g, nickname: nickname, avatarScale: v, firends: y=[], syncByEvent: b=!1, areaName: T, attitude: C=MotionType.Walk, pathName: A, viewMode: S="full", person: P, roomId: roomId, roomTypeId: M, hasAvatar: x=!1, syncToOthers: I=!1, prioritySync: w=!1, removeWhenDisconnected: O=!0, extra: D} = this.options; this.setCurrentNetworkOptions({ avatarId: avatarId, skinId: skinId, roomId: roomId, userId: userId, wsServerUrl: wsServerUrl, role: role, token: token, pageSession: pageSession, rotationRenderType: rotationRenderType, isAllSync: isAllSync, appId: f, camera: d, player: _, avatarComponents: g, nickname: nickname, avatarScale: v, firends: y, syncByEvent: b, areaName: T, attitude: C, pathName: A, person: P, roomTypeId: M, hasAvatar: x, syncToOthers: I, prioritySync: w, extra: D, removeWhenDisconnected: O }); this.userId = userId; this.canvas = canvas; T && (this.pathManager.currentArea = T); this.networkController = new NetworkController(this); this.setCurrentState({ areaName: T, pathName: A, attitude: C, speed: 0, viewMode: S, state: this.networkController._state, skinId: skinId }); try { await Promise.all([this.initNetwork(), this.initConfig()/*, this.initWasm()*/]); logger.info("network config wasm all ready, start to create game"); const F = await this.requestCreateRoom({ skinId: skinId }) , V = F.routeList.find(L=>L.areaName === T) , N = ((V == null ? void 0 : V.step) || 7.5) * 30; this.updateCurrentState({ skin: F, skinId: F.id, versionId: F.versionId, speed: N }), await this.initEngine(F) } catch (F) { return Promise.reject(F) } this.beforeJoinRoomResolve(); return this.receiveRtcData() } beforeJoinRoomResolve() { this.setupStats(), this.eventsController = new EventsController(this), this.eventsController.bindEvents(), this.panorama = new Panorama(this), this.beforeJoinRoomResolveHook() } afterJoinRoom() { this.joined = !0, this.viewMode === "observer" && this.setViewModeToObserver(), logger.infoAndReportMeasurement({ tag: this.viewMode, value: this.firstFrameTimestamp - this._startTime, startTime: Date.now(), metric: "joinRoom" }), this.camera.initialFov = this.sceneManager.cameraComponent.getCameraFov(), this.stats.on("stats", ({stats: e})=>{ reporter.report("stats", oe({}, e)) } ), this.debug = new Debug(this), this.afterJoinRoomHook() } afterReconnected() { this.avatarManager.clearOtherUsers(), this.afterReconnectedHook() } leave() { var e, t; return logger.info("Invoke room.leave"), (e = this.eventsController) == null || e.clearEvents(), (t = this.networkController) == null || t.quit(), this } validateOptions(e) { const {canvas: t, avatarId: avatarId, skinId: skinId, userId: userId, role: role, roomId: roomId, token: token, appId: appId, avatarComponents: avatarComponents} = e || {} const h = []; t instanceof HTMLCanvasElement || h.push(new ParamError("`canvas` must be instanceof of HTMLCanvasElement")); (!userId || typeof userId != "string") && h.push(new ParamError("`userId` must be string")); (!token || typeof token != "string") && h.push(new ParamError("`token` must be string")); (!appId || typeof appId != "string") && h.push(new ParamError("`appId` must be string")); role == "audience" || (!avatarId || !skinId) && h.push(new ParamError("`avatarId` and `skinId` is required when create room")); return h[0] } async initNetwork() { if (this.viewMode === "serverless") return Promise.resolve(); const e = Date.now(); try { await this.networkController.connect()._timeout(8e3, new InitNetworkTimeoutError), logger.infoAndReportMeasurement({ metric: "networkInitAt", startTime: this._startTime, group: "joinRoom" }), logger.infoAndReportMeasurement({ metric: "networkInitCost", startTime: e, group: "joinRoom" }) } catch (t) { throw logger.infoAndReportMeasurement({ metric: "networkInitAt", startTime: e, group: "joinRoom", error: t }), t } } async initConfig() { const e = Date.now(); try { await this.modelManager.getApplicationConfig()._timeout(8e3, new InitConfigTimeoutError), logger.infoAndReportMeasurement({ metric: "configInitAt", startTime: this._startTime, group: "joinRoom" }), logger.infoAndReportMeasurement({ metric: "configInitCost", startTime: e, group: "joinRoom" }) } catch (t) { throw logger.infoAndReportMeasurement({ metric: "configInitAt", startTime: e, group: "joinRoom", error: t }), t } } async initEngine(e) { const t = Date.now(); try { this.engineProxy = new EngineProxy(this), await this.engineProxy.initEngine(e), logger.infoAndReportMeasurement({ metric: "webglInitAt", startTime: this._startTime, group: "joinRoom" }), logger.infoAndReportMeasurement({ metric: "webglInitCost", startTime: t, group: "joinRoom" }); return } catch (r) { let n = r; return r.code !== Codes.InitEngineTimeout && (n = new InitEngineError), logger.error(r), logger.infoAndReportMeasurement({ metric: "webglInitAt", startTime: t, group: "joinRoom", error: n }), Promise.reject(n) } } async initWasm() { if (this.viewMode === "serverless") return Promise.resolve(); const e = Date.now(); try { await this.networkController.rtcp.workers.init(this.options.resolution)._timeout(8e3, new InitDecoderTimeoutError); this.networkController.rtcp.workers.registerFunction("error", t=>{ logger.error("decode error", t); const {code: code, message: n} = t; this.emit("error", { code: code, msg: n }) } ), logger.infoAndReportMeasurement({ metric: "wasmInitAt", group: "joinRoom", startTime: this._startTime }), logger.infoAndReportMeasurement({ metric: "wasmInitCost", group: "joinRoom", startTime: e }), eventsManager.on("traceId", t=>{ this.networkController.rtcp.workers.onTraceId(t) } ) } catch (t) { throw logger.infoAndReportMeasurement({ metric: "wasmInitAt", group: "joinRoom", startTime: e, error: t }), t } } async requestCreateRoom({skinId: e}) { let t; if (e) { t = await this.getSkin(e); const r = await this.modelManager.findRoute(e, this.options.pathName); this.updateCurrentNetworkOptions({ areaName: r.areaName, attitude: r.attitude, versionId: t.versionId }); const {camera: n, player: player} = util.getRandomItem(r.birthPointList) || this.options; this.options.camera || this.updateCurrentNetworkOptions({ camera: n }), this.options.player || this.updateCurrentNetworkOptions({ player: player }) } if (this.viewMode === "serverless") return t; try { await this.beforeStartGameHook(this.options); //const {room_id: room_id, data: n, session_id: session_id} = await this.networkController.startGame(); const room_id = this.options.roomId; const n = JSON.stringify(this.options.camera); const session_id = ''; this._id = room_id; const a = JSON.parse(n); this.isHost = a.IsHost, e = a.SkinID || e; const skin = await this.getSkin(e); this.updateCurrentNetworkOptions({ roomId: room_id, sessionId: session_id }); reporter.updateBody({ roomId: room_id, skinId: e, serverSession: session_id }); return skin } catch (r) { throw logger.error("Request create room error", r), r } } pause() { return this.engineProxy.pause() } resume() { return this.engineProxy.resume() } reconnect() { this.networkController.reconnect() } async setViewMode(e) {} handleRepetLogin() { logger.warn("receive " + Codes.RepeatLogin + " for repeat login"), this.emit("repeatLogin"), reporter.disable(), this.networkController.quit() } setPictureQualityLevel(e) { const t = { high: EImageQuality.high, low: EImageQuality.low, average: EImageQuality.mid }; return this.updateCurrentState({ pictureQualityLevel: e }), this.sceneManager.setImageQuality(t[e]) } async getSkin(skinId) { let t = (this.skinList = await this.modelManager.getSkinsList()).find(skin=>skin.id === skinId || skin.id === skinId) if (t) return t; { const n = `skin is invalid: skinId: ${skinId}`; return Promise.reject(new ParamError(n)) } } setupStats() { this.stats.assign({ roomId: this.id, userId: this.userId }), setInterval(this.engineProxy.updateStats, 1e3) } proxyEvents(e, t) { this.emit(e, t) } setCurrentNetworkOptions(e) { this._currentNetworkOptions = e } updateCurrentNetworkOptions(e) { Object.assign(this._currentNetworkOptions, e), Object.assign(this.options, e) } setCurrentState(e) { this._currentState = e } updateCurrentState(e) { e.skinId && (this.lastSkinId = this.currentState.skinId, this.updateCurrentNetworkOptions({ skinId: e.skinId })), e.versionId && this.updateCurrentNetworkOptions({ versionId: e.versionId }), Object.assign(this._currentState, e) } }