UNPKG

@vrspace/babylonjs

Version:

vrspace.org babylonjs client

223 lines (205 loc) 8.06 kB
import { VRObject } from '../client/vrspace.js'; import { Client } from '../client/vrspace.js'; import { HumanoidAvatar } from '../avatar/humanoid-avatar.js'; import { MeshAvatar } from '../avatar/mesh-avatar.js'; import { VideoAvatar } from '../avatar/video-avatar.js'; import { BotController } from '../avatar/bot-controller.js'; import { MeshLoader } from './mesh-loader.js'; export class AvatarLoader extends MeshLoader { constructor(scene, fps, loadCallback, loadErrorHandler){ super(loadCallback,loadErrorHandler); this.scene = scene; this.fps = fps; /** Create animations for movement of avatars, default true. Recommended for low fps.*/ this.createAnimations = true; /** Custom avatar options, applied to avatars after loading. Currently video avatars only */ this.customOptions = null; /** Custom avatar animations */ this.customAnimations = null; /** Avatar factory, default this.createAvatar */ this.avatarFactory = this.createAvatar; /** Default position applied after an avatar loads */ this.defaultPosition = new BABYLON.Vector3(1000, 1000, 1000); /** Default rotation applied after an avatar loads */ this.defaultRotation = new BABYLON.Vector3(0, 0, 0); } /** @param {VRObject} obj */ load(obj) { if (obj.video || obj.mesh == "video") { this.loadStream(obj); } else if (obj.humanoid) { this.loadAvatar(obj); } else { this.loadMeshAvatar(obj); } } /** Load a video avatar, attach a listener to it. @param {Client} obj */ loadStream(obj) { this.log("loading stream for " + obj.id); var video = this.avatarFactory(obj); video.mesh.name = obj.mesh; // obfuscators get in the way //video.mesh.id = obj.constructor.name+" "+obj.id; video.mesh.id = obj.className + " " + obj.id; obj.avatar = video; var parent = new BABYLON.TransformNode("Root of " + video.mesh.id, this.scene); video.mesh.parent = parent; parent.VRObject = obj; parent.avatar = video; // CHECKME this.log("Added stream " + obj.id); if (obj.position.x == 0 && obj.position.y == 0 && obj.position.z == 0) { // avatar position has not yet been initialized, use default parent.position = new BABYLON.Vector3(this.defaultPosition.x, this.defaultPosition.y, this.defaultPosition.z); obj.position = this.defaultPosition; var initialPosition = { position: {} }; this.changeObject(obj, initialPosition, parent); } else { // apply known position parent.position = new BABYLON.Vector3(obj.position.x, obj.position.y, obj.position.z); } if (obj.rotation) { if (obj.rotation.x == 0 && obj.rotation.y == 0 && obj.rotation.z == 0) { // avatar rotation has not yet been initialized, use default parent.rotation = new BABYLON.Vector3(this.defaultRotation.x, this.defaultRotation.y, this.defaultRotation.z); obj.rotation = this.defaultRotation; var initialRotation = { rotation: {} }; this.changeObject(obj, initialRotation, parent); } else { // apply known rotation parent.rotation = new BABYLON.Vector3(obj.rotation.x, obj.rotation.y, obj.rotation.z); } } obj.addListener((obj, changes) => this.changeObject(obj, changes, parent)); if (this.mediaStreams) { this.mediaStreams.streamToMesh(obj, video.mesh); } else { console.log("WARNING: unable to stream to " + obj.id + " - no MediaStreams"); } this.notifyLoadListeners(obj, video); } /** * Load a 3D avatar, attach a listener to it * @param {VRObject} obj VRObject that represents the user */ async loadAvatar(obj) { this.log("loading avatar " + obj.mesh); let avatar = await this.createAvatarFromUrl(obj.mesh); avatar.userHeight = obj.userHeight; avatar.load((avatar) => { obj.avatar = avatar; obj.instantiatedEntries = avatar.instantiatedEntries; avatar.VRObject = obj; avatar.parentMesh.VRObject = obj; // apply current name, position and rotation this.changeAvatar(obj, { name: obj.name, position: obj.position }); if (obj.rotation) { // FIXME rotation can be null sometimes (offline users?) this.changeAvatar(obj, { rotation: obj.rotation }); } // TODO also apply other non-null properties here if (obj.animation) { this.changeAvatar(obj, { animation: obj.animation }); } // add listener to process changes obj.addListener((obj, changes) => this.changeAvatar(obj, changes)); // subscribe to media stream here if available if (this.mediaStreams) { this.mediaStreams.streamToMesh(obj, obj.avatar.baseMesh()); } if (obj.className.indexOf("Bot") >= 0) { console.log("Bot loaded"); // TODO bot controller obj.avatarController = new BotController(avatar); } this.notifyLoadListeners(obj, avatar); }, (error) => { // FIXME - this fallback is not safe when loading multiple instances at once console.log("Failed to load humanoid avatar, loading as mesh", error); obj.humanoid = false; this.loadMeshAvatar(obj); }); } /** * Any 3d object can be an avatar * @param {VRObject} obj VRObject that represents the user */ loadMeshAvatar(obj) { let avatar = new MeshAvatar(this.scene, obj); obj.avatar = avatar; this.loadMesh(obj, mesh => { avatar.mesh = mesh; mesh.avatar = avatar; var bbox = avatar.baseMesh().getHierarchyBoundingVectors(); this.log("Bounding box:"); this.log(bbox); avatar.userHeight = bbox.max.y - bbox.min.y; avatar.setName(obj.name); }); } /** Apply remote changes to an avatar (VRObject listener) */ changeAvatar(obj, changes) { this.log('Processing changes on avatar'); this.log(changes); var avatar = obj.avatar; for (var field in changes) { var node = avatar.baseMesh(); this.routeEvent(obj, field, node); this.notifyListeners(obj, field, node); } } routeEvent(obj, field, node) { if (obj.avatar) { let object = obj.avatar; if (typeof object[field] === 'function') { // execute a change on avatars - e.g. wrote object[field](obj, node); } else if (typeof object[field + 'Changed'] === 'function') { // execute callback after changes are applied object[field + 'Changed'](obj, node); } else { console.log("Ignoring unknown event to "+obj+": "+field); } } else { console.log("Ignoring unknown event "+field+" to object "+obj.id); } } /** * Creates new Avatar instance from the URL * @param url URL to load avatar from */ async createAvatarFromUrl(url) { let avatar = await HumanoidAvatar.createFromUrl(this.scene, url); avatar.animations = this.customAnimations; avatar.fps = this.fps; avatar.generateAnimations = this.createAnimations; // GLTF characters are facing the user when loaded, turn it around // this doesn't do anything for cloned characters, affects only first one that loads avatar.turnAround = true; //avatar.debug = false; // or this.debug? return avatar; } /** * Default video avatar factory method * @param {Client} obj * @param {boolean} [autoStart=false] * @param {boolean} [autoAttach=false] */ createAvatar(obj, autoStart=false, autoAttach=false) { let avatar = new VideoAvatar(this.scene, null, this.customOptions); avatar.autoStart = autoStart; avatar.autoAttach = autoAttach; if (obj.picture) { avatar.altImage = obj.picture; } avatar.show(); if (obj.name) { avatar.setName(obj.name); } else { avatar.setName("u" + obj.id); } return avatar; } }