diff --git a/core/engines/anima/client.js b/core/engines/anima/client.js index 5ecf53639..e535c25e5 100644 --- a/core/engines/anima/client.js +++ b/core/engines/anima/client.js @@ -13,9 +13,15 @@ const Anima = { } getValue() { - const {startValue, endValue, startTime, endTime} = this; + const { startValue, endValue, startTime, endTime } = this; const now = Date.now(); - return startValue + ease(Math.max(Math.min((now - startTime) / (endTime - startTime), 1), 0)) * (endValue - startValue); + return ( + startValue + + ease( + Math.max(Math.min((now - startTime) / (endTime - startTime), 1), 0) + ) * + (endValue - startValue) + ); } isDone() { @@ -27,7 +33,8 @@ const Anima = { } } - const _makeAnimation = (startValue, endValue, duration) => new Animation(startValue, endValue, duration); + const _makeAnimation = (startValue, endValue, duration) => + new Animation(startValue, endValue, duration); return { makeAnimation: _makeAnimation, diff --git a/core/engines/biolumi/client.js b/core/engines/biolumi/client.js index 91bc8d392..0b85eb260 100644 --- a/core/engines/biolumi/client.js +++ b/core/engines/biolumi/client.js @@ -4,7 +4,7 @@ import menuShader from './lib/shaders/menu'; import transparentShader from './lib/shaders/transparent'; import rasterize from 'rasterize/frontend'; -const DEFAULT_FRAME_TIME = 1000 / (60 * 2) +const DEFAULT_FRAME_TIME = 1000 / (60 * 2); const SIDES = ['left', 'right']; @@ -14,7 +14,7 @@ class Biolumi { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; let live = true; const cleanups = []; @@ -27,22 +27,25 @@ class Biolumi { } }; - const _requestImage = src => new Promise((accept, reject) => { - const img = new Image(); - img.src = src; - img.onload = () => { - accept(img); - }; - img.onerror = err => { - reject(err); - }; - }); - const _requestImageBitmap = src => _requestImage(src) - .then(img => createImageBitmap(img, 0, 0, img.width, img.height)); + const _requestImage = src => + new Promise((accept, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => { + accept(img); + }; + img.onerror = err => { + reject(err); + }; + }); + const _requestImageBitmap = src => + _requestImage(src).then(img => + createImageBitmap(img, 0, 0, img.width, img.height) + ); const _requestTransparentImg = () => _requestImageBitmap(transparentImgUrl); const _requestUiWorker = () => { class UiWorker { - constructor({frameTime = DEFAULT_FRAME_TIME} = {}) { + constructor({ frameTime = DEFAULT_FRAME_TIME } = {}) { this.frameTime = frameTime; this.threads = []; @@ -53,7 +56,7 @@ class Biolumi { } add(thread) { - const {threads} = this; + const { threads } = this; threads.push(thread); this.work(); @@ -67,12 +70,12 @@ class Biolumi { } work(next) { - const {frameTime, threads} = this; + const { frameTime, threads } = this; const _recurseFrame = () => { const _recurseThread = () => { if (threads.length > 0) { - const {workTime} = this; + const { workTime } = this; if (workTime < frameTime) { const workStartTime = Date.now(); @@ -146,22 +149,17 @@ class Biolumi { _requestUiWorker(), _requestUiTimer(), rasterize(), - ]) - .then(([ - [ - three, - geometryUtils, - ], - transparentImg, - uiWorker, - uiTimer, - rasterizer, - ]) => { + ]).then( + ( + [[three, geometryUtils], transparentImg, uiWorker, uiTimer, rasterizer] + ) => { if (live) { - const {THREE, renderer} = three; + const { THREE, renderer } = three; if (rasterizer.type === 'internal') { - console.warn('warning: Server is using *slow* local rendering; VR clients will experience hitching. To fix this, contact the server admin.'); + console.warn( + 'warning: Server is using *slow* local rendering; VR clients will experience hitching. To fix this, contact the server admin.' + ); } const zeroQuaternion = new THREE.Quaternion(); @@ -173,7 +171,9 @@ class Biolumi { const downVector = new THREE.Vector3(0, -1, 0); const RAY_COLOR = 0x44c2ff; - const RAY_HIGHLIGHT_COLOR = new THREE.Color(RAY_COLOR).multiplyScalar(0.5).getHex(); + const RAY_HIGHLIGHT_COLOR = new THREE.Color(RAY_COLOR) + .multiplyScalar(0.5) + .getHex(); const dotMeshMaterial = new THREE.MeshBasicMaterial({ color: RAY_COLOR, @@ -216,7 +216,17 @@ class Biolumi { }; class Page { - constructor(spec, type, state, color, width, height, worldWidth, worldHeight, layer) { + constructor( + spec, + type, + state, + color, + width, + height, + worldWidth, + worldHeight, + layer + ) { this.spec = spec; this.type = type; this.state = state; @@ -228,10 +238,14 @@ class Biolumi { this.layer = layer; const mesh = (() => { - const geometry = geometryUtils.unindexBufferGeometry(new THREE.PlaneBufferGeometry(worldWidth, worldHeight)); + const geometry = geometryUtils.unindexBufferGeometry( + new THREE.PlaneBufferGeometry(worldWidth, worldHeight) + ); const material = (() => { const shaderUniforms = _makeMenuShaderUniforms(); - shaderUniforms.backgroundColor.value = Float32Array.from(color); + shaderUniforms.backgroundColor.value = Float32Array.from( + color + ); const shaderMaterial = new THREE.ShaderMaterial({ uniforms: shaderUniforms, vertexShader: menuShader.vertexShader, @@ -270,54 +284,61 @@ class Biolumi { }; const _requestLayerSpec = () => { - const {spec, state} = this; - cache.layerSpec = typeof spec === 'function' ? spec(state) : spec; + const { spec, state } = this; + cache.layerSpec = + typeof spec === 'function' ? spec(state) : spec; return Promise.resolve(); }; - const _requestImage = () => new Promise((accept, reject) => { - const {layerSpec} = cache; - const {type = 'html'} = layerSpec; - - if (type === 'html') { - const {width, height} = this; - const {src, w = width, h = height} = layerSpec; - rasterizer.rasterize(src, w, h) - .then(({imageBitmap, anchors, measures}) => { - cache.img = imageBitmap; - cache.anchors = anchors; - cache.measures = measures; - - accept(); - }) - .catch(err => { - console.warn('biolumi image load error', {src}, err); - - accept(); - }); - } else if (type === 'image') { - const {img} = layerSpec; + const _requestImage = () => + new Promise((accept, reject) => { + const { layerSpec } = cache; + const { type = 'html' } = layerSpec; + + if (type === 'html') { + const { width, height } = this; + const { src, w = width, h = height } = layerSpec; + rasterizer + .rasterize(src, w, h) + .then(({ imageBitmap, anchors, measures }) => { + cache.img = imageBitmap; + cache.anchors = anchors; + cache.measures = measures; + + accept(); + }) + .catch(err => { + console.warn('biolumi image load error', { src }, err); + + accept(); + }); + } else if (type === 'image') { + const { img } = layerSpec; - cache.img = img; + cache.img = img; - accept(); - } else { - accept(); - } - }); + accept(); + } else { + accept(); + } + }); const _requestTexture = () => { - const {layerSpec, img} = cache; + const { layerSpec, img } = cache; // const {pixelated = false} = layerSpec; - const {mesh: {material: {uniforms: {texture: {value: texture}}}}} = this; + const { + mesh: { + material: { uniforms: { texture: { value: texture } } }, + }, + } = this; texture.image = img; /* if (!pixelated) { texture.minFilter = THREE.LinearFilter; texture.magFilter = THREE.LinearFilter; texture.anisotropy = 16; } else { */ - texture.minFilter = THREE.NearestFilter; - texture.magFilter = THREE.NearestFilter; - texture.anisotropy = 1; + texture.minFilter = THREE.NearestFilter; + texture.magFilter = THREE.NearestFilter; + texture.anisotropy = 1; // } texture.needsUpdate = true; @@ -328,26 +349,28 @@ class Biolumi { return Promise.resolve(); }; const _requestLayer = () => { - const {layerSpec} = cache; - const {type = 'html'} = layerSpec; + const { layerSpec } = cache; + const { type = 'html' } = layerSpec; if (type === 'html') { - const {width, height} = this; - const {w = width, h = height} = layerSpec; - const {anchors, measures} = cache; + const { width, height } = this; + const { w = width, h = height } = layerSpec; + const { anchors, measures } = cache; const layer = new Layer(w, h, anchors, measures); this.layer = layer; } else if (type === 'image') { - const {width, height} = this; - const {x = 0, y = 0, w = width, h = height} = layerSpec; + const { width, height } = this; + const { x = 0, y = 0, w = width, h = height } = layerSpec; const layer = new Layer(w, h); this.layer = layer; } else { - console.warn('illegal layer spec type:' + JSON.stringify(type)); + console.warn( + 'illegal layer spec type:' + JSON.stringify(type) + ); this.layer = null; } @@ -416,12 +439,31 @@ class Biolumi { this.page = null; } - makePage(spec, {type = null, state = null, worldWidth, worldHeight, layer = null} = {}) { - const {page} = this; + makePage( + spec, + { + type = null, + state = null, + worldWidth, + worldHeight, + layer = null, + } = {} + ) { + const { page } = this; if (!page) { - const {width, height, color} = this; - const page = new Page(spec, type, state, color, width, height, worldWidth, worldHeight, layer); + const { width, height, color } = this; + const page = new Page( + spec, + type, + state, + color, + width, + height, + worldWidth, + worldHeight, + layer + ); this.page = page; return page.mesh; @@ -489,15 +531,15 @@ class Biolumi { return this.hoverStates[side]; } - update({pose, sides, controllerMeshes}) { + update({ pose, sides, controllerMeshes }) { const _hideSide = side => { - const {hoverStates, dotMeshes, boxMeshes} = this; + const { hoverStates, dotMeshes, boxMeshes } = this; const hoverState = hoverStates[side]; const dotMesh = dotMeshes[side]; const boxMesh = boxMeshes[side]; const controllerMesh = controllerMeshes[side]; - const {rayMesh} = controllerMesh; + const { rayMesh } = controllerMesh; hoverState.intersectionPoint = null; hoverState.target = null; @@ -527,10 +569,10 @@ class Biolumi { } }; - const {meshes, open} = this; + const { meshes, open } = this; if (open && meshes.length > 0) { - const {gamepads} = pose; - const {hoverStates, dotMeshes, boxMeshes} = this; + const { gamepads } = pose; + const { hoverStates, dotMeshes, boxMeshes } = this; for (let s = 0; s < SIDES.length; s++) { const side = SIDES[s]; @@ -539,17 +581,17 @@ class Biolumi { const boxMesh = boxMeshes[side]; const hoverState = hoverStates[side]; const controllerMesh = controllerMeshes[side]; - const {rayMesh} = controllerMesh; + const { rayMesh } = controllerMesh; if (sides.includes(side)) { const controllerLine = localLine.set( gamepad.worldPosition, - localVector.copy(gamepad.worldPosition) - .add( - localVector2.copy(forwardVector) - .applyQuaternion(gamepad.worldRotation) - .multiplyScalar(3) - ) + localVector.copy(gamepad.worldPosition).add( + localVector2 + .copy(forwardVector) + .applyQuaternion(gamepad.worldRotation) + .multiplyScalar(3) + ) ); let found = false; @@ -557,46 +599,86 @@ class Biolumi { const mesh = meshes[i]; if (_isWorldVisible(mesh)) { - const normal = localVector2.copy(backwardVector) - .applyQuaternion(localQuaternion.setFromRotationMatrix(mesh.matrixWorld)); + const normal = localVector2 + .copy(backwardVector) + .applyQuaternion( + localQuaternion.setFromRotationMatrix( + mesh.matrixWorld + ) + ); const uiPlane = localPlane.setFromNormalAndCoplanarPoint( normal, localVector3.setFromMatrixPosition(mesh.matrixWorld) ); - const intersectionPoint = uiPlane.intersectLine(controllerLine, localVector3); + const intersectionPoint = uiPlane.intersectLine( + controllerLine, + localVector3 + ); if (intersectionPoint) { hoverState.intersectionPoint = intersectionPoint; - const {page} = mesh; - const {width, height, worldWidth, worldHeight} = page; - const worldPosition = localVector4.setFromMatrixPosition(mesh.matrixWorld); - const worldRotation = localQuaternion.setFromRotationMatrix(mesh.matrixWorld); + const { page } = mesh; + const { + width, + height, + worldWidth, + worldHeight, + } = page; + const worldPosition = localVector4.setFromMatrixPosition( + mesh.matrixWorld + ); + const worldRotation = localQuaternion.setFromRotationMatrix( + mesh.matrixWorld + ); hoverState.target = page; const yAxis = localPlane2.setFromNormalAndCoplanarPoint( - localVector5.copy(rightVector) + localVector5 + .copy(rightVector) .applyQuaternion(worldRotation), - localVector6.copy(worldPosition) - .sub(localVector7.set(worldWidth / 2, 0, 0).applyQuaternion(worldRotation)) + localVector6 + .copy(worldPosition) + .sub( + localVector7 + .set(worldWidth / 2, 0, 0) + .applyQuaternion(worldRotation) + ) ); - const x = yAxis.distanceToPoint(intersectionPoint) / worldWidth * width; + const x = + yAxis.distanceToPoint(intersectionPoint) / + worldWidth * + width; const xAxis = localPlane3.setFromNormalAndCoplanarPoint( - localVector5.copy(downVector) + localVector5 + .copy(downVector) .applyQuaternion(worldRotation), - localVector6.copy(worldPosition) - .add(localVector7.set(0, worldHeight / 2, 0).applyQuaternion(worldRotation)) + localVector6 + .copy(worldPosition) + .add( + localVector7 + .set(0, worldHeight / 2, 0) + .applyQuaternion(worldRotation) + ) ); - const y = xAxis.distanceToPoint(intersectionPoint) / worldHeight * height; + const y = + xAxis.distanceToPoint(intersectionPoint) / + worldHeight * + height; if (x >= 0 && x < width && y > 0 && y <= height) { let anchor = null; - const {layer} = page; + const { layer } = page; const anchors = layer ? layer.anchors : []; for (let i = 0; i < anchors.length; i++) { const a = anchors[i]; - if (x >= a.left && x <= a.right && y >= a.top && y <= a.bottom) { + if ( + x >= a.left && + x <= a.right && + y >= a.top && + y <= a.bottom + ) { anchor = a; break; } @@ -604,24 +686,47 @@ class Biolumi { if (anchor) { hoverState.anchor = anchor; - hoverState.value = (x - anchor.left) / (anchor.right - anchor.left); - hoverState.crossValue = (y - anchor.top) / (anchor.bottom - anchor.top); + hoverState.value = + (x - anchor.left) / + (anchor.right - anchor.left); + hoverState.crossValue = + (y - anchor.top) / (anchor.bottom - anchor.top); const anchorMidpoint = localCoord.set( - ((anchor.left + anchor.right) / 2) / width * worldWidth, - ((anchor.top + anchor.bottom) / 2) / height * worldHeight + (anchor.left + anchor.right) / + 2 / + width * + worldWidth, + (anchor.top + anchor.bottom) / + 2 / + height * + worldHeight ); const anchorSize = localCoord2.set( - (anchor.right - anchor.left) / width * worldWidth, - (anchor.bottom - anchor.top) / height * worldHeight + (anchor.right - anchor.left) / + width * + worldWidth, + (anchor.bottom - anchor.top) / + height * + worldHeight ); - boxMesh.position.copy(worldPosition) + boxMesh.position + .copy(worldPosition) .add( - localVector7.set((-worldWidth / 2) + anchorMidpoint.x, (worldHeight / 2) - anchorMidpoint.y, 0) + localVector7 + .set( + -worldWidth / 2 + anchorMidpoint.x, + worldHeight / 2 - anchorMidpoint.y, + 0 + ) .applyQuaternion(worldRotation) ); boxMesh.quaternion.copy(worldRotation); - boxMesh.scale.set(anchorSize.x, anchorSize.y, 0.01); + boxMesh.scale.set( + anchorSize.x, + anchorSize.y, + 0.01 + ); boxMesh.updateMatrixWorld(); if (!boxMesh.visible) { @@ -643,14 +748,25 @@ class Biolumi { normal ); dotMesh.updateMatrixWorld(); - if (!gamepad.buttons.trigger.pressed && dotMesh.material.color.getHex() !== RAY_COLOR) { + if ( + !gamepad.buttons.trigger.pressed && + dotMesh.material.color.getHex() !== RAY_COLOR + ) { dotMesh.material.color.setHex(RAY_COLOR); - } else if (gamepad.buttons.trigger.pressed && dotMesh.material.color.getHex() !== RAY_HIGHLIGHT_COLOR) { - dotMesh.material.color.setHex(RAY_HIGHLIGHT_COLOR); + } else if ( + gamepad.buttons.trigger.pressed && + dotMesh.material.color.getHex() !== + RAY_HIGHLIGHT_COLOR + ) { + dotMesh.material.color.setHex( + RAY_HIGHLIGHT_COLOR + ); } dotMesh.visible = true; - rayMesh.scale.z = intersectionPoint.distanceTo(localLine.start); + rayMesh.scale.z = intersectionPoint.distanceTo( + localLine.start + ); rayMesh.updateMatrixWorld(); rayMesh.visible = true; @@ -682,9 +798,32 @@ class Biolumi { } } - const _makeUi = ({width, height, color = [1, 1, 1, 1]}) => new Ui(width, height, color); - const _makePage = (spec, {type = null, state = null, color = [1, 1, 1, 1], width, height, worldWidth, worldHeight, layer = null}) => - new Page(spec, type, state, color, width, height, worldWidth, worldHeight, layer); + const _makeUi = ({ width, height, color = [1, 1, 1, 1] }) => + new Ui(width, height, color); + const _makePage = ( + spec, + { + type = null, + state = null, + color = [1, 1, 1, 1], + width, + height, + worldWidth, + worldHeight, + layer = null, + } + ) => + new Page( + spec, + type, + state, + color, + width, + height, + worldWidth, + worldHeight, + layer + ); const _updateUiTimer = () => { uiTimer.update(); @@ -706,9 +845,12 @@ class Biolumi { const _getTransparentMaterial = () => transparentMaterial; const _getTextPropertiesFromCoord = (width, inputText, coordPx) => { - const index = Math.min(Math.floor(coordPx / width), inputText.length); + const index = Math.min( + Math.floor(coordPx / width), + inputText.length + ); const px = index * width; - return {index, px}; + return { index, px }; }; const _getKeyCode = s => keycode(s); @@ -724,51 +866,65 @@ class Biolumi { }; const _isPrintableKeycode = keyCode => (keyCode > 47 && keyCode < 58) || // number keys - (keyCode == 32) || // spacebar & return key(s) (if you want to allow carriage returns) + keyCode == 32 || // spacebar & return key(s) (if you want to allow carriage returns) (keyCode > 64 && keyCode < 91) || // letter keys (keyCode > 95 && keyCode < 112) || // numpad keys (keyCode > 185 && keyCode < 193) || // ;=,-./` (in order) (keyCode > 218 && keyCode < 223); // [\]' (in order)\ const _applyStateKeyEvent = (state, e) => { - const {inputText, inputIndex, width} = state; + const { inputText, inputIndex, width } = state; let change = false; let commit = false; if (_isPrintableKeycode(e.event.keyCode)) { // if (!(e.event.ctrlKey && e.event.keyCode === 86)) { // ctrl-v - state.inputText = inputText.slice(0, inputIndex) + _getKeyEventCharacter(e.event) + inputText.slice(inputIndex); - state.inputIndex++; - state.inputValue = width * state.inputIndex; + state.inputText = + inputText.slice(0, inputIndex) + + _getKeyEventCharacter(e.event) + + inputText.slice(inputIndex); + state.inputIndex++; + state.inputValue = width * state.inputIndex; - change = true; + change = true; // } - } else if (e.event.keyCode === 13) { // enter + } else if (e.event.keyCode === 13) { + // enter commit = true; - } else if (e.event.keyCode === 8) { // backspace + } else if (e.event.keyCode === 8) { + // backspace if (inputIndex > 0) { - state.inputText = inputText.slice(0, inputIndex - 1) + inputText.slice(inputIndex); + state.inputText = + inputText.slice(0, inputIndex - 1) + + inputText.slice(inputIndex); state.inputIndex--; state.inputValue = width * state.inputIndex; change = true; } - } else if (e.event.keyCode === 37) { // left + } else if (e.event.keyCode === 37) { + // left state.inputIndex = Math.max(state.inputIndex - 1, 0); state.inputValue = width * state.inputIndex; change = true; - } else if (e.event.keyCode === 39) { // right - state.inputIndex = Math.min(state.inputIndex + 1, inputText.length); + } else if (e.event.keyCode === 39) { + // right + state.inputIndex = Math.min( + state.inputIndex + 1, + inputText.length + ); state.inputValue = width * state.inputIndex; change = true; - } else if (e.event.keyCode === 38) { // up + } else if (e.event.keyCode === 38) { + // up state.inputIndex = 0; state.inputValue = width * state.inputIndex; change = true; - } else if (e.event.keyCode === 40) { // down + } else if (e.event.keyCode === 40) { + // down state.inputIndex = inputText.length; state.inputValue = width * state.inputIndex; @@ -785,29 +941,37 @@ class Biolumi { } }; - const _makeMeshPointGetter = ({position, rotation, scale, width, height, worldWidth, worldHeight}) => (x, y, z) => position.clone() - .add( - new THREE.Vector3( - -worldWidth / 2, - worldHeight / 2, - 0 - ) - .add( - new THREE.Vector3( - (x / width) * worldWidth, - (-y / height) * worldHeight, - z + const _makeMeshPointGetter = ({ + position, + rotation, + scale, + width, + height, + worldWidth, + worldHeight, + }) => (x, y, z) => + position.clone().add( + new THREE.Vector3(-worldWidth / 2, worldHeight / 2, 0) + .add( + new THREE.Vector3( + x / width * worldWidth, + -y / height * worldHeight, + z + ) ) - ) - .multiply(scale) - .applyQuaternion(rotation) + .multiply(scale) + .applyQuaternion(rotation) ); const _makeUiTracker = () => new UiTracker(); const _makeDotMesh = () => { - const geometry = new THREE.CylinderBufferGeometry(0.01, 0.01, 0.001, 32) - .applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2)); + const geometry = new THREE.CylinderBufferGeometry( + 0.01, + 0.01, + 0.001, + 32 + ).applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2)); const material = dotMeshMaterial; const mesh = new THREE.Mesh(geometry, material); @@ -846,7 +1010,8 @@ class Biolumi { makeBoxMesh: _makeBoxMesh, }; } - }); + } + ); } unmount() { @@ -858,11 +1023,18 @@ const fonts = `-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, const monospaceFonts = `Consolas, "Liberation Mono", Menlo, Courier, monospace`; const fontWeight = 300; const fontStyle = 'normal'; -const transparentImgUrl = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; +const transparentImgUrl = + 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; const _relativeWsUrl = s => { const l = window.location; - return ((l.protocol === 'https:') ? 'wss://' : 'ws://') + l.host + l.pathname + (!/\/$/.test(l.pathname) ? '/' : '') + s; + return ( + (l.protocol === 'https:' ? 'wss://' : 'ws://') + + l.host + + l.pathname + + (!/\/$/.test(l.pathname) ? '/' : '') + + s + ); }; const _debounce = fn => { let running = false; diff --git a/core/engines/biolumi/server.js b/core/engines/biolumi/server.js index f7acefef4..bfa2981f2 100644 --- a/core/engines/biolumi/server.js +++ b/core/engines/biolumi/server.js @@ -9,9 +9,9 @@ class Biolumi { } mount() { - const {_archae: archae} = this; - const {port} = archae; - const {express, app, wss} = archae.getCore(); + const { _archae: archae } = this; + const { port } = archae; + const { express, app, wss } = archae.getCore(); let live = true; this._cleanup = () => { @@ -23,21 +23,19 @@ class Biolumi { app, wss, port, - }) - .then(({ - type, - cleanup, - })=> { - if (live) { - if (type === 'internal') { - console.warn('warning: Could not start Chrome. Using *slow* local rendering; VR clients will experience hitching. To fix this, install Chrome.'); - } - - this._cleanup = cleanup; - } else { - cleanup(); + }).then(({ type, cleanup }) => { + if (live) { + if (type === 'internal') { + console.warn( + 'warning: Could not start Chrome. Using *slow* local rendering; VR clients will experience hitching. To fix this, install Chrome.' + ); } - }); + + this._cleanup = cleanup; + } else { + cleanup(); + } + }); } unmount() { diff --git a/core/engines/bootstrap/client.js b/core/engines/bootstrap/client.js index 36abb8fc2..7a636199d 100644 --- a/core/engines/bootstrap/client.js +++ b/core/engines/bootstrap/client.js @@ -4,7 +4,7 @@ class Bootstrap { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; let live = true; this._cleanup = () => { @@ -12,20 +12,19 @@ class Bootstrap { }; const initialUrl = document.location.href; - const initialPath = document.location.protocol + '//' + document.location.host + document.location.pathname; - - return archae.requestPlugins([ - '/core/utils/js-utils', - '/core/utils/network-utils', - ]) - .then(([ - jsUtils, - networkUtils, - ]) => { + const initialPath = + document.location.protocol + + '//' + + document.location.host + + document.location.pathname; + + return archae + .requestPlugins(['/core/utils/js-utils', '/core/utils/network-utils']) + .then(([jsUtils, networkUtils]) => { if (live) { - const {events} = jsUtils; - const {EventEmitter} = events; - const {AutoWs} = networkUtils; + const { events } = jsUtils; + const { EventEmitter } = events; + const { AutoWs } = networkUtils; let connectionState = null; const connection = (() => { @@ -48,7 +47,7 @@ class Bootstrap { } getWorldTime() { - const {startTime} = this; + const { startTime } = this; const now = Date.now(); const worldTime = now - startTime; return worldTime; @@ -119,7 +118,13 @@ class Bootstrap { const _relativeWsUrl = s => { const l = window.location; - return ((l.protocol === 'https:') ? 'wss://' : 'ws://') + l.host + l.pathname + (!/\/$/.test(l.pathname) ? '/' : '') + s; + return ( + (l.protocol === 'https:' ? 'wss://' : 'ws://') + + l.host + + l.pathname + + (!/\/$/.test(l.pathname) ? '/' : '') + + s + ); }; const _getQueryVariable = (url, variable) => { const match = url.match(/\?(.+)$/); diff --git a/core/engines/bootstrap/server.js b/core/engines/bootstrap/server.js index 488a0b64c..32a3c21bd 100644 --- a/core/engines/bootstrap/server.js +++ b/core/engines/bootstrap/server.js @@ -14,17 +14,10 @@ class Bootstrap { } mount() { - const {_archae: archae} = this; - const {app, wss, dirname, dataDirectory} = archae.getCore(); + const { _archae: archae } = this; + const { app, wss, dirname, dataDirectory } = archae.getCore(); const { - metadata: { - site: { - url: siteUrl, - }, - server: { - url: serverUrl, - }, - }, + metadata: { site: { url: siteUrl }, server: { url: serverUrl } }, } = archae; let live = true; @@ -32,154 +25,165 @@ class Bootstrap { live = false; }; - return archae.requestPlugins([ - '/core/engines/config', - ]) - .then(([ - config, - ]) => { - if (live) { - const _parseUrlSpec = url => { - const match = url.match(/^(?:([^:]+):\/\/)([^:]+)(?::([0-9]*?))?$/); - return match && { + return archae.requestPlugins(['/core/engines/config']).then(([config]) => { + if (live) { + const _parseUrlSpec = url => { + const match = url.match(/^(?:([^:]+):\/\/)([^:]+)(?::([0-9]*?))?$/); + return ( + match && { protocol: match[1], host: match[2], port: match[3] ? parseInt(match[3], 10) : null, - }; - }; - const siteSpec = _parseUrlSpec(siteUrl); - const serverSpec = _parseUrlSpec(serverUrl); - - const cleanups = []; - this._cleanup = () => { - for (let i = 0; i < cleanups.length; i++) { - const cleanup = cleanups[i]; - cleanup(); } - }; + ); + }; + const siteSpec = _parseUrlSpec(siteUrl); + const serverSpec = _parseUrlSpec(serverUrl); + + const cleanups = []; + this._cleanup = () => { + for (let i = 0; i < cleanups.length; i++) { + const cleanup = cleanups[i]; + cleanup(); + } + }; - const connections = []; - wss.on('connection', c => { - const {url} = c.upgradeReq; + const connections = []; + wss.on('connection', c => { + const { url } = c.upgradeReq; - let match; - if (match = url.match(/\/archae\/bootstrapWs$/)) { - const _sendInit = () => { - const e = connectionState; - const es = JSON.stringify(e); + let match; + if ((match = url.match(/\/archae\/bootstrapWs$/))) { + const _sendInit = () => { + const e = connectionState; + const es = JSON.stringify(e); - c.send(es); - }; - _sendInit(); + c.send(es); + }; + _sendInit(); - connections.push(c); + connections.push(c); - c.on('close', () => { - connections.splice(connections.indexOf(c), 1); - }); - } - }); - cleanups.push(() => { - for (let i = 0; i < connections.length; i++) { - const connection = connections[i]; - connection.close(); - } - }); - const _broadcastUpdate = () => { - const e = connectionState; - const es = JSON.stringify(e); - - for (let i = 0; i < connections.length; i++) { - const connection = connections[i]; - connection.send(es); - } - }; - - const connectionState = { - protocol: '', - address: '', - port: 0, - state: 'disconnected', - }; - - const _announceServer = () => new Promise((accept, reject) => { - publicIp.v4().then(ip => { - const options = { - method: 'POST', - host: siteSpec.host, - port: siteSpec.port, - path: '/prsnt/announce', - headers: { - 'Content-Type': 'application/json', - }, - rejectUnauthorized: siteSpec.host !== '127.0.0.1', - }; - const req = (siteSpec.protocol === 'http' ? http : https).request(options); - const configJson = config.getConfig(); - const {name, visibility} = configJson; - const {protocol, port} = serverSpec; - const address = ip; - req.end(JSON.stringify({ - name: name, - protocol: protocol, - address: address, - port: port, - users: [], // XXX announce the real users from the hub engine - visibility: visibility, - })); - - req.on('response', res => { - res.resume(); - - res.on('end', () => { - const {statusCode} = res; - - if (statusCode >= 200 && statusCode < 300) { - connectionState.state = visibility === 'public' ? 'connected' : 'private'; - _broadcastUpdate(); - - accept(); - } else if (statusCode === 502) { - connectionState.state = 'firewalled'; - _broadcastUpdate(); - - accept(); - } else { - connectionState.state = 'disconnected'; - _broadcastUpdate(); - - const err = new Error('server announce returned error status code: ' + statusCode); - err.code = 'EHTTP'; - err.statusCode = statusCode; + c.on('close', () => { + connections.splice(connections.indexOf(c), 1); + }); + } + }); + cleanups.push(() => { + for (let i = 0; i < connections.length; i++) { + const connection = connections[i]; + connection.close(); + } + }); + const _broadcastUpdate = () => { + const e = connectionState; + const es = JSON.stringify(e); + + for (let i = 0; i < connections.length; i++) { + const connection = connections[i]; + connection.send(es); + } + }; + + const connectionState = { + protocol: '', + address: '', + port: 0, + state: 'disconnected', + }; + + const _announceServer = () => + new Promise((accept, reject) => { + publicIp.v4().then( + ip => { + const options = { + method: 'POST', + host: siteSpec.host, + port: siteSpec.port, + path: '/prsnt/announce', + headers: { + 'Content-Type': 'application/json', + }, + rejectUnauthorized: siteSpec.host !== '127.0.0.1', + }; + const req = (siteSpec.protocol === 'http' + ? http + : https + ).request(options); + const configJson = config.getConfig(); + const { name, visibility } = configJson; + const { protocol, port } = serverSpec; + const address = ip; + req.end( + JSON.stringify({ + name: name, + protocol: protocol, + address: address, + port: port, + users: [], // XXX announce the real users from the hub engine + visibility: visibility, + }) + ); + + req.on('response', res => { + res.resume(); + + res.on('end', () => { + const { statusCode } = res; + + if (statusCode >= 200 && statusCode < 300) { + connectionState.state = + visibility === 'public' ? 'connected' : 'private'; + _broadcastUpdate(); + + accept(); + } else if (statusCode === 502) { + connectionState.state = 'firewalled'; + _broadcastUpdate(); + + accept(); + } else { + connectionState.state = 'disconnected'; + _broadcastUpdate(); + + const err = new Error( + 'server announce returned error status code: ' + + statusCode + ); + err.code = 'EHTTP'; + err.statusCode = statusCode; + err.options = options; + reject(err); + } + }); + res.on('error', err => { err.options = options; + reject(err); - } + }); }); - res.on('error', err => { + req.on('error', err => { err.options = options; reject(err); }); - }); - req.on('error', err => { - err.options = options; + connectionState.protocol = protocol; + connectionState.address = address; + connectionState.port = port; + connectionState.state = 'connecting'; + _broadcastUpdate(); + }, + err => { reject(err); - }); - - connectionState.protocol = protocol; - connectionState.address = address; - connectionState.port = port; - connectionState.state = 'connecting'; - _broadcastUpdate(); - }, err => { - reject(err); - }); + } + ); }); - const _tryServerAnnounce = () => new Promise((accept, reject) => { + const _tryServerAnnounce = () => + new Promise((accept, reject) => { const configJson = config.getConfig(); - const {visibility} = configJson; + const { visibility } = configJson; _announceServer() .then(() => { @@ -192,35 +196,34 @@ class Bootstrap { }); }); - const _queueServerAnnounce = _debounce(next => { - _tryServerAnnounce() - .then(ok => { - if (ok) { - next(); - } else { - setTimeout(() => { - _queueServerAnnounce(); - - next(); - }, SERVER_ANNOUNCE_RETRY_INTERVAL); - } - }); + const _queueServerAnnounce = _debounce(next => { + _tryServerAnnounce().then(ok => { + if (ok) { + next(); + } else { + setTimeout(() => { + _queueServerAnnounce(); + + next(); + }, SERVER_ANNOUNCE_RETRY_INTERVAL); + } }); + }); - if (siteSpec) { - _queueServerAnnounce(); + if (siteSpec) { + _queueServerAnnounce(); - const serverAnnounceInterval = setInterval(() => { - _queueServerAnnounce(); - }, SERVER_ANNOUNCE_INTERVAL); + const serverAnnounceInterval = setInterval(() => { + _queueServerAnnounce(); + }, SERVER_ANNOUNCE_INTERVAL); - cleanups.push(() => { - clearInterval(serverAnnounceInterval); - clearInterval(serverIconAnnounceInterval); - }); - } + cleanups.push(() => { + clearInterval(serverAnnounceInterval); + clearInterval(serverIconAnnounceInterval); + }); } - }); + } + }); } unmount() { diff --git a/core/engines/config/client.js b/core/engines/config/client.js index ddcc50ca3..a8bc3d25c 100644 --- a/core/engines/config/client.js +++ b/core/engines/config/client.js @@ -6,13 +6,11 @@ import { WORLD_WIDTH, WORLD_HEIGHT, WORLD_DEPTH, - STATS_WIDTH, STATS_HEIGHT, STATS_WORLD_WIDTH, STATS_WORLD_HEIGHT, STATS_WORLD_DEPTH, - STATS_REFRESH_RATE, } from './lib/constants/config'; import configUtils from './lib/utils/config'; @@ -33,16 +31,9 @@ class Config { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; const { - metadata: { - server: { - url: serverUrl, - }, - vrid: { - url: vridUrl, - }, - }, + metadata: { server: { url: serverUrl }, vrid: { url: vridUrl } }, } = archae; const cleanups = []; @@ -58,32 +49,37 @@ class Config { live = false; }); - const _requestGetBrowserConfig = () => new Promise((accept, reject) => { - const configString = localStorage.getItem('config'); - const config = configString ? JSON.parse(configString) : DEFAULT_BROWSER_CONFIG; - - accept(config); - }); - const _requestSetBrowserConfig = config => new Promise((accept, reject) => { - const configString = JSON.stringify(config); - localStorage.setItem('config', configString); - - accept(); - }); - const _requestGetServerConfig = () => fetch('archae/config/config.json', { - credentials: 'include', - }) - .then(res => res.json()); - const _requestSetServerConfig = config => fetch('archae/config/config.json', { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(config), - credentials: 'include', - }) - .then(res => res.blob()) - .then(() => {}) + const _requestGetBrowserConfig = () => + new Promise((accept, reject) => { + const configString = localStorage.getItem('config'); + const config = configString + ? JSON.parse(configString) + : DEFAULT_BROWSER_CONFIG; + + accept(config); + }); + const _requestSetBrowserConfig = config => + new Promise((accept, reject) => { + const configString = JSON.stringify(config); + localStorage.setItem('config', configString); + + accept(); + }); + const _requestGetServerConfig = () => + fetch('archae/config/config.json', { + credentials: 'include', + }).then(res => res.json()); + const _requestSetServerConfig = config => + fetch('archae/config/config.json', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(config), + credentials: 'include', + }) + .then(res => res.blob()) + .then(() => {}); return Promise.all([ archae.requestPlugins([ @@ -98,589 +94,609 @@ class Config { ]), _requestGetBrowserConfig(), _requestGetServerConfig(), - ]).then(([ - [ - input, - three, - webvr, - keyboard, - biolumi, - resource, - rend, - jsUtils, - ], - browserConfigSpec, - serverConfigSpec, - ]) => { - if (live) { - const {THREE, scene} = three; - const {events} = jsUtils; - const {EventEmitter} = events; - const {sfx} = resource; - - const _decomposeObjectMatrixWorld = object => { - const position = new THREE.Vector3(); - const rotation = new THREE.Quaternion(); - const scale = new THREE.Vector3(); - object.matrixWorld.decompose(position, rotation, scale); - return {position, rotation, scale}; - }; - - const transparentImg = biolumi.getTransparentImg(); - const transparentMaterial = biolumi.getTransparentMaterial(); - - const mainFontSpec = { - fonts: biolumi.getFonts(), - fontSize: 30, - lineHeight: 1.4, - fontWeight: biolumi.getFontWeight(), - fontStyle: biolumi.getFontStyle(), - }; - - const configState = { - resolutionValue: browserConfigSpec.resolution, - voiceChatCheckboxValue: browserConfigSpec.voiceChat, - statsCheckboxValue: browserConfigSpec.stats, - visibilityValue: serverConfigSpec.visibility, - nameValue: serverConfigSpec.name, - passwordValue: serverConfigSpec.password, - maxPlayersValue: serverConfigSpec.maxPlayers, - keyboardFocusState: null, - }; - const statsState = { - frame: 0, - }; - - const _resJson = res => { - if (res.status >= 200 && res.status < 300) { - return res.json(); - } else { - return Promise.reject({ - status: res.status, - stack: 'API returned invalid status code: ' + res.status, - }); - } - }; - const _resBlob = res => { - if (res.status >= 200 && res.status < 300) { - return res.blob(); - } else { - return Promise.reject({ - status: res.status, - stack: 'API returned invalid status code: ' + res.status, - }); - } - }; - const _requestRegisterRecentServer = name => fetch(`${vridUrl}/id/api/cookie/servers`, { - credentials: 'include', - }) - .then(_resJson) - .catch(err => { - console.warn(err); - - return Promise.resolve(); - }) - .then(servers => { - if (!Array.isArray(servers)) { - servers = []; + ]).then( + ( + [ + [input, three, webvr, keyboard, biolumi, resource, rend, jsUtils], + browserConfigSpec, + serverConfigSpec, + ] + ) => { + if (live) { + const { THREE, scene } = three; + const { events } = jsUtils; + const { EventEmitter } = events; + const { sfx } = resource; + + const _decomposeObjectMatrixWorld = object => { + const position = new THREE.Vector3(); + const rotation = new THREE.Quaternion(); + const scale = new THREE.Vector3(); + object.matrixWorld.decompose(position, rotation, scale); + return { position, rotation, scale }; + }; + + const transparentImg = biolumi.getTransparentImg(); + const transparentMaterial = biolumi.getTransparentMaterial(); + + const mainFontSpec = { + fonts: biolumi.getFonts(), + fontSize: 30, + lineHeight: 1.4, + fontWeight: biolumi.getFontWeight(), + fontStyle: biolumi.getFontStyle(), + }; + + const configState = { + resolutionValue: browserConfigSpec.resolution, + voiceChatCheckboxValue: browserConfigSpec.voiceChat, + statsCheckboxValue: browserConfigSpec.stats, + visibilityValue: serverConfigSpec.visibility, + nameValue: serverConfigSpec.name, + passwordValue: serverConfigSpec.password, + maxPlayersValue: serverConfigSpec.maxPlayers, + keyboardFocusState: null, + }; + const statsState = { + frame: 0, + }; + + const _resJson = res => { + if (res.status >= 200 && res.status < 300) { + return res.json(); + } else { + return Promise.reject({ + status: res.status, + stack: 'API returned invalid status code: ' + res.status, + }); } - let server = servers.find(server => server.url === serverUrl); - if (!server) { - server = { - url: serverUrl, - name: null, - timestamp: 0, - }; - servers.push(server); + }; + const _resBlob = res => { + if (res.status >= 200 && res.status < 300) { + return res.blob(); + } else { + return Promise.reject({ + status: res.status, + stack: 'API returned invalid status code: ' + res.status, + }); } - server.name = name; - server.timestamp = Date.now(); - servers.sort((a, b) => b.timestamp - a.timestamp); - servers.length = Math.min(servers.length, NUM_SERVERS); - - return fetch(`${vridUrl}/id/api/cookie/servers`, { - method: 'POST', - headers: (() => { - const headers = new Headers(); - headers.append('Content-Type', 'application/json'); - return headers; - })(), - body: JSON.stringify(servers), + }; + const _requestRegisterRecentServer = name => + fetch(`${vridUrl}/id/api/cookie/servers`, { credentials: 'include', }) - .then(_resBlob); - }); - const _applyName = name => { - rend.setStatus('name', name); + .then(_resJson) + .catch(err => { + console.warn(err); + + return Promise.resolve(); + }) + .then(servers => { + if (!Array.isArray(servers)) { + servers = []; + } + let server = servers.find(server => server.url === serverUrl); + if (!server) { + server = { + url: serverUrl, + name: null, + timestamp: 0, + }; + servers.push(server); + } + server.name = name; + server.timestamp = Date.now(); + servers.sort((a, b) => b.timestamp - a.timestamp); + servers.length = Math.min(servers.length, NUM_SERVERS); + + return fetch(`${vridUrl}/id/api/cookie/servers`, { + method: 'POST', + headers: (() => { + const headers = new Headers(); + headers.append('Content-Type', 'application/json'); + return headers; + })(), + body: JSON.stringify(servers), + credentials: 'include', + }).then(_resBlob); + }); + const _applyName = name => { + rend.setStatus('name', name); - _requestRegisterRecentServer(name) - .catch(err => { + _requestRegisterRecentServer(name).catch(err => { console.warn(err); }); - }; - _applyName(serverConfigSpec.name); - - const _saveBrowserConfig = () => { - const config = configApi.getBrowserConfig(); - _requestSetBrowserConfig(config); - }; - const _saveServerConfig = configUtils.debounce(next => { - const config = configApi.getServerConfig(); - _requestSetServerConfig(config) - .then(() => { - next(); - }) - .catch(err => { - console.warn(err); - - next(); - }); - }); - - const stats = new Stats(); - stats.render = () => {}; // overridden below - const statsDom = stats.dom.childNodes[0]; + }; + _applyName(serverConfigSpec.name); - const configMesh = (() => { - const configUi = biolumi.makeUi({ - width: WIDTH, - height: HEIGHT, - }); - const mesh = configUi.makePage(({ - config: { - resolutionValue, - voiceChatCheckboxValue, - statsCheckboxValue, - visibilityValue, - nameValue, - passwordValue, - maxPlayersValue, - keyboardFocusState, - flags, - }, - }) => { - const focusSpec = (() => { - if (keyboardFocusState) { - const {type} = keyboardFocusState; - - if (type === 'config:name') { - return { - type: 'name', - }; - } else if (type === 'config:password') { - return { - type: 'password', - }; - } else if (type === 'config:visibility') { - return { - type: 'visibility', - }; - } else { - return null; - } - } else { - return null; - } - })(); - const inputValue = keyboardFocusState ? keyboardFocusState.inputValue : 0; - - return { - type: 'html', - src: configRenderer.getConfigPageSrc({ - resolutionValue, - voiceChatCheckboxValue, - statsCheckboxValue, - visibilityValue, - nameValue, - passwordValue, - maxPlayersValue, - inputValue, - focusSpec, - flags, - }), - x: 0, - y: 0, - w: WIDTH, - h: HEIGHT, - }; - }, { - type: 'config', - state: { - config: configState, - }, - worldWidth: WORLD_WIDTH, - worldHeight: WORLD_HEIGHT, - isEnabled: () => rend.isOpen(), + const _saveBrowserConfig = () => { + const config = configApi.getBrowserConfig(); + _requestSetBrowserConfig(config); + }; + const _saveServerConfig = configUtils.debounce(next => { + const config = configApi.getServerConfig(); + _requestSetServerConfig(config) + .then(() => { + next(); + }) + .catch(err => { + console.warn(err); + + next(); + }); }); - mesh.visible = false; - // mesh.receiveShadow = true; - - const {page} = mesh; - rend.addPage(page); - page.initialUpdate(); - cleanups.push(() => { - rend.removePage(page); - }); + const stats = new Stats(); + stats.render = () => {}; // overridden below + const statsDom = stats.dom.childNodes[0]; - return mesh; - })(); - rend.registerMenuMesh('configMesh', configMesh); - configMesh.updateMatrixWorld(); - - const statsMesh = (() => { - const object = new THREE.Object3D(); - object.position.x = -(2 / 2) + (STATS_WORLD_WIDTH / 2); - object.position.y = -((2 / 1.5) / 2) + (STATS_WORLD_HEIGHT / 2); - object.visible = configState.statsCheckboxValue; - - const planeMesh = (() => { - const statsUi = biolumi.makeUi({ - width: STATS_WIDTH, - height: STATS_HEIGHT, + const configMesh = (() => { + const configUi = biolumi.makeUi({ + width: WIDTH, + height: HEIGHT, }); - const mesh = statsUi.makePage(({ - config: { - statsCheckboxValue, - }, - stats: { - frame, - }, - }) => ({ - type: 'image', - img: statsDom, - x: 0, - y: 0, - w: 500, - h: 500 * (48 / 80), - }), { - type: 'stats', - state: { + const mesh = configUi.makePage( + ({ config: { - statsCheckboxValue: configState.statsCheckboxValue, + resolutionValue, + voiceChatCheckboxValue, + statsCheckboxValue, + visibilityValue, + nameValue, + passwordValue, + maxPlayersValue, + keyboardFocusState, + flags, }, - stats: statsState, + }) => { + const focusSpec = (() => { + if (keyboardFocusState) { + const { type } = keyboardFocusState; + + if (type === 'config:name') { + return { + type: 'name', + }; + } else if (type === 'config:password') { + return { + type: 'password', + }; + } else if (type === 'config:visibility') { + return { + type: 'visibility', + }; + } else { + return null; + } + } else { + return null; + } + })(); + const inputValue = keyboardFocusState + ? keyboardFocusState.inputValue + : 0; + + return { + type: 'html', + src: configRenderer.getConfigPageSrc({ + resolutionValue, + voiceChatCheckboxValue, + statsCheckboxValue, + visibilityValue, + nameValue, + passwordValue, + maxPlayersValue, + inputValue, + focusSpec, + flags, + }), + x: 0, + y: 0, + w: WIDTH, + h: HEIGHT, + }; }, - worldWidth: STATS_WORLD_WIDTH, - worldHeight: STATS_WORLD_HEIGHT, - isEnabled: () => rend.isOpen(), - }); - mesh.position.z = 0.002; + { + type: 'config', + state: { + config: configState, + }, + worldWidth: WORLD_WIDTH, + worldHeight: WORLD_HEIGHT, + isEnabled: () => rend.isOpen(), + } + ); + mesh.visible = false; // mesh.receiveShadow = true; - /* const {page} = mesh; + const { page } = mesh; rend.addPage(page); + page.initialUpdate(); cleanups.push(() => { rend.removePage(page); - }); */ + }); return mesh; })(); - object.add(planeMesh); - object.planeMesh = planeMesh; + rend.registerMenuMesh('configMesh', configMesh); + configMesh.updateMatrixWorld(); + + const statsMesh = (() => { + const object = new THREE.Object3D(); + object.position.x = -(2 / 2) + STATS_WORLD_WIDTH / 2; + object.position.y = -(2 / 1.5 / 2) + STATS_WORLD_HEIGHT / 2; + object.visible = configState.statsCheckboxValue; + + const planeMesh = (() => { + const statsUi = biolumi.makeUi({ + width: STATS_WIDTH, + height: STATS_HEIGHT, + }); + const mesh = statsUi.makePage( + ({ config: { statsCheckboxValue }, stats: { frame } }) => ({ + type: 'image', + img: statsDom, + x: 0, + y: 0, + w: 500, + h: 500 * (48 / 80), + }), + { + type: 'stats', + state: { + config: { + statsCheckboxValue: configState.statsCheckboxValue, + }, + stats: statsState, + }, + worldWidth: STATS_WORLD_WIDTH, + worldHeight: STATS_WORLD_HEIGHT, + isEnabled: () => rend.isOpen(), + } + ); + mesh.position.z = 0.002; + // mesh.receiveShadow = true; - return object; - })(); - rend.registerMenuMesh('statsMesh', statsMesh); - statsMesh.updateMatrixWorld(); + /* const {page} = mesh; + rend.addPage(page); - stats.render = () => { - const {frame: oldFrame} = statsState; - const newFrame = Math.floor(Date.now() / STATS_REFRESH_RATE); + cleanups.push(() => { + rend.removePage(page); + }); */ - if (newFrame !== oldFrame) { - statsState.frame = newFrame; + return mesh; + })(); + object.add(planeMesh); + object.planeMesh = planeMesh; - const {planeMesh} = statsMesh; - const {page} = planeMesh; - page.update(); - } - }; + return object; + })(); + rend.registerMenuMesh('statsMesh', statsMesh); + statsMesh.updateMatrixWorld(); - const _updatePages = () => { - configMesh.page.update(); - }; + stats.render = () => { + const { frame: oldFrame } = statsState; + const newFrame = Math.floor(Date.now() / STATS_REFRESH_RATE); - const _trigger = e => { - const {side} = e; + if (newFrame !== oldFrame) { + statsState.frame = newFrame; - const _clickMenu = () => { - const hoverState = rend.getHoverState(side); - const {anchor} = hoverState; - const onclick = (anchor && anchor.onclick) || ''; + const { planeMesh } = statsMesh; + const { page } = planeMesh; + page.update(); + } + }; - let match; - if (onclick === 'config:resolution') { - const {value} = hoverState; + const _updatePages = () => { + configMesh.page.update(); + }; - configState.resolutionValue = value; + const _trigger = e => { + const { side } = e; - _saveBrowserConfig(); - configApi.updateBrowserConfig(); + const _clickMenu = () => { + const hoverState = rend.getHoverState(side); + const { anchor } = hoverState; + const onclick = (anchor && anchor.onclick) || ''; - _updatePages(); + let match; + if (onclick === 'config:resolution') { + const { value } = hoverState; - return true; - } else if (onclick === 'config:voiceChat') { - const {voiceChatCheckboxValue} = configState; + configState.resolutionValue = value; - configState.voiceChatCheckboxValue = !voiceChatCheckboxValue; + _saveBrowserConfig(); + configApi.updateBrowserConfig(); - _saveBrowserConfig(); - configApi.updateBrowserConfig(); + _updatePages(); - _updatePages(); + return true; + } else if (onclick === 'config:voiceChat') { + const { voiceChatCheckboxValue } = configState; - return true; - } else if (onclick === 'config:stats') { - const {statsCheckboxValue: oldStatsCheckboxValue} = configState; + configState.voiceChatCheckboxValue = !voiceChatCheckboxValue; - const newStatsCheckboxValue = !oldStatsCheckboxValue; - configState.statsCheckboxValue = newStatsCheckboxValue; - statsMesh.visible = newStatsCheckboxValue; + _saveBrowserConfig(); + configApi.updateBrowserConfig(); - _saveBrowserConfig(); - configApi.updateBrowserConfig(); + _updatePages(); - _updatePages(); + return true; + } else if (onclick === 'config:stats') { + const { + statsCheckboxValue: oldStatsCheckboxValue, + } = configState; - return true; - } else if (match = onclick.match(/^config:visibility(?::(public|private))?$/)) { - const visibilityValue = match[1] || null; + const newStatsCheckboxValue = !oldStatsCheckboxValue; + configState.statsCheckboxValue = newStatsCheckboxValue; + statsMesh.visible = newStatsCheckboxValue; - if (visibilityValue === null) { - const keyboardFocusState = keyboard.fakeFocus({ - type: 'config:visibility', + _saveBrowserConfig(); + configApi.updateBrowserConfig(); + + _updatePages(); + + return true; + } else if ( + (match = onclick.match( + /^config:visibility(?::(public|private))?$/ + )) + ) { + const visibilityValue = match[1] || null; + + if (visibilityValue === null) { + const keyboardFocusState = keyboard.fakeFocus({ + type: 'config:visibility', + }); + configState.keyboardFocusState = keyboardFocusState; + + keyboardFocusState.on('blur', () => { + configState.keyboardFocusState = null; + + _updatePages(); + }); + } else { + configState.visibilityValue = visibilityValue; + + _saveServerConfig(); + configApi.updateBrowserConfig(); + + keyboard.tryBlur(); + } + + _updatePages(); + + return true; + } else if (onclick === 'config:name') { + const { nameValue: inputText } = configState; + const { value, target: page } = hoverState; + const { layer: { measures } } = page; + const valuePx = value * (640 - (150 + 30 * 2 + 30)); + const { index, px } = biolumi.getTextPropertiesFromCoord( + measures['config:name'], + inputText, + valuePx + ); + const { hmd: hmdStatus } = webvr.getStatus(); + const { + worldPosition: hmdPosition, + worldRotation: hmdRotation, + } = hmdStatus; + const keyboardFocusState = keyboard.focus({ + type: 'config:name', + position: hmdPosition, + rotation: hmdRotation, + inputText: inputText, + inputIndex: index, + inputValue: px, + page: page, }); - configState.keyboardFocusState = keyboardFocusState; + keyboardFocusState.on('update', () => { + const { inputText: keyboardInputText } = keyboardFocusState; + const { nameValue: nameInputText } = configState; + if (keyboardInputText !== nameInputText) { + const newName = keyboardInputText; + configState.nameValue = newName; + + _saveServerConfig(); + configApi.updateBrowserConfig(); + + _applyName(newName); + } + + _updatePages(); + }); keyboardFocusState.on('blur', () => { configState.keyboardFocusState = null; _updatePages(); }); - } else { - configState.visibilityValue = visibilityValue; - _saveServerConfig(); - configApi.updateBrowserConfig(); + configState.keyboardFocusState = keyboardFocusState; - keyboard.tryBlur(); - } + _updatePages(); - _updatePages(); + return true; + } else if (onclick === 'config:password') { + const { passwordValue: inputText } = configState; + const { value, target: page } = hoverState; + const { layer: { measures } } = page; + const valuePx = value * (640 - (150 + 30 * 2 + 30)); + const { index, px } = biolumi.getTextPropertiesFromCoord( + measures['config:password'], + inputText, + valuePx + ); + const { hmd: hmdStatus } = webvr.getStatus(); + const { + worldPosition: hmdPosition, + worldRotation: hmdRotation, + } = hmdStatus; + const keyboardFocusState = keyboard.focus({ + type: 'config:password', + position: hmdPosition, + rotation: hmdRotation, + inputText: inputText, + inputIndex: index, + inputValue: px, + page: page, + }); + keyboardFocusState.on('update', () => { + const { inputText: keyboardInputText } = keyboardFocusState; + const { passwordValue: passwordInputText } = configState; - return true; - } else if (onclick === 'config:name') { - const {nameValue: inputText} = configState; - const {value, target: page} = hoverState; - const {layer: {measures}} = page; - const valuePx = value * (640 - (150 + (30 * 2) + 30)); - const {index, px} = biolumi.getTextPropertiesFromCoord(measures['config:name'], inputText, valuePx); - const {hmd: hmdStatus} = webvr.getStatus(); - const {worldPosition: hmdPosition, worldRotation: hmdRotation} = hmdStatus; - const keyboardFocusState = keyboard.focus({ - type: 'config:name', - position: hmdPosition, - rotation: hmdRotation, - inputText: inputText, - inputIndex: index, - inputValue: px, - page: page, - }); - keyboardFocusState.on('update', () => { - const {inputText: keyboardInputText} = keyboardFocusState; - const {nameValue: nameInputText} = configState; + if (keyboardInputText !== passwordInputText) { + configState.passwordValue = keyboardInputText; - if (keyboardInputText !== nameInputText) { - const newName = keyboardInputText; - configState.nameValue = newName; + _saveServerConfig(); + configApi.updateBrowserConfig(); + } - _saveServerConfig(); - configApi.updateBrowserConfig(); + _updatePages(); + }); + keyboardFocusState.on('blur', () => { + configState.keyboardFocusState = null; - _applyName(newName); - } + _updatePages(); + }); - _updatePages(); - }); - keyboardFocusState.on('blur', () => { - configState.keyboardFocusState = null; + configState.keyboardFocusState = keyboardFocusState; _updatePages(); - }); - - configState.keyboardFocusState = keyboardFocusState; - _updatePages(); - - return true; - } else if (onclick === 'config:password') { - const {passwordValue: inputText} = configState; - const {value, target: page} = hoverState; - const {layer: {measures}} = page; - const valuePx = value * (640 - (150 + (30 * 2) + 30)); - const {index, px} = biolumi.getTextPropertiesFromCoord(measures['config:password'], inputText, valuePx); - const {hmd: hmdStatus} = webvr.getStatus(); - const {worldPosition: hmdPosition, worldRotation: hmdRotation} = hmdStatus; - const keyboardFocusState = keyboard.focus({ - type: 'config:password', - position: hmdPosition, - rotation: hmdRotation, - inputText: inputText, - inputIndex: index, - inputValue: px, - page: page, - }); - keyboardFocusState.on('update', () => { - const {inputText: keyboardInputText} = keyboardFocusState; - const {passwordValue: passwordInputText} = configState; + return true; + } else if (onclick === 'config:maxPlayers') { + const { value } = hoverState; - if (keyboardInputText !== passwordInputText) { - configState.passwordValue = keyboardInputText; + configState.maxPlayersValue = 1 + Math.round(value * (8 - 1)); - _saveServerConfig(); - configApi.updateBrowserConfig(); - } - - _updatePages(); - }); - keyboardFocusState.on('blur', () => { - configState.keyboardFocusState = null; + _saveServerConfig(); + configApi.updateBrowserConfig(); _updatePages(); - }); - configState.keyboardFocusState = keyboardFocusState; - - _updatePages(); - - return true; - } else if (onclick === 'config:maxPlayers') { - const {value} = hoverState; + return true; + } else { + return false; + } + }; + const _clickMenuBackground = () => { + const hoverState = rend.getHoverState(side); + const { target } = hoverState; - configState.maxPlayersValue = 1 + Math.round(value * (8 - 1)); + if (target && target.mesh && target.mesh.parent === configMesh) { + return true; + } else { + return false; + } + }; - _saveServerConfig(); - configApi.updateBrowserConfig(); + if (_clickMenu()) { + sfx.digi_select.trigger(); - _updatePages(); + e.stopImmediatePropagation(); + } else if (_clickMenuBackground()) { + sfx.digi_plink.trigger(); - return true; - } else { - return false; + e.stopImmediatePropagation(); } }; - const _clickMenuBackground = () => { - const hoverState = rend.getHoverState(side); - const {target} = hoverState; + input.on('trigger', _trigger, { + priority: 1, + }); - if (target && target.mesh && target.mesh.parent === configMesh) { - return true; - } else { - return false; + const _update = () => { + if (configState.statsCheckboxValue) { + stats.render(); + } + }; + rend.on('update', _update); + const _updateStart = () => { + if (configState.statsCheckboxValue) { + stats.begin(); } }; + rend.on('updateStart', _updateStart); + const _updateEnd = () => { + if (configState.statsCheckboxValue) { + stats.end(); + } + }; + rend.on('updateEnd', _updateEnd); - if (_clickMenu()) { - sfx.digi_select.trigger(); + cleanups.push(() => { + input.removeListener('trigger', trigger); + input.removeListener('keydown', keydown); + input.removeListener('keyboarddown', keyboarddown); - e.stopImmediatePropagation(); - } else if (_clickMenuBackground()) { - sfx.digi_plink.trigger(); + rend.removeListener('update', _update); + rend.removeListener('updateStart', _updateStart); + rend.removeListener('updateEnd', _updateEnd); + }); - e.stopImmediatePropagation(); - } - }; - input.on('trigger', _trigger, { - priority: 1, - }); - - const _update = () => { - if (configState.statsCheckboxValue) { - stats.render(); - } - }; - rend.on('update', _update); - const _updateStart = () => { - if (configState.statsCheckboxValue) { - stats.begin(); - } - }; - rend.on('updateStart', _updateStart); - const _updateEnd = () => { - if (configState.statsCheckboxValue) { - stats.end(); - } - }; - rend.on('updateEnd', _updateEnd); - - cleanups.push(() => { - input.removeListener('trigger', trigger); - input.removeListener('keydown', keydown); - input.removeListener('keyboarddown', keyboarddown); - - rend.removeListener('update', _update); - rend.removeListener('updateStart', _updateStart); - rend.removeListener('updateEnd', _updateEnd); - }); - - class ConfigApi extends EventEmitter { - getBrowserConfig() { - return { - resolution: configState.resolutionValue, - voiceChat: configState.voiceChatCheckboxValue, - stats: configState.statsCheckboxValue, - }; - } + class ConfigApi extends EventEmitter { + getBrowserConfig() { + return { + resolution: configState.resolutionValue, + voiceChat: configState.voiceChatCheckboxValue, + stats: configState.statsCheckboxValue, + }; + } - setBrowserConfig(newConfig) { - ('resolution' in newConfig) && (configState.resolutionValue = newConfig.resolution); - ('voiceChat' in newConfig) && (configState.voiceChatCheckboxValue = newConfig.voiceChat); - ('stats' in newConfig) && (configState.statsCheckboxValue = newConfig.stats); + setBrowserConfig(newConfig) { + 'resolution' in newConfig && + (configState.resolutionValue = newConfig.resolution); + 'voiceChat' in newConfig && + (configState.voiceChatCheckboxValue = newConfig.voiceChat); + 'stats' in newConfig && + (configState.statsCheckboxValue = newConfig.stats); - _saveBrowserConfig(); - configApi.updateBrowserConfig(); + _saveBrowserConfig(); + configApi.updateBrowserConfig(); - _updatePages(); - } + _updatePages(); + } - getServerConfig() { - return { - visibility: configState.visibilityValue, - name: configState.nameValue, - password: configState.passwordValue, - maxPlayers: configState.maxPlayersValue, - }; - } + getServerConfig() { + return { + visibility: configState.visibilityValue, + name: configState.nameValue, + password: configState.passwordValue, + maxPlayers: configState.maxPlayersValue, + }; + } - setServerConfig(newConfig) { - ('name' in newConfig) && (configState.nameValue = newConfig.name); - ('password' in newConfig) && (configState.passwordValue = newConfig.password); - ('maxPlayers' in newConfig) && (configState.maxPlayersValue = newConfig.maxPlayers); + setServerConfig(newConfig) { + 'name' in newConfig && (configState.nameValue = newConfig.name); + 'password' in newConfig && + (configState.passwordValue = newConfig.password); + 'maxPlayers' in newConfig && + (configState.maxPlayersValue = newConfig.maxPlayers); - _saveServerConfig(); - configApi.updateServerConfig(); + _saveServerConfig(); + configApi.updateServerConfig(); - _updatePages(); - } + _updatePages(); + } - updateBrowserConfig() { - const browserConfig = this.getBrowserConfig(); - this.emit('browserConfig', browserConfig); - } + updateBrowserConfig() { + const browserConfig = this.getBrowserConfig(); + this.emit('browserConfig', browserConfig); + } - updateServerConfig() { - const serverConfig = this.getServerConfig(); - this.emit('serverConfig', serverConfig); + updateServerConfig() { + const serverConfig = this.getServerConfig(); + this.emit('serverConfig', serverConfig); + } } - } - const configApi = new ConfigApi(); + const configApi = new ConfigApi(); - return configApi; + return configApi; + } } - }); + ); } unmount() { diff --git a/core/engines/config/server.js b/core/engines/config/server.js index 717634f12..fb450cc65 100644 --- a/core/engines/config/server.js +++ b/core/engines/config/server.js @@ -18,8 +18,8 @@ class Config { } mount() { - const {_archae: archae} = this; - const {app, dirname, dataDirectory} = archae.getCore(); + const { _archae: archae } = this; + const { app, dirname, dataDirectory } = archae.getCore(); let live = true; this._cleanup = () => { @@ -29,42 +29,42 @@ class Config { const worldPath = path.join(dirname, dataDirectory, 'world'); const worldConfigJsonPath = path.join(worldPath, 'config.json'); - const _requestFile = (p, defaultValue) => new Promise((accept, reject) => { - fs.readFile(p, 'utf8', (err, s) => { - if (!err) { - const j = JSON.parse(s); - accept(j); - } else if (err.code === 'ENOENT') { - const j = defaultValue; - accept(j); - } else { - reject(err); - } + const _requestFile = (p, defaultValue) => + new Promise((accept, reject) => { + fs.readFile(p, 'utf8', (err, s) => { + if (!err) { + const j = JSON.parse(s); + accept(j); + } else if (err.code === 'ENOENT') { + const j = defaultValue; + accept(j); + } else { + reject(err); + } + }); }); - }); - const _requestConfigJson = () => _requestFile(worldConfigJsonPath, DEFAULT_SERVER_CONFIG); - const _ensureWorldPath = () => new Promise((accept, reject) => { - const worldPath = path.join(dirname, dataDirectory, 'world'); - - mkdirp(worldPath, err => { - if (!err) { - accept(); - } else { - reject(err); - } + const _requestConfigJson = () => + _requestFile(worldConfigJsonPath, DEFAULT_SERVER_CONFIG); + const _ensureWorldPath = () => + new Promise((accept, reject) => { + const worldPath = path.join(dirname, dataDirectory, 'world'); + + mkdirp(worldPath, err => { + if (!err) { + accept(); + } else { + reject(err); + } + }); }); - }); return Promise.all([ _requestConfigJson(), _ensureWorldPath(), - ]) - .then(([ - configJson, - ensureWorldPathResult, - ]) => { - if (live) { - const _saveFile = (p, j) => new Promise((accept, reject) => { + ]).then(([configJson, ensureWorldPathResult]) => { + if (live) { + const _saveFile = (p, j) => + new Promise((accept, reject) => { fs.writeFile(p, JSON.stringify(j, null, 2), 'utf8', err => { if (!err) { accept(); @@ -74,59 +74,59 @@ class Config { }); }); - function serveConfigGet(req, res, next) { - res.json(configJson); - } - app.get('/archae/config/config.json', serveConfigGet); - function serveConfigSet(req, res, next) { - bodyParserJson(req, res, () => { - const {body: data} = req; - - const _respondInvalid = () => { - res.status(400); - res.send(); - }; - - if (typeof data === 'object' && data !== null) { - configJson = data; - - _saveFile(worldConfigJsonPath, configJson) - .then(() => { - res.send(); - }) - .catch(err => { - res.status(500); - res.send(err.stack); - }); - } else { - _respondInvalid(); - } - }); - } - app.put('/archae/config/config.json', serveConfigSet); - - this._cleanup = () => { - function removeMiddlewares(route, i, routes) { - if ( - route.handle.name === 'serveConfigGet' || - route.handle.name === 'serveConfigSet' - ) { - routes.splice(i, 1); - } - if (route.route) { - route.route.stack.forEach(removeMiddlewares); - } + function serveConfigGet(req, res, next) { + res.json(configJson); + } + app.get('/archae/config/config.json', serveConfigGet); + function serveConfigSet(req, res, next) { + bodyParserJson(req, res, () => { + const { body: data } = req; + + const _respondInvalid = () => { + res.status(400); + res.send(); + }; + + if (typeof data === 'object' && data !== null) { + configJson = data; + + _saveFile(worldConfigJsonPath, configJson) + .then(() => { + res.send(); + }) + .catch(err => { + res.status(500); + res.send(err.stack); + }); + } else { + _respondInvalid(); + } + }); + } + app.put('/archae/config/config.json', serveConfigSet); + + this._cleanup = () => { + function removeMiddlewares(route, i, routes) { + if ( + route.handle.name === 'serveConfigGet' || + route.handle.name === 'serveConfigSet' + ) { + routes.splice(i, 1); } - app._router.stack.forEach(removeMiddlewares); - }; + if (route.route) { + route.route.stack.forEach(removeMiddlewares); + } + } + app._router.stack.forEach(removeMiddlewares); + }; - const _getConfig = () => configJson; + const _getConfig = () => configJson; - return { - getConfig: _getConfig, - }; - } - }); + return { + getConfig: _getConfig, + }; + } + }); } unmount() { diff --git a/core/engines/cyborg/client.js b/core/engines/cyborg/client.js index 87d917b69..8b75d28ca 100644 --- a/core/engines/cyborg/client.js +++ b/core/engines/cyborg/client.js @@ -14,239 +14,290 @@ class Cyborg { } mount() { - const {_archae: archae} = this; - const {metadata: {server: {enabled: serverEnabled}}} = archae; + const { _archae: archae } = this; + const { metadata: { server: { enabled: serverEnabled } } } = archae; let live = true; this._cleanup = () => { live = false; }; - return archae.requestPlugins([ - '/core/engines/bootstrap', - '/core/engines/three', - '/core/engines/webvr', - '/core/engines/resource', - '/core/engines/biolumi', - '/core/engines/rend', - '/core/engines/multiplayer', - '/core/utils/js-utils', - '/core/utils/geometry-utils', - '/core/utils/sprite-utils', - '/core/utils/skin-utils', - ]) - .then(([ - bootstrap, - three, - webvr, - resource, - biolumi, - rend, - multiplayer, - jsUtils, - geometryUtils, - spriteUtils, - skinUtils, - ]) => { - if (live) { - const {THREE, scene, camera} = three; - const {models: {hmdModelMesh, controllerModelMesh}} = resource; - const {events} = jsUtils; - const {EventEmitter} = events; - - const zeroVector = new THREE.Vector3(); - const localSkinStatus = { - hmd: { - position: null, - rotation: null, - }, - gamepads: { - left: { + return archae + .requestPlugins([ + '/core/engines/bootstrap', + '/core/engines/three', + '/core/engines/webvr', + '/core/engines/resource', + '/core/engines/biolumi', + '/core/engines/rend', + '/core/engines/multiplayer', + '/core/utils/js-utils', + '/core/utils/geometry-utils', + '/core/utils/sprite-utils', + '/core/utils/skin-utils', + ]) + .then( + ( + [ + bootstrap, + three, + webvr, + resource, + biolumi, + rend, + multiplayer, + jsUtils, + geometryUtils, + spriteUtils, + skinUtils, + ] + ) => { + if (live) { + const { THREE, scene, camera } = three; + const { models: { hmdModelMesh, controllerModelMesh } } = resource; + const { events } = jsUtils; + const { EventEmitter } = events; + + const zeroVector = new THREE.Vector3(); + const localSkinStatus = { + hmd: { position: null, rotation: null, }, - right: { - position: null, - rotation: null, + gamepads: { + left: { + position: null, + rotation: null, + }, + right: { + position: null, + rotation: null, + }, }, - }, - }; - - const solidMaterial = new THREE.MeshPhongMaterial({ - color: 0x666666, - shininess: 0, - shading: THREE.FlatShading, - }); - - const BUTTON_COLOR = 0xFF4444; - const BUTTON_COLOR_HIGHLIGHT = 0xffbb33; - - const RAY_COLOR = 0x44c2ff; - const RAY_HIGHLIGHT_COLOR = new THREE.Color(RAY_COLOR).multiplyScalar(0.5).getHex(); - - class PRS { - constructor() { - this.position = new THREE.Vector3(); - this.rotation = new THREE.Quaternion(); - this.scale = new THREE.Vector3(1, 1, 1); - this.worldPosition = new THREE.Vector3(); - this.worldRotation = new THREE.Quaternion(); - this.worldScale = new THREE.Vector3(1, 1, 1); + }; + + const solidMaterial = new THREE.MeshPhongMaterial({ + color: 0x666666, + shininess: 0, + shading: THREE.FlatShading, + }); + + const BUTTON_COLOR = 0xff4444; + const BUTTON_COLOR_HIGHLIGHT = 0xffbb33; + + const RAY_COLOR = 0x44c2ff; + const RAY_HIGHLIGHT_COLOR = new THREE.Color(RAY_COLOR) + .multiplyScalar(0.5) + .getHex(); + + class PRS { + constructor() { + this.position = new THREE.Vector3(); + this.rotation = new THREE.Quaternion(); + this.scale = new THREE.Vector3(1, 1, 1); + this.worldPosition = new THREE.Vector3(); + this.worldRotation = new THREE.Quaternion(); + this.worldScale = new THREE.Vector3(1, 1, 1); + } } - } - class PrevStatus { - constructor() { - // this.hmd = new PRS(); - this.gamepads = { - left: new PRS(), - right: new PRS(), - }; - this.timestamp = 0; + class PrevStatus { + constructor() { + // this.hmd = new PRS(); + this.gamepads = { + left: new PRS(), + right: new PRS(), + }; + this.timestamp = 0; + } } - } - class Player extends EventEmitter { - constructor() { - super(); + class Player extends EventEmitter { + constructor() { + super(); - const prevStatuses = Array(NUM_PREV_STATUSES); - for (let i = 0; i < NUM_PREV_STATUSES; i++) { - prevStatuses[i] = new PrevStatus(); + const prevStatuses = Array(NUM_PREV_STATUSES); + for (let i = 0; i < NUM_PREV_STATUSES; i++) { + prevStatuses[i] = new PrevStatus(); + } + this.prevStatuses = prevStatuses; + this.prevStatusIndex = NUM_PREV_STATUSES; } - this.prevStatuses = prevStatuses; - this.prevStatusIndex = NUM_PREV_STATUSES; - } - snapshotStatus(status) { - const {gamepads} = status; + snapshotStatus(status) { + const { gamepads } = status; - this.prevStatusIndex = mod(this.prevStatusIndex + 1, NUM_PREV_STATUSES); + this.prevStatusIndex = mod( + this.prevStatusIndex + 1, + NUM_PREV_STATUSES + ); - const prevStatus = this.prevStatuses[this.prevStatusIndex]; + const prevStatus = this.prevStatuses[this.prevStatusIndex]; - /* prevStatus.hmd.position.copy(camera.position); + /* prevStatus.hmd.position.copy(camera.position); prevStatus.hmd.rotation.copy(camera.quaternion); prevStatus.hmd.scale.copy(camera.scale); prevStatus.hmd.worldPosition.copy(camera.position); prevStatus.hmd.worldRotation.copy(camera.quaternion); prevStatus.hmd.worldScale.copy(camera.scale); */ - prevStatus.gamepads.left.position.copy(gamepads.left.position); - prevStatus.gamepads.left.rotation.copy(gamepads.left.rotation); - prevStatus.gamepads.left.scale.copy(gamepads.left.scale); - prevStatus.gamepads.left.worldPosition.copy(gamepads.left.worldPosition); - prevStatus.gamepads.left.worldRotation.copy(gamepads.left.worldRotation); - prevStatus.gamepads.left.worldScale.copy(gamepads.left.worldScale); - - prevStatus.gamepads.right.position.copy(gamepads.right.position); - prevStatus.gamepads.right.rotation.copy(gamepads.right.rotation); - prevStatus.gamepads.right.scale.copy(gamepads.right.scale); - prevStatus.gamepads.right.worldPosition.copy(gamepads.right.worldPosition); - prevStatus.gamepads.right.worldRotation.copy(gamepads.right.worldRotation); - prevStatus.gamepads.right.worldScale.copy(gamepads.right.worldScale); - - prevStatus.timestamp = Date.now(); - } - - getControllerLinearVelocity(side) { - const {prevStatuses, prevStatusIndex} = this; - - const lastStatus = prevStatuses[prevStatusIndex]; - const firstStatus = prevStatuses[mod(prevStatusIndex + 1, NUM_PREV_STATUSES)]; + prevStatus.gamepads.left.position.copy(gamepads.left.position); + prevStatus.gamepads.left.rotation.copy(gamepads.left.rotation); + prevStatus.gamepads.left.scale.copy(gamepads.left.scale); + prevStatus.gamepads.left.worldPosition.copy( + gamepads.left.worldPosition + ); + prevStatus.gamepads.left.worldRotation.copy( + gamepads.left.worldRotation + ); + prevStatus.gamepads.left.worldScale.copy( + gamepads.left.worldScale + ); + + prevStatus.gamepads.right.position.copy( + gamepads.right.position + ); + prevStatus.gamepads.right.rotation.copy( + gamepads.right.rotation + ); + prevStatus.gamepads.right.scale.copy(gamepads.right.scale); + prevStatus.gamepads.right.worldPosition.copy( + gamepads.right.worldPosition + ); + prevStatus.gamepads.right.worldRotation.copy( + gamepads.right.worldRotation + ); + prevStatus.gamepads.right.worldScale.copy( + gamepads.right.worldScale + ); + + prevStatus.timestamp = Date.now(); + } - const positionDiff = lastStatus.gamepads[side].worldPosition.clone() - .sub(firstStatus.gamepads[side].worldPosition); - const timeDiff = lastStatus.timestamp - firstStatus.timestamp; - return timeDiff > 0 ? positionDiff.divideScalar(timeDiff / 1000) : zeroVector; - } + getControllerLinearVelocity(side) { + const { prevStatuses, prevStatusIndex } = this; - getControllerAngularVelocity(side) { - const {prevStatuses, prevStatusIndex} = this; + const lastStatus = prevStatuses[prevStatusIndex]; + const firstStatus = + prevStatuses[mod(prevStatusIndex + 1, NUM_PREV_STATUSES)]; - const lastStatus = prevStatuses[prevStatusIndex]; - const firstStatus = prevStatuses[mod(prevStatusIndex + 1, NUM_PREV_STATUSES)]; + const positionDiff = lastStatus.gamepads[side].worldPosition + .clone() + .sub(firstStatus.gamepads[side].worldPosition); + const timeDiff = lastStatus.timestamp - firstStatus.timestamp; + return timeDiff > 0 + ? positionDiff.divideScalar(timeDiff / 1000) + : zeroVector; + } - const quaternionDiff = lastStatus.gamepads[side].worldRotation.clone() - .multiply(firstStatus.gamepads[side].worldRotation.clone().inverse()); - const angleDiff = (() => { - const x = quaternionDiff.x / Math.sqrt(1 - (quaternionDiff.w * quaternionDiff.w)); - const y = quaternionDiff.y / Math.sqrt(1 - (quaternionDiff.w * quaternionDiff.w)); - const z = quaternionDiff.z / Math.sqrt(1 - (quaternionDiff.w * quaternionDiff.w)); - const angle = 2 * Math.acos(quaternionDiff.w); - return new THREE.Vector3(x, y, z).multiplyScalar(angle); - })(); - const timeDiff = lastStatus.timestamp - firstStatus.timestamp; - return timeDiff > 0 ? positionDiff.divideScalar(timeDiff / 1000) : zeroVector; + getControllerAngularVelocity(side) { + const { prevStatuses, prevStatusIndex } = this; + + const lastStatus = prevStatuses[prevStatusIndex]; + const firstStatus = + prevStatuses[mod(prevStatusIndex + 1, NUM_PREV_STATUSES)]; + + const quaternionDiff = lastStatus.gamepads[side].worldRotation + .clone() + .multiply( + firstStatus.gamepads[side].worldRotation.clone().inverse() + ); + const angleDiff = (() => { + const x = + quaternionDiff.x / + Math.sqrt(1 - quaternionDiff.w * quaternionDiff.w); + const y = + quaternionDiff.y / + Math.sqrt(1 - quaternionDiff.w * quaternionDiff.w); + const z = + quaternionDiff.z / + Math.sqrt(1 - quaternionDiff.w * quaternionDiff.w); + const angle = 2 * Math.acos(quaternionDiff.w); + return new THREE.Vector3(x, y, z).multiplyScalar(angle); + })(); + const timeDiff = lastStatus.timestamp - firstStatus.timestamp; + return timeDiff > 0 + ? positionDiff.divideScalar(timeDiff / 1000) + : zeroVector; + } } - } - const player = new Player(); - - const _makePlayerPlaceholderMesh = () => { - const object = new THREE.Object3D(); - - const hmdMesh = (() => { - const mesh = hmdModelMesh.clone(true); - mesh.visible = false; - mesh.worldPosition = new THREE.Vector3(); - mesh.worldRotation = new THREE.Quaternion(); - mesh.worldScale = new THREE.Vector3(1, 1, 1); - return mesh; - })(); - object.add(hmdMesh); - object.hmdMesh = hmdMesh; + const player = new Player(); - const _makeControllerMesh = () => { + const _makePlayerPlaceholderMesh = () => { const object = new THREE.Object3D(); - object.worldPosition = new THREE.Vector3(); - object.worldRotation = new THREE.Quaternion(); - object.worldScale = new THREE.Vector3(1, 1, 1); - - const controllerMesh = controllerModelMesh.clone(true); - // const controllerMesh = mesh.children[0]; - // controllerMesh.material.color.setHex(0xFFFFFF); - // controllerMesh.material.map = loader.load(texturePath); - // controllerMesh.material.specularMap = loader.load(specularMapPath); - object.add(controllerMesh); - - const rayMesh = (() => { - const geometry = new THREE.CylinderBufferGeometry(0.001, 0.001, 1, 32, 1) - .applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2)) - .applyMatrix(new THREE.Matrix4().makeTranslation(0, 0, -0.5)); - const material = new THREE.MeshBasicMaterial({ - // color: 0x2196F3, - color: RAY_COLOR, - }); - - const mesh = new THREE.Mesh(geometry, material); + const hmdMesh = (() => { + const mesh = hmdModelMesh.clone(true); mesh.visible = false; + mesh.worldPosition = new THREE.Vector3(); + mesh.worldRotation = new THREE.Quaternion(); + mesh.worldScale = new THREE.Vector3(1, 1, 1); return mesh; })(); - object.add(rayMesh); - object.rayMesh = rayMesh; - - return object; - }; - const controllerMeshes = { - left: _makeControllerMesh(), - right: _makeControllerMesh(), - }; - object.add(controllerMeshes.left); - object.add(controllerMeshes.right); - object.controllerMeshes = controllerMeshes; + object.add(hmdMesh); + object.hmdMesh = hmdMesh; + + const _makeControllerMesh = () => { + const object = new THREE.Object3D(); + + object.worldPosition = new THREE.Vector3(); + object.worldRotation = new THREE.Quaternion(); + object.worldScale = new THREE.Vector3(1, 1, 1); + + const controllerMesh = controllerModelMesh.clone(true); + // const controllerMesh = mesh.children[0]; + // controllerMesh.material.color.setHex(0xFFFFFF); + // controllerMesh.material.map = loader.load(texturePath); + // controllerMesh.material.specularMap = loader.load(specularMapPath); + object.add(controllerMesh); + + const rayMesh = (() => { + const geometry = new THREE.CylinderBufferGeometry( + 0.001, + 0.001, + 1, + 32, + 1 + ) + .applyMatrix( + new THREE.Matrix4().makeRotationX(-Math.PI / 2) + ) + .applyMatrix( + new THREE.Matrix4().makeTranslation(0, 0, -0.5) + ); + const material = new THREE.MeshBasicMaterial({ + // color: 0x2196F3, + color: RAY_COLOR, + }); + + const mesh = new THREE.Mesh(geometry, material); + mesh.visible = false; + return mesh; + })(); + object.add(rayMesh); + object.rayMesh = rayMesh; + + return object; + }; + const controllerMeshes = { + left: _makeControllerMesh(), + right: _makeControllerMesh(), + }; + object.add(controllerMeshes.left); + object.add(controllerMeshes.right); + object.controllerMeshes = controllerMeshes; - object.update = (hmdStatus, gamepadsStatus) => { - const _updateHmdMesh = () => { - hmdMesh.position.copy(hmdStatus.position); - hmdMesh.quaternion.copy(hmdStatus.rotation); - hmdMesh.scale.copy(hmdStatus.scale); + object.update = (hmdStatus, gamepadsStatus) => { + const _updateHmdMesh = () => { + hmdMesh.position.copy(hmdStatus.position); + hmdMesh.quaternion.copy(hmdStatus.rotation); + hmdMesh.scale.copy(hmdStatus.scale); - hmdMesh.worldPosition.copy(hmdStatus.worldPosition); - hmdMesh.worldRotation.copy(hmdStatus.worldRotation); - hmdMesh.worldScale.copy(hmdStatus.worldScale); + hmdMesh.worldPosition.copy(hmdStatus.worldPosition); + hmdMesh.worldRotation.copy(hmdStatus.worldRotation); + hmdMesh.worldScale.copy(hmdStatus.worldScale); - /* const {labelMesh} = this; + /* const {labelMesh} = this; labelMesh.update({ hmdStatus: { position: hmdStatus.position.toArray(), @@ -259,224 +310,277 @@ class Cyborg { }, username: rend.getStatus('username'), }); */ - }; - const _updateControllerMeshes = () => { - _updateControllerMesh(controllerMeshes.left, gamepadsStatus.left); - _updateControllerMesh(controllerMeshes.right, gamepadsStatus.right); - }; - const _updateControllerMesh = (controllerMesh, gamepadStatus) => { - controllerMesh.position.copy(gamepadStatus.position); - controllerMesh.quaternion.copy(gamepadStatus.rotation); - controllerMesh.scale.copy(gamepadStatus.scale); - - controllerMesh.worldPosition.copy(gamepadStatus.worldPosition); - controllerMesh.worldRotation.copy(gamepadStatus.worldRotation); - controllerMesh.worldScale.copy(gamepadStatus.worldScale); - - const {buttons} = gamepadStatus; - if (!buttons.trigger.pressed && controllerMesh.rayMesh.material.color.getHex() !== RAY_COLOR) { - controllerMesh.rayMesh.material.color.setHex(RAY_COLOR); - } else if (buttons.trigger.pressed && controllerMesh.rayMesh.material.color.getHex() !== RAY_HIGHLIGHT_COLOR) { - controllerMesh.rayMesh.material.color.setHex(RAY_HIGHLIGHT_COLOR); - } + }; + const _updateControllerMeshes = () => { + _updateControllerMesh( + controllerMeshes.left, + gamepadsStatus.left + ); + _updateControllerMesh( + controllerMeshes.right, + gamepadsStatus.right + ); + }; + const _updateControllerMesh = ( + controllerMesh, + gamepadStatus + ) => { + controllerMesh.position.copy(gamepadStatus.position); + controllerMesh.quaternion.copy(gamepadStatus.rotation); + controllerMesh.scale.copy(gamepadStatus.scale); + + controllerMesh.worldPosition.copy( + gamepadStatus.worldPosition + ); + controllerMesh.worldRotation.copy( + gamepadStatus.worldRotation + ); + controllerMesh.worldScale.copy(gamepadStatus.worldScale); + + const { buttons } = gamepadStatus; + if ( + !buttons.trigger.pressed && + controllerMesh.rayMesh.material.color.getHex() !== RAY_COLOR + ) { + controllerMesh.rayMesh.material.color.setHex(RAY_COLOR); + } else if ( + buttons.trigger.pressed && + controllerMesh.rayMesh.material.color.getHex() !== + RAY_HIGHLIGHT_COLOR + ) { + controllerMesh.rayMesh.material.color.setHex( + RAY_HIGHLIGHT_COLOR + ); + } + }; + + _updateHmdMesh(); + _updateControllerMeshes(); }; - _updateHmdMesh(); - _updateControllerMeshes(); + return object; }; - - return object; - } - const playerPlaceholderMesh = _makePlayerPlaceholderMesh(); - camera.parent.add(playerPlaceholderMesh); - rend.registerAuxObject('controllerMeshes', playerPlaceholderMesh.controllerMeshes); - - let playerSkinMesh = null; - - const hudMesh = (() => { - const hudMesh = new THREE.Object3D(); - // hudMesh.visible = false; - - const circleMesh = (() => { - const geometry = (() => { - const geometry = new THREE.CylinderBufferGeometry(0.05, 0.05, 0.01, 8, 1) - .applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2)) - .applyMatrix(new THREE.Matrix4().makeRotationZ(Math.PI / 8)); - - const _almostZero = v => Math.abs(v) < 0.001; - - const {array: positions} = geometry.getAttribute('position'); - const numPositions = positions.length / 3; - for (let i = 0; i < numPositions; i++) { - const baseIndex = i * 3; - - if (_almostZero(positions[baseIndex + 0]) && _almostZero(positions[baseIndex + 1])) { - positions[baseIndex + 2] -= 0.005; + const playerPlaceholderMesh = _makePlayerPlaceholderMesh(); + camera.parent.add(playerPlaceholderMesh); + rend.registerAuxObject( + 'controllerMeshes', + playerPlaceholderMesh.controllerMeshes + ); + + let playerSkinMesh = null; + + const hudMesh = (() => { + const hudMesh = new THREE.Object3D(); + // hudMesh.visible = false; + + const circleMesh = (() => { + const geometry = (() => { + const geometry = new THREE.CylinderBufferGeometry( + 0.05, + 0.05, + 0.01, + 8, + 1 + ) + .applyMatrix( + new THREE.Matrix4().makeRotationX(-Math.PI / 2) + ) + .applyMatrix( + new THREE.Matrix4().makeRotationZ(Math.PI / 8) + ); + + const _almostZero = v => Math.abs(v) < 0.001; + + const { array: positions } = geometry.getAttribute( + 'position' + ); + const numPositions = positions.length / 3; + for (let i = 0; i < numPositions; i++) { + const baseIndex = i * 3; + + if ( + _almostZero(positions[baseIndex + 0]) && + _almostZero(positions[baseIndex + 1]) + ) { + positions[baseIndex + 2] -= 0.005; + } } - } - - geometry.computeVertexNormals(); - return geometry; - })(); - const material = solidMaterial; - - const mesh = new THREE.Mesh(geometry, material); - mesh.position.z = -0.1; + geometry.computeVertexNormals(); - const notchMesh = (() => { - const geometry = new THREE.SphereBufferGeometry(0.005, 5, 4) - .applyMatrix(new THREE.Matrix4().makeRotationY(Math.PI / 8)) - .applyMatrix(new THREE.Matrix4().makeTranslation(0, 0, 0.003)); + return geometry; + })(); const material = solidMaterial; const mesh = new THREE.Mesh(geometry, material); + mesh.position.z = -0.1; + + const notchMesh = (() => { + const geometry = new THREE.SphereBufferGeometry(0.005, 5, 4) + .applyMatrix(new THREE.Matrix4().makeRotationY(Math.PI / 8)) + .applyMatrix( + new THREE.Matrix4().makeTranslation(0, 0, 0.003) + ); + const material = solidMaterial; + + const mesh = new THREE.Mesh(geometry, material); + return mesh; + })(); + mesh.add(notchMesh); + mesh.notchMesh = notchMesh; + return mesh; })(); - mesh.add(notchMesh); - mesh.notchMesh = notchMesh; - - return mesh; - })(); - hudMesh.add(circleMesh); - hudMesh.circleMesh = circleMesh; - hudMesh.update = () => { - const vrMode = bootstrap.getVrMode(); - const mode = webvr.getMode() - const keys = webvr.getKeys(); - if (vrMode === 'keyboard' && mode !== null && keys !== null) { - const {axis} = keys; - - if (axis) { - hudMesh.position.copy(hmdStatus.position); - hudMesh.quaternion.copy(hmdStatus.rotation); - hudMesh.scale.copy(hmdStatus.scale); - - const {notchMesh} = circleMesh; - const gamepad = gamepadStatus[mode === 'center' ? 'left' : mode]; - const {axes} = gamepad; - notchMesh.position.set(axes[0] * 0.043, axes[1] * 0.043, (1 - new THREE.Vector2(axes[0], axes[1]).length()) * (-0.005)); - - if (!hudMesh.visible) { - hudMesh.visible = true; + hudMesh.add(circleMesh); + hudMesh.circleMesh = circleMesh; + hudMesh.update = () => { + const vrMode = bootstrap.getVrMode(); + const mode = webvr.getMode(); + const keys = webvr.getKeys(); + if (vrMode === 'keyboard' && mode !== null && keys !== null) { + const { axis } = keys; + + if (axis) { + hudMesh.position.copy(hmdStatus.position); + hudMesh.quaternion.copy(hmdStatus.rotation); + hudMesh.scale.copy(hmdStatus.scale); + + const { notchMesh } = circleMesh; + const gamepad = + gamepadStatus[mode === 'center' ? 'left' : mode]; + const { axes } = gamepad; + notchMesh.position.set( + axes[0] * 0.043, + axes[1] * 0.043, + (1 - new THREE.Vector2(axes[0], axes[1]).length()) * + -0.005 + ); + + if (!hudMesh.visible) { + hudMesh.visible = true; + } + } else { + if (hudMesh.visible) { + hudMesh.visible = false; + } } } else { if (hudMesh.visible) { hudMesh.visible = false; } } - } else { - if (hudMesh.visible) { - hudMesh.visible = false; - } + }; + + return hudMesh; + })(); + camera.parent.add(hudMesh); + + // camera.parent.add(hmdLabelMesh); + + const _getPlayer = () => player; + const _getHmd = () => hmd; + const _setSkin = (skinImg = null) => { + if (playerSkinMesh) { + scene.remove(playerSkinMesh); + playerSkinMesh.destroy(); + playerSkinMesh = null; + + multiplayer.updateSkin(null); + } + + if (skinImg) { + playerSkinMesh = skinUtils.makePlayerMesh(skinImg); + playerSkinMesh.frustumCulled = false; + scene.add(playerSkinMesh); + + const skinImgBuffer = spriteUtils.getImageData(skinImg).data; + multiplayer.updateSkin(skinImgBuffer); } }; + const _update = () => { + const status = webvr.getStatus(); + const { hmd: hmdStatus, gamepads: gamepadsStatus } = status; + + // update player placeholder mesh + playerPlaceholderMesh.update(hmdStatus, gamepadsStatus); + + // update player skin mesh + if (playerSkinMesh) { + localSkinStatus.hmd.position = hmdStatus.worldPosition; + localSkinStatus.hmd.rotation = hmdStatus.worldRotation; + localSkinStatus.gamepads.left.position = + gamepadsStatus.left.worldPosition; + localSkinStatus.gamepads.left.rotation = + gamepadsStatus.left.worldRotation; + localSkinStatus.gamepads.right.position = + gamepadsStatus.right.worldPosition; + localSkinStatus.gamepads.right.rotation = + gamepadsStatus.right.worldRotation; + playerSkinMesh.update(localSkinStatus); + } - return hudMesh; - })(); - camera.parent.add(hudMesh); + // update hud mesh + hudMesh.update(); - // camera.parent.add(hmdLabelMesh); + // update camera + camera.position.copy(hmdStatus.position); + camera.quaternion.copy(hmdStatus.rotation); + camera.scale.copy(hmdStatus.scale); + camera.parent.matrix.copy(webvr.getExternalMatrix()); + camera.parent.updateMatrixWorld(true); - const _getPlayer = () => player; - const _getHmd = () => hmd; - const _setSkin = (skinImg = null) => { - if (playerSkinMesh) { - scene.remove(playerSkinMesh); - playerSkinMesh.destroy(); - playerSkinMesh = null; + // snapshot current status + player.snapshotStatus(status); + }; + rend.on('update', _update); - multiplayer.updateSkin(null); - } + const _updateEyeStart = () => { + if (!playerSkinMesh) { + playerPlaceholderMesh.hmdMesh.visible = true; + } else { + playerPlaceholderMesh.controllerMeshes.left.visible = false; + playerPlaceholderMesh.controllerMeshes.right.visible = false; - if (skinImg) { - playerSkinMesh = skinUtils.makePlayerMesh(skinImg); - playerSkinMesh.frustumCulled = false; - scene.add(playerSkinMesh); + playerSkinMesh.setHeadVisible(true); + playerSkinMesh.visible = true; + } + }; + rend.on('updateEyeStart', _updateEyeStart); + const _updateEyeEnd = () => { + if (!playerSkinMesh) { + playerPlaceholderMesh.hmdMesh.visible = false; + } else { + playerPlaceholderMesh.controllerMeshes.left.visible = true; + playerPlaceholderMesh.controllerMeshes.right.visible = true; - const skinImgBuffer = spriteUtils.getImageData(skinImg).data; - multiplayer.updateSkin(skinImgBuffer); - } - }; - const _update = () => { - const status = webvr.getStatus(); - const {hmd: hmdStatus, gamepads: gamepadsStatus} = status; - - // update player placeholder mesh - playerPlaceholderMesh.update(hmdStatus, gamepadsStatus); - - // update player skin mesh - if (playerSkinMesh) { - localSkinStatus.hmd.position = hmdStatus.worldPosition; - localSkinStatus.hmd.rotation = hmdStatus.worldRotation; - localSkinStatus.gamepads.left.position = gamepadsStatus.left.worldPosition; - localSkinStatus.gamepads.left.rotation = gamepadsStatus.left.worldRotation; - localSkinStatus.gamepads.right.position = gamepadsStatus.right.worldPosition; - localSkinStatus.gamepads.right.rotation = gamepadsStatus.right.worldRotation; - playerSkinMesh.update(localSkinStatus); - } + playerSkinMesh.setHeadVisible(false); + playerSkinMesh.visible = false; + } + }; + rend.on('updateEyeEnd', _updateEyeEnd); - // update hud mesh - hudMesh.update(); - - // update camera - camera.position.copy(hmdStatus.position); - camera.quaternion.copy(hmdStatus.rotation); - camera.scale.copy(hmdStatus.scale); - camera.parent.matrix.copy(webvr.getExternalMatrix()); - camera.parent.updateMatrixWorld(true); - - // snapshot current status - player.snapshotStatus(status); - }; - rend.on('update', _update); - - const _updateEyeStart = () => { - if (!playerSkinMesh) { - playerPlaceholderMesh.hmdMesh.visible = true; - } else { - playerPlaceholderMesh.controllerMeshes.left.visible = false; - playerPlaceholderMesh.controllerMeshes.right.visible = false; - - playerSkinMesh.setHeadVisible(true); - playerSkinMesh.visible = true; - } - }; - rend.on('updateEyeStart', _updateEyeStart); - const _updateEyeEnd = () => { - if (!playerSkinMesh) { - playerPlaceholderMesh.hmdMesh.visible = false; - } else { - playerPlaceholderMesh.controllerMeshes.left.visible = true; - playerPlaceholderMesh.controllerMeshes.right.visible = true; - - playerSkinMesh.setHeadVisible(false); - playerSkinMesh.visible = false; - } - }; - rend.on('updateEyeEnd', _updateEyeEnd); - - this._cleanup = () => { - solidMaterial.dispose(); - - const {mesh: hmdMesh, /*, labelMesh: hmdLabelMesh*/} = hmd; - camera.parent.remove(playerPlaceholderMesh); - camera.parent.remove(hudMesh); - // camera.parent.remove(hmdLabelMesh); - - rend.removeListener('update', _update); - rend.removeListener('updateEyeStart', _updateEyeStart); - rend.removeListener('updateEyeEnd', _updateEyeEnd); - }; - - return { - getPlayer: _getPlayer, - getHmd: _getHmd, - setSkin: _setSkin, - update: _update, - }; + this._cleanup = () => { + solidMaterial.dispose(); + + const { mesh: hmdMesh /*, labelMesh: hmdLabelMesh*/ } = hmd; + camera.parent.remove(playerPlaceholderMesh); + camera.parent.remove(hudMesh); + // camera.parent.remove(hmdLabelMesh); + + rend.removeListener('update', _update); + rend.removeListener('updateEyeStart', _updateEyeStart); + rend.removeListener('updateEyeEnd', _updateEyeEnd); + }; + + return { + getPlayer: _getPlayer, + getHmd: _getHmd, + setSkin: _setSkin, + update: _update, + }; + } } - }); + ); } unmount() { diff --git a/core/engines/entity/client.js b/core/engines/entity/client.js index abb53c6dd..72c9aca65 100644 --- a/core/engines/entity/client.js +++ b/core/engines/entity/client.js @@ -4,7 +4,6 @@ import { WORLD_WIDTH, WORLD_HEIGHT, WORLD_DEPTH, - TAGS_WIDTH, TAGS_HEIGHT, TAGS_WORLD_WIDTH, @@ -20,8 +19,13 @@ class Entity { } mount() { - const {_archae: archae} = this; - const {metadata: {site: {url: siteUrl}, server: {url: serverUrl, enabled: serverEnabled}}} = archae; + const { _archae: archae } = this; + const { + metadata: { + site: { url: siteUrl }, + server: { url: serverUrl, enabled: serverEnabled }, + }, + } = archae; const cleanups = []; this._cleanup = () => { @@ -36,39 +40,40 @@ class Entity { live = false; }); - const _requestColorImgData = () => new Promise((accept, reject) => { - const img = new Image(); - img.onload = () => { - const {width, height} = img; - - const canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - - const ctx = canvas.getContext('2d'); - ctx.drawImage(img, 0, 0); - - const imageData = ctx.getImageData(0, 0, width, height); - const {data: imageDataData} = imageData; - imageData.getColorArray = (x, y) => { - const xPx = Math.floor(x * width); - const yPx = Math.floor(y * height); - const baseIndex = (xPx + (yPx * width)) * 4; - const colorArray = [ - imageDataData[baseIndex + 0] / 255, - imageDataData[baseIndex + 1] / 255, - imageDataData[baseIndex + 2] / 255, - ]; - return colorArray; - }; + const _requestColorImgData = () => + new Promise((accept, reject) => { + const img = new Image(); + img.onload = () => { + const { width, height } = img; + + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + + const imageData = ctx.getImageData(0, 0, width, height); + const { data: imageDataData } = imageData; + imageData.getColorArray = (x, y) => { + const xPx = Math.floor(x * width); + const yPx = Math.floor(y * height); + const baseIndex = (xPx + yPx * width) * 4; + const colorArray = [ + imageDataData[baseIndex + 0] / 255, + imageDataData[baseIndex + 1] / 255, + imageDataData[baseIndex + 2] / 255, + ]; + return colorArray; + }; - accept(imageData); - }; - img.onerror = err => { - reject(err); - }; - img.src = 'data:image/svg+xml;utf8,' + colorImg; - }); + accept(imageData); + }; + img.onerror = err => { + reject(err); + }; + img.src = 'data:image/svg+xml;utf8,' + colorImg; + }); return Promise.all([ archae.requestPlugins([ @@ -88,50 +93,55 @@ class Entity { '/core/utils/creature-utils', ]), _requestColorImgData(), - ]) - .then(([ - [ - three, - input, - webvr, - biolumi, - resource, - rend, - tags, - fs, - world, - file, - keyboard, - transform, - typeUtils, - creatureUtils, - ], - colorImgData, - ]) => { - if (live) { - const {THREE, scene} = three; - const {sfx} = resource; - - const entityRenderer = entityRender.makeRenderer({typeUtils, creatureUtils}); - - const transparentMaterial = biolumi.getTransparentMaterial(); - - const mainFontSpec = { - fonts: biolumi.getFonts(), - fontSize: 36, - lineHeight: 1.4, - fontWeight: biolumi.getFontWeight(), - fontStyle: biolumi.getFontStyle(), - }; - const subcontentFontSpec = { - fonts: biolumi.getFonts(), - fontSize: 24, - lineHeight: 1.4, - fontWeight: biolumi.getFontWeight(), - fontStyle: biolumi.getFontStyle(), - }; + ]).then( + ( + [ + [ + three, + input, + webvr, + biolumi, + resource, + rend, + tags, + fs, + world, + file, + keyboard, + transform, + typeUtils, + creatureUtils, + ], + colorImgData, + ] + ) => { + if (live) { + const { THREE, scene } = three; + const { sfx } = resource; + + const entityRenderer = entityRender.makeRenderer({ + typeUtils, + creatureUtils, + }); - /* const _decomposeObjectMatrixWorld = object => _decomposeMatrix(object.matrixWorld); + const transparentMaterial = biolumi.getTransparentMaterial(); + + const mainFontSpec = { + fonts: biolumi.getFonts(), + fontSize: 36, + lineHeight: 1.4, + fontWeight: biolumi.getFontWeight(), + fontStyle: biolumi.getFontStyle(), + }; + const subcontentFontSpec = { + fonts: biolumi.getFonts(), + fontSize: 24, + lineHeight: 1.4, + fontWeight: biolumi.getFontWeight(), + fontStyle: biolumi.getFontStyle(), + }; + + /* const _decomposeObjectMatrixWorld = object => _decomposeMatrix(object.matrixWorld); const _decomposeMatrix = matrix => { const position = new THREE.Vector3(); const rotation = new THREE.Quaternion(); @@ -140,186 +150,217 @@ class Entity { return {position, rotation, scale}; }; */ - const _decorateEntity = entity => { - const {id, name, version, displayName, module, attributes, instancing} = entity; - const attributeSpecs = tags.getAttributeSpecs(module); - for (const attributeName in attributes) { - const attributeSpec = attributeSpecs.find(attributeSpec => attributeSpec.name === attributeName); - const {value: attributeValue} = attributes[attributeName]; - attributeSpec.value = attributeValue; - } - return {id, name, version, displayName, attributes: attributeSpecs, instancing}; - }; - const _updateNpm = () => { - const {inputText} = npmState; - - const itemSpecs = tags.getTagMeshes() - .filter(({item}) => - item.type === 'entity' && - item.displayName.indexOf(inputText) !== -1 - ) - .map(({item}) => item); - - npmState.loading = false; - npmState.page = 0; - npmState.tagSpecs = itemSpecs; - npmState.numTags = itemSpecs.length; - - npmState.loading = false; - }; - - const npmState = { - loading: true, - inputText: '', - inputValue: 0, - entity: null, - tagSpecs: [], - numTags: 0, - page: 0, - }; - const focusState = { - keyboardFocusState: null, - }; - const npmCacheState = { - loaded: false, - }; - - const entityMesh = (() => { - const worldUi = biolumi.makeUi({ - width: WIDTH, - height: HEIGHT, - }); - const mesh = worldUi.makePage(({ - npm: { - loading, - inputText: npmInputText, - inputValue: npmInputValue, - entity, - tagSpecs, - numTags, - page, - }, - focus: { - keyboardFocusState, - }, - }) => { - const {type = '', inputText: attributeInputText = '', inputValue: attributeInputValue = 0} = keyboardFocusState || {}; - const focusSpec = (() => { - let match; - if (type === 'entity:search') { - return { - type: 'entity', - }; - } else if (match = type.match(/^entityAttribute:(.+?):(.+?)$/)) { - const tagId = match[1]; - const attributeName = match[2]; + const _decorateEntity = entity => { + const { + id, + name, + version, + displayName, + module, + attributes, + instancing, + } = entity; + const attributeSpecs = tags.getAttributeSpecs(module); + for (const attributeName in attributes) { + const attributeSpec = attributeSpecs.find( + attributeSpec => attributeSpec.name === attributeName + ); + const { value: attributeValue } = attributes[attributeName]; + attributeSpec.value = attributeValue; + } + return { + id, + name, + version, + displayName, + attributes: attributeSpecs, + instancing, + }; + }; + const _updateNpm = () => { + const { inputText } = npmState; + + const itemSpecs = tags + .getTagMeshes() + .filter( + ({ item }) => + item.type === 'entity' && + item.displayName.indexOf(inputText) !== -1 + ) + .map(({ item }) => item); + + npmState.loading = false; + npmState.page = 0; + npmState.tagSpecs = itemSpecs; + npmState.numTags = itemSpecs.length; + + npmState.loading = false; + }; - return { - type: 'entityAttribute', - tagId: tagId, - attributeName: attributeName, - }; - } else if (match = type.match(/^entityAttributeMatrix:(.+?):(.+?)$/)) { - const tagId = match[1]; - const attributeName = match[2]; + const npmState = { + loading: true, + inputText: '', + inputValue: 0, + entity: null, + tagSpecs: [], + numTags: 0, + page: 0, + }; + const focusState = { + keyboardFocusState: null, + }; + const npmCacheState = { + loaded: false, + }; - return { - type: 'entityAttributeMatrix', - tagId: tagId, - attributeName: attributeName, - }; - } else if (match = type.match(/^entityAttributeColor:(.+?):(.+?)$/)) { - const tagId = match[1]; - const attributeName = match[2]; + const entityMesh = (() => { + const worldUi = biolumi.makeUi({ + width: WIDTH, + height: HEIGHT, + }); + const mesh = worldUi.makePage( + ({ + npm: { + loading, + inputText: npmInputText, + inputValue: npmInputValue, + entity, + tagSpecs, + numTags, + page, + }, + focus: { keyboardFocusState }, + }) => { + const { + type = '', + inputText: attributeInputText = '', + inputValue: attributeInputValue = 0, + } = + keyboardFocusState || {}; + const focusSpec = (() => { + let match; + if (type === 'entity:search') { + return { + type: 'entity', + }; + } else if ( + (match = type.match(/^entityAttribute:(.+?):(.+?)$/)) + ) { + const tagId = match[1]; + const attributeName = match[2]; + + return { + type: 'entityAttribute', + tagId: tagId, + attributeName: attributeName, + }; + } else if ( + (match = type.match(/^entityAttributeMatrix:(.+?):(.+?)$/)) + ) { + const tagId = match[1]; + const attributeName = match[2]; + + return { + type: 'entityAttributeMatrix', + tagId: tagId, + attributeName: attributeName, + }; + } else if ( + (match = type.match(/^entityAttributeColor:(.+?):(.+?)$/)) + ) { + const tagId = match[1]; + const attributeName = match[2]; + + return { + type: 'entityAttributeColor', + tagId: tagId, + attributeName: attributeName, + }; + } else { + return null; + } + })(); return { - type: 'entityAttributeColor', - tagId: tagId, - attributeName: attributeName, + type: 'html', + src: entityRenderer.getEntityPageSrc({ + loading, + npmInputText, + npmInputValue, + attributeInputText, + attributeInputValue, + entity: entity && _decorateEntity(entity), + tagSpecs, + numTags, + page, + focusSpec, + }), + x: 0, + y: 0, + w: WIDTH, + h: HEIGHT, }; - } else { - return null; + }, + { + type: 'entity', + state: { + npm: npmState, + focus: focusState, + }, + worldWidth: WORLD_WIDTH, + worldHeight: WORLD_HEIGHT, + isEnabled: () => rend.isOpen(), } - })(); - - return { - type: 'html', - src: entityRenderer.getEntityPageSrc({ - loading, - npmInputText, - npmInputValue, - attributeInputText, - attributeInputValue, - entity: entity && _decorateEntity(entity), - tagSpecs, - numTags, - page, - focusSpec, - }), - x: 0, - y: 0, - w: WIDTH, - h: HEIGHT, - }; - }, { - type: 'entity', - state: { - npm: npmState, - focus: focusState, - }, - worldWidth: WORLD_WIDTH, - worldHeight: WORLD_HEIGHT, - isEnabled: () => rend.isOpen(), - }); - mesh.visible = false; - // mesh.receiveShadow = true; + ); + mesh.visible = false; + // mesh.receiveShadow = true; - const {page} = mesh; - rend.addPage(page); + const { page } = mesh; + rend.addPage(page); - cleanups.push(() => { - rend.removePage(page); - }); + cleanups.push(() => { + rend.removePage(page); + }); - return mesh; - })(); - rend.registerMenuMesh('entityMesh', entityMesh); - entityMesh.updateMatrixWorld(); + return mesh; + })(); + rend.registerMenuMesh('entityMesh', entityMesh); + entityMesh.updateMatrixWorld(); - const _updatePages = () => { - const {page} = entityMesh; - page.update(); - }; - _updatePages(); + const _updatePages = () => { + const { page } = entityMesh; + page.update(); + }; + _updatePages(); - const _tabchange = tab => { - if (tab === 'entity') { - keyboard.tryBlur(); + const _tabchange = tab => { + if (tab === 'entity') { + keyboard.tryBlur(); - const {loaded} = npmCacheState; - if (!loaded) { - _updateNpm(); - _updatePages(); + const { loaded } = npmCacheState; + if (!loaded) { + _updateNpm(); + _updatePages(); - npmCacheState.loaded = true; + npmCacheState.loaded = true; + } } - } - }; - rend.on('tabchange', _tabchange); + }; + rend.on('tabchange', _tabchange); - const _setEntity = item => { - npmState.entity = item; - npmState.page = 0; + const _setEntity = item => { + npmState.entity = item; + npmState.page = 0; - _updatePages(); - }; - const _entitychange = item => { - _setEntity(item); - }; - rend.on('entitychange', _entitychange); + _updatePages(); + }; + const _entitychange = item => { + _setEntity(item); + }; + rend.on('entitychange', _entitychange); - const _saveEntities = entitySpecs => { - /* const id = _makeId(); + const _saveEntities = entitySpecs => { + /* const id = _makeId(); const date = new Date(); const fileSpec = { type: 'file', @@ -350,265 +391,263 @@ class Entity { .catch(err => { console.warn(err); }); */ - }; - - const _saveAllEntities = () => { - const entitySpecs = tags.getTagMeshes() - .filter(({item}) => item.type === 'entity') - .map(({item}) => item); - _saveEntities(entitySpecs); - }; - rend.on('saveAllEntities', _saveAllEntities); - - const _trigger = e => { - const {side} = e; - - const _clickMenu = () => { - const hoverState = rend.getHoverState(side); - const {anchor} = hoverState; - const onclick = (anchor && anchor.onclick) || ''; - - let match; - if (onclick === 'entity:focus') { - const {inputText} = npmState; - const {value, target: page} = hoverState; - const {layer: {measures}} = page; - const valuePx = value * (WIDTH - (250 + (30 * 2))); - const {index, px} = biolumi.getTextPropertiesFromCoord(measures['entity:search'], inputText, valuePx); - const {hmd: hmdStatus} = webvr.getStatus(); - const {worldPosition: hmdPosition, worldRotation: hmdRotation} = hmdStatus; - const keyboardFocusState = keyboard.focus({ - type: 'entity:search', - position: hmdPosition, - rotation: hmdRotation, - inputText: inputText, - inputIndex: index, - inputValue: px, - page: page, - }); - focusState.keyboardFocusState = keyboardFocusState; - - keyboardFocusState.on('update', () => { - const {inputText: keyboardInputText, inputValue: keyboardInputValue} = keyboardFocusState; - const {inputText: npmInputText, inputValue: npmInputValue} = npmState; - - if (keyboardInputText !== npmInputText || npmInputValue !== keyboardInputValue) { - npmState.inputText = keyboardInputText; - npmState.inputValue = keyboardInputValue; - - _updateNpm(); - } + }; - _updatePages(); - }); - keyboardFocusState.on('blur', () => { - focusState.keyboardFocusState = null; + const _saveAllEntities = () => { + const entitySpecs = tags + .getTagMeshes() + .filter(({ item }) => item.type === 'entity') + .map(({ item }) => item); + _saveEntities(entitySpecs); + }; + rend.on('saveAllEntities', _saveAllEntities); - _updatePages(); - }); + const _trigger = e => { + const { side } = e; - _updatePages(); + const _clickMenu = () => { + const hoverState = rend.getHoverState(side); + const { anchor } = hoverState; + const onclick = (anchor && anchor.onclick) || ''; - return true; - } else if (match = onclick.match(/^entity:(up|down)$/)) { - const direction = match[1]; + let match; + if (onclick === 'entity:focus') { + const { inputText } = npmState; + const { value, target: page } = hoverState; + const { layer: { measures } } = page; + const valuePx = value * (WIDTH - (250 + 30 * 2)); + const { index, px } = biolumi.getTextPropertiesFromCoord( + measures['entity:search'], + inputText, + valuePx + ); + const { hmd: hmdStatus } = webvr.getStatus(); + const { + worldPosition: hmdPosition, + worldRotation: hmdRotation, + } = hmdStatus; + const keyboardFocusState = keyboard.focus({ + type: 'entity:search', + position: hmdPosition, + rotation: hmdRotation, + inputText: inputText, + inputIndex: index, + inputValue: px, + page: page, + }); + focusState.keyboardFocusState = keyboardFocusState; - npmState.page += (direction === 'up' ? -1 : 1); + keyboardFocusState.on('update', () => { + const { + inputText: keyboardInputText, + inputValue: keyboardInputValue, + } = keyboardFocusState; + const { + inputText: npmInputText, + inputValue: npmInputValue, + } = npmState; + + if ( + keyboardInputText !== npmInputText || + npmInputValue !== keyboardInputValue + ) { + npmState.inputText = keyboardInputText; + npmState.inputValue = keyboardInputValue; - _updatePages(); + _updateNpm(); + } - return true; - } else if (match = onclick.match(/^entity:entity:(.+)$/)) { - const tagId = match[1]; + _updatePages(); + }); + keyboardFocusState.on('blur', () => { + focusState.keyboardFocusState = null; - const tagMesh = tags.getTagMeshes().find(tagMesh => tagMesh.item.id === tagId); - const {item} = tagMesh; - _setEntity(item); + _updatePages(); + }); - return true; - } else if (onclick === 'entity:back') { - _setEntity(null); + _updatePages(); - return true; - } else if (match = onclick.match(/^entity:remove:(.+)$/)) { - const tagId = match[1]; + return true; + } else if ((match = onclick.match(/^entity:(up|down)$/))) { + const direction = match[1]; - world.removeTag(tagId); + npmState.page += direction === 'up' ? -1 : 1; - npmState.tagSpecs.splice(npmState.tagSpecs.findIndex(item => item.id === tagId), 1); - _setEntity(null); + _updatePages(); - return true; - } else if (match = onclick.match(/^entity:select:(.+)$/)) { - const tagId = match[1]; + return true; + } else if ((match = onclick.match(/^entity:entity:(.+)$/))) { + const tagId = match[1]; - const entitySpec = npmState.tagSpecs.find(item => item.id === tagId); - entitySpec.selected = !entitySpec.selected; + const tagMesh = tags + .getTagMeshes() + .find(tagMesh => tagMesh.item.id === tagId); + const { item } = tagMesh; + _setEntity(item); - _updatePages(); + return true; + } else if (onclick === 'entity:back') { + _setEntity(null); - return true; - } else if (onclick === 'entity:selectAll') { - for (let i = 0; i < npmState.tagSpecs.length; i++) { - const entitySpec = npmState.tagSpecs[i]; - entitySpec.selected = true; - } + return true; + } else if ((match = onclick.match(/^entity:remove:(.+)$/))) { + const tagId = match[1]; - _updatePages(); + world.removeTag(tagId); - return true; - } else if (onclick === 'entity:clearAll') { - for (let i = 0; i < npmState.tagSpecs.length; i++) { - const entitySpec = npmState.tagSpecs[i]; - entitySpec.selected = false; - } + npmState.tagSpecs.splice( + npmState.tagSpecs.findIndex(item => item.id === tagId), + 1 + ); + _setEntity(null); - _updatePages(); + return true; + } else if ((match = onclick.match(/^entity:select:(.+)$/))) { + const tagId = match[1]; - return true; - } else if (onclick === 'entity:saveEntities') { - const entitySpecs = npmState.tagSpecs.filter(entitySpec => entitySpec.selected); - _saveEntities(entitySpecs); + const entitySpec = npmState.tagSpecs.find( + item => item.id === tagId + ); + entitySpec.selected = !entitySpec.selected; - return true; - } else { - return false; - } - }; - const _clickAttribute = () => { - const hoverState = rend.getHoverState(side); - const {anchor} = hoverState; - const onclick = (anchor && anchor.onclick) || ''; - - let match; - if (match = onclick.match(/^entityAttribute:([^:]+):([^:]+)(?::([^:]+))?:(focus|set|tweak|pick|color|toggle|link|matrix)(?::([^:]+))?$/)) { - const tagId = match[1]; - const attributeName = match[2]; - const key = match[3]; - const action = match[4]; - const value = match[5]; - - const tagMesh = tags.getTagMeshes().find(tagMesh => tagMesh.item.id === tagId); - const {item} = tagMesh; - const {module, attributes} = item; - const attribute = attributes[attributeName] || tags.getAttributeSpec(module, attributeName); - const {value: attributeValue} = attribute; - - if (action === 'focus') { - const {value: hoverValue, target: page} = hoverState; - const {layer: {measures}} = page; - const {type} = tags.getAttributeSpec(module, attributeName); - - const inputText = typeUtils.castValueValueToString(attributeValue, type); - const textProperties = (() => { - if (type === 'text') { - const hoverValuePx = hoverValue * 400; - return biolumi.getTextPropertiesFromCoord(measures[`entityAttribute:${tagId}:${attributeName}`], inputText, hoverValuePx); - } else if (type === 'number') { - const hoverValuePx = hoverValue * 100; - return biolumi.getTextPropertiesFromCoord(measures[`entityAttribute:${tagId}:${attributeName}`], inputText, hoverValuePx); - } else if (type === 'color') { - const hoverValuePx = hoverValue * 300; - return biolumi.getTextPropertiesFromCoord(measures[`entityAttribute:${tagId}:${attributeName}`], inputText, hoverValuePx); - } else { - return null; - } - })(); - const keyboardFocusState = (() => { - if (textProperties) { - const {hmd: hmdStatus} = webvr.getStatus(); - const {worldPosition: hmdPosition, worldRotation: hmdRotation} = hmdStatus; - const {index, px} = textProperties; - return keyboard.focus({ - type: 'entityAttribute:' + tagId + ':' + attributeName, - position: hmdPosition, - rotation: hmdRotation, - inputText: inputText, - inputIndex: index, - inputValue: px, - page: page, - }); - } else { - return keyboard.fakeFocus({ - type: 'entityAttribute:' + tagId + ':' + attributeName, - }); - } - })(); - focusState.keyboardFocusState = keyboardFocusState; + _updatePages(); - keyboardFocusState.on('update', () => { - const {inputText} = keyboardFocusState; - tagMesh.setAttribute(attributeName, inputText); + return true; + } else if (onclick === 'entity:selectAll') { + for (let i = 0; i < npmState.tagSpecs.length; i++) { + const entitySpec = npmState.tagSpecs[i]; + entitySpec.selected = true; + } - _updatePages(); - }); - keyboardFocusState.on('blur', () => { - focusState.keyboardFocusState = null; + _updatePages(); - _updateNpm(); - _updatePages(); - }); + return true; + } else if (onclick === 'entity:clearAll') { + for (let i = 0; i < npmState.tagSpecs.length; i++) { + const entitySpec = npmState.tagSpecs[i]; + entitySpec.selected = false; + } _updatePages(); - } else if (action === 'set') { - tags.emit('setAttribute', { - id: tagId, - name: attributeName, - value: value, - }); - keyboard.tryBlur(); + return true; + } else if (onclick === 'entity:saveEntities') { + const entitySpecs = npmState.tagSpecs.filter( + entitySpec => entitySpec.selected + ); + _saveEntities(entitySpecs); - setTimeout(() => { - _updateNpm(); - _updatePages(); - }); - } else if (action === 'tweak') { - const attributeSpec = tags.getAttributeSpec(module, attributeName); - const {type} = attributeSpec; - - if (type === 'number') { - const newValue = (() => { - const {value} = hoverState; - const {min, max, step} = attributeSpec; - - let n = min + (value * (max - min)); - if (step > 0) { - n = _roundToDecimals(Math.round(n / step) * step, 8); + return true; + } else { + return false; + } + }; + const _clickAttribute = () => { + const hoverState = rend.getHoverState(side); + const { anchor } = hoverState; + const onclick = (anchor && anchor.onclick) || ''; + + let match; + if ( + (match = onclick.match( + /^entityAttribute:([^:]+):([^:]+)(?::([^:]+))?:(focus|set|tweak|pick|color|toggle|link|matrix)(?::([^:]+))?$/ + )) + ) { + const tagId = match[1]; + const attributeName = match[2]; + const key = match[3]; + const action = match[4]; + const value = match[5]; + + const tagMesh = tags + .getTagMeshes() + .find(tagMesh => tagMesh.item.id === tagId); + const { item } = tagMesh; + const { module, attributes } = item; + const attribute = + attributes[attributeName] || + tags.getAttributeSpec(module, attributeName); + const { value: attributeValue } = attribute; + + if (action === 'focus') { + const { value: hoverValue, target: page } = hoverState; + const { layer: { measures } } = page; + const { type } = tags.getAttributeSpec(module, attributeName); + + const inputText = typeUtils.castValueValueToString( + attributeValue, + type + ); + const textProperties = (() => { + if (type === 'text') { + const hoverValuePx = hoverValue * 400; + return biolumi.getTextPropertiesFromCoord( + measures[`entityAttribute:${tagId}:${attributeName}`], + inputText, + hoverValuePx + ); + } else if (type === 'number') { + const hoverValuePx = hoverValue * 100; + return biolumi.getTextPropertiesFromCoord( + measures[`entityAttribute:${tagId}:${attributeName}`], + inputText, + hoverValuePx + ); + } else if (type === 'color') { + const hoverValuePx = hoverValue * 300; + return biolumi.getTextPropertiesFromCoord( + measures[`entityAttribute:${tagId}:${attributeName}`], + inputText, + hoverValuePx + ); + } else { + return null; } - return n; })(); + const keyboardFocusState = (() => { + if (textProperties) { + const { hmd: hmdStatus } = webvr.getStatus(); + const { + worldPosition: hmdPosition, + worldRotation: hmdRotation, + } = hmdStatus; + const { index, px } = textProperties; + return keyboard.focus({ + type: 'entityAttribute:' + tagId + ':' + attributeName, + position: hmdPosition, + rotation: hmdRotation, + inputText: inputText, + inputIndex: index, + inputValue: px, + page: page, + }); + } else { + return keyboard.fakeFocus({ + type: 'entityAttribute:' + tagId + ':' + attributeName, + }); + } + })(); + focusState.keyboardFocusState = keyboardFocusState; - tags.emit('setAttribute', { - id: tagId, - name: attributeName, - value: newValue, - }); + keyboardFocusState.on('update', () => { + const { inputText } = keyboardFocusState; + tagMesh.setAttribute(attributeName, inputText); - keyboard.tryBlur(); + _updatePages(); + }); + keyboardFocusState.on('blur', () => { + focusState.keyboardFocusState = null; - setTimeout(() => { _updateNpm(); _updatePages(); }); - } else if (type ==='vector') { - const newKeyValue = (() => { - const {value} = hoverState; - const {min, max, step} = attributeSpec; - - let n = min + (value * (max - min)); - if (step > 0) { - n = _roundToDecimals(Math.round(n / step) * step, 8); - } - return n; - })(); - const newValue = attributeValue.slice(); - newValue[AXES.indexOf(key)] = newKeyValue; + _updatePages(); + } else if (action === 'set') { tags.emit('setAttribute', { id: tagId, name: attributeName, - value: newValue, + value: value, }); keyboard.tryBlur(); @@ -617,137 +656,220 @@ class Entity { _updateNpm(); _updatePages(); }); - } - } else if (action === 'pick') { - const keyboardFocusState = keyboard.fakeFocus({ - type: 'entityAttributeColor:' + tagId + ':' + attributeName, - }); - focusState.keyboardFocusState = keyboardFocusState; + } else if (action === 'tweak') { + const attributeSpec = tags.getAttributeSpec( + module, + attributeName + ); + const { type } = attributeSpec; + + if (type === 'number') { + const newValue = (() => { + const { value } = hoverState; + const { min, max, step } = attributeSpec; + + let n = min + value * (max - min); + if (step > 0) { + n = _roundToDecimals(Math.round(n / step) * step, 8); + } + return n; + })(); + + tags.emit('setAttribute', { + id: tagId, + name: attributeName, + value: newValue, + }); - keyboardFocusState.on('blur', () => { - focusState.keyboardFocusState = null; + keyboard.tryBlur(); - _updateNpm(); - _updatePages(); - }); + setTimeout(() => { + _updateNpm(); + _updatePages(); + }); + } else if (type === 'vector') { + const newKeyValue = (() => { + const { value } = hoverState; + const { min, max, step } = attributeSpec; + + let n = min + value * (max - min); + if (step > 0) { + n = _roundToDecimals(Math.round(n / step) * step, 8); + } + return n; + })(); + const newValue = attributeValue.slice(); + newValue[AXES.indexOf(key)] = newKeyValue; + + tags.emit('setAttribute', { + id: tagId, + name: attributeName, + value: newValue, + }); - _updatePages(); - } else if (action === 'color') { - const {value: x, crossValue: y} = hoverState; + keyboard.tryBlur(); - const c = new THREE.Color().fromArray(colorImgData.getColorArray(x, y)); - const newValue = '#' + c.getHexString(); + setTimeout(() => { + _updateNpm(); + _updatePages(); + }); + } + } else if (action === 'pick') { + const keyboardFocusState = keyboard.fakeFocus({ + type: 'entityAttributeColor:' + tagId + ':' + attributeName, + }); + focusState.keyboardFocusState = keyboardFocusState; - tags.emit('setAttribute', { - id: tagId, - name: attributeName, - value: newValue, - }); + keyboardFocusState.on('blur', () => { + focusState.keyboardFocusState = null; + + _updateNpm(); + _updatePages(); + }); - keyboard.tryBlur(); + _updatePages(); + } else if (action === 'color') { + const { value: x, crossValue: y } = hoverState; - _updatePages(); - } else if (action === 'toggle') { - const newValue = !attributeValue; + const c = new THREE.Color().fromArray( + colorImgData.getColorArray(x, y) + ); + const newValue = '#' + c.getHexString(); - tags.emit('setAttribute', { - id: tagId, - name: attributeName, - value: newValue, - }); + tags.emit('setAttribute', { + id: tagId, + name: attributeName, + value: newValue, + }); + + keyboard.tryBlur(); - setTimeout(() => { - _updateNpm(); _updatePages(); - }); - } else if (action === 'link') { - console.log('link', { // XXX - tagId, - attributeName, - attributeValue, - }); - } else if (action === 'matrix') { - const {keyboardFocusState: oldKeyboardFocusState} = focusState; + } else if (action === 'toggle') { + const newValue = !attributeValue; - if (oldKeyboardFocusState && /^entityAttributeMatrix:/.test(oldKeyboardFocusState.type)) { - keyboard.tryBlur(); - } else { - const keyboardFocusState = keyboard.fakeFocus({ - type: 'entityAttributeMatrix:' + tagId + ':' + attributeName, + tags.emit('setAttribute', { + id: tagId, + name: attributeName, + value: newValue, }); - focusState.keyboardFocusState = keyboardFocusState; - - const position = new THREE.Vector3(attributeValue[0], attributeValue[1], attributeValue[2]); - const rotation = new THREE.Quaternion(attributeValue[3], attributeValue[4], attributeValue[5], attributeValue[6]); - const scale = new THREE.Vector3(attributeValue[7], attributeValue[8], attributeValue[9]); - const transformGizmo = transform.makeTransformGizmo({ - position: position, - rotation: rotation, - scale: scale, - /* onpreview: (position, rotation, scale) => { + setTimeout(() => { + _updateNpm(); + _updatePages(); + }); + } else if (action === 'link') { + console.log('link', { + // XXX + tagId, + attributeName, + attributeValue, + }); + } else if (action === 'matrix') { + const { + keyboardFocusState: oldKeyboardFocusState, + } = focusState; + + if ( + oldKeyboardFocusState && + /^entityAttributeMatrix:/.test(oldKeyboardFocusState.type) + ) { + keyboard.tryBlur(); + } else { + const keyboardFocusState = keyboard.fakeFocus({ + type: + 'entityAttributeMatrix:' + tagId + ':' + attributeName, + }); + focusState.keyboardFocusState = keyboardFocusState; + + const position = new THREE.Vector3( + attributeValue[0], + attributeValue[1], + attributeValue[2] + ); + const rotation = new THREE.Quaternion( + attributeValue[3], + attributeValue[4], + attributeValue[5], + attributeValue[6] + ); + const scale = new THREE.Vector3( + attributeValue[7], + attributeValue[8], + attributeValue[9] + ); + + const transformGizmo = transform.makeTransformGizmo({ + position: position, + rotation: rotation, + scale: scale, + /* onpreview: (position, rotation, scale) => { this.updateBoundingBox(position, rotation, scale); }, */ - onupdate: (position, rotation, scale) => { - tags.emit('setAttribute', { - id: tagId, - name: attributeName, - value: position.toArray().concat(rotation.toArray()).concat(scale.toArray()), - }); - }, - }); - scene.add(transformGizmo); - transformGizmo.updateMatrixWorld(); + onupdate: (position, rotation, scale) => { + tags.emit('setAttribute', { + id: tagId, + name: attributeName, + value: position + .toArray() + .concat(rotation.toArray()) + .concat(scale.toArray()), + }); + }, + }); + scene.add(transformGizmo); + transformGizmo.updateMatrixWorld(); - keyboardFocusState.on('blur', () => { - focusState.keyboardFocusState = null; + keyboardFocusState.on('blur', () => { + focusState.keyboardFocusState = null; - transform.destroyTransformGizmo(transformGizmo); - scene.remove(transformGizmo); + transform.destroyTransformGizmo(transformGizmo); + scene.remove(transformGizmo); - _updatePages(); - }); + _updatePages(); + }); - _updatePages(); + _updatePages(); + } } + + return true; + } else { + return false; } + }; + const _clickMenuBackground = () => { + const hoverState = rend.getHoverState(side); + const { target } = hoverState; - return true; - } else { - return false; - } - }; - const _clickMenuBackground = () => { - const hoverState = rend.getHoverState(side); - const {target} = hoverState; - - if (target && target.mesh && target.mesh.parent === entityMesh) { - return true; - } else { - return false; - } - }; + if (target && target.mesh && target.mesh.parent === entityMesh) { + return true; + } else { + return false; + } + }; - if (_clickMenu() || _clickAttribute()) { - sfx.digi_select.trigger(); + if (_clickMenu() || _clickAttribute()) { + sfx.digi_select.trigger(); - e.stopImmediatePropagation(); - } else if (_clickMenuBackground()) { - sfx.digi_plink.trigger(); + e.stopImmediatePropagation(); + } else if (_clickMenuBackground()) { + sfx.digi_plink.trigger(); - e.stopImmediatePropagation(); - } - }; - input.on('trigger', _trigger); - - cleanups.push(() => { - rend.removeListener('tabchange', _tabchange); - rend.removeListener('entitychange', _entitychange); - rend.removeListener('saveAllEntities', _saveAllEntities); - input.removeListener('trigger', _trigger); - }); + e.stopImmediatePropagation(); + } + }; + input.on('trigger', _trigger); + + cleanups.push(() => { + rend.removeListener('tabchange', _tabchange); + rend.removeListener('entitychange', _entitychange); + rend.removeListener('saveAllEntities', _saveAllEntities); + input.removeListener('trigger', _trigger); + }); + } } - }); + ); } unmount() { @@ -755,7 +877,11 @@ class Entity { } } -const _makeId = () => Math.random().toString(36).substring(7); -const _roundToDecimals = (value, decimals) => Number(Math.round(value+'e'+decimals)+'e-'+decimals); +const _makeId = () => + Math.random() + .toString(36) + .substring(7); +const _roundToDecimals = (value, decimals) => + Number(Math.round(value + 'e' + decimals) + 'e-' + decimals); module.exports = Entity; diff --git a/core/engines/file/client.js b/core/engines/file/client.js index 4545f78f9..8f3da6189 100644 --- a/core/engines/file/client.js +++ b/core/engines/file/client.js @@ -4,7 +4,6 @@ import { WORLD_WIDTH, WORLD_HEIGHT, WORLD_DEPTH, - TAGS_WIDTH, TAGS_HEIGHT, TAGS_WORLD_WIDTH, @@ -20,8 +19,13 @@ class FileEngine { } mount() { - const {_archae: archae} = this; - const {metadata: {site: {url: siteUrl}, server: {url: serverUrl, enabled: serverEnabled}}} = archae; + const { _archae: archae } = this; + const { + metadata: { + site: { url: siteUrl }, + server: { url: serverUrl, enabled: serverEnabled }, + }, + } = archae; const cleanups = []; this._cleanup = () => { @@ -36,41 +40,32 @@ class FileEngine { live = false; }); - return archae.requestPlugins([ - '/core/engines/three', - '/core/engines/input', - '/core/engines/webvr', - '/core/engines/biolumi', - '/core/engines/resource', - '/core/engines/rend', - '/core/engines/tags', - // '/core/engines/fs', - '/core/engines/world', - '/core/engines/keyboard', - '/core/utils/creature-utils', - ]).then(([ - three, - input, - webvr, - biolumi, - resource, - rend, - tags, - // fs, - world, - keyboard, - creatureUtils, - ]) => { - if (live) { - const {THREE} = three; - const {sfx} = resource; - - const fileRenderer = fileRender.makeRenderer({creatureUtils}); - - const transparentImg = biolumi.getTransparentImg(); - // const blackImg = biolumi.getBlackImg(); - - /* const _decorateFile = item => { + return archae + .requestPlugins([ + '/core/engines/three', + '/core/engines/input', + '/core/engines/webvr', + '/core/engines/biolumi', + '/core/engines/resource', + '/core/engines/rend', + '/core/engines/tags', + // '/core/engines/fs', + '/core/engines/world', + '/core/engines/keyboard', + '/core/utils/creature-utils', + ]) + .then(([three, input, webvr, biolumi, resource, rend, tags, // fs, + world, keyboard, creatureUtils]) => { + if (live) { + const { THREE } = three; + const { sfx } = resource; + + const fileRenderer = fileRender.makeRenderer({ creatureUtils }); + + const transparentImg = biolumi.getTransparentImg(); + // const blackImg = biolumi.getBlackImg(); + + /* const _decorateFile = item => { const {id, name, mimeType, instancing, paused, value} = item; const mode = fs.getFileMode(mimeType); const media = null; @@ -200,20 +195,20 @@ class FileEngine { } }; */ - const updatePromises = []; - const _cancelNpm = () => { - if (updatePromises.length > 0) { - for (let i = 0; i < updatePromises.length; i++) { - const updatePromise = updatePromises[i]; - updatePromise.cancel(); + const updatePromises = []; + const _cancelNpm = () => { + if (updatePromises.length > 0) { + for (let i = 0; i < updatePromises.length; i++) { + const updatePromise = updatePromises[i]; + updatePromise.cancel(); + } + updatePromises.length = 0; } - updatePromises.length = 0; - } - }; - const _updateNpm = () => { - _cancelNpm(); + }; + const _updateNpm = () => { + _cancelNpm(); - /* const {inputText} = npmState; + /* const {inputText} = npmState; const files = tags.getTagMeshes() .filter(({item}) => @@ -241,312 +236,341 @@ class FileEngine { _updatePages(); }); } */ - const files = []; + const files = []; - npmState.loading = false; - npmState.page = 0; - npmState.tagSpecs = files; - npmState.numTags = files.length; + npmState.loading = false; + npmState.page = 0; + npmState.tagSpecs = files; + npmState.numTags = files.length; - npmState.loading = false; - }; + npmState.loading = false; + }; - const npmState = { - loading: true, - inputText: '', - tagSpecs: [], - numTags: 0, - file: null, - value: 0, - page: 0, - }; - const focusState = { - keyboardFocusState: null, - }; - const npmCacheState = { - loaded: false, - }; + const npmState = { + loading: true, + inputText: '', + tagSpecs: [], + numTags: 0, + file: null, + value: 0, + page: 0, + }; + const focusState = { + keyboardFocusState: null, + }; + const npmCacheState = { + loaded: false, + }; - const fileMesh = (() => { - const object = new THREE.Object3D(); - object.visible = false; + const fileMesh = (() => { + const object = new THREE.Object3D(); + object.visible = false; - const planeMesh = (() => { - const worldUi = biolumi.makeUi({ - width: WIDTH, - height: HEIGHT, - }); - const mesh = worldUi.makePage(({ - npm: { - loading, - inputText, - tagSpecs, - numTags, - file, - value, - page, - }, - focus: { - keyboardFocusState, - }, - }) => { - const {type = '', inputValue = 0} = keyboardFocusState || {}; - const focus = type === 'file:search'; - - return { - type: 'html', - src: fileRenderer.getFilePageSrc({ - loading, - inputText, - inputValue, - tagSpecs, - numTags, - file, - value, - page, - focus, - }), - x: 0, - y: 0, - w: WIDTH, - h: HEIGHT, + const planeMesh = (() => { + const worldUi = biolumi.makeUi({ + width: WIDTH, + height: HEIGHT, + }); + const mesh = worldUi.makePage( + ({ + npm: { + loading, + inputText, + tagSpecs, + numTags, + file, + value, + page, + }, + focus: { keyboardFocusState }, + }) => { + const { type = '', inputValue = 0 } = + keyboardFocusState || {}; + const focus = type === 'file:search'; + + return { + type: 'html', + src: fileRenderer.getFilePageSrc({ + loading, + inputText, + inputValue, + tagSpecs, + numTags, + file, + value, + page, + focus, + }), + x: 0, + y: 0, + w: WIDTH, + h: HEIGHT, + }; + }, + { + type: 'file', + state: { + npm: npmState, + focus: focusState, + }, + worldWidth: WORLD_WIDTH, + worldHeight: WORLD_HEIGHT, + isEnabled: () => rend.isOpen(), + } + ); + mesh.receiveShadow = true; + + const { page } = mesh; + rend.addPage(page); + + cleanups.push(() => { + rend.removePage(page); + }); + + return mesh; + })(); + object.add(planeMesh); + object.planeMesh = planeMesh; + + const size = 480; + const worldWidth = size / WIDTH * WORLD_WIDTH; + const worldHeight = size / HEIGHT * WORLD_HEIGHT; + const detailsMesh = (() => { + const geometry = new THREE.PlaneBufferGeometry( + worldWidth, + worldHeight + ); + const texture = new THREE.Texture( + transparentImg, + THREE.UVMapping, + THREE.ClampToEdgeWrapping, + THREE.ClampToEdgeWrapping, + THREE.NearestFilter, + THREE.NearestFilter, + THREE.RGBAFormat, + THREE.UnsignedByteType, + 16 + ); + texture.needsUpdate = true; + const material = new THREE.MeshBasicMaterial({ + color: 0xffffff, + map: texture, + side: THREE.DoubleSide, + }); + + const mesh = new THREE.Mesh(geometry, material); + mesh.position.set( + -(WORLD_WIDTH / 2) + worldWidth / 2 + 30 / WIDTH * WORLD_WIDTH, + WORLD_HEIGHT / 2 - + worldHeight / 2 - + (30 + 80) / HEIGHT * WORLD_HEIGHT, + 0.001 + ); + mesh.visible = false; + mesh.setAspectRatio = aspectRatio => { + mesh.scale.x = aspectRatio < 1 ? aspectRatio : 1; + mesh.scale.y = aspectRatio > 1 ? 1 / aspectRatio : 1; + mesh.updateMatrixWorld(); }; - }, { - type: 'file', - state: { - npm: npmState, - focus: focusState, + + return mesh; + })(); + object.add(detailsMesh); + object.detailsMesh = detailsMesh; + + const anchors = [ + { + rect: { + left: 0, + right: size, + top: 0, + bottom: size, + }, + onclick: 'file:media', + }, + ]; + const detailsPage = biolumi.makePage(null, { + type: 'file:media', + width: size, + height: size, + worldWidth: worldWidth, + worldHeight: worldHeight, + color: [1, 1, 1, 0], + layer: { + getAnchors: () => anchors, }, - worldWidth: WORLD_WIDTH, - worldHeight: WORLD_HEIGHT, - isEnabled: () => rend.isOpen(), }); - mesh.receiveShadow = true; - - const {page} = mesh; - rend.addPage(page); + detailsPage.mesh.position.copy(detailsMesh.position); + detailsPage.mesh.visible = false; + detailsPage.setId = id => { + anchors[0].onclick = id ? 'file:media:' + id : 'file:media'; + }; + object.add(detailsPage.mesh); + object.detailsPage = detailsPage; + rend.addPage(detailsPage); cleanups.push(() => { - rend.removePage(page); + rend.removePage(detailsPage); }); - return mesh; + return object; })(); - object.add(planeMesh); - object.planeMesh = planeMesh; - - const size = 480; - const worldWidth = (size / WIDTH) * WORLD_WIDTH; - const worldHeight = (size / HEIGHT) * WORLD_HEIGHT; - const detailsMesh = (() => { - const geometry = new THREE.PlaneBufferGeometry(worldWidth, worldHeight); - const texture = new THREE.Texture( - transparentImg, - THREE.UVMapping, - THREE.ClampToEdgeWrapping, - THREE.ClampToEdgeWrapping, - THREE.NearestFilter, - THREE.NearestFilter, - THREE.RGBAFormat, - THREE.UnsignedByteType, - 16 - ); - texture.needsUpdate = true; - const material = new THREE.MeshBasicMaterial({ - color: 0xFFFFFF, - map: texture, - side: THREE.DoubleSide, - }); + rend.registerMenuMesh('fileMesh', fileMesh); + fileMesh.updateMatrixWorld(); - const mesh = new THREE.Mesh(geometry, material); - mesh.position.set( - -(WORLD_WIDTH / 2) + (worldWidth / 2) + ((30 / WIDTH) * WORLD_WIDTH), - (WORLD_HEIGHT / 2) - (worldHeight / 2) - (((30 + 80) / HEIGHT) * WORLD_HEIGHT), - 0.001 - ); - mesh.visible = false; - mesh.setAspectRatio = aspectRatio => { - mesh.scale.x = aspectRatio < 1 ? aspectRatio : 1; - mesh.scale.y = aspectRatio > 1 ? (1 / aspectRatio) : 1; - mesh.updateMatrixWorld(); - }; - - return mesh; - })(); - object.add(detailsMesh); - object.detailsMesh = detailsMesh; - - const anchors = [ - { - rect: { - left: 0, - right: size, - top: 0, - bottom: size, - }, - onclick: 'file:media', - }, - ]; - const detailsPage = biolumi.makePage(null, { - type: 'file:media', - width: size, - height: size, - worldWidth: worldWidth, - worldHeight: worldHeight, - color: [1, 1, 1, 0], - layer: { - getAnchors: () => anchors, - }, - }); - detailsPage.mesh.position.copy(detailsMesh.position); - detailsPage.mesh.visible = false; - detailsPage.setId = id => { - anchors[0].onclick = id ? ('file:media:' + id) : 'file:media'; + const _updatePages = () => { + const { planeMesh } = fileMesh; + const { page } = planeMesh; + return page.update(); }; - object.add(detailsPage.mesh); - object.detailsPage = detailsPage; - rend.addPage(detailsPage); - - cleanups.push(() => { - rend.removePage(detailsPage); - }); - - return object; - })(); - rend.registerMenuMesh('fileMesh', fileMesh); - fileMesh.updateMatrixWorld(); - - const _updatePages = () => { - const {planeMesh} = fileMesh; - const {page} = planeMesh; - return page.update(); - }; - _updatePages(); + _updatePages(); - const _tabchange = tab => { - if (tab === 'file') { - keyboard.tryBlur(); - - const {loaded} = npmCacheState; - if (!loaded) { - _updateNpm(); - _updatePages(); - - npmCacheState.loaded = true; - } - } - }; - rend.on('tabchange', _tabchange); - - const _trigger = e => { - const {side} = e; - - const _clickMenu = () => { - const hoverState = rend.getHoverState(side); - const {anchor} = hoverState; - const onclick = (anchor && anchor.onclick) || ''; - - let match; - if (onclick === 'file:focus') { - const {inputText} = npmState; - const {value, target: page} = hoverState; - const {layer: {measures}} = page; - const valuePx = value * (WIDTH - (250 + (30 * 2))); - const {index, px} = biolumi.getTextPropertiesFromCoord(measures['file:search'], inputText, valuePx); - const {hmd: hmdStatus} = webvr.getStatus(); - const {worldPosition: hmdPosition, worldRotation: hmdRotation} = hmdStatus; - const keyboardFocusState = keyboard.focus({ - type: 'file:search', - position: hmdPosition, - rotation: hmdRotation, - inputText: inputText, - inputIndex: index, - inputValue: px, - page: page, - }); - focusState.keyboardFocusState = keyboardFocusState; - - keyboardFocusState.on('update', () => { - const {inputText: keyboardInputText} = keyboardFocusState; - const {inputText: npmInputText} = npmState; - - if (keyboardInputText !== npmInputText) { - npmState.inputText = keyboardInputText; - - _updateNpm(); - } + const _tabchange = tab => { + if (tab === 'file') { + keyboard.tryBlur(); + const { loaded } = npmCacheState; + if (!loaded) { + _updateNpm(); _updatePages(); - }); - keyboardFocusState.on('blur', () => { - focusState.keyboardFocusState = null; - _updatePages(); - }); + npmCacheState.loaded = true; + } + } + }; + rend.on('tabchange', _tabchange); + + const _trigger = e => { + const { side } = e; + + const _clickMenu = () => { + const hoverState = rend.getHoverState(side); + const { anchor } = hoverState; + const onclick = (anchor && anchor.onclick) || ''; + + let match; + if (onclick === 'file:focus') { + const { inputText } = npmState; + const { value, target: page } = hoverState; + const { layer: { measures } } = page; + const valuePx = value * (WIDTH - (250 + 30 * 2)); + const { index, px } = biolumi.getTextPropertiesFromCoord( + measures['file:search'], + inputText, + valuePx + ); + const { hmd: hmdStatus } = webvr.getStatus(); + const { + worldPosition: hmdPosition, + worldRotation: hmdRotation, + } = hmdStatus; + const keyboardFocusState = keyboard.focus({ + type: 'file:search', + position: hmdPosition, + rotation: hmdRotation, + inputText: inputText, + inputIndex: index, + inputValue: px, + page: page, + }); + focusState.keyboardFocusState = keyboardFocusState; - _updatePages(); + keyboardFocusState.on('update', () => { + const { inputText: keyboardInputText } = keyboardFocusState; + const { inputText: npmInputText } = npmState; - return true; - } else if (match = onclick.match(/^file:(up|down)$/)) { - const direction = match[1]; + if (keyboardInputText !== npmInputText) { + npmState.inputText = keyboardInputText; - npmState.page += (direction === 'up' ? -1 : 1); + _updateNpm(); + } - _updatePages(); + _updatePages(); + }); + keyboardFocusState.on('blur', () => { + focusState.keyboardFocusState = null; - return true; - } else if (match = onclick.match(/^file:file:(.+)$/)) { - const id = match[1]; + _updatePages(); + }); - const itemSpec = npmState.tagSpecs.find(tagSpec => tagSpec.id === id); - _setFile(itemSpec); + _updatePages(); - return true; - } else if (onclick === 'file:back') { - _setFile(null); + return true; + } else if ((match = onclick.match(/^file:(up|down)$/))) { + const direction = match[1]; - return true; - } else if (match = onclick.match(/^file:media:(.+)$/)) { - const id = match[1]; + npmState.page += direction === 'up' ? -1 : 1; - const itemSpec = npmState.tagSpecs.find(tagSpec => tagSpec.id === id); - const {media} = itemSpec; + _updatePages(); - if (media && (media.tagName === 'AUDIO' || media.tagName === 'VIDEO')) { - if (media.paused) { - media.play(); - } else { - media.pause(); + return true; + } else if ((match = onclick.match(/^file:file:(.+)$/))) { + const id = match[1]; + + const itemSpec = npmState.tagSpecs.find( + tagSpec => tagSpec.id === id + ); + _setFile(itemSpec); + + return true; + } else if (onclick === 'file:back') { + _setFile(null); + + return true; + } else if ((match = onclick.match(/^file:media:(.+)$/))) { + const id = match[1]; + + const itemSpec = npmState.tagSpecs.find( + tagSpec => tagSpec.id === id + ); + const { media } = itemSpec; + + if ( + media && + (media.tagName === 'AUDIO' || media.tagName === 'VIDEO') + ) { + if (media.paused) { + media.play(); + } else { + media.pause(); + } } - } - return true; - } else if (match = onclick.match(/^file:seek:(.+)$/)) { - const id = match[1]; + return true; + } else if ((match = onclick.match(/^file:seek:(.+)$/))) { + const id = match[1]; - const itemSpec = npmState.tagSpecs.find(tagSpec => tagSpec.id === id); - const {media} = itemSpec; + const itemSpec = npmState.tagSpecs.find( + tagSpec => tagSpec.id === id + ); + const { media } = itemSpec; - if (media && (media.tagName === 'AUDIO' || media.tagName === 'VIDEO')) { - const {value} = hoverState; - media.currentTime = value * media.duration; + if ( + media && + (media.tagName === 'AUDIO' || media.tagName === 'VIDEO') + ) { + const { value } = hoverState; + media.currentTime = value * media.duration; - npmState.value = value; + npmState.value = value; - _updatePages(); - } + _updatePages(); + } - return true; - } else if (match = onclick.match(/^file:remove:(.+)$/)) { - const id = match[1]; + return true; + } else if ((match = onclick.match(/^file:remove:(.+)$/))) { + const id = match[1]; - world.removeTag(id); + world.removeTag(id); - npmState.tagSpecs.splice(npmState.tagSpecs.findIndex(tagSpec => tagSpec.id === id), 1); - _setFile(null); + npmState.tagSpecs.splice( + npmState.tagSpecs.findIndex(tagSpec => tagSpec.id === id), + 1 + ); + _setFile(null); - return true; - /* } else if (match = onclick.match(/^file:loadEntities:(.+)$/)) { + return true; + /* } else if (match = onclick.match(/^file:loadEntities:(.+)$/)) { const id = match[1]; const file = npmState.tagSpecs.find(tagSpec => tagSpec.id === id); @@ -591,39 +615,39 @@ class FileEngine { }); return true; */ - } else { - return false; - } - }; - const _clickMenuBackground = () => { - const hoverState = rend.getHoverState(side); - const {target} = hoverState; - - if (target && target.mesh && target.mesh.parent === fileMesh) { - return true; - } else { - return false; - } - }; + } else { + return false; + } + }; + const _clickMenuBackground = () => { + const hoverState = rend.getHoverState(side); + const { target } = hoverState; + + if (target && target.mesh && target.mesh.parent === fileMesh) { + return true; + } else { + return false; + } + }; - if (_clickMenu()) { - sfx.digi_select.trigger(); + if (_clickMenu()) { + sfx.digi_select.trigger(); - e.stopImmediatePropagation(); - } else if (_clickMenuBackground()) { - sfx.digi_plink.trigger(); + e.stopImmediatePropagation(); + } else if (_clickMenuBackground()) { + sfx.digi_plink.trigger(); - e.stopImmediatePropagation(); - } - }; - input.on('trigger', _trigger); + e.stopImmediatePropagation(); + } + }; + input.on('trigger', _trigger); - cleanups.push(() => { - rend.removeListener('tabchange', _tabchange); - input.removeListener('trigger', _trigger); - }); + cleanups.push(() => { + rend.removeListener('tabchange', _tabchange); + input.removeListener('trigger', _trigger); + }); - /* const _setFile = itemSpec => { + /* const _setFile = itemSpec => { if (itemSpec) { npmState.file = itemSpec; @@ -697,18 +721,21 @@ class FileEngine { _setFile(fileItem); }; */ - return { - // addFile: _addFile, - }; - } - }); + return { + // addFile: _addFile, + }; + } + }); } unmount() { this._cleanup(); } } -const _makeId = () => Math.random().toString(36).substring(7); +const _makeId = () => + Math.random() + .toString(36) + .substring(7); const _resizeImage = (img, width, height) => { const canvas = document.createElement('canvas'); canvas.width = width; diff --git a/core/engines/fs/client.js b/core/engines/fs/client.js index 3c133bca5..89a8fc437 100644 --- a/core/engines/fs/client.js +++ b/core/engines/fs/client.js @@ -8,33 +8,31 @@ class Fs { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; let live = true; this._cleanup = () => { live = false; }; - return archae.requestPlugins([ - '/core/engines/three', - '/core/engines/webvr', - '/core/utils/js-utils', - ]).then(([ - three, - webvr, - jsUtils, - ]) => { - if (live) { - const {THREE} = three; - const {events} = jsUtils; - const {EventEmitter} = events; - - const forwardVector = new THREE.Vector3(0, 0, -1); - const localVector = new THREE.Vector3(); - const localVector2 = new THREE.Vector3(); - const localVector3 = new THREE.Vector3(); - - /* const libRequestPromises = {}; + return archae + .requestPlugins([ + '/core/engines/three', + '/core/engines/webvr', + '/core/utils/js-utils', + ]) + .then(([three, webvr, jsUtils]) => { + if (live) { + const { THREE } = three; + const { events } = jsUtils; + const { EventEmitter } = events; + + const forwardVector = new THREE.Vector3(0, 0, -1); + const localVector = new THREE.Vector3(); + const localVector2 = new THREE.Vector3(); + const localVector3 = new THREE.Vector3(); + + /* const libRequestPromises = {}; const _requestLib = libPath => { let entry = libRequestPromises[libPath]; if (!entry) { @@ -89,83 +87,95 @@ class Fs { const _requestGLTFLoader = () => _requestLib('three-extra/GLTFLoader.js') .then(GLTFLoader => GLTFLoader(THREE)); */ - const dragover = e => { - e.preventDefault(); - }; - document.addEventListener('dragover', dragover); - const drop = e => { - e.preventDefault(); - - const {dataTransfer: {items}} = e; - if (items.length > 0) { - const _getFiles = items => { - const entries = Array.from(items) - .map(item => item.webkitGetAsEntry()) - .filter(entry => entry !== null); - - const files = []; - const _recurseEntries = entries => Promise.all(entries.map(_recurseEntry)); - const _recurseEntry = entry => new Promise((accept, reject) => { - if (entry.isFile) { - entry.file(file => { - file.path = entry.fullPath; - - files.push(file); - - accept(); - }); - } else if (entry.isDirectory) { - const directoryReader = entry.createReader(); - directoryReader.readEntries(entries => { - _recurseEntries(Array.from(entries)) - .then(() => { + const dragover = e => { + e.preventDefault(); + }; + document.addEventListener('dragover', dragover); + const drop = e => { + e.preventDefault(); + + const { dataTransfer: { items } } = e; + if (items.length > 0) { + const _getFiles = items => { + const entries = Array.from(items) + .map(item => item.webkitGetAsEntry()) + .filter(entry => entry !== null); + + const files = []; + const _recurseEntries = entries => + Promise.all(entries.map(_recurseEntry)); + const _recurseEntry = entry => + new Promise((accept, reject) => { + if (entry.isFile) { + entry.file(file => { + file.path = entry.fullPath; + + files.push(file); + accept(); }); + } else if (entry.isDirectory) { + const directoryReader = entry.createReader(); + directoryReader.readEntries(entries => { + _recurseEntries(Array.from(entries)).then(() => { + accept(); + }); + }); + } else { + accept(); + } }); - } else { - accept(); - } - }); - return _recurseEntries(entries) - .then(() => files); - }; - - _getFiles(items) - .then(files => Promise.all(files.map((file, i) => { - const {type} = file; - const remoteFile = fsApi.makeRemoteFile(); - const dropMatrix = (() => { - const {hmd} = webvr.getStatus(); - const {worldPosition: hmdPosition, worldRotation: hmdRotation, worldScale: hmdScale} = hmd; - const width = 0.2; - const fullWidth = (files.length - 1) * width; - localVector.copy(hmdPosition) - .add( - localVector2.copy(forwardVector).multiplyScalar(0.5) - .add(localVector3.set(-fullWidth/2 + i*width, 0, 0)) - .applyQuaternion(hmdRotation) - ); - return localVector.toArray().concat(hmdRotation.toArray()).concat(hmdScale.toArray()); - })(); + return _recurseEntries(entries).then(() => files); + }; - return remoteFile.write(file) - .then(() => { - fsApi.emit('upload', { - file: remoteFile, - dropMatrix, + _getFiles(items).then(files => + Promise.all( + files.map((file, i) => { + const { type } = file; + const remoteFile = fsApi.makeRemoteFile(); + const dropMatrix = (() => { + const { hmd } = webvr.getStatus(); + const { + worldPosition: hmdPosition, + worldRotation: hmdRotation, + worldScale: hmdScale, + } = hmd; + const width = 0.2; + const fullWidth = (files.length - 1) * width; + localVector.copy(hmdPosition).add( + localVector2 + .copy(forwardVector) + .multiplyScalar(0.5) + .add( + localVector3.set(-fullWidth / 2 + i * width, 0, 0) + ) + .applyQuaternion(hmdRotation) + ); + return localVector + .toArray() + .concat(hmdRotation.toArray()) + .concat(hmdScale.toArray()); + })(); + + return remoteFile.write(file).then(() => { + fsApi.emit('upload', { + file: remoteFile, + dropMatrix, + }); }); - }); - }))); - } - }; - document.addEventListener('drop', drop); + }) + ) + ); + } + }; + document.addEventListener('drop', drop); - this._cleanup = () => { - document.removeEventListener('dragover', dragover); - document.removeEventListener('drop', drop); - }; + this._cleanup = () => { + document.removeEventListener('dragover', dragover); + document.removeEventListener('drop', drop); + }; - /* class FsFile { + /* class FsFile { constructor(url) { this.url = url; } @@ -357,64 +367,65 @@ class Fs { } } */ - const _resBlob = res => { - if (res.status >= 200 && res.status < 300) { - return res.blob(); - } else if (res.status === 404) { - return Promise.resolve(null); - } else { - return Promise.reject({ - status: res.status, - stack: 'API returned invalid status code: ' + res.status, - }); - } - }; - - class RemoteFile { - constructor(id) { - this.n = id !== undefined ? (typeof id === 'number' ? id : murmur(id)) : _makeN(); - } + const _resBlob = res => { + if (res.status >= 200 && res.status < 300) { + return res.blob(); + } else if (res.status === 404) { + return Promise.resolve(null); + } else { + return Promise.reject({ + status: res.status, + stack: 'API returned invalid status code: ' + res.status, + }); + } + }; + + class RemoteFile { + constructor(id) { + this.n = + id !== undefined + ? typeof id === 'number' ? id : murmur(id) + : _makeN(); + } - /* getFileName() { + /* getFileName() { return this.id.match(/([^\[\.]*)/)[1]; } */ - getUrl() { - return `/archae/fs/hash/${this.n}`; - } + getUrl() { + return `/archae/fs/hash/${this.n}`; + } - read() { - return fetch(this.getUrl(), { - credentials: 'include', - }) - .then(_resBlob); - } + read() { + return fetch(this.getUrl(), { + credentials: 'include', + }).then(_resBlob); + } - write(d) { - return fetch(this.getUrl(), { - method: 'PUT', - body: d, - credentials: 'include', - }) - .then(_resBlob); - } + write(d) { + return fetch(this.getUrl(), { + method: 'PUT', + body: d, + credentials: 'include', + }).then(_resBlob); + } - download() { - const a = document.createElement('a'); - a.href = this.getUrl(); - a.download = 'file'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); + download() { + const a = document.createElement('a'); + a.href = this.getUrl(); + a.download = 'file'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + } } - } - class FsApi extends EventEmitter { - makeRemoteFile(id) { - return new RemoteFile(id); - } + class FsApi extends EventEmitter { + makeRemoteFile(id) { + return new RemoteFile(id); + } - /* makeFile(url) { + /* makeFile(url) { return new FsFile(url); } @@ -492,15 +503,15 @@ class Fs { }); } */ - dragover(e) { - dragover(e); + dragover(e) { + dragover(e); + } } - } - const fsApi = new FsApi(); - return fsApi; - } - }); + const fsApi = new FsApi(); + return fsApi; + } + }); } unmount() { diff --git a/core/engines/fs/server.js b/core/engines/fs/server.js index 59a27d2c2..912bc9000 100644 --- a/core/engines/fs/server.js +++ b/core/engines/fs/server.js @@ -10,8 +10,8 @@ class Fs { } mount() { - const {_archae: archae} = this; - const {express, app, dirname, dataDirectory} = archae.getCore(); + const { _archae: archae } = this; + const { express, app, dirname, dataDirectory } = archae.getCore(); const cleanups = []; this._cleanup = () => { @@ -26,59 +26,65 @@ class Fs { }); const fsPath = path.join(dirname, dataDirectory, 'fs'); - const _ensureDirectory = p => new Promise((accept, reject) => { - mkdirp(p, err => { - if (!err) { - accept(); - } else { - reject(err); - } + const _ensureDirectory = p => + new Promise((accept, reject) => { + mkdirp(p, err => { + if (!err) { + accept(); + } else { + reject(err); + } + }); }); - }); - return _ensureDirectory(fsPath) - .then(() => { - if (live) { - const fsStatic = express.static(fsPath); - function serveFsDownload(req, res, next) { - const fileName = req.params[0] === 'name' ? String(murmur(req.params[1])) : req.params[1]; - req.url = '/' + fileName; - fsStatic(req, res, next); - } - app.get(/^\/archae\/fs\/(name|hash)\/([^\/]+)$/, serveFsDownload); - - function serveFsUpload(req, res, next) { - const fileName = req.params[0] === 'name' ? String(murmur(req.params[1])) : req.params[1]; - const ws = fs.createWriteStream(path.join(fsPath, fileName)); - req.pipe(ws); - ws.on('finish', err => { - res.send(); - }); - ws.on('error', err => { - res.status(500); - res.json({ - error: err.stack, - }); + return _ensureDirectory(fsPath).then(() => { + if (live) { + const fsStatic = express.static(fsPath); + function serveFsDownload(req, res, next) { + const fileName = + req.params[0] === 'name' + ? String(murmur(req.params[1])) + : req.params[1]; + req.url = '/' + fileName; + fsStatic(req, res, next); + } + app.get(/^\/archae\/fs\/(name|hash)\/([^\/]+)$/, serveFsDownload); + + function serveFsUpload(req, res, next) { + const fileName = + req.params[0] === 'name' + ? String(murmur(req.params[1])) + : req.params[1]; + const ws = fs.createWriteStream(path.join(fsPath, fileName)); + req.pipe(ws); + ws.on('finish', err => { + res.send(); + }); + ws.on('error', err => { + res.status(500); + res.json({ + error: err.stack, }); - } - app.put(/^\/archae\/fs\/(name|hash)\/([^\/]+)$/, serveFsUpload); - - cleanups.push(() => { - function removeMiddlewares(route, i, routes) { - if ( - route.handle.name === 'serveFsDownload' || - route.handle.name === 'serveFsUpload' - ) { - routes.splice(i, 1); - } - if (route.route) { - route.route.stack.forEach(removeMiddlewares); - } - } - app._router.stack.forEach(removeMiddlewares); }); + } + app.put(/^\/archae\/fs\/(name|hash)\/([^\/]+)$/, serveFsUpload); + + cleanups.push(() => { + function removeMiddlewares(route, i, routes) { + if ( + route.handle.name === 'serveFsDownload' || + route.handle.name === 'serveFsUpload' + ) { + routes.splice(i, 1); + } + if (route.route) { + route.route.stack.forEach(removeMiddlewares); + } + } + app._router.stack.forEach(removeMiddlewares); + }); - /* class FsFile { + /* class FsFile { constructor(dirname, pathname) { this.dirname = dirname; this.pathname = pathname; @@ -127,8 +133,8 @@ class Fs { return { makeFile: _makeFile, }; */ - } - }); + } + }); } unmount() { diff --git a/core/engines/hand/client.js b/core/engines/hand/client.js index 634253fe3..adc6a74f6 100644 --- a/core/engines/hand/client.js +++ b/core/engines/hand/client.js @@ -9,8 +9,10 @@ class Hand { } mount() { - const {_archae: archae} = this; - const {metadata: {site: {url: siteUrl}, server: {enabled: serverEnabled}}} = archae; + const { _archae: archae } = this; + const { + metadata: { site: { url: siteUrl }, server: { enabled: serverEnabled } }, + } = archae; const cleanups = []; this._cleanup = () => { @@ -25,439 +27,525 @@ class Hand { live = false; }); - return archae.requestPlugins([ - '/core/engines/three', - '/core/engines/input', - '/core/engines/webvr', - '/core/engines/rend', - '/core/engines/multiplayer', - '/core/utils/js-utils', - '/core/utils/network-utils', - ]).then(([ - three, - input, - webvr, - rend, - multiplayer, - jsUtils, - networkUtils, - ]) => { - if (live) { - const {THREE, scene, camera} = three; - const {events} = jsUtils; - const {EventEmitter} = events; - const {AutoWs} = networkUtils; - - const buffer = new ArrayBuffer(protocolUtils.BUFFER_SIZE); - - const localUserId = multiplayer.getId(); - - const grabbables = {}; - - const connection = (() => { - const connection = new AutoWs(_relativeWsUrl('archae/handWs?id=' + localUserId)); - connection.on('message', msg => { - const {data} = msg; - - if (typeof data === 'string') { - const m = JSON.parse(data); - const {type} = m; - - if (type === 'grab') { - const {args} = m; - const [n, userId, side] = args; - - const grabbable = grabbables[n]; - grabbable.userId = userId; - grabbable.side = side; - - const e = { - userId, - side, - grabbable, - }; - grabbable.emit('grab', e); - handApi.emit('grab', e); - } else if (type === 'release') { - const {args} = m; - const [n] = args; - - const grabbable = grabbables[n]; - const {userId, side} = grabbable; - grabbable.userId = null; - grabbable.side = null; - - const e = { - userId, - side, - grabbable, - live: true, - stopImmediatePropagation() { - e.live = false; - }, - }; - grabbable.emit('release', e); - handApi.emit('release', e); - } else if (type === 'data') { - const {args} = m; - const [n, key, value] = args; - - const grabbable = grabbables[n]; - if (grabbable) { - grabbable.setData(key, value); + return archae + .requestPlugins([ + '/core/engines/three', + '/core/engines/input', + '/core/engines/webvr', + '/core/engines/rend', + '/core/engines/multiplayer', + '/core/utils/js-utils', + '/core/utils/network-utils', + ]) + .then( + ([three, input, webvr, rend, multiplayer, jsUtils, networkUtils]) => { + if (live) { + const { THREE, scene, camera } = three; + const { events } = jsUtils; + const { EventEmitter } = events; + const { AutoWs } = networkUtils; + + const buffer = new ArrayBuffer(protocolUtils.BUFFER_SIZE); + + const localUserId = multiplayer.getId(); + + const grabbables = {}; + + const connection = (() => { + const connection = new AutoWs( + _relativeWsUrl('archae/handWs?id=' + localUserId) + ); + connection.on('message', msg => { + const { data } = msg; + + if (typeof data === 'string') { + const m = JSON.parse(data); + const { type } = m; + + if (type === 'grab') { + const { args } = m; + const [n, userId, side] = args; + + const grabbable = grabbables[n]; + grabbable.userId = userId; + grabbable.side = side; + + const e = { + userId, + side, + grabbable, + }; + grabbable.emit('grab', e); + handApi.emit('grab', e); + } else if (type === 'release') { + const { args } = m; + const [n] = args; + + const grabbable = grabbables[n]; + const { userId, side } = grabbable; + grabbable.userId = null; + grabbable.side = null; + + const e = { + userId, + side, + grabbable, + live: true, + stopImmediatePropagation() { + e.live = false; + }, + }; + grabbable.emit('release', e); + handApi.emit('release', e); + } else if (type === 'data') { + const { args } = m; + const [n, key, value] = args; + + const grabbable = grabbables[n]; + if (grabbable) { + grabbable.setData(key, value); + } + + const e = { + key, + value, + grabbable, + }; + grabbable.emit('data', e); + handApi.emit('data', e); + } else { + console.warn('unknown hand message type:', type); + } + } else { + const n = protocolUtils.parseUpdateN(data); + const grabbable = grabbables[n]; + protocolUtils.parseUpdate( + grabbable.position, + grabbable.rotation, + grabbable.scale, + grabbable.localPosition, + grabbable.localRotation, + grabbable.localScale, + data + ); + grabbable.emitUpdate(); } + }); + return connection; + })(); + + const _broadcastObject = (method, args) => { + connection.send( + JSON.stringify({ + method, + args, + }) + ); + }; + const _broadcastBuffer = buffer => { + connection.send(buffer); + }; - const e = { - key, - value, - grabbable, - }; - grabbable.emit('data', e); - handApi.emit('data', e); - } else { - console.warn('unknown hand message type:', type); - } - } else { - const n = protocolUtils.parseUpdateN(data); - const grabbable = grabbables[n]; - protocolUtils.parseUpdate(grabbable.position, grabbable.rotation, grabbable.scale, grabbable.localPosition, grabbable.localRotation, grabbable.localScale, data); - grabbable.emitUpdate(); - } - }); - return connection; - })(); - - const _broadcastObject = (method, args) => { - connection.send(JSON.stringify({ - method, - args, - })); - }; - const _broadcastBuffer = buffer => { - connection.send(buffer); - }; - - const _makeGrabState = () => ({ - grabbedGrabbable: null, - }); - const grabStates = { - left: _makeGrabState(), - right: _makeGrabState(), - }; - - class Grabbable extends EventEmitter { - constructor( - n, - position = new THREE.Vector3(), - rotation = new THREE.Quaternion(), - scale = new THREE.Vector3(1, 1, 1), - localPosition = new THREE.Vector3(), - localRotation = new THREE.Quaternion(), - localScale = new THREE.Vector3(1, 1, 1), - ) { - super(); - - this.n = n; - this.position = position; - this.rotation = rotation; - this.scale = scale; - this.localPosition = localPosition; - this.localRotation = localRotation; - this.localScale = localScale; - - this.userId = null; - this.side = null; - } - - isGrabbed() { - return Boolean(this.userId); - } - - getGrabberId() { - return this.userId; - } - - getGrabberSide() { - return this.side; - } - - distanceTo(p) { - return this.position.distanceTo(p); - } + const _makeGrabState = () => ({ + grabbedGrabbable: null, + }); + const grabStates = { + left: _makeGrabState(), + right: _makeGrabState(), + }; - add() { - const {n, position, rotation, scale, localPosition, localRotation, localScale} = this; + class Grabbable extends EventEmitter { + constructor( + n, + position = new THREE.Vector3(), + rotation = new THREE.Quaternion(), + scale = new THREE.Vector3(1, 1, 1), + localPosition = new THREE.Vector3(), + localRotation = new THREE.Quaternion(), + localScale = new THREE.Vector3(1, 1, 1) + ) { + super(); + + this.n = n; + this.position = position; + this.rotation = rotation; + this.scale = scale; + this.localPosition = localPosition; + this.localRotation = localRotation; + this.localScale = localScale; + + this.userId = null; + this.side = null; + } - _broadcastObject('addGrabbable', [n, position, rotation, scale, localPosition, localRotation, localScale]); - } + isGrabbed() { + return Boolean(this.userId); + } - remove() { - _broadcastObject('removeGrabbable', [this.n]); - } + getGrabberId() { + return this.userId; + } - grab(side) { - const {n} = this; - const userId = localUserId; + getGrabberSide() { + return this.side; + } - this.userId = userId; - this.side = side; + distanceTo(p) { + return this.position.distanceTo(p); + } - const grabState = grabStates[side]; - grabState.grabbedGrabbable = this; + add() { + const { + n, + position, + rotation, + scale, + localPosition, + localRotation, + localScale, + } = this; + + _broadcastObject('addGrabbable', [ + n, + position, + rotation, + scale, + localPosition, + localRotation, + localScale, + ]); + } - _broadcastObject('grab', [n, side]); + remove() { + _broadcastObject('removeGrabbable', [this.n]); + } - const e = { - userId, - side, - grabbable: this, - }; - this.emit('grab', e); - handApi.emit('grab', e); - } + grab(side) { + const { n } = this; + const userId = localUserId; - release() { - const {userId} = this; + this.userId = userId; + this.side = side; - if (userId) { - const {n, side} = this; + const grabState = grabStates[side]; + grabState.grabbedGrabbable = this; - this.userId = null; - this.side = null; + _broadcastObject('grab', [n, side]); - for (let i = 0; i < SIDES.length; i++) { - const side = SIDES[i]; - const grabState = grabStates[side]; - const {grabbedGrabbable} = grabState; + const e = { + userId, + side, + grabbable: this, + }; + this.emit('grab', e); + handApi.emit('grab', e); + } - if (grabbedGrabbable === this) { - grabState.grabbedGrabbable = null; + release() { + const { userId } = this; + + if (userId) { + const { n, side } = this; + + this.userId = null; + this.side = null; + + for (let i = 0; i < SIDES.length; i++) { + const side = SIDES[i]; + const grabState = grabStates[side]; + const { grabbedGrabbable } = grabState; + + if (grabbedGrabbable === this) { + grabState.grabbedGrabbable = null; + } + } + + _broadcastObject('release', [n]); + + const e = { + userId, + side, + grabbable: this, + live: true, + stopImmediatePropagation() { + e.live = false; + }, + }; + this.emit('release', e); + handApi.emit('release', e); } } - _broadcastObject('release', [n]); - - const e = { - userId, - side, - grabbable: this, - live: true, - stopImmediatePropagation() { - e.live = false; - }, - }; - this.emit('release', e); - handApi.emit('release', e); - } - } + setData(key, value) { + const { n } = this; - setData(key, value) { - const {n} = this; + _broadcastObject('data', [n, key, value]); - _broadcastObject('data', [n, key, value]); + const e = { + key, + value, + grabbable: this, + }; + this.emit('data', e); + handApi.emit('data', e); + } - const e = { - key, - value, - grabbable: this, - }; - this.emit('data', e); - handApi.emit('data', e); - } + destroy() { + const { userId, side } = this; - destroy() { - const {userId, side} = this; + for (let i = 0; i < SIDES.length; i++) { + const side = SIDES[i]; + const grabState = grabStates[side]; + const { grabbedGrabbable } = grabState; - for (let i = 0; i < SIDES.length; i++) { - const side = SIDES[i]; - const grabState = grabStates[side]; - const {grabbedGrabbable} = grabState; + if (grabbedGrabbable === this) { + grabState.grabbedGrabbable = null; + } + } - if (grabbedGrabbable === this) { - grabState.grabbedGrabbable = null; + const e = { + userId, + side, + grabbable: this, + }; + this.emit('destroy', e); + handApi.emit('destroy', e); } - } - const e = { - userId, - side, - grabbable: this, - }; - this.emit('destroy', e); - handApi.emit('destroy', e); - } - - setState(position, rotation, scale) { - if (!this.position.equals(position) || !this.rotation.equals(rotation) || !this.scale.equals(scale)) { - this.position.copy(position); - this.rotation.copy(rotation); - this.scale.copy(scale); + setState(position, rotation, scale) { + if ( + !this.position.equals(position) || + !this.rotation.equals(rotation) || + !this.scale.equals(scale) + ) { + this.position.copy(position); + this.rotation.copy(rotation); + this.scale.copy(scale); + + this.emitUpdate(); + this.broadcastUpdate(); + } + } - this.emitUpdate(); - this.broadcastUpdate(); - } - } + setLocalTransform(localPosition, localRotation, localScale) { + if ( + !this.localPosition.equals(localPosition) || + !this.localRotation.equals(localRotation) || + !this.localScale.equals(localScale) + ) { + this.localPosition.copy(localPosition); + this.localRotation.copy(localRotation); + this.localScale.copy(localScale); + + this.emitUpdate(); + this.broadcastUpdate(); + } + } - setLocalTransform(localPosition, localRotation, localScale) { - if (!this.localPosition.equals(localPosition) || !this.localRotation.equals(localRotation) || !this.localScale.equals(localScale)) { - this.localPosition.copy(localPosition); - this.localRotation.copy(localRotation); - this.localScale.copy(localScale); + setStateLocal(position, rotation, scale) { + if ( + !this.position.equals(position) || + !this.rotation.equals(rotation) || + !this.scale.equals(scale) + ) { + this.position.copy(position); + this.rotation.copy(rotation); + this.scale.copy(scale); + + this.emitUpdate(); + } + } - this.emitUpdate(); - this.broadcastUpdate(); - } - } + setFullStateLocal( + position, + rotation, + scale, + localPosition, + localRotation, + localScale + ) { + if ( + !this.position.equals(position) || + !this.rotation.equals(rotation) || + !this.scale.equals(scale) || + !this.localPosition.equals(localPosition) || + !this.localRotation.equals(localRotation) || + !this.localScale.equals(localScale) + ) { + this.position.copy(position); + this.rotation.copy(rotation); + this.scale.copy(scale); + this.localPosition.copy(localPosition); + this.localRotation.copy(localRotation); + this.localScale.copy(localScale); + + this.emitUpdate(); + } + } - setStateLocal(position, rotation, scale) { - if (!this.position.equals(position) || !this.rotation.equals(rotation) || !this.scale.equals(scale)) { - this.position.copy(position); - this.rotation.copy(rotation); - this.scale.copy(scale); + emitUpdate() { + this.emit('update'); + } - this.emitUpdate(); + broadcastUpdate() { + _broadcastBuffer( + protocolUtils.stringifyUpdate( + this.n, + this.position, + this.rotation, + this.scale, + this.localPosition, + this.localRotation, + this.localScale, + buffer, + 0 + ) + ); + } } - } - setFullStateLocal(position, rotation, scale, localPosition, localRotation, localScale) { - if (!this.position.equals(position) || !this.rotation.equals(rotation) || !this.scale.equals(scale) || !this.localPosition.equals(localPosition) || !this.localRotation.equals(localRotation) || !this.localScale.equals(localScale)) { - this.position.copy(position); - this.rotation.copy(rotation); - this.scale.copy(scale); - this.localPosition.copy(localPosition); - this.localRotation.copy(localRotation); - this.localScale.copy(localScale); + const _getHoveredGrabbable = side => { + const { gamepads } = webvr.getStatus(); + const gamepad = gamepads[side]; + const { worldPosition: controllerPosition } = gamepad; + const grabState = grabStates[side]; - this.emitUpdate(); - } - } + let bestDistance = Infinity; + let bestGrabbable = null; + for (const n in grabbables) { + const grabbable = grabbables[n]; - emitUpdate() { - this.emit('update'); - } + const distance = grabbable.distanceTo(controllerPosition); + if (distance < GRAB_DISTANCE && distance < bestDistance) { + bestDistance = distance; + bestGrabbable = grabbable; + } + } + return bestGrabbable; + }; - broadcastUpdate() { - _broadcastBuffer(protocolUtils.stringifyUpdate(this.n, this.position, this.rotation, this.scale, this.localPosition, this.localRotation, this.localScale, buffer, 0)); - } - } + const _gripdown = e => { + const { side } = e; + const grabState = grabStates[side]; + const { grabbedGrabbable } = grabState; - const _getHoveredGrabbable = side => { - const {gamepads} = webvr.getStatus(); - const gamepad = gamepads[side]; - const {worldPosition: controllerPosition} = gamepad; - const grabState = grabStates[side]; - - let bestDistance = Infinity; - let bestGrabbable = null; - for (const n in grabbables) { - const grabbable = grabbables[n]; - - const distance = grabbable.distanceTo(controllerPosition); - if (distance < GRAB_DISTANCE && distance < bestDistance) { - bestDistance = distance; - bestGrabbable = grabbable; - } - } - return bestGrabbable; - }; + if (!grabbedGrabbable) { + const hoveredGrabbable = _getHoveredGrabbable(side); - const _gripdown = e => { - const {side} = e; - const grabState = grabStates[side]; - const {grabbedGrabbable} = grabState; + if (hoveredGrabbable) { + hoveredGrabbable.grab(side); - if (!grabbedGrabbable) { - const hoveredGrabbable = _getHoveredGrabbable(side); + e.stopImmediatePropagation(); + } + } + }; + input.on('gripdown', _gripdown, { + priority: -3, + }); + const _gripup = e => { + const { side } = e; + const grabState = grabStates[side]; + const { grabbedGrabbable } = grabState; - if (hoveredGrabbable) { - hoveredGrabbable.grab(side); + if (grabbedGrabbable) { + grabbedGrabbable.release(); - e.stopImmediatePropagation(); - } - } - }; - input.on('gripdown', _gripdown, { - priority: -3, - }); - const _gripup = e => { - const {side} = e; - const grabState = grabStates[side]; - const {grabbedGrabbable} = grabState; + grabState.grabbedGrabbable = null; - if (grabbedGrabbable) { - grabbedGrabbable.release(); + e.stopImmediatePropagation(); + } + }; + input.on('gripup', _gripup, { + priority: -3, + }); - grabState.grabbedGrabbable = null; + const _update = () => { + const { gamepads } = webvr.getStatus(); - e.stopImmediatePropagation(); - } - }; - input.on('gripup', _gripup, { - priority: -3, - }); - - const _update = () => { - const {gamepads} = webvr.getStatus(); - - for (let i = 0; i < SIDES.length; i++) { - const side = SIDES[i]; - const gamepad = gamepads[side]; - const grabState = grabStates[side]; - const {grabbedGrabbable} = grabState; - - if (grabbedGrabbable) { - const {worldPosition: controllerPosition, worldRotation: controllerRotation, worldScale: controllerScale} = gamepad; - grabbedGrabbable.setState(controllerPosition, controllerRotation, controllerScale); - } - } - }; - rend.on('update', _update); + for (let i = 0; i < SIDES.length; i++) { + const side = SIDES[i]; + const gamepad = gamepads[side]; + const grabState = grabStates[side]; + const { grabbedGrabbable } = grabState; + + if (grabbedGrabbable) { + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + worldScale: controllerScale, + } = gamepad; + grabbedGrabbable.setState( + controllerPosition, + controllerRotation, + controllerScale + ); + } + } + }; + rend.on('update', _update); + + cleanups.push(() => { + input.removeListener('gripdown', _gripdown); + input.removeListener('gripup', _gripup); + + rend.removeListener('update', _update); + }); + + class HandApi extends EventEmitter { + makeGrabbable( + n, + position, + rotation, + scale, + localPosition, + localRotation, + localScale + ) { + const grabbable = new Grabbable( + n, + position, + rotation, + scale, + localPosition, + localRotation, + localScale + ); + this.addGrabbable(grabbable); + return grabbable; + } - cleanups.push(() => { - input.removeListener('gripdown', _gripdown); - input.removeListener('gripup', _gripup); + getGrabbedGrabbable(side) { + return grabStates[side].grabbedGrabbable; + } - rend.removeListener('update', _update); - }); + addGrabbable(grabbable) { + const { n } = grabbable; - class HandApi extends EventEmitter { - makeGrabbable(n, position, rotation, scale, localPosition, localRotation, localScale) { - const grabbable = new Grabbable(n, position, rotation, scale, localPosition, localRotation, localScale); - this.addGrabbable(grabbable); - return grabbable; - } + if (!grabbables[n]) { + grabbable.add(); - getGrabbedGrabbable(side) { - return grabStates[side].grabbedGrabbable; - } + grabbables[n] = grabbable; + } + } - addGrabbable(grabbable) { - const {n} = grabbable; + destroyGrabbable(grabbable) { + const { n } = grabbable; - if (!grabbables[n]) { - grabbable.add(); + if (grabbables[n]) { + grabbable.destroy(); + grabbable.remove(); - grabbables[n] = grabbable; + delete grabbables[n]; + } + } } - } - - destroyGrabbable(grabbable) { - const {n} = grabbable; - - if (grabbables[n]) { - grabbable.destroy(); - grabbable.remove(); + HandApi.prototype.Grabbable = Grabbable; + const handApi = new HandApi(); - delete grabbables[n]; - } + return handApi; } } - HandApi.prototype.Grabbable = Grabbable; - const handApi = new HandApi(); - - return handApi; - } - }); + ); } unmount() { @@ -465,10 +553,17 @@ class Hand { } } -const _arrayEquals = (a, b) => a.length === b.length && a.every((ai, i) => ai === b[i]); +const _arrayEquals = (a, b) => + a.length === b.length && a.every((ai, i) => ai === b[i]); const _relativeWsUrl = s => { const l = window.location; - return ((l.protocol === 'https:') ? 'wss://' : 'ws://') + l.host + l.pathname + (!/\/$/.test(l.pathname) ? '/' : '') + s; + return ( + (l.protocol === 'https:' ? 'wss://' : 'ws://') + + l.host + + l.pathname + + (!/\/$/.test(l.pathname) ? '/' : '') + + s + ); }; module.exports = Hand; diff --git a/core/engines/hand/server.js b/core/engines/hand/server.js index 41b8bcc3b..40a3d08c6 100644 --- a/core/engines/hand/server.js +++ b/core/engines/hand/server.js @@ -6,24 +6,28 @@ class Hand { } mount() { - const {_archae: archae} = this; - const {ws, app, wss} = archae.getCore(); + const { _archae: archae } = this; + const { ws, app, wss } = archae.getCore(); let live = true; this._cleanup = () => { live = false; }; - return archae.requestPlugins([ - '/core/engines/three', - ]).then(([ - three, - ]) => { + return archae.requestPlugins(['/core/engines/three']).then(([three]) => { if (live) { - const {THREE} = three; + const { THREE } = three; class Grabbable { - constructor(n, position, rotation, scale, localPosition, localRotation, localScale) { + constructor( + n, + position, + rotation, + scale, + localPosition, + localRotation, + localScale + ) { this.n = n; this.position = position; this.rotation = rotation; @@ -38,7 +42,7 @@ class Hand { } grab(userId, side) { - const {n} = this; + const { n } = this; this.userId = userId; this.side = side; @@ -53,7 +57,14 @@ class Hand { this.data[key] = value; } - setState(position, rotation, scale, localPosition, localRotation, localScale) { + setState( + position, + rotation, + scale, + localPosition, + localRotation, + localScale + ) { this.position = position; this.rotation = rotation; this.scale = scale; @@ -69,10 +80,10 @@ class Hand { const connections = []; wss.on('connection', c => { - const {url} = c.upgradeReq; + const { url } = c.upgradeReq; let match; - if (match = url.match(/\/archae\/handWs\?id=(.+)$/)) { + if ((match = url.match(/\/archae\/handWs\?id=(.+)$/))) { const userId = match[1]; c.userId = userId; @@ -102,7 +113,7 @@ class Hand { const connection = connections[i]; if (connection.readyState === ws.OPEN && connection !== c) { - const {userId} = connection; + const { userId } = connection; if (interest.includes(userId)) { connection.send(es); @@ -117,7 +128,7 @@ class Hand { for (let i = 0; i < connections.length; i++) { const connection = connections[i]; - const {userId} = connection; + const { userId } = connection; if (interest.includes(userId) && connection !== c) { connection.send(buffer); } @@ -131,11 +142,24 @@ class Hand { if (typeof o === 'string') { const m = JSON.parse(o); - if (typeof m === 'object' && m !== null && typeof m.method === 'string' && Array.isArray(m.args)) { - const {method, args} = m; + if ( + typeof m === 'object' && + m !== null && + typeof m.method === 'string' && + Array.isArray(m.args) + ) { + const { method, args } = m; if (method === 'addGrabbable') { - const [n, position, rotation, scale, localPosition, localRotation, localScale] = args; + const [ + n, + position, + rotation, + scale, + localPosition, + localRotation, + localScale, + ] = args; const grabbable = grabbables[n]; if (!grabbable) { @@ -165,7 +189,7 @@ class Hand { } if (grabbable) { - const {userId, side, data} = grabbable; + const { userId, side, data } = grabbable; if (userId) { _sendObject('grab', [n, userId, side]); } @@ -174,8 +198,27 @@ class Hand { _sendObject('data', [n, key, value]); } - const {position, rotation, scale, localPosition, localRotation, localScale} = grabbable; - _sendBuffer(protocolUtils.stringifyUpdate(n, position, rotation, scale, localPosition, localRotation, localScale, buffer, 0)); + const { + position, + rotation, + scale, + localPosition, + localRotation, + localScale, + } = grabbable; + _sendBuffer( + protocolUtils.stringifyUpdate( + n, + position, + rotation, + scale, + localPosition, + localRotation, + localScale, + buffer, + 0 + ) + ); } } else if (method === 'removeGrabbable') { const [n] = args; @@ -222,7 +265,9 @@ class Hand { _broadcastObject(n, 'data', [n, key, value]); } } else { - console.warn('no such hand method:' + JSON.stringify(method)); + console.warn( + 'no such hand method:' + JSON.stringify(method) + ); } } else { console.warn('invalid message', m); @@ -232,7 +277,16 @@ class Hand { const grabbable = grabbables[n]; if (grabbable) { - protocolUtils.parseUpdate(grabbable.position, grabbable.rotation, grabbable.scale, grabbable.localPosition, grabbable.localRotation, grabbable.localScale, o.buffer, o.byteOffset); + protocolUtils.parseUpdate( + grabbable.position, + grabbable.rotation, + grabbable.scale, + grabbable.localPosition, + grabbable.localRotation, + grabbable.localScale, + o.buffer, + o.byteOffset + ); _broadcastBuffer(n, o); } diff --git a/core/engines/input/client.js b/core/engines/input/client.js index c759c37e0..ac7ae1923 100644 --- a/core/engines/input/client.js +++ b/core/engines/input/client.js @@ -94,14 +94,16 @@ class Input { inputEvent.reset(); } - add(handler, {priority}) { + add(handler, { priority }) { const listener = new Listener(handler, priority); this.listeners.push(listener); this.listeners.sort((a, b) => b.priority - a.priority); } remove(handler) { - const listener = this.listeners.find(listener => listener.handler === handler); + const listener = this.listeners.find( + listener => listener.handler === handler + ); if (listener) { listener.live = false; @@ -119,8 +121,8 @@ class Input { const _preventKeyHijack = e => { // prevent some key combinations from hijacking input if ( - (e.keyCode === 8) || // Backspace - (e.keyCode === 18) || // Alt + e.keyCode === 8 || // Backspace + e.keyCode === 18 || // Alt (e.ctrlKey && e.keyCode === 70) || // Ctrl-F (e.ctrlKey && e.keyCode === 87) || // Ctrl-W (e.ctrlKey && e.keyCode === 83) // Ctrl-S @@ -170,7 +172,7 @@ class Input { document.removeEventListener('paste', eventRouters.paste.handle); }; - function _on(event, handler, {priority = 0} = {}) { + function _on(event, handler, { priority = 0 } = {}) { const eventRouter = eventRouters[event]; if (eventRouter) { eventRouter.add(handler, { diff --git a/core/engines/keyboard/client.js b/core/engines/keyboard/client.js index 28da160ee..7ed5cbfd7 100644 --- a/core/engines/keyboard/client.js +++ b/core/engines/keyboard/client.js @@ -4,12 +4,10 @@ import { KEYBOARD_WORLD_WIDTH, KEYBOARD_WORLD_HEIGHT, KEYBOARD_WORLD_DEPTH, - KEYBOARD_HEADER_WIDTH, KEYBOARD_HEADER_HEIGHT, KEYBOARD_HEADER_WORLD_WIDTH, KEYBOARD_HEADER_WORLD_HEIGHT, - DEFAULT_USER_HEIGHT, } from './lib/constants/keyboard'; import keyboardImgString from './lib/img/keyboard'; @@ -18,9 +16,11 @@ import dotsImgString from './lib/img/dots'; import dotsHighlightImgString from './lib/img/dots-highlight'; const keyboardImgSrc = 'data:image/svg+xml;base64,' + btoa(keyboardImgString); -const keyboardHighlightImgSrc = 'data:image/svg+xml;base64,' + btoa(keyboardHighlightImgString); +const keyboardHighlightImgSrc = + 'data:image/svg+xml;base64,' + btoa(keyboardHighlightImgString); const dotsImgSrc = 'data:image/svg+xml;base64,' + btoa(dotsImgString); -const dotsHighlightImgSrc = 'data:image/svg+xml;base64,' + btoa(dotsHighlightImgString); +const dotsHighlightImgSrc = + 'data:image/svg+xml;base64,' + btoa(dotsHighlightImgString); const SIDES = ['left', 'right']; @@ -30,7 +30,7 @@ class Keyboard { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; const cleanups = []; this._cleanup = () => { @@ -45,18 +45,19 @@ class Keyboard { live = false; }); - const _requestImage = src => new Promise((accept, reject) => { - const img = new Image(); - img.onload = () => { - accept(img); - }; - img.onerror = err => { - reject(err); - }; - img.src = src; - }); - const _requestImageCanvas = src => _requestImage(src) - .then(img => { + const _requestImage = src => + new Promise((accept, reject) => { + const img = new Image(); + img.onload = () => { + accept(img); + }; + img.onerror = err => { + reject(err); + }; + img.src = src; + }); + const _requestImageCanvas = src => + _requestImage(src).then(img => { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; @@ -83,81 +84,61 @@ class Keyboard { _requestImageCanvas(keyboardHighlightImgSrc), _requestImage(dotsImgSrc), _requestImage(dotsHighlightImgSrc), - ]).then(([ - [ - input, - three, - webvr, - biolumi, - resource, - rend, - jsUtils, - geometryUtils, - ], - keyboardImg, - keyboardHighlightCanvas, - dotImg, - dotHighlightImg, - ]) => { - if (live) { - const {THREE, scene, camera} = three; - const {events} = jsUtils; - const {EventEmitter} = events; - const {sfx} = resource; - - const transparentImg = biolumi.getTransparentImg(); - const transparentMaterial = biolumi.getTransparentMaterial(); - - const _decomposeObjectMatrixWorld = object => { - const position = new THREE.Vector3(); - const rotation = new THREE.Quaternion(); - const scale = new THREE.Vector3(); - object.matrixWorld.decompose(position, rotation, scale); - return {position, rotation, scale}; - }; - - const nop = () => {}; + ]).then( + ( + [ + [ + input, + three, + webvr, + biolumi, + resource, + rend, + jsUtils, + geometryUtils, + ], + keyboardImg, + keyboardHighlightCanvas, + dotImg, + dotHighlightImg, + ] + ) => { + if (live) { + const { THREE, scene, camera } = three; + const { events } = jsUtils; + const { EventEmitter } = events; + const { sfx } = resource; + + const transparentImg = biolumi.getTransparentImg(); + const transparentMaterial = biolumi.getTransparentMaterial(); + + const _decomposeObjectMatrixWorld = object => { + const position = new THREE.Vector3(); + const rotation = new THREE.Quaternion(); + const scale = new THREE.Vector3(); + object.matrixWorld.decompose(position, rotation, scale); + return { position, rotation, scale }; + }; - const keyboardState = { - focusSpec: null, - }; + const nop = () => {}; - const keyboardMesh = (() => { - const object = new THREE.Object3D(); - object.rotation.order = camera.rotation.order; - object.visible = false; - - const planeMesh = (() => { - const geometry = new THREE.PlaneBufferGeometry(KEYBOARD_WORLD_WIDTH, KEYBOARD_WORLD_HEIGHT); - const material = (() => { - const texture = new THREE.Texture( - keyboardImg, - THREE.UVMapping, - THREE.ClampToEdgeWrapping, - THREE.ClampToEdgeWrapping, - THREE.LinearFilter, - THREE.LinearFilter, - THREE.RGBAFormat, - THREE.UnsignedByteType, - 16 - ); - texture.needsUpdate = true; + const keyboardState = { + focusSpec: null, + }; - const material = new THREE.MeshBasicMaterial({ - map: texture, - side: THREE.DoubleSide, - transparent: true, - alphaTest: 0.5, - }); - return material; - })(); - const mesh = new THREE.Mesh(geometry, material); + const keyboardMesh = (() => { + const object = new THREE.Object3D(); + object.rotation.order = camera.rotation.order; + object.visible = false; - const headerMesh = (() => { - const geometry = new THREE.PlaneBufferGeometry(KEYBOARD_HEADER_WORLD_WIDTH, KEYBOARD_HEADER_WORLD_HEIGHT); + const planeMesh = (() => { + const geometry = new THREE.PlaneBufferGeometry( + KEYBOARD_WORLD_WIDTH, + KEYBOARD_WORLD_HEIGHT + ); const material = (() => { const texture = new THREE.Texture( - dotImg, + keyboardImg, THREE.UVMapping, THREE.ClampToEdgeWrapping, THREE.ClampToEdgeWrapping, @@ -177,635 +158,804 @@ class Keyboard { }); return material; })(); - const mesh = new THREE.Mesh(geometry, material); - mesh.position.y = (KEYBOARD_WORLD_HEIGHT / 2) + (KEYBOARD_HEADER_WORLD_HEIGHT / 2); - return mesh; - })(); - mesh.add(headerMesh); - mesh.headerMesh = headerMesh; - const headerSolidMesh = (() => { - const mesh = new THREE.Object3D(); - mesh.position.copy(headerMesh.position); - return mesh; - })(); - mesh.add(headerSolidMesh); - mesh.headerSolidMesh = headerSolidMesh; - - return mesh; - })(); - object.add(planeMesh); - object.planeMesh = planeMesh; - - const keyboardPage = biolumi.makePage(null, { - type: 'keyboard', - width: KEYBOARD_WIDTH, - height: KEYBOARD_HEIGHT, - worldWidth: KEYBOARD_WORLD_WIDTH, - worldHeight: KEYBOARD_WORLD_HEIGHT, - color: [1, 1, 1, 0], - }); - object.add(keyboardPage.mesh); - object.keyboardPage = keyboardPage; - rend.addPage(keyboardPage); - - const keyboardHeaderPage = biolumi.makePage(null, { - type: 'keyboard', - width: KEYBOARD_HEADER_WIDTH, - height: KEYBOARD_HEADER_HEIGHT, - worldWidth: KEYBOARD_HEADER_WORLD_WIDTH, - worldHeight: KEYBOARD_HEADER_WORLD_HEIGHT, - color: [1, 1, 1, 0], - }); - keyboardHeaderPage.mesh.position.copy(planeMesh.headerMesh.position); - keyboardHeaderPage.mesh.quaternion.copy(planeMesh.headerMesh.quaternion); - keyboardHeaderPage.mesh.scale.copy(planeMesh.headerMesh.scale); - object.add(keyboardHeaderPage.mesh); - object.keyboardHeaderPage = keyboardHeaderPage; - rend.addPage(keyboardHeaderPage); - - cleanups.push(() => { - rend.removePage(keyboardPage); - rend.removePage(keyboardHeaderPage); - }); - - const keySpecs = (() => { - class KeySpec { - constructor(key, rect, imageData, width, height, worldWidth, worldHeight, highlightOffset, highlightScale, onmouseover, onmouseout) { - this.key = key; - this.rect = rect; - this.imageData = imageData; - this.width = width; - this.height = height; - this.worldWidth = worldWidth; - this.worldHeight = worldHeight; - this.highlightOffset = highlightOffset; - this.highlightScale = highlightScale; - this.onmouseover = onmouseover; - this.onmouseout = onmouseout; - } - } + const headerMesh = (() => { + const geometry = new THREE.PlaneBufferGeometry( + KEYBOARD_HEADER_WORLD_WIDTH, + KEYBOARD_HEADER_WORLD_HEIGHT + ); + const material = (() => { + const texture = new THREE.Texture( + dotImg, + THREE.UVMapping, + THREE.ClampToEdgeWrapping, + THREE.ClampToEdgeWrapping, + THREE.LinearFilter, + THREE.LinearFilter, + THREE.RGBAFormat, + THREE.UnsignedByteType, + 16 + ); + texture.needsUpdate = true; + + const material = new THREE.MeshBasicMaterial({ + map: texture, + side: THREE.DoubleSide, + transparent: true, + alphaTest: 0.5, + }); + return material; + })(); + + const mesh = new THREE.Mesh(geometry, material); + mesh.position.y = + KEYBOARD_WORLD_HEIGHT / 2 + KEYBOARD_HEADER_WORLD_HEIGHT / 2; + return mesh; + })(); + mesh.add(headerMesh); + mesh.headerMesh = headerMesh; - const div = document.createElement('div'); - div.style.cssText = 'position: absolute; top: 0; left: 0; width: ' + KEYBOARD_WIDTH + 'px; height: ' + KEYBOARD_HEIGHT + 'px;'; - div.innerHTML = keyboardImgString; - - document.body.appendChild(div); - - const keyEls = div.querySelectorAll(':scope > svg > g[key]'); - const result = Array(keyEls.length + 1); - for (let i = 0; i < keyEls.length; i++) { - const keyEl = keyEls[i]; - const key = keyEl.getAttribute('key'); - const rect = keyEl.getBoundingClientRect(); - const imageData = (() => { - const {top, bottom, left, right} = rect; - const width = right - left; - const height = bottom - top; - let imageData = keyboardHighlightCanvas.ctx.getImageData(left, top, width, height); - if (key === 'enter') { // special case the enter key; it has a non-rectangular shape - const canvas = document.createElement('canvas'); - canvas.width = imageData.width; - canvas.height = imageData.height; - - const ctx = canvas.getContext('2d'); - ctx.putImageData(imageData, 0, 0); - ctx.clearRect(0, 0, 80, 140); - - imageData = ctx.getImageData(0, 0, imageData.width, imageData.height); - } - return imageData; + const headerSolidMesh = (() => { + const mesh = new THREE.Object3D(); + mesh.position.copy(headerMesh.position); + return mesh; })(); + mesh.add(headerSolidMesh); + mesh.headerSolidMesh = headerSolidMesh; - const keySpec = new KeySpec( - key, - rect, - imageData, - KEYBOARD_WIDTH, - KEYBOARD_HEIGHT, - KEYBOARD_WORLD_WIDTH, - KEYBOARD_WORLD_HEIGHT, - 0.02, - 1.5, - nop, - nop - ); - result[i] = keySpec; - } - document.body.removeChild(div); - - let numHeaderHovers = 0; - result[keyEls.length] = new KeySpec( - 'header', - { - top: -KEYBOARD_HEADER_HEIGHT, - bottom: 0, - left: 0, - right: KEYBOARD_HEADER_WIDTH, - width: KEYBOARD_HEADER_WIDTH, - height: KEYBOARD_HEADER_HEIGHT, - }, - dotHighlightImg, - KEYBOARD_HEADER_WIDTH, - KEYBOARD_HEADER_HEIGHT, - KEYBOARD_HEADER_WORLD_WIDTH, - KEYBOARD_HEADER_WORLD_HEIGHT, - 0, - 1, - () => { // mouseover - numHeaderHovers++; - - if (numHeaderHovers === 1) { - const {headerMesh} = planeMesh; - headerMesh.visible = false; - } - }, - () => { // mouseout - numHeaderHovers--; + return mesh; + })(); + object.add(planeMesh); + object.planeMesh = planeMesh; + + const keyboardPage = biolumi.makePage(null, { + type: 'keyboard', + width: KEYBOARD_WIDTH, + height: KEYBOARD_HEIGHT, + worldWidth: KEYBOARD_WORLD_WIDTH, + worldHeight: KEYBOARD_WORLD_HEIGHT, + color: [1, 1, 1, 0], + }); + object.add(keyboardPage.mesh); + object.keyboardPage = keyboardPage; + rend.addPage(keyboardPage); + + const keyboardHeaderPage = biolumi.makePage(null, { + type: 'keyboard', + width: KEYBOARD_HEADER_WIDTH, + height: KEYBOARD_HEADER_HEIGHT, + worldWidth: KEYBOARD_HEADER_WORLD_WIDTH, + worldHeight: KEYBOARD_HEADER_WORLD_HEIGHT, + color: [1, 1, 1, 0], + }); + keyboardHeaderPage.mesh.position.copy( + planeMesh.headerMesh.position + ); + keyboardHeaderPage.mesh.quaternion.copy( + planeMesh.headerMesh.quaternion + ); + keyboardHeaderPage.mesh.scale.copy(planeMesh.headerMesh.scale); + object.add(keyboardHeaderPage.mesh); + object.keyboardHeaderPage = keyboardHeaderPage; + rend.addPage(keyboardHeaderPage); + + cleanups.push(() => { + rend.removePage(keyboardPage); + rend.removePage(keyboardHeaderPage); + }); - if (numHeaderHovers === 0) { - const {headerMesh} = planeMesh; - headerMesh.visible = true; + const keySpecs = (() => { + class KeySpec { + constructor( + key, + rect, + imageData, + width, + height, + worldWidth, + worldHeight, + highlightOffset, + highlightScale, + onmouseover, + onmouseout + ) { + this.key = key; + this.rect = rect; + this.imageData = imageData; + this.width = width; + this.height = height; + this.worldWidth = worldWidth; + this.worldHeight = worldHeight; + this.highlightOffset = highlightOffset; + this.highlightScale = highlightScale; + this.onmouseover = onmouseover; + this.onmouseout = onmouseout; } } - ); - - return result; - })(); - object.keySpecs = keySpecs; - const _makeKeyMesh = () => { - const object = new THREE.Object3D(); - - const subMesh = (() => { - const geometry = new THREE.PlaneBufferGeometry(1, 1); - const material = (() => { - const texture = new THREE.Texture( - transparentImg, - THREE.UVMapping, - THREE.ClampToEdgeWrapping, - THREE.ClampToEdgeWrapping, - THREE.LinearFilter, - THREE.LinearFilter, - THREE.RGBAFormat, - THREE.UnsignedByteType, - 16 + const div = document.createElement('div'); + div.style.cssText = + 'position: absolute; top: 0; left: 0; width: ' + + KEYBOARD_WIDTH + + 'px; height: ' + + KEYBOARD_HEIGHT + + 'px;'; + div.innerHTML = keyboardImgString; + + document.body.appendChild(div); + + const keyEls = div.querySelectorAll(':scope > svg > g[key]'); + const result = Array(keyEls.length + 1); + for (let i = 0; i < keyEls.length; i++) { + const keyEl = keyEls[i]; + const key = keyEl.getAttribute('key'); + const rect = keyEl.getBoundingClientRect(); + const imageData = (() => { + const { top, bottom, left, right } = rect; + const width = right - left; + const height = bottom - top; + let imageData = keyboardHighlightCanvas.ctx.getImageData( + left, + top, + width, + height + ); + if (key === 'enter') { + // special case the enter key; it has a non-rectangular shape + const canvas = document.createElement('canvas'); + canvas.width = imageData.width; + canvas.height = imageData.height; + + const ctx = canvas.getContext('2d'); + ctx.putImageData(imageData, 0, 0); + ctx.clearRect(0, 0, 80, 140); + + imageData = ctx.getImageData( + 0, + 0, + imageData.width, + imageData.height + ); + } + return imageData; + })(); + + const keySpec = new KeySpec( + key, + rect, + imageData, + KEYBOARD_WIDTH, + KEYBOARD_HEIGHT, + KEYBOARD_WORLD_WIDTH, + KEYBOARD_WORLD_HEIGHT, + 0.02, + 1.5, + nop, + nop ); - texture.needsUpdate = true; + result[i] = keySpec; + } + document.body.removeChild(div); + + let numHeaderHovers = 0; + result[keyEls.length] = new KeySpec( + 'header', + { + top: -KEYBOARD_HEADER_HEIGHT, + bottom: 0, + left: 0, + right: KEYBOARD_HEADER_WIDTH, + width: KEYBOARD_HEADER_WIDTH, + height: KEYBOARD_HEADER_HEIGHT, + }, + dotHighlightImg, + KEYBOARD_HEADER_WIDTH, + KEYBOARD_HEADER_HEIGHT, + KEYBOARD_HEADER_WORLD_WIDTH, + KEYBOARD_HEADER_WORLD_HEIGHT, + 0, + 1, + () => { + // mouseover + numHeaderHovers++; + + if (numHeaderHovers === 1) { + const { headerMesh } = planeMesh; + headerMesh.visible = false; + } + }, + () => { + // mouseout + numHeaderHovers--; + + if (numHeaderHovers === 0) { + const { headerMesh } = planeMesh; + headerMesh.visible = true; + } + } + ); - const material = new THREE.MeshBasicMaterial({ - map: texture, - side: THREE.DoubleSide, - transparent: true, - alphaTest: 0.5, - }); - return material; + return result; + })(); + object.keySpecs = keySpecs; + + const _makeKeyMesh = () => { + const object = new THREE.Object3D(); + + const subMesh = (() => { + const geometry = new THREE.PlaneBufferGeometry(1, 1); + const material = (() => { + const texture = new THREE.Texture( + transparentImg, + THREE.UVMapping, + THREE.ClampToEdgeWrapping, + THREE.ClampToEdgeWrapping, + THREE.LinearFilter, + THREE.LinearFilter, + THREE.RGBAFormat, + THREE.UnsignedByteType, + 16 + ); + texture.needsUpdate = true; + + const material = new THREE.MeshBasicMaterial({ + map: texture, + side: THREE.DoubleSide, + transparent: true, + alphaTest: 0.5, + }); + return material; + })(); + + const mesh = new THREE.Mesh(geometry, material); + return mesh; })(); + object.add(subMesh); + object.subMesh = subMesh; - const mesh = new THREE.Mesh(geometry, material); - return mesh; - })(); - object.add(subMesh); - object.subMesh = subMesh; + object.visible = false; + object.key = null; + object.keydown = null; - object.visible = false; - object.key = null; - object.keydown = null; + return object; + }; + const keyMeshes = { + left: _makeKeyMesh(), + right: _makeKeyMesh(), + }; + object.add(keyMeshes.left); + object.add(keyMeshes.right); + object.keyMeshes = keyMeshes; return object; + })(); + scene.add(keyboardMesh); + keyboardMesh.updateMatrixWorld(); + + const _makeKeyboardHoverState = () => ({ + key: null, + }); + const keyboardHoverStates = { + left: _makeKeyboardHoverState(), + right: _makeKeyboardHoverState(), }; - const keyMeshes = { - left: _makeKeyMesh(), - right: _makeKeyMesh(), - }; - object.add(keyMeshes.left); - object.add(keyMeshes.right); - object.keyMeshes = keyMeshes; - - return object; - })(); - scene.add(keyboardMesh); - keyboardMesh.updateMatrixWorld(); - - const _makeKeyboardHoverState = () => ({ - key: null, - }); - const keyboardHoverStates = { - left: _makeKeyboardHoverState(), - right: _makeKeyboardHoverState(), - }; - const _triggerdown = e => { - const {side} = e; - const {keyMeshes} = keyboardMesh; - const keyMesh = keyMeshes[side]; - const {key} = keyMesh; + const _triggerdown = e => { + const { side } = e; + const { keyMeshes } = keyboardMesh; + const keyMesh = keyMeshes[side]; + const { key } = keyMesh; - if (key) { - if (key !== 'header') { - const keyCode = biolumi.getKeyCode(key); + if (key) { + if (key !== 'header') { + const keyCode = biolumi.getKeyCode(key); - const {subMesh} = keyMesh; - subMesh.position.z = -0.02 / 2; - subMesh.updateMatrixWorld(); + const { subMesh } = keyMesh; + subMesh.position.z = -0.02 / 2; + subMesh.updateMatrixWorld(); - sfx.digi_select.trigger(); + sfx.digi_select.trigger(); - input.triggerEvent('keyboarddown', { - key, - keyCode, - side, - }); - input.triggerEvent('keyboardpress', { - key, - keyCode, - side, - }); - } + input.triggerEvent('keyboarddown', { + key, + keyCode, + side, + }); + input.triggerEvent('keyboardpress', { + key, + keyCode, + side, + }); + } - keyMesh.keydown = key; + keyMesh.keydown = key; - e.stopImmediatePropagation(); - } - }; - input.on('triggerdown', _triggerdown); - const _triggerup = e => { - const {side} = e; - const {keyMeshes} = keyboardMesh; - const keyMesh = keyMeshes[side]; - const {keydown} = keyMesh; - - if (keydown) { - e.stopImmediatePropagation(); - } - }; - input.on('triggerup', _triggerup); - const _trigger = e => { - const {side} = e; - const {keyMeshes} = keyboardMesh; - const keyMesh = keyMeshes[side]; - const {keydown} = keyMesh; - - if (keydown) { - if (keydown !== 'header') { - const key = keydown; - const keyCode = biolumi.getKeyCode(key); - - const {subMesh} = keyMesh; - subMesh.position.z = 0; - subMesh.updateMatrixWorld(); - - input.triggerEvent('keyboardup', { - key, - keyCode, - side, - }); + e.stopImmediatePropagation(); + } + }; + input.on('triggerdown', _triggerdown); + const _triggerup = e => { + const { side } = e; + const { keyMeshes } = keyboardMesh; + const keyMesh = keyMeshes[side]; + const { keydown } = keyMesh; + + if (keydown) { + e.stopImmediatePropagation(); } + }; + input.on('triggerup', _triggerup); + const _trigger = e => { + const { side } = e; + const { keyMeshes } = keyboardMesh; + const keyMesh = keyMeshes[side]; + const { keydown } = keyMesh; + + if (keydown) { + if (keydown !== 'header') { + const key = keydown; + const keyCode = biolumi.getKeyCode(key); + + const { subMesh } = keyMesh; + subMesh.position.z = 0; + subMesh.updateMatrixWorld(); + + input.triggerEvent('keyboardup', { + key, + keyCode, + side, + }); + } - keyMesh.keydown = null; + keyMesh.keydown = null; - e.stopImmediatePropagation(); - } - }; - input.on('trigger', _trigger); + e.stopImmediatePropagation(); + } + }; + input.on('trigger', _trigger); - const _keydown = e => { - const {focusState} = keyboardState; + const _keydown = e => { + const { focusState } = keyboardState; - if (focusState) { - focusState.handleEvent(e); - } - }; - input.on('keydown', _keydown, { - priority: 1, - }); - const _keyboarddown = _keydown; - input.on('keyboarddown', _keyboarddown, { - priority: 1, - }); - /* const _paste = _keydown; + if (focusState) { + focusState.handleEvent(e); + } + }; + input.on('keydown', _keydown, { + priority: 1, + }); + const _keyboarddown = _keydown; + input.on('keyboarddown', _keyboarddown, { + priority: 1, + }); + /* const _paste = _keydown; input.on('paste', _paste, { priority: 1, }); */ - const _update = () => { - const {focusState} = keyboardState; + const _update = () => { + const { focusState } = keyboardState; - if (focusState) { - const _updateDrag = () => { - const {gamepads} = webvr.getStatus(); + if (focusState) { + const _updateDrag = () => { + const { gamepads } = webvr.getStatus(); - SIDES.forEach(side => { - const {keyMeshes} = keyboardMesh; - const keyMesh = keyMeshes[side]; - const {keydown} = keyMesh; + SIDES.forEach(side => { + const { keyMeshes } = keyboardMesh; + const keyMesh = keyMeshes[side]; + const { keydown } = keyMesh; - if (keydown === 'header') { - const gamepad = gamepads[side]; + if (keydown === 'header') { + const gamepad = gamepads[side]; - if (gamepad) { - const {worldPosition: controllerPosition, worldRotation: controllerRotation} = gamepad; - const controllerEnd = controllerPosition.clone() - .add( - new THREE.Vector3( - 0, - -(KEYBOARD_WORLD_HEIGHT + KEYBOARD_HEADER_WORLD_HEIGHT) / 2, - -Math.sqrt(Math.pow(0.6, 2) + Math.pow(0.4, 2)) - ).applyQuaternion(controllerRotation) - ); - keyboardMesh.position.copy(controllerEnd); - keyboardMesh.quaternion.copy(controllerRotation); - keyboardMesh.updateMatrixWorld(); - } - } - }); - }; - const _updateHover = () => { - const {gamepads} = webvr.getStatus(); - - SIDES.forEach(side => { - const hoverState = rend.getHoverState(side); - const {target} = hoverState; - const {keyMeshes} = keyboardMesh; - const keyMesh = keyMeshes[side]; - - const _hide = () => { - const {key: oldKey} = keyMesh; - if (oldKey) { - const {keySpecs} = keyboardMesh; - const oldKeySpec = keySpecs.find(keySpec => keySpec.key === oldKey); - const {onmouseout} = oldKeySpec; - onmouseout(); + if (gamepad) { + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + } = gamepad; + const controllerEnd = controllerPosition + .clone() + .add( + new THREE.Vector3( + 0, + -( + KEYBOARD_WORLD_HEIGHT + + KEYBOARD_HEADER_WORLD_HEIGHT + ) / 2, + -Math.sqrt(Math.pow(0.6, 2) + Math.pow(0.4, 2)) + ).applyQuaternion(controllerRotation) + ); + keyboardMesh.position.copy(controllerEnd); + keyboardMesh.quaternion.copy(controllerRotation); + keyboardMesh.updateMatrixWorld(); + } } + }); + }; + const _updateHover = () => { + const { gamepads } = webvr.getStatus(); + + SIDES.forEach(side => { + const hoverState = rend.getHoverState(side); + const { target } = hoverState; + const { keyMeshes } = keyboardMesh; + const keyMesh = keyMeshes[side]; + + const _hide = () => { + const { key: oldKey } = keyMesh; + if (oldKey) { + const { keySpecs } = keyboardMesh; + const oldKeySpec = keySpecs.find( + keySpec => keySpec.key === oldKey + ); + const { onmouseout } = oldKeySpec; + onmouseout(); + } - keyMesh.key = null; + keyMesh.key = null; - if (keyMesh.visible) { - keyMesh.visible = false; - } - }; - - if (target && target.type === 'keyboard') { - const {intersectionPoint} = hoverState; - const gamepad = gamepads[side]; - const {worldPosition: controllerPosition, worldRotation: controllerRotation, worldScale: controllerScale} = gamepad; - - const {planeMesh} = keyboardMesh; - const {position: keyboardPosition, rotation: keyboardRotation} = _decomposeObjectMatrixWorld(planeMesh); - const xAxis = new THREE.Vector3(1, 0, 0).applyQuaternion(keyboardRotation); - const negativeYAxis = new THREE.Vector3(0, -1, 0).applyQuaternion(keyboardRotation); - const keyboardTopLeftPoint = keyboardPosition.clone() - .add(xAxis.clone().multiplyScalar(-KEYBOARD_WORLD_WIDTH / 2)) - .add(negativeYAxis.clone().multiplyScalar(-KEYBOARD_WORLD_HEIGHT / 2)); - - const matchingKeySpec = (() => { - const intersectionRay = intersectionPoint.clone().sub(keyboardTopLeftPoint); - const ax = intersectionRay.clone().projectOnVector(xAxis).dot(xAxis); - const ay = intersectionRay.clone().projectOnVector(negativeYAxis).dot(negativeYAxis); - const {keySpecs} = keyboardMesh; - const matchingKeySpecs = keySpecs.filter(keySpec => { - const {rect: {top, bottom, left, right}, width, height, worldWidth, worldHeight} = keySpec; - const x = ax / worldWidth * width; - const y = ay / worldHeight * height; - return x >= left && x < right && y >= top && y < bottom; - }); - - if (matchingKeySpecs.length > 0) { - return matchingKeySpecs[0]; - } else { - const x = ax / KEYBOARD_WORLD_WIDTH * KEYBOARD_WIDTH; - const y = ay / KEYBOARD_WORLD_HEIGHT * KEYBOARD_HEIGHT; - if (x >= 0 && x < KEYBOARD_WIDTH && y >= 0 && y < KEYBOARD_HEIGHT) { - const intersectionPointVector = new THREE.Vector2(x, y); - const keySpecDistanceSpecs = keySpecs.map(keySpec => { - const {rect: {top, bottom, left, right, width, height}} = keySpec; - const center = new THREE.Vector2(left + (width / 2), top + (height / 2)); - const distance = center.distanceTo(intersectionPointVector); - - return { - keySpec, - distance, - }; - }); - return keySpecDistanceSpecs.sort((a, b) => a.distance - b.distance)[0].keySpec; - } else { - return null; - } + if (keyMesh.visible) { + keyMesh.visible = false; } - })(); - - if (matchingKeySpec) { - const {key} = matchingKeySpec; + }; + + if (target && target.type === 'keyboard') { + const { intersectionPoint } = hoverState; + const gamepad = gamepads[side]; + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + worldScale: controllerScale, + } = gamepad; + + const { planeMesh } = keyboardMesh; + const { + position: keyboardPosition, + rotation: keyboardRotation, + } = _decomposeObjectMatrixWorld(planeMesh); + const xAxis = new THREE.Vector3(1, 0, 0).applyQuaternion( + keyboardRotation + ); + const negativeYAxis = new THREE.Vector3( + 0, + -1, + 0 + ).applyQuaternion(keyboardRotation); + const keyboardTopLeftPoint = keyboardPosition + .clone() + .add( + xAxis.clone().multiplyScalar(-KEYBOARD_WORLD_WIDTH / 2) + ) + .add( + negativeYAxis + .clone() + .multiplyScalar(-KEYBOARD_WORLD_HEIGHT / 2) + ); - if (key !== keyMesh.key) { - const { - rect: { - top, - bottom, - left, - right, + const matchingKeySpec = (() => { + const intersectionRay = intersectionPoint + .clone() + .sub(keyboardTopLeftPoint); + const ax = intersectionRay + .clone() + .projectOnVector(xAxis) + .dot(xAxis); + const ay = intersectionRay + .clone() + .projectOnVector(negativeYAxis) + .dot(negativeYAxis); + const { keySpecs } = keyboardMesh; + const matchingKeySpecs = keySpecs.filter(keySpec => { + const { + rect: { top, bottom, left, right }, width, height, - }, - imageData, - width: fullWidth, - height: fullHeight, - worldWidth, - worldHeight, - highlightOffset, - highlightScale, - onmouseover, - onmouseout, - } = matchingKeySpec; - const {subMesh: {material: {map: texture}}} = keyMesh; - texture.image = imageData; - texture.needsUpdate = true; - - keyMesh.position.copy( - // keyboardTopLeftPoint.clone() - new THREE.Vector3(-KEYBOARD_WORLD_WIDTH / 2, KEYBOARD_WORLD_HEIGHT / 2, 0) - .add(new THREE.Vector3(1, 0, 0).multiplyScalar((left + (width / 2)) / fullWidth * worldWidth)) - .add(new THREE.Vector3(0, -1, 0).multiplyScalar((top + (height / 2)) / fullHeight * worldHeight)) - .add(new THREE.Vector3(0, 0, highlightOffset)/*.applyQuaternion(keyboardRotation)*/) - ); - // keyMesh.quaternion.copy(keyboardRotation); - keyMesh.scale.set( - width / fullWidth * worldWidth * highlightScale, - height / fullHeight * worldHeight * highlightScale, - 1 - ); - keyMesh.updateMatrixWorld(); - - const {key: oldKey} = keyMesh; - if (oldKey) { - const {keySpecs} = keyboardMesh; - const oldKeySpec = keySpecs.find(keySpec => keySpec.key === oldKey); - const {onmouseout} = oldKeySpec; - onmouseout(); + worldWidth, + worldHeight, + } = keySpec; + const x = ax / worldWidth * width; + const y = ay / worldHeight * height; + return x >= left && x < right && y >= top && y < bottom; + }); + + if (matchingKeySpecs.length > 0) { + return matchingKeySpecs[0]; + } else { + const x = ax / KEYBOARD_WORLD_WIDTH * KEYBOARD_WIDTH; + const y = ay / KEYBOARD_WORLD_HEIGHT * KEYBOARD_HEIGHT; + if ( + x >= 0 && + x < KEYBOARD_WIDTH && + y >= 0 && + y < KEYBOARD_HEIGHT + ) { + const intersectionPointVector = new THREE.Vector2( + x, + y + ); + const keySpecDistanceSpecs = keySpecs.map(keySpec => { + const { + rect: { top, bottom, left, right, width, height }, + } = keySpec; + const center = new THREE.Vector2( + left + width / 2, + top + height / 2 + ); + const distance = center.distanceTo( + intersectionPointVector + ); + + return { + keySpec, + distance, + }; + }); + return keySpecDistanceSpecs.sort( + (a, b) => a.distance - b.distance + )[0].keySpec; + } else { + return null; + } + } + })(); + + if (matchingKeySpec) { + const { key } = matchingKeySpec; + + if (key !== keyMesh.key) { + const { + rect: { top, bottom, left, right, width, height }, + imageData, + width: fullWidth, + height: fullHeight, + worldWidth, + worldHeight, + highlightOffset, + highlightScale, + onmouseover, + onmouseout, + } = matchingKeySpec; + const { + subMesh: { material: { map: texture } }, + } = keyMesh; + texture.image = imageData; + texture.needsUpdate = true; + + keyMesh.position.copy( + // keyboardTopLeftPoint.clone() + new THREE.Vector3( + -KEYBOARD_WORLD_WIDTH / 2, + KEYBOARD_WORLD_HEIGHT / 2, + 0 + ) + .add( + new THREE.Vector3(1, 0, 0).multiplyScalar( + (left + width / 2) / fullWidth * worldWidth + ) + ) + .add( + new THREE.Vector3(0, -1, 0).multiplyScalar( + (top + height / 2) / fullHeight * worldHeight + ) + ) + .add( + new THREE.Vector3( + 0, + 0, + highlightOffset + ) /*.applyQuaternion(keyboardRotation)*/ + ) + ); + // keyMesh.quaternion.copy(keyboardRotation); + keyMesh.scale.set( + width / fullWidth * worldWidth * highlightScale, + height / fullHeight * worldHeight * highlightScale, + 1 + ); + keyMesh.updateMatrixWorld(); + + const { key: oldKey } = keyMesh; + if (oldKey) { + const { keySpecs } = keyboardMesh; + const oldKeySpec = keySpecs.find( + keySpec => keySpec.key === oldKey + ); + const { onmouseout } = oldKeySpec; + onmouseout(); + } + + keyMesh.key = key; + + onmouseover(); } - keyMesh.key = key; - - onmouseover(); - } - - if (!keyMesh.visible) { - keyMesh.visible = true; + if (!keyMesh.visible) { + keyMesh.visible = true; + } + } else { + _hide(); } } else { _hide(); } - } else { - _hide(); - } - }); - }; + }); + }; - _updateDrag(); - _updateHover(); - } - }; - rend.on('update', _update); + _updateDrag(); + _updateHover(); + } + }; + rend.on('update', _update); - cleanups.push(() => { - scene.remove(keyboardMesh); + cleanups.push(() => { + scene.remove(keyboardMesh); - input.removeListener('keydown', _keydown); - input.removeListener('keyboarddown', _keyboarddown); - input.on('triggerdown', _triggerdown); - input.on('triggerup', _triggerup); - input.on('trigger', _trigger); - // input.removeListener('paste', _paste); + input.removeListener('keydown', _keydown); + input.removeListener('keyboarddown', _keyboarddown); + input.on('triggerdown', _triggerdown); + input.on('triggerup', _triggerup); + input.on('trigger', _trigger); + // input.removeListener('paste', _paste); - rend.removeListener('update', _update); - }); + rend.removeListener('update', _update); + }); - class KeyboardFocusState extends EventEmitter { - constructor({type, inputText, inputIndex, inputValue, page}) { - super(); + class KeyboardFocusState extends EventEmitter { + constructor({ type, inputText, inputIndex, inputValue, page }) { + super(); - this.type = type; - this.inputText = inputText; - this.inputIndex = inputIndex; - this.inputValue = inputValue; - this.page = page; - this.width = this.page.layer.measures[this.type]; - } + this.type = type; + this.inputText = inputText; + this.inputIndex = inputIndex; + this.inputValue = inputValue; + this.page = page; + this.width = this.page.layer.measures[this.type]; + } - handleEvent(e) { - const applySpec = biolumi.applyStateKeyEvent(this, e); + handleEvent(e) { + const applySpec = biolumi.applyStateKeyEvent(this, e); - if (applySpec) { - const {commit} = applySpec; - if (commit) { - this.blur(); - } + if (applySpec) { + const { commit } = applySpec; + if (commit) { + this.blur(); + } - this.update(); + this.update(); - e.preventDefault(); - e.stopImmediatePropagation(); + e.preventDefault(); + e.stopImmediatePropagation(); + } } - } - update() { - this.emit('update'); - } + update() { + this.emit('update'); + } - blur() { - this.emit('blur'); + blur() { + this.emit('blur'); + } } - } - class FakeKeyboardFocusState extends EventEmitter { - constructor({type}) { - super(); + class FakeKeyboardFocusState extends EventEmitter { + constructor({ type }) { + super(); - this.type = type; - } + this.type = type; + } - handleEvent(e) { - if (e.keyCode === 13) { // enter - this.blur(); + handleEvent(e) { + if (e.keyCode === 13) { + // enter + this.blur(); - e.preventDefault(); - e.stopImmediatePropagation(); + e.preventDefault(); + e.stopImmediatePropagation(); + } } - } - blur() { - this.emit('blur'); - } - } - - class KeyboardApi { - getFocusState() { - return keyboardState.focusState; + blur() { + this.emit('blur'); + } } - focus({type, position, rotation, inputText, inputIndex, inputValue, page}) { - const {focusState: oldFocusState} = keyboardState; - if (oldFocusState) { - oldFocusState.blur(); + class KeyboardApi { + getFocusState() { + return keyboardState.focusState; } - const newFocusState = new KeyboardFocusState({type, inputText, inputIndex, inputValue, page}); + focus({ + type, + position, + rotation, + inputText, + inputIndex, + inputValue, + page, + }) { + const { focusState: oldFocusState } = keyboardState; + if (oldFocusState) { + oldFocusState.blur(); + } - keyboardState.focusState = newFocusState; + const newFocusState = new KeyboardFocusState({ + type, + inputText, + inputIndex, + inputValue, + page, + }); - keyboardMesh.position.copy( - position.clone().add(new THREE.Vector3(0, -0.6, -0.4).applyQuaternion(rotation)) - ); - const euler = new THREE.Euler().setFromQuaternion(rotation, camera.rotation.order); - keyboardMesh.quaternion.copy( - new THREE.Quaternion().setFromEuler(new THREE.Euler(euler.x - Math.atan2(0.6, 0.4), euler.y, 0, camera.rotation.order)) - ); - keyboardMesh.updateMatrixWorld(); - keyboardMesh.visible = true; + keyboardState.focusState = newFocusState; + + keyboardMesh.position.copy( + position + .clone() + .add( + new THREE.Vector3(0, -0.6, -0.4).applyQuaternion(rotation) + ) + ); + const euler = new THREE.Euler().setFromQuaternion( + rotation, + camera.rotation.order + ); + keyboardMesh.quaternion.copy( + new THREE.Quaternion().setFromEuler( + new THREE.Euler( + euler.x - Math.atan2(0.6, 0.4), + euler.y, + 0, + camera.rotation.order + ) + ) + ); + keyboardMesh.updateMatrixWorld(); + keyboardMesh.visible = true; - newFocusState.on('blur', () => { - keyboardState.focusState = null; + newFocusState.on('blur', () => { + keyboardState.focusState = null; - keyboardMesh.visible = false; + keyboardMesh.visible = false; - const {keyMeshes} = keyboardMesh; - SIDES.forEach(side => { - const keyMesh = keyMeshes[side]; - keyMesh.key = null; + const { keyMeshes } = keyboardMesh; + SIDES.forEach(side => { + const keyMesh = keyMeshes[side]; + keyMesh.key = null; + }); }); - }); - return newFocusState; - } + return newFocusState; + } - tryBlur() { - const {focusState} = keyboardState; + tryBlur() { + const { focusState } = keyboardState; - if (focusState) { - focusState.blur(); + if (focusState) { + focusState.blur(); - keyboardState.focusState = null; + keyboardState.focusState = null; + } } - } - fakeFocus({type}) { - const {focusState: oldFocusState} = keyboardState; - if (oldFocusState) { - oldFocusState.blur(); - } + fakeFocus({ type }) { + const { focusState: oldFocusState } = keyboardState; + if (oldFocusState) { + oldFocusState.blur(); + } - const newFocusState = new FakeKeyboardFocusState({type}); + const newFocusState = new FakeKeyboardFocusState({ type }); - keyboardState.focusState = newFocusState; + keyboardState.focusState = newFocusState; - return newFocusState; + return newFocusState; + } } + const keyboardApi = new KeyboardApi(); + return keyboardApi; } - const keyboardApi = new KeyboardApi(); - return keyboardApi; } - }); + ); } unmount() { diff --git a/core/engines/loader/client.js b/core/engines/loader/client.js index cc2e57507..ce4e3fda6 100644 --- a/core/engines/loader/client.js +++ b/core/engines/loader/client.js @@ -4,7 +4,7 @@ class Loader { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; class LoaderApi { requestPlugin(plugin) { @@ -43,6 +43,6 @@ class Loader { unmount() { this._cleanup(); } -}; +} module.exports = Loader; diff --git a/core/engines/multiplayer/client.js b/core/engines/multiplayer/client.js index 4dac759d9..b397faa17 100644 --- a/core/engines/multiplayer/client.js +++ b/core/engines/multiplayer/client.js @@ -8,7 +8,7 @@ class Multiplayer { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; const cleanups = []; this._cleanup = () => { @@ -23,300 +23,325 @@ class Multiplayer { live = false; }); - return archae.requestPlugins([ - '/core/engines/three', - '/core/engines/webvr', - '/core/engines/resource', - '/core/engines/biolumi', - '/core/engines/rend', - '/core/utils/js-utils', - '/core/utils/network-utils', - '/core/utils/skin-utils', - ]).then(([ - three, - webvr, - resource, - biolumi, - rend, - jsUtils, - networkUtils, - skinUtils, - ]) => { - if (live) { - const {THREE, scene, camera} = three; - const {models: {hmdModelMesh, controllerModelMesh}} = resource; - const {events} = jsUtils; - const {EventEmitter} = events; - const {AutoWs} = networkUtils; - - const zeroVector = new THREE.Vector3(); - const zeroQuaternion = new THREE.Quaternion(); - const oneVector = new THREE.Vector3(1, 1, 1); - - const buffer = new ArrayBuffer(protocolUtils.BUFFER_SIZE); - - let connection = null; - const _addressChange = ({address, username}) => { - let pendingMessage = null; - - connection = new AutoWs(_relativeWsUrl('archae/multiplayerWs?id=' + encodeURIComponent(String(multiplayerApi.getId())) + '&address=' + encodeURIComponent(address) + '&username=' + encodeURIComponent(username))); - connection.on('message', msg => { - const {data} = msg; - - if (typeof data === 'string') { - const m = JSON.parse(data); - const {type} = m; - - if (type === 'playerEnter') { - const {n, username} = m; - multiplayerApi.emit('playerEnter', { - id: n, - username, - }); - } else if (type === 'playerLeave') { - const {n} = m; - multiplayerApi.emit('playerLeave', n); - } else if (type === 'setSkin') { - pendingMessage = m; - } else if (type === 'clearSkin') { - _handleClearSkinEntry(m); - } else { - console.log('unknown message type', JSON.stringify(type)); - } - } else { - if (!pendingMessage) { // update - _handleStatusMessage(data); - } else { // pending message - _handleSetSkinEntry(pendingMessage, new Uint8Array(data)); - pendingMessage = null; - } - } - }); - - const _handleStatusMessage = buffer => { - const n = protocolUtils.parseUpdateN(buffer); - - const playerStatus = multiplayerApi.getPlayerStatus(n); - protocolUtils.parseUpdate( - playerStatus.hmd.position, - playerStatus.hmd.rotation, - playerStatus.hmd.scale, - playerStatus.gamepads.left.position, - playerStatus.gamepads.left.rotation, - playerStatus.gamepads.left.scale, - playerStatus.gamepads.right.position, - playerStatus.gamepads.right.rotation, - playerStatus.gamepads.right.scale, - playerStatus.metadata.menu, - playerStatus.metadata.menu.position, - playerStatus.metadata.menu.rotation, - playerStatus.metadata.menu.scale, - buffer, - 0 - ); - - multiplayerApi.emit('playerStatusUpdate', n); - }; - const _handleSetSkinEntry = ({n}, skinImgBuffer) => { - multiplayerApi.setPlayerSkin(n, skinImgBuffer); - }; - const _handleClearSkinEntry = ({n}) => { - multiplayerApi.setPlayerSkin(n, null); - }; - - cleanups.push(() => { - multiplayerApi.reset(); - - connection.destroy(); - }); - }; - rend.once('addressChange', _addressChange); + return archae + .requestPlugins([ + '/core/engines/three', + '/core/engines/webvr', + '/core/engines/resource', + '/core/engines/biolumi', + '/core/engines/rend', + '/core/utils/js-utils', + '/core/utils/network-utils', + '/core/utils/skin-utils', + ]) + .then( + ( + [ + three, + webvr, + resource, + biolumi, + rend, + jsUtils, + networkUtils, + skinUtils, + ] + ) => { + if (live) { + const { THREE, scene, camera } = three; + const { models: { hmdModelMesh, controllerModelMesh } } = resource; + const { events } = jsUtils; + const { EventEmitter } = events; + const { AutoWs } = networkUtils; + + const zeroVector = new THREE.Vector3(); + const zeroQuaternion = new THREE.Quaternion(); + const oneVector = new THREE.Vector3(1, 1, 1); + + const buffer = new ArrayBuffer(protocolUtils.BUFFER_SIZE); + + let connection = null; + const _addressChange = ({ address, username }) => { + let pendingMessage = null; + + connection = new AutoWs( + _relativeWsUrl( + 'archae/multiplayerWs?id=' + + encodeURIComponent(String(multiplayerApi.getId())) + + '&address=' + + encodeURIComponent(address) + + '&username=' + + encodeURIComponent(username) + ) + ); + connection.on('message', msg => { + const { data } = msg; + + if (typeof data === 'string') { + const m = JSON.parse(data); + const { type } = m; + + if (type === 'playerEnter') { + const { n, username } = m; + multiplayerApi.emit('playerEnter', { + id: n, + username, + }); + } else if (type === 'playerLeave') { + const { n } = m; + multiplayerApi.emit('playerLeave', n); + } else if (type === 'setSkin') { + pendingMessage = m; + } else if (type === 'clearSkin') { + _handleClearSkinEntry(m); + } else { + console.log('unknown message type', JSON.stringify(type)); + } + } else { + if (!pendingMessage) { + // update + _handleStatusMessage(data); + } else { + // pending message + _handleSetSkinEntry(pendingMessage, new Uint8Array(data)); + pendingMessage = null; + } + } + }); - class MutiplayerInterface extends EventEmitter { - constructor(n) { - super(); + const _handleStatusMessage = buffer => { + const n = protocolUtils.parseUpdateN(buffer); + + const playerStatus = multiplayerApi.getPlayerStatus(n); + protocolUtils.parseUpdate( + playerStatus.hmd.position, + playerStatus.hmd.rotation, + playerStatus.hmd.scale, + playerStatus.gamepads.left.position, + playerStatus.gamepads.left.rotation, + playerStatus.gamepads.left.scale, + playerStatus.gamepads.right.position, + playerStatus.gamepads.right.rotation, + playerStatus.gamepads.right.scale, + playerStatus.metadata.menu, + playerStatus.metadata.menu.position, + playerStatus.metadata.menu.rotation, + playerStatus.metadata.menu.scale, + buffer, + 0 + ); + + multiplayerApi.emit('playerStatusUpdate', n); + }; + const _handleSetSkinEntry = ({ n }, skinImgBuffer) => { + multiplayerApi.setPlayerSkin(n, skinImgBuffer); + }; + const _handleClearSkinEntry = ({ n }) => { + multiplayerApi.setPlayerSkin(n, null); + }; + + cleanups.push(() => { + multiplayerApi.reset(); + + connection.destroy(); + }); + }; + rend.once('addressChange', _addressChange); - this.n = n; + class MutiplayerInterface extends EventEmitter { + constructor(n) { + super(); - this.playerStatuses = new Map(); - this.playerUsernames = new Map(); - this.remotePlayerMeshes = new Map(); - this.remotePlayerSkinMeshes = new Map(); - } + this.n = n; - getId() { - return this.n; - } + this.playerStatuses = new Map(); + this.playerUsernames = new Map(); + this.remotePlayerMeshes = new Map(); + this.remotePlayerSkinMeshes = new Map(); + } - getPlayerStatuses() { - const result = []; + getId() { + return this.n; + } - this.playerStatuses.forEach((status, playerId) => { - result.push({ - playerId, - status, - }); - }); + getPlayerStatuses() { + const result = []; - return result; - } + this.playerStatuses.forEach((status, playerId) => { + result.push({ + playerId, + status, + }); + }); - getPlayerStatus(n) { - return this.playerStatuses.get(n) || null; - } + return result; + } + getPlayerStatus(n) { + return this.playerStatuses.get(n) || null; + } - setPlayerSkin(n, skinImgBuffer) { - const oldSkinMesh = this.remotePlayerSkinMeshes.get(n); - if (oldSkinMesh) { - scene.remove(oldSkinMesh); - oldSkinMesh.destroy(); - this.remotePlayerSkinMeshes.delete(oldSkinMesh); - } + setPlayerSkin(n, skinImgBuffer) { + const oldSkinMesh = this.remotePlayerSkinMeshes.get(n); + if (oldSkinMesh) { + scene.remove(oldSkinMesh); + oldSkinMesh.destroy(); + this.remotePlayerSkinMeshes.delete(oldSkinMesh); + } - if (skinImgBuffer) { - const newSkinImg = _makeImg(skinImgBuffer, 64, 64); - const newSkinMesh = skinUtils.makePlayerMesh(newSkinImg); - scene.add(newSkinMesh); - this.remotePlayerSkinMeshes.set(n, newSkinMesh); - } - } + if (skinImgBuffer) { + const newSkinImg = _makeImg(skinImgBuffer, 64, 64); + const newSkinMesh = skinUtils.makePlayerMesh(newSkinImg); + scene.add(newSkinMesh); + this.remotePlayerSkinMeshes.set(n, newSkinMesh); + } + } - addPlayer(n, username) { - const status = _makePlayerStatus(); - this.playerStatuses.set(n, status); + addPlayer(n, username) { + const status = _makePlayerStatus(); + this.playerStatuses.set(n, status); - this.playerUsernames.set(n, username); + this.playerUsernames.set(n, username); - const remotePlayerMesh = _makeRemotePlayerMesh(username); - remotePlayerMesh.update(status); - scene.add(remotePlayerMesh); - this.remotePlayerMeshes.set(n, remotePlayerMesh); + const remotePlayerMesh = _makeRemotePlayerMesh(username); + remotePlayerMesh.update(status); + scene.add(remotePlayerMesh); + this.remotePlayerMeshes.set(n, remotePlayerMesh); - rend.setStatus('users', multiplayerApi.getUsers()); - } + rend.setStatus('users', multiplayerApi.getUsers()); + } - removePlayer(n) { - this.playerStatuses.delete(n); + removePlayer(n) { + this.playerStatuses.delete(n); - this.playerUsernames.delete(n); + this.playerUsernames.delete(n); - const remotePlayerMesh = this.remotePlayerMeshes.get(n); - scene.remove(remotePlayerMesh); - remotePlayerMesh.destroy(); - this.remotePlayerMeshes.delete(n); + const remotePlayerMesh = this.remotePlayerMeshes.get(n); + scene.remove(remotePlayerMesh); + remotePlayerMesh.destroy(); + this.remotePlayerMeshes.delete(n); - const skinMesh = this.remotePlayerSkinMeshes.get(n); - if (skinMesh) { - scene.remove(skinMesh); - skinMesh.destroy(); - this.remotePlayerSkinMeshes.delete(n); - } + const skinMesh = this.remotePlayerSkinMeshes.get(n); + if (skinMesh) { + scene.remove(skinMesh); + skinMesh.destroy(); + this.remotePlayerSkinMeshes.delete(n); + } - rend.setStatus('users', multiplayerApi.getUsers()); - } + rend.setStatus('users', multiplayerApi.getUsers()); + } - getUsers() { - const {playerUsernames} = this; - const result = Array(playerUsernames.size); - let i = 0; - playerUsernames.forEach(username => { - result[i++] = username; - }); - return result.sort((a, b) => a.localeCompare(b)); - } + getUsers() { + const { playerUsernames } = this; + const result = Array(playerUsernames.size); + let i = 0; + playerUsernames.forEach(username => { + result[i++] = username; + }); + return result.sort((a, b) => a.localeCompare(b)); + } - updateSkin(skinImgBuffer) { - if (skinImgBuffer) { - connection.send(JSON.stringify({ - type: 'setSkin', - n: this.n, - })); - connection.send(skinImgBuffer); - } else { - connection.send(JSON.stringify({ - type: 'clearSkin', - n: this.n, - })); - } - } + updateSkin(skinImgBuffer) { + if (skinImgBuffer) { + connection.send( + JSON.stringify({ + type: 'setSkin', + n: this.n, + }) + ); + connection.send(skinImgBuffer); + } else { + connection.send( + JSON.stringify({ + type: 'clearSkin', + n: this.n, + }) + ); + } + } - getRemotePlayerMesh(n) { - return this.remotePlayerMeshes.get(n) || null; - } + getRemotePlayerMesh(n) { + return this.remotePlayerMeshes.get(n) || null; + } - getRemotePlayerSkinMesh(n) { - return this.remotePlayerSkinMeshes.get(n) || null; - } + getRemotePlayerSkinMesh(n) { + return this.remotePlayerSkinMeshes.get(n) || null; + } - getRemoteControllerMeshes(n) { - const remotePlayerMesh = this.getRemotePlayerMesh(n); - return remotePlayerMesh ? remotePlayerMesh.controllers : null; - } + getRemoteControllerMeshes(n) { + const remotePlayerMesh = this.getRemotePlayerMesh(n); + return remotePlayerMesh ? remotePlayerMesh.controllers : null; + } - reset() { - const {remotePlayerMeshes: oldRemotePlayerMeshes} = this; + reset() { + const { remotePlayerMeshes: oldRemotePlayerMeshes } = this; - this.playerStatuses = new Map(); - this.playerUsernames = new Map(); - this.remotePlayerMeshes = new Map(); + this.playerStatuses = new Map(); + this.playerUsernames = new Map(); + this.remotePlayerMeshes = new Map(); - oldRemotePlayerMeshes.forEach(mesh => { - scene.remove(mesh); - }); + oldRemotePlayerMeshes.forEach(mesh => { + scene.remove(mesh); + }); - rend.setStatus('users', multiplayerApi.getUsers()); - } - } - const multiplayerApi = new MutiplayerInterface(_makeN()); + rend.setStatus('users', multiplayerApi.getUsers()); + } + } + const multiplayerApi = new MutiplayerInterface(_makeN()); - const _makeRemotePlayerMesh = username => { - const object = new THREE.Object3D(); + const _makeRemotePlayerMesh = username => { + const object = new THREE.Object3D(); - const hmd = hmdModelMesh.clone(); - object.add(hmd); - object.hmd = hmd; + const hmd = hmdModelMesh.clone(); + object.add(hmd); + object.hmd = hmd; - /* const label = resource.makePlayerLabelMesh({ + /* const label = resource.makePlayerLabelMesh({ username: status.username, }); object.add(label); object.label = label; */ - const menu = resource.makePlayerMenuMesh({ - username, - }); - object.add(menu); - object.menu = menu; - - const _makeControllerMesh = () => controllerModelMesh.clone(); - const controllers = { - left: _makeControllerMesh(), - right: _makeControllerMesh(), - }; - object.add(controllers.left); - object.add(controllers.right); - object.controllers = controllers; - - object.update = status => { - const _updateHmd = () => { - hmd.position.copy(status.hmd.position); - hmd.quaternion.copy(status.hmd.rotation); - hmd.scale.copy(status.hmd.scale); - // hmd.updateMatrixWorld(); - }; - const _updateControllers = () => { - controllers.left.position.copy(status.gamepads.left.position); - controllers.left.quaternion.copy(status.gamepads.left.rotation); - controllers.left.scale.copy(status.gamepads.left.scale); - // controllers.left.updateMatrixWorld(); - - controllers.right.position.copy(status.gamepads.right.position); - controllers.right.quaternion.copy(status.gamepads.right.rotation); - controllers.right.scale.copy(status.gamepads.right.scale); - // rightController.updateMatrixWorld(); - }; - /* const _updateLabel = () => { + const menu = resource.makePlayerMenuMesh({ + username, + }); + object.add(menu); + object.menu = menu; + + const _makeControllerMesh = () => controllerModelMesh.clone(); + const controllers = { + left: _makeControllerMesh(), + right: _makeControllerMesh(), + }; + object.add(controllers.left); + object.add(controllers.right); + object.controllers = controllers; + + object.update = status => { + const _updateHmd = () => { + hmd.position.copy(status.hmd.position); + hmd.quaternion.copy(status.hmd.rotation); + hmd.scale.copy(status.hmd.scale); + // hmd.updateMatrixWorld(); + }; + const _updateControllers = () => { + controllers.left.position.copy(status.gamepads.left.position); + controllers.left.quaternion.copy( + status.gamepads.left.rotation + ); + controllers.left.scale.copy(status.gamepads.left.scale); + // controllers.left.updateMatrixWorld(); + + controllers.right.position.copy( + status.gamepads.right.position + ); + controllers.right.quaternion.copy( + status.gamepads.right.rotation + ); + controllers.right.scale.copy(status.gamepads.right.scale); + // rightController.updateMatrixWorld(); + }; + /* const _updateLabel = () => { const {hmd: hmdStatus, username} = status; label.update({ @@ -324,160 +349,190 @@ class Multiplayer { username, }); }; */ - const _updateMetadata = () => { - menu.update(status.metadata.menu); - }; - const _updateMatrix = () => { - object.updateMatrixWorld(); + const _updateMetadata = () => { + menu.update(status.metadata.menu); + }; + const _updateMatrix = () => { + object.updateMatrixWorld(); + }; + + _updateHmd(); + _updateControllers(); + // _updateLabel(); + _updateMetadata(); + _updateMatrix(); + }; + object.destroy = () => { + // label.destroy(); + }; + + return object; }; - _updateHmd(); - _updateControllers(); - // _updateLabel(); - _updateMetadata(); - _updateMatrix(); - }; - object.destroy = () => { - // label.destroy(); - }; + const playerStatusUpdate = n => { + const status = multiplayerApi.getPlayerStatus(n); - return object; - }; - - const playerStatusUpdate = n => { - const status = multiplayerApi.getPlayerStatus(n); - - const remotePlayerMesh = multiplayerApi.getRemotePlayerMesh(n); - remotePlayerMesh.update(status); - - const remotePlayerSkinMesh = multiplayerApi.getRemotePlayerSkinMesh(n); - if (remotePlayerSkinMesh) { - remotePlayerSkinMesh.update(status); - } - }; - const playerEnter = ({id: n, username}) => { - multiplayerApi.addPlayer(n, username); - }; - const playerLeave = n => { - multiplayerApi.removePlayer(n); - }; - multiplayerApi.on('playerStatusUpdate', playerStatusUpdate); - multiplayerApi.on('playerEnter', playerEnter); - multiplayerApi.on('playerLeave', playerLeave); - - const _makePlayerStatus = () => ({ - hmd: { - position: zeroVector.clone(), - rotation: zeroQuaternion.clone(), - scale: oneVector.clone(), - }, - gamepads: { - left: { - position: zeroVector.clone(), - rotation: zeroQuaternion.clone(), - scale: oneVector.clone(), - }, - right: { - position: zeroVector.clone(), - rotation: zeroQuaternion.clone(), - scale: oneVector.clone(), - }, - }, - metadata: { - menu: { - open: false, - position: zeroVector.clone(), - rotation: zeroQuaternion.clone(), - scale: oneVector.clone(), - }, - }, - }); - const localStatus = _makePlayerStatus(); - const _update = () => { - const status = webvr.getStatus(); - const menuState = rend.getMenuState(); - - let updated = false; - const _updateHmd = () => { - const {hmd} = status; - const {worldPosition: hmdPosition, worldRotation: hmdRotation, worldScale: hmdScale} = hmd; - - if (!localStatus.hmd.position.equals(hmdPosition) || !localStatus.hmd.rotation.equals(hmdRotation)) { - localStatus.hmd.position.copy(hmdPosition); - localStatus.hmd.rotation.copy(hmdRotation); - localStatus.hmd.scale.copy(hmdScale); - - updated = true; - } - }; - const _updateControllers = () => { - const {gamepads} = status; + const remotePlayerMesh = multiplayerApi.getRemotePlayerMesh(n); + remotePlayerMesh.update(status); - for (let i = 0; i < SIDES.length; i++) { - const side = SIDES[i]; - const gamepad = gamepads[side]; + const remotePlayerSkinMesh = multiplayerApi.getRemotePlayerSkinMesh( + n + ); + if (remotePlayerSkinMesh) { + remotePlayerSkinMesh.update(status); + } + }; + const playerEnter = ({ id: n, username }) => { + multiplayerApi.addPlayer(n, username); + }; + const playerLeave = n => { + multiplayerApi.removePlayer(n); + }; + multiplayerApi.on('playerStatusUpdate', playerStatusUpdate); + multiplayerApi.on('playerEnter', playerEnter); + multiplayerApi.on('playerLeave', playerLeave); + + const _makePlayerStatus = () => ({ + hmd: { + position: zeroVector.clone(), + rotation: zeroQuaternion.clone(), + scale: oneVector.clone(), + }, + gamepads: { + left: { + position: zeroVector.clone(), + rotation: zeroQuaternion.clone(), + scale: oneVector.clone(), + }, + right: { + position: zeroVector.clone(), + rotation: zeroQuaternion.clone(), + scale: oneVector.clone(), + }, + }, + metadata: { + menu: { + open: false, + position: zeroVector.clone(), + rotation: zeroQuaternion.clone(), + scale: oneVector.clone(), + }, + }, + }); + const localStatus = _makePlayerStatus(); + const _update = () => { + const status = webvr.getStatus(); + const menuState = rend.getMenuState(); + + let updated = false; + const _updateHmd = () => { + const { hmd } = status; + const { + worldPosition: hmdPosition, + worldRotation: hmdRotation, + worldScale: hmdScale, + } = hmd; - if (gamepad) { - const {worldPosition: controllerPosition, worldRotation: controllerRotation, worldScale: controllerScale} = gamepad; + if ( + !localStatus.hmd.position.equals(hmdPosition) || + !localStatus.hmd.rotation.equals(hmdRotation) + ) { + localStatus.hmd.position.copy(hmdPosition); + localStatus.hmd.rotation.copy(hmdRotation); + localStatus.hmd.scale.copy(hmdScale); - const lastGamepadStatus = localStatus.gamepads[side]; + updated = true; + } + }; + const _updateControllers = () => { + const { gamepads } = status; + + for (let i = 0; i < SIDES.length; i++) { + const side = SIDES[i]; + const gamepad = gamepads[side]; + + if (gamepad) { + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + worldScale: controllerScale, + } = gamepad; + + const lastGamepadStatus = localStatus.gamepads[side]; + if ( + !lastGamepadStatus || + !lastGamepadStatus.position.equals(controllerPosition) || + !lastGamepadStatus.rotation.equals(controllerRotation) || + !lastGamepadStatus.scale.equals(controllerScale) + ) { + localStatus.gamepads[side].position.copy( + controllerPosition + ); + localStatus.gamepads[side].rotation.copy( + controllerRotation + ); + localStatus.gamepads[side].scale.copy(controllerScale); + + updated = true; + } + } + } + }; + const _updateMetadata = () => { if ( - !lastGamepadStatus || - !lastGamepadStatus.position.equals(controllerPosition) || - !lastGamepadStatus.rotation.equals(controllerRotation) || - !lastGamepadStatus.scale.equals(controllerScale) + menuState.open !== localStatus.metadata.menu.open || + !menuState.position.equals( + localStatus.metadata.menu.position + ) || + !menuState.rotation.equals( + localStatus.metadata.menu.rotation + ) || + !menuState.scale.equals(localStatus.metadata.menu.scale) ) { - localStatus.gamepads[side].position.copy(controllerPosition); - localStatus.gamepads[side].rotation.copy(controllerRotation); - localStatus.gamepads[side].scale.copy(controllerScale); + localStatus.metadata.menu.open = menuState.open; + localStatus.metadata.menu.position.copy(menuState.position); + localStatus.metadata.menu.rotation.copy(menuState.rotation); + localStatus.metadata.menu.scale.copy(menuState.scale); updated = true; } - } - } - }; - const _updateMetadata = () => { - if ( - menuState.open !== localStatus.metadata.menu.open || - !menuState.position.equals(localStatus.metadata.menu.position) || - !menuState.rotation.equals(localStatus.metadata.menu.rotation) || - !menuState.scale.equals(localStatus.metadata.menu.scale) - ) { - localStatus.metadata.menu.open = menuState.open; - localStatus.metadata.menu.position.copy(menuState.position); - localStatus.metadata.menu.rotation.copy(menuState.rotation); - localStatus.metadata.menu.scale.copy(menuState.scale); - - updated = true; - } - }; - const _sendUpdate = () => { - if (updated) { - protocolUtils.stringifyUpdate(multiplayerApi.getId(), localStatus, buffer, 0); - connection.send(buffer); - } - }; + }; + const _sendUpdate = () => { + if (updated) { + protocolUtils.stringifyUpdate( + multiplayerApi.getId(), + localStatus, + buffer, + 0 + ); + connection.send(buffer); + } + }; - _updateHmd(); - _updateControllers(); - _updateMetadata(); - _sendUpdate(); - }; - rend.on('update', _update); + _updateHmd(); + _updateControllers(); + _updateMetadata(); + _sendUpdate(); + }; + rend.on('update', _update); - cleanups.push(() => { - rend.removeListener('addressChange', _addressChange); + cleanups.push(() => { + rend.removeListener('addressChange', _addressChange); - multiplayerApi.removeListener('playerStatusUpdate', playerStatusUpdate); - multiplayerApi.removeListener('playerEnter', playerEnter); - multiplayerApi.removeListener('playerLeave', playerLeave); + multiplayerApi.removeListener( + 'playerStatusUpdate', + playerStatusUpdate + ); + multiplayerApi.removeListener('playerEnter', playerEnter); + multiplayerApi.removeListener('playerLeave', playerLeave); - rend.removeListener('update', _update); - }); + rend.removeListener('update', _update); + }); - return multiplayerApi; - } - }); + return multiplayerApi; + } + } + ); } unmount() { @@ -487,10 +542,20 @@ class Multiplayer { const _relativeWsUrl = s => { const l = window.location; - return ((l.protocol === 'https:') ? 'wss://' : 'ws://') + l.host + l.pathname + (!/\/$/.test(l.pathname) ? '/' : '') + s; + return ( + (l.protocol === 'https:' ? 'wss://' : 'ws://') + + l.host + + l.pathname + + (!/\/$/.test(l.pathname) ? '/' : '') + + s + ); }; -const _makeN = () => Math.floor(Math.random() * 0xFFFFFFFF); -const _arrayEquals = (a, b) => Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((ae, i) => b[i] === ae); +const _makeN = () => Math.floor(Math.random() * 0xffffffff); +const _arrayEquals = (a, b) => + Array.isArray(a) && + Array.isArray(b) && + a.length === b.length && + a.every((ae, i) => b[i] === ae); const _makeImg = (imgBuffer, width, height) => { const canvas = document.createElement('canvas'); canvas.width = width; diff --git a/core/engines/multiplayer/server.js b/core/engines/multiplayer/server.js index 5ee33ff08..6ae804b05 100644 --- a/core/engines/multiplayer/server.js +++ b/core/engines/multiplayer/server.js @@ -1,5 +1,5 @@ const events = require('events'); -const {EventEmitter} = events; +const { EventEmitter } = events; const path = require('path'); const protocolUtils = require('./lib/utils/protocol-utils'); @@ -10,257 +10,293 @@ class Multiplayer { } mount() { - const {_archae: archae} = this; - const {express, ws, app, wss} = archae.getCore(); - const {metadata: {maxUsers, transient}} = archae; + const { _archae: archae } = this; + const { express, ws, app, wss } = archae.getCore(); + const { metadata: { maxUsers, transient } } = archae; let live = true; this._cleanup = () => { live = false; }; - return archae.requestPlugins([ - '/core/engines/three', - '/core/engines/webvr', - '/core/engines/resource', - '/core/engines/biolumi', - '/core/engines/rend', - '/core/utils/js-utils', - '/core/utils/network-utils', - '/core/utils/skin-utils', - ]).then(([ - three, - ]) => { - if (live) { - const {THREE} = three; - - const zeroVector = new THREE.Vector3(); - const zeroQuaternion = new THREE.Quaternion(); - const oneVector = new THREE.Vector3(1, 1, 1); - - const buffer = new ArrayBuffer(protocolUtils.BUFFER_SIZE); - - const connections = []; - const statuses = new Map(); - const usernames = new Map(); - const skins = new Map(); - - const _makePlayerStatus = () => ({ - hmd: { - position: zeroVector.clone(), - rotation: zeroQuaternion.clone(), - scale: oneVector.clone(), - }, - gamepads: { - left: { + return archae + .requestPlugins([ + '/core/engines/three', + '/core/engines/webvr', + '/core/engines/resource', + '/core/engines/biolumi', + '/core/engines/rend', + '/core/utils/js-utils', + '/core/utils/network-utils', + '/core/utils/skin-utils', + ]) + .then(([three]) => { + if (live) { + const { THREE } = three; + + const zeroVector = new THREE.Vector3(); + const zeroQuaternion = new THREE.Quaternion(); + const oneVector = new THREE.Vector3(1, 1, 1); + + const buffer = new ArrayBuffer(protocolUtils.BUFFER_SIZE); + + const connections = []; + const statuses = new Map(); + const usernames = new Map(); + const skins = new Map(); + + const _makePlayerStatus = () => ({ + hmd: { position: zeroVector.clone(), rotation: zeroQuaternion.clone(), scale: oneVector.clone(), }, - right: { - position: zeroVector.clone(), - rotation: zeroQuaternion.clone(), - scale: oneVector.clone(), + gamepads: { + left: { + position: zeroVector.clone(), + rotation: zeroQuaternion.clone(), + scale: oneVector.clone(), + }, + right: { + position: zeroVector.clone(), + rotation: zeroQuaternion.clone(), + scale: oneVector.clone(), + }, }, - }, - metadata: { - menu: { - open: false, - position: zeroVector.clone(), - rotation: zeroQuaternion.clone(), - scale: oneVector.clone(), + metadata: { + menu: { + open: false, + position: zeroVector.clone(), + rotation: zeroQuaternion.clone(), + scale: oneVector.clone(), + }, }, - }, - }); - - wss.on('connection', c => { - const {url} = c.upgradeReq; - - let match; - if (match = url.match(/^\/archae\/multiplayerWs\?id=(.+?)&address=(.+?)&username=(.+?)$/)) { - if (connections.length < maxUsers) { - const n = parseInt(match[1], 10); - const address = decodeURIComponent(match[2]); - const username = decodeURIComponent(match[3]); - - const remoteAddress = c.upgradeReq.connection.remoteAddress.replace(/^::ffff:/, ''); - console.log('multiplayer connection', {n, address, username, remoteAddress}); - - const _init = () => { - statuses.forEach((status, n) => { - c.send(JSON.stringify({ - type: 'playerEnter', - n, - username: usernames.get(n), - })); - - protocolUtils.stringifyUpdate(n, status, buffer, 0); - c.send(buffer); - }); - skins.forEach((skinImgBuffer, n) => { - c.send(JSON.stringify({ - type: 'setSkin', - n: n, - })); - c.send(skinImgBuffer); - }); - - statuses.set(n, _makePlayerStatus()); - usernames.set(n, username); - - const es = JSON.stringify({ - type: 'playerEnter', + }); + + wss.on('connection', c => { + const { url } = c.upgradeReq; + + let match; + if ( + (match = url.match( + /^\/archae\/multiplayerWs\?id=(.+?)&address=(.+?)&username=(.+?)$/ + )) + ) { + if (connections.length < maxUsers) { + const n = parseInt(match[1], 10); + const address = decodeURIComponent(match[2]); + const username = decodeURIComponent(match[3]); + + const remoteAddress = c.upgradeReq.connection.remoteAddress.replace( + /^::ffff:/, + '' + ); + console.log('multiplayer connection', { n, + address, username, + remoteAddress, }); - for (let i = 0; i < connections.length; i++) { - const connection = connections[i]; - if (connection.readyState === ws.OPEN && connection !== c) { - connection.send(es); + + const _init = () => { + statuses.forEach((status, n) => { + c.send( + JSON.stringify({ + type: 'playerEnter', + n, + username: usernames.get(n), + }) + ); + + protocolUtils.stringifyUpdate(n, status, buffer, 0); + c.send(buffer); + }); + skins.forEach((skinImgBuffer, n) => { + c.send( + JSON.stringify({ + type: 'setSkin', + n: n, + }) + ); + c.send(skinImgBuffer); + }); + + statuses.set(n, _makePlayerStatus()); + usernames.set(n, username); + + const es = JSON.stringify({ + type: 'playerEnter', + n, + username, + }); + for (let i = 0; i < connections.length; i++) { + const connection = connections[i]; + if (connection.readyState === ws.OPEN && connection !== c) { + connection.send(es); + } } - } - multiplayerApi.emit('playerEnter', { - id: n, - address, - username, - }); - }; - _init(); - - let pendingMessage = null; - c.on('message', o => { - if (typeof o === 'string') { - const m = JSON.parse(o); - const {type} = m; - - if (type === 'setSkin') { - pendingMessage = m; - } else if (type === 'clearSkin') { - skins.delete(n); - - const e = { - type: 'clearSkin', - n, - }; - const es = JSON.stringify(e); - for (let i = 0; i < connections.length; i++) { - const connection = connections[i]; - - if (connection.readyState === ws.OPEN && connection !== c) { - connection.send(es); - connection.send(skinImgBuffer); + multiplayerApi.emit('playerEnter', { + id: n, + address, + username, + }); + }; + _init(); + + let pendingMessage = null; + c.on('message', o => { + if (typeof o === 'string') { + const m = JSON.parse(o); + const { type } = m; + + if (type === 'setSkin') { + pendingMessage = m; + } else if (type === 'clearSkin') { + skins.delete(n); + + const e = { + type: 'clearSkin', + n, + }; + const es = JSON.stringify(e); + for (let i = 0; i < connections.length; i++) { + const connection = connections[i]; + + if ( + connection.readyState === ws.OPEN && + connection !== c + ) { + connection.send(es); + connection.send(skinImgBuffer); + } } + } else { + console.warn( + 'multiplayer unknown message type', + JSON.stringify(type) + ); } } else { - console.warn('multiplayer unknown message type', JSON.stringify(type)); - } - } else { - if (!pendingMessage) { // update - const n = protocolUtils.parseUpdateN(o.buffer, o.byteOffset); - - const status = statuses.get(n); - if (status) { - protocolUtils.parseUpdate( - status.hmd.position, - status.hmd.rotation, - status.hmd.scale, - status.gamepads.left.position, - status.gamepads.left.rotation, - status.gamepads.left.scale, - status.gamepads.right.position, - status.gamepads.right.rotation, - status.gamepads.right.scale, - status.metadata.menu, - status.metadata.menu.position, - status.metadata.menu.rotation, - status.metadata.menu.scale, + if (!pendingMessage) { + // update + const n = protocolUtils.parseUpdateN( o.buffer, o.byteOffset ); - for (let i = 0; i < connections.length; i++) { - const connection = connections[i]; - - if (connection.readyState === ws.OPEN && connection !== c) { - connection.send(o); + const status = statuses.get(n); + if (status) { + protocolUtils.parseUpdate( + status.hmd.position, + status.hmd.rotation, + status.hmd.scale, + status.gamepads.left.position, + status.gamepads.left.rotation, + status.gamepads.left.scale, + status.gamepads.right.position, + status.gamepads.right.rotation, + status.gamepads.right.scale, + status.metadata.menu, + status.metadata.menu.position, + status.metadata.menu.rotation, + status.metadata.menu.scale, + o.buffer, + o.byteOffset + ); + + for (let i = 0; i < connections.length; i++) { + const connection = connections[i]; + + if ( + connection.readyState === ws.OPEN && + connection !== c + ) { + connection.send(o); + } } + } else { + console.warn( + 'multiplayer ignoring status for nonexistent user', + { n } + ); } } else { - console.warn('multiplayer ignoring status for nonexistent user', {n}); - } - } else { // pending message - const skinImgBuffer = o; - skins.set(n, skinImgBuffer); - - const es = JSON.stringify({ - type: 'setSkin', - n, - }); - for (let i = 0; i < connections.length; i++) { - const connection = connections[i]; - if (connection.readyState === ws.OPEN && connection !== c) { - connection.send(es); - connection.send(skinImgBuffer); + // pending message + const skinImgBuffer = o; + skins.set(n, skinImgBuffer); + + const es = JSON.stringify({ + type: 'setSkin', + n, + }); + for (let i = 0; i < connections.length; i++) { + const connection = connections[i]; + if ( + connection.readyState === ws.OPEN && + connection !== c + ) { + connection.send(es); + connection.send(skinImgBuffer); + } } - } - pendingMessage = null; + pendingMessage = null; + } } - } - }); - c.on('close', () => { - statuses.delete(n); - usernames.delete(n); - skins.delete(n); - - const es = JSON.stringify({ - type: 'playerLeave', - n, }); - for (let i = 0; i < connections.length; i++) { - const connection = connections[i]; - if (connection.readyState === ws.OPEN && connection !== c) { - connection.send(es); + c.on('close', () => { + statuses.delete(n); + usernames.delete(n); + skins.delete(n); + + const es = JSON.stringify({ + type: 'playerLeave', + n, + }); + for (let i = 0; i < connections.length; i++) { + const connection = connections[i]; + if (connection.readyState === ws.OPEN && connection !== c) { + connection.send(es); + } } - } - connections.splice(connections.indexOf(c), 1); + connections.splice(connections.indexOf(c), 1); - multiplayerApi.emit('playerLeave', n); - }); + multiplayerApi.emit('playerLeave', n); + }); - connections.push(c); - } else { - connection.close(); + connections.push(c); + } else { + connection.close(); + } } - } - }); + }); - const _getNumUsers = () => connections.length; + const _getNumUsers = () => connections.length; - transient.multiplayer = { - getNumUsers: _getNumUsers, - }; + transient.multiplayer = { + getNumUsers: _getNumUsers, + }; - this._cleanup = () => { - for (let i = 0; i < connections.length; i++) { - const connection = connections[i]; - connection.close(); - } + this._cleanup = () => { + for (let i = 0; i < connections.length; i++) { + const connection = connections[i]; + connection.close(); + } - delete transient.multiplayer; - }; + delete transient.multiplayer; + }; - class MultiplayerApi extends EventEmitter { - getPlayerStatuses() { - return statuses; + class MultiplayerApi extends EventEmitter { + getPlayerStatuses() { + return statuses; + } } + const multiplayerApi = new MultiplayerApi(); + return multiplayerApi; } - const multiplayerApi = new MultiplayerApi(); - return multiplayerApi; - } - }); + }); } unmount() { diff --git a/core/engines/notification/client.js b/core/engines/notification/client.js index dbd1737cf..4a458082a 100644 --- a/core/engines/notification/client.js +++ b/core/engines/notification/client.js @@ -17,8 +17,10 @@ class Notification { } mount() { - const {_archae: archae} = this; - const {metadata: {site: {url: siteUrl}, server: {enabled: serverEnabled}}} = archae; + const { _archae: archae } = this; + const { + metadata: { site: { url: siteUrl }, server: { enabled: serverEnabled } }, + } = archae; const cleanups = []; this._cleanup = () => { @@ -33,181 +35,213 @@ class Notification { live = false; }); - return archae.requestPlugins([ - '/core/engines/three', - '/core/engines/input', - '/core/engines/webvr', - '/core/engines/biolumi', - '/core/engines/keyboard', - '/core/engines/rend', - '/core/engines/tags', - '/core/utils/geometry-utils', - '/core/utils/creature-utils', - ]).then(([ - three, - input, - webvr, - biolumi, - keyboard, - rend, - tags, - geometryUtils, - creatureUtils, - ]) => { - if (live) { - const {THREE, scene, camera} = three; - - const localVector = new THREE.Vector3(); - const localVector2 = new THREE.Vector3(); - const localQuaternion = new THREE.Quaternion(); - - const notifications = []; - - const hudMesh = (() => { - const menuUi = biolumi.makeUi({ - width: WIDTH, - height: HEIGHT, - color: [1, 1, 1, 0], - }); - const mesh = menuUi.makePage(({ - notifications, - }) => { - const text = _escape(notifications.map(({text}) => text).join(' ')); - - return { - type: 'html', - src: notificationRenderer.getHudSrc(text), - x: 0, - y: 0, - w: WIDTH, - h: HEIGHT, - }; - }, { - type: 'notification', - state: { - notifications, - }, - worldWidth: WORLD_WIDTH, - worldHeight: WORLD_HEIGHT, - }); - mesh.visible = false; - - mesh.needsUpdate = false; - mesh.update = () => { - if (mesh.needsUpdate) { - const {page} = mesh; - page.update(); + return archae + .requestPlugins([ + '/core/engines/three', + '/core/engines/input', + '/core/engines/webvr', + '/core/engines/biolumi', + '/core/engines/keyboard', + '/core/engines/rend', + '/core/engines/tags', + '/core/utils/geometry-utils', + '/core/utils/creature-utils', + ]) + .then( + ( + [ + three, + input, + webvr, + biolumi, + keyboard, + rend, + tags, + geometryUtils, + creatureUtils, + ] + ) => { + if (live) { + const { THREE, scene, camera } = three; + + const localVector = new THREE.Vector3(); + const localVector2 = new THREE.Vector3(); + const localQuaternion = new THREE.Quaternion(); + + const notifications = []; + + const hudMesh = (() => { + const menuUi = biolumi.makeUi({ + width: WIDTH, + height: HEIGHT, + color: [1, 1, 1, 0], + }); + const mesh = menuUi.makePage( + ({ notifications }) => { + const text = _escape( + notifications.map(({ text }) => text).join(' ') + ); + + return { + type: 'html', + src: notificationRenderer.getHudSrc(text), + x: 0, + y: 0, + w: WIDTH, + h: HEIGHT, + }; + }, + { + type: 'notification', + state: { + notifications, + }, + worldWidth: WORLD_WIDTH, + worldHeight: WORLD_HEIGHT, + } + ); + mesh.visible = false; mesh.needsUpdate = false; - } - }; - mesh.align = (position, rotation, scale, lerpFactor) => { - const targetPosition = position.clone().add( - new THREE.Vector3( - 0, - -WORLD_HEIGHT * 0.25, - -0.5 - ).applyQuaternion(rotation) - ); - const targetRotation = rotation; - const distance = position.distanceTo(targetPosition); - - if (lerpFactor < 1) { - mesh.position.add( - targetPosition.clone().sub(mesh.position).multiplyScalar(distance * lerpFactor) + mesh.update = () => { + if (mesh.needsUpdate) { + const { page } = mesh; + page.update(); + + mesh.needsUpdate = false; + } + }; + mesh.align = (position, rotation, scale, lerpFactor) => { + const targetPosition = position + .clone() + .add( + new THREE.Vector3( + 0, + -WORLD_HEIGHT * 0.25, + -0.5 + ).applyQuaternion(rotation) + ); + const targetRotation = rotation; + const distance = position.distanceTo(targetPosition); + + if (lerpFactor < 1) { + mesh.position.add( + targetPosition + .clone() + .sub(mesh.position) + .multiplyScalar(distance * lerpFactor) + ); + mesh.quaternion.slerp(targetRotation, lerpFactor); + mesh.scale.copy(scale); + } else { + mesh.position.copy(targetPosition); + mesh.quaternion.copy(targetRotation); + mesh.scale.copy(scale); + } + + mesh.updateMatrixWorld(); + }; + + camera.matrixWorld.decompose( + localVector, + localQuaternion, + localVector2 ); - mesh.quaternion.slerp(targetRotation, lerpFactor); - mesh.scale.copy(scale); - } else { - mesh.position.copy(targetPosition); - mesh.quaternion.copy(targetRotation); - mesh.scale.copy(scale); - } - - mesh.updateMatrixWorld(); - }; - - camera.matrixWorld.decompose(localVector, localQuaternion, localVector2); - mesh.align(localVector, localQuaternion, localVector2, 1); + mesh.align(localVector, localQuaternion, localVector2, 1); + + return mesh; + })(); + scene.add(hudMesh); + + let lastUpdateTime = Date.now(); + const _update = () => { + const now = Date.now(); + + const _updateHudMesh = () => { + if (hudMesh.visible) { + hudMesh.update(); + } + }; + const _alignHudMesh = () => { + if (hudMesh.visible) { + camera.matrixWorld.decompose( + localVector, + localQuaternion, + localVector2 + ); + hudMesh.align( + localVector, + localQuaternion, + localVector2, + (now - lastUpdateTime) * 0.02 + ); + } + }; + + _updateHudMesh(); + _alignHudMesh(); + + lastUpdateTime = now; + }; + rend.on('update', _update); - return mesh; - })(); - scene.add(hudMesh); + cleanups.push(() => { + scene.remove(hudMesh); - let lastUpdateTime = Date.now(); - const _update = () => { - const now = Date.now(); + rend.removeListener('update', _update); + }); - const _updateHudMesh = () => { - if (hudMesh.visible) { - hudMesh.update(); - } - }; - const _alignHudMesh = () => { - if (hudMesh.visible) { - camera.matrixWorld.decompose(localVector, localQuaternion, localVector2); - hudMesh.align(localVector, localQuaternion, localVector2, (now - lastUpdateTime) * 0.02); + class Notification { + constructor(text) { + this.text = text; + } } - }; - _updateHudMesh(); - _alignHudMesh(); + const _addNotification = text => { + const notification = new Notification(text); + notifications.push(notification); - lastUpdateTime = now; - }; - rend.on('update', _update); + if (notifications.length === 1) { + camera.matrixWorld.decompose( + localVector, + localQuaternion, + localVector2 + ); + hudMesh.align(localVector, localQuaternion, localVector2, 1); + hudMesh.visible = true; + } - cleanups.push(() => { - scene.remove(hudMesh); + hudMesh.needsUpdate = true; - rend.removeListener('update', _update); - }); - - class Notification { - constructor(text) { - this.text = text; - } - } - - const _addNotification = text => { - const notification = new Notification(text); - notifications.push(notification); - - if (notifications.length === 1) { - camera.matrixWorld.decompose(localVector, localQuaternion, localVector2); - hudMesh.align(localVector, localQuaternion, localVector2, 1); - hudMesh.visible = true; - } + return notification; + }; + const _removeNotification = notification => { + notifications.splice(notifications.indexOf(notification), 1); - hudMesh.needsUpdate = true; + if (notifications.length === 0) { + hudMesh.visible = false; + } - return notification; - }; - const _removeNotification = notification => { - notifications.splice(notifications.indexOf(notification), 1); + hudMesh.needsUpdate = true; + }; - if (notifications.length === 0) { - hudMesh.visible = false; + return { + addNotification: _addNotification, + removeNotification: _removeNotification, + }; } - - hudMesh.needsUpdate = true; - }; - - return { - addNotification: _addNotification, - removeNotification: _removeNotification, - }; - } - }); + } + ); } unmount() { this._cleanup(); } } -const _escape = s => s - .replace(//g, '>') - .replace(/&/g, '&'); +const _escape = s => + s + .replace(//g, '>') + .replace(/&/g, '&'); module.exports = Notification; diff --git a/core/engines/rend/client.js b/core/engines/rend/client.js index 13153c97e..da35384c8 100644 --- a/core/engines/rend/client.js +++ b/core/engines/rend/client.js @@ -27,16 +27,9 @@ class Rend { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; const { - metadata: { - site: { - url: siteUrl, - }, - server: { - enabled: serverEnabled, - }, - }, + metadata: { site: { url: siteUrl }, server: { enabled: serverEnabled } }, } = archae; const cleanups = []; @@ -53,287 +46,301 @@ class Rend { live = false; }); - return archae.requestPlugins([ - '/core/engines/bootstrap', - '/core/engines/input', - '/core/engines/three', - '/core/engines/webvr', - '/core/engines/biolumi', - '/core/engines/resource', - '/core/utils/js-utils', - '/core/utils/geometry-utils', - '/core/utils/hash-utils', - '/core/utils/creature-utils', - ]).then(([ - bootstrap, - input, - three, - webvr, - biolumi, - resource, - jsUtils, - geometryUtils, - hashUtils, - creatureUtils, - ]) => { - if (live) { - const {THREE, scene, camera, renderer} = three; - const {events} = jsUtils; - const {EventEmitter} = events; - const {murmur} = hashUtils; - const {sfx} = resource; - - const transparentMaterial = biolumi.getTransparentMaterial(); - - const menuRenderer = menuRender.makeRenderer({ - creatureUtils, - }); - - const uiTracker = biolumi.makeUiTracker(); - const {dotMeshes, boxMeshes} = uiTracker; - SIDES.forEach(side => { - scene.add(dotMeshes[side]); - scene.add(boxMeshes[side]); - }); - - const localUpdates = []; - - const auxObjects = { - tagsLinesMesh: null, - transformGizmos: null, - colorWheels: null, - controllerMeshes: null, - }; - - const statusState = { - state: 'connecting', - url: '', - address: '', - port: 0, - username: '', - users: [], - }; - const menuState = { - open: false, - position: new THREE.Vector3(0, DEFAULT_USER_HEIGHT, -1.5), - rotation: new THREE.Quaternion(), - scale: new THREE.Vector3(1, 1, 1), - }; - const navbarState = { - tab: 'status', - }; - - const menuMesh = (() => { - const object = new THREE.Object3D(); - object.position.copy(menuState.position); - object.quaternion.copy(menuState.rotation); - object.scale.copy(menuState.scale); - object.visible = menuState.open; - - const statusMesh = (() => { - const menuUi = biolumi.makeUi({ - width: WIDTH, - height: HEIGHT, + return archae + .requestPlugins([ + '/core/engines/bootstrap', + '/core/engines/input', + '/core/engines/three', + '/core/engines/webvr', + '/core/engines/biolumi', + '/core/engines/resource', + '/core/utils/js-utils', + '/core/utils/geometry-utils', + '/core/utils/hash-utils', + '/core/utils/creature-utils', + ]) + .then( + ( + [ + bootstrap, + input, + three, + webvr, + biolumi, + resource, + jsUtils, + geometryUtils, + hashUtils, + creatureUtils, + ] + ) => { + if (live) { + const { THREE, scene, camera, renderer } = three; + const { events } = jsUtils; + const { EventEmitter } = events; + const { murmur } = hashUtils; + const { sfx } = resource; + + const transparentMaterial = biolumi.getTransparentMaterial(); + + const menuRenderer = menuRender.makeRenderer({ + creatureUtils, }); - const mesh = menuUi.makePage(({ - status, - }) => ({ - type: 'html', - src: menuRenderer.getStatusSrc({status}), - x: 0, - y: 0, - w: WIDTH, - h: HEIGHT, - }), { - type: 'status', - state: { - status: statusState, - }, - worldWidth: WORLD_WIDTH, - worldHeight: WORLD_HEIGHT, + + const uiTracker = biolumi.makeUiTracker(); + const { dotMeshes, boxMeshes } = uiTracker; + SIDES.forEach(side => { + scene.add(dotMeshes[side]); + scene.add(boxMeshes[side]); }); - // mesh.receiveShadow = true; - const {page} = mesh; - uiTracker.addPage(page); + const localUpdates = []; - cleanups.push(() => { - uiTracker.removePage(page); - }); + const auxObjects = { + tagsLinesMesh: null, + transformGizmos: null, + colorWheels: null, + controllerMeshes: null, + }; - return mesh; - })(); - object.add(statusMesh); - object.statusMesh = statusMesh; - - object.worldMesh = null; - object.entityMesh = null; - object.fileMesh = null; - object.serversMesh = null; - object.walletMesh = null; - object.configMesh = null; - object.statsMesh = null; - - const navbarMesh = (() => { - const navbarUi = biolumi.makeUi({ - width: NAVBAR_WIDTH, - height: NAVBAR_HEIGHT, - }); - const mesh = navbarUi.makePage(({ - navbar: { - tab, - }, - }) => ({ - type: 'html', - src: menuRenderer.getNavbarSrc({tab}), - x: 0, - y: 0, - w: NAVBAR_WIDTH, - h: NAVBAR_HEIGHT, - }), { - type: 'navbar', - state: { - navbar: navbarState, - }, - worldWidth: NAVBAR_WORLD_WIDTH, - worldHeight: NAVBAR_WORLD_HEIGHT, - }); - mesh.position.y = (WORLD_HEIGHT / 2) + (NAVBAR_WORLD_HEIGHT / 2); - mesh.receiveShadow = true; + const statusState = { + state: 'connecting', + url: '', + address: '', + port: 0, + username: '', + users: [], + }; + const menuState = { + open: false, + position: new THREE.Vector3(0, DEFAULT_USER_HEIGHT, -1.5), + rotation: new THREE.Quaternion(), + scale: new THREE.Vector3(1, 1, 1), + }; + const navbarState = { + tab: 'status', + }; - const {page} = mesh; - uiTracker.addPage(page); + const menuMesh = (() => { + const object = new THREE.Object3D(); + object.position.copy(menuState.position); + object.quaternion.copy(menuState.rotation); + object.scale.copy(menuState.scale); + object.visible = menuState.open; + + const statusMesh = (() => { + const menuUi = biolumi.makeUi({ + width: WIDTH, + height: HEIGHT, + }); + const mesh = menuUi.makePage( + ({ status }) => ({ + type: 'html', + src: menuRenderer.getStatusSrc({ status }), + x: 0, + y: 0, + w: WIDTH, + h: HEIGHT, + }), + { + type: 'status', + state: { + status: statusState, + }, + worldWidth: WORLD_WIDTH, + worldHeight: WORLD_HEIGHT, + } + ); + // mesh.receiveShadow = true; + + const { page } = mesh; + uiTracker.addPage(page); + + cleanups.push(() => { + uiTracker.removePage(page); + }); + + return mesh; + })(); + object.add(statusMesh); + object.statusMesh = statusMesh; + + object.worldMesh = null; + object.entityMesh = null; + object.fileMesh = null; + object.serversMesh = null; + object.walletMesh = null; + object.configMesh = null; + object.statsMesh = null; + + const navbarMesh = (() => { + const navbarUi = biolumi.makeUi({ + width: NAVBAR_WIDTH, + height: NAVBAR_HEIGHT, + }); + const mesh = navbarUi.makePage( + ({ navbar: { tab } }) => ({ + type: 'html', + src: menuRenderer.getNavbarSrc({ tab }), + x: 0, + y: 0, + w: NAVBAR_WIDTH, + h: NAVBAR_HEIGHT, + }), + { + type: 'navbar', + state: { + navbar: navbarState, + }, + worldWidth: NAVBAR_WORLD_WIDTH, + worldHeight: NAVBAR_WORLD_HEIGHT, + } + ); + mesh.position.y = WORLD_HEIGHT / 2 + NAVBAR_WORLD_HEIGHT / 2; + mesh.receiveShadow = true; + + const { page } = mesh; + uiTracker.addPage(page); + + cleanups.push(() => { + uiTracker.removePage(page); + }); + + return mesh; + })(); + object.add(navbarMesh); + object.navbarMesh = navbarMesh; + + return object; + })(); + scene.add(menuMesh); + menuMesh.updateMatrixWorld(); + + const _setConnectionState = connectionState => { + const { state, protocol, address, port } = connectionState; + const url = protocol + '://' + address + ':' + port; + + statusState.state = state; + statusState.url = url; + statusState.address = address; + statusState.port = port; + }; + const _connectionStateChange = connectionState => { + _setConnectionState(connectionState); - cleanups.push(() => { - uiTracker.removePage(page); - }); + _updatePages(); + }; + bootstrap.on('connectionStateChange', _connectionStateChange); + const connectionState = bootstrap.getConnectionState(); + if (connectionState) { + _setConnectionState(connectionState); + } - return mesh; - })(); - object.add(navbarMesh); - object.navbarMesh = navbarMesh; - - return object; - })(); - scene.add(menuMesh); - menuMesh.updateMatrixWorld(); - - const _setConnectionState = connectionState => { - const {state, protocol, address, port} = connectionState; - const url = protocol + '://' + address + ':' + port; - - statusState.state = state; - statusState.url = url; - statusState.address = address; - statusState.port = port; - }; - const _connectionStateChange = connectionState => { - _setConnectionState(connectionState); - - _updatePages(); - }; - bootstrap.on('connectionStateChange', _connectionStateChange); - const connectionState = bootstrap.getConnectionState(); - if (connectionState) { - _setConnectionState(connectionState); - } + const _addressChange = address => { + const username = + names[Math.floor(murmur(address) / 0xffffffff * names.length)]; + statusState.username = username; + + _updatePages(); + + rendApi.emit('addressChange', { address, username }); + }; + bootstrap.on('addressChange', _addressChange); - const _addressChange = address => { - const username = names[Math.floor((murmur(address) / 0xFFFFFFFF) * names.length)]; - statusState.username = username; + const trigger = e => { + const { side } = e; - _updatePages(); + const _doClickNavbar = () => { + const hoverState = uiTracker.getHoverState(side); + const { anchor } = hoverState; + const onclick = (anchor && anchor.onclick) || ''; - rendApi.emit('addressChange', {address, username}); - }; - bootstrap.on('addressChange', _addressChange); + let match; + if ( + (match = onclick.match( + /^navbar:(status|world|entity|file|servers|wallet|options)$/ + )) + ) { + const newTab = match[1]; - const trigger = e => { - const {side} = e; + rendApi.setTab(newTab); - const _doClickNavbar = () => { - const hoverState = uiTracker.getHoverState(side); - const {anchor} = hoverState; - const onclick = (anchor && anchor.onclick) || ''; + return true; + } else { + return false; + } + }; + const _doClickMenu = () => { + const hoverState = uiTracker.getHoverState(side); + const { anchor } = hoverState; + const onclick = (anchor && anchor.onclick) || ''; - let match; - if (match = onclick.match(/^navbar:(status|world|entity|file|servers|wallet|options)$/)) { - const newTab = match[1]; + if (onclick === 'status:saveWorld') { + rendApi.saveAllEntities(); - rendApi.setTab(newTab); + return true; + } else if (onclick === 'status:clearWorld') { + rendApi.clearAllEntities(); - return true; - } else { - return false; - } - }; - const _doClickMenu = () => { - const hoverState = uiTracker.getHoverState(side); - const {anchor} = hoverState; - const onclick = (anchor && anchor.onclick) || ''; - - if (onclick === 'status:saveWorld') { - rendApi.saveAllEntities(); - - return true; - } else if (onclick === 'status:clearWorld') { - rendApi.clearAllEntities(); - - return true; - } else { - return false; - } - }; - const _doClickMenuBackground = () => { - const hoverState = uiTracker.getHoverState(side); - const {target} = hoverState; - - if (target && target.mesh && target.mesh.parent === menuMesh) { - return true; - } else { - return false; - } - }; + return true; + } else { + return false; + } + }; + const _doClickMenuBackground = () => { + const hoverState = uiTracker.getHoverState(side); + const { target } = hoverState; - if (_doClickNavbar() || _doClickMenu() || _doClickMenuBackground()) { - sfx.digi_plink.trigger(); + if (target && target.mesh && target.mesh.parent === menuMesh) { + return true; + } else { + return false; + } + }; - e.stopImmediatePropagation(); - } - }; - input.on('trigger', trigger, { - priority: -1, - }); - // this needs to be a native click event rather than a soft trigger click event due for clipboard copy security reasons - const click = () => { - const mode = webvr.getMode(); - - if (SIDES.indexOf(mode) !== -1) { - const side = mode; - const hoverState = uiTracker.getHoverState(side); - const {anchor} = hoverState; - const onclick = (anchor && anchor.onclick) || ''; - - if (onclick === 'status:url') { - const {url} = statusState; - const clipboardText = url; - - const ok = _copyToClipboard(clipboardText); - if (ok) { - console.log('copied to clipboard: ' + clipboardText); - } else { - console.warn('failed to copy URL:\n' + clipboardText); + if ( + _doClickNavbar() || + _doClickMenu() || + _doClickMenuBackground() + ) { + sfx.digi_plink.trigger(); + + e.stopImmediatePropagation(); } - } - } - }; - input.on('click', click); - const _closeMenu = () => { - menuMesh.visible = false; + }; + input.on('trigger', trigger, { + priority: -1, + }); + // this needs to be a native click event rather than a soft trigger click event due for clipboard copy security reasons + const click = () => { + const mode = webvr.getMode(); + + if (SIDES.indexOf(mode) !== -1) { + const side = mode; + const hoverState = uiTracker.getHoverState(side); + const { anchor } = hoverState; + const onclick = (anchor && anchor.onclick) || ''; + + if (onclick === 'status:url') { + const { url } = statusState; + const clipboardText = url; + + const ok = _copyToClipboard(clipboardText); + if (ok) { + console.log('copied to clipboard: ' + clipboardText); + } else { + console.warn('failed to copy URL:\n' + clipboardText); + } + } + } + }; + input.on('click', click); + const _closeMenu = () => { + menuMesh.visible = false; - menuState.open = false; // XXX need to cancel other menu states as well + menuState.open = false; // XXX need to cancel other menu states as well - /* const {transformGizmos} = auxObjects; + /* const {transformGizmos} = auxObjects; for (let i = 0; i < transformGizmos.length; i++) { const transformGizmo = transformGizmos[i]; transformGizmo.visible = false; @@ -342,38 +349,47 @@ class Rend { const {tagsLinesMesh} = auxObjects; tagsLinesMesh.visible = false; */ - uiTracker.setOpen(false); - _updateUiTracker(); - - sfx.digi_powerdown.trigger(); - - rendApi.emit('close'); - }; - const _openMenu = () => { - const {hmd: hmdStatus} = webvr.getStatus(); - const {worldPosition: hmdPosition, worldRotation: hmdRotation} = hmdStatus; - - const newMenuRotation = (() => { - const hmdEuler = new THREE.Euler().setFromQuaternion(hmdRotation, camera.rotation.order); - hmdEuler.x = 0; - hmdEuler.z = 0; - return new THREE.Quaternion().setFromEuler(hmdEuler); - })(); - const newMenuPosition = hmdPosition.clone() - .add(new THREE.Vector3(0, 0, -1.5).applyQuaternion(newMenuRotation)); - const newMenuScale = new THREE.Vector3(1, 1, 1); - menuMesh.position.copy(newMenuPosition); - menuMesh.quaternion.copy(newMenuRotation); - menuMesh.scale.copy(newMenuScale); - menuMesh.visible = true; - menuMesh.updateMatrixWorld(); - - menuState.open = true; - menuState.position.copy(newMenuPosition); - menuState.rotation.copy(newMenuRotation); - menuState.scale.copy(newMenuScale); - - /* const {transformGizmos} = auxObjects; + uiTracker.setOpen(false); + _updateUiTracker(); + + sfx.digi_powerdown.trigger(); + + rendApi.emit('close'); + }; + const _openMenu = () => { + const { hmd: hmdStatus } = webvr.getStatus(); + const { + worldPosition: hmdPosition, + worldRotation: hmdRotation, + } = hmdStatus; + + const newMenuRotation = (() => { + const hmdEuler = new THREE.Euler().setFromQuaternion( + hmdRotation, + camera.rotation.order + ); + hmdEuler.x = 0; + hmdEuler.z = 0; + return new THREE.Quaternion().setFromEuler(hmdEuler); + })(); + const newMenuPosition = hmdPosition + .clone() + .add( + new THREE.Vector3(0, 0, -1.5).applyQuaternion(newMenuRotation) + ); + const newMenuScale = new THREE.Vector3(1, 1, 1); + menuMesh.position.copy(newMenuPosition); + menuMesh.quaternion.copy(newMenuRotation); + menuMesh.scale.copy(newMenuScale); + menuMesh.visible = true; + menuMesh.updateMatrixWorld(); + + menuState.open = true; + menuState.position.copy(newMenuPosition); + menuState.rotation.copy(newMenuRotation); + menuState.scale.copy(newMenuScale); + + /* const {transformGizmos} = auxObjects; for (let i = 0; i < transformGizmos.length; i++) { const transformGizmo = transformGizmos[i]; transformGizmo.visible = true; @@ -383,275 +399,291 @@ class Rend { const {tagsLinesMesh} = auxObjects; tagsLinesMesh.visible = true; */ - uiTracker.setOpen(true); - _updateUiTracker(); + uiTracker.setOpen(true); + _updateUiTracker(); - sfx.digi_slide.trigger(); + sfx.digi_slide.trigger(); - rendApi.emit('open', { - position: newMenuPosition, - rotation: newMenuRotation, - scale: newMenuScale, - }); - }; - const menudown = () => { - const {open} = menuState; + rendApi.emit('open', { + position: newMenuPosition, + rotation: newMenuRotation, + scale: newMenuScale, + }); + }; + const menudown = () => { + const { open } = menuState; - if (open) { - _closeMenu(); - } else { - _openMenu(); - } - }; - input.on('menudown', menudown); - - scene.onRenderEye = camera => { - rendApi.updateEye(camera); - }; - scene.onBeforeRenderEye = () => { - rendApi.updateEyeStart(); - }; - scene.onAfterRenderEye = () => { - rendApi.updateEyeEnd(); - }; - - cleanups.push(() => { - scene.remove(menuMesh); - - for (let i = 0; i < SIDES.length; i++) { - const side = SIDES[i]; - scene.remove(uiTracker.dotMeshes[side]); - scene.remove(uiTracker.boxMeshes[side]); - } + if (open) { + _closeMenu(); + } else { + _openMenu(); + } + }; + input.on('menudown', menudown); - broadcast.removeListener('connectionStateChange', _connectionStateChange); - bootstrap.removeListener('addressChange', _addressChange); + scene.onRenderEye = camera => { + rendApi.updateEye(camera); + }; + scene.onBeforeRenderEye = () => { + rendApi.updateEyeStart(); + }; + scene.onAfterRenderEye = () => { + rendApi.updateEyeEnd(); + }; - input.removeListener('trigger', trigger); - input.removeListener('click', click); - input.removeListener('menudown', menudown); + cleanups.push(() => { + scene.remove(menuMesh); - scene.onRenderEye = null; - scene.onBeforeRenderEye = null; - scene.onAfterRenderEye = null; - }); + for (let i = 0; i < SIDES.length; i++) { + const side = SIDES[i]; + scene.remove(uiTracker.dotMeshes[side]); + scene.remove(uiTracker.boxMeshes[side]); + } - let lastMenuStatusJsonString = ''; - const _updateMenuPage = () => { - if (menuMesh) { - const menuStatusJsonString = JSON.stringify(statusState); + broadcast.removeListener( + 'connectionStateChange', + _connectionStateChange + ); + bootstrap.removeListener('addressChange', _addressChange); - if (menuStatusJsonString !== lastMenuStatusJsonString) { - const {statusMesh} = menuMesh; - const {page} = statusMesh; - page.update(); + input.removeListener('trigger', trigger); + input.removeListener('click', click); + input.removeListener('menudown', menudown); - lastMenuStatusJsonString = menuStatusJsonString; - } - }; - }; - const _updateNavbarPage = () => { - if (menuMesh) { - const {navbarMesh} = menuMesh; - const {page} = navbarMesh; - page.update(); - }; - }; - const _updatePages = () => { - _updateMenuPage(); - _updateNavbarPage(); - }; - _updatePages(); - - const _updateUiTracker = () => { - uiTracker.update({ - pose: webvr.getStatus(), - sides: (() => { - const vrMode = bootstrap.getVrMode(); - - if (vrMode === 'hmd') { - return SIDES; - } else { - const mode = webvr.getMode(); + scene.onRenderEye = null; + scene.onBeforeRenderEye = null; + scene.onAfterRenderEye = null; + }); - if (mode !== 'center') { - return [mode]; - } else { - return SIDES; + let lastMenuStatusJsonString = ''; + const _updateMenuPage = () => { + if (menuMesh) { + const menuStatusJsonString = JSON.stringify(statusState); + + if (menuStatusJsonString !== lastMenuStatusJsonString) { + const { statusMesh } = menuMesh; + const { page } = statusMesh; + page.update(); + + lastMenuStatusJsonString = menuStatusJsonString; } } - })(), - controllerMeshes: auxObjects.controllerMeshes, - }); - }; - - localUpdates.push(() => { - const _updateMenu = () => { - if (menuState.open) { - if (menuMesh.position.distanceTo(webvr.getStatus().hmd.worldPosition) > MENU_RANGE) { - _closeMenu(); + }; + const _updateNavbarPage = () => { + if (menuMesh) { + const { navbarMesh } = menuMesh; + const { page } = navbarMesh; + page.update(); } - } - }; - /* const _updateRenderer = () => { + }; + const _updatePages = () => { + _updateMenuPage(); + _updateNavbarPage(); + }; + _updatePages(); + + const _updateUiTracker = () => { + uiTracker.update({ + pose: webvr.getStatus(), + sides: (() => { + const vrMode = bootstrap.getVrMode(); + + if (vrMode === 'hmd') { + return SIDES; + } else { + const mode = webvr.getMode(); + + if (mode !== 'center') { + return [mode]; + } else { + return SIDES; + } + } + })(), + controllerMeshes: auxObjects.controllerMeshes, + }); + }; + + localUpdates.push(() => { + const _updateMenu = () => { + if (menuState.open) { + if ( + menuMesh.position.distanceTo( + webvr.getStatus().hmd.worldPosition + ) > MENU_RANGE + ) { + _closeMenu(); + } + } + }; + /* const _updateRenderer = () => { renderer.shadowMap.needsUpdate = true; }; */ - const _updateUiTimerLocal = () => { - biolumi.updateUiTimer(); - }; - const _updateUiTrackerLocal = () => { - if (menuState.open) { - _updateUiTracker(); - } - }; - - _updateMenu(); - // _updateRenderer(); - _updateUiTimerLocal(); - _updateUiTrackerLocal(); - }); - - class RendApi extends EventEmitter { - constructor() { - super(); - - this.setMaxListeners(100); - } - - isOpen() { - return menuState.open; - } + const _updateUiTimerLocal = () => { + biolumi.updateUiTimer(); + }; + const _updateUiTrackerLocal = () => { + if (menuState.open) { + _updateUiTracker(); + } + }; - getMenuState() { - return menuState; - } + _updateMenu(); + // _updateRenderer(); + _updateUiTimerLocal(); + _updateUiTrackerLocal(); + }); - getTab() { - return navbarState.tab; - } + class RendApi extends EventEmitter { + constructor() { + super(); - setTab(newTab) { - const _getTabMesh = tab => { - switch (tab) { - case 'status': return menuMesh.statusMesh; - case 'world': return menuMesh.worldMesh; - case 'entity': return menuMesh.entityMesh; - case 'file': return menuMesh.fileMesh; - case 'servers': return menuMesh.serversMesh; - case 'wallet': return menuMesh.walletMesh; - case 'options': return menuMesh.configMesh; - default: return null; + this.setMaxListeners(100); } - }; - const {tab: oldTab} = navbarState; - const oldMesh = _getTabMesh(oldTab); - const newMesh = _getTabMesh(newTab); + isOpen() { + return menuState.open; + } - oldMesh.visible = false; - newMesh.visible = true; + getMenuState() { + return menuState; + } - navbarState.tab = newTab; + getTab() { + return navbarState.tab; + } - _updateNavbarPage(); + setTab(newTab) { + const _getTabMesh = tab => { + switch (tab) { + case 'status': + return menuMesh.statusMesh; + case 'world': + return menuMesh.worldMesh; + case 'entity': + return menuMesh.entityMesh; + case 'file': + return menuMesh.fileMesh; + case 'servers': + return menuMesh.serversMesh; + case 'wallet': + return menuMesh.walletMesh; + case 'options': + return menuMesh.configMesh; + default: + return null; + } + }; + + const { tab: oldTab } = navbarState; + const oldMesh = _getTabMesh(oldTab); + const newMesh = _getTabMesh(newTab); + + oldMesh.visible = false; + newMesh.visible = true; + + navbarState.tab = newTab; + + _updateNavbarPage(); + + this.emit('tabchange', newTab); + } - this.emit('tabchange', newTab); - } + getMenuMesh() { + return menuMesh; + } - getMenuMesh() { - return menuMesh; - } + registerMenuMesh(name, object) { + menuMesh.add(object); + menuMesh[name] = object; + } - registerMenuMesh(name, object) { - menuMesh.add(object); - menuMesh[name] = object; - } + registerAuxObject(name, object) { + auxObjects[name] = object; + } - registerAuxObject(name, object) { - auxObjects[name] = object; - } + getStatus(name) { + return statusState[name]; + } - getStatus(name) { - return statusState[name]; - } + setStatus(name, value) { + statusState[name] = value; - setStatus(name, value) { - statusState[name] = value; + _updateMenuPage(); + } - _updateMenuPage(); - } + update() { + this.emit('update'); + } - update() { - this.emit('update'); - } + updateStart() { + this.emit('updateStart'); + } - updateStart() { - this.emit('updateStart'); - } + updateEnd() { + this.emit('updateEnd'); + } - updateEnd() { - this.emit('updateEnd'); - } + updateEye(camera) { + this.emit('updateEye', camera); + } - updateEye(camera) { - this.emit('updateEye', camera); - } + updateEyeStart() { + this.emit('updateEyeStart'); + } - updateEyeStart() { - this.emit('updateEyeStart'); - } + updateEyeEnd() { + this.emit('updateEyeEnd'); + } - updateEyeEnd() { - this.emit('updateEyeEnd'); - } + grab(options) { + this.emit('grab', options); + } - grab(options) { - this.emit('grab', options); - } + release(options) { + this.emit('release', options); + } - release(options) { - this.emit('release', options); - } + setEntity(item) { + this.emit('entitychange', item); + } - setEntity(item) { - this.emit('entitychange', item); - } + addPage(page) { + uiTracker.addPage(page); + } - addPage(page) { - uiTracker.addPage(page); - } + removePage(page) { + uiTracker.removePage(page); + } - removePage(page) { - uiTracker.removePage(page); - } + loadEntities(itemSpecs) { + this.emit('loadEntities', itemSpecs); + } - loadEntities(itemSpecs) { - this.emit('loadEntities', itemSpecs); - } + saveAllEntities() { + this.emit('saveAllEntities'); + } - saveAllEntities() { - this.emit('saveAllEntities'); - } + clearAllEntities() { + this.emit('clearAllEntities'); + } - clearAllEntities() { - this.emit('clearAllEntities'); - } + getHoverState(side) { + return uiTracker.getHoverState(side); + } + } + const rendApi = new RendApi(); + rendApi.on('update', () => { + for (let i = 0; i < localUpdates.length; i++) { + const localUpdate = localUpdates[i]; + localUpdate(); + } + }); - getHoverState(side) { - return uiTracker.getHoverState(side); + return rendApi; } } - const rendApi = new RendApi(); - rendApi.on('update', () => { - for (let i = 0; i < localUpdates.length; i++) { - const localUpdate = localUpdates[i]; - localUpdate(); - } - }); - - return rendApi; - } - }); + ); } unmount() { @@ -662,21 +694,24 @@ class Rend { const _copyToClipboard = s => { const mark = document.createElement('span'); mark.textContent = s; - mark.setAttribute('style', [ - // reset user styles for span element - 'all: unset', - // prevents scrolling to the end of the page - 'position: fixed', - 'top: 0', - 'clip: rect(0, 0, 0, 0)', - // used to preserve spaces and line breaks - 'white-space: pre', - // do not inherit user-select (it may be `none`) - '-webkit-user-select: text', - '-moz-user-select: text', - '-ms-user-select: text', - 'user-select: text', - ].join(';')); + mark.setAttribute( + 'style', + [ + // reset user styles for span element + 'all: unset', + // prevents scrolling to the end of the page + 'position: fixed', + 'top: 0', + 'clip: rect(0, 0, 0, 0)', + // used to preserve spaces and line breaks + 'white-space: pre', + // do not inherit user-select (it may be `none`) + '-webkit-user-select: text', + '-moz-user-select: text', + '-ms-user-select: text', + 'user-select: text', + ].join(';') + ); document.body.appendChild(mark); const range = document.createRange(); diff --git a/core/engines/rend/server.js b/core/engines/rend/server.js index 95b0833c4..a5a7aef1e 100644 --- a/core/engines/rend/server.js +++ b/core/engines/rend/server.js @@ -9,9 +9,9 @@ class Rend { } mount() { - const {_archae: archae} = this; - const {metadata: {site: {url: siteUrl}}} = archae; - const {app, dirname} = archae.getCore(); + const { _archae: archae } = this; + const { metadata: { site: { url: siteUrl } } } = archae; + const { app, dirname } = archae.getCore(); const mq = modulequery({ dirname: dirname, @@ -21,9 +21,10 @@ class Rend { function serveSearch(req, res, next) { const q = req.query.q ? decodeURIComponent(req.query.q) : ''; - mq.search(q, { - keywords: ['zeo-mod'], - }) + mq + .search(q, { + keywords: ['zeo-mod'], + }) .then(modSpecs => { res.json(modSpecs); }) @@ -36,7 +37,8 @@ class Rend { function serveMods(req, res, next) { const q = req.query.q ? decodeURIComponent(req.query.q) : ''; - mq.getModule(q) + mq + .getModule(q) .then(modSpec => { res.json(modSpec); }) diff --git a/core/engines/resource/client.js b/core/engines/resource/client.js index 37b13867a..d64d021f3 100644 --- a/core/engines/resource/client.js +++ b/core/engines/resource/client.js @@ -3,7 +3,6 @@ import { LABEL_HEIGHT, WORLD_LABEL_WIDTH, WORLD_LABEL_HEIGHT, - MENU_WIDTH, MENU_HEIGHT, WORLD_MENU_WIDTH, @@ -39,47 +38,45 @@ class Assets { live = false; }; - const _requestJson = url => fetch(url, { - credentials: 'include', - }) - .then(res => res.json()); - const _requestImage = url => new Promise((accept, reject) => { - const img = new Image(); + const _requestJson = url => + fetch(url, { + credentials: 'include', + }).then(res => res.json()); + const _requestImage = url => + new Promise((accept, reject) => { + const img = new Image(); - img.onload = () => { - _cleanup(); + img.onload = () => { + _cleanup(); - accept(img); - }; - img.onerror = err => { - reject(err); - }; + accept(img); + }; + img.onerror = err => { + reject(err); + }; - img.crossOrigin = true; - img.src = url; + img.crossOrigin = true; + img.src = url; - const _cleanup = () => { - img.oncanplay = null; - img.onerror = null; - }; - }); - const _requestSpritesheet = () => Promise.all([ - _requestImage(imgPath + '/spritesheet.png'), - _requestJson(imgPath + '/sprites.json'), - _requestJson(imgPath + '/assets.json'), - ]) - .then(([ - img, - spriteCoords, - assetSprites, - ]) => { + const _cleanup = () => { + img.oncanplay = null; + img.onerror = null; + }; + }); + const _requestSpritesheet = () => + Promise.all([ + _requestImage(imgPath + '/spritesheet.png'), + _requestJson(imgPath + '/sprites.json'), + _requestJson(imgPath + '/assets.json'), + ]).then(([img, spriteCoords, assetSprites]) => { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); const spriteSize = 16; - canvas.getSpriteImageData = (x, y, w, h) => ctx.getImageData(x, y, spriteSize, spriteSize); + canvas.getSpriteImageData = (x, y, w, h) => + ctx.getImageData(x, y, spriteSize, spriteSize); const spriteNames = Object.keys(spriteCoords); @@ -90,8 +87,10 @@ class Assets { assetSprites, }; }); - const _requestSfx = () => Promise.all(SFX.map(sfx => sfxr.requestSfx(sfxPath + '/' + sfx + '.ogg'))) - .then(audios => { + const _requestSfx = () => + Promise.all( + SFX.map(sfx => sfxr.requestSfx(sfxPath + '/' + sfx + '.ogg')) + ).then(audios => { const result = {}; for (let i = 0; i < SFX.length; i++) { result[SFX[i]] = audios[i]; @@ -111,78 +110,80 @@ class Assets { _requestSpritesheet(), _requestSfx(), ]) - .then(([ - plugins, - hmdModelJson, - controllerModelJson, - spritesheet, - sfx, - ]) => { - if (live) { - const [ - three, - biolumi, - hashUtils, - creatureUtils, - ] = plugins; - const {THREE, camera} = three; - - const _requestJsonMesh = (modelJson, modelTexturePath) => new Promise((accept, reject) => { - const loader = new THREE.ObjectLoader(); - loader.setTexturePath(modelTexturePath); - loader.parse(modelJson, accept); - }); - const _requestHmdMesh = () => _requestJsonMesh(hmdModelJson, hmdModelPath.replace(/[^\/]+$/, '')) - .then(mesh => { - const object = new THREE.Object3D(); - - mesh.scale.set(0.045, 0.045, 0.045); - mesh.rotation.order = camera.rotation.order; - mesh.rotation.y = Math.PI; - - object.add(mesh); - - return object; - }); - const _requestControllerMesh = () => _requestJsonMesh(controllerModelJson, controllerModelPath.replace(/[^\/]+$/, '')); - - return Promise.all([ - Promise.resolve(plugins), - _requestHmdMesh(), - _requestControllerMesh(), - Promise.resolve(spritesheet), - Promise.resolve(sfx), - ]); + .then( + ([plugins, hmdModelJson, controllerModelJson, spritesheet, sfx]) => { + if (live) { + const [three, biolumi, hashUtils, creatureUtils] = plugins; + const { THREE, camera } = three; + + const _requestJsonMesh = (modelJson, modelTexturePath) => + new Promise((accept, reject) => { + const loader = new THREE.ObjectLoader(); + loader.setTexturePath(modelTexturePath); + loader.parse(modelJson, accept); + }); + const _requestHmdMesh = () => + _requestJsonMesh( + hmdModelJson, + hmdModelPath.replace(/[^\/]+$/, '') + ).then(mesh => { + const object = new THREE.Object3D(); + + mesh.scale.set(0.045, 0.045, 0.045); + mesh.rotation.order = camera.rotation.order; + mesh.rotation.y = Math.PI; + + object.add(mesh); + + return object; + }); + const _requestControllerMesh = () => + _requestJsonMesh( + controllerModelJson, + controllerModelPath.replace(/[^\/]+$/, '') + ); + + return Promise.all([ + Promise.resolve(plugins), + _requestHmdMesh(), + _requestControllerMesh(), + Promise.resolve(spritesheet), + Promise.resolve(sfx), + ]); + } } - }) - .then(([ - [ - three, - biolumi, - hashUtils, - creatureUtils, - ], - hmdModelMesh, - controllerModelMesh, - spritesheet, - sfx, - ]) => { - if (live) { - const {THREE, camera} = three; - const {murmur} = hashUtils; - const menuRenderer = menuRender.makeRenderer({ - creatureUtils, - }); + ) + .then( + ( + [ + [three, biolumi, hashUtils, creatureUtils], + hmdModelMesh, + controllerModelMesh, + spritesheet, + sfx, + ] + ) => { + if (live) { + const { THREE, camera } = three; + const { murmur } = hashUtils; + const menuRenderer = menuRender.makeRenderer({ + creatureUtils, + }); - const _getSpriteImageData = s => { - const spriteName = spritesheet.assetSprites[s] || - spritesheet.spriteNames[Math.floor((murmur(s) / 0xFFFFFFFF) * spritesheet.spriteNames.length)]; - const spriteCoods = spritesheet.spriteCoords[spriteName]; - const [x, y] = spriteCoods; - const imageData = spritesheet.canvas.getSpriteImageData(x, y); - return imageData; - }; - /* const _makePlayerLabelMesh = ({username}) => { + const _getSpriteImageData = s => { + const spriteName = + spritesheet.assetSprites[s] || + spritesheet.spriteNames[ + Math.floor( + murmur(s) / 0xffffffff * spritesheet.spriteNames.length + ) + ]; + const spriteCoods = spritesheet.spriteCoords[spriteName]; + const [x, y] = spriteCoods; + const imageData = spritesheet.canvas.getSpriteImageData(x, y); + return imageData; + }; + /* const _makePlayerLabelMesh = ({username}) => { const labelState = { username: username, }; @@ -244,74 +245,80 @@ class Assets { return mesh; }; */ - const _makePlayerMenuMesh = ({username}) => { - const menuState = { - username: username, - }; - - const menuUi = biolumi.makeUi({ - width: MENU_WIDTH, - height: MENU_HEIGHT, - // color: [1, 1, 1, 0], - }); - const mesh = menuUi.makePage(({ - menu: menuState, - }) => ({ - type: 'html', - src: menuRenderer.getMenuSrc({ - menu: menuState, - }), - x: 0, - y: 0, - w: MENU_WIDTH, - h: MENU_HEIGHT, - }), { - type: 'menu', - state: { - menu: menuState, - }, - worldWidth: WORLD_MENU_WIDTH, - worldHeight: WORLD_MENU_HEIGHT, - }); - mesh.rotation.order = camera.rotation.order; - - const {page} = mesh; - page.update(); - - mesh.update = menuStatus => { - if (menuStatus.open) { - if (!menuStatus.position.equals(mesh.position) || !menuStatus.rotation.equals(mesh.rotation) || !menuStatus.scale.equals(mesh.scale)) { - mesh.position.copy(menuStatus.position); - mesh.quaternion.copy(menuStatus.rotation); - mesh.scale.copy(menuStatus.scale); - mesh.updateMatrixWorld(); + const _makePlayerMenuMesh = ({ username }) => { + const menuState = { + username: username, + }; + + const menuUi = biolumi.makeUi({ + width: MENU_WIDTH, + height: MENU_HEIGHT, + // color: [1, 1, 1, 0], + }); + const mesh = menuUi.makePage( + ({ menu: menuState }) => ({ + type: 'html', + src: menuRenderer.getMenuSrc({ + menu: menuState, + }), + x: 0, + y: 0, + w: MENU_WIDTH, + h: MENU_HEIGHT, + }), + { + type: 'menu', + state: { + menu: menuState, + }, + worldWidth: WORLD_MENU_WIDTH, + worldHeight: WORLD_MENU_HEIGHT, } + ); + mesh.rotation.order = camera.rotation.order; - if (!mesh.visible) { - mesh.visible = true; - } - } else { - if (mesh.visible) { - mesh.visible = false; + const { page } = mesh; + page.update(); + + mesh.update = menuStatus => { + if (menuStatus.open) { + if ( + !menuStatus.position.equals(mesh.position) || + !menuStatus.rotation.equals(mesh.rotation) || + !menuStatus.scale.equals(mesh.scale) + ) { + mesh.position.copy(menuStatus.position); + mesh.quaternion.copy(menuStatus.rotation); + mesh.scale.copy(menuStatus.scale); + mesh.updateMatrixWorld(); + } + + if (!mesh.visible) { + mesh.visible = true; + } + } else { + if (mesh.visible) { + mesh.visible = false; + } } - } - }; + }; - return mesh; - }; + return mesh; + }; - return { - models: { - hmdModelMesh, - controllerModelMesh, - }, - sfx: sfx, - getSpriteImageData: _getSpriteImageData, - // makePlayerLabelMesh: _makePlayerLabelMesh, - makePlayerMenuMesh: _makePlayerMenuMesh, - }; + return { + models: { + hmdModelMesh, + controllerModelMesh, + }, + sfx: sfx, + getSpriteImageData: _getSpriteImageData, + // makePlayerLabelMesh: _makePlayerLabelMesh, + makePlayerMenuMesh: _makePlayerMenuMesh, + }; + } } - }); + ); } unmount() { diff --git a/core/engines/resource/server.js b/core/engines/resource/server.js index eebdbc845..cb2057f04 100644 --- a/core/engines/resource/server.js +++ b/core/engines/resource/server.js @@ -12,14 +12,19 @@ class Resource { } mount() { - const {_archae: archae} = this; - const {express, app} = archae.getCore(); + const { _archae: archae } = this; + const { express, app } = archae.getCore(); - const controllerjsModelPath = path.join(path.dirname(require.resolve('controllerjs')), 'model'); + const controllerjsModelPath = path.join( + path.dirname(require.resolve('controllerjs')), + 'model' + ); const imgPath = path.join(__dirname, 'lib', 'img'); const sfxPath = path.join(__dirname, 'lib', 'sfx'); - const assetsHmdStatic = express.static(path.join(__dirname, 'lib', 'models', 'hmd')); + const assetsHmdStatic = express.static( + path.join(__dirname, 'lib', 'models', 'hmd') + ); function serveAssetsHmd(req, res, next) { assetsHmdStatic(req, res, next); } diff --git a/core/engines/scale/client.js b/core/engines/scale/client.js index 033688118..f2b589ab4 100644 --- a/core/engines/scale/client.js +++ b/core/engines/scale/client.js @@ -6,30 +6,26 @@ class Scale { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; let live = true; this._cleanup = () => { live = false; }; - return archae.requestPlugins([ - '/core/engines/three', - '/core/engines/webvr', - '/core/engines/input', - '/core/engines/rend', - '/core/engines/cyborg', - ]).then(([ - three, - webvr, - input, - rend, - cyborg, - ]) => { - if (live) { - const {THREE, scene, camera} = three; - - /* const _decomposeObjectMatrixWorld = object => _decomposeMatrix(object.matrixWorld); + return archae + .requestPlugins([ + '/core/engines/three', + '/core/engines/webvr', + '/core/engines/input', + '/core/engines/rend', + '/core/engines/cyborg', + ]) + .then(([three, webvr, input, rend, cyborg]) => { + if (live) { + const { THREE, scene, camera } = three; + + /* const _decomposeObjectMatrixWorld = object => _decomposeMatrix(object.matrixWorld); const _decomposeMatrix = matrix => { const position = new THREE.Vector3(); const rotation = new THREE.Quaternion(); @@ -41,115 +37,148 @@ class Scale { scale, }; }; */ - const _avgVectors = a => { - const result = new THREE.Vector3(); - for (let i = 0; i < a.length; i++) { - const e = a[i]; - result.add(e); - } - result.divideScalar(a.length); - return result; - }; - - const _makeScaleState = () => ({ - gripStart: null, - }); - const scaleStates = { - left: _makeScaleState(), - right: _makeScaleState(), - }; - - const scaleState = { - startScaleMid: null, - startScaleDistance: null, - startStageMatrix: null, - }; - - const _gripdown = e => { - const {side} = e; - const {gamepads} = webvr.getStatus(); - const gamepad = gamepads[side]; - - if (gamepad) { + const _avgVectors = a => { + const result = new THREE.Vector3(); + for (let i = 0; i < a.length; i++) { + const e = a[i]; + result.add(e); + } + result.divideScalar(a.length); + return result; + }; + + const _makeScaleState = () => ({ + gripStart: null, + }); + const scaleStates = { + left: _makeScaleState(), + right: _makeScaleState(), + }; + + const scaleState = { + startScaleMid: null, + startScaleDistance: null, + startStageMatrix: null, + }; + + const _gripdown = e => { + const { side } = e; + const { gamepads } = webvr.getStatus(); + const gamepad = gamepads[side]; + + if (gamepad) { + const scaleState = scaleStates[side]; + scaleState.gripStart = gamepad.position.clone(); + } + }; + input.on('gripdown', _gripdown); + const _gripup = e => { + const { side } = e; const scaleState = scaleStates[side]; - scaleState.gripStart = gamepad.position.clone(); - } - }; - input.on('gripdown', _gripdown); - const _gripup = e => { - const {side} = e; - const scaleState = scaleStates[side]; - - scaleState.gripStart = null; - }; - input.on('gripup', _gripup); - - const _update = () => { - const scaling = SIDES.every(side => scaleStates[side].gripStart !== null); - - if (scaling) { - const {gamepads} = webvr.getStatus(); - const haveGamepads = SIDES.every(side => Boolean(gamepads[side])); - - if (haveGamepads) { - const scaleMid = gamepads.left.position.clone() - .add(gamepads.right.position) - .divideScalar(2); - const scaleDistance = gamepads.left.position.clone() - .distanceTo(gamepads.right.position); - - let {startScaleMid, startScaleDistance, startStageMatrix} = scaleState; - if (startScaleMid === null) { - startScaleMid = scaleMid; - scaleState.startScaleMid = scaleMid; - } - if (startScaleDistance === null) { - startScaleDistance = scaleDistance; - scaleState.startScaleDistance = scaleDistance; - } - if (startStageMatrix === null) { - startStageMatrix = webvr.getStageMatrix(); - scaleState.startStageMatrix = startStageMatrix; - } - const scaleMidDiff = scaleMid.clone().sub(startScaleMid); - const scaleDistanceRatio = startScaleDistance / scaleDistance; - const newStageMatrix = startStageMatrix.clone() - .multiply(new THREE.Matrix4().makeTranslation(-scaleMidDiff.x, -scaleMidDiff.y, -scaleMidDiff.z)) - .multiply(new THREE.Matrix4().makeTranslation(scaleMid.x, scaleMid.y, scaleMid.z)) - .multiply(new THREE.Matrix4().makeScale(scaleDistanceRatio, scaleDistanceRatio, scaleDistanceRatio)) - .multiply(new THREE.Matrix4().makeTranslation(-scaleMid.x, -scaleMid.y, -scaleMid.z)); - webvr.setStageMatrix(newStageMatrix); - - // webvr.updateStatus(); - // webvr.updateUserStageMatrix(); - // cyborg.update(); + scaleState.gripStart = null; + }; + input.on('gripup', _gripup); + + const _update = () => { + const scaling = SIDES.every( + side => scaleStates[side].gripStart !== null + ); + + if (scaling) { + const { gamepads } = webvr.getStatus(); + const haveGamepads = SIDES.every(side => Boolean(gamepads[side])); + + if (haveGamepads) { + const scaleMid = gamepads.left.position + .clone() + .add(gamepads.right.position) + .divideScalar(2); + const scaleDistance = gamepads.left.position + .clone() + .distanceTo(gamepads.right.position); + + let { + startScaleMid, + startScaleDistance, + startStageMatrix, + } = scaleState; + if (startScaleMid === null) { + startScaleMid = scaleMid; + scaleState.startScaleMid = scaleMid; + } + if (startScaleDistance === null) { + startScaleDistance = scaleDistance; + scaleState.startScaleDistance = scaleDistance; + } + if (startStageMatrix === null) { + startStageMatrix = webvr.getStageMatrix(); + scaleState.startStageMatrix = startStageMatrix; + } + + const scaleMidDiff = scaleMid.clone().sub(startScaleMid); + const scaleDistanceRatio = startScaleDistance / scaleDistance; + const newStageMatrix = startStageMatrix + .clone() + .multiply( + new THREE.Matrix4().makeTranslation( + -scaleMidDiff.x, + -scaleMidDiff.y, + -scaleMidDiff.z + ) + ) + .multiply( + new THREE.Matrix4().makeTranslation( + scaleMid.x, + scaleMid.y, + scaleMid.z + ) + ) + .multiply( + new THREE.Matrix4().makeScale( + scaleDistanceRatio, + scaleDistanceRatio, + scaleDistanceRatio + ) + ) + .multiply( + new THREE.Matrix4().makeTranslation( + -scaleMid.x, + -scaleMid.y, + -scaleMid.z + ) + ); + webvr.setStageMatrix(newStageMatrix); + + // webvr.updateStatus(); + // webvr.updateUserStageMatrix(); + // cyborg.update(); + } else { + scaleState.startScaleMid = null; + scaleState.startScaleDistance = null; + scaleState.startStageMatrix = null; + } } else { scaleState.startScaleMid = null; scaleState.startScaleDistance = null; scaleState.startStageMatrix = null; } - } else { - scaleState.startScaleMid = null; - scaleState.startScaleDistance = null; - scaleState.startStageMatrix = null; - } - }; - // rend.on('update', _update); // XXX - - this._cleanup = () => { - input.removeListener('gripdown', _gripdown); - input.removeListener('gripup', _gripup); - - rend.removeListener('update', _update); - }; - } - }); + }; + // rend.on('update', _update); // XXX + + this._cleanup = () => { + input.removeListener('gripdown', _gripdown); + input.removeListener('gripup', _gripup); + + rend.removeListener('update', _update); + }; + } + }); } unmount() { this._cleanup(); } -}; +} module.exports = Scale; diff --git a/core/engines/servers/client.js b/core/engines/servers/client.js index f0845da7f..741288ff4 100644 --- a/core/engines/servers/client.js +++ b/core/engines/servers/client.js @@ -15,16 +15,9 @@ class Servers { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; const { - metadata: { - site: { - url: siteUrl, - }, - server: { - enabled: serverEnabled, - }, - }, + metadata: { site: { url: siteUrl }, server: { enabled: serverEnabled } }, } = archae; const cleanups = []; @@ -40,214 +33,221 @@ class Servers { live = false; }); - return archae.requestPlugins([ - '/core/engines/bootstrap', - '/core/engines/input', - '/core/engines/three', - '/core/engines/webvr', - '/core/engines/biolumi', - '/core/engines/resource', - '/core/engines/rend', - '/core/utils/js-utils', - '/core/utils/creature-utils', - ]).then(([ - bootstrap, - input, - three, - webvr, - biolumi, - resource, - rend, - jsUtils, - creatureUtils, - ]) => { - if (live) { - const {THREE, scene} = three; - const {events} = jsUtils; - const {EventEmitter} = events; - const {sfx} = resource; - - const menuRenderer = menuRender.makeRenderer({ - creatureUtils, - }); - - const _decomposeObjectMatrixWorld = object => { - const position = new THREE.Vector3(); - const rotation = new THREE.Quaternion(); - const scale = new THREE.Vector3(); - object.matrixWorld.decompose(position, rotation, scale); - return {position, rotation, scale}; - }; - - const transparentImg = biolumi.getTransparentImg(); - const transparentMaterial = biolumi.getTransparentMaterial(); - - const serversState = { - remoteServers: [], - page: 0, - loaded: false, - loading: true, - }; - - const _requestRemoteServers = () => fetch(siteUrl + '/servers/servers.json') - .then(res => res.json() - .then(servers => { - for (let i = 0; i < servers.length; i++) { - const server = servers[i]; - server.local = false; - } - - return servers; - }) - ); - const _updatePages = () => { - const {page} = serversMesh; - page.update(); - }; - - const serversMesh = (() => { - const serversUi = biolumi.makeUi({ - width: WIDTH, - height: HEIGHT, - }); - const mesh = serversUi.makePage(({ - servers: { - remoteServers, - page, - loading, - }, - }) => ({ - type: 'html', - src: menuRenderer.getServersPageSrc({ - remoteServers, - page, - loading, - }), - x: 0, - y: 0, - w: WIDTH, - h: HEIGHT, - }), { - type: 'servers', - state: { - servers: serversState, - }, - worldWidth: WORLD_WIDTH, - worldHeight: WORLD_HEIGHT, - isEnabled: () => rend.isOpen(), - }); - mesh.visible = false; - // mesh.receiveShadow = true; - - const {page} = mesh; - rend.addPage(page); - page.initialUpdate(); - - cleanups.push(() => { - rend.removePage(page); - }); - - return mesh; - })(); - rend.registerMenuMesh('serversMesh', serversMesh); - serversMesh.updateMatrixWorld(); - - const _connectServer = serverUrl => { - window.parent.location = serverUrl; - }; - - const _trigger = e => { - const {side} = e; - const hoverState = rend.getHoverState(side); - const {anchor} = hoverState; - const onclick = (anchor && anchor.onclick) || ''; - - const _clickMenu = () => { - let match; - if (match = onclick.match(/^servers:go:([0-9]+)$/)) { - const index = parseInt(match[1], 10); - - const {remoteServers} = serversState; - const remoteServer = remoteServers[index]; - const {url: remoteServerUrl} = remoteServer; - _connectServer(remoteServerUrl); - - return true; - } else if (match = onclick.match(/^servers:(up|down)$/)) { - const direction = match[1]; - - serversState.page += (direction === 'up' ? -1 : 1); - - _updatePages(); - - return true; - } else { - return false; - } - }; - const _clickMenuBackground = () => { - const hoverState = rend.getHoverState(side); - const {target} = hoverState; - - if (target && target.mesh && target.mesh.parent === serversMesh) { - return true; - } else { - return false; - } - }; - - if (_clickMenu()) { - sfx.digi_select.trigger(); - - e.stopImmediatePropagation(); - } else if (_clickMenuBackground()) { - sfx.digi_plink.trigger(); - - e.stopImmediatePropagation(); - } - }; - input.on('trigger', _trigger, { - priority: 1, - }); + return archae + .requestPlugins([ + '/core/engines/bootstrap', + '/core/engines/input', + '/core/engines/three', + '/core/engines/webvr', + '/core/engines/biolumi', + '/core/engines/resource', + '/core/engines/rend', + '/core/utils/js-utils', + '/core/utils/creature-utils', + ]) + .then( + ( + [ + bootstrap, + input, + three, + webvr, + biolumi, + resource, + rend, + jsUtils, + creatureUtils, + ] + ) => { + if (live) { + const { THREE, scene } = three; + const { events } = jsUtils; + const { EventEmitter } = events; + const { sfx } = resource; + + const menuRenderer = menuRender.makeRenderer({ + creatureUtils, + }); + + const _decomposeObjectMatrixWorld = object => { + const position = new THREE.Vector3(); + const rotation = new THREE.Quaternion(); + const scale = new THREE.Vector3(); + object.matrixWorld.decompose(position, rotation, scale); + return { position, rotation, scale }; + }; + + const transparentImg = biolumi.getTransparentImg(); + const transparentMaterial = biolumi.getTransparentMaterial(); + + const serversState = { + remoteServers: [], + page: 0, + loaded: false, + loading: true, + }; + + const _requestRemoteServers = () => + fetch(siteUrl + '/servers/servers.json').then(res => + res.json().then(servers => { + for (let i = 0; i < servers.length; i++) { + const server = servers[i]; + server.local = false; + } + + return servers; + }) + ); + const _updatePages = () => { + const { page } = serversMesh; + page.update(); + }; + + const serversMesh = (() => { + const serversUi = biolumi.makeUi({ + width: WIDTH, + height: HEIGHT, + }); + const mesh = serversUi.makePage( + ({ servers: { remoteServers, page, loading } }) => ({ + type: 'html', + src: menuRenderer.getServersPageSrc({ + remoteServers, + page, + loading, + }), + x: 0, + y: 0, + w: WIDTH, + h: HEIGHT, + }), + { + type: 'servers', + state: { + servers: serversState, + }, + worldWidth: WORLD_WIDTH, + worldHeight: WORLD_HEIGHT, + isEnabled: () => rend.isOpen(), + } + ); + mesh.visible = false; + // mesh.receiveShadow = true; + + const { page } = mesh; + rend.addPage(page); + page.initialUpdate(); + + cleanups.push(() => { + rend.removePage(page); + }); + + return mesh; + })(); + rend.registerMenuMesh('serversMesh', serversMesh); + serversMesh.updateMatrixWorld(); + + const _connectServer = serverUrl => { + window.parent.location = serverUrl; + }; + + const _trigger = e => { + const { side } = e; + const hoverState = rend.getHoverState(side); + const { anchor } = hoverState; + const onclick = (anchor && anchor.onclick) || ''; + + const _clickMenu = () => { + let match; + if ((match = onclick.match(/^servers:go:([0-9]+)$/))) { + const index = parseInt(match[1], 10); + + const { remoteServers } = serversState; + const remoteServer = remoteServers[index]; + const { url: remoteServerUrl } = remoteServer; + _connectServer(remoteServerUrl); + + return true; + } else if ((match = onclick.match(/^servers:(up|down)$/))) { + const direction = match[1]; + + serversState.page += direction === 'up' ? -1 : 1; - cleanups.push(() => { - rend.removeListener('tabchange', _tabchange); - input.removeListener('trigger', _trigger); - }); + _updatePages(); - const _tabchange = tab => { - if (tab === 'servers') { - const {loaded} = serversState; + return true; + } else { + return false; + } + }; + const _clickMenuBackground = () => { + const hoverState = rend.getHoverState(side); + const { target } = hoverState; + + if ( + target && + target.mesh && + target.mesh.parent === serversMesh + ) { + return true; + } else { + return false; + } + }; + + if (_clickMenu()) { + sfx.digi_select.trigger(); + + e.stopImmediatePropagation(); + } else if (_clickMenuBackground()) { + sfx.digi_plink.trigger(); + + e.stopImmediatePropagation(); + } + }; + input.on('trigger', _trigger, { + priority: 1, + }); - if (!loaded) { - serversState.loading = true; - serversState.loaded = true; + cleanups.push(() => { + rend.removeListener('tabchange', _tabchange); + input.removeListener('trigger', _trigger); + }); - _updatePages(); + const _tabchange = tab => { + if (tab === 'servers') { + const { loaded } = serversState; - _requestRemoteServers() - .then(remoteServers => { - serversState.remoteServers = remoteServers; - serversState.page = 0; - serversState.loading = false; + if (!loaded) { + serversState.loading = true; + serversState.loaded = true; _updatePages(); - }) - .catch(err => { - console.warn(err); - }); - } - } - }; - rend.on('tabchange', _tabchange); - cleanups.push(() => { - input.removeListener('trigger', _trigger); + _requestRemoteServers() + .then(remoteServers => { + serversState.remoteServers = remoteServers; + serversState.page = 0; + serversState.loading = false; + + _updatePages(); + }) + .catch(err => { + console.warn(err); + }); + } + } + }; + rend.on('tabchange', _tabchange); - rend.removeListener('tabchange', _tabchange); - }); - } - }); + cleanups.push(() => { + input.removeListener('trigger', _trigger); + + rend.removeListener('tabchange', _tabchange); + }); + } + } + ); } unmount() { diff --git a/core/engines/somnifer/client.js b/core/engines/somnifer/client.js index ebf891a81..02b77fa2b 100644 --- a/core/engines/somnifer/client.js +++ b/core/engines/somnifer/client.js @@ -6,20 +6,16 @@ class Somnifer { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; let live = true; this._cleanup = () => { live = false; }; - return archae.requestPlugins([ - '/core/engines/three', - ]).then(([ - three, - ]) => { + return archae.requestPlugins(['/core/engines/three']).then(([three]) => { if (live) { - const {THREE, camera} = three; + const { THREE, camera } = three; const listener = new THREE.AudioListener(); camera.add(listener); @@ -31,7 +27,10 @@ class Somnifer { const sound = new THREE.PositionalAudio(listener); this.sound = sound; - const analyser = new THREE.AudioAnalyser(sound, ANALYSER_RESOLUTION); + const analyser = new THREE.AudioAnalyser( + sound, + ANALYSER_RESOLUTION + ); this.analyser = analyser; this.object = null; @@ -40,16 +39,18 @@ class Somnifer { } setInputElement(el) { - const {sound} = this; + const { sound } = this; const source = sound.context.createMediaElementSource(el); sound.setNodeSource(source); } setInputElements(els) { - const {sound} = this; + const { sound } = this; - const sources = els.map(el => sound.context.createMediaElementSource(el)); + const sources = els.map(el => + sound.context.createMediaElementSource(el) + ); const merger = sound.context.createChannelMerger(2); let outputIndex = 0; @@ -66,20 +67,20 @@ class Somnifer { } setInputMediaStream(mediaStream) { - const {sound} = this; + const { sound } = this; const source = sound.context.createMediaStreamSource(mediaStream); sound.setNodeSource(source); } setInputSource(source) { - const {sound} = this; + const { sound } = this; sound.setNodeSource(source); } setObject(object) { - const {sound} = this; + const { sound } = this; object.add(sound); @@ -87,13 +88,13 @@ class Somnifer { } getAmplitude() { - const {analyser} = this; + const { analyser } = this; return analyser.getAverageFrequency() / 255; } destroy() { - const {sound, object} = this; + const { sound, object } = this; if (object) { object.remove(sound); diff --git a/core/engines/stage/client.js b/core/engines/stage/client.js index a05c6b81c..9bc54d5e2 100644 --- a/core/engines/stage/client.js +++ b/core/engines/stage/client.js @@ -4,23 +4,24 @@ class Stage { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; let live = true; this._cleanup = () => { live = false; }; - const _requestImage = src => new Promise((accept, reject) => { - const img = new Image(); - img.onload = () => { - accept(img); - }; - img.onerror = err => { - reject(err); - }; - img.src = src; - }); + const _requestImage = src => + new Promise((accept, reject) => { + const img = new Image(); + img.onload = () => { + accept(img); + }; + img.onerror = err => { + reject(err); + }; + img.src = src; + }); return Promise.all([ archae.requestPlugins([ @@ -29,25 +30,21 @@ class Stage { '/core/utils/js-utils', ]), _requestImage('/archae/stage/img/grid.png'), - ]).then(([ - [ - three, - webvr, - jsUtils, - ], - gridImg, - ]) => { + ]).then(([[three, webvr, jsUtils], gridImg]) => { if (live) { - const {THREE, scene, camera, renderer} = three; - const {events} = jsUtils; - const {EventEmitter} = events; + const { THREE, scene, camera, renderer } = three; + const { events } = jsUtils; + const { EventEmitter } = events; const floorGridMesh = (() => { - const geometry = new THREE.PlaneBufferGeometry(10, 10) - .applyMatrix(new THREE.Matrix4().makeRotationFromQuaternion(new THREE.Quaternion().setFromUnitVectors( - new THREE.Vector3(0, 0, -1), - new THREE.Vector3(0, 1, 0) - ))); + const geometry = new THREE.PlaneBufferGeometry(10, 10).applyMatrix( + new THREE.Matrix4().makeRotationFromQuaternion( + new THREE.Quaternion().setFromUnitVectors( + new THREE.Vector3(0, 0, -1), + new THREE.Vector3(0, 1, 0) + ) + ) + ); const uvs = geometry.getAttribute('uv').array; const numUvs = uvs.length / 2; for (let i = 0; i < numUvs; i++) { @@ -152,11 +149,13 @@ class Stage { floorGridMesh.scale ); floorGridMesh.matrix.copy(stageMatrix); - floorGridMesh.matrixWorld.multiplyMatrices(floorGridMesh.parent.matrixWorld, stageMatrix); + floorGridMesh.matrixWorld.multiplyMatrices( + floorGridMesh.parent.matrixWorld, + stageMatrix + ); } }); - this._cleanup = () => { stageApi.destroy(); }; diff --git a/core/engines/stage/server.js b/core/engines/stage/server.js index 0138af84d..d09292c42 100644 --- a/core/engines/stage/server.js +++ b/core/engines/stage/server.js @@ -6,8 +6,8 @@ class Stage { } mount() { - const {_archae: archae} = this; - const {express, app} = archae.getCore(); + const { _archae: archae } = this; + const { express, app } = archae.getCore(); const stageImgStatic = express.static(path.join(__dirname, 'img')); function serveStageImg(req, res, next) { @@ -17,9 +17,7 @@ class Stage { this._cleanup = () => { function removeMiddlewares(route, i, routes) { - if ( - route.handle.name === 'serveStageImg' - ) { + if (route.handle.name === 'serveStageImg') { routes.splice(i, 1); } if (route.route) { diff --git a/core/engines/stck/client.js b/core/engines/stck/client.js index 4ad6a59cf..51e40785d 100644 --- a/core/engines/stck/client.js +++ b/core/engines/stck/client.js @@ -17,132 +17,142 @@ class Stck { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; let live = true; this._cleanup = () => { live = false; }; - return archae.requestPlugins([ - '/core/engines/three', - '/core/utils/js-utils', - ]).then(([ - three, - jsUtils, - ]) => { - if (live) { - const {THREE} = three; - const {events} = jsUtils; - const {EventEmitter} = events; - - const localVector = new THREE.Vector3(); - - const bodies = {}; - - const worker = new Worker('archae/plugins/_core_engines_stck/build/worker.js'); - let queues = {}; - let numRemovedQueues = 0; - const _cleanupQueues = () => { - if (++numRemovedQueues >= 16) { - const newQueues = {}; - for (const id in queues) { - const entry = queues[id]; - if (entry !== null) { - newQueues[id] = entry; + return archae + .requestPlugins(['/core/engines/three', '/core/utils/js-utils']) + .then(([three, jsUtils]) => { + if (live) { + const { THREE } = three; + const { events } = jsUtils; + const { EventEmitter } = events; + + const localVector = new THREE.Vector3(); + + const bodies = {}; + + const worker = new Worker( + 'archae/plugins/_core_engines_stck/build/worker.js' + ); + let queues = {}; + let numRemovedQueues = 0; + const _cleanupQueues = () => { + if (++numRemovedQueues >= 16) { + const newQueues = {}; + for (const id in queues) { + const entry = queues[id]; + if (entry !== null) { + newQueues[id] = entry; + } } + queues = newQueues; + numRemovedQueues = 0; } - queues = newQueues; - numRemovedQueues = 0; - } - }; - worker.requestAddBody = (n, type, spec) => { - worker.postMessage({ - method: 'addBody', - args: [n, type, spec], - }); - }; - worker.requestRemoveBody = n => { - worker.postMessage({ - method: 'removeBody', - args: [n], - }); - }; - /* worker.requestSetState = (n, spec) => { + }; + worker.requestAddBody = (n, type, spec) => { + worker.postMessage({ + method: 'addBody', + args: [n, type, spec], + }); + }; + worker.requestRemoveBody = n => { + worker.postMessage({ + method: 'removeBody', + args: [n], + }); + }; + /* worker.requestSetState = (n, spec) => { worker.postMessage({ method: 'setState', args: [n, spec], }); }; */ - worker.requestSetData = (n, data) => { - worker.postMessage({ - method: 'setData', - args: [n, data], - }); - }; - worker.requestCheck = (position, rotation, cb) => { - const id = _makeId(); - worker.postMessage({ - method: 'check', - args: [id, position, rotation], - }); - queues[id] = data => { - cb(protocolUtils.parseCheck(data, 0)); + worker.requestSetData = (n, data) => { + worker.postMessage({ + method: 'setData', + args: [n, data], + }); }; - }; - worker.requestTeleport = (position, rotation, cb) => { - const id = _makeId(); - worker.postMessage({ - method: 'teleport', - args: [id, position, rotation], - }); - queues[id] = data => { - cb(protocolUtils.parseTeleport(localVector, data, 0)); + worker.requestCheck = (position, rotation, cb) => { + const id = _makeId(); + worker.postMessage({ + method: 'check', + args: [id, position, rotation], + }); + queues[id] = data => { + cb(protocolUtils.parseCheck(data, 0)); + }; }; - }; - worker.onmessage = e => { - const {data} = e; - const type = protocolUtils.parseType(data); - - if (type === protocolUtils.TYPE_UPDATE) { - const n = protocolUtils.parseN(data); - const body = bodies[n]; - - if (body) { - protocolUtils.parseUpdate(body.position, body.rotation, body.scale, body.velocity, data); - body.emit('update'); - } - } else if (type === protocolUtils.TYPE_COLLIDE) { - const n = protocolUtils.parseN(data); - const body = bodies[n]; - - if (body) { - body.emit('collide'); - } - } else if (type === protocolUtils.TYPE_RESPONSE) { - const id = protocolUtils.parseN(data); + worker.requestTeleport = (position, rotation, cb) => { + const id = _makeId(); + worker.postMessage({ + method: 'teleport', + args: [id, position, rotation], + }); + queues[id] = data => { + cb(protocolUtils.parseTeleport(localVector, data, 0)); + }; + }; + worker.onmessage = e => { + const { data } = e; + const type = protocolUtils.parseType(data); + + if (type === protocolUtils.TYPE_UPDATE) { + const n = protocolUtils.parseN(data); + const body = bodies[n]; + + if (body) { + protocolUtils.parseUpdate( + body.position, + body.rotation, + body.scale, + body.velocity, + data + ); + body.emit('update'); + } + } else if (type === protocolUtils.TYPE_COLLIDE) { + const n = protocolUtils.parseN(data); + const body = bodies[n]; - queues[id](data); - queues[id] = null; + if (body) { + body.emit('collide'); + } + } else if (type === protocolUtils.TYPE_RESPONSE) { + const id = protocolUtils.parseN(data); - _cleanupQueues(); - } else { - console.warn('stck worker got invalid message type: ', type); - } - }; + queues[id](data); + queues[id] = null; - class Body extends EventEmitter { - constructor(n, position = new THREE.Vector3(), rotation = new THREE.Quaternion(), scale = new THREE.Vector3(), velocity = new THREE.Vector3()) { - super(); + _cleanupQueues(); + } else { + console.warn('stck worker got invalid message type: ', type); + } + }; - this.n = n; - this.position = position; - this.rotation = rotation; - this.scale = scale; - this.velocity = velocity; - } + class Body extends EventEmitter { + constructor( + n, + position = new THREE.Vector3(), + rotation = new THREE.Quaternion(), + scale = new THREE.Vector3(), + velocity = new THREE.Vector3() + ) { + super(); + + this.n = n; + this.position = position; + this.rotation = rotation; + this.scale = scale; + this.velocity = velocity; + } - /* setState(position, rotation, scale, velocity) { + /* setState(position, rotation, scale, velocity) { worker.requestSetState(this.n, { position, rotation, @@ -151,93 +161,109 @@ class Stck { }); } */ - setData(data) { - worker.requestSetData(this.n, data); + setData(data) { + worker.requestSetData(this.n, data); + } } - } - return { - makeDynamicBoxBody(position, size, velocity) { - const n = _makeN(); - const body = new Body(n); - bodies[n] = body; - - worker.requestAddBody(n, 'dynamicBox', { - position: position.toArray(), - rotation: [0, 0, 0, 1], - scale: [1, 1, 1], - size: size.toArray(), - velocity: velocity.toArray(), - }); - - return body; - }, - makeStaticHeightfieldBody(position, width, depth, data) { - const ox = Math.floor(position.x / NUM_CELLS); - const oz = Math.floor(position.z / NUM_CELLS); - const n = _getStaticBodyIndex(STATIC_BODY_TYPES.staticHeightfield, ox, oz); - const body = new Body(n); - bodies[n] = body; - - worker.requestAddBody(n, 'staticHeightfield', { - position: position.toArray(), - width, - depth, - data, - }); - - return body; - }, - makeStaticEtherfieldBody(position, width, height, depth, data) { - const ox = Math.floor(position.x / NUM_CELLS); - const oz = Math.floor(position.z / NUM_CELLS); - const n = _getStaticBodyIndex(STATIC_BODY_TYPES.staticEtherfield, ox, oz); - const body = new Body(n); - bodies[n] = body; - - worker.requestAddBody(n, 'staticEtherfield', { - position: position.toArray(), - width, - height, - depth, - data, - }); - - return body; - }, - makeStaticBlockfieldBody(position, width, height, depth, data) { - const ox = Math.floor(position.x / NUM_CELLS); - const oz = Math.floor(position.z / NUM_CELLS); - const n = _getStaticBodyIndex(STATIC_BODY_TYPES.staticBlockfield, ox, oz); - const body = new Body(n); - bodies[n] = body; - - worker.requestAddBody(n, 'staticBlockfield', { - position: position.toArray(), - width, - height, - depth, - data, - }); - - return body; - }, - requestCheck(position, rotation, cb) { - worker.requestCheck(position.toArray(), rotation.toArray(), cb); - }, - requestTeleport(position, rotation, cb) { - worker.requestTeleport(position.toArray(), rotation.toArray(), cb); - }, - destroyBody(body) { - const {n} = body; - - worker.requestRemoveBody(n); - - delete bodies[n]; - }, - }; - } - }); + return { + makeDynamicBoxBody(position, size, velocity) { + const n = _makeN(); + const body = new Body(n); + bodies[n] = body; + + worker.requestAddBody(n, 'dynamicBox', { + position: position.toArray(), + rotation: [0, 0, 0, 1], + scale: [1, 1, 1], + size: size.toArray(), + velocity: velocity.toArray(), + }); + + return body; + }, + makeStaticHeightfieldBody(position, width, depth, data) { + const ox = Math.floor(position.x / NUM_CELLS); + const oz = Math.floor(position.z / NUM_CELLS); + const n = _getStaticBodyIndex( + STATIC_BODY_TYPES.staticHeightfield, + ox, + oz + ); + const body = new Body(n); + bodies[n] = body; + + worker.requestAddBody(n, 'staticHeightfield', { + position: position.toArray(), + width, + depth, + data, + }); + + return body; + }, + makeStaticEtherfieldBody(position, width, height, depth, data) { + const ox = Math.floor(position.x / NUM_CELLS); + const oz = Math.floor(position.z / NUM_CELLS); + const n = _getStaticBodyIndex( + STATIC_BODY_TYPES.staticEtherfield, + ox, + oz + ); + const body = new Body(n); + bodies[n] = body; + + worker.requestAddBody(n, 'staticEtherfield', { + position: position.toArray(), + width, + height, + depth, + data, + }); + + return body; + }, + makeStaticBlockfieldBody(position, width, height, depth, data) { + const ox = Math.floor(position.x / NUM_CELLS); + const oz = Math.floor(position.z / NUM_CELLS); + const n = _getStaticBodyIndex( + STATIC_BODY_TYPES.staticBlockfield, + ox, + oz + ); + const body = new Body(n); + bodies[n] = body; + + worker.requestAddBody(n, 'staticBlockfield', { + position: position.toArray(), + width, + height, + depth, + data, + }); + + return body; + }, + requestCheck(position, rotation, cb) { + worker.requestCheck(position.toArray(), rotation.toArray(), cb); + }, + requestTeleport(position, rotation, cb) { + worker.requestTeleport( + position.toArray(), + rotation.toArray(), + cb + ); + }, + destroyBody(body) { + const { n } = body; + + worker.requestRemoveBody(n); + + delete bodies[n]; + }, + }; + } + }); } unmount() { @@ -251,8 +277,9 @@ let ns = 0; const _makeN = () => ns++; function mod(value, divisor) { var n = value % divisor; - return n < 0 ? (divisor + n) : n; + return n < 0 ? divisor + n : n; } -const _getStaticBodyIndex = (t, x, z) => (mod(t, 0xFFFF) << 16) | (mod(x, 0xFF) << 8) | mod(z, 0xFF); +const _getStaticBodyIndex = (t, x, z) => + (mod(t, 0xffff) << 16) | (mod(x, 0xff) << 8) | mod(z, 0xff); module.exports = Stck; diff --git a/core/engines/stck/worker.js b/core/engines/stck/worker.js index 4bc46bb6b..c1664ac4c 100644 --- a/core/engines/stck/worker.js +++ b/core/engines/stck/worker.js @@ -1,5 +1,5 @@ importScripts('/archae/assets/three.js'); -const {exports: THREE} = self.module; +const { exports: THREE } = self.module; self.module = {}; const protocolUtils = require('./lib/utils/protocol-utils'); @@ -26,17 +26,20 @@ const forwardVector = new THREE.Vector3(0, 0, -1); function mod(value, divisor) { var n = value % divisor; - return n < 0 ? (divisor + n) : n; + return n < 0 ? divisor + n : n; } -const _getStaticBodyIndex = (t, x, z) => (mod(t, 0xFFFF) << 16) | (mod(x, 0xFF) << 8) | mod(z, 0xFF); -const _getEtherfieldIndex = (x, y, z) => x + (z * NUM_CELLS_OVERSCAN) + (y * NUM_CELLS_OVERSCAN * NUM_CELLS_OVERSCAN); -const _li = (left, right, x) => left*(1-x) + right*x; -const _bi = (bottomLeft, bottomRight, topLeft, topRight, x, y) => bottomLeft*(1-x)*(1-y) + bottomRight*x*(1-y) + topLeft*(1-x)*y + topRight*x*y; -const _tri = (c010, c110, c000, c100, c011, c111, c001, c101, x, y, z) => _li( - _bi(c010, c110, c000, c100, x, z), - _bi(c011, c111, c001, c101, x, z), - y -); +const _getStaticBodyIndex = (t, x, z) => + (mod(t, 0xffff) << 16) | (mod(x, 0xff) << 8) | mod(z, 0xff); +const _getEtherfieldIndex = (x, y, z) => + x + z * NUM_CELLS_OVERSCAN + y * NUM_CELLS_OVERSCAN * NUM_CELLS_OVERSCAN; +const _li = (left, right, x) => left * (1 - x) + right * x; +const _bi = (bottomLeft, bottomRight, topLeft, topRight, x, y) => + bottomLeft * (1 - x) * (1 - y) + + bottomRight * x * (1 - y) + + topLeft * (1 - x) * y + + topRight * x * y; +const _tri = (c010, c110, c000, c100, c011, c111, c001, c101, x, y, z) => + _li(_bi(c010, c110, c000, c100, x, z), _bi(c011, c111, c001, c101, x, z), y); const buffer = new ArrayBuffer(protocolUtils.BUFFER_SIZE); @@ -44,7 +47,14 @@ const dynamicBodies = {}; const staticBodies = {}; class BoxBody { - constructor(n, position = new THREE.Vector3(), rotation = new THREE.Quaternion(), scale = new THREE.Vector3(1, 1, 1), size = new THREE.Vector3(0.1, 0.1, 0.1), velocity = new THREE.Vector3()) { + constructor( + n, + position = new THREE.Vector3(), + rotation = new THREE.Quaternion(), + scale = new THREE.Vector3(1, 1, 1), + size = new THREE.Vector3(0.1, 0.1, 0.1), + velocity = new THREE.Vector3() + ) { this.n = n; this.position = position; this.rotation = rotation; @@ -59,7 +69,15 @@ class BoxBody { this.scale.copy(scale); this.velocity.copy(velocity); - protocolUtils.stringifyUpdate(this.n, this.position, this.rotation, this.scale, this.velocity, buffer, 0); + protocolUtils.stringifyUpdate( + this.n, + this.position, + this.rotation, + this.scale, + this.velocity, + buffer, + 0 + ); postMessage(buffer); } @@ -80,7 +98,14 @@ class BoxBody { } */ class EtherfieldBody { - constructor(n, position = new THREE.Vector3(), width = 0, height = 0, depth = 0, data = new Float32Array(0)) { + constructor( + n, + position = new THREE.Vector3(), + width = 0, + height = 0, + depth = 0, + data = new Float32Array(0) + ) { this.n = n; this.position = position; this.width = width; @@ -91,7 +116,14 @@ class EtherfieldBody { } class BlockfieldBody { - constructor(n, position = new THREE.Vector3(), width = 0, height = 0, depth = 0, data = new Uint8Array(0)) { + constructor( + n, + position = new THREE.Vector3(), + width = 0, + height = 0, + depth = 0, + data = new Uint8Array(0) + ) { this.n = n; this.position = position; this.width = width; @@ -162,7 +194,11 @@ const _checkCollision = position => { } } */ - const staticEtherfieldIndex = _getStaticBodyIndex(STATIC_BODY_TYPES.staticEtherfield, ox, oz); + const staticEtherfieldIndex = _getStaticBodyIndex( + STATIC_BODY_TYPES.staticEtherfield, + ox, + oz + ); const staticEtherfieldBody = staticBodies[staticEtherfieldIndex]; if (staticEtherfieldBody) { const ox = Math.floor(position.x / NUM_CELLS); @@ -180,7 +216,8 @@ const _checkCollision = position => { const aly = ly - minY; const alz = lz - minZ; - const _getEtherfield = (x, y, z) => staticEtherfieldBody.data[_getEtherfieldIndex(x, y, z)]; + const _getEtherfield = (x, y, z) => + staticEtherfieldBody.data[_getEtherfieldIndex(x, y, z)]; const v = _tri( _getEtherfield(minX, minY, minZ), @@ -200,7 +237,11 @@ const _checkCollision = position => { } } - const staticBlockfieldIndex = _getStaticBodyIndex(STATIC_BODY_TYPES.staticBlockfield, ox, oz); + const staticBlockfieldIndex = _getStaticBodyIndex( + STATIC_BODY_TYPES.staticBlockfield, + ox, + oz + ); const staticBlockfieldBody = staticBodies[staticBlockfieldIndex]; if (staticBlockfieldBody) { const ax = Math.floor(position.x); @@ -214,10 +255,12 @@ const _checkCollision = position => { const _getBlockfieldIndex = (x, y, z) => { const oy = Math.floor(y / NUM_CELLS); - return oy * NUM_CELLS * NUM_CELLS * NUM_CELLS + - (x) + - ((y - oy * NUM_CELLS) * NUM_CELLS) + - (z * NUM_CELLS * NUM_CELLS); + return ( + oy * NUM_CELLS * NUM_CELLS * NUM_CELLS + + x + + (y - oy * NUM_CELLS) * NUM_CELLS + + z * NUM_CELLS * NUM_CELLS + ); }; const block = staticBlockfieldBody.data[_getBlockfieldIndex(lx, ly, lz)]; @@ -235,10 +278,12 @@ const interval = setInterval(() => { for (const index in dynamicBodies) { const body = dynamicBodies[index]; if (body) { - const {position, velocity, size} = body; - nextVelocity.copy(velocity) + const { position, velocity, size } = body; + nextVelocity + .copy(velocity) .add(localVector.copy(upVector).multiplyScalar(GRAVITY * timeDiff)); - nextPosition.copy(position) + nextPosition + .copy(position) .add(localVector.copy(nextVelocity).multiplyScalar(timeDiff / 1000)); let collided = false; @@ -250,7 +295,8 @@ const interval = setInterval(() => { collided = collided || !velocity.equals(zeroVector); } - if ((nextPosition.y - (size.y / 2)) < 0) { // hard limit to y=0 + if (nextPosition.y - size.y / 2 < 0) { + // hard limit to y=0 nextPosition.y = size.y / 2; nextVelocity.copy(zeroVector); @@ -275,7 +321,8 @@ this._cleanup = () => { const TELEPORT_GRAVITY = GRAVITY * 1000 / 50000; const _getTeleportTarget = (position, rotation) => { - const velocity = localVector2.copy(forwardVector) // can't use localVector since it's an argument + const velocity = localVector2 + .copy(forwardVector) // can't use localVector since it's an argument .applyQuaternion(rotation) .multiplyScalar(0.05); for (let i = 0; i < 1000; i++) { @@ -288,7 +335,8 @@ const _getTeleportTarget = (position, rotation) => { return null; }; const _getCheckResult = (position, rotation) => { - const velocity = localVector2.copy(forwardVector) // can't use localVector since it's an argument + const velocity = localVector2 + .copy(forwardVector) // can't use localVector since it's an argument .applyQuaternion(rotation) .multiplyScalar(0.1); for (let i = 0; i < 10; i++) { @@ -301,17 +349,17 @@ const _getCheckResult = (position, rotation) => { }; self.onmessage = e => { - const {data} = e; - const {method} = data; + const { data } = e; + const { method } = data; switch (method) { case 'addBody': { - const {args} = data; + const { args } = data; const [n, type, spec] = args; switch (type) { case 'dynamicBox': { - const {position, rotation, scale, size, velocity} = spec; + const { position, rotation, scale, size, velocity } = spec; const body = new BoxBody( n, new THREE.Vector3().fromArray(position), @@ -341,7 +389,7 @@ self.onmessage = e => { break; } */ case 'staticEtherfield': { - const {position, width, height, depth, data} = spec; + const { position, width, height, depth, data } = spec; const body = new EtherfieldBody( n, new THREE.Vector3().fromArray(position), @@ -352,13 +400,17 @@ self.onmessage = e => { ); const ox = Math.floor(position[0] / NUM_CELLS); const oz = Math.floor(position[2] / NUM_CELLS); - const index = _getStaticBodyIndex(STATIC_BODY_TYPES.staticEtherfield, ox, oz); + const index = _getStaticBodyIndex( + STATIC_BODY_TYPES.staticEtherfield, + ox, + oz + ); staticBodies[index] = body; break; } case 'staticBlockfield': { - const {position, width, height, depth, data} = spec; + const { position, width, height, depth, data } = spec; const body = new BlockfieldBody( n, new THREE.Vector3().fromArray(position), @@ -369,7 +421,11 @@ self.onmessage = e => { ); const ox = Math.floor(position[0] / NUM_CELLS); const oz = Math.floor(position[2] / NUM_CELLS); - const index = _getStaticBodyIndex(STATIC_BODY_TYPES.staticBlockfield, ox, oz); + const index = _getStaticBodyIndex( + STATIC_BODY_TYPES.staticBlockfield, + ox, + oz + ); staticBodies[index] = body; break; @@ -384,7 +440,7 @@ self.onmessage = e => { break; } case 'removeBody': { - const {args} = data; + const { args } = data; const [n] = args; if (dynamicBodies[n]) { @@ -405,7 +461,7 @@ self.onmessage = e => { break; } */ case 'setData': { - const {args} = data; + const { args } = data; const [n, newData] = args; staticBodies[n].data = newData; @@ -413,25 +469,44 @@ self.onmessage = e => { break; } case 'check': { - const {args} = data; + const { args } = data; const [id, positionArray, rotationArray] = args; - protocolUtils.stringifyCheck(id, _getCheckResult(localVector.fromArray(positionArray), localQuaternion.fromArray(rotationArray)), buffer, 0); + protocolUtils.stringifyCheck( + id, + _getCheckResult( + localVector.fromArray(positionArray), + localQuaternion.fromArray(rotationArray) + ), + buffer, + 0 + ); postMessage(buffer); break; } case 'teleport': { - const {args} = data; + const { args } = data; const [id, positionArray, rotationArray] = args; - protocolUtils.stringifyTeleport(id, _getTeleportTarget(localVector.fromArray(positionArray), localQuaternion.fromArray(rotationArray)), buffer, 0); + protocolUtils.stringifyTeleport( + id, + _getTeleportTarget( + localVector.fromArray(positionArray), + localQuaternion.fromArray(rotationArray) + ), + buffer, + 0 + ); postMessage(buffer); break; } default: { - console.warn('invalid heightfield worker method:', JSON.stringify(method)); + console.warn( + 'invalid heightfield worker method:', + JSON.stringify(method) + ); break; } } diff --git a/core/engines/tags/client.js b/core/engines/tags/client.js index 064401a7b..18fcb1d56 100644 --- a/core/engines/tags/client.js +++ b/core/engines/tags/client.js @@ -11,7 +11,6 @@ import { DETAILS_HEIGHT, MENU_WIDTH, MENU_HEIGHT, - WORLD_WIDTH, WORLD_HEIGHT, WORLD_DEPTH, @@ -42,7 +41,7 @@ class Tags { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; const cleanups = []; this._cleanup = () => { @@ -58,514 +57,599 @@ class Tags { live = false; }); - return archae.requestPlugins([ - '/core/engines/bootstrap', - '/core/engines/three', - '/core/engines/input', - '/core/engines/webvr', - '/core/engines/cyborg', - '/core/engines/biolumi', - '/core/engines/keyboard', - '/core/engines/loader', - // '/core/engines/fs', - '/core/engines/somnifer', - '/core/engines/rend', - '/core/utils/js-utils', - '/core/utils/type-utils', - '/core/utils/geometry-utils', - '/core/utils/image-utils', - '/core/utils/creature-utils', - ]) - .then(([ - bootstrap, - three, - input, - webvr, - cyborg, - biolumi, - keyboard, - loader, - // fs, - somnifer, - rend, - jsUtils, - typeUtils, - geometryUtils, - imageUtils, - creatureUtils, - ]) => { - if (live) { - const {THREE, scene, camera} = three; - const {events} = jsUtils; - const {EventEmitter} = events; - - const upVector = new THREE.Vector3(0, 1, 0); - const lineGeometry = geometryUtils.unindexBufferGeometry(new THREE.BoxBufferGeometry(1, 1, 1)); - - const nubbinMaterial = new THREE.MeshBasicMaterial({ - color: 0xCCCCCC, - }); - cleanups.push(() => { - nubbinMaterial.dispose(); - }); - const scalerMaterial = new THREE.MeshBasicMaterial({ - color: 0xFFFF00, - }); - cleanups.push(() => { - scalerMaterial.dispose(); - }); - - const transparentImg = biolumi.getTransparentImg(); - - const _decomposeObjectMatrixWorld = object => _decomposeMatrix(object.matrixWorld); - const _decomposeMatrix = matrix => { - const position = new THREE.Vector3(); - const rotation = new THREE.Quaternion(); - const scale = new THREE.Vector3(); - matrix.decompose(position, rotation, scale); - return {position, rotation, scale}; - }; - const _getWorldPosition = object => new THREE.Vector3().setFromMatrixPosition(object.matrixWorld); - - const lineMaterial = new THREE.MeshBasicMaterial({ - color: 0x000000, - transparent: true, - opacity: 0.5, - }); + return archae + .requestPlugins([ + '/core/engines/bootstrap', + '/core/engines/three', + '/core/engines/input', + '/core/engines/webvr', + '/core/engines/cyborg', + '/core/engines/biolumi', + '/core/engines/keyboard', + '/core/engines/loader', + // '/core/engines/fs', + '/core/engines/somnifer', + '/core/engines/rend', + '/core/utils/js-utils', + '/core/utils/type-utils', + '/core/utils/geometry-utils', + '/core/utils/image-utils', + '/core/utils/creature-utils', + ]) + .then( + ( + [ + bootstrap, + three, + input, + webvr, + cyborg, + biolumi, + keyboard, + loader, + // fs, + somnifer, + rend, + jsUtils, + typeUtils, + geometryUtils, + imageUtils, + creatureUtils, + ] + ) => { + if (live) { + const { THREE, scene, camera } = three; + const { events } = jsUtils; + const { EventEmitter } = events; + + const upVector = new THREE.Vector3(0, 1, 0); + const lineGeometry = geometryUtils.unindexBufferGeometry( + new THREE.BoxBufferGeometry(1, 1, 1) + ); + + const nubbinMaterial = new THREE.MeshBasicMaterial({ + color: 0xcccccc, + }); + cleanups.push(() => { + nubbinMaterial.dispose(); + }); + const scalerMaterial = new THREE.MeshBasicMaterial({ + color: 0xffff00, + }); + cleanups.push(() => { + scalerMaterial.dispose(); + }); + + const transparentImg = biolumi.getTransparentImg(); + + const _decomposeObjectMatrixWorld = object => + _decomposeMatrix(object.matrixWorld); + const _decomposeMatrix = matrix => { + const position = new THREE.Vector3(); + const rotation = new THREE.Quaternion(); + const scale = new THREE.Vector3(); + matrix.decompose(position, rotation, scale); + return { position, rotation, scale }; + }; + const _getWorldPosition = object => + new THREE.Vector3().setFromMatrixPosition(object.matrixWorld); + + const lineMaterial = new THREE.MeshBasicMaterial({ + color: 0x000000, + transparent: true, + opacity: 0.5, + }); + + const subcontentFontSpec = { + fonts: biolumi.getFonts(), + fontSize: 24, + lineHeight: 1.4, + fontWeight: biolumi.getFontWeight(), + fontStyle: biolumi.getFontStyle(), + }; - const subcontentFontSpec = { - fonts: biolumi.getFonts(), - fontSize: 24, - lineHeight: 1.4, - fontWeight: biolumi.getFontWeight(), - fontStyle: biolumi.getFontStyle(), - }; - - const modulesMutex = new MultiMutex(); - const modules = new Map(); - class ModuleTracker { - constructor() { - this.refCount = 0; + const modulesMutex = new MultiMutex(); + const modules = new Map(); + class ModuleTracker { + constructor() { + this.refCount = 0; + } } - } - const _addModule = name => { - return modulesMutex.lock(name) - .then(unlock => new Promise((accept, reject) => { - const entry = modules.get(name); + const _addModule = name => { + return modulesMutex.lock(name).then( + unlock => + new Promise((accept, reject) => { + const entry = modules.get(name); - if (entry) { - entry.refCount++; - - accept(true); - - unlock(); - } else { - loader.requestPlugin(name) - .then(pluginInstance => { - const entry = new ModuleTracker(); + if (entry) { entry.refCount++; - modules.set(name, entry); - - accept(false); + accept(true); unlock(); - }) - .catch(err => { - reject(err); + } else { + loader + .requestPlugin(name) + .then(pluginInstance => { + const entry = new ModuleTracker(); + entry.refCount++; - unlock(); - }); - } - })); - }; - const _removeModule = name => { - return modulesMutex.lock(name) - .then(unlock => new Promise((accept, reject) => { - const entry = modules.get(name); - - if (entry.refCount === 1) { - loader.removePlugin(name) - .then(() => { - modules.delete(name); + modules.set(name, entry); - accept(); + accept(false); - unlock(); - }) - .catch(err => { - reject(err); + unlock(); + }) + .catch(err => { + reject(err); - unlock(); - }); - } else { - entry.refCount--; + unlock(); + }); + } + }) + ); + }; + const _removeModule = name => { + return modulesMutex.lock(name).then( + unlock => + new Promise((accept, reject) => { + const entry = modules.get(name); + + if (entry.refCount === 1) { + loader + .removePlugin(name) + .then(() => { + modules.delete(name); + + accept(); + + unlock(); + }) + .catch(err => { + reject(err); + + unlock(); + }); + } else { + entry.refCount--; - accept(); + accept(); - unlock(); - } - })); - }; + unlock(); + } + }) + ); + }; - const rootWorldElement = document.createElement('world'); - rootWorldElement.style.cssText = 'display: none !important;'; - document.body.appendChild(rootWorldElement); + const rootWorldElement = document.createElement('world'); + rootWorldElement.style.cssText = 'display: none !important;'; + document.body.appendChild(rootWorldElement); - const _addEntityCallback = (componentApi, entityElement) => { - let {_numComponents: numComponents} = entityElement; + const _addEntityCallback = (componentApi, entityElement) => { + let { _numComponents: numComponents } = entityElement; - if (numComponents === undefined) { - numComponents = 0; - } - numComponents++; - entityElement._numComponents = numComponents; + if (numComponents === undefined) { + numComponents = 0; + } + numComponents++; + entityElement._numComponents = numComponents; - componentApi.entityAddedCallback(entityElement); + componentApi.entityAddedCallback(entityElement); - if (numComponents === 1) { - tagsApi.emit('elementAdded', entityElement); - } - }; - const _entityValueChangedCallbacks = (componentApi, entityElement, entityAttributes) => { - const {attributes: componentAttributes = {}} = componentApi; - - for (const componentAttributeName in componentAttributes) { - const componentAttribute = componentAttributes[componentAttributeName]; - const {type: attributeType} = componentAttribute; - const entityAttribute = entityAttributes[componentAttributeName] || {}; - const {value: attributeValueJson} = entityAttribute; - - if (attributeValueJson !== undefined) { - const oldValue = null; - const newValue = attributeValueJson; - - componentApi.entityAttributeValueChangedCallback(entityElement, componentAttributeName, oldValue, newValue); - - const {item} = entityElement; - const {id: entityId} = item; - tagsApi.emit('attributeValueChanged', { - entityId: entityId, - attributeName: componentAttributeName, - type: attributeType, - oldValue: oldValue, - newValue: newValue, - }); + if (numComponents === 1) { + tagsApi.emit('elementAdded', entityElement); } - } - }; - const _removeEntityCallback = (componentApi, entityElement) => { - let {_numComponents: numComponents} = entityElement; + }; + const _entityValueChangedCallbacks = ( + componentApi, + entityElement, + entityAttributes + ) => { + const { attributes: componentAttributes = {} } = componentApi; + + for (const componentAttributeName in componentAttributes) { + const componentAttribute = + componentAttributes[componentAttributeName]; + const { type: attributeType } = componentAttribute; + const entityAttribute = + entityAttributes[componentAttributeName] || {}; + const { value: attributeValueJson } = entityAttribute; + + if (attributeValueJson !== undefined) { + const oldValue = null; + const newValue = attributeValueJson; + + componentApi.entityAttributeValueChangedCallback( + entityElement, + componentAttributeName, + oldValue, + newValue + ); + + const { item } = entityElement; + const { id: entityId } = item; + tagsApi.emit('attributeValueChanged', { + entityId: entityId, + attributeName: componentAttributeName, + type: attributeType, + oldValue: oldValue, + newValue: newValue, + }); + } + } + }; + const _removeEntityCallback = (componentApi, entityElement) => { + let { _numComponents: numComponents } = entityElement; - numComponents--; - entityElement._numComponents = numComponents; + numComponents--; + entityElement._numComponents = numComponents; - componentApi.entityRemovedCallback(entityElement); + componentApi.entityRemovedCallback(entityElement); - if (numComponents === 0) { - tagsApi.emit('elementRemoved', entityElement); - } - }; + if (numComponents === 0) { + tagsApi.emit('elementRemoved', entityElement); + } + }; - const _getElementJsonAttributes = element => { - const result = {}; + const _getElementJsonAttributes = element => { + const result = {}; - const {attributes} = element; - for (let i = 0; i < attributes.length; i++) { - const attribute = attributes[i]; - const {name, value: valueString} = attribute; - const value = _parseAttribute(valueString); + const { attributes } = element; + for (let i = 0; i < attributes.length; i++) { + const attribute = attributes[i]; + const { name, value: valueString } = attribute; + const value = _parseAttribute(valueString); - result[name] = { - value: value, - }; - } + result[name] = { + value: value, + }; + } - return result; - }; - const _parseAttribute = attributeString => { - if (attributeString !== null) { - return _jsonParse(attributeString); - } else { - return undefined; - } - }; - const _stringifyAttribute = attributeValue => { - if (attributeValue !== undefined) { - return JSON.stringify(attributeValue); - } else { - return ''; - } - }; - const _someList = (list, predicate) => { - for (let i = 0; i < list.length; i++) { - const e = list[i]; - if (predicate(e)) { - return true; + return result; + }; + const _parseAttribute = attributeString => { + if (attributeString !== null) { + return _jsonParse(attributeString); + } else { + return undefined; + } + }; + const _stringifyAttribute = attributeValue => { + if (attributeValue !== undefined) { + return JSON.stringify(attributeValue); + } else { + return ''; + } + }; + const _someList = (list, predicate) => { + for (let i = 0; i < list.length; i++) { + const e = list[i]; + if (predicate(e)) { + return true; + } + } + return false; + }; + const _addEntity = (module, element) => { + const { item: initialEntityItem } = element; + const entityAttributes = _getElementJsonAttributes(element); + if (!initialEntityItem) { + // element added manually + const tagName = element.tagName.toLowerCase(); + tagsApi.emit('mutateAddEntity', { + element: element, + tagName: tagName, + attributes: entityAttributes, + }); } - } - return false; - }; - const _addEntity = (module, element) => { - const {item: initialEntityItem} = element; - const entityAttributes = _getElementJsonAttributes(element); - if (!initialEntityItem) { // element added manually - const tagName = element.tagName.toLowerCase(); - tagsApi.emit('mutateAddEntity', { - element: element, - tagName: tagName, - attributes: entityAttributes, - }); - } - const {item} = element; - item.instancing = true; + const { item } = element; + item.instancing = true; - _addModule(module) - .then(existed => { - if (existed) { - const componentApis = tagComponentApis[module] || []; + _addModule(module) + .then(existed => { + if (existed) { + const componentApis = tagComponentApis[module] || []; - for (let i = 0; i < componentApis.length; i++) { - const componentApi = componentApis[i]; + for (let i = 0; i < componentApis.length; i++) { + const componentApi = componentApis[i]; - _addEntityCallback(componentApi, element); - _entityValueChangedCallbacks(componentApi, element, entityAttributes); + _addEntityCallback(componentApi, element); + _entityValueChangedCallbacks( + componentApi, + element, + entityAttributes + ); + } } - } - item.instancing = false; - }) - .catch(err => { - console.warn(err); - }); - }; - const _removeEntity = (module, element) => { - const {item: initialEntityItem} = element; - if (initialEntityItem) { // element removed manually - const {id: entityId} = initialEntityItem; - tagsApi.emit('mutateRemoveEntity', { - id: entityId, - }); - } + item.instancing = false; + }) + .catch(err => { + console.warn(err); + }); + }; + const _removeEntity = (module, element) => { + const { item: initialEntityItem } = element; + if (initialEntityItem) { + // element removed manually + const { id: entityId } = initialEntityItem; + tagsApi.emit('mutateRemoveEntity', { + id: entityId, + }); + } - const componentApis = tagComponentApis[module]; - for (let i = 0; i < componentApis.length; i++) { - const componentApi = componentApis[i]; - _removeEntityCallback(componentApi, element); - } + const componentApis = tagComponentApis[module]; + for (let i = 0; i < componentApis.length; i++) { + const componentApi = componentApis[i]; + _removeEntityCallback(componentApi, element); + } - _removeModule(module) - .then(() => { - if (element.getAttribute('module') === module) { - // nothing - } - }) - .catch(err => { - console.warn(err); - }); - }; - - const rootEntitiesElement = document.createElement('entities'); - rootWorldElement.appendChild(rootEntitiesElement); - const entityMutationIgnores = []; - const rootEntitiesObserver = new MutationObserver(mutations => { - for (let i = 0; i < mutations.length; i++) { - const mutation = mutations[i]; - const {type} = mutation; - - if (type === 'childList') { - const {addedNodes} = mutation; - for (let j = 0; j < addedNodes.length; j++) { - const addedNode = addedNodes[j]; - - if (addedNode.nodeType === Node.ELEMENT_NODE && _someList(addedNode.attributes, a => a.name === 'module' && Boolean(a.value))) { - const entityElement = addedNode; - const module = entityElement.getAttribute('module'); - _addEntity(module, entityElement); + _removeModule(module) + .then(() => { + if (element.getAttribute('module') === module) { + // nothing } - } - - const {removedNodes} = mutation; - for (let k = 0; k < removedNodes.length; k++) { - const removedNode = removedNodes[k]; + }) + .catch(err => { + console.warn(err); + }); + }; - if (removedNode.nodeType === Node.ELEMENT_NODE && _someList(removedNode.attributes, a => a.name === 'module' && Boolean(a.value))) { - const entityElement = removedNode; - const module = entityElement.getAttribute('module'); - _removeEntity(module, entityElement); + const rootEntitiesElement = document.createElement('entities'); + rootWorldElement.appendChild(rootEntitiesElement); + const entityMutationIgnores = []; + const rootEntitiesObserver = new MutationObserver(mutations => { + for (let i = 0; i < mutations.length; i++) { + const mutation = mutations[i]; + const { type } = mutation; + + if (type === 'childList') { + const { addedNodes } = mutation; + for (let j = 0; j < addedNodes.length; j++) { + const addedNode = addedNodes[j]; + + if ( + addedNode.nodeType === Node.ELEMENT_NODE && + _someList( + addedNode.attributes, + a => a.name === 'module' && Boolean(a.value) + ) + ) { + const entityElement = addedNode; + const module = entityElement.getAttribute('module'); + _addEntity(module, entityElement); + } } - } - } else if (type === 'attributes') { - const {target} = mutation; - - if (target.nodeType === Node.ELEMENT_NODE) { - const entityElement = target; - const {attributeName, oldValue: oldValueString} = mutation; - const newValueString = entityElement.getAttribute(attributeName); - if (attributeName === 'module') { - if (oldValueString !== null) { - const module = oldValueString; + const { removedNodes } = mutation; + for (let k = 0; k < removedNodes.length; k++) { + const removedNode = removedNodes[k]; + + if ( + removedNode.nodeType === Node.ELEMENT_NODE && + _someList( + removedNode.attributes, + a => a.name === 'module' && Boolean(a.value) + ) + ) { + const entityElement = removedNode; + const module = entityElement.getAttribute('module'); _removeEntity(module, entityElement); } - if (newValueString !== null) { - const module = newValueString; - _addEntity(module, entityElement); - } - } else { - const oldValueJson = _parseAttribute(oldValueString); - const newValueJson = _parseAttribute(newValueString); - - const {item: entityItem} = entityElement; - const {id: entityId} = entityItem; - if (!entityMutationIgnores.some(({type, args: [id, name, value]}) => - type === 'setAttribute' && - id === entityId && - name === attributeName && - deepEqual(value, newValueJson) - )) { - tagsApi.emit('mutateSetAttribute', { - id: entityId, - name: attributeName, - value: newValueJson, - }); - } - - const {module} = entityItem; - const componentApis = tagComponentApis[module] || []; - for (let i = 0; i < componentApis.length; i++) { - const componentApi = componentApis[i]; - const {attributes: componentAttributes = []} = componentApi; - const componentAttribute = componentAttributes[attributeName]; - - if (componentAttribute !== undefined) { - const {type: attributeType} = componentAttribute; - const oldAttributeValue = oldValueJson; - const newAttributeValue = newValueJson; - - componentApi.entityAttributeValueChangedCallback(entityElement, attributeName, oldAttributeValue, newAttributeValue); - - tagsApi.emit('attributeValueChanged', { - entityId: entityId, - attributeName: attributeName, - type: attributeType, - oldValue: oldAttributeValue, - newValue: newAttributeValue, + } + } else if (type === 'attributes') { + const { target } = mutation; + + if (target.nodeType === Node.ELEMENT_NODE) { + const entityElement = target; + const { + attributeName, + oldValue: oldValueString, + } = mutation; + const newValueString = entityElement.getAttribute( + attributeName + ); + + if (attributeName === 'module') { + if (oldValueString !== null) { + const module = oldValueString; + _removeEntity(module, entityElement); + } + if (newValueString !== null) { + const module = newValueString; + _addEntity(module, entityElement); + } + } else { + const oldValueJson = _parseAttribute(oldValueString); + const newValueJson = _parseAttribute(newValueString); + + const { item: entityItem } = entityElement; + const { id: entityId } = entityItem; + if ( + !entityMutationIgnores.some( + ({ type, args: [id, name, value] }) => + type === 'setAttribute' && + id === entityId && + name === attributeName && + deepEqual(value, newValueJson) + ) + ) { + tagsApi.emit('mutateSetAttribute', { + id: entityId, + name: attributeName, + value: newValueJson, }); } - } - const {attributes: entityAttributes} = entityItem; - if (newValueString !== null) { - entityAttributes[attributeName] = { - value: newValueJson, - }; - } else { - delete entityAttributes[attributeName]; + const { module } = entityItem; + const componentApis = tagComponentApis[module] || []; + for (let i = 0; i < componentApis.length; i++) { + const componentApi = componentApis[i]; + const { + attributes: componentAttributes = [], + } = componentApi; + const componentAttribute = + componentAttributes[attributeName]; + + if (componentAttribute !== undefined) { + const { type: attributeType } = componentAttribute; + const oldAttributeValue = oldValueJson; + const newAttributeValue = newValueJson; + + componentApi.entityAttributeValueChangedCallback( + entityElement, + attributeName, + oldAttributeValue, + newAttributeValue + ); + + tagsApi.emit('attributeValueChanged', { + entityId: entityId, + attributeName: attributeName, + type: attributeType, + oldValue: oldAttributeValue, + newValue: newAttributeValue, + }); + } + } + + const { attributes: entityAttributes } = entityItem; + if (newValueString !== null) { + entityAttributes[attributeName] = { + value: newValueJson, + }; + } else { + delete entityAttributes[attributeName]; + } } } } } - } - entityMutationIgnores.length = 0; - }); - rootEntitiesObserver.observe(rootEntitiesElement, { - childList: true, - attributes: true, - // characterData: true, - subtree: true, - attributeOldValue: true, - // characterDataOldValue: true, - }); - - const linesMesh = (() => { - const maxNumLines = 100; - const numBoxPoints = 36; - - const geometry = (() => { - const geometry = new THREE.BufferGeometry(); - geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(maxNumLines * numBoxPoints * 3), 3)); - geometry.setDrawRange(0, 0); - return geometry; - })(); - const material = lineMaterial; - - const mesh = new THREE.Mesh(geometry, material); - mesh.frustumCulled = false; - mesh.visible = false; + entityMutationIgnores.length = 0; + }); + rootEntitiesObserver.observe(rootEntitiesElement, { + childList: true, + attributes: true, + // characterData: true, + subtree: true, + attributeOldValue: true, + // characterDataOldValue: true, + }); + + const linesMesh = (() => { + const maxNumLines = 100; + const numBoxPoints = 36; + + const geometry = (() => { + const geometry = new THREE.BufferGeometry(); + geometry.addAttribute( + 'position', + new THREE.BufferAttribute( + new Float32Array(maxNumLines * numBoxPoints * 3), + 3 + ) + ); + geometry.setDrawRange(0, 0); + return geometry; + })(); + const material = lineMaterial; + + const mesh = new THREE.Mesh(geometry, material); + mesh.frustumCulled = false; + mesh.visible = false; + + class Line { + constructor() { + this.start = null; + this.end = null; + } - class Line { - constructor() { - this.start = null; - this.end = null; + set(start, end) { + this.start = start; + this.end = end; + } } - set(start, end) { - this.start = start; - this.end = end; - } - } + const lines = []; + mesh.addLine = () => { + const line = new Line(); + lines.push(line); + return line; + }; + mesh.removeLine = line => { + lines.splice(lines.indexOf(line), 1); + }; + mesh.render = () => { + const positionsAttribute = geometry.getAttribute('position'); + const { array: positions } = positionsAttribute; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const { start, end } = line; + const startPosition = _getWorldPosition(start); + const endPosition = _getWorldPosition(end); + const midpoint = startPosition + .clone() + .add(endPosition) + .divideScalar(2); + const diffVector = endPosition.clone().sub(startPosition); + const diffVectorLength = diffVector.length(); + const quaternion = new THREE.Quaternion().setFromUnitVectors( + upVector, + diffVector.clone().divideScalar(diffVectorLength) + ); + + const localLineGeometry = lineGeometry + .clone() + .applyMatrix( + new THREE.Matrix4().makeScale( + 0.002, + diffVectorLength, + 0.002 + ) + ) + .applyMatrix( + new THREE.Matrix4().makeRotationFromQuaternion(quaternion) + ) + .applyMatrix( + new THREE.Matrix4().makeTranslation( + midpoint.x, + midpoint.y, + midpoint.z + ) + ); + const localPositions = localLineGeometry.getAttribute( + 'position' + ).array; + const baseIndex = i * numBoxPoints * 3; + positions.set(localPositions, baseIndex); + } + positionsAttribute.needsUpdate = true; - const lines = []; - mesh.addLine = () => { - const line = new Line(); - lines.push(line); - return line; + geometry.setDrawRange(0, lines.length * numBoxPoints); + }; + + return mesh; + })(); + // scene.add(linesMesh); + rend.registerAuxObject('tagsLinesMesh', linesMesh); + + const detailsState = { + inputText: '', + inputIndex: 0, + inputValue: 0, + transforms: [], + page: 0, }; - mesh.removeLine = line => { - lines.splice(lines.indexOf(line), 1); + const focusState = { + keyboardFocusState: null, }; - mesh.render = () => { - const positionsAttribute = geometry.getAttribute('position'); - const {array: positions} = positionsAttribute; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const {start, end} = line; - const startPosition = _getWorldPosition(start); - const endPosition = _getWorldPosition(end); - const midpoint = startPosition.clone().add(endPosition).divideScalar(2); - const diffVector = endPosition.clone().sub(startPosition); - const diffVectorLength = diffVector.length(); - const quaternion = new THREE.Quaternion().setFromUnitVectors( - upVector, - diffVector.clone().divideScalar(diffVectorLength) - ); - - const localLineGeometry = lineGeometry.clone() - .applyMatrix(new THREE.Matrix4().makeScale(0.002, diffVectorLength, 0.002)) - .applyMatrix(new THREE.Matrix4().makeRotationFromQuaternion(quaternion)) - .applyMatrix(new THREE.Matrix4().makeTranslation(midpoint.x, midpoint.y, midpoint.z)); - const localPositions = localLineGeometry.getAttribute('position').array; - const baseIndex = i * numBoxPoints * 3; - positions.set(localPositions, baseIndex); - } - positionsAttribute.needsUpdate = true; - geometry.setDrawRange(0, lines.length * numBoxPoints); - }; + const localUpdates = []; - return mesh; - })(); - // scene.add(linesMesh); - rend.registerAuxObject('tagsLinesMesh', linesMesh); - - const detailsState = { - inputText: '', - inputIndex: 0, - inputValue: 0, - transforms: [], - page: 0, - }; - const focusState = { - keyboardFocusState: null, - }; - - const localUpdates = []; - - /* const _getItemPreviewMode = item => fs.getFileMode(item.mimeType); + /* const _getItemPreviewMode = item => fs.getFileMode(item.mimeType); const _requestFileItemImageMesh = item => new Promise((accept, reject) => { const geometry = new THREE.PlaneBufferGeometry(0.2, 0.2); const material = (() => { @@ -742,65 +826,67 @@ class Tags { const _requestFileItemModelMesh = item => fs.makeFile('fs/' + item.id + item.name) .read({type: 'model'}); */ - const _trigger = e => { - const {side} = e; - - const _doClickAux = () => { - const hoverState = rend.getHoverState(side); - const {intersectionPoint} = hoverState; - - if (intersectionPoint) { - const {anchor} = hoverState; - const onclick = (anchor && anchor.onclick) || ''; - - let match; - if (match = onclick.match(/^entity:addAttribute:(.+)$/)) { - const id = match[1]; - - const tagMesh = tagMeshes.find(tagMesh => tagMesh.item.id === id); - const {item} = tagMesh; - const {attributes} = item; - const newAttributeName = (() => { - for (let i = 1;; i++) { - const attributeName = 'attribute-' + i; - if (!(attributeName in attributes)) { - return attributeName; + const _trigger = e => { + const { side } = e; + + const _doClickAux = () => { + const hoverState = rend.getHoverState(side); + const { intersectionPoint } = hoverState; + + if (intersectionPoint) { + const { anchor } = hoverState; + const onclick = (anchor && anchor.onclick) || ''; + + let match; + if ((match = onclick.match(/^entity:addAttribute:(.+)$/))) { + const id = match[1]; + + const tagMesh = tagMeshes.find( + tagMesh => tagMesh.item.id === id + ); + const { item } = tagMesh; + const { attributes } = item; + const newAttributeName = (() => { + for (let i = 1; ; i++) { + const attributeName = 'attribute-' + i; + if (!(attributeName in attributes)) { + return attributeName; + } } - } - return null; - })(); + return null; + })(); - tagsApi.emit('setAttribute', { - id: id, - name: newAttributeName, - value: 'value', - }); + tagsApi.emit('setAttribute', { + id: id, + name: newAttributeName, + value: 'value', + }); - e.stopImmediatePropagation(); + e.stopImmediatePropagation(); - return true; - } else if (match = onclick.match(/^tag:open:(.+)$/)) { - const id = match[1]; + return true; + } else if ((match = onclick.match(/^tag:open:(.+)$/))) { + const id = match[1]; - tagsApi.emit('open', { - id: id, - }); + tagsApi.emit('open', { + id: id, + }); - e.stopImmediatePropagation(); + e.stopImmediatePropagation(); - return true; - } else if (match = onclick.match(/^tag:close:(.+)$/)) { - const id = match[1]; + return true; + } else if ((match = onclick.match(/^tag:close:(.+)$/))) { + const id = match[1]; - tagsApi.emit('close', { - id: id, - }); + tagsApi.emit('close', { + id: id, + }); - e.stopImmediatePropagation(); + e.stopImmediatePropagation(); - return true; - /* } else if (match = onclick.match(/^tag:download:(.+)$/)) { + return true; + /* } else if (match = onclick.match(/^tag:download:(.+)$/)) { const id = match[1]; const downloadEvent = { @@ -809,213 +895,223 @@ class Tags { tagsApi.emit('download', downloadEvent); return true; */ - } else if (match = onclick.match(/^attribute:remove:(.+?):(.+?)$/)) { - const id = match[1]; - const name = match[2]; - - tagsApi.emit('setAttribute', { - id: id, - name: name, - value: undefined, - }); - - return true; - } else if (match = onclick.match(/^media:(play|pause|seek):(.+)$/)) { - const action = match[1]; - const id = match[2]; + } else if ( + (match = onclick.match(/^attribute:remove:(.+?):(.+?)$/)) + ) { + const id = match[1]; + const name = match[2]; - if (action === 'play') { - tagsApi.emit('play', { + tagsApi.emit('setAttribute', { id: id, + name: name, + value: undefined, }); - } else if (action === 'pause') { - tagsApi.emit('pause', { - id: id, - }); - } else if (action === 'seek') { - const tagMesh = tagMeshes.find(tagMesh => tagMesh.item.id === id); - const {item} = tagMesh; - item.getMedia() - .then(({media}) => { - const {value} = hoverState; + return true; + } else if ( + (match = onclick.match(/^media:(play|pause|seek):(.+)$/)) + ) { + const action = match[1]; + const id = match[2]; - tagsApi.emit('seek', { - id: id, - value: value, - }); - }) - .catch(err => { - console.warn(err); + if (action === 'play') { + tagsApi.emit('play', { + id: id, }); - } + } else if (action === 'pause') { + tagsApi.emit('pause', { + id: id, + }); + } else if (action === 'seek') { + const tagMesh = tagMeshes.find( + tagMesh => tagMesh.item.id === id + ); + const { item } = tagMesh; + + item + .getMedia() + .then(({ media }) => { + const { value } = hoverState; + + tagsApi.emit('seek', { + id: id, + value: value, + }); + }) + .catch(err => { + console.warn(err); + }); + } - return true; + return true; + } else { + return false; + } } else { return false; } - } else { - return false; - } - }; + }; - if (_doClickAux()) { - e.stopImmediatePropagation(); - } - }; - input.on('trigger', _trigger); - - const _update = () => { - const _updateLocal = () => { - for (let i = 0; i < localUpdates.length; i++) { - const update = localUpdates[i]; - update(); + if (_doClickAux()) { + e.stopImmediatePropagation(); } }; + input.on('trigger', _trigger); - _updateLocal(); - }; - rend.on('update', _update); - - cleanups.push(() => { - for (let i = 0; i < tagMeshes.length; i++) { - const tagMesh = tagMeshes[i]; - tagMesh.parent.remove(tagMesh); - } - scene.remove(linesMesh); - - input.removeListener('trigger', _trigger); - rend.removeListener('update', _update); - }); + const _update = () => { + const _updateLocal = () => { + for (let i = 0; i < localUpdates.length; i++) { + const update = localUpdates[i]; + update(); + } + }; - class Item extends EventEmitter { - constructor( - type, - id, - name, - displayName, - module, - description, - version, - versions, // XXX need to fetch these from the backend every time instead of saving it to the tags json - readme, - tagName, - attributes, - mimeType, - quantity, - words, - open, - details, - paused, - value, - metadata // transient state: isStatic, exists - ) { - super(); - - this.type = type; - this.id = id; - this.name = name; - this.displayName = displayName; - this.module = module; - this.description = description; - this.version = version; - this.versions = versions; - this.readme = readme; - this.tagName = tagName; - this.attributes = attributes; - this.mimeType = mimeType; - this.quantity = quantity; - this.words = words; - this.open = open; - this.details = details; - this.paused = paused; - this.value = value; - this.metadata = metadata; - - // we use symbols so these keys don't show up in the JSON.stringify - this[itemInstanceSymbol] = null; - this[itemInstancingSymbol] = false; - this[itemPageSymbol] = 0; - this[itemPreviewSymbol] = false; - this[itemTempSymbol] = false; - this[itemMediaPromiseSymbol] = null; - } + _updateLocal(); + }; + rend.on('update', _update); - get instance() { - return this[itemInstanceSymbol]; - } - set instance(instance) { - this[itemInstanceSymbol] = instance; - } - get instancing() { - return this[itemInstancingSymbol]; - } - set instancing(instancing) { - this[itemInstancingSymbol] = instancing; - } - get page() { - return this[itemPageSymbol]; - } - set page(page) { - this[itemPageSymbol] = page; - } - get selected() { - return this[itemSelectedSymbol]; - } - set selected(selected) { - this[itemSelectedSymbol] = selected; - } - get preview() { - return this[itemPreviewSymbol]; - } - set preview(preview) { - this[itemPreviewSymbol] = preview; - } - get temp() { - return this[itemTempSymbol]; - } - set temp(temp) { - this[itemTempSymbol] = temp; - } + cleanups.push(() => { + for (let i = 0; i < tagMeshes.length; i++) { + const tagMesh = tagMeshes[i]; + tagMesh.parent.remove(tagMesh); + } + scene.remove(linesMesh); + + input.removeListener('trigger', _trigger); + rend.removeListener('update', _update); + }); + + class Item extends EventEmitter { + constructor( + type, + id, + name, + displayName, + module, + description, + version, + versions, + readme, + tagName, + attributes, + mimeType, + quantity, + words, + open, + details, + paused, + value, + metadata // transient state: isStatic, exists + ) { + super(); + + this.type = type; + this.id = id; + this.name = name; + this.displayName = displayName; + this.module = module; + this.description = description; + this.version = version; + this.versions = versions; + this.readme = readme; + this.tagName = tagName; + this.attributes = attributes; + this.mimeType = mimeType; + this.quantity = quantity; + this.words = words; + this.open = open; + this.details = details; + this.paused = paused; + this.value = value; + this.metadata = metadata; + + // we use symbols so these keys don't show up in the JSON.stringify + this[itemInstanceSymbol] = null; + this[itemInstancingSymbol] = false; + this[itemPageSymbol] = 0; + this[itemPreviewSymbol] = false; + this[itemTempSymbol] = false; + this[itemMediaPromiseSymbol] = null; + } // XXX need to fetch these from the backend every time instead of saving it to the tags json + + get instance() { + return this[itemInstanceSymbol]; + } + set instance(instance) { + this[itemInstanceSymbol] = instance; + } + get instancing() { + return this[itemInstancingSymbol]; + } + set instancing(instancing) { + this[itemInstancingSymbol] = instancing; + } + get page() { + return this[itemPageSymbol]; + } + set page(page) { + this[itemPageSymbol] = page; + } + get selected() { + return this[itemSelectedSymbol]; + } + set selected(selected) { + this[itemSelectedSymbol] = selected; + } + get preview() { + return this[itemPreviewSymbol]; + } + set preview(preview) { + this[itemPreviewSymbol] = preview; + } + get temp() { + return this[itemTempSymbol]; + } + set temp(temp) { + this[itemTempSymbol] = temp; + } - setAttribute(attributeName, newValue) { - const {type} = this; + setAttribute(attributeName, newValue) { + const { type } = this; - if (type === 'entity') { - const {instance: entityElement} = this; + if (type === 'entity') { + const { instance: entityElement } = this; - if (newValue !== undefined) { - entityElement.setAttribute(attributeName, _stringifyAttribute(newValue)); - } else { - entityElement.removeAttribute(attributeName); - } - } else if (type === 'asset') { - const {attributes} = this; + if (newValue !== undefined) { + entityElement.setAttribute( + attributeName, + _stringifyAttribute(newValue) + ); + } else { + entityElement.removeAttribute(attributeName); + } + } else if (type === 'asset') { + const { attributes } = this; - if (newValue !== undefined) { - attributes[attributeName] = newValue; - } else { - delete attributes[attributeName]; + if (newValue !== undefined) { + attributes[attributeName] = newValue; + } else { + delete attributes[attributeName]; + } } } - } - setData(value) { - this.data = value; + setData(value) { + this.data = value; - const {instance} = this; - if (instance) { - const entityElement = instance; + const { instance } = this; + if (instance) { + const entityElement = instance; - if (value !== undefined) { - entityElement.innerText = JSON.stringify(value, null, 2); - } else { - entityElement.innerText = ''; + if (value !== undefined) { + entityElement.innerText = JSON.stringify(value, null, 2); + } else { + entityElement.innerText = ''; + } } } - } - /* getMedia() { + /* getMedia() { if (!this[itemMediaPromiseSymbol]) { this[itemMediaPromiseSymbol] = new Promise((accept, reject) => { const previewMesh = new THREE.Object3D(); @@ -1189,8 +1285,8 @@ class Tags { this.emit('update'); } */ - destroy() { - /* this.destroyMedia(); + destroy() { + /* this.destroyMedia(); const {type, temp} = this; if (type === 'file' && !temp) { @@ -1222,229 +1318,260 @@ class Tags { } } } */ + } } - } - const tagMeshes = []; - const tagComponentApis = {}; // plugin name -> [ component api ] + const tagMeshes = []; + const tagComponentApis = {}; // plugin name -> [ component api ] - const _getAttributeSpec = (module, attributeName) => { - const componentApis = tagComponentApis[module] || []; + const _getAttributeSpec = (module, attributeName) => { + const componentApis = tagComponentApis[module] || []; - for (let i = 0; i < componentApis.length; i++) { - const componentApi = componentApis[i]; - const {attributes: componentAttributes = {}} = componentApi; - const componentAttribute = componentAttributes[attributeName]; + for (let i = 0; i < componentApis.length; i++) { + const componentApi = componentApis[i]; + const { attributes: componentAttributes = {} } = componentApi; + const componentAttribute = componentAttributes[attributeName]; - if (componentAttribute) { - const attributeSpec = _shallowClone(componentAttribute); - attributeSpec.name = attributeName; - return attributeSpec; + if (componentAttribute) { + const attributeSpec = _shallowClone(componentAttribute); + attributeSpec.name = attributeName; + return attributeSpec; + } } - } - - return null; - }; - const _getAttributeSpecs = module => { - const result = []; - const componentApis = tagComponentApis[module] || []; - for (let i = 0; i < componentApis.length; i++) { - const componentApi = componentApis[i]; - const {attributes: componentAttributes} = componentApi; - - if (componentAttributes) { - for (const componentAttributeName in componentAttributes) { - if (!result.some(attributeSpec => attributeSpec.name === componentAttributeName)) { - const componentAttribute = componentAttributes[componentAttributeName]; - - const attributeSpec = _shallowClone(componentAttribute); - attributeSpec.name = componentAttributeName; - result.push(attributeSpec); + return null; + }; + const _getAttributeSpecs = module => { + const result = []; + + const componentApis = tagComponentApis[module] || []; + for (let i = 0; i < componentApis.length; i++) { + const componentApi = componentApis[i]; + const { attributes: componentAttributes } = componentApi; + + if (componentAttributes) { + for (const componentAttributeName in componentAttributes) { + if ( + !result.some( + attributeSpec => + attributeSpec.name === componentAttributeName + ) + ) { + const componentAttribute = + componentAttributes[componentAttributeName]; + + const attributeSpec = _shallowClone(componentAttribute); + attributeSpec.name = componentAttributeName; + result.push(attributeSpec); + } } } } - } - return result; - }; - const _getAttributeSpecsMap = module => { - const result = {}; - - const componentApis = tagComponentApis[module] || []; - for (let i = 0; i < componentApis.length; i++) { - const componentApi = componentApis[i]; - const {attributes: componentAttributes} = componentApi; - - if (componentAttributes) { - for (const componentAttributeName in componentAttributes) { - if (!result[componentAttributeName]) { - const componentAttribute = componentAttributes[componentAttributeName]; - const attributeSpec = _shallowClone(componentAttribute); - result[componentAttributeName] = attributeSpec; + return result; + }; + const _getAttributeSpecsMap = module => { + const result = {}; + + const componentApis = tagComponentApis[module] || []; + for (let i = 0; i < componentApis.length; i++) { + const componentApi = componentApis[i]; + const { attributes: componentAttributes } = componentApi; + + if (componentAttributes) { + for (const componentAttributeName in componentAttributes) { + if (!result[componentAttributeName]) { + const componentAttribute = + componentAttributes[componentAttributeName]; + const attributeSpec = _shallowClone(componentAttribute); + result[componentAttributeName] = attributeSpec; + } } } } - } - return result; - }; - - class TagsApi extends EventEmitter { - constructor() { - super(); - - this.setMaxListeners(100); - } - - registerEntity(pluginInstance, componentApi) { - const normalizedComponentApi = menuUtils.normalizeComponentApi(componentApi); - componentApi[normalizedSymbol] = normalizedComponentApi; + return result; + }; - const name = archae.getPath(pluginInstance); + class TagsApi extends EventEmitter { + constructor() { + super(); - let tagComponentApiComponents = tagComponentApis[name]; - if (!tagComponentApiComponents) { - tagComponentApiComponents = []; - tagComponentApis[name] = tagComponentApiComponents; + this.setMaxListeners(100); } - tagComponentApiComponents.push(normalizedComponentApi); - const entityTags = tagMeshes.filter(({item}) => item.type === 'entity' && item.module === name); - for (let i = 0; i < entityTags.length; i++) { - const entityTag = entityTags[i]; - const {item} = entityTag; - const {instance: entityElement} = item; + registerEntity(pluginInstance, componentApi) { + const normalizedComponentApi = menuUtils.normalizeComponentApi( + componentApi + ); + componentApi[normalizedSymbol] = normalizedComponentApi; + + const name = archae.getPath(pluginInstance); - if (entityElement) { - _addEntityCallback(normalizedComponentApi, entityElement); + let tagComponentApiComponents = tagComponentApis[name]; + if (!tagComponentApiComponents) { + tagComponentApiComponents = []; + tagComponentApis[name] = tagComponentApiComponents; + } + tagComponentApiComponents.push(normalizedComponentApi); - const entityAttributes = _getElementJsonAttributes(entityElement); - _entityValueChangedCallbacks(normalizedComponentApi, entityElement, entityAttributes); + const entityTags = tagMeshes.filter( + ({ item }) => item.type === 'entity' && item.module === name + ); + for (let i = 0; i < entityTags.length; i++) { + const entityTag = entityTags[i]; + const { item } = entityTag; + const { instance: entityElement } = item; + + if (entityElement) { + _addEntityCallback(normalizedComponentApi, entityElement); + + const entityAttributes = _getElementJsonAttributes( + entityElement + ); + _entityValueChangedCallbacks( + normalizedComponentApi, + entityElement, + entityAttributes + ); + } } } - } - unregisterEntity(pluginInstance, componentApi) { - const {[normalizedSymbol]: normalizedComponentApi} = componentApi; + unregisterEntity(pluginInstance, componentApi) { + const { + [normalizedSymbol]: normalizedComponentApi, + } = componentApi; - const name = archae.getPath(pluginInstance); + const name = archae.getPath(pluginInstance); - const entityTags = tagMeshes.filter(({item}) => item.type === 'entity' && item.module === name && !item.instancing); - for (let i = 0; i < entityTags.length; i++) { - const entityTag = entityTags[i]; - const {item} = entityTag; - const {instance: entityElement} = item; + const entityTags = tagMeshes.filter( + ({ item }) => + item.type === 'entity' && + item.module === name && + !item.instancing + ); + for (let i = 0; i < entityTags.length; i++) { + const entityTag = entityTags[i]; + const { item } = entityTag; + const { instance: entityElement } = item; + + if (entityElement) { + _removeEntityCallback( + normalizedComponentApi, + entityElement + ); + } + } - if (entityElement) { - _removeEntityCallback(normalizedComponentApi, entityElement); + const tagComponentApiComponents = tagComponentApis[name]; + tagComponentApiComponents.splice( + tagComponentApiComponents.indexOf(normalizedComponentApi), + 1 + ); + if (tagComponentApiComponents.length === 0) { + delete tagComponentApis[name]; } } - const tagComponentApiComponents = tagComponentApis[name]; - tagComponentApiComponents.splice(tagComponentApiComponents.indexOf(normalizedComponentApi), 1); - if (tagComponentApiComponents.length === 0) { - delete tagComponentApis[name]; + getTagMeshes() { + return tagMeshes; } - } - - getTagMeshes() { - return tagMeshes; - } - - getAttributeSpec(module, attributeName) { - return _getAttributeSpec(module, attributeName); - } - - getAttributeSpecs(module) { - return _getAttributeSpecs(module); - } - getAttributeSpecsMap(module) { - return _getAttributeSpecsMap(module); - } + getAttributeSpec(module, attributeName) { + return _getAttributeSpec(module, attributeName); + } - makeTag(itemSpec, {initialUpdate = true} = {}) { - const object = new THREE.Object3D(); - - const item = new Item( - itemSpec.type, - itemSpec.id, - itemSpec.name, - itemSpec.displayName, - itemSpec.module, - itemSpec.description, - itemSpec.version, - itemSpec.versions, - itemSpec.readme, - itemSpec.tagName, // XXX get rid of these - itemSpec.attributes, - itemSpec.mimeType, - itemSpec.quantity, - itemSpec.words, - itemSpec.open, - itemSpec.details, - itemSpec.paused, - itemSpec.value, - itemSpec.metadata - ); - object.item = item; + getAttributeSpecs(module) { + return _getAttributeSpecs(module); + } - if (itemSpec.type === 'entity' || itemSpec.type === 'asset') { - object.setAttribute = (attribute, value) => { - item.setAttribute(attribute, value); - }; + getAttributeSpecsMap(module) { + return _getAttributeSpecsMap(module); } - if (itemSpec.type === 'file') { - object.open = () => { - const tagMesh = object; - const {item} = tagMesh; - item.open = true; - if (item.type === 'file') { - item.getMedia() - .then(({previewMesh}) => { - planeOpenMesh.add(previewMesh); - }) - .catch(err => { - console.warn(err); - }); - } + makeTag(itemSpec, { initialUpdate = true } = {}) { + const object = new THREE.Object3D(); + + const item = new Item( + itemSpec.type, + itemSpec.id, + itemSpec.name, + itemSpec.displayName, + itemSpec.module, + itemSpec.description, + itemSpec.version, + itemSpec.versions, + itemSpec.readme, + itemSpec.tagName, // XXX get rid of these + itemSpec.attributes, + itemSpec.mimeType, + itemSpec.quantity, + itemSpec.words, + itemSpec.open, + itemSpec.details, + itemSpec.paused, + itemSpec.value, + itemSpec.metadata + ); + object.item = item; - const {page} = planeOpenMesh; - page.initialUpdate(); - }; - object.close = () => { - const tagMesh = object; - const {item} = tagMesh; - item.open = false; + if (itemSpec.type === 'entity' || itemSpec.type === 'asset') { + object.setAttribute = (attribute, value) => { + item.setAttribute(attribute, value); + }; + } + if (itemSpec.type === 'file') { + object.open = () => { + const tagMesh = object; + const { item } = tagMesh; + item.open = true; + + if (item.type === 'file') { + item + .getMedia() + .then(({ previewMesh }) => { + planeOpenMesh.add(previewMesh); + }) + .catch(err => { + console.warn(err); + }); + } - if (item.type === 'file') { - if (item.preview && item.preview.visible) { - item.preview.visible = false; + const { page } = planeOpenMesh; + page.initialUpdate(); + }; + object.close = () => { + const tagMesh = object; + const { item } = tagMesh; + item.open = false; + + if (item.type === 'file') { + if (item.preview && item.preview.visible) { + item.preview.visible = false; + } } - } - }; - object.play = () => { - const tagMesh = object; - const {item} = tagMesh; + }; + object.play = () => { + const tagMesh = object; + const { item } = tagMesh; - item.play(); - }; - object.pause = () => { - const tagMesh = object; - const {item} = tagMesh; + item.play(); + }; + object.pause = () => { + const tagMesh = object; + const { item } = tagMesh; - item.pause(); - }; - object.seek = value => { - const tagMesh = object; - const {item} = tagMesh; + item.pause(); + }; + object.seek = value => { + const tagMesh = object; + const { item } = tagMesh; - item.seek(value); - }; + item.seek(value); + }; - /* if (item.open) { + /* if (item.open) { object.open(); } if (item.value !== undefined) { @@ -1453,165 +1580,182 @@ class Tags { if (item.paused === false) { object.play(); } */ - } - - object.destroy = () => { - const {item} = object; - item.destroy(); - }; - - tagMeshes.push(object); - - return object; - } + } - destroyTag(tagMesh) { - const index = tagMeshes.indexOf(tagMesh); + object.destroy = () => { + const { item } = object; + item.destroy(); + }; - if (index !== -1) { - tagMesh.destroy(); + tagMeshes.push(object); - tagMeshes.splice(index, 1); + return object; } - } - mutateAddEntity(tagMesh) { - const {item} = tagMesh; - const {tagName: entityTagName, module: entityModule, version: entityVersion, attributes: entityAttributes} = item; - const entityElement = document.createElement(entityTagName); + destroyTag(tagMesh) { + const index = tagMeshes.indexOf(tagMesh); - const plugin = _getPlugin(entityModule, entityVersion); - entityElement.setAttribute('module', plugin); + if (index !== -1) { + tagMesh.destroy(); - for (const attributeName in entityAttributes) { - const attribute = entityAttributes[attributeName]; - const {value: attributeValue} = attribute; - const attributeValueString = _stringifyAttribute(attributeValue); - entityElement.setAttribute(attributeName, attributeValueString); + tagMeshes.splice(index, 1); + } } - entityElement.item = item; - item.instance = entityElement; - - rootEntitiesElement.appendChild(entityElement); - - return entityElement; - } + mutateAddEntity(tagMesh) { + const { item } = tagMesh; + const { + tagName: entityTagName, + module: entityModule, + version: entityVersion, + attributes: entityAttributes, + } = item; + const entityElement = document.createElement(entityTagName); + + const plugin = _getPlugin(entityModule, entityVersion); + entityElement.setAttribute('module', plugin); + + for (const attributeName in entityAttributes) { + const attribute = entityAttributes[attributeName]; + const { value: attributeValue } = attribute; + const attributeValueString = _stringifyAttribute( + attributeValue + ); + entityElement.setAttribute( + attributeName, + attributeValueString + ); + } - mutateRemoveEntity(tagMesh) { - const {item} = tagMesh; - const {instance: entityElement} = item; + entityElement.item = item; + item.instance = entityElement; - entityElement.item = null; - item.instance = null; + rootEntitiesElement.appendChild(entityElement); - if (entityElement.parentNode) { - entityElement.parentNode.removeChild(entityElement); + return entityElement; } - return entityElement; - } - - getTagComponentApis(tag) { - return tagComponentApis[tag]; - } + mutateRemoveEntity(tagMesh) { + const { item } = tagMesh; + const { instance: entityElement } = item; - makeListener(selector) { - const listener = new EventEmitter(); + entityElement.item = null; + item.instance = null; - const _elementAdded = entityElement => { - if (entityElement.matches(selector)) { - listener.emit('add', entityElement); - } - }; - this.on('elementAdded', _elementAdded); - const _elementRemoved = entityElement => { - if (entityElement.matches(selector)) { - listener.emit('remove', entityElement); + if (entityElement.parentNode) { + entityElement.parentNode.removeChild(entityElement); } - }; - this.on('elementRemoved', _elementRemoved); - const initialEntityElement = rootEntitiesElement.querySelector(selector); - let timeout = null; - if (initialEntityElement && ('_numComponents' in initialEntityElement) && initialEntityElement._numComponents > 0) { - timeout = setTimeout(() => { - timeout = null; + return entityElement; + } - listener.emit('add', initialEntityElement); - }); + getTagComponentApis(tag) { + return tagComponentApis[tag]; } - listener.destroy = () => { - this.removeListener('elementAdded', _elementAdded); - this.removeListener('elementRemoved', _elementRemoved); + makeListener(selector) { + const listener = new EventEmitter(); - if (timeout !== null) { - clearTimeout(timeout); + const _elementAdded = entityElement => { + if (entityElement.matches(selector)) { + listener.emit('add', entityElement); + } + }; + this.on('elementAdded', _elementAdded); + const _elementRemoved = entityElement => { + if (entityElement.matches(selector)) { + listener.emit('remove', entityElement); + } + }; + this.on('elementRemoved', _elementRemoved); + + const initialEntityElement = rootEntitiesElement.querySelector( + selector + ); + let timeout = null; + if ( + initialEntityElement && + '_numComponents' in initialEntityElement && + initialEntityElement._numComponents > 0 + ) { + timeout = setTimeout(() => { + timeout = null; + + listener.emit('add', initialEntityElement); + }); } - }; - return listener; - } + listener.destroy = () => { + this.removeListener('elementAdded', _elementAdded); + this.removeListener('elementRemoved', _elementRemoved); - destroyListener(listener) { - listener.destroy(); - } + if (timeout !== null) { + clearTimeout(timeout); + } + }; - requestElement(selector, {timeout = 30 * 1000} = {}) { - return new Promise((accept, reject) => { - const element = rootEntitiesElement.querySelector(selector); - if (element && element._numComponents > 0) { - accept(element); - } else { - const _elementAdded = entityElement => { - if (entityElement.matches(selector)) { - clearTimeout(localTimeout); + return listener; + } - accept(entityElement); - } - }; - this.on('elementAdded', _elementAdded); + destroyListener(listener) { + listener.destroy(); + } - const localTimeout = setTimeout(() => { - this.removeListener('elementAdded', _elementAdded); + requestElement(selector, { timeout = 30 * 1000 } = {}) { + return new Promise((accept, reject) => { + const element = rootEntitiesElement.querySelector(selector); + if (element && element._numComponents > 0) { + accept(element); + } else { + const _elementAdded = entityElement => { + if (entityElement.matches(selector)) { + clearTimeout(localTimeout); - const err = new Error('timeout out'); - err.code = 'ETIMEOUT'; - reject(err); - }, timeout); - } - }); - } + accept(entityElement); + } + }; + this.on('elementAdded', _elementAdded); - getWorldElement() { - return rootWorldElement; - } + const localTimeout = setTimeout(() => { + this.removeListener('elementAdded', _elementAdded); - getEntitiesElement() { - return rootEntitiesElement; - } + const err = new Error('timeout out'); + err.code = 'ETIMEOUT'; + reject(err); + }, timeout); + } + }); + } - message(detail) { - const e = new CustomEvent('message', { - detail: detail, - }); - rootWorldElement.dispatchEvent(e); - } + getWorldElement() { + return rootWorldElement; + } - ignoreEntityMutation(entityMutationIgnoreSpec) { - entityMutationIgnores.push(entityMutationIgnoreSpec); - } + getEntitiesElement() { + return rootEntitiesElement; + } - updateLinesMesh() { - linesMesh.render(); + message(detail) { + const e = new CustomEvent('message', { + detail: detail, + }); + rootWorldElement.dispatchEvent(e); + } + + ignoreEntityMutation(entityMutationIgnoreSpec) { + entityMutationIgnores.push(entityMutationIgnoreSpec); + } + + updateLinesMesh() { + linesMesh.render(); + } } - }; - const tagsApi = new TagsApi(); + const tagsApi = new TagsApi(); - return tagsApi; + return tagsApi; + } } - }); + ); } unmount() { @@ -1643,8 +1787,12 @@ const _shallowClone = o => { return result; }; -const _makeId = () => Math.random().toString(36).substring(7); +const _makeId = () => + Math.random() + .toString(36) + .substring(7); const _clone = o => JSON.parse(JSON.stringify(o)); -const _getPlugin = (module, version) => /^\//.test(module) ? module : `${module}@${version}`; +const _getPlugin = (module, version) => + /^\//.test(module) ? module : `${module}@${version}`; module.exports = Tags; diff --git a/core/engines/tags/server.js b/core/engines/tags/server.js index d32b478cf..fcffaebfa 100644 --- a/core/engines/tags/server.js +++ b/core/engines/tags/server.js @@ -1,5 +1,5 @@ const events = require('events'); -const {EventEmitter} = events; +const { EventEmitter } = events; class Tags { constructor(archae) { @@ -7,7 +7,7 @@ class Tags { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; class Element extends EventEmitter { constructor(tagName, module) { @@ -33,12 +33,12 @@ class Tags { const result = []; const _recurse = e => { - const {tagName} = e; + const { tagName } = e; if (tagName === selector) { result.push(e); } - const {children} = e; + const { children } = e; for (let i = 0; i < children.length; i++) { const child = children[i]; _recurse(child); @@ -56,11 +56,11 @@ class Tags { while (queue.length > 0) { const e = queue.pop(); - const {tagName} = e; + const { tagName } = e; if (tagName === selector) { return e; } else { - const {children} = e; + const { children } = e; queue.push.apply(queue, children); } } @@ -79,7 +79,7 @@ class Tags { const worldElement = new Element('world', 'zeo'); const _getWorldElement = () => worldElement; - const _requestElement = (selector, {timeout = 30 * 1000} = {}) => { + const _requestElement = (selector, { timeout = 30 * 1000 } = {}) => { selector = selector.toUpperCase(); const element = worldElement.querySelector(selector); @@ -87,26 +87,30 @@ class Tags { return Promise.resolve(element); } else { let _elementAdded = null; - const _requestElementAdded = () => new Promise((accept, reject) => { - _elementAdded = element => { - const {tagName} = element; - - if (tagName === selector) { - accept(element); - } - }; - worldElement.on('elementAdded', _elementAdded); - }); + const _requestElementAdded = () => + new Promise((accept, reject) => { + _elementAdded = element => { + const { tagName } = element; + + if (tagName === selector) { + accept(element); + } + }; + worldElement.on('elementAdded', _elementAdded); + }); let timeoutInstance = null; - const _requestTimeout = () => new Promise((accept, reject) => { - timeoutInstance = setTimeout(() => { - timeoutInstance = null; - - const err = new Error(`element request for ${JSON.stringify(selector)} timed out`); - err.code = 'ETIMEOUT'; - reject(err); - }, timeout); - }); + const _requestTimeout = () => + new Promise((accept, reject) => { + timeoutInstance = setTimeout(() => { + timeoutInstance = null; + + const err = new Error( + `element request for ${JSON.stringify(selector)} timed out` + ); + err.code = 'ETIMEOUT'; + reject(err); + }, timeout); + }); const _cleanup = () => { worldElement.removeListener('elementAdded', _elementAdded); @@ -115,10 +119,7 @@ class Tags { } }; - return Promise.race([ - _requestElementAdded(), - _requestTimeout(), - ]) + return Promise.race([_requestElementAdded(), _requestTimeout()]) .then(element => { _cleanup(); @@ -135,7 +136,7 @@ class Tags { entityApi.tagName = _makeTagName(archae.getPath(pluginInstance)); worldElement.appendChild(entityApi); - const {entityAddedCallback = nop} = entityApi; + const { entityAddedCallback = nop } = entityApi; entityAddedCallback(entityApi); worldElement.emit('elementAdded', entityApi); @@ -143,7 +144,7 @@ class Tags { const _unregisterEntity = (pluginInstance, entityApi) => { worldElement.removeChild(entityApi); - const {entityRemovedCallback = nop} = entityApi; + const { entityRemovedCallback = nop } = entityApi; entityRemovedCallback(entityApi); worldElement.emit('elementRemoved', entityApi); @@ -163,10 +164,11 @@ class Tags { } const nop = () => {}; -const _makeTagName = s => s - .toUpperCase() - .replace(/[^A-Z0-9-]/g, '-') - .replace(/--+/g, '-') - .replace(/(?:^-|-$)/g, ''); +const _makeTagName = s => + s + .toUpperCase() + .replace(/[^A-Z0-9-]/g, '-') + .replace(/--+/g, '-') + .replace(/(?:^-|-$)/g, ''); module.exports = Tags; diff --git a/core/engines/teleport/client.js b/core/engines/teleport/client.js index 818bf55e5..2f37c6999 100644 --- a/core/engines/teleport/client.js +++ b/core/engines/teleport/client.js @@ -8,327 +8,402 @@ class Teleport { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; let live = true; this._cleanup = () => { live = false; }; - return archae.requestPlugins([ - '/core/engines/bootstrap', - '/core/engines/three', - '/core/engines/webvr', - '/core/engines/input', - '/core/engines/rend', - '/core/engines/cyborg', - '/core/engines/stck', - '/core/utils/js-utils', - ]).then(([ - bootstrap, - three, - webvr, - input, - rend, - cyborg, - stck, - jsUtils, - ]) => { - if (live) { - const {THREE, scene, camera} = three; - const {events} = jsUtils; - const {EventEmitter} = events; - - const forwardVector = new THREE.Vector3(0, 0, -1); - const localVector = new THREE.Vector3(); - const localVector2 = new THREE.Vector3(); - const localEuler = new THREE.Euler(); - const localMatrix = new THREE.Matrix4(); - const localMatrix2 = new THREE.Matrix4(); - - const teleportMeshMaterial = new THREE.MeshBasicMaterial({ - color: 0x2196F3, - //shading: THREE.FlatShading, - // opacity: 0.5, - // transparent: true, - }); - - const _makeTeleportFloorMesh = () => { - const geometry = new THREE.TorusBufferGeometry(0.5, 0.15, 3, 5); - geometry.applyMatrix(new THREE.Matrix4().makeRotationX(-(Math.PI / 2))); - geometry.applyMatrix(new THREE.Matrix4().makeRotationY((1 / 20) * (Math.PI * 2))); - - const material = teleportMeshMaterial; - - const mesh = new THREE.Mesh(geometry, material); - mesh.visible = false; - return mesh; - }; - const teleportFloorMeshes = { - left: _makeTeleportFloorMesh(), - right: _makeTeleportFloorMesh(), - }; - scene.add(teleportFloorMeshes.left); - scene.add(teleportFloorMeshes.right); - - const _makeTeleportAirMesh = () => { - const geometry = new THREE.BoxBufferGeometry(1, 1, 1); - - const material = teleportMeshMaterial; - - const mesh = new THREE.Mesh(geometry, material); - mesh.visible = false; - return mesh; - }; - const teleportAirMeshes = { - left: _makeTeleportAirMesh(), - right: _makeTeleportAirMesh(), - }; - scene.add(teleportAirMeshes.left); - scene.add(teleportAirMeshes.right); - - const _makeTeleportState = () => ({ - teleporting: false, - lastUpdateTime: 0, - }); - const teleportStates = { - left: _makeTeleportState(), - right: _makeTeleportState(), - }; - - const _paddown = e => { - const {side} = e; - - const teleportState = teleportStates[side]; - teleportState.teleporting = true; - - teleportApi.emit('start', {side}); - }; - input.on('paddown', _paddown); - const _padup = e => { - const {side} = e; - - const teleportState = teleportStates[side]; - teleportState.teleporting = false; - - teleportApi.emit('end', {side}); - }; - input.on('padup', _padup); - - const _makeQueueState = () => ({ - running: false, - queued: null, - }); - const queues = { - left: _makeQueueState(), - right: _makeQueueState(), - }; - const _requestStckUpdate = (side, spec) => { - const queue = queues[side]; - const {running} = queue; - - if (!running) { - const {position, rotation, scale, axes} = spec; - stck.requestTeleport(position, rotation, targetPosition => { - const teleportFloorMesh = teleportFloorMeshes[side]; - const teleportAirMesh = teleportAirMeshes[side]; - - if (targetPosition) { - teleportFloorMesh.position.copy(targetPosition); - teleportFloorMesh.rotation.setFromQuaternion(rotation, camera.rotation.order); - teleportFloorMesh.rotation.x = 0; - teleportFloorMesh.rotation.z = 0; - teleportFloorMesh.scale.copy(scale); - teleportFloorMesh.updateMatrixWorld(); - - teleportFloorMesh.visible = true; - teleportAirMesh.visible = false; - } else { - const axisFactor = (axes[1] - (-1)) / 2; - const teleportDistance = axisFactor * TELEPORT_DISTANCE * ((scale.x + scale.y + scale.z) / 3); - - teleportAirMesh.position.copy(position) - .add( - localVector.copy(forwardVector) - .applyQuaternion(rotation) - .multiplyScalar(teleportDistance) - ); - teleportAirMesh.rotation.setFromQuaternion(rotation, camera.rotation.order); - teleportAirMesh.rotation.x = 0; - teleportAirMesh.rotation.z = 0; - teleportAirMesh.scale.copy(scale); - teleportAirMesh.updateMatrixWorld(); - - teleportAirMesh.visible = true; - teleportFloorMesh.visible = false; - } + return archae + .requestPlugins([ + '/core/engines/bootstrap', + '/core/engines/three', + '/core/engines/webvr', + '/core/engines/input', + '/core/engines/rend', + '/core/engines/cyborg', + '/core/engines/stck', + '/core/utils/js-utils', + ]) + .then(([bootstrap, three, webvr, input, rend, cyborg, stck, jsUtils]) => { + if (live) { + const { THREE, scene, camera } = three; + const { events } = jsUtils; + const { EventEmitter } = events; + + const forwardVector = new THREE.Vector3(0, 0, -1); + const localVector = new THREE.Vector3(); + const localVector2 = new THREE.Vector3(); + const localEuler = new THREE.Euler(); + const localMatrix = new THREE.Matrix4(); + const localMatrix2 = new THREE.Matrix4(); + + const teleportMeshMaterial = new THREE.MeshBasicMaterial({ + color: 0x2196f3, + //shading: THREE.FlatShading, + // opacity: 0.5, + // transparent: true, + }); - queue.running = false; + const _makeTeleportFloorMesh = () => { + const geometry = new THREE.TorusBufferGeometry(0.5, 0.15, 3, 5); + geometry.applyMatrix( + new THREE.Matrix4().makeRotationX(-(Math.PI / 2)) + ); + geometry.applyMatrix( + new THREE.Matrix4().makeRotationY(1 / 20 * (Math.PI * 2)) + ); - const {queued} = queue; - if (queued) { - queue.queued = null; + const material = teleportMeshMaterial; - _requestStckUpdate(side, queued); - } - }); + const mesh = new THREE.Mesh(geometry, material); + mesh.visible = false; + return mesh; + }; + const teleportFloorMeshes = { + left: _makeTeleportFloorMesh(), + right: _makeTeleportFloorMesh(), + }; + scene.add(teleportFloorMeshes.left); + scene.add(teleportFloorMeshes.right); + + const _makeTeleportAirMesh = () => { + const geometry = new THREE.BoxBufferGeometry(1, 1, 1); + + const material = teleportMeshMaterial; + + const mesh = new THREE.Mesh(geometry, material); + mesh.visible = false; + return mesh; + }; + const teleportAirMeshes = { + left: _makeTeleportAirMesh(), + right: _makeTeleportAirMesh(), + }; + scene.add(teleportAirMeshes.left); + scene.add(teleportAirMeshes.right); + + const _makeTeleportState = () => ({ + teleporting: false, + lastUpdateTime: 0, + }); + const teleportStates = { + left: _makeTeleportState(), + right: _makeTeleportState(), + }; + + const _paddown = e => { + const { side } = e; + + const teleportState = teleportStates[side]; + teleportState.teleporting = true; + + teleportApi.emit('start', { side }); + }; + input.on('paddown', _paddown); + const _padup = e => { + const { side } = e; - queue.running = true; - } else { - queue.queued = spec; - } - }; + const teleportState = teleportStates[side]; + teleportState.teleporting = false; - const _update = () => { - const {hmd, gamepads} = webvr.getStatus(); + teleportApi.emit('end', { side }); + }; + input.on('padup', _padup); - const _updateDrag = () => { - for (let i = 0; i < SIDES.length; i++) { - const side = SIDES[i]; - const teleportState = teleportStates[side]; - const {teleporting} = teleportState; + const _makeQueueState = () => ({ + running: false, + queued: null, + }); + const queues = { + left: _makeQueueState(), + right: _makeQueueState(), + }; + const _requestStckUpdate = (side, spec) => { + const queue = queues[side]; + const { running } = queue; + + if (!running) { + const { position, rotation, scale, axes } = spec; + stck.requestTeleport(position, rotation, targetPosition => { + const teleportFloorMesh = teleportFloorMeshes[side]; + const teleportAirMesh = teleportAirMeshes[side]; + + if (targetPosition) { + teleportFloorMesh.position.copy(targetPosition); + teleportFloorMesh.rotation.setFromQuaternion( + rotation, + camera.rotation.order + ); + teleportFloorMesh.rotation.x = 0; + teleportFloorMesh.rotation.z = 0; + teleportFloorMesh.scale.copy(scale); + teleportFloorMesh.updateMatrixWorld(); + + teleportFloorMesh.visible = true; + teleportAirMesh.visible = false; + } else { + const axisFactor = (axes[1] - -1) / 2; + const teleportDistance = + axisFactor * + TELEPORT_DISTANCE * + ((scale.x + scale.y + scale.z) / 3); + + teleportAirMesh.position.copy(position).add( + localVector + .copy(forwardVector) + .applyQuaternion(rotation) + .multiplyScalar(teleportDistance) + ); + teleportAirMesh.rotation.setFromQuaternion( + rotation, + camera.rotation.order + ); + teleportAirMesh.rotation.x = 0; + teleportAirMesh.rotation.z = 0; + teleportAirMesh.scale.copy(scale); + teleportAirMesh.updateMatrixWorld(); - if (teleporting) { - const {lastUpdateTime} = teleportState; - const now = Date.now(); + teleportAirMesh.visible = true; + teleportFloorMesh.visible = false; + } - if ((now - lastUpdateTime) > 15) { - const gamepad = gamepads[side]; - const {worldPosition: controllerPosition, worldRotation: controllerRotation, worldScale: controllerScale, axes} = gamepad; + queue.running = false; - _requestStckUpdate(side, {position: controllerPosition, rotation: controllerRotation, scale: controllerScale, axes}); + const { queued } = queue; + if (queued) { + queue.queued = null; - teleportState.lastUpdateTime = now; + _requestStckUpdate(side, queued); } - } + }); + + queue.running = true; + } else { + queue.queued = spec; } }; - const _updateEnd = () => { - for (let i = 0; i < SIDES.length; i++) { - const side = SIDES[i]; - const teleportState = teleportStates[side]; - const {teleporting} = teleportState; - const gamepad = gamepads[side]; - const teleportFloorMesh = teleportFloorMeshes[side]; - const teleportAirMesh = teleportAirMeshes[side]; - - if (!teleporting) { - if (teleportFloorMesh.visible) { - const vrMode = bootstrap.getVrMode(); - - if (vrMode === 'hmd') { - const cameraPosition = camera.getWorldPosition(localVector); - const hmdOffsetY = localVector2.setFromMatrixPosition(webvr.getSittingToStandingTransform()).y + hmd.position.y; - // const hmdOffsetY = hmd.position.y; - const teleportMeshEuler = localEuler.setFromQuaternion(teleportFloorMesh.quaternion, 'XZY'); - teleportMeshEuler.y = 0; - webvr.setStageMatrix( - localMatrix.copy(webvr.getStageMatrix()) - .premultiply(localMatrix2.makeTranslation( - -cameraPosition.x, - -cameraPosition.y, - -cameraPosition.z - )) // move back to origin - .premultiply(localMatrix2.makeTranslation(0, hmdOffsetY, 0)) // move to height - .premultiply(localMatrix2.makeRotationFromEuler(teleportMeshEuler)) // rotate to mesh normal - .premultiply(localMatrix2.makeTranslation( - teleportFloorMesh.position.x, - teleportFloorMesh.position.y, - teleportFloorMesh.position.z - )) // move to teleport location - ); - } else if (vrMode === 'keyboard') { - const hmdLocalEuler = localEuler.setFromQuaternion(hmd.rotation, 'YXZ'); - hmdLocalEuler.y = 0; - - webvr.setStageMatrix( - localMatrix.copy(camera.matrixWorldInverse) - .multiply(webvr.getStageMatrix()) // move back to origin - .premultiply(localMatrix2.makeRotationFromEuler(hmdLocalEuler)) // rotate to HMD - .premultiply(teleportFloorMesh.matrixWorld) // move to teleport location - .premultiply(webvr.getSittingToStandingTransform()) // move above target - ); - } - webvr.updateStatus(); - cyborg.update(); - - teleportApi.emit('teleport'); - } else if (teleportAirMesh.visible) { - const vrMode = bootstrap.getVrMode(); - - if (vrMode === 'hmd') { - const cameraPosition = camera.getWorldPosition(); - const hmdOffsetY = _decomposeMatrix(webvr.getSittingToStandingTransform()).position.y + hmd.position.y; - // const hmdOffsetY = hmd.position.y; - const teleportMeshEuler = new THREE.Euler().setFromQuaternion(teleportAirMesh.quaternion, 'XZY'); - teleportMeshEuler.y = 0; - webvr.setStageMatrix( - webvr.getStageMatrix().clone() - .premultiply(new THREE.Matrix4().makeTranslation( - -cameraPosition.x, - -cameraPosition.y, - -cameraPosition.z - )) // move back to origin - .premultiply(new THREE.Matrix4().makeTranslation(0, hmdOffsetY, 0)) // move to height - .premultiply(new THREE.Matrix4().makeRotationFromEuler(teleportMeshEuler)) // rotate to mesh normal - .premultiply(new THREE.Matrix4().makeTranslation( - teleportAirMesh.position.x, - teleportAirMesh.position.y, - teleportAirMesh.position.z - )) // move to teleport location - ); - } else if (vrMode === 'keyboard') { - const hmdLocalEuler = new THREE.Euler().setFromQuaternion(hmd.rotation, 'YXZ'); - - webvr.setStageMatrix( - camera.matrixWorldInverse.clone() - .multiply(webvr.getStageMatrix()) // move back to origin - .premultiply(new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(hmdLocalEuler.x, 0, hmdLocalEuler.z, 'YXZ'))) // rotate to HMD - .premultiply(teleportAirMesh.matrixWorld) // move to teleport location - .premultiply(webvr.getSittingToStandingTransform()) // move above target - ); + const _update = () => { + const { hmd, gamepads } = webvr.getStatus(); + + const _updateDrag = () => { + for (let i = 0; i < SIDES.length; i++) { + const side = SIDES[i]; + const teleportState = teleportStates[side]; + const { teleporting } = teleportState; + + if (teleporting) { + const { lastUpdateTime } = teleportState; + const now = Date.now(); + + if (now - lastUpdateTime > 15) { + const gamepad = gamepads[side]; + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + worldScale: controllerScale, + axes, + } = gamepad; + + _requestStckUpdate(side, { + position: controllerPosition, + rotation: controllerRotation, + scale: controllerScale, + axes, + }); + + teleportState.lastUpdateTime = now; } - - webvr.updateStatus(); - cyborg.update(); - - teleportApi.emit('teleport'); } + } + }; + const _updateEnd = () => { + for (let i = 0; i < SIDES.length; i++) { + const side = SIDES[i]; + const teleportState = teleportStates[side]; + const { teleporting } = teleportState; + const gamepad = gamepads[side]; + const teleportFloorMesh = teleportFloorMeshes[side]; + const teleportAirMesh = teleportAirMeshes[side]; + + if (!teleporting) { + if (teleportFloorMesh.visible) { + const vrMode = bootstrap.getVrMode(); + + if (vrMode === 'hmd') { + const cameraPosition = camera.getWorldPosition( + localVector + ); + const hmdOffsetY = + localVector2.setFromMatrixPosition( + webvr.getSittingToStandingTransform() + ).y + hmd.position.y; + // const hmdOffsetY = hmd.position.y; + const teleportMeshEuler = localEuler.setFromQuaternion( + teleportFloorMesh.quaternion, + 'XZY' + ); + teleportMeshEuler.y = 0; + webvr.setStageMatrix( + localMatrix + .copy(webvr.getStageMatrix()) + .premultiply( + localMatrix2.makeTranslation( + -cameraPosition.x, + -cameraPosition.y, + -cameraPosition.z + ) + ) // move back to origin + .premultiply( + localMatrix2.makeTranslation(0, hmdOffsetY, 0) + ) // move to height + .premultiply( + localMatrix2.makeRotationFromEuler( + teleportMeshEuler + ) + ) // rotate to mesh normal + .premultiply( + localMatrix2.makeTranslation( + teleportFloorMesh.position.x, + teleportFloorMesh.position.y, + teleportFloorMesh.position.z + ) + ) // move to teleport location + ); + } else if (vrMode === 'keyboard') { + const hmdLocalEuler = localEuler.setFromQuaternion( + hmd.rotation, + 'YXZ' + ); + hmdLocalEuler.y = 0; + + webvr.setStageMatrix( + localMatrix + .copy(camera.matrixWorldInverse) + .multiply(webvr.getStageMatrix()) // move back to origin + .premultiply( + localMatrix2.makeRotationFromEuler(hmdLocalEuler) + ) // rotate to HMD + .premultiply(teleportFloorMesh.matrixWorld) // move to teleport location + .premultiply(webvr.getSittingToStandingTransform()) // move above target + ); + } + + webvr.updateStatus(); + cyborg.update(); + + teleportApi.emit('teleport'); + } else if (teleportAirMesh.visible) { + const vrMode = bootstrap.getVrMode(); + + if (vrMode === 'hmd') { + const cameraPosition = camera.getWorldPosition(); + const hmdOffsetY = + _decomposeMatrix(webvr.getSittingToStandingTransform()) + .position.y + hmd.position.y; + // const hmdOffsetY = hmd.position.y; + const teleportMeshEuler = new THREE.Euler().setFromQuaternion( + teleportAirMesh.quaternion, + 'XZY' + ); + teleportMeshEuler.y = 0; + webvr.setStageMatrix( + webvr + .getStageMatrix() + .clone() + .premultiply( + new THREE.Matrix4().makeTranslation( + -cameraPosition.x, + -cameraPosition.y, + -cameraPosition.z + ) + ) // move back to origin + .premultiply( + new THREE.Matrix4().makeTranslation( + 0, + hmdOffsetY, + 0 + ) + ) // move to height + .premultiply( + new THREE.Matrix4().makeRotationFromEuler( + teleportMeshEuler + ) + ) // rotate to mesh normal + .premultiply( + new THREE.Matrix4().makeTranslation( + teleportAirMesh.position.x, + teleportAirMesh.position.y, + teleportAirMesh.position.z + ) + ) // move to teleport location + ); + } else if (vrMode === 'keyboard') { + const hmdLocalEuler = new THREE.Euler().setFromQuaternion( + hmd.rotation, + 'YXZ' + ); + + webvr.setStageMatrix( + camera.matrixWorldInverse + .clone() + .multiply(webvr.getStageMatrix()) // move back to origin + .premultiply( + new THREE.Matrix4().makeRotationFromEuler( + new THREE.Euler( + hmdLocalEuler.x, + 0, + hmdLocalEuler.z, + 'YXZ' + ) + ) + ) // rotate to HMD + .premultiply(teleportAirMesh.matrixWorld) // move to teleport location + .premultiply(webvr.getSittingToStandingTransform()) // move above target + ); + } + + webvr.updateStatus(); + cyborg.update(); + + teleportApi.emit('teleport'); + } - teleportFloorMesh.visible = false; - teleportAirMesh.visible = false; + teleportFloorMesh.visible = false; + teleportAirMesh.visible = false; + } } - } + }; + _updateDrag(); + _updateEnd(); }; - _updateDrag(); - _updateEnd(); - }; - rend.on('update', _update); + rend.on('update', _update); - this._cleanup = () => { - intersect.destroyIntersecter(intersecter); + this._cleanup = () => { + intersect.destroyIntersecter(intersecter); - SIDES.forEach(side => { - scene.remove(teleportFloorMeshes[side]); - scene.remove(teleportAirMeshes[side]); - }); + SIDES.forEach(side => { + scene.remove(teleportFloorMeshes[side]); + scene.remove(teleportAirMeshes[side]); + }); - input.removeListener('paddown', _paddown); - input.removeListener('padup', _padup); + input.removeListener('paddown', _paddown); + input.removeListener('padup', _padup); - rend.removeListener('update', _update); - }; + rend.removeListener('update', _update); + }; - const teleportApi = new EventEmitter(); - return teleportApi; - } - }); + const teleportApi = new EventEmitter(); + return teleportApi; + } + }); } unmount() { this._cleanup(); } -}; +} module.exports = Teleport; diff --git a/core/engines/three/client.js b/core/engines/three/client.js index 16cb05224..7cf95eebb 100644 --- a/core/engines/three/client.js +++ b/core/engines/three/client.js @@ -3,88 +3,93 @@ const CAMERA_ROTATION_ORDER = 'YXZ'; class Three { mount() { - const _requestThree = () => new Promise((accept, reject) => { - window.module = {}; - - const script = document.createElement('script'); - script.src = 'archae/assets/three.js'; - script.async = true; - script.onload = () => { - const {exports: THREE} = window.module; + const _requestThree = () => + new Promise((accept, reject) => { window.module = {}; - accept(THREE); + const script = document.createElement('script'); + script.src = 'archae/assets/three.js'; + script.async = true; + script.onload = () => { + const { exports: THREE } = window.module; + window.module = {}; - _cleanup(); - }; - script.onerror = err => { - reject(err); + accept(THREE); - _cleanup(); - }; - document.body.appendChild(script); + _cleanup(); + }; + script.onerror = err => { + reject(err); - const _cleanup = () => { - document.body.removeChild(script); - }; - }); + _cleanup(); + }; + document.body.appendChild(script); - return _requestThree() - .then(THREE => { - const scene = new THREE.Scene(); - scene.background = new THREE.Color(0xFFFFFF); - scene.autoUpdate = false; - scene.fog = new THREE.FogExp2(0xFFFFFF, 0); - - const camera = (() => { - const result = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.1, 10 * 1024); - result.position.x = 0; - result.position.y = DEFAULT_USER_HEIGHT; - result.position.z = 0; - result.rotation.order = CAMERA_ROTATION_ORDER; - result.up = new THREE.Vector3(0, 1, 0); - result.name = 'left'; // for webvr updateEye() - - const target = new THREE.Vector3(0, DEFAULT_USER_HEIGHT, -1); - result.lookAt(target); - result.target = target; - - return result; - })(); - const cameraParent = new THREE.Object3D(); - cameraParent.matrixAutoUpdate = false; - cameraParent.add(camera); - scene.add(cameraParent); - - const canvas = document.querySelector('#canvas'); - const renderer = new THREE.WebGLRenderer({ - canvas: canvas, - antialias: true, - }); - renderer.setSize(window.innerWidth, window.innerHeight); - renderer.setPixelRatio(window.devicePixelRatio); - renderer.sortObjects = false; - // renderer.shadowMap.enabled = true; - // renderer.shadowMap.autoUpdate = false; - // renderer.shadowMap.type = THREE.PCFSoftShadowMap; - window.document.body.appendChild(renderer.domElement); - - window.addEventListener('resize', () => { - if (!renderer.vr.enabled) { - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - - renderer.setSize(window.innerWidth, window.innerHeight); - } - }); - - return { - THREE, - scene, - camera, - renderer, + const _cleanup = () => { + document.body.removeChild(script); }; }); + + return _requestThree().then(THREE => { + const scene = new THREE.Scene(); + scene.background = new THREE.Color(0xffffff); + scene.autoUpdate = false; + scene.fog = new THREE.FogExp2(0xffffff, 0); + + const camera = (() => { + const result = new THREE.PerspectiveCamera( + 90, + window.innerWidth / window.innerHeight, + 0.1, + 10 * 1024 + ); + result.position.x = 0; + result.position.y = DEFAULT_USER_HEIGHT; + result.position.z = 0; + result.rotation.order = CAMERA_ROTATION_ORDER; + result.up = new THREE.Vector3(0, 1, 0); + result.name = 'left'; // for webvr updateEye() + + const target = new THREE.Vector3(0, DEFAULT_USER_HEIGHT, -1); + result.lookAt(target); + result.target = target; + + return result; + })(); + const cameraParent = new THREE.Object3D(); + cameraParent.matrixAutoUpdate = false; + cameraParent.add(camera); + scene.add(cameraParent); + + const canvas = document.querySelector('#canvas'); + const renderer = new THREE.WebGLRenderer({ + canvas: canvas, + antialias: true, + }); + renderer.setSize(window.innerWidth, window.innerHeight); + renderer.setPixelRatio(window.devicePixelRatio); + renderer.sortObjects = false; + // renderer.shadowMap.enabled = true; + // renderer.shadowMap.autoUpdate = false; + // renderer.shadowMap.type = THREE.PCFSoftShadowMap; + window.document.body.appendChild(renderer.domElement); + + window.addEventListener('resize', () => { + if (!renderer.vr.enabled) { + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + + renderer.setSize(window.innerWidth, window.innerHeight); + } + }); + + return { + THREE, + scene, + camera, + renderer, + }; + }); } unmount() { diff --git a/core/engines/transform/client.js b/core/engines/transform/client.js index a0ccecc28..a8d6065f7 100644 --- a/core/engines/transform/client.js +++ b/core/engines/transform/client.js @@ -8,7 +8,7 @@ class Transform { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; const cleanups = []; this._cleanup = () => { @@ -24,24 +24,18 @@ class Transform { live = false; }); - return archae.requestPlugins([ - '/core/engines/three', - '/core/engines/input', - '/core/engines/webvr', - '/core/engines/biolumi', - '/core/engines/rend', - '/core/utils/geometry-utils', - ]) - .then(([ - three, - input, - webvr, - biolumi, - rend, - geometryUtils, - ]) => { + return archae + .requestPlugins([ + '/core/engines/three', + '/core/engines/input', + '/core/engines/webvr', + '/core/engines/biolumi', + '/core/engines/rend', + '/core/utils/geometry-utils', + ]) + .then(([three, input, webvr, biolumi, rend, geometryUtils]) => { if (live) { - const {THREE, scene} = three; + const { THREE, scene } = three; const THREETransformControls = TransformControls(THREE); const { @@ -59,13 +53,13 @@ class Transform { const zeroQuaternion = new THREE.Quaternion(); const nubbinMaterial = new THREE.MeshBasicMaterial({ - color: 0xCCCCCC, + color: 0xcccccc, }); cleanups.push(() => { nubbinMaterial.dispose(); }); const scalerMaterial = new THREE.MeshBasicMaterial({ - color: 0xFFFF00, + color: 0xffff00, }); cleanups.push(() => { scalerMaterial.dispose(); @@ -86,9 +80,19 @@ class Transform { const rotateScale = 0.5; const scaleScale = 0.3; - const scaleVector = new THREE.Vector3(scaleScale, scaleScale, scaleScale); + const scaleVector = new THREE.Vector3( + scaleScale, + scaleScale, + scaleScale + ); const scaleFactor = scaleVector.length(); - const _makeTransformGizmo = ({position = zeroVector, rotation = zeroQuaternion, scale = oneVector, onpreview = nop, onupdate = nop}) => { + const _makeTransformGizmo = ({ + position = zeroVector, + rotation = zeroQuaternion, + scale = oneVector, + onpreview = nop, + onupdate = nop, + }) => { const transformId = _makeId(); const transformGizmo = (() => { @@ -125,8 +129,11 @@ class Transform { }; object.syncScale = () => { properties.scale.copy( - state.initialScale.clone() - .multiply(scaleGizmo.position.clone().divideScalar(scaleFactor)) + state.initialScale + .clone() + .multiply( + scaleGizmo.position.clone().divideScalar(scaleFactor) + ) ); }; object.onpreview = onpreview; @@ -138,7 +145,11 @@ class Transform { const rotateGizmo = new THREETransformGizmoRotate(); const rotateGizmoNubbin = (() => { - const geometry = new THREE.SphereBufferGeometry(0.1 / 2 / rotateScale, 8, 8); + const geometry = new THREE.SphereBufferGeometry( + 0.1 / 2 / rotateScale, + 8, + 8 + ); const material = nubbinMaterial; const mesh = new THREE.Mesh(geometry, material); mesh.position.z = 1; @@ -150,7 +161,11 @@ class Transform { object.rotateGizmo = rotateGizmo; const scaleGizmo = (() => { - const geometry = new THREE.BoxBufferGeometry(0.075, 0.075, 0.075); + const geometry = new THREE.BoxBufferGeometry( + 0.075, + 0.075, + 0.075 + ); const material = scalerMaterial; const mesh = new THREE.Mesh(geometry, material); mesh.position.copy(scaleVector); @@ -163,8 +178,17 @@ class Transform { })(); const boxAnchors = []; - const _addBoxTarget = (position, rotation, scale, size, anchor, onupdate = nop) => { - const geometry = geometryUtils.unindexBufferGeometry(new THREE.BoxBufferGeometry(size.x, size.y, size.z)); + const _addBoxTarget = ( + position, + rotation, + scale, + size, + anchor, + onupdate = nop + ) => { + const geometry = geometryUtils.unindexBufferGeometry( + new THREE.BoxBufferGeometry(size.x, size.y, size.z) + ); const material = transparentMaterial; const boxAnchor = new THREE.Mesh(geometry, material); @@ -244,7 +268,9 @@ class Transform { } ); _addBoxTarget( - new THREE.Vector3(0, 0, rotateScale).applyQuaternion(transformGizmo.rotateGizmo.quaternion), + new THREE.Vector3(0, 0, rotateScale).applyQuaternion( + transformGizmo.rotateGizmo.quaternion + ), new THREE.Quaternion(), new THREE.Vector3(1, 1, 1), new THREE.Vector3(0.1, 0.1, 0.1), @@ -252,7 +278,11 @@ class Transform { onmousedown: `transform:${transformId}:rotate`, }, function() { - this.position.copy(new THREE.Vector3(0, 0, rotateScale).applyQuaternion(transformGizmo.rotateGizmo.quaternion)); + this.position.copy( + new THREE.Vector3(0, 0, rotateScale).applyQuaternion( + transformGizmo.rotateGizmo.quaternion + ) + ); } ); _addBoxTarget( @@ -306,26 +336,35 @@ class Transform { }; const _triggerdown = e => { - const {side} = e; + const { side } = e; const _doClickTransformGizmo = () => { const hoverState = rend.getHoverState(side); - const {intersectionPoint} = hoverState; + const { intersectionPoint } = hoverState; if (intersectionPoint) { - const {anchor} = hoverState; + const { anchor } = hoverState; const onmousedown = (anchor && anchor.onmousedown) || ''; let match; - if (match = onmousedown.match(/^transform:([^:]+):(x|y|z|xyz|xy|yz|xz|rotate|scale)$/)) { + if ( + (match = onmousedown.match( + /^transform:([^:]+):(x|y|z|xyz|xy|yz|xz|rotate|scale)$/ + )) + ) { const transformId = match[1]; const mode = match[2]; const dragState = dragStates[side]; - const {gamepads} = webvr.getStatus(); + const { gamepads } = webvr.getStatus(); const gamepad = gamepads[side]; - const {worldPosition: controllerPosition, worldRotation: controllerRotation} = gamepad; - const transformGizmo = transformGizmos.find(transformGizmo => transformGizmo.transformId === transformId); + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + } = gamepad; + const transformGizmo = transformGizmos.find( + transformGizmo => transformGizmo.transformId === transformId + ); dragState.src = { transformId: transformId, mode: mode, @@ -352,15 +391,21 @@ class Transform { }; input.on('triggerdown', _triggerdown); const _triggerup = e => { - const {side} = e; + const { side } = e; const dragState = dragStates[side]; - const {src} = dragState; + const { src } = dragState; if (src) { - const {transformId} = src; - const transformGizmo = transformGizmos.find(transformGizmo => transformGizmo.transformId === transformId); - - const {position, rotation, scale} = transformGizmo.getProperties(); + const { transformId } = src; + const transformGizmo = transformGizmos.find( + transformGizmo => transformGizmo.transformId === transformId + ); + + const { + position, + rotation, + scale, + } = transformGizmo.getProperties(); transformGizmo.update(position, rotation, scale); transformGizmo.onupdate(position, rotation, scale); @@ -374,24 +419,50 @@ class Transform { const _update = () => { SIDES.forEach(side => { const dragState = dragStates[side]; - const {src} = dragState; - const {gamepads} = webvr.getStatus(); + const { src } = dragState; + const { gamepads } = webvr.getStatus(); const gamepad = gamepads[side]; if (src) { - const {transformId, mode, startControllerPosition, startControllerRotation, startIntersectionPoint, startPosition} = src; - const transformGizmo = transformGizmos.find(transformGizmo => transformGizmo.transformId === transformId); + const { + transformId, + mode, + startControllerPosition, + startControllerRotation, + startIntersectionPoint, + startPosition, + } = src; + const transformGizmo = transformGizmos.find( + transformGizmo => transformGizmo.transformId === transformId + ); const _preview = () => { - const {position, rotation, scale} = transformGizmo.getProperties(); + const { + position, + rotation, + scale, + } = transformGizmo.getProperties(); transformGizmo.onpreview(position, rotation, scale); }; if (mode === 'x') { - const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(new THREE.Vector3(0, 0, 1), startIntersectionPoint); - const {worldPosition: controllerPosition, worldRotation: controllerRotation, worldScale: controllerScale} = gamepad; - const controllerLine = geometryUtils.makeControllerLine(controllerPosition, controllerRotation, controllerScale); - const controllerIntersectionPoint = plane.intersectLine(controllerLine); + const plane = new THREE.Plane().setFromNormalAndCoplanarPoint( + new THREE.Vector3(0, 0, 1), + startIntersectionPoint + ); + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + worldScale: controllerScale, + } = gamepad; + const controllerLine = geometryUtils.makeControllerLine( + controllerPosition, + controllerRotation, + controllerScale + ); + const controllerIntersectionPoint = plane.intersectLine( + controllerLine + ); if (controllerIntersectionPoint) { const endIntersectionPoint = new THREE.Vector3( @@ -399,7 +470,9 @@ class Transform { startIntersectionPoint.y, startIntersectionPoint.z ); - const positionDiff = endIntersectionPoint.clone().sub(startIntersectionPoint); + const positionDiff = endIntersectionPoint + .clone() + .sub(startIntersectionPoint); const endPosition = startPosition.clone().add(positionDiff); transformGizmo.position.copy(endPosition); transformGizmo.updateMatrixWorld(); @@ -409,10 +482,23 @@ class Transform { _preview(); } } else if (mode === 'y') { - const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(new THREE.Vector3(0, 0, 1), startIntersectionPoint); - const {worldPosition: controllerPosition, worldRotation: controllerRotation, worldScale: controllerScale} = gamepad; - const controllerLine = geometryUtils.makeControllerLine(controllerPosition, controllerRotation, controllerScale); - const controllerIntersectionPoint = plane.intersectLine(controllerLine); + const plane = new THREE.Plane().setFromNormalAndCoplanarPoint( + new THREE.Vector3(0, 0, 1), + startIntersectionPoint + ); + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + worldScale: controllerScale, + } = gamepad; + const controllerLine = geometryUtils.makeControllerLine( + controllerPosition, + controllerRotation, + controllerScale + ); + const controllerIntersectionPoint = plane.intersectLine( + controllerLine + ); if (controllerIntersectionPoint) { const endIntersectionPoint = new THREE.Vector3( @@ -420,7 +506,9 @@ class Transform { controllerIntersectionPoint.y, startIntersectionPoint.z ); - const positionDiff = endIntersectionPoint.clone().sub(startIntersectionPoint); + const positionDiff = endIntersectionPoint + .clone() + .sub(startIntersectionPoint); const endPosition = startPosition.clone().add(positionDiff); transformGizmo.position.copy(endPosition); transformGizmo.updateMatrixWorld(); @@ -430,10 +518,23 @@ class Transform { _preview(); } } else if (mode === 'z') { - const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(new THREE.Vector3(1, 0, 0), startIntersectionPoint); - const {worldPosition: controllerPosition, worldRotation: controllerRotation, worldScale: controllerScale} = gamepad; - const controllerLine = geometryUtils.makeControllerLine(controllerPosition, controllerRotation, controllerScale); - const controllerIntersectionPoint = plane.intersectLine(controllerLine); + const plane = new THREE.Plane().setFromNormalAndCoplanarPoint( + new THREE.Vector3(1, 0, 0), + startIntersectionPoint + ); + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + worldScale: controllerScale, + } = gamepad; + const controllerLine = geometryUtils.makeControllerLine( + controllerPosition, + controllerRotation, + controllerScale + ); + const controllerIntersectionPoint = plane.intersectLine( + controllerLine + ); if (controllerIntersectionPoint) { const endIntersectionPoint = new THREE.Vector3( @@ -441,7 +542,9 @@ class Transform { startIntersectionPoint.y, controllerIntersectionPoint.z ); - const positionDiff = endIntersectionPoint.clone().sub(startIntersectionPoint); + const positionDiff = endIntersectionPoint + .clone() + .sub(startIntersectionPoint); const endPosition = startPosition.clone().add(positionDiff); transformGizmo.position.copy(endPosition); transformGizmo.updateMatrixWorld(); @@ -451,13 +554,28 @@ class Transform { _preview(); } } else if (mode === 'xy') { - const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(new THREE.Vector3(0, 0, 1), startIntersectionPoint); - const {worldPosition: controllerPosition, worldRotation: controllerRotation, worldScale: controllerScale} = gamepad; - const controllerLine = geometryUtils.makeControllerLine(controllerPosition, controllerRotation, controllerScale); - const endIntersectionPoint = plane.intersectLine(controllerLine); + const plane = new THREE.Plane().setFromNormalAndCoplanarPoint( + new THREE.Vector3(0, 0, 1), + startIntersectionPoint + ); + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + worldScale: controllerScale, + } = gamepad; + const controllerLine = geometryUtils.makeControllerLine( + controllerPosition, + controllerRotation, + controllerScale + ); + const endIntersectionPoint = plane.intersectLine( + controllerLine + ); if (endIntersectionPoint) { - const positionDiff = endIntersectionPoint.clone().sub(startIntersectionPoint); + const positionDiff = endIntersectionPoint + .clone() + .sub(startIntersectionPoint); const endPosition = startPosition.clone().add(positionDiff); transformGizmo.position.copy(endPosition); transformGizmo.updateMatrixWorld(); @@ -467,13 +585,28 @@ class Transform { _preview(); } } else if (mode === 'yz') { - const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(new THREE.Vector3(1, 0, 0), startIntersectionPoint); - const {worldPosition: controllerPosition, worldRotation: controllerRotation, worldScale: controllerScale} = gamepad; - const controllerLine = geometryUtils.makeControllerLine(controllerPosition, controllerRotation, controllerScale); - const endIntersectionPoint = plane.intersectLine(controllerLine); + const plane = new THREE.Plane().setFromNormalAndCoplanarPoint( + new THREE.Vector3(1, 0, 0), + startIntersectionPoint + ); + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + worldScale: controllerScale, + } = gamepad; + const controllerLine = geometryUtils.makeControllerLine( + controllerPosition, + controllerRotation, + controllerScale + ); + const endIntersectionPoint = plane.intersectLine( + controllerLine + ); if (endIntersectionPoint) { - const positionDiff = endIntersectionPoint.clone().sub(startIntersectionPoint); + const positionDiff = endIntersectionPoint + .clone() + .sub(startIntersectionPoint); const endPosition = startPosition.clone().add(positionDiff); transformGizmo.position.copy(endPosition); transformGizmo.updateMatrixWorld(); @@ -483,13 +616,28 @@ class Transform { _preview(); } } else if (mode === 'xz') { - const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(new THREE.Vector3(0, 1, 0), startIntersectionPoint); - const {worldPosition: controllerPosition, worldRotation: controllerRotation, worldScale: controllerScale} = gamepad; - const controllerLine = geometryUtils.makeControllerLine(controllerPosition, controllerRotation, controllerScale); - const endIntersectionPoint = plane.intersectLine(controllerLine); + const plane = new THREE.Plane().setFromNormalAndCoplanarPoint( + new THREE.Vector3(0, 1, 0), + startIntersectionPoint + ); + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + worldScale: controllerScale, + } = gamepad; + const controllerLine = geometryUtils.makeControllerLine( + controllerPosition, + controllerRotation, + controllerScale + ); + const endIntersectionPoint = plane.intersectLine( + controllerLine + ); if (endIntersectionPoint) { - const positionDiff = endIntersectionPoint.clone().sub(startIntersectionPoint); + const positionDiff = endIntersectionPoint + .clone() + .sub(startIntersectionPoint); const endPosition = startPosition.clone().add(positionDiff); transformGizmo.position.copy(endPosition); transformGizmo.updateMatrixWorld(); @@ -499,16 +647,23 @@ class Transform { _preview(); } } else if (mode === 'xyz') { - const {worldPosition: controllerPosition, worldRotation: controllerRotation} = gamepad; - const endPosition = controllerPosition.clone() + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + } = gamepad; + const endPosition = controllerPosition + .clone() .add( new THREE.Vector3(0, 0, -1) .applyQuaternion(controllerRotation) - .multiplyScalar(startIntersectionPoint.clone().sub(startControllerPosition).length()) + .multiplyScalar( + startIntersectionPoint + .clone() + .sub(startControllerPosition) + .length() + ) ) - .add( - startPosition.clone().sub(startIntersectionPoint) - ); + .add(startPosition.clone().sub(startIntersectionPoint)); transformGizmo.position.copy(endPosition); transformGizmo.updateMatrixWorld(); @@ -516,39 +671,64 @@ class Transform { _preview(); } else if (mode === 'rotate') { - const {worldPosition: controllerPosition, worldRotation: controllerRotation} = gamepad; - const endPosition = controllerPosition.clone() + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + } = gamepad; + const endPosition = controllerPosition + .clone() .add( - new THREE.Vector3(0, 0, -1) - .applyQuaternion(controllerRotation) + new THREE.Vector3(0, 0, -1).applyQuaternion( + controllerRotation + ) ); - const endSpherePoint = new THREE.Sphere(startPosition.clone(), rotateScale) - .clampPoint(endPosition); + const endSpherePoint = new THREE.Sphere( + startPosition.clone(), + rotateScale + ).clampPoint(endPosition); const rotationMatrix = new THREE.Matrix4().lookAt( endSpherePoint, startPosition, upVector.clone().applyQuaternion(controllerRotation) ); - transformGizmo.rotateGizmo.quaternion.setFromRotationMatrix(rotationMatrix); + transformGizmo.rotateGizmo.quaternion.setFromRotationMatrix( + rotationMatrix + ); transformGizmo.rotateGizmo.updateMatrixWorld(); transformGizmo.syncRotation(); _preview(); } else if (mode === 'scale') { - const {worldPosition: controllerPosition, worldRotation: controllerRotation} = gamepad; + const { + worldPosition: controllerPosition, + worldRotation: controllerRotation, + } = gamepad; const endPlanePoint = new THREE.Plane() - .setFromNormalAndCoplanarPoint(scaleNormalVector.clone(), startPosition.clone()) + .setFromNormalAndCoplanarPoint( + scaleNormalVector.clone(), + startPosition.clone() + ) .intersectLine( new THREE.Line3( controllerPosition.clone(), - controllerPosition.clone().add(new THREE.Vector3(0, 0, -15).applyQuaternion(controllerRotation)) + controllerPosition + .clone() + .add( + new THREE.Vector3(0, 0, -15).applyQuaternion( + controllerRotation + ) + ) ) ); if (endPlanePoint) { - const endLinePoint = new THREE.Line3(startPosition.clone(), startPosition.clone().add(oneVector)) - .closestPointToPoint(endPlanePoint, false); - const endScalePoint = endLinePoint.clone().sub(startPosition); + const endLinePoint = new THREE.Line3( + startPosition.clone(), + startPosition.clone().add(oneVector) + ).closestPointToPoint(endPlanePoint, false); + const endScalePoint = endLinePoint + .clone() + .sub(startPosition); transformGizmo.scaleGizmo.position.copy(endScalePoint); transformGizmo.scaleGizmo.updateMatrixWorld(); @@ -581,7 +761,10 @@ class Transform { this._cleanup(); } } -const _makeId = () => Math.random().toString(36).substring(7); +const _makeId = () => + Math.random() + .toString(36) + .substring(7); const nop = () => {}; const yes = () => true; diff --git a/core/engines/voicechat/client.js b/core/engines/voicechat/client.js index 12f78305c..a2523a602 100644 --- a/core/engines/voicechat/client.js +++ b/core/engines/voicechat/client.js @@ -8,8 +8,8 @@ export default class VoiceChat { } mount() { - const {_archae: archae} = this; - const {metadata: {server: {enabled: serverEnabled}}} = archae; + const { _archae: archae } = this; + const { metadata: { server: { enabled: serverEnabled } } } = archae; const cleanups = []; this._cleanup = () => { @@ -25,159 +25,172 @@ export default class VoiceChat { }); if (serverEnabled) { - archae.requestPlugins([ - '/core/engines/three', - '/core/engines/webvr', - '/core/engines/input', - '/core/engines/somnifer', - '/core/engines/config', - '/core/engines/multiplayer', - '/core/utils/js-utils', - '/core/utils/network-utils', - ]) - .then(([ - three, - webvr, - input, - somnifer, - config, - multiplayer, - jsUtils, - networkUtils, - ]) => { - if (live) { - const {THREE} = three; - const {events} = jsUtils; - const {EventEmitter} = events; - const {AutoWs} = networkUtils; - - const callInterface = (() => { - let currentRemotePeerId = null; - - const connection = new AutoWs(_relativeWsUrl('archae/voicechatWs?id=' + multiplayer.getId())); - connection.on('message', msg => { - if (typeof msg.data === 'string') { - const e = JSON.parse(msg.data) ; - const {type} = e; - - if (type === 'id') { - const {id: messageId} = e; - currentRemotePeerId = messageId; + archae + .requestPlugins([ + '/core/engines/three', + '/core/engines/webvr', + '/core/engines/input', + '/core/engines/somnifer', + '/core/engines/config', + '/core/engines/multiplayer', + '/core/utils/js-utils', + '/core/utils/network-utils', + ]) + .then( + ( + [ + three, + webvr, + input, + somnifer, + config, + multiplayer, + jsUtils, + networkUtils, + ] + ) => { + if (live) { + const { THREE } = three; + const { events } = jsUtils; + const { EventEmitter } = events; + const { AutoWs } = networkUtils; + + const callInterface = (() => { + let currentRemotePeerId = null; + + const connection = new AutoWs( + _relativeWsUrl('archae/voicechatWs?id=' + multiplayer.getId()) + ); + connection.on('message', msg => { + if (typeof msg.data === 'string') { + const e = JSON.parse(msg.data); + const { type } = e; + + if (type === 'id') { + const { id: messageId } = e; + currentRemotePeerId = messageId; + } else { + console.warn( + 'unknown message type', + JSON.stringify(type) + ); + } } else { - console.warn('unknown message type', JSON.stringify(type)); + if (currentRemotePeerId !== null) { + callInterface.emit('buffer', { + id: currentRemotePeerId, + data: new Float32Array(msg.data), + }); + } else { + console.warn('buffer data before remote peer id', msg); + } } - } else { - if (currentRemotePeerId !== null) { - callInterface.emit('buffer', { - id: currentRemotePeerId, - data: new Float32Array(msg.data), - }); - } else { - console.warn('buffer data before remote peer id', msg); + }); + connection.on('disconnect', () => { + currentRemotePeerId = null; + }); + + class CallInterface extends EventEmitter { + write(d) { + connection.sendUnbuffered(d); } - } - }); - connection.on('disconnect', () => { - currentRemotePeerId = null; - }); - class CallInterface extends EventEmitter { - write(d) { - connection.sendUnbuffered(d); + destroy() { + connection.destroy(); + } } + const callInterface = new CallInterface(); + return callInterface; + })(); - destroy() { - connection.destroy(); - } - } - const callInterface = new CallInterface(); - return callInterface; - })(); - - const _init = () => { - const _makeSoundBody = id => { - const result = somnifer.makeBody(); - - const inputNode = new WebAudioBufferQueue({ - audioContext: result.sound.context, - // channels: 2, - channels: 1, - // bufferSize: 16384, - objectMode: true, - }); - result.setInputSource(inputNode); - result.inputNode = inputNode; + const _init = () => { + const _makeSoundBody = id => { + const result = somnifer.makeBody(); + + const inputNode = new WebAudioBufferQueue({ + audioContext: result.sound.context, + // channels: 2, + channels: 1, + // bufferSize: 16384, + objectMode: true, + }); + result.setInputSource(inputNode); + result.inputNode = inputNode; - const remotePlayerMesh = multiplayer.getRemotePlayerMesh(id).children[0]; - result.setObject(remotePlayerMesh); + const remotePlayerMesh = multiplayer.getRemotePlayerMesh(id) + .children[0]; + result.setObject(remotePlayerMesh); - return result; - }; + return result; + }; - const soundBodies = (() => { - const playerStatuses = multiplayer.getPlayerStatuses(); + const soundBodies = (() => { + const playerStatuses = multiplayer.getPlayerStatuses(); - const result = new Map(); - playerStatuses.forEach((status, id) => { - result.set(id, _makeSoundBody(id)); + const result = new Map(); + playerStatuses.forEach((status, id) => { + result.set(id, _makeSoundBody(id)); + }); + return result; + })(); + + const _playerEnter = ({ id }) => { + soundBodies.set(id, _makeSoundBody(id)); + }; + multiplayer.on('playerEnter', _playerEnter); + const _playerLeave = id => { + const soundBody = soundBodies.get(id); + soundBody.destroy(); + + soundBodies.delete(id); + }; + multiplayer.on('playerLeave', _playerLeave); + cleanups.push(() => { + multiplayer.removeListener('playerEnter', _playerEnter); + multiplayer.removeListener('playerLeave', _playerLeave); }); - return result; - })(); - const _playerEnter = ({id}) => { - soundBodies.set(id, _makeSoundBody(id)); - }; - multiplayer.on('playerEnter', _playerEnter); - const _playerLeave = id => { - const soundBody = soundBodies.get(id); - soundBody.destroy(); + callInterface.on('buffer', ({ id, data }) => { + const soundBody = soundBodies.get(id); - soundBodies.delete(id); + if (soundBody) { + const { inputNode } = soundBody; + inputNode.write(data); + } + }); }; - multiplayer.on('playerLeave', _playerLeave); - cleanups.push(() => { - multiplayer.removeListener('playerEnter', _playerEnter); - multiplayer.removeListener('playerLeave', _playerLeave); - }); + _init(); - callInterface.on('buffer', ({id, data}) => { - const soundBody = soundBodies.get(id); - - if (soundBody) { - const {inputNode} = soundBody; - inputNode.write(data); + const localCleanups = []; + const _localCleanup = () => { + for (let i = 0; i < localCleanups.length; i++) { + const localCleanup = localCleanups[i]; + localCleanup(); } - }); - }; - _init(); - - const localCleanups = []; - const _localCleanup = () => { - for (let i = 0; i < localCleanups.length; i++) { - const localCleanup = localCleanups[i]; - localCleanup(); - } - localCleanups.length = 0; - }; - - let srcAudioContext = null; - let enabled = false; - const _enable = () => { - enabled = true; - localCleanups.push(() => { - enabled = false; - }); + localCleanups.length = 0; + }; - const _requestMicrophoneMediaStream = () => navigator.mediaDevices.getUserMedia({ - audio: true, - }).then(mediaStream => { + let srcAudioContext = null; + let enabled = false; + const _enable = () => { + enabled = true; localCleanups.push(() => { - _closeMediaStream(mediaStream); + enabled = false; }); - return mediaStream; - }); - /* const _requestCameraMediaStream = () => navigator.mediaDevices.getUserMedia({ + const _requestMicrophoneMediaStream = () => + navigator.mediaDevices + .getUserMedia({ + audio: true, + }) + .then(mediaStream => { + localCleanups.push(() => { + _closeMediaStream(mediaStream); + }); + + return mediaStream; + }); + /* const _requestCameraMediaStream = () => navigator.mediaDevices.getUserMedia({ audio: true, }).then(mediaStream => { localCleanups.push(() => { @@ -187,17 +200,26 @@ export default class VoiceChat { return mediaStream; }); */ - _requestMicrophoneMediaStream() - .then(mediaStream => { + _requestMicrophoneMediaStream().then(mediaStream => { if (!srcAudioContext) { srcAudioContext = new AudioContext(); } - const source = srcAudioContext.createMediaStreamSource(mediaStream); - const scriptNode = srcAudioContext.createScriptProcessor(4096, 1, 1); + const source = srcAudioContext.createMediaStreamSource( + mediaStream + ); + const scriptNode = srcAudioContext.createScriptProcessor( + 4096, + 1, + 1 + ); scriptNode.onaudioprocess = e => { - const {inputBuffer} = e; + const { inputBuffer } = e; - for (let channel = 0; channel < inputBuffer.numberOfChannels; channel++) { + for ( + let channel = 0; + channel < inputBuffer.numberOfChannels; + channel++ + ) { const inputData = inputBuffer.getChannelData(channel); callInterface.write(inputData); } @@ -213,53 +235,54 @@ export default class VoiceChat { _closeMediaStream(mediaStream); }); }); - }; - const _disable = () => { - _localCleanup(); - }; - - const _menudown = e => { - const {side} = e; - const {gamepads} = webvr.getStatus(); - const gamepad = gamepads[side]; - - if (gamepad.buttons.grip.pressed) { - const browserConfig = config.getBrowserConfig(); - browserConfig.voiceChat = !browserConfig.voiceChat; - config.setBrowserConfig(browserConfig); - - e.stopImmediatePropagation(); - } - }; - input.on('menudown', _menudown, { - priority: 1, - }); - - const _updateEnabled = () => { - const {voiceChat} = config.getBrowserConfig(); - const shouldBeEnabled = voiceChat; - - if (shouldBeEnabled && !enabled) { - _enable(); - } else if (!shouldBeEnabled && enabled) { - _disable(); }; - }; - const _browserConfig = _updateEnabled; - config.on('browserConfig', _browserConfig); + const _disable = () => { + _localCleanup(); + }; - _updateEnabled(); + const _menudown = e => { + const { side } = e; + const { gamepads } = webvr.getStatus(); + const gamepad = gamepads[side]; - cleanups.push(() => { - _localCleanup(); + if (gamepad.buttons.grip.pressed) { + const browserConfig = config.getBrowserConfig(); + browserConfig.voiceChat = !browserConfig.voiceChat; + config.setBrowserConfig(browserConfig); - callInterface.destroy(); + e.stopImmediatePropagation(); + } + }; + input.on('menudown', _menudown, { + priority: 1, + }); + + const _updateEnabled = () => { + const { voiceChat } = config.getBrowserConfig(); + const shouldBeEnabled = voiceChat; + + if (shouldBeEnabled && !enabled) { + _enable(); + } else if (!shouldBeEnabled && enabled) { + _disable(); + } + }; + const _browserConfig = _updateEnabled; + config.on('browserConfig', _browserConfig); - input.removeListener('menudown', _menudown); - config.removeListener('browserConfig', _browserConfig); - }); + _updateEnabled(); + + cleanups.push(() => { + _localCleanup(); + + callInterface.destroy(); + + input.removeListener('menudown', _menudown); + config.removeListener('browserConfig', _browserConfig); + }); + } } - }); + ); } } @@ -270,7 +293,13 @@ export default class VoiceChat { const _relativeWsUrl = s => { const l = window.location; - return ((l.protocol === 'https:') ? 'wss://' : 'ws://') + l.host + l.pathname + (!/\/$/.test(l.pathname) ? '/' : '') + s; + return ( + (l.protocol === 'https:' ? 'wss://' : 'ws://') + + l.host + + l.pathname + + (!/\/$/.test(l.pathname) ? '/' : '') + + s + ); }; const _closeMediaStream = mediaStream => { diff --git a/core/engines/voicechat/server.js b/core/engines/voicechat/server.js index eefdda470..dba383beb 100644 --- a/core/engines/voicechat/server.js +++ b/core/engines/voicechat/server.js @@ -1,5 +1,5 @@ const events = require('events'); -const {EventEmitter} = events; +const { EventEmitter } = events; class VoiceChat { constructor(archae) { @@ -7,8 +7,8 @@ class VoiceChat { } mount() { - const {_archae: archae} = this; - const {app, wss} = archae.getCore(); + const { _archae: archae } = this; + const { app, wss } = archae.getCore(); const connections = []; const audioBuffers = new Map(); @@ -55,10 +55,10 @@ class VoiceChat { }; wss.on('connection', c => { - const {url} = c.upgradeReq; + const { url } = c.upgradeReq; let match; - if (match = url.match(/\/archae\/voicechatWs\?id=(.+)$/)) { + if ((match = url.match(/\/archae\/voicechatWs\?id=(.+)$/))) { const peerId = parseInt(decodeURIComponent(match[1]), 10); c.peerId = peerId; @@ -67,7 +67,7 @@ class VoiceChat { const audioBuffer = _getAudioBuffer(c.peerId); audioBuffer.write(msg); } else { - console.warn('voicechat invalid message', {msg, flags}); + console.warn('voicechat invalid message', { msg, flags }); } }); c.on('close', () => { diff --git a/core/engines/wallet/client.js b/core/engines/wallet/client.js index 68a02eff2..f422fd8fa 100644 --- a/core/engines/wallet/client.js +++ b/core/engines/wallet/client.js @@ -9,11 +9,7 @@ import { import walletRender from './lib/render/wallet'; import menuUtils from './lib/utils/menu'; -const DEFAULT_MATRIX = [ - 0, 0, 0, - 0, 0, 0, 1, - 1, 1, 1, -]; +const DEFAULT_MATRIX = [0, 0, 0, 0, 0, 0, 1, 1, 1, 1]; const NUM_POSITIONS = 100 * 1024; const ROTATE_SPEED = 0.0004; const CREDIT_ASSET_NAME = 'CRD'; @@ -25,25 +21,25 @@ const ASSET_SHADER = { }, }, vertexShader: [ - "uniform float theta;", - "attribute vec3 color;", - "attribute vec2 dy;", - "varying vec3 vcolor;", + 'uniform float theta;', + 'attribute vec3 color;', + 'attribute vec2 dy;', + 'varying vec3 vcolor;', `float rotateSpeed = ${ROTATE_SPEED.toFixed(8)};`, - "void main() {", - " gl_Position = projectionMatrix * modelViewMatrix * vec4(position.x - dy.x + (dy.x*cos(theta) - dy.y*sin(theta)), position.y, position.z - dy.y + (dy.y*cos(theta) + dy.x*sin(theta)), 1.0);", - " vcolor = color;", - "}" - ].join("\n"), + 'void main() {', + ' gl_Position = projectionMatrix * modelViewMatrix * vec4(position.x - dy.x + (dy.x*cos(theta) - dy.y*sin(theta)), position.y, position.z - dy.y + (dy.y*cos(theta) + dy.x*sin(theta)), 1.0);', + ' vcolor = color;', + '}', + ].join('\n'), fragmentShader: [ - "varying vec3 vcolor;", - "void main() {", - " gl_FragColor = vec4(vcolor, 1.0);", - "}" - ].join("\n") + 'varying vec3 vcolor;', + 'void main() {', + ' gl_FragColor = vec4(vcolor, 1.0);', + '}', + ].join('\n'), }; const SIDES = ['left', 'right']; -const STRG_URL = 'https://cdn.rawgit.com/modulesio/strg/486ed70c/iframe.html' +const STRG_URL = 'https://cdn.rawgit.com/modulesio/strg/486ed70c/iframe.html'; class Wallet { constructor(archae) { @@ -51,12 +47,14 @@ class Wallet { } mount() { - const {_archae: archae} = this; - const {metadata: {site: {url: siteUrl}, vrid: {url: vridUrl}}} = archae; + const { _archae: archae } = this; + const { + metadata: { site: { url: siteUrl }, vrid: { url: vridUrl } }, + } = archae; const strg = strgLib(STRG_URL); - const _addStrgAsset = (asset, quantity) => strg.get('assets') - .then(assets => { + const _addStrgAsset = (asset, quantity) => + strg.get('assets').then(assets => { assets = assets || []; let assetSpec = assets.find(assetSpec => assetSpec.asset === asset); if (!assetSpec) { @@ -71,10 +69,12 @@ class Wallet { return strg.set('assets', assets); }); - const _removeStrgAsset = (asset, quantity) => strg.get('assets') - .then(assets => { + const _removeStrgAsset = (asset, quantity) => + strg.get('assets').then(assets => { assets = assets || []; - const assetSpecIndex = assets.findIndex(assetSpec => assetSpec.asset === asset); + const assetSpecIndex = assets.findIndex( + assetSpec => assetSpec.asset === asset + ); if (assetSpecIndex !== -1) { const assetSpec = assets[assetSpecIndex]; assetSpec.quantity--; @@ -98,581 +98,686 @@ class Wallet { live = false; }); - return archae.requestPlugins([ - '/core/engines/bootstrap', - '/core/engines/three', - '/core/engines/input', - '/core/engines/fs', - '/core/engines/webvr', - '/core/engines/biolumi', - '/core/engines/resource', - '/core/engines/cyborg', - '/core/engines/keyboard', - '/core/engines/hand', - '/core/engines/rend', - '/core/engines/tags', - '/core/engines/multiplayer', - '/core/engines/stck', - '/core/engines/notification', - '/core/utils/js-utils', - '/core/utils/hash-utils', - '/core/utils/network-utils', - '/core/utils/creature-utils', - '/core/utils/sprite-utils', - '/core/utils/vrid-utils', - ]).then(([ - bootstrap, - three, - input, - fs, - webvr, - biolumi, - resource, - cyborg, - keyboard, - hand, - rend, - tags, - multiplayer, - stck, - notification, - jsUtils, - hashUtils, - networkUtils, - creatureUtils, - spriteUtils, - vridUtils, - ]) => { - if (live) { - const {THREE, scene, camera} = three; - const {events} = jsUtils; - const {EventEmitter} = events; - const {murmur} = hashUtils; - const {AutoWs} = networkUtils; - const {Grabbable} = hand; - const {sfx} = resource; - - const walletRenderer = walletRender.makeRenderer({creatureUtils}); - const localUserId = multiplayer.getId(); - - const pixelSize = 0.015; - const numPixels = 12; - const assetSize = pixelSize * numPixels; - const assetSizeVector = new THREE.Vector3(assetSize, assetSize, assetSize); - - const zeroArray = new Float32Array(0); - const zeroArray2 = new Float32Array(0); - const zeroVector = new THREE.Vector3(); - const oneVector = new THREE.Vector3(1, 1, 1); - const assetOffsetVector = new THREE.Vector3(0, 0, -pixelSize/2); - const zeroQuaternion = new THREE.Quaternion(); - const forwardQuaternion = new THREE.Quaternion().setFromUnitVectors( - new THREE.Vector3(0, 1, 0), - new THREE.Vector3(0, 0, -1) - ); - const assetsMaterial = new THREE.ShaderMaterial({ - uniforms: THREE.UniformsUtils.clone(ASSET_SHADER.uniforms), - vertexShader: ASSET_SHADER.vertexShader, - fragmentShader: ASSET_SHADER.fragmentShader, - // transparent: true, - // depthTest: false, - }); - const mainFontSpec = { - fonts: biolumi.getFonts(), - fontSize: 36, - lineHeight: 1.4, - fontWeight: biolumi.getFontWeight(), - fontStyle: biolumi.getFontStyle(), - }; - - const _addAsset = (id, type, value, n, physics, matrix) => { - const position = new THREE.Vector3(matrix[0], matrix[1], matrix[2]); - const rotation = new THREE.Quaternion(matrix[3], matrix[4], matrix[5], matrix[6]); - const scale = new THREE.Vector3(matrix[7], matrix[8], matrix[9]); - - const assetInstance = assetsMesh.addAssetInstance( - id, - type, - value, - n, - physics, - position, - rotation, - scale, - zeroVector, - zeroQuaternion, - oneVector - ); - _bindAssetInstance(assetInstance); - _bindAssetInstancePhysics(assetInstance); - }; - - const connection = new AutoWs(_relativeWsUrl('archae/walletWs')); - connection.on('message', e => { - const {data} = e; - const m = JSON.parse(data); - const {type, args} = m; - - if (type === 'init') { - const {assets} = args; - for (let i = 0; i < assets.length; i++) { - const assetSpec = assets[i]; - const { + return archae + .requestPlugins([ + '/core/engines/bootstrap', + '/core/engines/three', + '/core/engines/input', + '/core/engines/fs', + '/core/engines/webvr', + '/core/engines/biolumi', + '/core/engines/resource', + '/core/engines/cyborg', + '/core/engines/keyboard', + '/core/engines/hand', + '/core/engines/rend', + '/core/engines/tags', + '/core/engines/multiplayer', + '/core/engines/stck', + '/core/engines/notification', + '/core/utils/js-utils', + '/core/utils/hash-utils', + '/core/utils/network-utils', + '/core/utils/creature-utils', + '/core/utils/sprite-utils', + '/core/utils/vrid-utils', + ]) + .then( + ( + [ + bootstrap, + three, + input, + fs, + webvr, + biolumi, + resource, + cyborg, + keyboard, + hand, + rend, + tags, + multiplayer, + stck, + notification, + jsUtils, + hashUtils, + networkUtils, + creatureUtils, + spriteUtils, + vridUtils, + ] + ) => { + if (live) { + const { THREE, scene, camera } = three; + const { events } = jsUtils; + const { EventEmitter } = events; + const { murmur } = hashUtils; + const { AutoWs } = networkUtils; + const { Grabbable } = hand; + const { sfx } = resource; + + const walletRenderer = walletRender.makeRenderer({ creatureUtils }); + const localUserId = multiplayer.getId(); + + const pixelSize = 0.015; + const numPixels = 12; + const assetSize = pixelSize * numPixels; + const assetSizeVector = new THREE.Vector3( + assetSize, + assetSize, + assetSize + ); + + const zeroArray = new Float32Array(0); + const zeroArray2 = new Float32Array(0); + const zeroVector = new THREE.Vector3(); + const oneVector = new THREE.Vector3(1, 1, 1); + const assetOffsetVector = new THREE.Vector3(0, 0, -pixelSize / 2); + const zeroQuaternion = new THREE.Quaternion(); + const forwardQuaternion = new THREE.Quaternion().setFromUnitVectors( + new THREE.Vector3(0, 1, 0), + new THREE.Vector3(0, 0, -1) + ); + const assetsMaterial = new THREE.ShaderMaterial({ + uniforms: THREE.UniformsUtils.clone(ASSET_SHADER.uniforms), + vertexShader: ASSET_SHADER.vertexShader, + fragmentShader: ASSET_SHADER.fragmentShader, + // transparent: true, + // depthTest: false, + }); + const mainFontSpec = { + fonts: biolumi.getFonts(), + fontSize: 36, + lineHeight: 1.4, + fontWeight: biolumi.getFontWeight(), + fontStyle: biolumi.getFontStyle(), + }; + + const _addAsset = (id, type, value, n, physics, matrix) => { + const position = new THREE.Vector3( + matrix[0], + matrix[1], + matrix[2] + ); + const rotation = new THREE.Quaternion( + matrix[3], + matrix[4], + matrix[5], + matrix[6] + ); + const scale = new THREE.Vector3(matrix[7], matrix[8], matrix[9]); + + const assetInstance = assetsMesh.addAssetInstance( id, type, value, n, physics, - matrix, - } = assetSpec; - - _addAsset(id, type, value, n, physics, matrix); - } - } else if (type === 'addAsset') { - const { - id, - type, - value, - n, - physics, - matrix, - } = args; - - _addAsset(id, type, value, n, physics, matrix); - } else if (type === 'removeAsset') { - const { - id, - } = args; - - const assetInstance = assetsMesh.getAssetInstance(id); - _unbindAssetInstance(assetInstance); - - assetsMesh.removeAssetInstance(id); - } else if (type === 'setPhysics') { - const { - id, - physics, - } = args; - - const assetInstance = assetsMesh.getAssetInstance(id); - assetInstance.updatePhysics(physics); - } else { - console.warn('wallet got unknown message type:', JSON.stringify(type)); - } - }); - - const _isInBody = p => { - const vrMode = bootstrap.getVrMode(); - - if (vrMode === 'hmd') { - const {hmd} = webvr.getStatus(); - const {worldPosition: hmdPosition, worldRotation: hmdRotation} = hmd; - const hmdEuler = new THREE.Euler().setFromQuaternion(hmdRotation, camera.rotation.order); - hmdEuler.z = 0; - const hmdQuaternion = new THREE.Quaternion().setFromEuler(hmdEuler); - const bodyPosition = hmdPosition.clone() - .add( - new THREE.Vector3(0, -0.5, 0) - .applyQuaternion(hmdQuaternion) - ); - return p.distanceTo(bodyPosition) < 0.35; - } else if (vrMode === 'keyboard') { - const {hmd: {worldPosition, worldRotation}} = webvr.getStatus(); - const hmdEuler = new THREE.Euler().setFromQuaternion(worldRotation, camera.rotation.order); - hmdEuler.x = 0; - hmdEuler.z = 0; - const hmdQuaternion = new THREE.Quaternion().setFromEuler(hmdEuler); - const bodyPosition = worldPosition.clone() - .add( - new THREE.Vector3(0, -0.4, 0.2) - .applyQuaternion(hmdQuaternion) + position, + rotation, + scale, + zeroVector, + zeroQuaternion, + oneVector ); - return p.distanceTo(bodyPosition) < 0.35; - } - }; - - const _makeHoverState = () => ({ - worldAsset: null, - worldGrabAsset: null, - }); - const hoverStates = { - left: _makeHoverState(), - right: _makeHoverState(), - }; - - const _makeAssetsMesh = () => { - const mesh = new THREE.Object3D(); - - class AssetInstance extends Grabbable { - constructor( - id, - type, - value, - n, - physics, - position, - rotation, - scale, - localPosition, - localRotation, - localScale - ) { - super(n, position, rotation, scale, localPosition, localRotation, localScale); - - this.id = id; - this.type = type; - this.value = value; - this.physics = physics; - } + _bindAssetInstance(assetInstance); + _bindAssetInstancePhysics(assetInstance); + }; - emit(t, e) { - switch (t) { - case 'grab': { - const {userId, side} = e; + const connection = new AutoWs(_relativeWsUrl('archae/walletWs')); + connection.on('message', e => { + const { data } = e; + const m = JSON.parse(data); + const { type, args } = m; - hoverStates[side].worldGrabAsset = this; + if (type === 'init') { + const { assets } = args; + for (let i = 0; i < assets.length; i++) { + const assetSpec = assets[i]; + const { id, type, value, n, physics, matrix } = assetSpec; - super.emit(t, { - userId, - side, - item: this, - }); + _addAsset(id, type, value, n, physics, matrix); + } + } else if (type === 'addAsset') { + const { id, type, value, n, physics, matrix } = args; + + _addAsset(id, type, value, n, physics, matrix); + } else if (type === 'removeAsset') { + const { id } = args; + + const assetInstance = assetsMesh.getAssetInstance(id); + _unbindAssetInstance(assetInstance); + + assetsMesh.removeAssetInstance(id); + } else if (type === 'setPhysics') { + const { id, physics } = args; + + const assetInstance = assetsMesh.getAssetInstance(id); + assetInstance.updatePhysics(physics); + } else { + console.warn( + 'wallet got unknown message type:', + JSON.stringify(type) + ); + } + }); - assetsMesh.geometryNeedsUpdate = true; + const _isInBody = p => { + const vrMode = bootstrap.getVrMode(); + + if (vrMode === 'hmd') { + const { hmd } = webvr.getStatus(); + const { + worldPosition: hmdPosition, + worldRotation: hmdRotation, + } = hmd; + const hmdEuler = new THREE.Euler().setFromQuaternion( + hmdRotation, + camera.rotation.order + ); + hmdEuler.z = 0; + const hmdQuaternion = new THREE.Quaternion().setFromEuler( + hmdEuler + ); + const bodyPosition = hmdPosition + .clone() + .add( + new THREE.Vector3(0, -0.5, 0).applyQuaternion(hmdQuaternion) + ); + return p.distanceTo(bodyPosition) < 0.35; + } else if (vrMode === 'keyboard') { + const { + hmd: { worldPosition, worldRotation }, + } = webvr.getStatus(); + const hmdEuler = new THREE.Euler().setFromQuaternion( + worldRotation, + camera.rotation.order + ); + hmdEuler.x = 0; + hmdEuler.z = 0; + const hmdQuaternion = new THREE.Quaternion().setFromEuler( + hmdEuler + ); + const bodyPosition = worldPosition + .clone() + .add( + new THREE.Vector3(0, -0.4, 0.2).applyQuaternion( + hmdQuaternion + ) + ); + return p.distanceTo(bodyPosition) < 0.35; + } + }; + + const _makeHoverState = () => ({ + worldAsset: null, + worldGrabAsset: null, + }); + const hoverStates = { + left: _makeHoverState(), + right: _makeHoverState(), + }; + + const _makeAssetsMesh = () => { + const mesh = new THREE.Object3D(); + + class AssetInstance extends Grabbable { + constructor( + id, + type, + value, + n, + physics, + position, + rotation, + scale, + localPosition, + localRotation, + localScale + ) { + super( + n, + position, + rotation, + scale, + localPosition, + localRotation, + localScale + ); - break; + this.id = id; + this.type = type; + this.value = value; + this.physics = physics; } - case 'release': { - const {userId, side, live, stopImmediatePropagation} = e; - hoverStates[side].worldGrabAsset = null; + emit(t, e) { + switch (t) { + case 'grab': { + const { userId, side } = e; - const e2 = { - userId, - side, - item: this, - live, - stopImmediatePropagation, - }; - super.emit(t, e2); + hoverStates[side].worldGrabAsset = this; + + super.emit(t, { + userId, + side, + item: this, + }); + + assetsMesh.geometryNeedsUpdate = true; + + break; + } + case 'release': { + const { + userId, + side, + live, + stopImmediatePropagation, + } = e; + + hoverStates[side].worldGrabAsset = null; + + const e2 = { + userId, + side, + item: this, + live, + stopImmediatePropagation, + }; + super.emit(t, e2); + + if (e2.live) { + _checkGripup(e2); + } + + break; + } + case 'update': { + super.emit(t, e); + + break; + } + case 'destroy': { + const { userId, side } = e; + if (userId) { + hoverStates[side].worldGrabAsset = null; + } - if (e2.live) { - _checkGripup(e2); + super.emit(t, { + userId, + side, + item: this, + }); + + break; + } + default: { + super.emit(t, e); + + break; + } } + } - break; + show() { + this.emit('show'); } - case 'update': { - super.emit(t, e); - break; + hide() { + this.emit('hide'); } - case 'destroy': { - const {userId, side} = e; - if (userId) { - hoverStates[side].worldGrabAsset = null; - } - super.emit(t, { - userId, - side, - item: this, - }); + enablePhysics() { + this.physics = true; + this.emit('physics', true); + + connection.send( + JSON.stringify({ + method: 'setPhysics', + args: { + id: this.id, + physics: true, + }, + }) + ); + } - break; + disablePhysics() { + this.physics = false; + this.emit('physics', false); + + connection.send( + JSON.stringify({ + method: 'setPhysics', + args: { + id: this.id, + physics: false, + }, + }) + ); } - default: { - super.emit(t, e); - break; + updatePhysics(physics) { + this.physics = physics; + this.emit('physics', physics); } - } - } - show() { - this.emit('show'); - } + collide() { + this.emit('collide'); + } + } - hide() { - this.emit('hide'); - } + const assetInstances = []; + mesh.getAssetInstances = id => assetInstances; + mesh.getAssetInstance = id => + assetInstances.find(assetInstance => assetInstance.id === id); + mesh.addAssetInstance = ( + id, + type, + value, + n, + physics, + position, + rotation, + scale, + localPosition, + localRotation, + localScale + ) => { + const assetInstance = new AssetInstance( + id, + type, + value, + n, + physics, + position, + rotation, + scale, + localPosition, + localRotation, + localScale + ); + hand.addGrabbable(assetInstance); + assetInstances.push(assetInstance); + + const mesh = (() => { + let live = true; + + const geometry = (() => { + const spriteName = type === 'asset' ? value : 'FIL'; + const imageData = resource.getSpriteImageData(spriteName); + + spriteUtils + .requestSpriteGeometry(imageData, pixelSize) + .then(geometrySpec => { + if (live) { + const { + positions, + normals, + colors, + dys, + zeroDys, + } = geometrySpec; + + geometry.addAttribute( + 'position', + new THREE.BufferAttribute(positions, 3) + ); + // geometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3)); + geometry.addAttribute( + 'color', + new THREE.BufferAttribute(colors, 3) + ); + geometry.addAttribute( + 'dy', + new THREE.BufferAttribute( + geometry.getAttribute('dy').array === geometry.dys + ? dys + : zeroDys, + 2 + ) + ); + + geometry.dys = dys; + geometry.zeroDys = zeroDys; + + geometry.destroy = function() { + this.dispose(); + spriteUtils.releaseSpriteGeometry(geometrySpec); + }; + } + }) + .catch(err => { + if (live) { + console.warn(err); + } + }); + + const geometry = new THREE.BufferGeometry(); + const dys = zeroArray; // two of these so we can tell which is active + const zeroDys = zeroArray2; + geometry.addAttribute( + 'dy', + new THREE.BufferAttribute(dys, 2) + ); + geometry.dys = dys; + geometry.zeroDys = zeroDys; + geometry.boundingSphere = new THREE.Sphere(zeroVector, 1); + geometry.destroy = function() { + this.dispose(); + }; + return geometry; + })(); + const material = assetsMaterial; + const mesh = new THREE.Mesh(geometry, material); - enablePhysics() { - this.physics = true; - this.emit('physics', true); + mesh.destroy = () => { + geometry.destroy(); - connection.send(JSON.stringify({ - method: 'setPhysics', - args: { - id: this.id, - physics: true, - }, - })); - } + live = false; + }; - disablePhysics() { - this.physics = false; - this.emit('physics', false); + return mesh; + })(); + scene.add(mesh); + assetInstance.mesh = mesh; + + assetInstance.on('grab', () => { + const { geometry } = mesh; + const dyAttribute = geometry.getAttribute('dy'); + dyAttribute.array = geometry.zeroDys; + dyAttribute.needsUpdate = true; + }); + assetInstance.on('release', () => { + const { geometry } = mesh; + const dyAttribute = geometry.getAttribute('dy'); + dyAttribute.array = geometry.dys; + dyAttribute.needsUpdate = true; + }); + const localVector = new THREE.Vector3(); + const localQuaternion = new THREE.Quaternion(); + assetInstance.on('update', () => { + const { + position, + rotation, + scale, + localPosition, + localRotation, + localScale, + } = assetInstance; + + mesh.position.copy(position); + + localQuaternion.copy(rotation); + if (assetInstance.isGrabbed()) { + localQuaternion.multiply(forwardQuaternion); + } + mesh.quaternion.copy(localQuaternion).multiply(localRotation); + + mesh.scale.copy(scale).multiply(localScale); + + if (assetInstance.isGrabbed()) { + mesh.position.add( + localVector + .copy(localPosition) + .add(assetOffsetVector) + .applyQuaternion(localQuaternion) + ); + // mesh.scale.multiplyScalar(0.5); + } - connection.send(JSON.stringify({ - method: 'setPhysics', - args: { - id: this.id, - physics: false, - }, - })); - } + mesh.updateMatrixWorld(); + }); + assetInstance.on('show', () => { + mesh.visible = true; + }); + assetInstance.on('hide', () => { + mesh.visible = false; + }); - updatePhysics(physics) { - this.physics = physics; - this.emit('physics', physics); - } + return assetInstance; + }; + mesh.removeAssetInstance = id => { + const assetInstance = assetInstances.splice( + assetInstances.findIndex( + assetInstance => assetInstance.id === id + ), + 1 + )[0]; + hand.destroyGrabbable(assetInstance); - collide() { - this.emit('collide'); - } - } + const { mesh } = assetInstance; + scene.remove(mesh); + mesh.destroy(); + }; - const assetInstances = []; - mesh.getAssetInstances = id => assetInstances; - mesh.getAssetInstance = id => assetInstances.find(assetInstance => assetInstance.id === id); - mesh.addAssetInstance = (id, type, value, n, physics, position, rotation, scale, localPosition, localRotation, localScale) => { - const assetInstance = new AssetInstance(id, type, value, n, physics, position, rotation, scale, localPosition, localRotation, localScale); - hand.addGrabbable(assetInstance); - assetInstances.push(assetInstance); - - const mesh = (() => { - let live = true; - - const geometry = (() => { - const spriteName = type === 'asset' ? value : 'FIL'; - const imageData = resource.getSpriteImageData(spriteName); - - spriteUtils.requestSpriteGeometry(imageData, pixelSize) - .then(geometrySpec => { - if (live) { - const {positions, normals, colors, dys, zeroDys} = geometrySpec; - - geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3)); - // geometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3)); - geometry.addAttribute('color', new THREE.BufferAttribute(colors, 3)); - geometry.addAttribute('dy', new THREE.BufferAttribute(geometry.getAttribute('dy').array === geometry.dys ? dys : zeroDys, 2)); - - geometry.dys = dys; - geometry.zeroDys = zeroDys; - - geometry.destroy = function() { - this.dispose(); - spriteUtils.releaseSpriteGeometry(geometrySpec); - }; - } - }) - .catch(err => { - if (live) { - console.warn(err); - } - }); + return mesh; + }; + const assetsMesh = _makeAssetsMesh(); + scene.add(assetsMesh); + + const walletState = { + loading: true, + error: false, + inputText: '', + // address: null, + asset: null, + assets: [], + equipments: (() => { + const numEquipments = 4; + const result = _makeArray(numEquipments); + for (let i = 0; i < numEquipments; i++) { + result[i] = { + id: `equipment:${i}`, + asset: null, + }; + } + return result; + })(), + numTags: 0, + page: 0, + }; + const focusState = { + keyboardFocusState: null, + }; - const geometry = new THREE.BufferGeometry(); - const dys = zeroArray; // two of these so we can tell which is active - const zeroDys = zeroArray2; - geometry.addAttribute('dy', new THREE.BufferAttribute(dys, 2)); - geometry.dys = dys; - geometry.zeroDys = zeroDys; - geometry.boundingSphere = new THREE.Sphere( - zeroVector, - 1 - ); - geometry.destroy = function() { - this.dispose(); - }; - return geometry; - })(); - const material = assetsMaterial; - const mesh = new THREE.Mesh(geometry, material); + const menuMesh = (() => { + const worldUi = biolumi.makeUi({ + width: WIDTH, + height: HEIGHT, + }); + const mesh = worldUi.makePage( + ({ + wallet: { + loading, + error, + inputText, + asset, + assets, + equipments, + numTags, + page, + }, + focus: { keyboardFocusState }, + }) => { + const { type = '', inputValue = 0 } = + keyboardFocusState || {}; + const focus = type === 'wallet:search'; + + return { + type: 'html', + src: walletRenderer.getWalletPageSrc({ + loading, + error, + inputText, + inputValue, + asset, + assets, + equipments, + numTags, + page, + focus, + }), + x: 0, + y: 0, + w: WIDTH, + h: HEIGHT, + }; + }, + { + type: 'wallet', + state: { + wallet: walletState, + focus: focusState, + }, + worldWidth: WORLD_WIDTH, + worldHeight: WORLD_HEIGHT, + isEnabled: () => rend.isOpen(), + } + ); + mesh.visible = false; - mesh.destroy = () => { - geometry.destroy(); + const { page } = mesh; + rend.addPage(page); - live = false; - }; + cleanups.push(() => { + rend.removePage(page); + }); return mesh; })(); - scene.add(mesh); - assetInstance.mesh = mesh; - - assetInstance.on('grab', () => { - const {geometry} = mesh; - const dyAttribute = geometry.getAttribute('dy'); - dyAttribute.array = geometry.zeroDys; - dyAttribute.needsUpdate = true; - }); - assetInstance.on('release', () => { - const {geometry} = mesh; - const dyAttribute = geometry.getAttribute('dy'); - dyAttribute.array = geometry.dys; - dyAttribute.needsUpdate = true; - }); - const localVector = new THREE.Vector3(); - const localQuaternion = new THREE.Quaternion(); - assetInstance.on('update', () => { - const {position, rotation, scale, localPosition, localRotation, localScale} = assetInstance; + rend.registerMenuMesh('walletMesh', menuMesh); + menuMesh.updateMatrixWorld(); - mesh.position.copy(position); + const _updatePages = () => { + const { page } = menuMesh; + page.update(); + }; + _updatePages(); - localQuaternion.copy(rotation); - if (assetInstance.isGrabbed()) { - localQuaternion.multiply(forwardQuaternion); + const _resJson = res => { + if (res.status >= 200 && res.status < 300) { + return res.json(); + } else { + return Promise.reject({ + status: res.status, + stack: 'API returned invalid status code: ' + res.status, + }); } - mesh.quaternion.copy(localQuaternion) - .multiply(localRotation); - - mesh.scale.copy(scale) - .multiply(localScale); - - if (assetInstance.isGrabbed()) { - mesh.position - .add( - localVector.copy(localPosition) - .add(assetOffsetVector) - .applyQuaternion(localQuaternion) - ); - // mesh.scale.multiplyScalar(0.5); + }; + const _resBlob = res => { + if (res.status >= 200 && res.status < 300) { + return res.blob(); + } else { + return Promise.reject({ + status: res.status, + stack: 'API returned invalid status code: ' + res.status, + }); } - - mesh.updateMatrixWorld(); - }); - assetInstance.on('show', () => { - mesh.visible = true; - }); - assetInstance.on('hide', () => { - mesh.visible = false; - }); - - return assetInstance; - }; - mesh.removeAssetInstance = id => { - const assetInstance = assetInstances.splice(assetInstances.findIndex(assetInstance => assetInstance.id === id), 1)[0]; - hand.destroyGrabbable(assetInstance); - - const {mesh} = assetInstance; - scene.remove(mesh); - mesh.destroy(); - }; - - return mesh; - }; - const assetsMesh = _makeAssetsMesh(); - scene.add(assetsMesh); - - const walletState = { - loading: true, - error: false, - inputText: '', - // address: null, - asset: null, - assets: [], - equipments: (() => { - const numEquipments = 4; - const result = _makeArray(numEquipments); - for (let i = 0; i < numEquipments; i++) { - result[i] = { - id: `equipment:${i}`, - asset: null, - }; - } - return result; - })(), - numTags: 0, - page: 0, - }; - const focusState = { - keyboardFocusState: null, - }; - - const menuMesh = (() => { - const worldUi = biolumi.makeUi({ - width: WIDTH, - height: HEIGHT, - }); - const mesh = worldUi.makePage(({ - wallet: { - loading, - error, - inputText, - asset, - assets, - equipments, - numTags, - page, - }, - focus: { - keyboardFocusState, - }, - }) => { - const {type = '', inputValue = 0} = keyboardFocusState || {}; - const focus = type === 'wallet:search'; - - return { - type: 'html', - src: walletRenderer.getWalletPageSrc({loading, error, inputText, inputValue, asset, assets, equipments, numTags, page, focus}), - x: 0, - y: 0, - w: WIDTH, - h: HEIGHT, }; - }, { - type: 'wallet', - state: { - wallet: walletState, - focus: focusState, - }, - worldWidth: WORLD_WIDTH, - worldHeight: WORLD_HEIGHT, - isEnabled: () => rend.isOpen(), - }); - mesh.visible = false; - - const {page} = mesh; - rend.addPage(page); - - cleanups.push(() => { - rend.removePage(page); - }); - - return mesh; - })(); - rend.registerMenuMesh('walletMesh', menuMesh); - menuMesh.updateMatrixWorld(); - - const _updatePages = () => { - const {page} = menuMesh; - page.update(); - }; - _updatePages(); - - const _resJson = res => { - if (res.status >= 200 && res.status < 300) { - return res.json(); - } else { - return Promise.reject({ - status: res.status, - stack: 'API returned invalid status code: ' + res.status, - }); - } - }; - const _resBlob = res => { - if (res.status >= 200 && res.status < 300) { - return res.blob(); - } else { - return Promise.reject({ - status: res.status, - stack: 'API returned invalid status code: ' + res.status, - }); - } - }; - /* const _requestAssets = () => fetch(`${vridUrl}/id/api/assets`, { + /* const _requestAssets = () => fetch(`${vridUrl}/id/api/assets`, { credentials: 'include', }) .then(_resJson); @@ -681,74 +786,80 @@ class Wallet { }) .then(_resJson) .then(equipments => equipments !== null ? equipments : _makeArray(4)); */ - const _requestAssets = () => strg.get('assets') - .then(assets => assets || []); - const _requestEquipments = () => strg.get('equipments') - .then(equipments => equipments || _makeArray(4)) - .then(equipments => equipments.map((asset, i) => ({id: `equipment:${i}`, asset: asset}))); - const _refreshAssets = () => Promise.all([ - _requestAssets(), - _requestEquipments(), - ]) - .then(([ - assets, - equipments, - ]) => { - const {equipments: oldEquipments} = walletState; - - walletState.page = 0; - walletState.asset = null; - walletState.assets = assets.map(({asset, quantity}) => ({ - id: asset, - asset: asset, - quantity: quantity, - })); - const newEquipments = equipments.filter(equipmentSpec => - equipmentSpec.asset === null || walletState.assets.some(assetSpec => assetSpec.asset === equipmentSpec.asset) - ); - walletState.equipments = newEquipments; - walletState.numTags = assets.length; - - _rebindEquipments(oldEquipments, newEquipments); + const _requestAssets = () => + strg.get('assets').then(assets => assets || []); + const _requestEquipments = () => + strg + .get('equipments') + .then(equipments => equipments || _makeArray(4)) + .then(equipments => + equipments.map((asset, i) => ({ + id: `equipment:${i}`, + asset: asset, + })) + ); + const _refreshAssets = () => + Promise.all([_requestAssets(), _requestEquipments()]) + .then(([assets, equipments]) => { + const { equipments: oldEquipments } = walletState; + + walletState.page = 0; + walletState.asset = null; + walletState.assets = assets.map(({ asset, quantity }) => ({ + id: asset, + asset: asset, + quantity: quantity, + })); + const newEquipments = equipments.filter( + equipmentSpec => + equipmentSpec.asset === null || + walletState.assets.some( + assetSpec => assetSpec.asset === equipmentSpec.asset + ) + ); + walletState.equipments = newEquipments; + walletState.numTags = assets.length; - _updatePages(); - }) - .catch(err => { - console.warn(err); + _rebindEquipments(oldEquipments, newEquipments); - walletState.error = true; - }); - const _updateWallet = menuUtils.debounce(next => { - /* const {inputText} = walletState; + _updatePages(); + }) + .catch(err => { + console.warn(err); + + walletState.error = true; + }); + const _updateWallet = menuUtils.debounce(next => { + /* const {inputText} = walletState; const searchText = inputText.toLowerCase(); */ - _refreshAssets() - .then(() => { - walletState.loading = false; + _refreshAssets().then(() => { + walletState.loading = false; - next(); - }); + next(); + }); - const {numTags} = walletState; - walletState.loading = numTags === 0; - }); - const _ensureInitialLoaded = () => { - walletState.loading = true; - _updatePages(); + const { numTags } = walletState; + walletState.loading = numTags === 0; + }); + const _ensureInitialLoaded = () => { + walletState.loading = true; + _updatePages(); - return _refreshAssets() - .then(() => { - walletState.loading = false; + return _refreshAssets().then(() => { + walletState.loading = false; - _updatePages(); - }); - }; - _ensureInitialLoaded(); + _updatePages(); + }); + }; + _ensureInitialLoaded(); - const _saveEquipments = _debounce(next => { - const equipments = walletState.equipments.map(({asset}) => asset); + const _saveEquipments = _debounce(next => { + const equipments = walletState.equipments.map( + ({ asset }) => asset + ); - /* fetch(`${vridUrl}/id/api/cookie/equipment`, { + /* fetch(`${vridUrl}/id/api/cookie/equipment`, { method: 'POST', headers: (() => { const headers = new Headers(); @@ -760,661 +871,744 @@ class Wallet { }) .then(_resBlob); */ - strg.set('equipments', equipments) - .then(() => { - next(); - }) - .catch(err => { - console.warn(err); + strg + .set('equipments', equipments) + .then(() => { + next(); + }) + .catch(err => { + console.warn(err); - next(); + next(); + }); }); - }); - const _trigger = e => { - const {side} = e; - - const _downloadFile = () => { - return false; - const grabbedGrabbable = hand.getGrabbedGrabbable(side); - - if (grabbedGrabbable && grabbedGrabbable.type === 'file') { - fs.makeRemoteFile(grabbedGrabbable.value).download(); - return true; - } else { - return false; - } - }; - const _clickMenu = () => { - const hoverState = rend.getHoverState(side); - const {anchor} = hoverState; - const onclick = (anchor && anchor.onclick) || ''; - - let match; - if (onclick === 'wallet:focus') { - const {inputText} = walletState; - const {value, target: page} = hoverState; - const {layer: {measures}} = page; - const valuePx = value * (WIDTH - 250); - const {index, px} = biolumi.getTextPropertiesFromCoord(measures['wallet:search'], inputText, valuePx); - const {hmd: hmdStatus} = webvr.getStatus(); - const {worldPosition: hmdPosition, worldRotation: hmdRotation} = hmdStatus; - const keyboardFocusState = keyboard.focus({ - type: 'wallet:search', - position: hmdPosition, - rotation: hmdRotation, - inputText: inputText, - inputIndex: index, - inputValue: px, - page: page, - }); - focusState.keyboardFocusState = keyboardFocusState; + const _trigger = e => { + const { side } = e; + + const _downloadFile = () => { + return false; + const grabbedGrabbable = hand.getGrabbedGrabbable(side); + + if (grabbedGrabbable && grabbedGrabbable.type === 'file') { + fs.makeRemoteFile(grabbedGrabbable.value).download(); + return true; + } else { + return false; + } + }; + const _clickMenu = () => { + const hoverState = rend.getHoverState(side); + const { anchor } = hoverState; + const onclick = (anchor && anchor.onclick) || ''; + + let match; + if (onclick === 'wallet:focus') { + const { inputText } = walletState; + const { value, target: page } = hoverState; + const { layer: { measures } } = page; + const valuePx = value * (WIDTH - 250); + const { index, px } = biolumi.getTextPropertiesFromCoord( + measures['wallet:search'], + inputText, + valuePx + ); + const { hmd: hmdStatus } = webvr.getStatus(); + const { + worldPosition: hmdPosition, + worldRotation: hmdRotation, + } = hmdStatus; + const keyboardFocusState = keyboard.focus({ + type: 'wallet:search', + position: hmdPosition, + rotation: hmdRotation, + inputText: inputText, + inputIndex: index, + inputValue: px, + page: page, + }); + focusState.keyboardFocusState = keyboardFocusState; - keyboardFocusState.on('update', () => { - const {inputText: keyboardInputText} = keyboardFocusState; - const {inputText: walletInputText} = walletState; + keyboardFocusState.on('update', () => { + const { inputText: keyboardInputText } = keyboardFocusState; + const { inputText: walletInputText } = walletState; - if (keyboardInputText !== walletInputText) { - walletState.inputText = keyboardInputText; + if (keyboardInputText !== walletInputText) { + walletState.inputText = keyboardInputText; - _updateWallet(); - } + _updateWallet(); + } - _updatePages(); - }); - keyboardFocusState.on('blur', () => { - focusState.keyboardFocusState = null; + _updatePages(); + }); + keyboardFocusState.on('blur', () => { + focusState.keyboardFocusState = null; - _updatePages(); - }); + _updatePages(); + }); - _updatePages(); + _updatePages(); - return true; - } else if (match = onclick.match(/^asset:main:(.+)$/)) { - const asset = match[1]; + return true; + } else if ((match = onclick.match(/^asset:main:(.+)$/))) { + const asset = match[1]; - walletState.asset = walletState.asset !== asset ? asset : null; + walletState.asset = + walletState.asset !== asset ? asset : null; - _updatePages(); + _updatePages(); - return true; - } else if (match = onclick.match(/^asset:equip:(.+)$/)) { - const asset = match[1]; + return true; + } else if ((match = onclick.match(/^asset:equip:(.+)$/))) { + const asset = match[1]; - const {equipments: oldEquipments} = walletState; - const index = (() => { - for (let i = 0; i < oldEquipments.length; i++) { - const oldEquipment = oldEquipments[i]; - if (oldEquipment.asset === null) { - return i; - } - } - return oldEquipments.length - 1; - })(); - const newEquipments = _clone(oldEquipments); - newEquipments[index].asset = asset; + const { equipments: oldEquipments } = walletState; + const index = (() => { + for (let i = 0; i < oldEquipments.length; i++) { + const oldEquipment = oldEquipments[i]; + if (oldEquipment.asset === null) { + return i; + } + } + return oldEquipments.length - 1; + })(); + const newEquipments = _clone(oldEquipments); + newEquipments[index].asset = asset; - _rebindEquipments(oldEquipments, newEquipments); + _rebindEquipments(oldEquipments, newEquipments); - walletState.equipments = newEquipments; - _saveEquipments(); - _updatePages(); + walletState.equipments = newEquipments; + _saveEquipments(); + _updatePages(); - return true; - } else if (match = onclick.match(/^asset:unequip:equipment:([0-9]+)$/)) { - const index = parseInt(match[1], 10); + return true; + } else if ( + (match = onclick.match(/^asset:unequip:equipment:([0-9]+)$/)) + ) { + const index = parseInt(match[1], 10); - const {equipments: oldEquipments} = walletState; - const newEquipments = _clone(oldEquipments); - newEquipments[index].asset = null; + const { equipments: oldEquipments } = walletState; + const newEquipments = _clone(oldEquipments); + newEquipments[index].asset = null; - _rebindEquipments(oldEquipments, newEquipments); + _rebindEquipments(oldEquipments, newEquipments); - walletState.equipments = newEquipments; - _saveEquipments(); - _updatePages(); + walletState.equipments = newEquipments; + _saveEquipments(); + _updatePages(); - return true; - } else if (onclick === 'wallet:refresh') { - _updateWallet(); - _updatePages(); + return true; + } else if (onclick === 'wallet:refresh') { + _updateWallet(); + _updatePages(); - return true; - } else { - return false; - } - }; - const _clickMenuBackground = () => { - const hoverState = rend.getHoverState(side); - const {target} = hoverState; - - if (target && target.mesh && target.mesh.parent === menuMesh) { - return true; - } else { - return false; - } - }; + return true; + } else { + return false; + } + }; + const _clickMenuBackground = () => { + const hoverState = rend.getHoverState(side); + const { target } = hoverState; + + if (target && target.mesh && target.mesh.parent === menuMesh) { + return true; + } else { + return false; + } + }; - if (_downloadFile()) { - // nothing - } else { - if (_clickMenu()) { - sfx.digi_select.trigger(); + if (_downloadFile()) { + // nothing + } else { + if (_clickMenu()) { + sfx.digi_select.trigger(); - e.stopImmediatePropagation(); - } else if (_clickMenuBackground()) { - sfx.digi_plink.trigger(); + e.stopImmediatePropagation(); + } else if (_clickMenuBackground()) { + sfx.digi_plink.trigger(); - e.stopImmediatePropagation(); - } - } - }; - input.on('trigger', _trigger, { - priority: 1, - }); - - const _bindAssetInstancePhysics = assetInstance => { - let body = null; - const _addBody = ({velocity = new THREE.Vector3()} = {}) => { - body = stck.makeDynamicBoxBody(assetInstance.position, assetSizeVector, velocity); - body.on('update', () => { - assetInstance.setStateLocal(body.position, body.rotation, body.scale); - }); - body.on('collide', () => { - assetInstance.collide(); + e.stopImmediatePropagation(); + } + } + }; + input.on('trigger', _trigger, { + priority: 1, }); - }; - const _removeBody = () => { - stck.destroyBody(body); - body = null; - }; - assetInstance.on('release', e => { - const {userId} = e; + const _bindAssetInstancePhysics = assetInstance => { + let body = null; + const _addBody = ({ velocity = new THREE.Vector3() } = {}) => { + body = stck.makeDynamicBoxBody( + assetInstance.position, + assetSizeVector, + velocity + ); + body.on('update', () => { + assetInstance.setStateLocal( + body.position, + body.rotation, + body.scale + ); + }); + body.on('collide', () => { + assetInstance.collide(); + }); + }; + const _removeBody = () => { + stck.destroyBody(body); + body = null; + }; - if (userId === localUserId) { - const {side} = e; - const player = cyborg.getPlayer(); - const linearVelocity = player.getControllerLinearVelocity(side); + assetInstance.on('release', e => { + const { userId } = e; - _addBody({ - velocity: linearVelocity, + if (userId === localUserId) { + const { side } = e; + const player = cyborg.getPlayer(); + const linearVelocity = player.getControllerLinearVelocity( + side + ); + + _addBody({ + velocity: linearVelocity, + }); + + assetInstance.enablePhysics(); + } + }); + assetInstance.on('grab', e => { + const { userId } = e; + if (userId === localUserId) { + assetInstance.disablePhysics(); + } + }); + assetInstance.on('physics', enabled => { + if (enabled && !body) { + _addBody(); + } else if (!enabled && body) { + _removeBody(); + } + }); + assetInstance.on('destroy', () => { + if (body) { + _removeBody(); + } }); - assetInstance.enablePhysics(); - } - }); - assetInstance.on('grab', e => { - const {userId} = e; - if (userId === localUserId) { - assetInstance.disablePhysics(); - } - }); - assetInstance.on('physics', enabled => { - if (enabled && !body) { - _addBody(); - } else if (!enabled && body) { - _removeBody(); - } - }); - assetInstance.on('destroy', () => { - if (body) { - _removeBody(); - } - }); + if (assetInstance.physics) { + _addBody(); + } + }; - if (assetInstance.physics) { - _addBody(); - } - }; - - const _pullItem = (asset, side) => { - const id = _makeId(); - // const owner = bootstrap.getAddress(); - const itemSpec = { - type: 'asset', - id: id, - name: asset, - displayName: asset, - attributes: { - type: {value: 'asset'}, - value: {value: asset}, - position: {value: DEFAULT_MATRIX}, - // owner: {value: owner}, - owner: {value: null}, - bindOwner: {value: null}, - physics: {value: false}, - }, - metadata: {}, - }; - const assetInstance = walletApi.makeItem(itemSpec); - assetInstance.grab(side); - - const address = bootstrap.getAddress(); - const quantity = 1; - const {assets: oldAssets} = walletState; - _removeStrgAsset(asset, quantity) - .then(() => { - const {assets: newAssets} = walletState; - - if (oldAssets === newAssets) { - const newAsset = newAssets.find(assetSpec => assetSpec.asset === asset); - if (newAsset) { - if (--newAsset.quantity === 0) { - newAssets.splice(newAssets.indexOf(newAsset), 1); - - const {equipments} = walletState; - const removedEquipments = equipments.filter(equipmentSpec => equipmentSpec.asset === asset); - if (removedEquipments.length > 0) { - for (let i = 0; i < equipments.length; i++) { - const equipment = equipments[i]; - if (removedEquipments.includes(equipment)) { - equipment.asset = null; + const _pullItem = (asset, side) => { + const id = _makeId(); + // const owner = bootstrap.getAddress(); + const itemSpec = { + type: 'asset', + id: id, + name: asset, + displayName: asset, + attributes: { + type: { value: 'asset' }, + value: { value: asset }, + position: { value: DEFAULT_MATRIX }, + // owner: {value: owner}, + owner: { value: null }, + bindOwner: { value: null }, + physics: { value: false }, + }, + metadata: {}, + }; + const assetInstance = walletApi.makeItem(itemSpec); + assetInstance.grab(side); + + const address = bootstrap.getAddress(); + const quantity = 1; + const { assets: oldAssets } = walletState; + _removeStrgAsset(asset, quantity) + .then(() => { + const { assets: newAssets } = walletState; + + if (oldAssets === newAssets) { + const newAsset = newAssets.find( + assetSpec => assetSpec.asset === asset + ); + if (newAsset) { + if (--newAsset.quantity === 0) { + newAssets.splice(newAssets.indexOf(newAsset), 1); + + const { equipments } = walletState; + const removedEquipments = equipments.filter( + equipmentSpec => equipmentSpec.asset === asset + ); + if (removedEquipments.length > 0) { + for (let i = 0; i < equipments.length; i++) { + const equipment = equipments[i]; + if (removedEquipments.includes(equipment)) { + equipment.asset = null; + } + } + + _saveEquipments(); } } - _saveEquipments(); + _updatePages(); } } + }) + .catch(err => { + console.warn(err); + }); + + sfx.drop.trigger(); + const newNotification = notification.addNotification( + `Pulled out ${asset}.` + ); + setTimeout(() => { + notification.removeNotification(newNotification); + }, 3000); + }; + const _storeItem = assetInstance => { + walletApi.destroyItem(assetInstance); + + const { type } = assetInstance; + if (type === 'asset') { + const { value } = assetInstance; + const address = bootstrap.getAddress(); + const quantity = 1; + const { assets: oldAssets } = walletState; + _addStrgAsset(value, quantity) + .then(() => { + const { assets: newAssets } = walletState; + + if (oldAssets === newAssets) { + let newAsset = newAssets.find( + assetSpec => assetSpec.asset === value + ); + if (!newAsset) { + newAsset = { + id: value, + asset: value, + quantity: 0, + }; + newAssets.push(newAsset); + } + newAsset.quantity++; - _updatePages(); - } + _updatePages(); + } + }) + .catch(err => { + console.warn(err); + }); + + sfx.drop.trigger(); + const newNotification = notification.addNotification( + `Stored ${value}.` + ); + setTimeout(() => { + notification.removeNotification(newNotification); + }, 3000); + } + }; + const _checkGripdown = side => { + const hoverState = hoverStates[side]; + const { worldGrabAsset } = hoverState; + const { asset } = walletState; + const { gamepads } = webvr.getStatus(); + const gamepad = gamepads[side]; + const { worldPosition: position } = gamepad; + + if (!worldGrabAsset && asset && _isInBody(position)) { + _pullItem(asset, side); + + return true; + } else { + return false; } - }) - .catch(err => { - console.warn(err); + }; + const _checkGripup = e => { + const { item } = e; + const { position } = item; + + if (_isInBody(position)) { + _storeItem(item); + + e.stopImmediatePropagation(); + } + }; + const _gripdown = e => { + const { side } = e; + if (_checkGripdown(side)) { + e.stopImmediatePropagation(); + } + }; + input.on('gripdown', _gripdown, { + priority: -2, }); - sfx.drop.trigger(); - const newNotification = notification.addNotification(`Pulled out ${asset}.`); - setTimeout(() => { - notification.removeNotification(newNotification); - }, 3000); - }; - const _storeItem = assetInstance => { - walletApi.destroyItem(assetInstance); - - const {type} = assetInstance; - if (type === 'asset') { - const {value} = assetInstance; - const address = bootstrap.getAddress(); - const quantity = 1; - const {assets: oldAssets} = walletState; - _addStrgAsset(value, quantity) - .then(() => { - const {assets: newAssets} = walletState; - - if (oldAssets === newAssets) { - let newAsset = newAssets.find(assetSpec => assetSpec.asset === value); - if (!newAsset) { - newAsset = { - id: value, - asset: value, - quantity: 0, - }; - newAssets.push(newAsset); - } - newAsset.quantity++; + const _upload = ({ file, dropMatrix }) => { + const id = String(file.n); + const itemSpec = { + type: 'file', + id: id, + name: id, + displayName: id, + attributes: { + type: { value: 'file' }, + value: { value: id }, + position: { value: dropMatrix }, + owner: { value: null }, + bindOwner: { value: null }, + physics: { value: true }, + }, + metadata: {}, + }; + walletApi.makeItem(itemSpec); + }; + fs.on('upload', _upload); - _updatePages(); - } - }) - .catch(err => { - console.warn(err); - }); + const _update = () => { + assetsMaterial.uniforms.theta.value = + (Date.now() * ROTATE_SPEED * (Math.PI * 2)) % (Math.PI * 2); + }; + rend.on('update', _update); - sfx.drop.trigger(); - const newNotification = notification.addNotification(`Stored ${value}.`); - setTimeout(() => { - notification.removeNotification(newNotification); - }, 3000); - } - }; - const _checkGripdown = side => { - const hoverState = hoverStates[side]; - const {worldGrabAsset} = hoverState; - const {asset} = walletState; - const {gamepads} = webvr.getStatus(); - const gamepad = gamepads[side]; - const {worldPosition: position} = gamepad; - - if (!worldGrabAsset && asset && _isInBody(position)) { - _pullItem(asset, side); - - return true; - } else { - return false; - } - }; - const _checkGripup = e => { - const {item} = e; - const {position} = item; + const itemApis = {}; + const equipmentApis = {}; + const _bindAssetInstance = assetInstance => { + const { value } = assetInstance; + const itemEntry = itemApis[value]; - if (_isInBody(position)) { - _storeItem(item); + if (itemEntry) { + for (let i = 0; i < itemEntry.length; i++) { + const itemApi = itemEntry[i]; - e.stopImmediatePropagation(); - } - }; - const _gripdown = e => { - const {side} = e; - if (_checkGripdown(side)) { - e.stopImmediatePropagation(); - } - }; - input.on('gripdown', _gripdown, { - priority: -2, - }); - - const _upload = ({file, dropMatrix}) => { - const id = String(file.n); - const itemSpec = { - type: 'file', - id: id, - name: id, - displayName: id, - attributes: { - type: {value: 'file'}, - value: {value: id}, - position: {value: dropMatrix}, - owner: {value: null}, - bindOwner: {value: null}, - physics: {value: true}, - }, - metadata: {}, - }; - walletApi.makeItem(itemSpec); - }; - fs.on('upload', _upload); - - const _update = () => { - assetsMaterial.uniforms.theta.value = (Date.now() * ROTATE_SPEED * (Math.PI * 2) % (Math.PI * 2)); - }; - rend.on('update', _update); - - const itemApis = {}; - const equipmentApis = {}; - const _bindAssetInstance = assetInstance => { - const {value} = assetInstance; - const itemEntry = itemApis[value]; - - if (itemEntry) { - for (let i = 0; i < itemEntry.length; i++) { - const itemApi = itemEntry[i]; - - if (typeof itemApi.itemAddedCallback === 'function') { - itemApi.itemAddedCallback(assetInstance); + if (typeof itemApi.itemAddedCallback === 'function') { + itemApi.itemAddedCallback(assetInstance); + } + } } - } - } - }; - const _unbindAssetInstance = assetInstance => { - const {value} = assetInstance; - const itemEntry = itemApis[value]; + }; + const _unbindAssetInstance = assetInstance => { + const { value } = assetInstance; + const itemEntry = itemApis[value]; - if (itemEntry) { - for (let i = 0; i < itemEntry.length; i++) { - const itemApi = itemEntry[i]; + if (itemEntry) { + for (let i = 0; i < itemEntry.length; i++) { + const itemApi = itemEntry[i]; - if (typeof itemApi.itemRemovedCallback === 'function') { - itemApi.itemRemovedCallback(assetInstance); + if (typeof itemApi.itemRemovedCallback === 'function') { + itemApi.itemRemovedCallback(assetInstance); + } + } } - } - } - }; - const _bindItemApi = itemApi => { - if (typeof itemApi.asset === 'string' && typeof itemApi.itemAddedCallback === 'function') { - const {asset} = itemApi; - const boundAssetInstances = assetsMesh.getAssetInstances() - .filter(assetInstance => assetInstance.value === asset); - - for (let i = 0; i < boundAssetInstances.length; i++) { - const assetInstance = boundAssetInstances[i]; - itemApi.itemAddedCallback(assetInstance); - } - } - }; - const _unbindItemApi = itemApi => { - if (typeof itemApi.asset === 'string' && typeof itemApi.itemRemovedCallback === 'function') { - const {asset} = itemApi; - const boundAssetInstances = assetsMesh.getAssetInstances() - .filter(assetInstance => assetInstance.value === asset); - - for (let i = 0; i < boundAssetInstances.length; i++) { - const assetInstance = boundAssetInstances[i]; - itemApi.itemRemovedCallback(assetInstance); - } - } - }; - - const _rebindEquipments = (oldEquipments, newEquipments) => { - const removedEquipments = oldEquipments.filter(oldEquipment => - oldEquipment.asset !== null && !newEquipments.some(newEquipment => newEquipment.asset === oldEquipment.asset) - ); - for (let i = 0; i < removedEquipments.length; i++) { - const removedEquipment = removedEquipments[i]; - const {asset} = removedEquipment; - _unbindEquipment(asset); - } - const addedEquipments = newEquipments.filter(newEquipment => - newEquipment.asset !== null && !oldEquipments.some(oldEquipment => oldEquipment.asset === newEquipment.asset) - ); - for (let i = 0; i < addedEquipments.length; i++) { - const addedEquipment = addedEquipments[i]; - const {asset} = addedEquipment; - _bindEquipment(asset); - } - }; - const _bindEquipment = asset => { - const equipmentEntry = equipmentApis[asset]; - - if (equipmentEntry) { - for (let i = 0; i < equipmentEntry.length; i++) { - const equipmentApi = equipmentEntry[i]; + }; + const _bindItemApi = itemApi => { + if ( + typeof itemApi.asset === 'string' && + typeof itemApi.itemAddedCallback === 'function' + ) { + const { asset } = itemApi; + const boundAssetInstances = assetsMesh + .getAssetInstances() + .filter(assetInstance => assetInstance.value === asset); + + for (let i = 0; i < boundAssetInstances.length; i++) { + const assetInstance = boundAssetInstances[i]; + itemApi.itemAddedCallback(assetInstance); + } + } + }; + const _unbindItemApi = itemApi => { + if ( + typeof itemApi.asset === 'string' && + typeof itemApi.itemRemovedCallback === 'function' + ) { + const { asset } = itemApi; + const boundAssetInstances = assetsMesh + .getAssetInstances() + .filter(assetInstance => assetInstance.value === asset); + + for (let i = 0; i < boundAssetInstances.length; i++) { + const assetInstance = boundAssetInstances[i]; + itemApi.itemRemovedCallback(assetInstance); + } + } + }; - if (typeof equipmentApi.equipmentAddedCallback === 'function') { - equipmentApi.equipmentAddedCallback(); + const _rebindEquipments = (oldEquipments, newEquipments) => { + const removedEquipments = oldEquipments.filter( + oldEquipment => + oldEquipment.asset !== null && + !newEquipments.some( + newEquipment => newEquipment.asset === oldEquipment.asset + ) + ); + for (let i = 0; i < removedEquipments.length; i++) { + const removedEquipment = removedEquipments[i]; + const { asset } = removedEquipment; + _unbindEquipment(asset); } - } - } - }; - const _unbindEquipment = asset => { - const equipmentEntry = equipmentApis[asset]; + const addedEquipments = newEquipments.filter( + newEquipment => + newEquipment.asset !== null && + !oldEquipments.some( + oldEquipment => oldEquipment.asset === newEquipment.asset + ) + ); + for (let i = 0; i < addedEquipments.length; i++) { + const addedEquipment = addedEquipments[i]; + const { asset } = addedEquipment; + _bindEquipment(asset); + } + }; + const _bindEquipment = asset => { + const equipmentEntry = equipmentApis[asset]; - if (equipmentEntry) { - for (let i = 0; i < equipmentEntry.length; i++) { - const equipmentApi = equipmentEntry[i]; + if (equipmentEntry) { + for (let i = 0; i < equipmentEntry.length; i++) { + const equipmentApi = equipmentEntry[i]; - if (typeof equipmentApi.equipmentRemovedCallback === 'function') { - equipmentApi.equipmentRemovedCallback(); + if ( + typeof equipmentApi.equipmentAddedCallback === 'function' + ) { + equipmentApi.equipmentAddedCallback(); + } + } } - } - } - }; - const _bindEquipmentApi = equipmentApi => { - if (typeof equipmentApi.asset === 'string' && typeof equipmentApi.equipmentAddedCallback === 'function') { - const {asset} = equipmentApi; + }; + const _unbindEquipment = asset => { + const equipmentEntry = equipmentApis[asset]; - if (walletState.equipments.some(equipmentSpec => equipmentSpec.asset === asset)) { - equipmentApi.equipmentAddedCallback(); - } - } - }; - const _unbindEquipmentApi = equipmentApi => { - if (typeof equipmentApi.asset === 'string' && typeof equipmentApi.equipmentRemovedCallback === 'function') { - const {asset} = equipmentApi; + if (equipmentEntry) { + for (let i = 0; i < equipmentEntry.length; i++) { + const equipmentApi = equipmentEntry[i]; - if (walletState.equipments.some(equipmentSpec => equipmentSpec.asset === asset)) { - equipmentApi.equipmentRemovedCallback(); - } - } - }; + if ( + typeof equipmentApi.equipmentRemovedCallback === 'function' + ) { + equipmentApi.equipmentRemovedCallback(); + } + } + } + }; + const _bindEquipmentApi = equipmentApi => { + if ( + typeof equipmentApi.asset === 'string' && + typeof equipmentApi.equipmentAddedCallback === 'function' + ) { + const { asset } = equipmentApi; + + if ( + walletState.equipments.some( + equipmentSpec => equipmentSpec.asset === asset + ) + ) { + equipmentApi.equipmentAddedCallback(); + } + } + }; + const _unbindEquipmentApi = equipmentApi => { + if ( + typeof equipmentApi.asset === 'string' && + typeof equipmentApi.equipmentRemovedCallback === 'function' + ) { + const { asset } = equipmentApi; + + if ( + walletState.equipments.some( + equipmentSpec => equipmentSpec.asset === asset + ) + ) { + equipmentApi.equipmentRemovedCallback(); + } + } + }; - cleanups.push(() => { - input.removeListener('trigger', _trigger); - input.removeListener('gripdown', _gripdown); + cleanups.push(() => { + input.removeListener('trigger', _trigger); + input.removeListener('gripdown', _gripdown); - fs.removeListener('upload', _upload); + fs.removeListener('upload', _upload); - rend.removeListener('update', _update); - }); + rend.removeListener('update', _update); + }); - class WalletApi extends EventEmitter { - getAssetsMaterial() { - return assetsMaterial; - } + class WalletApi extends EventEmitter { + getAssetsMaterial() { + return assetsMaterial; + } - getAsset(id) { - return assetsMesh.getAssetInstance(id); - } + getAsset(id) { + return assetsMesh.getAssetInstance(id); + } - makeItem(itemSpec) { - const { - id, - attributes: { - type: {value: type}, - value: {value: value}, - position: {value: matrix}, - physics: {value: physics}, - }, - } = itemSpec; - const n = murmur(id); - const position = new THREE.Vector3(matrix[0], matrix[1], matrix[2]); - const rotation = new THREE.Quaternion(matrix[3], matrix[4], matrix[5], matrix[6]); - const scale = new THREE.Vector3(matrix[7], matrix[8], matrix[9]); - - const assetInstance = assetsMesh.addAssetInstance( - id, - type, - value, - n, - physics, - position, - rotation, - scale, - zeroVector, - zeroQuaternion, - oneVector - ); - _bindAssetInstance(assetInstance); - _bindAssetInstancePhysics(assetInstance); + makeItem(itemSpec) { + const { + id, + attributes: { + type: { value: type }, + value: { value: value }, + position: { value: matrix }, + physics: { value: physics }, + }, + } = itemSpec; + const n = murmur(id); + const position = new THREE.Vector3( + matrix[0], + matrix[1], + matrix[2] + ); + const rotation = new THREE.Quaternion( + matrix[3], + matrix[4], + matrix[5], + matrix[6] + ); + const scale = new THREE.Vector3( + matrix[7], + matrix[8], + matrix[9] + ); - connection.send(JSON.stringify({ - method: 'addAsset', - args: { - id, - type, - value, - n, - physics, - matrix, - }, - })); + const assetInstance = assetsMesh.addAssetInstance( + id, + type, + value, + n, + physics, + position, + rotation, + scale, + zeroVector, + zeroQuaternion, + oneVector + ); + _bindAssetInstance(assetInstance); + _bindAssetInstancePhysics(assetInstance); + + connection.send( + JSON.stringify({ + method: 'addAsset', + args: { + id, + type, + value, + n, + physics, + matrix, + }, + }) + ); - return assetInstance; - } + return assetInstance; + } - destroyItem(itemSpec) { - const {id} = itemSpec; - const assetInstance = assetsMesh.getAssetInstance(id); - _unbindAssetInstance(assetInstance); + destroyItem(itemSpec) { + const { id } = itemSpec; + const assetInstance = assetsMesh.getAssetInstance(id); + _unbindAssetInstance(assetInstance); - assetsMesh.removeAssetInstance(id); + assetsMesh.removeAssetInstance(id); - connection.send(JSON.stringify({ - method: 'removeAsset', - args: { - id, - }, - })); - } + connection.send( + JSON.stringify({ + method: 'removeAsset', + args: { + id, + }, + }) + ); + } - makeFile(fileSpec) { - const {data, matrix} = fileSpec; - const file = fs.makeRemoteFile(); - return file.write(data) - .then(() => { - return this.reifyFile({file, matrix}); - }); - } + makeFile(fileSpec) { + const { data, matrix } = fileSpec; + const file = fs.makeRemoteFile(); + return file.write(data).then(() => { + return this.reifyFile({ file, matrix }); + }); + } - reifyFile(fileSpec) { - const {file, matrix} = fileSpec; - const {n} = file; - const id = String(n); - const itemSpec = { - type: 'file', - id: id, - name: id, - displayName: id, - attributes: { - type: {value: 'file'}, - value: {value: n}, - position: {value: matrix}, - owner: {value: null}, - bindOwner: {value: null}, - physics: {value: true}, - }, - metadata: {}, - }; - return walletApi.makeItem(itemSpec); - } + reifyFile(fileSpec) { + const { file, matrix } = fileSpec; + const { n } = file; + const id = String(n); + const itemSpec = { + type: 'file', + id: id, + name: id, + displayName: id, + attributes: { + type: { value: 'file' }, + value: { value: n }, + position: { value: matrix }, + owner: { value: null }, + bindOwner: { value: null }, + physics: { value: true }, + }, + metadata: {}, + }; + return walletApi.makeItem(itemSpec); + } - registerItem(pluginInstance, itemApi) { - const {asset} = itemApi; + registerItem(pluginInstance, itemApi) { + const { asset } = itemApi; - let entry = itemApis[asset]; - if (!entry) { - entry = []; - itemApis[asset] = entry; - } - entry.push(itemApi); + let entry = itemApis[asset]; + if (!entry) { + entry = []; + itemApis[asset] = entry; + } + entry.push(itemApi); - _bindItemApi(itemApi); - } + _bindItemApi(itemApi); + } - unregisterItem(pluginInstance, itemApi) { - const {asset} = itemApi; + unregisterItem(pluginInstance, itemApi) { + const { asset } = itemApi; - const entry = itemApis[asset]; - entry.splice(entry.indexOf(itemApi), 1); - if (entry.length === 0) { - delete itemApis[asset]; - } + const entry = itemApis[asset]; + entry.splice(entry.indexOf(itemApi), 1); + if (entry.length === 0) { + delete itemApis[asset]; + } - _unbindItemApi(itemApi); - } + _unbindItemApi(itemApi); + } - registerEquipment(pluginInstance, equipmentApi) { - const {asset} = equipmentApi; + registerEquipment(pluginInstance, equipmentApi) { + const { asset } = equipmentApi; - let entry = equipmentApis[asset]; - if (!entry) { - entry = []; - equipmentApis[asset] = entry; - } - entry.push(equipmentApi); + let entry = equipmentApis[asset]; + if (!entry) { + entry = []; + equipmentApis[asset] = entry; + } + entry.push(equipmentApi); - _bindEquipmentApi(equipmentApi); - } + _bindEquipmentApi(equipmentApi); + } - unregisterEquipment(pluginInstance, equipmentApi) { - const {asset} = equipmentApi; + unregisterEquipment(pluginInstance, equipmentApi) { + const { asset } = equipmentApi; - const entry = equipmentApis[asset]; - entry.splice(entry.indexOf(equipmentApi), 1); - if (entry.length === 0) { - delete equipmentApis[asset]; + const entry = equipmentApis[asset]; + entry.splice(entry.indexOf(equipmentApi), 1); + if (entry.length === 0) { + delete equipmentApis[asset]; + } + + _unbindEquipmentApi(equipmentApi); + } } + const walletApi = new WalletApi(); - _unbindEquipmentApi(equipmentApi); + return walletApi; } } - const walletApi = new WalletApi(); - - return walletApi; - } - }); + ); } unmount() { @@ -1422,7 +1616,10 @@ class Wallet { } } -const _makeId = () => Math.random().toString(36).substring(7); +const _makeId = () => + Math.random() + .toString(36) + .substring(7); const _makeArray = n => { const result = Array(n); for (let i = 0; i < n; i++) { @@ -1432,7 +1629,13 @@ const _makeArray = n => { }; const _relativeWsUrl = s => { const l = window.location; - return ((l.protocol === 'https:') ? 'wss://' : 'ws://') + l.host + l.pathname + (!/\/$/.test(l.pathname) ? '/' : '') + s; + return ( + (l.protocol === 'https:' ? 'wss://' : 'ws://') + + l.host + + l.pathname + + (!/\/$/.test(l.pathname) ? '/' : '') + + s + ); }; const _clone = o => JSON.parse(JSON.stringify(o)); const _debounce = fn => { diff --git a/core/engines/wallet/server.js b/core/engines/wallet/server.js index dec08bfd5..0b771174e 100644 --- a/core/engines/wallet/server.js +++ b/core/engines/wallet/server.js @@ -1,14 +1,14 @@ const events = require('events'); -const {EventEmitter} = events; +const { EventEmitter } = events; class Wallet { -constructor(archae) { + constructor(archae) { this._archae = archae; } mount() { - const {_archae: archae} = this; - const {ws, wss} = archae.getCore(); + const { _archae: archae } = this; + const { ws, wss } = archae.getCore(); class AssetInstance { constructor(id, type, value, n, physics, matrix) { @@ -25,16 +25,18 @@ constructor(archae) { const connections = []; wss.on('connection', c => { - const {url} = c.upgradeReq; + const { url } = c.upgradeReq; if (url === '/archae/walletWs') { const _init = () => { - c.send(JSON.stringify({ - type: 'init', - args: { - assets: assetInstances, - } - })); + c.send( + JSON.stringify({ + type: 'init', + args: { + assets: assetInstances, + }, + }) + ); }; _init(); @@ -44,32 +46,53 @@ constructor(archae) { if (connection.readyState === ws.OPEN && connection !== c) { connection.send(m); } - }; + } }; c.on('message', s => { const m = _jsonParse(s); - const {method, args} = m; + const { method, args } = m; if (method === 'addAsset') { - const {id, type, value, n, physics, matrix} = args; - const assetInstance = new AssetInstance(id, type, value, n, physics, matrix); + const { id, type, value, n, physics, matrix } = args; + const assetInstance = new AssetInstance( + id, + type, + value, + n, + physics, + matrix + ); assetInstances.push(assetInstance); - _broadcast(JSON.stringify({type: 'addAsset', args: {id, type, value, n, physics, matrix}})); + _broadcast( + JSON.stringify({ + type: 'addAsset', + args: { id, type, value, n, physics, matrix }, + }) + ); } else if (method === 'removeAsset') { - const {id} = args; - assetInstances.splice(assetInstances.findIndex(assetInstance => assetInstance.id === id), 1); - - _broadcast(JSON.stringify({type: 'removeAsset', args: {id}})); + const { id } = args; + assetInstances.splice( + assetInstances.findIndex( + assetInstance => assetInstance.id === id + ), + 1 + ); + + _broadcast(JSON.stringify({ type: 'removeAsset', args: { id } })); } else if (method === 'setPhysics') { - const {id, physics} = args; - const assetInstance = assetInstances.find(assetInstance => assetInstance.id === id); + const { id, physics } = args; + const assetInstance = assetInstances.find( + assetInstance => assetInstance.id === id + ); if (assetInstance) { assetInstance.physics = physics; - _broadcast(JSON.stringify({type: 'setPhysics', args: {id, physics}})); + _broadcast( + JSON.stringify({ type: 'setPhysics', args: { id, physics } }) + ); } } else { console.warn('no such method:' + JSON.stringify(method)); diff --git a/core/engines/webvr/client.js b/core/engines/webvr/client.js index 581fe4388..281fcab74 100644 --- a/core/engines/webvr/client.js +++ b/core/engines/webvr/client.js @@ -11,7 +11,10 @@ class VRFrameDataFake { } } class VRPoseFake { - constructor(position = new Float32Array(3), orientation = new Float32Array(4)) { + constructor( + position = new Float32Array(3), + orientation = new Float32Array(4) + ) { this.position = position; this.orientation = orientation; } @@ -38,11 +41,19 @@ const DEFAULT_ASPECT_RATIO = DEFAULT_WIDTH / DEFAULT_HEIGHT; const CONTROLLER_DEFAULT_OFFSETS = [0.2, -0.1, -0.2]; const DEFAULT_GAMEPAD_POSES = [ { - position: [-CONTROLLER_DEFAULT_OFFSETS[0], CONTROLLER_DEFAULT_OFFSETS[1], CONTROLLER_DEFAULT_OFFSETS[2]], + position: [ + -CONTROLLER_DEFAULT_OFFSETS[0], + CONTROLLER_DEFAULT_OFFSETS[1], + CONTROLLER_DEFAULT_OFFSETS[2], + ], orientation: [0, 0, 0, 1], }, { - position: [CONTROLLER_DEFAULT_OFFSETS[0], CONTROLLER_DEFAULT_OFFSETS[1], CONTROLLER_DEFAULT_OFFSETS[2]], + position: [ + CONTROLLER_DEFAULT_OFFSETS[0], + CONTROLLER_DEFAULT_OFFSETS[1], + CONTROLLER_DEFAULT_OFFSETS[2], + ], orientation: [0, 0, 0, 1], }, ]; @@ -62,7 +73,15 @@ const BUTTONS = { const SIDES = ['left', 'right']; class EventSpec { - constructor(buttonName, rootName, downName, upName, touchName, touchdownName, touchupName) { + constructor( + buttonName, + rootName, + downName, + upName, + touchName, + touchdownName, + touchupName + ) { this.buttonName = buttonName; this.rootName = rootName; this.downName = downName; @@ -83,10 +102,42 @@ const VR_EVENTS = { }; const EVENT_SPECS = [ - new EventSpec('trigger', 'trigger', 'triggerdown', 'triggerup', 'triggertouch', 'triggertouchdown', 'triggertouchup'), - new EventSpec('pad', 'pad', 'paddown', 'padup', 'padtouch', 'padtouchdown', 'padtouchup'), - new EventSpec('grip', 'grip', 'gripdown', 'gripup', 'griptouch', 'griptouchdown', 'griptouchup'), - new EventSpec('menu', 'menu', 'menudown', 'menuup', 'menutouch', 'menutouchdown', 'menutouchup'), + new EventSpec( + 'trigger', + 'trigger', + 'triggerdown', + 'triggerup', + 'triggertouch', + 'triggertouchdown', + 'triggertouchup' + ), + new EventSpec( + 'pad', + 'pad', + 'paddown', + 'padup', + 'padtouch', + 'padtouchdown', + 'padtouchup' + ), + new EventSpec( + 'grip', + 'grip', + 'gripdown', + 'gripup', + 'griptouch', + 'griptouchdown', + 'griptouchup' + ), + new EventSpec( + 'menu', + 'menu', + 'menudown', + 'menuup', + 'menutouch', + 'menutouchdown', + 'menutouchup' + ), ]; class WebVR { @@ -95,7 +146,7 @@ class WebVR { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; let live = true; this._cleanup = () => { @@ -118,19 +169,11 @@ class WebVR { '/core/utils/js-utils', ]), _getVRDisplays(), - ]).then(([ - [ - bootstrap, - input, - three, - jsUtils, - ], - displays, - ]) => { + ]).then(([[bootstrap, input, three, jsUtils], displays]) => { if (live) { - const {THREE, scene, camera, renderer} = three; - const {domElement} = renderer; - const {events} = jsUtils; + const { THREE, scene, camera, renderer } = three; + const { domElement } = renderer; + const { events } = jsUtils; const EventEmitter = events; const _decomposeMatrix = matrix => { @@ -138,9 +181,10 @@ class WebVR { const rotation = new THREE.Quaternion(); const scale = new THREE.Vector3(); matrix.decompose(position, rotation, scale); - return {position, rotation, scale}; + return { position, rotation, scale }; }; - const _decomposeObjectMatrixWorld = object => _decomposeMatrix(object.matrixWorld); + const _decomposeObjectMatrixWorld = object => + _decomposeMatrix(object.matrixWorld); const zeroVector = new THREE.Vector3(); const zeroQuaternion = new THREE.Quaternion(); @@ -161,10 +205,16 @@ class WebVR { })[0]; const _getPropertiesFromPose = pose => { - const position = (pose && pose.position !== null) ? new THREE.Vector3().fromArray(pose.position) : zeroVector; - const rotation = (pose && pose.orientation !== null) ? new THREE.Quaternion().fromArray(pose.orientation) : zeroQuaternion; + const position = + pose && pose.position !== null + ? new THREE.Vector3().fromArray(pose.position) + : zeroVector; + const rotation = + pose && pose.orientation !== null + ? new THREE.Quaternion().fromArray(pose.orientation) + : zeroQuaternion; const scale = oneVector; - return {position, rotation, scale}; + return { position, rotation, scale }; }; const _getPropertiesFromPoseTo = (pose, position, rotation, scale) => { if (pose && pose.position !== null) { @@ -188,7 +238,14 @@ class WebVR { } class HmdStatus { - constructor(position, rotation, scale, worldPosition, worldRotation, worldScale) { + constructor( + position, + rotation, + scale, + worldPosition, + worldRotation, + worldScale + ) { this.position = position; this.rotation = rotation; this.scale = scale; @@ -204,7 +261,16 @@ class WebVR { } } class GamepadStatus { - constructor(position, rotation, scale, worldPosition, worldRotation, worldScale, buttons, axes) { + constructor( + position, + rotation, + scale, + worldPosition, + worldRotation, + worldScale, + buttons, + axes + ) { this.position = position; this.rotation = rotation; this.scale = scale; @@ -259,25 +325,33 @@ class WebVR { } } - const _makeDefaultHmdStatus = () => (() => { - const {position: worldPosition, rotation: worldRotation, scale: worldScale} = _decomposeObjectMatrixWorld(camera); - - return new HmdStatus( - camera.position.clone(), - camera.quaternion.clone(), - camera.scale.clone(), - worldPosition, - worldRotation, - worldScale - ); - })(); + const _makeDefaultHmdStatus = () => + (() => { + const { + position: worldPosition, + rotation: worldRotation, + scale: worldScale, + } = _decomposeObjectMatrixWorld(camera); + + return new HmdStatus( + camera.position.clone(), + camera.quaternion.clone(), + camera.scale.clone(), + worldPosition, + worldRotation, + worldScale + ); + })(); const _makeDefaultGamepadStatus = index => { - const {position, rotation, scale} = _getPropertiesFromPose(DEFAULT_GAMEPAD_POSES[index]); + const { position, rotation, scale } = _getPropertiesFromPose( + DEFAULT_GAMEPAD_POSES[index] + ); const worldPosition = position.clone(); const worldRotation = rotation.clone(); const worldScale = scale.clone(); - const _makeDefaultButtonStatus = () => new GamepadButton(false, false, 0); + const _makeDefaultButtonStatus = () => + new GamepadButton(false, false, 0); const buttons = new GamepadButtons( _makeDefaultButtonStatus(), _makeDefaultButtonStatus(), @@ -370,7 +444,10 @@ class WebVR { this.isOpen = false; }); - const frameData = (!display || (display instanceof FakeVRDisplay)) ? new VRFrameDataFake() : new VRFrameData(); + const frameData = + !display || display instanceof FakeVRDisplay + ? new VRFrameDataFake() + : new VRFrameData(); this._frameData = frameData; if (display && stereoscopic) { @@ -390,9 +467,13 @@ class WebVR { const displayStageMatrix = new THREE.Matrix4(); if (display && display.stageParameters) { - displayStageMatrix.fromArray(display.stageParameters.sittingToStandingTransform); + displayStageMatrix.fromArray( + display.stageParameters.sittingToStandingTransform + ); } - this.setStageMatrix(displayStageMatrix.premultiply(this.getSpawnMatrix())); + this.setStageMatrix( + displayStageMatrix.premultiply(this.getSpawnMatrix()) + ); this.updateStatus(); cleanups.push(() => { @@ -404,19 +485,30 @@ class WebVR { if (!display) { // const originalStageMatrix = stageMatrix.clone(); - const {lookMatrix} = this; - const {position, rotation, scale} = _decomposeMatrix(lookMatrix); - const euler = new THREE.Euler().setFromQuaternion(rotation, camera.rotation.order); + const { lookMatrix } = this; + const { position, rotation, scale } = _decomposeMatrix( + lookMatrix + ); + const euler = new THREE.Euler().setFromQuaternion( + rotation, + camera.rotation.order + ); const mousemove = e => { - const xFactor = -0.5 + (e.event.clientX / window.innerWidth); - const yFactor = -0.5 + (e.event.clientY / window.innerHeight); + const xFactor = + -0.5 + e.event.clientX / window.innerWidth; + const yFactor = + -0.5 + e.event.clientY / window.innerHeight; localEuler.copy(euler); localEuler.y -= xFactor * (Math.PI * 0.1); localEuler.x -= yFactor * (Math.PI * 0.1); - lookMatrix.compose(position, localQuaternion.setFromEuler(localEuler), scale); + lookMatrix.compose( + position, + localQuaternion.setFromEuler(localEuler), + scale + ); }; input.on('mousemove', mousemove); @@ -469,14 +561,16 @@ class WebVR { }; accept(api); } else { - const err = new Error('webvr engine is already render looping. destroy() the old render first.'); + const err = new Error( + 'webvr engine is already render looping. destroy() the old render first.' + ); reject(err); } }); result.destroy = _destroy; return result; - }; + } requestEnterVR({ stereoscopic = true, @@ -489,16 +583,17 @@ class WebVR { onExit = () => {}, }) { // NOTE: these promises *need* to be synchronous because the WebVR api can only be triggered in the same tick as a user action - const _checkNotOpening = () => new SynchronousPromise((accept, reject) => { - const {isOpening} = this; + const _checkNotOpening = () => + new SynchronousPromise((accept, reject) => { + const { isOpening } = this; - if (!isOpening) { - accept(); - } else { - const err = new Error('webvr engine is already entering vr'); - reject(err); - } - }); + if (!isOpening) { + accept(); + } else { + const err = new Error('webvr engine is already entering vr'); + reject(err); + } + }); const _startOpening = () => { this.isOpening = true; @@ -541,85 +636,100 @@ class WebVR { return display.requestPresent([ { source: domElement, - } + }, ]); } else { return Promise.resolve(); } }; - return _requestPresent() - .then(() => new Promise((accept, reject) => { - const _listen = () => { - if (display instanceof FakeVRDisplay) { - const pointerlockchange = () => { - const {isPresenting} = display; - if (!isPresenting) { - _destroy(); - - onExit(); - } - }; - document.addEventListener('pointerlockchange', pointerlockchange); - - cleanups.push(() => { - display.destroy(); - - document.removeEventListener('pointerlockchange', pointerlockchange); + return _requestPresent().then( + () => + new Promise((accept, reject) => { + const _listen = () => { + if (display instanceof FakeVRDisplay) { + const pointerlockchange = () => { + const { isPresenting } = display; + if (!isPresenting) { + _destroy(); + + onExit(); + } + }; + document.addEventListener( + 'pointerlockchange', + pointerlockchange + ); + + cleanups.push(() => { + display.destroy(); + + document.removeEventListener( + 'pointerlockchange', + pointerlockchange + ); + }); + } else { + const vrdisplaypresentchange = () => { + const { isPresenting } = display; + if (!isPresenting) { + _destroy(); + + onExit(); + } + }; + window.addEventListener( + 'vrdisplaypresentchange', + vrdisplaypresentchange + ); + const keydown = e => { + if (e.keyCode === 27) { + // esc + display.exitPresent(); + } + }; + document.addEventListener('keydown', keydown); + + cleanups.push(() => { + window.removeEventListener( + 'vrdisplaypresentchange', + vrdisplaypresentchange + ); + document.removeEventListener('keydown', keydown); + }); + } + }; + const _requestRenderLoop = () => { + const renderLoopPromise = this.requestRenderLoop({ + display, + stereoscopic, + update, + // updateEye, + updateStart, + updateEnd, + // renderStart, + // renderEnd, }); - } else { - const vrdisplaypresentchange = () => { - const {isPresenting} = display; - if (!isPresenting) { - _destroy(); - - onExit(); - } - }; - window.addEventListener('vrdisplaypresentchange', vrdisplaypresentchange); - const keydown = e => { - if (e.keyCode === 27) { // esc - display.exitPresent(); - } - }; - document.addEventListener('keydown', keydown); cleanups.push(() => { - window.removeEventListener('vrdisplaypresentchange', vrdisplaypresentchange); - document.removeEventListener('keydown', keydown); + renderLoopPromise.destroy(); }); - } - }; - const _requestRenderLoop = () => { - const renderLoopPromise = this.requestRenderLoop({ - display, - stereoscopic, - update, - // updateEye, - updateStart, - updateEnd, - // renderStart, - // renderEnd, - }); - - cleanups.push(() => { - renderLoopPromise.destroy(); - }); - - return renderLoopPromise; - }; - _listen(); - - return _requestRenderLoop() - .then(_stopOpening) - .then(() => { - return { - destroy: _destroy, - }; - }) - .catch(_handleError); - })); + return renderLoopPromise; + }; + + _listen(); + + return _requestRenderLoop() + .then(_stopOpening) + .then(() => { + return { + destroy: _destroy, + }; + }) + .catch(_handleError); + }) + ); }) .catch(_handleError); result.destroy = _destroy; @@ -629,12 +739,12 @@ class WebVR { updateStatus() { const _setHmdStatus = () => { - const {display} = this; + const { display } = this; if (display && display.updatePose) { display.updatePose(); } - const {_frameData: frameData} = this; + const { _frameData: frameData } = this; if (display) { display.getFrameData(frameData); } @@ -644,7 +754,12 @@ class WebVR { this.status.hmd.rotation, this.status.hmd.scale ); - localMatrix.compose(this.status.hmd.position, this.status.hmd.rotation, this.status.hmd.scale) + localMatrix + .compose( + this.status.hmd.position, + this.status.hmd.rotation, + this.status.hmd.scale + ) .premultiply(this.stageMatrix) .decompose( this.status.hmd.worldPosition, @@ -684,7 +799,11 @@ class WebVR { gamepad.updateButtons(); } - const {pose, buttons: [pad, trigger, grip, menu], axes: [x, y]} = gamepad; + const { + pose, + buttons: [pad, trigger, grip, menu], + axes: [x, y], + } = gamepad; const gamepadStatus = this.status.gamepads[side]; _getPropertiesFromPoseTo( @@ -693,7 +812,12 @@ class WebVR { gamepadStatus.rotation, gamepadStatus.scale ); - localMatrix.compose(gamepadStatus.position, gamepadStatus.rotation, gamepadStatus.scale) + localMatrix + .compose( + gamepadStatus.position, + gamepadStatus.rotation, + gamepadStatus.scale + ) .premultiply(this.stageMatrix) .decompose( gamepadStatus.worldPosition, @@ -701,11 +825,19 @@ class WebVR { gamepadStatus.worldScale ); - const buttons = {pad, trigger, grip, menu}; + const buttons = { pad, trigger, grip, menu }; const axes = [x, y]; for (let e = 0; e < EVENT_SPECS.length; e++) { const eventSpec = EVENT_SPECS[e]; - const {buttonName, rootName, downName, upName, touchName, touchdownName, touchupName} = eventSpec; + const { + buttonName, + rootName, + downName, + upName, + touchName, + touchdownName, + touchupName, + } = eventSpec; const oldPressed = gamepadStatus.buttons[buttonName].pressed; const newPressed = buttons[buttonName].pressed; @@ -740,9 +872,14 @@ class WebVR { const _setGamepadAxes = (src, dst) => { dst[0] = src[0]; dst[1] = src[1]; - } + }; function _isGamepadAvailable(gamepad) { - return Boolean(gamepad) && Boolean(gamepad.pose) && gamepad.pose.position !== null && gamepad.pose.orientation !== null; + return ( + Boolean(gamepad) && + Boolean(gamepad.pose) && + gamepad.pose.position !== null && + gamepad.pose.orientation !== null + ); } _setHmdStatus(); @@ -774,7 +911,10 @@ class WebVR { } getSpawnTransform() { - return new THREE.Matrix4().fromArray(this.display.stageParameters.sittingToStandingTransform) + return new THREE.Matrix4() + .fromArray( + this.display.stageParameters.sittingToStandingTransform + ) .premultiply(this.spawnMatrix); } @@ -791,7 +931,7 @@ class WebVR { } getMode() { - const {display} = this; + const { display } = this; if (display instanceof FakeVRDisplay) { return display.getMode(); } else { @@ -800,7 +940,7 @@ class WebVR { } getKeys() { - const {display} = this; + const { display } = this; if (display instanceof FakeVRDisplay) { return display.getKeys(); } else { @@ -809,11 +949,13 @@ class WebVR { } getSittingToStandingTransform() { - const {display} = this; + const { display } = this; const result = new THREE.Matrix4(); if (display) { - result.fromArray(display.stageParameters.sittingToStandingTransform); + result.fromArray( + display.stageParameters.sittingToStandingTransform + ); } return result; } @@ -822,7 +964,7 @@ class WebVR { let left = null; let right = null; - const {display} = this; + const { display } = this; if (display.getGamepads) { const gamepads = display.getGamepads(); @@ -835,7 +977,7 @@ class WebVR { const gamepad = gamepads[i]; if (gamepad) { - const {hand} = gamepad; + const { hand } = gamepad; if (hand === 'left') { left = gamepad; @@ -847,7 +989,7 @@ class WebVR { } const _vibrate = gamepad => { - const {hapticActuators} = gamepad; + const { hapticActuators } = gamepad; if (hapticActuators.length > 0) { hapticActuators[0].pulse(value, time); @@ -874,7 +1016,12 @@ class WebVR { this.scale = new THREE.Vector3(1, 1, 1); this.matrix = new THREE.Matrix4(); - this.rotationOffset = new THREE.Euler(0, 0, 0, camera.rotation.order); + this.rotationOffset = new THREE.Euler( + 0, + 0, + 0, + camera.rotation.order + ); this.poseNeedsUpdate = false; this.stageParameters = { @@ -883,7 +1030,8 @@ class WebVR { new THREE.Vector3(0, DEFAULT_USER_HEIGHT, 0), new THREE.Quaternion(), new THREE.Vector3(1, 1, 1) - ).toArray(), + ) + .toArray(), }; const keys = { @@ -915,7 +1063,10 @@ class WebVR { keys.axis = false; }; - const gamepads = [new FakeVRGamepad(this, 0), new FakeVRGamepad(this, 1)]; + const gamepads = [ + new FakeVRGamepad(this, 0), + new FakeVRGamepad(this, 1), + ]; this.gamepads = gamepads; this.mode = 'right'; @@ -1016,11 +1167,27 @@ class WebVR { }; const mousemove = e => { if (this.isPresenting) { - const _handleGamepad = () => e.event.ctrlKey || e.event.altKey || this.keys.axis; // handled by the fake gamepad + const _handleGamepad = () => + e.event.ctrlKey || e.event.altKey || this.keys.axis; // handled by the fake gamepad const _handleDisplay = () => { - if (Math.abs(e.event.movementX) < 300 && Math.abs(e.event.movementY) < 300) { // work around fast movement glitching - this.rotationOffset.x = Math.max(Math.min(this.rotationOffset.x - e.event.movementY * ROTATION_SPEED, Math.PI / 2), -Math.PI / 2); - this.rotationOffset.y = mod(this.rotationOffset.y - e.event.movementX * ROTATION_SPEED, Math.PI * 2); + if ( + Math.abs(e.event.movementX) < 300 && + Math.abs(e.event.movementY) < 300 + ) { + // work around fast movement glitching + this.rotationOffset.x = Math.max( + Math.min( + this.rotationOffset.x - + e.event.movementY * ROTATION_SPEED, + Math.PI / 2 + ), + -Math.PI / 2 + ); + this.rotationOffset.y = mod( + this.rotationOffset.y - + e.event.movementX * ROTATION_SPEED, + Math.PI * 2 + ); this.poseNeedsUpdate = true; this.gamepads[0].poseNeedsUpdate = true; @@ -1036,7 +1203,7 @@ class WebVR { } }; const pointerlockchange = e => { - const {isPresenting: wasPresenting} = this; + const { isPresenting: wasPresenting } = this; const isPresenting = document.pointerLockElement !== null; this.isPresenting = isPresenting; @@ -1070,8 +1237,14 @@ class WebVR { input.removeListener('mousedown', mousedown); input.removeListener('mouseup', mouseup); input.removeListener('mousemove', mousemove); - document.removeEventListener('pointerlockchange', pointerlockchange); - document.removeEventListener('pointerlockerror', pointerlockerror); + document.removeEventListener( + 'pointerlockchange', + pointerlockchange + ); + document.removeEventListener( + 'pointerlockerror', + pointerlockerror + ); }; } @@ -1128,7 +1301,7 @@ class WebVR { getEyeParameters(side) { return { - offset: [(DEFAULT_USER_IPD / 2) * (side === 'left' ? -1 : 1), 0, 0], + offset: [DEFAULT_USER_IPD / 2 * (side === 'left' ? -1 : 1), 0, 0], fieldOfView: { upDegrees: DEFAULT_USER_FOV / 2, rightDegrees: DEFAULT_USER_FOV / 2, @@ -1158,7 +1331,9 @@ class WebVR { let matrixNeedsUpdate = false; localVector.set(0, 0, 0); - const speed = this.keys.shift ? POSITION_SPEED_FAST : POSITION_SPEED; + const speed = this.keys.shift + ? POSITION_SPEED_FAST + : POSITION_SPEED; let moved = false; if (this.keys.up) { localVector.z -= speed; @@ -1267,17 +1442,31 @@ class WebVR { const mousemove = e => { if (this.displayIsInControllerMode()) { const _isReversed = () => { - const {_parent: parent, _index: index} = this; + const { _parent: parent, _index: index } = this; const mode = parent.getMode(); return mode === 'center' && index === 1; }; if (e.event.ctrlKey) { - this.move(-e.event.movementX, -e.event.movementY, 0, _isReversed()); + this.move( + -e.event.movementX, + -e.event.movementY, + 0, + _isReversed() + ); } else if (e.altKey) { - this.move(-e.event.movementX, 0, -e.event.movementY, _isReversed()); + this.move( + -e.event.movementX, + 0, + -e.event.movementY, + _isReversed() + ); } else if (this._parent.keys.axis) { - this.axis(-e.event.movementX, -e.event.movementY, _isReversed()); + this.axis( + -e.event.movementX, + -e.event.movementY, + _isReversed() + ); } } }; @@ -1289,9 +1478,14 @@ class WebVR { } displayIsInControllerMode() { - const {_parent: parent, _index: index} = this; + const { _parent: parent, _index: index } = this; const mode = parent.getMode(); - return parent.isPresenting && ((mode === 'center') || (mode === 'left' && index === 0) || (mode === 'right' && index === 1)); + return ( + parent.isPresenting && + (mode === 'center' || + (mode === 'left' && index === 0) || + (mode === 'right' && index === 1)) + ); } move(x, y, z, reverse) { @@ -1304,11 +1498,11 @@ class WebVR { } axis(x, y, reverse) { - const {axes} = this; + const { axes } = this; const reverseFactor = !reverse ? 1 : -1; - axes[0] = _clampAxis(axes[0] - (x * MOVE_FACTOR * reverseFactor)); - axes[1] = _clampAxis(axes[1] + (y * MOVE_FACTOR * reverseFactor)); + axes[0] = _clampAxis(axes[0] - x * MOVE_FACTOR * reverseFactor); + axes[1] = _clampAxis(axes[1] + y * MOVE_FACTOR * reverseFactor); this.poseNeedsUpdate = true; } @@ -1325,7 +1519,12 @@ class WebVR { updatePose() { if (this.poseNeedsUpdate) { - localMatrix.compose(this.positionOffset, localQuaternion.setFromEuler(this.rotationOffset), oneVector) + localMatrix + .compose( + this.positionOffset, + localQuaternion.setFromEuler(this.rotationOffset), + oneVector + ) .premultiply(this._parent.matrix); localMatrix.decompose(this.position, this.rotation, this.scale); @@ -1368,7 +1567,8 @@ class WebVR { } const _isPolyfillDisplay = vrDisplay => /polyfill/i.test(vrDisplay.displayName); -const _canPresent = vrDisplay => vrDisplay ? vrDisplay.capabilities.canPresent : false; +const _canPresent = vrDisplay => + vrDisplay ? vrDisplay.capabilities.canPresent : false; const _clampAxis = v => Math.min(Math.max(v, -1), 1); module.exports = WebVR; diff --git a/core/engines/world/client.js b/core/engines/world/client.js index 6d2dc8151..c70ae9f07 100644 --- a/core/engines/world/client.js +++ b/core/engines/world/client.js @@ -5,7 +5,6 @@ import { WORLD_WIDTH, WORLD_HEIGHT, WORLD_DEPTH, - TAGS_WIDTH, TAGS_HEIGHT, TAGS_WORLD_WIDTH, @@ -24,7 +23,7 @@ class World { } mount() { - const {_archae: archae} = this; + const { _archae: archae } = this; const cleanups = []; this._cleanup = () => { @@ -39,636 +38,684 @@ class World { live = false; }); - return archae.requestPlugins([ - '/core/engines/bootstrap', - '/core/engines/three', - '/core/engines/input', - '/core/engines/webvr', - '/core/engines/cyborg', - '/core/engines/multiplayer', - '/core/engines/biolumi', - '/core/engines/resource', - '/core/engines/rend', - '/core/engines/wallet', - '/core/engines/keyboard', - '/core/engines/transform', - '/core/engines/hand', - '/core/engines/loader', - '/core/engines/tags', - // '/core/engines/fs', - '/core/utils/network-utils', - '/core/utils/geometry-utils', - '/core/utils/creature-utils', - ]).then(([ - bootstrap, - three, - input, - webvr, - cyborg, - multiplayer, - biolumi, - resource, - rend, - wallet, - keyboard, - transform, - hand, - loader, - tags, - // fs, - networkUtils, - geometryUtils, - creatureUtils, - ]) => { - if (live) { - const {THREE, scene, camera} = three; - const {AutoWs} = networkUtils; - const {sfx} = resource; - - const worldRenderer = worldRender.makeRenderer({creatureUtils}); - - const mainFontSpec = { - fonts: biolumi.getFonts(), - fontSize: 36, - lineHeight: 1.4, - fontWeight: biolumi.getFontWeight(), - fontStyle: biolumi.getFontStyle(), - }; - - const localUserId = multiplayer.getId(); + return archae + .requestPlugins([ + '/core/engines/bootstrap', + '/core/engines/three', + '/core/engines/input', + '/core/engines/webvr', + '/core/engines/cyborg', + '/core/engines/multiplayer', + '/core/engines/biolumi', + '/core/engines/resource', + '/core/engines/rend', + '/core/engines/wallet', + '/core/engines/keyboard', + '/core/engines/transform', + '/core/engines/hand', + '/core/engines/loader', + '/core/engines/tags', + // '/core/engines/fs', + '/core/utils/network-utils', + '/core/utils/geometry-utils', + '/core/utils/creature-utils', + ]) + .then( + ( + [ + bootstrap, + three, + input, + webvr, + cyborg, + multiplayer, + biolumi, + resource, + rend, + wallet, + keyboard, + transform, + hand, + loader, + tags, + // fs, + networkUtils, + geometryUtils, + creatureUtils, + ] + ) => { + if (live) { + const { THREE, scene, camera } = three; + const { AutoWs } = networkUtils; + const { sfx } = resource; + + const worldRenderer = worldRender.makeRenderer({ creatureUtils }); + + const mainFontSpec = { + fonts: biolumi.getFonts(), + fontSize: 36, + lineHeight: 1.4, + fontWeight: biolumi.getFontWeight(), + fontStyle: biolumi.getFontStyle(), + }; - class ElementManager { - constructor() { - this.tagMeshes = {}; - } + const localUserId = multiplayer.getId(); - getTagMeshes() { - const {tagMeshes} = this; + class ElementManager { + constructor() { + this.tagMeshes = {}; + } - const result = []; - for (const k in tagMeshes) { - const tagMesh = tagMeshes[k]; - result.push(tagMesh); - } - return result; - } + getTagMeshes() { + const { tagMeshes } = this; - getTagMesh(id) { - return this.tagMeshes[id]; - } + const result = []; + for (const k in tagMeshes) { + const tagMesh = tagMeshes[k]; + result.push(tagMesh); + } + return result; + } - add(tagMesh) { - const {tagMeshes} = this; - const {item} = tagMesh; - const {id} = item; - tagMeshes[id] = tagMesh; + getTagMesh(id) { + return this.tagMeshes[id]; + } - if (!rend.isOpen()) { - tagMesh.visible = false; - } + add(tagMesh) { + const { tagMeshes } = this; + const { item } = tagMesh; + const { id } = item; + tagMeshes[id] = tagMesh; - // scene.add(tagMesh); - // tagMesh.updateMatrixWorld(); - } + if (!rend.isOpen()) { + tagMesh.visible = false; + } - remove(tagMesh) { - const {tagMeshes} = this; - const {item} = tagMesh; - const {id} = item; - delete tagMeshes[id]; + // scene.add(tagMesh); + // tagMesh.updateMatrixWorld(); + } - // tagMesh.parent.remove(tagMesh); - } - } - const elementManager = new ElementManager(); + remove(tagMesh) { + const { tagMeshes } = this; + const { item } = tagMesh; + const { id } = item; + delete tagMeshes[id]; - const _request = (method, args) => { - if (connection) { - const e = { - method, - args, + // tagMesh.parent.remove(tagMesh); + } + } + const elementManager = new ElementManager(); + + const _request = (method, args) => { + if (connection) { + const e = { + method, + args, + }; + const es = JSON.stringify(e); + connection.send(es); + } + }; + const _addTag = (itemSpec, { element = null } = {}) => { + const newElement = _handleAddTag(localUserId, itemSpec, { + element, + }); + _request('addTag', [localUserId, itemSpec]); + return newElement; + }; + const _addTags = itemSpecs => { + _handleAddTags(localUserId, itemSpecs); + _request('addTags', [localUserId, itemSpecs]); + }; + const _removeTag = id => { + const newElement = _handleRemoveTag(localUserId, id); + _request('removeTag', [localUserId, id]); + return newElement; + }; + const _removeTags = ids => { + _handleRemoveTags(localUserId, ids); + _request('removeTags', [localUserId, ids]); + }; + const _setTagAttribute = (id, { name, value }) => { + _handleSetTagAttribute(localUserId, id, { name, value }); + _request('setTagAttribute', [localUserId, id, { name, value }]); + }; + const _setTagAttributes = (id, newAttributes) => { + _handleSetTagAttributes(localUserId, id, newAttributes); + _request('setTagAttributes', [localUserId, id, newAttributes]); }; - const es = JSON.stringify(e); - connection.send(es); - } - }; - const _addTag = (itemSpec, {element = null} = {}) => { - const newElement = _handleAddTag(localUserId, itemSpec, {element}); - _request('addTag', [localUserId, itemSpec]); - return newElement; - }; - const _addTags = (itemSpecs) => { - _handleAddTags(localUserId, itemSpecs); - _request('addTags', [localUserId, itemSpecs]); - }; - const _removeTag = id => { - const newElement = _handleRemoveTag(localUserId, id); - _request('removeTag', [localUserId, id]); - return newElement; - }; - const _removeTags = ids => { - _handleRemoveTags(localUserId, ids); - _request('removeTags', [localUserId, ids]); - }; - const _setTagAttribute = (id, {name, value}) => { - _handleSetTagAttribute(localUserId, id, {name, value}); - _request('setTagAttribute', [localUserId, id, {name, value}]); - }; - const _setTagAttributes = (id, newAttributes) => { - _handleSetTagAttributes(localUserId, id, newAttributes); - _request('setTagAttributes', [localUserId, id, newAttributes]); - }; - - const _handleAddTag = (userId, itemSpec, {element = null} = {}) => { - const tagMesh = tags.makeTag(itemSpec); - const {item} = tagMesh; - - if (element) { - element.item = item; - item.instance = element; - } - - let result = null; - if (item.type === 'entity' && !item.instance) { - result = tags.mutateAddEntity(tagMesh); - } - elementManager.add(tagMesh); + const _handleAddTag = ( + userId, + itemSpec, + { element = null } = {} + ) => { + const tagMesh = tags.makeTag(itemSpec); + const { item } = tagMesh; + + if (element) { + element.item = item; + item.instance = element; + } - return result; - }; - const _handleAddTags = (userId, itemSpecs) => { - for (let i = 0; i < itemSpecs.length; i++) { - const itemSpec = itemSpecs[i]; - _handleAddTag(userId, itemSpec); - } - }; - const _handleRemoveTag = (userId, id) => { - const tagMesh = elementManager.getTagMesh(id); - const {item} = tagMesh; + let result = null; + if (item.type === 'entity' && !item.instance) { + result = tags.mutateAddEntity(tagMesh); + } - let result = null; - if (item.type === 'entity' && item.instance) { - result = tags.mutateRemoveEntity(tagMesh); - } + elementManager.add(tagMesh); - tags.destroyTag(tagMesh); + return result; + }; + const _handleAddTags = (userId, itemSpecs) => { + for (let i = 0; i < itemSpecs.length; i++) { + const itemSpec = itemSpecs[i]; + _handleAddTag(userId, itemSpec); + } + }; + const _handleRemoveTag = (userId, id) => { + const tagMesh = elementManager.getTagMesh(id); + const { item } = tagMesh; - return result; - }; - const _handleRemoveTags = (userId, ids) => { - for (let i = 0; i < ids.length; i++) { - const id = ids[i]; - _handleRemoveTag(userId, id); - } - }; - const _handleSetTagAttribute = (userId, id, {name, value}) => { - // same for local and remote user ids + let result = null; + if (item.type === 'entity' && item.instance) { + result = tags.mutateRemoveEntity(tagMesh); + } - const tagMesh = elementManager.getTagMesh(id); - tagMesh.setAttribute(name, value); + tags.destroyTag(tagMesh); - return tagMesh; - }; - const _handleSetTagAttributes = (userId, id, newAttributes) => { - // same for local and remote user ids + return result; + }; + const _handleRemoveTags = (userId, ids) => { + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + _handleRemoveTag(userId, id); + } + }; + const _handleSetTagAttribute = (userId, id, { name, value }) => { + // same for local and remote user ids - return newAttributes.map(newAttribute => { - const {name, value} = newAttribute; - return _handleSetTagAttribute(userId, id, {name, value}); - }); - }; - const _handleTagOpen = (userId, id) => { - // same for local and remote user ids + const tagMesh = elementManager.getTagMesh(id); + tagMesh.setAttribute(name, value); - const tagMesh = elementManager.getTagMesh(id); - tagMesh.open(); - }; - const _handleTagClose = (userId, id) => { - // same for local and remote user ids - - const tagMesh = elementManager.getTagMesh(id); - tagMesh.close(); - }; - const _handleTagPlay = (userId, id) => { - // same for local and remote user ids + return tagMesh; + }; + const _handleSetTagAttributes = (userId, id, newAttributes) => { + // same for local and remote user ids - const tagMesh = elementManager.getTagMesh(id); - tagMesh.play(); - }; - const _handleTagPause = (userId, id) => { - // same for local and remote user ids + return newAttributes.map(newAttribute => { + const { name, value } = newAttribute; + return _handleSetTagAttribute(userId, id, { name, value }); + }); + }; + const _handleTagOpen = (userId, id) => { + // same for local and remote user ids - const tagMesh = elementManager.getTagMesh(id); - tagMesh.pause(); - }; - const _handleTagSeek = (userId, id, value) => { - // same for local and remote user ids + const tagMesh = elementManager.getTagMesh(id); + tagMesh.open(); + }; + const _handleTagClose = (userId, id) => { + // same for local and remote user ids - const tagMesh = elementManager.getTagMesh(id); - tagMesh.seek(value); - }; - const _handleLoadModule = (userId, module, version) => { - const plugin = _getPlugin(module, version); - loader.requestPlugin(plugin) - .catch(err => { - console.warn(err); - }); - }; - const _handleUnloadModule = (userId, module, version) => { - const plugin = _getPlugin(module, version); - loader.releasePlugin(plugin) - .catch(err => { - console.warn(err); - }); - }; - const _handleMessage = detail => { - tags.message(detail); - }; + const tagMesh = elementManager.getTagMesh(id); + tagMesh.close(); + }; + const _handleTagPlay = (userId, id) => { + // same for local and remote user ids - const _searchNpm = (q = '') => fetch(`archae/rend/search?q=${encodeURIComponent(q)}`, { - credentials: 'include', - }) - .then(res => res.json()); - const _updateNpm = menuUtils.debounce(next => { - const {inputText} = npmState; - - _searchNpm(inputText) - .then(itemSpecs => - Promise.all(itemSpecs.map(itemSpec => { - itemSpec.metadata.isStatic = true; // XXX can probably be hardcoded in the render - itemSpec.metadata.exists = elementManager.getTagMeshes() - .some(tagMesh => - tagMesh.item.type === itemSpec.type && - tagMesh.item.name === itemSpec.name - ); + const tagMesh = elementManager.getTagMesh(id); + tagMesh.play(); + }; + const _handleTagPause = (userId, id) => { + // same for local and remote user ids - return tags.makeTag(itemSpec, { - initialUpdate: false, - }); - })) - .then(tagMeshes => { - const {tagMeshes: oldTagMeshes} = npmCacheState; + const tagMesh = elementManager.getTagMesh(id); + tagMesh.pause(); + }; + const _handleTagSeek = (userId, id, value) => { + // same for local and remote user ids - npmState.loading = false; - npmState.page = 0; - npmState.tagSpecs = itemSpecs; - npmState.numTags = itemSpecs.length; - npmCacheState.tagMeshes = tagMeshes; + const tagMesh = elementManager.getTagMesh(id); + tagMesh.seek(value); + }; + const _handleLoadModule = (userId, module, version) => { + const plugin = _getPlugin(module, version); + loader.requestPlugin(plugin).catch(err => { + console.warn(err); + }); + }; + const _handleUnloadModule = (userId, module, version) => { + const plugin = _getPlugin(module, version); + loader.releasePlugin(plugin).catch(err => { + console.warn(err); + }); + }; + const _handleMessage = detail => { + tags.message(detail); + }; - _updatePages(); + const _searchNpm = (q = '') => + fetch(`archae/rend/search?q=${encodeURIComponent(q)}`, { + credentials: 'include', + }).then(res => res.json()); + const _updateNpm = menuUtils.debounce(next => { + const { inputText } = npmState; + + _searchNpm(inputText) + .then(itemSpecs => + Promise.all( + itemSpecs.map(itemSpec => { + itemSpec.metadata.isStatic = true; // XXX can probably be hardcoded in the render + itemSpec.metadata.exists = elementManager + .getTagMeshes() + .some( + tagMesh => + tagMesh.item.type === itemSpec.type && + tagMesh.item.name === itemSpec.name + ); + + return tags.makeTag(itemSpec, { + initialUpdate: false, + }); + }) + ).then(tagMeshes => { + const { tagMeshes: oldTagMeshes } = npmCacheState; + + npmState.loading = false; + npmState.page = 0; + npmState.tagSpecs = itemSpecs; + npmState.numTags = itemSpecs.length; + npmCacheState.tagMeshes = tagMeshes; + + _updatePages(); + + next(); + }) + ) + .catch(err => { + console.warn(err); next(); - }) - ) - .catch(err => { - console.warn(err); + }); - next(); + const { numTags } = npmState; + npmState.loading = numTags === 0; }); - const {numTags} = npmState; - npmState.loading = numTags === 0; - }); - - const npmState = { - loading: true, - inputText: '', - module: null, - tagSpecs: [], - numTags: 0, - page: 0, - }; - const focusState = { - keyboardFocusState: null, - }; - const npmCacheState = { - tagMeshes: [], - loaded: false, - }; - - const worldMesh = (() => { - const object = new THREE.Object3D(); - object.visible = false; - - const planeMesh = (() => { - const worldUi = biolumi.makeUi({ - width: WIDTH, - height: HEIGHT, - }); - const mesh = worldUi.makePage(({ - npm: { - loading, - inputText, - module, - tagSpecs, - numTags, - page, - }, - focus: { - keyboardFocusState, - }, - }) => { - const {type: focusType = '', inputValue = 0} = keyboardFocusState || {}; - - return { - type: 'html', - src: worldRenderer.getWorldPageSrc({loading, inputText, inputValue, module, tagSpecs, numTags, page, focusType}), - x: 0, - y: 0, - w: WIDTH, - h: HEIGHT, - }; - }, { - type: 'world', - state: { - npm: npmState, - focus: focusState, - }, - worldWidth: WORLD_WIDTH, - worldHeight: WORLD_HEIGHT, - isEnabled: () => rend.isOpen(), - }); - mesh.receiveShadow = true; + const npmState = { + loading: true, + inputText: '', + module: null, + tagSpecs: [], + numTags: 0, + page: 0, + }; + const focusState = { + keyboardFocusState: null, + }; + const npmCacheState = { + tagMeshes: [], + loaded: false, + }; - const {page} = mesh; - rend.addPage(page); + const worldMesh = (() => { + const object = new THREE.Object3D(); + object.visible = false; - cleanups.push(() => { - rend.removePage(page); - }); + const planeMesh = (() => { + const worldUi = biolumi.makeUi({ + width: WIDTH, + height: HEIGHT, + }); + const mesh = worldUi.makePage( + ({ + npm: { + loading, + inputText, + module, + tagSpecs, + numTags, + page, + }, + focus: { keyboardFocusState }, + }) => { + const { type: focusType = '', inputValue = 0 } = + keyboardFocusState || {}; + + return { + type: 'html', + src: worldRenderer.getWorldPageSrc({ + loading, + inputText, + inputValue, + module, + tagSpecs, + numTags, + page, + focusType, + }), + x: 0, + y: 0, + w: WIDTH, + h: HEIGHT, + }; + }, + { + type: 'world', + state: { + npm: npmState, + focus: focusState, + }, + worldWidth: WORLD_WIDTH, + worldHeight: WORLD_HEIGHT, + isEnabled: () => rend.isOpen(), + } + ); + mesh.receiveShadow = true; - return mesh; - })(); - object.add(planeMesh); - object.planeMesh = planeMesh; + const { page } = mesh; + rend.addPage(page); - return object; - })(); - rend.registerMenuMesh('worldMesh', worldMesh); - worldMesh.updateMatrixWorld(); + cleanups.push(() => { + rend.removePage(page); + }); - const _updatePages = () => { - const {planeMesh} = worldMesh; - const {page} = planeMesh; - page.update(); - }; - _updatePages(); + return mesh; + })(); + object.add(planeMesh); + object.planeMesh = planeMesh; - const _update = e => { - // XXX - }; - rend.on('update', _update); + return object; + })(); + rend.registerMenuMesh('worldMesh', worldMesh); + worldMesh.updateMatrixWorld(); - const _tabchange = tab => { - if (tab === 'world') { - keyboard.tryBlur(); + const _updatePages = () => { + const { planeMesh } = worldMesh; + const { page } = planeMesh; + page.update(); + }; + _updatePages(); - const {loaded} = npmCacheState; - if (!loaded) { - _updateNpm(); - _updatePages(); + const _update = e => { + // XXX + }; + rend.on('update', _update); - npmCacheState.loaded = true; - } - } - }; - rend.on('tabchange', _tabchange); + const _tabchange = tab => { + if (tab === 'world') { + keyboard.tryBlur(); - const _loadEntities = itemSpecs => { - _addTags(itemSpecs); - }; - rend.on('loadEntities', _loadEntities); - const _clearAllEntities = () => { - const entityItemIds = tags.getTagMeshes() - .filter(({item}) => item.type === 'entity') - .map(({item: {id}}) => id); - _removeTags(entityItemIds); - }; - rend.on('clearAllEntities', _clearAllEntities); - - const _trigger = e => { - const {side} = e; - - const _clickMenu = () => { - const hoverState = rend.getHoverState(side); - const {anchor} = hoverState; - const onclick = (anchor && anchor.onclick) || ''; - - let match; - if (onclick === 'npm:focus') { - const {inputText} = npmState; - const {value, target: page} = hoverState; - const {layer: {measures}} = page; - const valuePx = value * (WIDTH - (250 + (30 * 2))); - const {index, px} = biolumi.getTextPropertiesFromCoord(measures['npm:search'], inputText, valuePx); - const {hmd: hmdStatus} = webvr.getStatus(); - const {worldPosition: hmdPosition, worldRotation: hmdRotation} = hmdStatus; - const keyboardFocusState = keyboard.focus({ - type: 'npm:search', - position: hmdPosition, - rotation: hmdRotation, - inputText: inputText, - inputIndex: index, - inputValue: px, - page: page, - }); - focusState.keyboardFocusState = keyboardFocusState; + const { loaded } = npmCacheState; + if (!loaded) { + _updateNpm(); + _updatePages(); - keyboardFocusState.on('update', () => { - const {inputText: keyboardInputText} = keyboardFocusState; - const {inputText: npmInputText} = npmState; + npmCacheState.loaded = true; + } + } + }; + rend.on('tabchange', _tabchange); - if (keyboardInputText !== npmInputText) { - npmState.inputText = keyboardInputText; + const _loadEntities = itemSpecs => { + _addTags(itemSpecs); + }; + rend.on('loadEntities', _loadEntities); + const _clearAllEntities = () => { + const entityItemIds = tags + .getTagMeshes() + .filter(({ item }) => item.type === 'entity') + .map(({ item: { id } }) => id); + _removeTags(entityItemIds); + }; + rend.on('clearAllEntities', _clearAllEntities); + + const _trigger = e => { + const { side } = e; + + const _clickMenu = () => { + const hoverState = rend.getHoverState(side); + const { anchor } = hoverState; + const onclick = (anchor && anchor.onclick) || ''; + + let match; + if (onclick === 'npm:focus') { + const { inputText } = npmState; + const { value, target: page } = hoverState; + const { layer: { measures } } = page; + const valuePx = value * (WIDTH - (250 + 30 * 2)); + const { index, px } = biolumi.getTextPropertiesFromCoord( + measures['npm:search'], + inputText, + valuePx + ); + const { hmd: hmdStatus } = webvr.getStatus(); + const { + worldPosition: hmdPosition, + worldRotation: hmdRotation, + } = hmdStatus; + const keyboardFocusState = keyboard.focus({ + type: 'npm:search', + position: hmdPosition, + rotation: hmdRotation, + inputText: inputText, + inputIndex: index, + inputValue: px, + page: page, + }); + focusState.keyboardFocusState = keyboardFocusState; + + keyboardFocusState.on('update', () => { + const { inputText: keyboardInputText } = keyboardFocusState; + const { inputText: npmInputText } = npmState; + + if (keyboardInputText !== npmInputText) { + npmState.inputText = keyboardInputText; + + // XXX the backend should cache responses to prevent local re-requests from hitting external APIs + _updateNpm(); + } - // XXX the backend should cache responses to prevent local re-requests from hitting external APIs - _updateNpm(); - } + _updatePages(); + }); + keyboardFocusState.on('blur', () => { + focusState.keyboardFocusState = null; - _updatePages(); - }); - keyboardFocusState.on('blur', () => { - focusState.keyboardFocusState = null; + _updatePages(); + }); - _updatePages(); - }); + _updatePages(); - _updatePages(); + return true; + } else if ( + (match = onclick.match(/^(?:npm|module):(up|down)$/)) + ) { + const direction = match[1]; - return true; - } else if (match = onclick.match(/^(?:npm|module):(up|down)$/)) { - const direction = match[1]; + npmState.page += direction === 'up' ? -1 : 1; - npmState.page += (direction === 'up' ? -1 : 1); + _updatePages(); - _updatePages(); + return true; + } else if ((match = onclick.match(/^module:main:(.+)$/))) { + const id = match[1]; - return true; - } else if (match = onclick.match(/^module:main:(.+)$/)) { - const id = match[1]; + const tagMesh = tags + .getTagMeshes() + .find(tagMesh => tagMesh.item.id === id); + const { item } = tagMesh; + npmState.module = item; + npmState.page = 0; - const tagMesh = tags.getTagMeshes().find(tagMesh => tagMesh.item.id === id); - const {item} = tagMesh; - npmState.module = item; - npmState.page = 0; + _updatePages(); - _updatePages(); + return true; + } else if (onclick === 'module:back') { + // const id = match[1]; - return true; - } else if (onclick === 'module:back') { - // const id = match[1]; + npmState.module = null; - npmState.module = null; + _updatePages(); - _updatePages(); + return true; + } else if (onclick === 'module:focusVersion') { + const keyboardFocusState = keyboard.fakeFocus({ + type: 'version', + }); + focusState.keyboardFocusState = keyboardFocusState; - return true; - } else if (onclick === 'module:focusVersion') { - const keyboardFocusState = keyboard.fakeFocus({ - type: 'version', - }); - focusState.keyboardFocusState = keyboardFocusState; + keyboardFocusState.on('blur', () => { + focusState.keyboardFocusState = null; - keyboardFocusState.on('blur', () => { - focusState.keyboardFocusState = null; + _updatePages(); + }); - _updatePages(); - }); + _updatePages(); - _updatePages(); + return true; + } else if ( + (match = onclick.match(/^module:setVersion:(.+?):(.+?)$/)) + ) { + // XXX load the version metadata here + const id = match[1]; + const version = match[2]; + + const moduleTagMesh = tags + .getTagMeshes() + .find( + tagMesh => + tagMesh.item.type === 'module' && tagMesh.item.id === id + ); + const { item: moduleItem } = moduleTagMesh; + moduleItem.version = version; + + keyboard.tryBlur(); - return true; - } else if (match = onclick.match(/^module:setVersion:(.+?):(.+?)$/)) { // XXX load the version metadata here - const id = match[1]; - const version = match[2]; + _updatePages(); - const moduleTagMesh = tags.getTagMeshes().find(tagMesh => tagMesh.item.type === 'module' && tagMesh.item.id === id); - const {item: moduleItem} = moduleTagMesh; - moduleItem.version = version; + return true; + } else if ((match = onclick.match(/^module:add:(.+)$/))) { + const id = match[1]; + + const moduleTagMesh = tags + .getTagMeshes() + .find( + tagMesh => + tagMesh.item.type === 'module' && tagMesh.item.id === id + ); + const { item: moduleItem } = moduleTagMesh; + const { name: module, displayName: moduleName } = moduleItem; + const tagName = _makeTagName(module); + const attributes = tags.getAttributeSpecsMap(module); + + const itemSpec = { + type: 'entity', + id: _makeId(), + name: module, + displayName: moduleName, + version: '0.0.1', + module: module, + tagName: tagName, + attributes: attributes, + metadata: {}, + }; + const entityElement = _addTag(itemSpec); + const { item } = entityElement; + + rend.setTab('entity'); + rend.setEntity(item); + + return true; + } else { + return false; + } + }; + const _clickMenuBackground = () => { + const hoverState = rend.getHoverState(side); + const { target } = hoverState; - keyboard.tryBlur(); + if (target && target.mesh && target.mesh.parent === worldMesh) { + return true; + } else { + return false; + } + }; - _updatePages(); + if (_clickMenu()) { + sfx.digi_select.trigger(); - return true; - } else if (match = onclick.match(/^module:add:(.+)$/)) { - const id = match[1]; + e.stopImmediatePropagation(); + } else if (_clickMenuBackground()) { + sfx.digi_plink.trigger(); - const moduleTagMesh = tags.getTagMeshes().find(tagMesh => tagMesh.item.type === 'module' && tagMesh.item.id === id); - const {item: moduleItem} = moduleTagMesh; - const {name: module, displayName: moduleName} = moduleItem; - const tagName = _makeTagName(module); - const attributes = tags.getAttributeSpecsMap(module); + e.stopImmediatePropagation(); + } + }; + input.on('trigger', _trigger, { + priority: 1, + }); + const _mutateAddEntity = ({ element, tagName, attributes }) => { const itemSpec = { type: 'entity', id: _makeId(), - name: module, - displayName: moduleName, + name: 'Manual entity', + displayName: 'Manual entity', version: '0.0.1', - module: module, tagName: tagName, attributes: attributes, metadata: {}, }; - const entityElement = _addTag(itemSpec); - const {item} = entityElement; - - rend.setTab('entity'); - rend.setEntity(item); - - return true; - } else { - return false; - } - }; - const _clickMenuBackground = () => { - const hoverState = rend.getHoverState(side); - const {target} = hoverState; - - if (target && target.mesh && target.mesh.parent === worldMesh) { - return true; - } else { - return false; - } - }; - - if (_clickMenu()) { - sfx.digi_select.trigger(); - - e.stopImmediatePropagation(); - } else if (_clickMenuBackground()) { - sfx.digi_plink.trigger(); - - e.stopImmediatePropagation(); - } - }; - input.on('trigger', _trigger, { - priority: 1, - }); - - const _mutateAddEntity = ({element, tagName, attributes}) => { - const itemSpec = { - type: 'entity', - id: _makeId(), - name: 'Manual entity', - displayName: 'Manual entity', - version: '0.0.1', - tagName: tagName, - attributes: attributes, - metadata: {}, - }; - _addTag(itemSpec, {element}); - }; - tags.on('mutateAddEntity', _mutateAddEntity); - const _mutateRemoveEntity = ({id}) => { - _removeTag(id); - }; - tags.on('mutateRemoveEntity', _mutateRemoveEntity); - const _mutateSetAttribute = ({id, name, value}) => { - _request('setTagAttribute', [localUserId, id, {name, value}]); - }; - tags.on('mutateSetAttribute', _mutateSetAttribute); - const _tagsSetAttribute = ({id, name, value}) => { - _handleSetTagAttribute(localUserId, id, {name, value}); - }; - tags.on('setAttribute', _tagsSetAttribute); - const _tagsOpen = ({id}) => { - _request('tagOpen', [localUserId, id]); + _addTag(itemSpec, { element }); + }; + tags.on('mutateAddEntity', _mutateAddEntity); + const _mutateRemoveEntity = ({ id }) => { + _removeTag(id); + }; + tags.on('mutateRemoveEntity', _mutateRemoveEntity); + const _mutateSetAttribute = ({ id, name, value }) => { + _request('setTagAttribute', [localUserId, id, { name, value }]); + }; + tags.on('mutateSetAttribute', _mutateSetAttribute); + const _tagsSetAttribute = ({ id, name, value }) => { + _handleSetTagAttribute(localUserId, id, { name, value }); + }; + tags.on('setAttribute', _tagsSetAttribute); + const _tagsOpen = ({ id }) => { + _request('tagOpen', [localUserId, id]); - _handleTagOpen(localUserId, id); - }; - tags.on('open', _tagsOpen); - const _tagsClose = ({id}) => { - _request('tagClose', [localUserId, id]); + _handleTagOpen(localUserId, id); + }; + tags.on('open', _tagsOpen); + const _tagsClose = ({ id }) => { + _request('tagClose', [localUserId, id]); - _handleTagClose(localUserId, id); - }; - tags.on('close', _tagsClose); - const _tagsPlay = ({id}) => { - _request('tagPlay', [localUserId, id]); + _handleTagClose(localUserId, id); + }; + tags.on('close', _tagsClose); + const _tagsPlay = ({ id }) => { + _request('tagPlay', [localUserId, id]); - _handleTagPlay(localUserId, id); - }; - tags.on('play', _tagsPlay); - const _tagsPause = ({id}) => { - _request('tagPause', [localUserId, id]); + _handleTagPlay(localUserId, id); + }; + tags.on('play', _tagsPlay); + const _tagsPause = ({ id }) => { + _request('tagPause', [localUserId, id]); - _handleTagPause(localUserId, id); - }; - tags.on('pause', _tagsPause); - const _tagsSeek = ({id, value}) => { - _request('tagSeek', [localUserId, id, value]); + _handleTagPause(localUserId, id); + }; + tags.on('pause', _tagsPause); + const _tagsSeek = ({ id, value }) => { + _request('tagSeek', [localUserId, id, value]); - _handleTagSeek(localUserId, id, value); - }; - tags.on('seek', _tagsSeek); - const _tagsSeekUpdate = ({id, value}) => { - _request('tagSeekUpdate', [localUserId, id, value]); - }; - tags.on('seekUpdate', _tagsSeekUpdate); - /* const _reinstallModule = ({id}) => { + _handleTagSeek(localUserId, id, value); + }; + tags.on('seek', _tagsSeek); + const _tagsSeekUpdate = ({ id, value }) => { + _request('tagSeekUpdate', [localUserId, id, value]); + }; + tags.on('seekUpdate', _tagsSeekUpdate); + /* const _reinstallModule = ({id}) => { const tagMesh = elementManager.getTagMesh(id); const {item} = tagMesh; const {module, version} = item; @@ -687,7 +734,7 @@ class World { }; tags.on('reinstallModule', _reinstallModule); */ - /* const _download = ({id}) => { + /* const _download = ({id}) => { const a = document.createElement('a'); a.href = fs.getFileUrl(id); a.style.display = 'none'; @@ -697,7 +744,7 @@ class World { }; tags.on('download', _download); */ - /* const _makeFileTagFromSpec = fileSpec => { + /* const _makeFileTagFromSpec = fileSpec => { const { id, name, @@ -829,209 +876,220 @@ class World { }; fs.on('upload', _upload); */ - const initPromise = (() => { - let _accept = null; - let _reject = null; - const result = new Promise((accept, reject) => { - _accept = accept; - _reject = reject; - }); - result.resolve = _accept; - result.reject = _reject; - return result; - })(); - const _loadTags = itemSpecs => { - for (let i = 0; i < itemSpecs.length; i++) { - _handleAddTag(localUserId, itemSpecs[i]); - } - }; + const initPromise = (() => { + let _accept = null; + let _reject = null; + const result = new Promise((accept, reject) => { + _accept = accept; + _reject = reject; + }); + result.resolve = _accept; + result.reject = _reject; + return result; + })(); + const _loadTags = itemSpecs => { + for (let i = 0; i < itemSpecs.length; i++) { + _handleAddTag(localUserId, itemSpecs[i]); + } + }; + + const connection = new AutoWs( + _relativeWsUrl('archae/worldWs?id=' + localUserId) + ); + let connectionInitialized = false; + connection.on('message', msg => { + const m = JSON.parse(msg.data); + const { type } = m; + + if (type === 'init') { + if (!connectionInitialized) { + // XXX temporary hack until we correctly unload tags on disconnect + initPromise // wait for core to be loaded before initializing user plugins + .then(() => { + const { args: [itemSpecs] } = m; + + _loadTags(itemSpecs); + + connectionInitialized = true; + }); + } + } else if (type === 'addTag') { + const { args: [userId, itemSpec] } = m; + + _handleAddTag(userId, itemSpec); + } else if (type === 'addTags') { + const { args: [userId, itemSpecs] } = m; - const connection = new AutoWs(_relativeWsUrl('archae/worldWs?id=' + localUserId)); - let connectionInitialized = false; - connection.on('message', msg => { - const m = JSON.parse(msg.data); - const {type} = m; + _handleAddTags(userId, itemSpecs); + } else if (type === 'removeTag') { + const { args: [userId, id] } = m; - if (type === 'init') { - if (!connectionInitialized) { // XXX temporary hack until we correctly unload tags on disconnect - initPromise // wait for core to be loaded before initializing user plugins - .then(() => { - const {args: [itemSpecs]} = m; + _handleRemoveTag(userId, id); + } else if (type === 'removeTags') { + const { args: [userId, ids] } = m; - _loadTags(itemSpecs); + _handleRemoveTags(userId, ids); + } else if (type === 'setTagAttribute') { + const { args: [userId, id, { name, value }] } = m; - connectionInitialized = true; + const tagMesh = _handleSetTagAttribute(userId, id, { + name, + value, }); - } - } else if (type === 'addTag') { - const {args: [userId, itemSpec]} = m; - - _handleAddTag(userId, itemSpec); - } else if (type === 'addTags') { - const {args: [userId, itemSpecs]} = m; - - _handleAddTags(userId, itemSpecs); - } else if (type === 'removeTag') { - const {args: [userId, id]} = m; - - _handleRemoveTag(userId, id); - } else if (type === 'removeTags') { - const {args: [userId, ids]} = m; - - _handleRemoveTags(userId, ids); - } else if (type === 'setTagAttribute') { - const {args: [userId, id, {name, value}]} = m; - - const tagMesh = _handleSetTagAttribute(userId, id, {name, value}); - // this prevents this mutation from triggering an infinite recursion multiplayer update - // we simply ignore this mutation during the next entity mutation tick - tags.ignoreEntityMutation({ - type: 'setAttribute', - args: [id, name, value], - }); - } else if (type === 'setTagAttributes') { - const {args: [userId, id, newAttributes]} = m; - - const tagMeshes = _handleSetTagAttributes(userId, id, newAttributes); - for (let i = 0; i < tagMeshes.length; i++) { - const tagMesh = tagMeshes[i]; - // this prevents this mutation from triggering an infinite recursion multiplayer update - // we simply ignore this mutation during the next entity mutation tick - tags.ignoreEntityMutation({ - type: 'setAttribute', - args: [id, name, value], - }); - } - } else if (type === 'tagOpen') { - const {args: [userId, id]} = m; - - _handleTagOpen(userId, id); - } else if (type === 'tagClose') { - const {args: [userId, id]} = m; - - _handleTagClose(userId, id); - } else if (type === 'tagOpenDetails') { - const {args: [userId, id]} = m; - - _handleTagOpenDetails(userId, id); - } else if (type === 'tagCloseDetails') { - const {args: [userId, id]} = m; - - _handleTagCloseDetails(userId, id); - } else if (type === 'tagPlay') { - const {args: [userId, id]} = m; - - _handleTagPlay(userId, id); - } else if (type === 'tagPause') { - const {args: [userId, id]} = m; - - _handleTagPause(userId, id); - } else if (type === 'tagSeek') { - const {args: [userId, id, value]} = m; - - _handleTagSeek(userId, id, value); - } else if (type === 'loadModule') { - const {args: [userId, plugin]} = m; - - _handleLoadModule(userId, plugin); - } else if (type === 'unloadModule') { - const {args: [userId, pluginName]} = m; - - _handleUnloadModule(userId, pluginName); - } else if (type === 'message') { - const {args: [detail]} = m; - - _handleMessage(detail); - } else if (type === 'response') { - const {id} = m; - - const requestHandler = requestHandlers.get(id); - if (requestHandler) { - const {error, result} = m; - requestHandler(error, result); - } else { - console.warn('unregistered handler:', JSON.stringify(id)); - } - } else { - console.log('unknown message', m); - } - }); - - cleanups.push(() => { - rend.removeListener('update', _update); - rend.removeListener('tabchange', _tabchange); - rend.removeListener('loadEntities', _loadEntities); - rend.removeListener('clearAllEntities', _clearAllEntities); - - input.removeListener('trigger', _trigger); - - // tags.removeListener('download', _download); - tags.removeListener('mutateAddEntity', _mutateAddEntity); - tags.removeListener('mutateRemoveEntity', _mutateRemoveEntity); - tags.removeListener('mutateSetAttribute', _mutateSetAttribute); - tags.removeListener('setAttribute', _tagsSetAttribute); - tags.removeListener('open', _tagsOpen); - tags.removeListener('close', _tagsClose); - tags.removeListener('play', _tagsPlay); - tags.removeListener('pause', _tagsPause); - tags.removeListener('seek', _tagsSeek); - tags.removeListener('seekUpdate', _tagsSeekUpdate); - - // fs.removeListener('upload', _upload); - - connection.destroy(); - }); - - class WorldApi { - init() { - initPromise.resolve(); - } + // this prevents this mutation from triggering an infinite recursion multiplayer update + // we simply ignore this mutation during the next entity mutation tick + tags.ignoreEntityMutation({ + type: 'setAttribute', + args: [id, name, value], + }); + } else if (type === 'setTagAttributes') { + const { args: [userId, id, newAttributes] } = m; + + const tagMeshes = _handleSetTagAttributes( + userId, + id, + newAttributes + ); + for (let i = 0; i < tagMeshes.length; i++) { + const tagMesh = tagMeshes[i]; + // this prevents this mutation from triggering an infinite recursion multiplayer update + // we simply ignore this mutation during the next entity mutation tick + tags.ignoreEntityMutation({ + type: 'setAttribute', + args: [id, name, value], + }); + } + } else if (type === 'tagOpen') { + const { args: [userId, id] } = m; - addTag(itemSpec) { - _addTag(itemSpec); - } + _handleTagOpen(userId, id); + } else if (type === 'tagClose') { + const { args: [userId, id] } = m; - addTags(itemSpecs) { - _addTags(itemSpecs); - } + _handleTagClose(userId, id); + } else if (type === 'tagOpenDetails') { + const { args: [userId, id] } = m; - removeTag(id) { - _removeTag(id); - } + _handleTagOpenDetails(userId, id); + } else if (type === 'tagCloseDetails') { + const { args: [userId, id] } = m; - removeTags(ids) { - _removeTags(ids); - } + _handleTagCloseDetails(userId, id); + } else if (type === 'tagPlay') { + const { args: [userId, id] } = m; - makeFile({ext = 'txt'} = {}) { - const id = _makeId(); - const name = id + '.' + ext; - const mimeType = 'mime/' + ext.toLowerCase(); - const path = '/' + name; - const file = new Blob([], { - type: mimeType, - }); - file.path = path; - const files = [file]; + _handleTagPlay(userId, id); + } else if (type === 'tagPause') { + const { args: [userId, id] } = m; - return _makeFileTagFromSpec({ - id, - name, - mimeType, - files, - }).then(tagMesh => { - const {item} = tagMesh; - const {id, name} = item; + _handleTagPause(userId, id); + } else if (type === 'tagSeek') { + const { args: [userId, id, value] } = m; + + _handleTagSeek(userId, id, value); + } else if (type === 'loadModule') { + const { args: [userId, plugin] } = m; + + _handleLoadModule(userId, plugin); + } else if (type === 'unloadModule') { + const { args: [userId, pluginName] } = m; + + _handleUnloadModule(userId, pluginName); + } else if (type === 'message') { + const { args: [detail] } = m; + + _handleMessage(detail); + } else if (type === 'response') { + const { id } = m; + + const requestHandler = requestHandlers.get(id); + if (requestHandler) { + const { error, result } = m; + requestHandler(error, result); + } else { + console.warn('unregistered handler:', JSON.stringify(id)); + } + } else { + console.log('unknown message', m); + } + }); - return fs.makeFile('fs/' + id + '/' + name); + cleanups.push(() => { + rend.removeListener('update', _update); + rend.removeListener('tabchange', _tabchange); + rend.removeListener('loadEntities', _loadEntities); + rend.removeListener('clearAllEntities', _clearAllEntities); + + input.removeListener('trigger', _trigger); + + // tags.removeListener('download', _download); + tags.removeListener('mutateAddEntity', _mutateAddEntity); + tags.removeListener('mutateRemoveEntity', _mutateRemoveEntity); + tags.removeListener('mutateSetAttribute', _mutateSetAttribute); + tags.removeListener('setAttribute', _tagsSetAttribute); + tags.removeListener('open', _tagsOpen); + tags.removeListener('close', _tagsClose); + tags.removeListener('play', _tagsPlay); + tags.removeListener('pause', _tagsPause); + tags.removeListener('seek', _tagsSeek); + tags.removeListener('seekUpdate', _tagsSeekUpdate); + + // fs.removeListener('upload', _upload); + + connection.destroy(); }); + + class WorldApi { + init() { + initPromise.resolve(); + } + + addTag(itemSpec) { + _addTag(itemSpec); + } + + addTags(itemSpecs) { + _addTags(itemSpecs); + } + + removeTag(id) { + _removeTag(id); + } + + removeTags(ids) { + _removeTags(ids); + } + + makeFile({ ext = 'txt' } = {}) { + const id = _makeId(); + const name = id + '.' + ext; + const mimeType = 'mime/' + ext.toLowerCase(); + const path = '/' + name; + const file = new Blob([], { + type: mimeType, + }); + file.path = path; + const files = [file]; + + return _makeFileTagFromSpec({ + id, + name, + mimeType, + files, + }).then(tagMesh => { + const { item } = tagMesh; + const { id, name } = item; + + return fs.makeFile('fs/' + id + '/' + name); + }); + } + } + const worldApi = new WorldApi(); + + return worldApi; } } - const worldApi = new WorldApi(); - - return worldApi; - } - }); + ); } unmount() { @@ -1042,9 +1100,18 @@ class World { const _clone = o => JSON.parse(JSON.stringify(o)); const _relativeWsUrl = s => { const l = window.location; - return ((l.protocol === 'https:') ? 'wss://' : 'ws://') + l.host + l.pathname + (!/\/$/.test(l.pathname) ? '/' : '') + s; + return ( + (l.protocol === 'https:' ? 'wss://' : 'ws://') + + l.host + + l.pathname + + (!/\/$/.test(l.pathname) ? '/' : '') + + s + ); }; -const _makeId = () => Math.random().toString(36).substring(7); +const _makeId = () => + Math.random() + .toString(36) + .substring(7); const _padNumber = (n, width) => { n = n + ''; return n.length >= width ? n : new Array(width - n.length + 1).join('0') + n; @@ -1077,6 +1144,7 @@ const _makeTagName = s => { } return s; }; -const _getPlugin = (module, version) => /^\//.test(module) ? module : `${module}@${version}`; +const _getPlugin = (module, version) => + /^\//.test(module) ? module : `${module}@${version}`; module.exports = World; diff --git a/core/engines/world/server.js b/core/engines/world/server.js index 867cb3e15..ae73af4e2 100644 --- a/core/engines/world/server.js +++ b/core/engines/world/server.js @@ -1,5 +1,5 @@ const events = require('events'); -const {EventEmitter} = events; +const { EventEmitter } = events; const path = require('path'); const fs = require('fs'); const crypto = require('crypto'); @@ -22,8 +22,8 @@ class World { } mount() { - const {_archae: archae} = this; - const {app, ws, wss, dirname, dataDirectory} = archae.getCore(); + const { _archae: archae } = this; + const { app, ws, wss, dirname, dataDirectory } = archae.getCore(); let live = true; this._cleanup = () => { @@ -34,51 +34,46 @@ class World { const worldTagsJsonPath = path.join(worldPath, 'tags.json'); const worldFilesJsonPath = path.join(worldPath, 'files.json'); - const _requestFile = (p, defaultValue) => new Promise((accept, reject) => { - fs.readFile(p, 'utf8', (err, s) => { - if (!err) { - const j = JSON.parse(s); - accept(j); - } else if (err.code === 'ENOENT') { - const j = defaultValue; - accept(j); - } else { - reject(err); - } + const _requestFile = (p, defaultValue) => + new Promise((accept, reject) => { + fs.readFile(p, 'utf8', (err, s) => { + if (!err) { + const j = JSON.parse(s); + accept(j); + } else if (err.code === 'ENOENT') { + const j = defaultValue; + accept(j); + } else { + reject(err); + } + }); }); - }); - const _requestTagsJson = () => _requestFile(worldTagsJsonPath, DEFAULT_TAGS); - const _requestFilesJson = () => _requestFile(worldFilesJsonPath, DEFAULT_FILES); - const _ensureWorldPath = () => new Promise((accept, reject) => { - const worldPath = path.join(dirname, dataDirectory, 'world'); - - mkdirp(worldPath, err => { - if (!err) { - accept(); - } else { - reject(err); - } + const _requestTagsJson = () => + _requestFile(worldTagsJsonPath, DEFAULT_TAGS); + const _requestFilesJson = () => + _requestFile(worldFilesJsonPath, DEFAULT_FILES); + const _ensureWorldPath = () => + new Promise((accept, reject) => { + const worldPath = path.join(dirname, dataDirectory, 'world'); + + mkdirp(worldPath, err => { + if (!err) { + accept(); + } else { + reject(err); + } + }); }); - }); return Promise.all([ - archae.requestPlugins([ - '/core/engines/multiplayer', - ]), + archae.requestPlugins(['/core/engines/multiplayer']), _requestTagsJson(), _requestFilesJson(), _ensureWorldPath(), - ]) - .then(([ - [ - multiplayer, - ], - tagsJson, - filesJson, - ensureWorldPathResult, - ]) => { - if (live) { - const _saveFile = (p, j) => new Promise((accept, reject) => { + ]).then(([[multiplayer], tagsJson, filesJson, ensureWorldPathResult]) => { + if (live) { + const _saveFile = (p, j) => + new Promise((accept, reject) => { fs.writeFile(p, JSON.stringify(j, null, 2), 'utf8', err => { if (!err) { accept(); @@ -87,42 +82,62 @@ class World { } }); }); - const _saveTags = _debounce(next => { - _saveFile(worldTagsJsonPath, tagsJson) - .then(() => { - next(); - }) - .catch(err => { - console.warn(err); - }); - }); - const _broadcastGlobal = (type, args) => { - if (connections.length > 0) { - const e = { - type, - args, - }; - const es = JSON.stringify(e); + const _saveTags = _debounce(next => { + _saveFile(worldTagsJsonPath, tagsJson) + .then(() => { + next(); + }) + .catch(err => { + console.warn(err); + }); + }); + const _broadcastGlobal = (type, args) => { + if (connections.length > 0) { + const e = { + type, + args, + }; + const es = JSON.stringify(e); - for (let i = 0; i < connections.length; i++) { - const connection = connections[i]; + for (let i = 0; i < connections.length; i++) { + const connection = connections[i]; - if (connection.readyState === ws.OPEN) { - connection.send(es); - } + if (connection.readyState === ws.OPEN) { + connection.send(es); } } - }; - const _removeTag = (userId, id) => { - delete tagsJson.tags[id]; + } + }; + const _removeTag = (userId, id) => { + delete tagsJson.tags[id]; + + _saveTags(); + + _broadcastGlobal('removeTag', [userId, id]); + }; + const _setTagAttribute = (userId, id, { name, value }) => { + const itemSpec = tagsJson.tags[id]; + const { attributes } = itemSpec; + if (value !== undefined) { + attributes[name] = { + value, + }; + } else { + delete attributes[name]; + } - _saveTags(); + _saveTags(); + + _broadcastGlobal('setTagAttribute', [userId, id, { name, value }]); + }; + const _setTagAttributes = (userId, id, newAttributes) => { + const itemSpec = tagsJson.tags[id]; + const { attributes } = itemSpec; + + for (let i = 0; i < newAttributes.length; i++) { + const newAttribute = newAttributes[i]; + const { name, value } = newAttribute; - _broadcastGlobal('removeTag', [userId, id]); - }; - const _setTagAttribute = (userId, id, {name, value}) => { - const itemSpec = tagsJson.tags[id]; - const {attributes} = itemSpec; if (value !== undefined) { attributes[name] = { value, @@ -130,102 +145,104 @@ class World { } else { delete attributes[name]; } + } - _saveTags(); + _saveTags(); - _broadcastGlobal('setTagAttribute', [userId, id, {name, value}]); - }; - const _setTagAttributes = (userId, id, newAttributes) => { - const itemSpec = tagsJson.tags[id]; - const {attributes} = itemSpec; + _broadcastGlobal('setTagAttributes', [userId, id, newAttributes]); + }; - for (let i = 0; i < newAttributes.length; i++) { - const newAttribute = newAttributes[i]; - const {name, value} = newAttribute; + function worldAddTag(req, res, next) { + bodyParserJson(req, res, () => { + const itemSpec = req.body; - if (value !== undefined) { - attributes[name] = { - value, - }; - } else { - delete attributes[name]; - } - } + const { id } = itemSpec; + tagsJson.tags[id] = itemSpec; _saveTags(); - _broadcastGlobal('setTagAttributes', [userId, id, newAttributes]); - }; - - function worldAddTag(req, res, next) { - bodyParserJson(req, res, () => { - const itemSpec = req.body; - - const {id} = itemSpec; - tagsJson.tags[id] = itemSpec; - - _saveTags(); + _broadcastLocal('addTags', [null, itemSpec]); + }); + } + app.post('/archae/world/addTag', worldAddTag); - _broadcastLocal('addTags', [null, itemSpec]); - }); - } - app.post('/archae/world/addTag', worldAddTag); + const connections = []; + const usersJson = {}; + wss.on('connection', c => { + const { url } = c.upgradeReq; - const connections = []; - const usersJson = {}; - wss.on('connection', c => { - const {url} = c.upgradeReq; + let match; + if ((match = url.match(/\/archae\/worldWs\?id=(.+)$/))) { + const userId = match[1]; - let match; - if (match = url.match(/\/archae\/worldWs\?id=(.+)$/)) { - const userId = match[1]; + const user = { + id: userId, + }; + usersJson[userId] = user; - const user = { - id: userId, + const _sendInit = () => { + const e = { + type: 'init', + args: [_arrayify(tagsJson.tags), _arrayify(usersJson)], }; - usersJson[userId] = user; + const es = JSON.stringify(e); + c.send(es); + }; + _sendInit(); - const _sendInit = () => { + const _broadcastLocal = (type, args) => { + if (connections.some(connection => connection !== c)) { const e = { - type: 'init', - args: [ - _arrayify(tagsJson.tags), - _arrayify(usersJson), - ], + type, + args, }; const es = JSON.stringify(e); - c.send(es); - }; - _sendInit(); - - const _broadcastLocal = (type, args) => { - if (connections.some(connection => connection !== c)) { - const e = { - type, - args, - }; - const es = JSON.stringify(e); - for (let i = 0; i < connections.length; i++) { - const connection = connections[i]; - if (connection !== c) { - connection.send(es); - } + for (let i = 0; i < connections.length; i++) { + const connection = connections[i]; + if (connection !== c) { + connection.send(es); } } - }; - const _removeTag = (userId, id) => { - const itemSpec = tagsJson.tags[id]; - delete tagsJson.tags[id]; + } + }; + const _removeTag = (userId, id) => { + const itemSpec = tagsJson.tags[id]; + delete tagsJson.tags[id]; - _saveTags(); + _saveTags(); - _broadcastLocal('removeTag', [userId, id]); - }; - const _setTagAttribute = (userId, id, {name, value}) => { - const itemSpec = tagsJson.tags[id]; - const {attributes} = itemSpec; - const oldValue = attributes[name] ? attributes[name].value : undefined; + _broadcastLocal('removeTag', [userId, id]); + }; + const _setTagAttribute = (userId, id, { name, value }) => { + const itemSpec = tagsJson.tags[id]; + const { attributes } = itemSpec; + const oldValue = attributes[name] + ? attributes[name].value + : undefined; + + if (value !== undefined) { + attributes[name] = { + value, + }; + } else { + delete attributes[name]; + } + + _saveTags(); + + _broadcastLocal('setTagAttribute', [userId, id, { name, value }]); + }; + const _setTagAttributes = (userId, id, newAttributes) => { + const itemSpec = tagsJson.tags[id]; + const { type, attributes } = itemSpec; + + for (let i = 0; i < newAttributes.length; i++) { + const newAttribute = newAttributes[i]; + const { name, value } = newAttribute; + const oldValue = attributes[name] + ? attributes[name].value + : undefined; if (value !== undefined) { attributes[name] = { @@ -234,191 +251,184 @@ class World { } else { delete attributes[name]; } + } - _saveTags(); - - _broadcastLocal('setTagAttribute', [userId, id, {name, value}]); - }; - const _setTagAttributes = (userId, id, newAttributes) => { - const itemSpec = tagsJson.tags[id]; - const {type, attributes} = itemSpec; - - for (let i = 0; i < newAttributes.length; i++) { - const newAttribute = newAttributes[i]; - const {name, value} = newAttribute; - const oldValue = attributes[name] ? attributes[name].value : undefined; - - if (value !== undefined) { - attributes[name] = { - value, - }; - } else { - delete attributes[name]; - } - } - - _saveTags(); + _saveTags(); - _broadcastLocal('setTagAttributes', [userId, id, newAttributes]); - }; + _broadcastLocal('setTagAttributes', [userId, id, newAttributes]); + }; - c.on('message', s => { - const m = _jsonParse(s); + c.on('message', s => { + const m = _jsonParse(s); - if (typeof m === 'object' && m !== null && typeof m.method === 'string' && Array.isArray(m.args)) { - const {method, args} = m; + if ( + typeof m === 'object' && + m !== null && + typeof m.method === 'string' && + Array.isArray(m.args) + ) { + const { method, args } = m; - if (method === 'addTag') { - const [userId, itemSpec] = args; + if (method === 'addTag') { + const [userId, itemSpec] = args; - const {id} = itemSpec; - tagsJson.tags[id] = itemSpec; + const { id } = itemSpec; + tagsJson.tags[id] = itemSpec; - _saveTags(); + _saveTags(); - _broadcastLocal('addTag', [userId, itemSpec]); - } else if (method === 'addTags') { - const [userId, itemSpecs] = args; + _broadcastLocal('addTag', [userId, itemSpec]); + } else if (method === 'addTags') { + const [userId, itemSpecs] = args; - for (let i = 0; i < itemSpecs.length; i++) { - const itemSpec = itemSpecs[i]; - const {id} = itemSpec; - tagsJson.tags[id] = itemSpec; - } + for (let i = 0; i < itemSpecs.length; i++) { + const itemSpec = itemSpecs[i]; + const { id } = itemSpec; + tagsJson.tags[id] = itemSpec; + } - _saveTags(); + _saveTags(); - _broadcastLocal('addTags', [userId, itemSpecs]); - } else if (method === 'removeTag') { - const [userId, id] = args; + _broadcastLocal('addTags', [userId, itemSpecs]); + } else if (method === 'removeTag') { + const [userId, id] = args; - _removeTag(userId, id); - } else if (method === 'removeTags') { - const [userId, ids] = args; + _removeTag(userId, id); + } else if (method === 'removeTags') { + const [userId, ids] = args; - for (let i = 0; i < ids.length; i++) { - const id = ids[i]; - delete tagsJson.tags[id]; - } + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + delete tagsJson.tags[id]; + } - _saveTags(); + _saveTags(); - _broadcastLocal('removeTags', [userId, ids]); - } else if (method === 'setTagAttribute') { - const [userId, id, {name, value}] = args; + _broadcastLocal('removeTags', [userId, ids]); + } else if (method === 'setTagAttribute') { + const [userId, id, { name, value }] = args; - _setTagAttribute(userId, id, {name, value}); - } else if (method === 'setTagAttributes') { - const [userId, id, newAttributes] = args; + _setTagAttribute(userId, id, { name, value }); + } else if (method === 'setTagAttributes') { + const [userId, id, newAttributes] = args; - _setTagAttributes(userId, id, newAttributes); - } else if (method === 'loadModule') { - const [userId, id] = args; + _setTagAttributes(userId, id, newAttributes); + } else if (method === 'loadModule') { + const [userId, id] = args; - _broadcastLocal('loadModule', [userId, id]); - } else if (method === 'unloadModule') { - const [userId, id] = args; + _broadcastLocal('loadModule', [userId, id]); + } else if (method === 'unloadModule') { + const [userId, id] = args; - _broadcastLocal('unloadModule', [userId, id]); - } else { - console.warn('no such method:' + JSON.stringify(method)); - } + _broadcastLocal('unloadModule', [userId, id]); } else { - console.warn('invalid message', m); + console.warn('no such method:' + JSON.stringify(method)); } - }); - c.on('close', () => { - delete usersJson[userId]; - connections.splice(connections.indexOf(c), 1); - }); + } else { + console.warn('invalid message', m); + } + }); + c.on('close', () => { + delete usersJson[userId]; + connections.splice(connections.indexOf(c), 1); + }); - connections.push(c); - } - }); + connections.push(c); + } + }); + + const _playerLeave = ({ address }) => { + const { tags } = tagsJson; + + const _inheritAbandonedAssets = (tags, address) => { + for (const id in tags) { + const tag = tags[id]; + const { type } = tag; + + if (type === 'asset') { + const { attributes } = tag; + const { + owner: ownerAttribute, + bindOwner: bindOwnerAttribute, + } = attributes; + const owner = ownerAttribute ? ownerAttribute.value : null; + const bindOwner = bindOwnerAttribute + ? bindOwnerAttribute.value + : null; + + if (owner === address) { + const { asset: assetAttribute } = attributes; + const { quantity: quantityAttribute } = attributes; + + if (assetAttribute && quantityAttribute) { + const srcAddress = address; + const { value: asset } = assetAttribute; + const { value: quantity } = quantityAttribute; + const privateKey = crypto.randomBytes(32); + const dstAddress = _getAddress(privateKey); + const privateKeyString = privateKey.toString('base64'); - const _playerLeave = ({address}) => { - const {tags} = tagsJson; - - const _inheritAbandonedAssets = (tags, address) => { - for (const id in tags) { - const tag = tags[id]; - const {type} = tag; - - if (type === 'asset') { - const {attributes} = tag; - const {owner: ownerAttribute, bindOwner: bindOwnerAttribute} = attributes; - const owner = ownerAttribute ? ownerAttribute.value : null; - const bindOwner = bindOwnerAttribute ? bindOwnerAttribute.value : null; - - if (owner === address) { - const {asset: assetAttribute} = attributes; - const {quantity: quantityAttribute} = attributes; - - if (assetAttribute && quantityAttribute) { - const srcAddress = address; - const {value: asset} = assetAttribute; - const {value: quantity} = quantityAttribute; - const privateKey = crypto.randomBytes(32); - const dstAddress = _getAddress(privateKey); - const privateKeyString = privateKey.toString('base64'); - - _removeTag(owner, id); - } else { - // remove the tag since it's corrupted - _removeTag(owner, id); - } - } else if (bindOwner === address) { + _removeTag(owner, id); + } else { + // remove the tag since it's corrupted _removeTag(owner, id); } + } else if (bindOwner === address) { + _removeTag(owner, id); } } - }; - _inheritAbandonedAssets(tags, address); + } }; - multiplayer.on('playerLeave', _playerLeave); + _inheritAbandonedAssets(tags, address); + }; + multiplayer.on('playerLeave', _playerLeave); + + this._cleanup = () => { + for (let i = 0; i < connections.length; i++) { + const connection = connections[i]; + connection.close(); + } - this._cleanup = () => { - for (let i = 0; i < connections.length; i++) { - const connection = connections[i]; - connection.close(); + function removeMiddlewares(route, i, routes) { + if (route.handle.name === 'worldAddTag') { + routes.splice(i, 1); } - - function removeMiddlewares(route, i, routes) { - if (route.handle.name === 'worldAddTag') { - routes.splice(i, 1); - } - if (route.route) { - route.route.stack.forEach(removeMiddlewares); - } + if (route.route) { + route.route.stack.forEach(removeMiddlewares); } - app._router.stack.forEach(removeMiddlewares); + } + app._router.stack.forEach(removeMiddlewares); - multiplayer.removeListener('playerLeave', _playerLeave); - }; + multiplayer.removeListener('playerLeave', _playerLeave); + }; - class WorldApi extends EventEmitter { - getTags() { - return tagsJson.tags; - } + class WorldApi extends EventEmitter { + getTags() { + return tagsJson.tags; + } - initTags() { - const plugins = []; - for (const id in tagsJson.tags) { - const tagSpec = tagsJson.tags[id]; - if (tagSpec.type === 'entity') { - plugins.push(path.isAbsolute(tagSpec.module) ? tagSpec.module : `${tagSpec.module}@${tagSpec.version}`); - } + initTags() { + const plugins = []; + for (const id in tagsJson.tags) { + const tagSpec = tagsJson.tags[id]; + if (tagSpec.type === 'entity') { + plugins.push( + path.isAbsolute(tagSpec.module) + ? tagSpec.module + : `${tagSpec.module}@${tagSpec.version}` + ); } - return archae.requestPlugins(plugins, { - hotload: true, - }); } + return archae.requestPlugins(plugins, { + hotload: true, + }); } - const worldApi = new WorldApi(); - - return worldApi; } - }); + const worldApi = new WorldApi(); + + return worldApi; + } + }); } unmount() { @@ -464,6 +474,7 @@ const _debounce = fn => { }; return _go; }; -const _getAddress = privateKey => Uint8Array.from(secp256k1.keyFromPrivate(privateKey).getPublic('arr')); +const _getAddress = privateKey => + Uint8Array.from(secp256k1.keyFromPrivate(privateKey).getPublic('arr')); module.exports = World; diff --git a/core/engines/zeo/client.js b/core/engines/zeo/client.js index 68a8c7c54..7864acb87 100644 --- a/core/engines/zeo/client.js +++ b/core/engines/zeo/client.js @@ -4,8 +4,8 @@ class Zeo { } mount() { - const {_archae: archae} = this; - const {metadata: {site: {url: siteUrl}}} = archae; + const { _archae: archae } = this; + const { metadata: { site: { url: siteUrl } } } = archae; let cleanups = []; const _cleanup = () => { @@ -24,104 +24,107 @@ class Zeo { const $ = s => document.querySelectorAll(s); const $$ = (el, s) => el.querySelectorAll(s); - const _requestBlocker = () => new Promise((accept, reject) => { - const loaderOverlay = $('#loader-overlay')[0]; - const loaderPlugin = $('#loader-plugin')[0]; - const loaderError = $('#loader-error')[0]; - - let loggedIn = false; - const pendingPlugins = {}; - let loadTimeout = null; - const _kickLoadTimeout = () => { - if (loadTimeout !== null) { - clearTimeout(loadTimeout); - } - - loadTimeout = setTimeout(() => { - loaderError.innerHTML = `\ + const _requestBlocker = () => + new Promise((accept, reject) => { + const loaderOverlay = $('#loader-overlay')[0]; + const loaderPlugin = $('#loader-plugin')[0]; + const loaderError = $('#loader-error')[0]; + + let loggedIn = false; + const pendingPlugins = {}; + let loadTimeout = null; + const _kickLoadTimeout = () => { + if (loadTimeout !== null) { + clearTimeout(loadTimeout); + } + + loadTimeout = setTimeout(() => { + loaderError.innerHTML = `\