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 = `\

:/

This is taking way too long. These plugins are hung:
- + `; - }, 10 * 1000); - }; - _kickLoadTimeout(); - - const pluginloadstart = plugin => { - if (pendingPlugins[plugin] === undefined) { - pendingPlugins[plugin] = 0; - } + }, 10 * 1000); + }; + _kickLoadTimeout(); - pendingPlugins[plugin]++; + const pluginloadstart = plugin => { + if (pendingPlugins[plugin] === undefined) { + pendingPlugins[plugin] = 0; + } - if (pendingPlugins[plugin] === 1) { - _updateText(); - } - }; - archae.on('pluginloadstart', pluginloadstart); - const pluginmount = plugin => { - pendingPlugins[plugin]--; - if (pendingPlugins[plugin] === 0) { - delete pendingPlugins[plugin]; + pendingPlugins[plugin]++; - _updateText(); - } - } - archae.on('pluginmount', pluginmount); + if (pendingPlugins[plugin] === 1) { + _updateText(); + } + }; + archae.on('pluginloadstart', pluginloadstart); + const pluginmount = plugin => { + pendingPlugins[plugin]--; + if (pendingPlugins[plugin] === 0) { + delete pendingPlugins[plugin]; + + _updateText(); + } + }; + archae.on('pluginmount', pluginmount); - const _updateText = () => { - loaderPlugin.innerText = (() => { - if (!loggedIn) { - return 'Logging in...'; - } else { - if (pendingPlugins.length > 0) { - return pendingPlugins[0]; + const _updateText = () => { + loaderPlugin.innerText = (() => { + if (!loggedIn) { + return 'Logging in...'; } else { - return 'Waiting for plugins...'; + if (pendingPlugins.length > 0) { + return pendingPlugins[0]; + } else { + return 'Waiting for plugins...'; + } } - } - })(); - }; + })(); + }; - const cleanup = () => { - loaderOverlay.style.display = 'none'; + const cleanup = () => { + loaderOverlay.style.display = 'none'; - archae.removeListener('pluginloadstart', pluginloadstart); - archae.removeListener('pluginmount', pluginmount); - }; - cleanups.push(cleanup); + archae.removeListener('pluginloadstart', pluginloadstart); + archae.removeListener('pluginmount', pluginmount); + }; + cleanups.push(cleanup); - const _setLoggedIn = () => { - loggedIn = true; + const _setLoggedIn = () => { + loggedIn = true; - _updateText(); - }; - const _destroy = () => { - cleanup(); - cleanups.splice(cleanups.indexOf(cleanup), 1); - }; - - accept({ - setLoggedIn: _setLoggedIn, - destroy: _destroy, + _updateText(); + }; + const _destroy = () => { + cleanup(); + cleanups.splice(cleanups.indexOf(cleanup), 1); + }; + + accept({ + setLoggedIn: _setLoggedIn, + destroy: _destroy, + }); }); - }); - return _requestBlocker() - .then(blocker => { - if (live) { - const _resJson = res => { - if (res.status >= 200 && res.status < 300) { - return res.json(); - } else { - return null; - } - }; - const _requestLogin = () => fetch(`${siteUrl}/id/api/address`, { + return _requestBlocker().then(blocker => { + if (live) { + const _resJson = res => { + if (res.status >= 200 && res.status < 300) { + return res.json(); + } else { + return null; + } + }; + const _requestLogin = () => + fetch(`${siteUrl}/id/api/address`, { credentials: 'include', }) .then(_resJson) - .then(({address}) => { + .then(({ address }) => { blocker.setLoggedIn(); return Promise.resolve(address); @@ -133,7 +136,8 @@ class Zeo { return Promise.resolve(null); }); - const _requestPlugins = () => archae.requestPlugins([ + const _requestPlugins = () => + archae.requestPlugins([ '/core/engines/bootstrap', '/core/engines/input', '/core/engines/webvr', @@ -177,11 +181,12 @@ class Zeo { '/core/utils/vrid-utils', ]); - return Promise.all([ - _requestLogin(), - _requestPlugins(), - ]) - .then(([ + return Promise.all([ + _requestLogin(), + _requestPlugins(), + ]).then( + ( + [ address, [ bootstrap, @@ -226,100 +231,100 @@ class Zeo { spriteUtils, vridUtils, ], - ]) => { - if (live) { - blocker.destroy(); - - const {THREE, scene, camera, renderer} = three; - const {domElement} = renderer; - const {EVENTS: INPUT_EVENTS} = input; - const {events} = jsUtils; - const {EventEmitter} = events; - - address = address || '127.0.0.1'; - bootstrap.setAddress(address); - const supportsWebVR = webvr.supportsWebVR(); - - const _update = () => { - rend.update(); - }; - /* const _updateEye = camera => { + ] + ) => { + if (live) { + blocker.destroy(); + + const { THREE, scene, camera, renderer } = three; + const { domElement } = renderer; + const { EVENTS: INPUT_EVENTS } = input; + const { events } = jsUtils; + const { EventEmitter } = events; + + address = address || '127.0.0.1'; + bootstrap.setAddress(address); + const supportsWebVR = webvr.supportsWebVR(); + + const _update = () => { + rend.update(); + }; + /* const _updateEye = camera => { rend.updateEye(camera); }; */ - const _updateStart = () => { - rend.updateStart(); - }; - const _updateEnd = () => { - rend.updateEnd(); - }; - /* const _renderStart = () => { + const _updateStart = () => { + rend.updateStart(); + }; + const _updateEnd = () => { + rend.updateEnd(); + }; + /* const _renderStart = () => { rend.renderStart(); }; const _renderEnd = () => { rend.renderEnd(); }; */ - const _enterNormal = () => { - _stopRenderLoop(); + const _enterNormal = () => { + _stopRenderLoop(); - renderLoop = webvr.requestRenderLoop({ - update: _update, - // updateEye: _updateEye, - updateStart: _updateStart, - updateEnd: _updateEnd, - /* renderStart: _renderStart, + renderLoop = webvr.requestRenderLoop({ + update: _update, + // updateEye: _updateEye, + updateStart: _updateStart, + updateEnd: _updateEnd, + /* renderStart: _renderStart, renderEnd: _renderEnd, */ - }); + }); - return renderLoop; - }; - const _enterVR = ({stereoscopic, onExit}) => { - _stopRenderLoop(); - - const _onExit = () => { - onExit(); - - _enterNormal(); - }; + return renderLoop; + }; + const _enterVR = ({ stereoscopic, onExit }) => { + _stopRenderLoop(); - renderLoop = webvr.requestEnterVR({ - stereoscopic, - update: _update, - // updateEye: _updateEye, - updateStart: _updateStart, - updateEnd: _updateEnd, - /* renderStart: _renderStart, - renderEnd: _renderEnd, */ - onExit: _onExit, - }); + const _onExit = () => { + onExit(); - return renderLoop; + _enterNormal(); }; - let renderLoop = null; - const _stopRenderLoop = () => { - if (renderLoop) { - renderLoop.destroy(); - renderLoop = null; - } - }; - const _startRenderLoop = () => { - cleanups.push(() => { - _stopRenderLoop(); - }); + renderLoop = webvr.requestEnterVR({ + stereoscopic, + update: _update, + // updateEye: _updateEye, + updateStart: _updateStart, + updateEnd: _updateEnd, + /* renderStart: _renderStart, + renderEnd: _renderEnd, */ + onExit: _onExit, + }); + + return renderLoop; + }; + + let renderLoop = null; + const _stopRenderLoop = () => { + if (renderLoop) { + renderLoop.destroy(); + renderLoop = null; + } + }; + const _startRenderLoop = () => { + cleanups.push(() => { + _stopRenderLoop(); + }); - return _enterNormal(); - }; + return _enterNormal(); + }; - return _startRenderLoop() - .then(() => { - if (live) { - renderer.domElement.style.display = 'block'; + return _startRenderLoop().then(() => { + if (live) { + renderer.domElement.style.display = 'block'; - // begin helper content + // begin helper content - const overlay = document.createElement('div'); - overlay.style.cssText = `\ + const overlay = document.createElement('div'); + overlay.style.cssText = `\ display: flex; position: absolute; top: 0; @@ -329,243 +334,257 @@ class Zeo { align-items: center; font-family: ${biolumi.getFonts()}; `; - overlay.innerHTML = `\ + overlay.innerHTML = `\
`; - document.body.insertBefore(overlay, renderer.domElement.nextSibling); - const overlayContent = overlay.querySelector('.overlay-content'); - - const helper = document.createElement('div'); - helper.style.cssText = `\ + document.body.insertBefore( + overlay, + renderer.domElement.nextSibling + ); + const overlayContent = overlay.querySelector( + '.overlay-content' + ); + + const helper = document.createElement('div'); + helper.style.cssText = `\ display: flex; width: 500px; flex-direction: column; justify-content: center; align-items: center; `; - const fonts = biolumi.getFonts().replace(/"/g, "'"); - helper.innerHTML = `\ + const fonts = biolumi.getFonts().replace(/"/g, "'"); + helper.innerHTML = `\

Paused

`; - helper.addEventListener('dragover', e => { - e.preventDefault(); // needed to prevent browser drag-and-drop behavior - }); + helper.addEventListener('dragover', e => { + e.preventDefault(); // needed to prevent browser drag-and-drop behavior + }); - const enterHelperContent = document.createElement('div'); - enterHelperContent.style.cssText = `display: flex; width: 500px; margin-bottom: 20px;`; - enterHelperContent.innerHTML = `\ + const enterHelperContent = document.createElement('div'); + enterHelperContent.style.cssText = `display: flex; width: 500px; margin-bottom: 20px;`; + enterHelperContent.innerHTML = `\ `; - const errorMessage = document.createElement('div'); - errorMessage.style.cssText = `width: 100%; height: 80px;`; - errorMessage.innerHTML = `\ + const errorMessage = document.createElement('div'); + errorMessage.style.cssText = `width: 100%; height: 80px;`; + errorMessage.innerHTML = `\
No WebVR
WebVR is not supported by your browser, so you can't use a headset. Learn more `; - const _enterHeadsetVR = () => { - _enterVR({ - stereoscopic: true, - onExit: () => { - overlay.style.display = 'flex'; - - bootstrap.setVrMode(null); - }, - }); - - bootstrap.setVrMode('hmd'); - - overlay.style.display = 'none'; - }; - const _enterKeyboardVR = () => { - _enterVR({ - stereoscopic: false, - onExit: () => { - overlay.style.display = 'flex'; - - bootstrap.setVrMode(null); - }, - }); - - bootstrap.setVrMode('keyboard'); - - overlay.style.display = 'none'; - }; - - const _styleButton = button => { - button.addEventListener('mouseover', e => { - button.style.backgroundColor = '#FFF'; - button.style.borderColor = 'transparent'; - button.style.color = '#000'; - }); - button.addEventListener('mouseout', e => { - button.style.backgroundColor = 'transparent'; - button.style.borderColor = 'currentColor'; - button.style.color = '#FFF'; - }); - }; - - const headsetButton = $$(enterHelperContent, '.headset-button')[0]; - if (supportsWebVR) { - _styleButton(headsetButton); - - headsetButton.addEventListener('click', e => { - if (!webvr.isPresenting()) { - _enterHeadsetVR(); - } - }); - - errorMessage.style.display = 'none'; - - window.onvrdisplayactivate = null; - window.addEventListener('vrdisplayactivate', e => { - _enterHeadsetVR(); - }); - - const urlRequestPresent = _getQueryVariable('e'); - if (webvr.displayIsPresenting() || urlRequestPresent === 'hmd') { - _enterHeadsetVR(); - } else if (urlRequestPresent === 'keyboard') { - _enterKeyboardVR(); - } - } else { - headsetButton.style.display = 'none'; - } + const _enterHeadsetVR = () => { + _enterVR({ + stereoscopic: true, + onExit: () => { + overlay.style.display = 'flex'; - const keyboardButton = $$(enterHelperContent, '.keyboard-button')[0]; - _styleButton(keyboardButton); + bootstrap.setVrMode(null); + }, + }); - keyboardButton.addEventListener('click', e => { - if (!webvr.isPresenting()) { - _enterKeyboardVR(); - } - }); + bootstrap.setVrMode('hmd'); - overlayContent.appendChild(helper); - helper.appendChild(enterHelperContent); - helper.appendChild(errorMessage); + overlay.style.display = 'none'; + }; + const _enterKeyboardVR = () => { + _enterVR({ + stereoscopic: false, + onExit: () => { + overlay.style.display = 'flex'; - // end helper content + bootstrap.setVrMode(null); + }, + }); - class Listener { - constructor(handler, priority) { - this.handler = handler; - this.priority = priority; - } + bootstrap.setVrMode('keyboard'); + + overlay.style.display = 'none'; + }; + + const _styleButton = button => { + button.addEventListener('mouseover', e => { + button.style.backgroundColor = '#FFF'; + button.style.borderColor = 'transparent'; + button.style.color = '#000'; + }); + button.addEventListener('mouseout', e => { + button.style.backgroundColor = 'transparent'; + button.style.borderColor = 'currentColor'; + button.style.color = '#FFF'; + }); + }; + + const headsetButton = $$( + enterHelperContent, + '.headset-button' + )[0]; + if (supportsWebVR) { + _styleButton(headsetButton); + + headsetButton.addEventListener('click', e => { + if (!webvr.isPresenting()) { + _enterHeadsetVR(); } + }); + + errorMessage.style.display = 'none'; + + window.onvrdisplayactivate = null; + window.addEventListener('vrdisplayactivate', e => { + _enterHeadsetVR(); + }); + + const urlRequestPresent = _getQueryVariable('e'); + if ( + webvr.displayIsPresenting() || + urlRequestPresent === 'hmd' + ) { + _enterHeadsetVR(); + } else if (urlRequestPresent === 'keyboard') { + _enterKeyboardVR(); + } + } else { + headsetButton.style.display = 'none'; + } - const _makeEventListener = () => { - const listeners = []; + const keyboardButton = $$( + enterHelperContent, + '.keyboard-button' + )[0]; + _styleButton(keyboardButton); - const result = e => { - let live = true; - e.stopImmediatePropagation = (stopImmediatePropagation => () => { - live = false; + keyboardButton.addEventListener('click', e => { + if (!webvr.isPresenting()) { + _enterKeyboardVR(); + } + }); - stopImmediatePropagation.call(e); - })(e.stopImmediatePropagation); + overlayContent.appendChild(helper); + helper.appendChild(enterHelperContent); + helper.appendChild(errorMessage); - const oldListeners = listeners.slice(); - for (let i = 0; i < oldListeners.length; i++) { - const listener = oldListeners[i]; - const {handler} = listener; + // end helper content - handler(e); + class Listener { + constructor(handler, priority) { + this.handler = handler; + this.priority = priority; + } + } - if (!live) { - break; - } - } - }; - result.add = (handler, {priority}) => { - const listener = new Listener(handler, priority); - listeners.push(listener); - listeners.sort((a, b) => b.priority - a.priority); - }; - result.remove = handler => { - const index = listeners.indexOf(handler); - if (index !== -1) { - listeners.splice(index, 1); - } - }; + const _makeEventListener = () => { + const listeners = []; + + const result = e => { + let live = true; + e.stopImmediatePropagation = (stopImmediatePropagation => () => { + live = false; - return result; - }; + stopImmediatePropagation.call(e); + })(e.stopImmediatePropagation); - this._cleanup = () => { - _stopRenderLoop(); - }; + const oldListeners = listeners.slice(); + for (let i = 0; i < oldListeners.length; i++) { + const listener = oldListeners[i]; + const { handler } = listener; - class ZeoThreeApi { - constructor() { - this.THREE = THREE; - this.scene = scene; - this.camera = camera; - this.renderer = renderer; + handler(e); + + if (!live) { + break; } } + }; + result.add = (handler, { priority }) => { + const listener = new Listener(handler, priority); + listeners.push(listener); + listeners.sort((a, b) => b.priority - a.priority); + }; + result.remove = handler => { + const index = listeners.indexOf(handler); + if (index !== -1) { + listeners.splice(index, 1); + } + }; - class ZeoPoseApi { - getStatus() { - return webvr.getStatus(); - } + return result; + }; - getStageMatrix() { - return webvr.getStageMatrix(); - } + this._cleanup = () => { + _stopRenderLoop(); + }; - setStageMatrix(stageMatrix) { - webvr.setStageMatrix(stageMatrix); - } + class ZeoThreeApi { + constructor() { + this.THREE = THREE; + this.scene = scene; + this.camera = camera; + this.renderer = renderer; + } + } - updateStatus() { - webvr.updateStatus(); - } + class ZeoPoseApi { + getStatus() { + return webvr.getStatus(); + } - resetPose() { - webvr.resetPose(); - } + getStageMatrix() { + return webvr.getStageMatrix(); + } - getControllerLinearVelocity(side) { - const player = cyborg.getPlayer(); - return player.getControllerLinearVelocity(side); - } + setStageMatrix(stageMatrix) { + webvr.setStageMatrix(stageMatrix); + } - getControllerAngularVelocity(side) { - const player = cyborg.getPlayer(); - return player.getControllerAngularVelocity(side); - } + updateStatus() { + webvr.updateStatus(); + } - getVrMode() { - return bootstrap.getVrMode(); - } - } + resetPose() { + webvr.resetPose(); + } - class ZeoInputApi { - on(event, handler, options) { - return input.on(event, handler, options); - } + getControllerLinearVelocity(side) { + const player = cyborg.getPlayer(); + return player.getControllerLinearVelocity(side); + } - removeListener(event, handler) { - return input.removeListener(event, handler); - } + getControllerAngularVelocity(side) { + const player = cyborg.getPlayer(); + return player.getControllerAngularVelocity(side); + } - removeAllListeners(event) { - return input.removeAllListeners(event); - } + getVrMode() { + return bootstrap.getVrMode(); + } + } - vibrate(side, value, time) { - webvr.vibrate(side, value, time); - } - } + class ZeoInputApi { + on(event, handler, options) { + return input.on(event, handler, options); + } - class ZeoRenderApi { - constructor() { - /* rend.on('update', () => { + removeListener(event, handler) { + return input.removeListener(event, handler); + } + + removeAllListeners(event) { + return input.removeAllListeners(event); + } + + vibrate(side, value, time) { + webvr.vibrate(side, value, time); + } + } + + class ZeoRenderApi { + constructor() { + /* rend.on('update', () => { this.emit('update'); }); rend.on('updateStart', () => { @@ -586,51 +605,51 @@ class Zeo { tags.on('mutate', () => { this.emit('mutate'); }); */ - } + } - on(event, handler) { - return rend.on(event, handler); - } + on(event, handler) { + return rend.on(event, handler); + } - removeListener(event, handler) { - return rend.removeListener(event, handler); - } + removeListener(event, handler) { + return rend.removeListener(event, handler); + } - removeAllListeners(event) { - return rend.removeAllListeners(event); - } - } + removeAllListeners(event) { + return rend.removeAllListeners(event); + } + } - class ZeoElementsApi { - registerEntity(pluginInstance, componentApi) { - tags.registerEntity(pluginInstance, componentApi); - } + class ZeoElementsApi { + registerEntity(pluginInstance, componentApi) { + tags.registerEntity(pluginInstance, componentApi); + } - unregisterEntity(pluginInstance, componentApi) { - tags.unregisterEntity(pluginInstance, componentApi); - } + unregisterEntity(pluginInstance, componentApi) { + tags.unregisterEntity(pluginInstance, componentApi); + } - getWorldElement() { - return tags.getWorldElement(); - } + getWorldElement() { + return tags.getWorldElement(); + } - getEntitiesElement() { - return tags.getEntitiesElement(); - } + getEntitiesElement() { + return tags.getEntitiesElement(); + } - makeListener(selector) { - return tags.makeListener(selector); - } + makeListener(selector) { + return tags.makeListener(selector); + } - destroyListener(listener) { - tags.destroyListener(listener); - } + destroyListener(listener) { + tags.destroyListener(listener); + } - requestElement(selector) { - return tags.requestElement(selector); - } + requestElement(selector) { + return tags.requestElement(selector); + } - /* on(event, handler) { + /* on(event, handler) { return tags.on(event, handler); } @@ -641,372 +660,388 @@ class Zeo { removeAllListeners(event) { return tags.removeAllListeners(event); } */ - } + } - class ZeoWorldApi { - getWorldTime() { - return bootstrap.getWorldTime(); - } + class ZeoWorldApi { + getWorldTime() { + return bootstrap.getWorldTime(); + } - getTags() { - return bootstrap.getWorldTime(); - } + getTags() { + return bootstrap.getWorldTime(); + } - getSpawnMatrix() { - return webvr.getSpawnMatrix(); - } + getSpawnMatrix() { + return webvr.getSpawnMatrix(); + } - setSpawnMatrix(spawnMatrix) { - webvr.setSpawnMatrix(spawnMatrix); - } - } + setSpawnMatrix(spawnMatrix) { + webvr.setSpawnMatrix(spawnMatrix); + } + } - /* const controllerMeshes = (() => { + /* const controllerMeshes = (() => { const controllers = cyborg.getControllers(); return { left: controllers['left'].mesh, right: controllers['right'].mesh, }; })(); */ - class ZeoPlayerApi { - getId() { - return multiplayer.getId(); - } + class ZeoPlayerApi { + getId() { + return multiplayer.getId(); + } - /* getControllerMeshes() { + /* getControllerMeshes() { return controllerMeshes; } */ - getRemoteStatuses() { - return multiplayer.getPlayerStatuses(); - } + getRemoteStatuses() { + return multiplayer.getPlayerStatuses(); + } - getRemoteStatus(userId) { - return multiplayer.getPlayerStatus(userId); - } + getRemoteStatus(userId) { + return multiplayer.getPlayerStatus(userId); + } - getRemoteHmdMesh(userId) { - const remotePlayerMesh = multiplayer.getRemotePlayerMesh(userId); + getRemoteHmdMesh(userId) { + const remotePlayerMesh = multiplayer.getRemotePlayerMesh( + userId + ); - if (remotePlayerMesh) { - const {hmd: hmdMesh} = remotePlayerMesh; - return hmdMesh; - } else { - return null; - } - } + if (remotePlayerMesh) { + const { hmd: hmdMesh } = remotePlayerMesh; + return hmdMesh; + } else { + return null; + } + } - getRemoteControllerMeshes(userId) { - return multiplayer.getRemoteControllerMeshes(userId); - } + getRemoteControllerMeshes(userId) { + return multiplayer.getRemoteControllerMeshes(userId); + } - setSkin(skinImg) { - return cyborg.setSkin(skinImg); - } + setSkin(skinImg) { + return cyborg.setSkin(skinImg); + } - on(event, handler) { - return multiplayer.on(event, handler); - } + on(event, handler) { + return multiplayer.on(event, handler); + } - removeListener(event, handler) { - return multiplayer.removeListener(event, handler); - } + removeListener(event, handler) { + return multiplayer.removeListener(event, handler); + } - removeAllListeners(event) { - return multiplayer.removeAllListeners(event); - } - } + removeAllListeners(event) { + return multiplayer.removeAllListeners(event); + } + } - class ZeoUiApi { - makeUi(options) { - return biolumi.makeUi(options); - } + class ZeoUiApi { + makeUi(options) { + return biolumi.makeUi(options); + } - addPage(page) { - rend.addPage(page); - } + addPage(page) { + rend.addPage(page); + } - removePage(page) { - rend.removePage(page); - } + removePage(page) { + rend.removePage(page); + } - getHoverState(side) { - return rend.getHoverState(side); - } + getHoverState(side) { + return rend.getHoverState(side); + } - getTransparentImg() { - return biolumi.getTransparentImg(); - } - } + getTransparentImg() { + return biolumi.getTransparentImg(); + } + } - class ZeoSoundApi { - requestSfx(url) { - return somnifer.requestSfx(url); - } + class ZeoSoundApi { + requestSfx(url) { + return somnifer.requestSfx(url); + } - makeBody() { - return somnifer.makeBody(); - } - } + makeBody() { + return somnifer.makeBody(); + } + } - class ZeoStageApi { - getStage() { - return stage.getStage(); - } + class ZeoStageApi { + getStage() { + return stage.getStage(); + } - setStage(st) { - stage.setStage(st); - } + setStage(st) { + stage.setStage(st); + } - add(st, object) { - stage.add(st, object); - } + add(st, object) { + stage.add(st, object); + } - remove(st, object) { - stage.remove(st, object); - } + remove(st, object) { + stage.remove(st, object); + } - on() { - return stage.on.apply(stage, arguments); - } + on() { + return stage.on.apply(stage, arguments); + } - removeListener() { - return stage.removeListener.apply(stage, arguments); - } + removeListener() { + return stage.removeListener.apply(stage, arguments); + } - removeAllListeners() { - return stage.removeAllListeners.apply(stage, arguments); - } - } + removeAllListeners() { + return stage.removeAllListeners.apply(stage, arguments); + } + } - class ZeoStckApi { - requestCheck() { - return stck.requestCheck.apply(stck, arguments); - } + class ZeoStckApi { + requestCheck() { + return stck.requestCheck.apply(stck, arguments); + } - makeDynamicBoxBody() { - return stck.makeDynamicBoxBody.apply(stck, arguments); - } + makeDynamicBoxBody() { + return stck.makeDynamicBoxBody.apply(stck, arguments); + } - makeStaticHeightfieldBody() { - return stck.makeStaticHeightfieldBody.apply(stck, arguments); - } + makeStaticHeightfieldBody() { + return stck.makeStaticHeightfieldBody.apply( + stck, + arguments + ); + } - makeStaticEtherfieldBody() { - return stck.makeStaticEtherfieldBody.apply(stck, arguments); - } + makeStaticEtherfieldBody() { + return stck.makeStaticEtherfieldBody.apply( + stck, + arguments + ); + } - makeStaticBlockfieldBody() { - return stck.makeStaticBlockfieldBody.apply(stck, arguments); - } + makeStaticBlockfieldBody() { + return stck.makeStaticBlockfieldBody.apply( + stck, + arguments + ); + } - destroyBody(body) { - stck.destroyBody(body); - } - } + destroyBody(body) { + stck.destroyBody(body); + } + } - class ZeoIntersectApi { - makeIntersecter() { - return intersect.makeIntersecter(); - } + class ZeoIntersectApi { + makeIntersecter() { + return intersect.makeIntersecter(); + } - destroyIntersecter(object) { - intersect.destroyIntersecter(object); - } - } + destroyIntersecter(object) { + intersect.destroyIntersecter(object); + } + } - class ZeoTeleportApi { - addTarget(object, options) { - teleport.addTarget(object, options); - } + class ZeoTeleportApi { + addTarget(object, options) { + teleport.addTarget(object, options); + } - removeTarget(object) { - teleport.removeTarget(object); - } + removeTarget(object) { + teleport.removeTarget(object); + } - getHoverState(side) { - return teleport.getHoverState(side); - } + getHoverState(side) { + return teleport.getHoverState(side); + } - on(event, handler, options) { - return teleport.on(event, handler, options); - } + on(event, handler, options) { + return teleport.on(event, handler, options); + } - removeListener(event, handler) { - return teleport.removeListener(event, handler); - } + removeListener(event, handler) { + return teleport.removeListener(event, handler); + } - removeAllListeners(event) { - return teleport.removeAllListeners(event); - } - } + removeAllListeners(event) { + return teleport.removeAllListeners(event); + } + } - class ZeoHandsApi { - makeGrabbable(id, options) { - return hand.makeGrabbable(id, options); - } + class ZeoHandsApi { + makeGrabbable(id, options) { + return hand.makeGrabbable(id, options); + } - destroyGrabbable(grabbable) { - hand.destroyGrabbable(grabbable); - } + destroyGrabbable(grabbable) { + hand.destroyGrabbable(grabbable); + } - getGrabbedGrabbable(side) { - return hand.getGrabbedGrabbable(side); - } + getGrabbedGrabbable(side) { + return hand.getGrabbedGrabbable(side); + } - on() { - return hand.on.apply(hand, arguments); - } + on() { + return hand.on.apply(hand, arguments); + } - removeListener() { - return hand.removeListener.apply(hand, arguments); - } + removeListener() { + return hand.removeListener.apply(hand, arguments); + } - removeAllListeners() { - return hand.removeAllListeners.apply(hand, arguments); - } - } + removeAllListeners() { + return hand.removeAllListeners.apply(hand, arguments); + } + } - class ZeoTransformApi { - makeTransformGizmo(spec) { - return transform.makeTransformGizmo(spec); - } + class ZeoTransformApi { + makeTransformGizmo(spec) { + return transform.makeTransformGizmo(spec); + } - destroyTransformGizmo(transformGizmo) { - return transform.destroyTransformGizmo(transformGizmo); - } - } + destroyTransformGizmo(transformGizmo) { + return transform.destroyTransformGizmo(transformGizmo); + } + } - class ZeoAnimationApi { - makeAnimation(startValue, endValue, duration) { - return anima.makeAnimation(startValue, endValue, duration); - } - } + class ZeoAnimationApi { + makeAnimation(startValue, endValue, duration) { + return anima.makeAnimation( + startValue, + endValue, + duration + ); + } + } - class ZeoItemsApi { - getItem(id) { - return wallet.getAsset(id); - } + class ZeoItemsApi { + getItem(id) { + return wallet.getAsset(id); + } - makeItem(itemSpec) { - return wallet.makeItem(itemSpec); - } + makeItem(itemSpec) { + return wallet.makeItem(itemSpec); + } - destroyItem(item) { - wallet.destroyItem(item); - } + destroyItem(item) { + wallet.destroyItem(item); + } - makeFile(options) { - return wallet.makeFile(options); - } + makeFile(options) { + return wallet.makeFile(options); + } - reifyFile(options) { - return wallet.reifyFile(options); - } + reifyFile(options) { + return wallet.reifyFile(options); + } - getFile(id) { - return fs.makeRemoteFile(id); - } + getFile(id) { + return fs.makeRemoteFile(id); + } - registerItem(pluginInstance, itemApi) { - wallet.registerItem(pluginInstance, itemApi); - } + registerItem(pluginInstance, itemApi) { + wallet.registerItem(pluginInstance, itemApi); + } - unregisterItem(pluginInstance, itemApi) { - wallet.unregisterItem(pluginInstance, itemApi); - } + unregisterItem(pluginInstance, itemApi) { + wallet.unregisterItem(pluginInstance, itemApi); + } - registerEquipment(pluginInstance, equipmentApi) { - wallet.registerEquipment(pluginInstance, equipmentApi); - } + registerEquipment(pluginInstance, equipmentApi) { + wallet.registerEquipment(pluginInstance, equipmentApi); + } - unregisterEquipment(pluginInstance, equipmentApi) { - wallet.unregisterEquipment(pluginInstance, equipmentApi); - } + unregisterEquipment(pluginInstance, equipmentApi) { + wallet.unregisterEquipment(pluginInstance, equipmentApi); + } - registerRecipe(pluginInstance, recipe) { - craft.registerRecipe(pluginInstance, recipe); - } + registerRecipe(pluginInstance, recipe) { + craft.registerRecipe(pluginInstance, recipe); + } - unregisterRecipe(pluginInstance, recipe) { - craft.unregisterRecipe(pluginInstance, recipe); - } + unregisterRecipe(pluginInstance, recipe) { + craft.unregisterRecipe(pluginInstance, recipe); + } - getAssetsMaterial() { - return wallet.getAssetsMaterial(); - } + getAssetsMaterial() { + return wallet.getAssetsMaterial(); + } - on() { - return wallet.on.apply(wallet, arguments); - } + on() { + return wallet.on.apply(wallet, arguments); + } - removeListener() { - return wallet.removeListener.apply(wallet, arguments); - } + removeListener() { + return wallet.removeListener.apply(wallet, arguments); + } - removeAllListeners() { - return wallet.removeAllListeners.apply(wallet, arguments); - } - } + removeAllListeners() { + return wallet.removeAllListeners.apply(wallet, arguments); + } + } - class ZeoJsApi { - constructor() { - this.events = events; - } - } + class ZeoJsApi { + constructor() { + this.events = events; + } + } - class ZeoUtilsApi { - constructor() { - this.js = jsUtils; - this.network = networkUtils; - this.geometry = geometryUtils; - this.hash = hashUtils; - this.random = randomUtils; - this.text = textUtils; - this.menu = menuUtils; - this.skin = skinUtils; - this.creature = creatureUtils; - this.sprite = spriteUtils; - } - } + class ZeoUtilsApi { + constructor() { + this.js = jsUtils; + this.network = networkUtils; + this.geometry = geometryUtils; + this.hash = hashUtils; + this.random = randomUtils; + this.text = textUtils; + this.menu = menuUtils; + this.skin = skinUtils; + this.creature = creatureUtils; + this.sprite = spriteUtils; + } + } - class ZeoApi extends EventEmitter { - constructor() { - super(); - - this.three = new ZeoThreeApi(); - this.pose = new ZeoPoseApi(); - this.input = new ZeoInputApi(); - this.render = new ZeoRenderApi(); - this.elements = new ZeoElementsApi(); - this.world = new ZeoWorldApi(); - this.player = new ZeoPlayerApi(); - this.ui = new ZeoUiApi(); - this.sound = new ZeoSoundApi(); - this.stage = new ZeoStageApi(); - this.stck = new ZeoStckApi(); - this.teleport = new ZeoTeleportApi(); - this.hands = new ZeoHandsApi(); - this.transform = new ZeoTransformApi(); - this.animation = new ZeoAnimationApi(); - this.items = new ZeoItemsApi(); - this.utils = new ZeoUtilsApi(); - } - } - const zeoApi = new ZeoApi(); - window.zeo = zeoApi; + class ZeoApi extends EventEmitter { + constructor() { + super(); + + this.three = new ZeoThreeApi(); + this.pose = new ZeoPoseApi(); + this.input = new ZeoInputApi(); + this.render = new ZeoRenderApi(); + this.elements = new ZeoElementsApi(); + this.world = new ZeoWorldApi(); + this.player = new ZeoPlayerApi(); + this.ui = new ZeoUiApi(); + this.sound = new ZeoSoundApi(); + this.stage = new ZeoStageApi(); + this.stck = new ZeoStckApi(); + this.teleport = new ZeoTeleportApi(); + this.hands = new ZeoHandsApi(); + this.transform = new ZeoTransformApi(); + this.animation = new ZeoAnimationApi(); + this.items = new ZeoItemsApi(); + this.utils = new ZeoUtilsApi(); + } + } + const zeoApi = new ZeoApi(); + window.zeo = zeoApi; - renderer.compile(scene, camera); + renderer.compile(scene, camera); - world.init(); + world.init(); - return zeoApi; - } - }) - } - }); - } - }); + return zeoApi; + } + }); + } + } + ); + } + }); } unmount() { diff --git a/core/engines/zeo/server.js b/core/engines/zeo/server.js index e92b6fa75..9bf677b3f 100644 --- a/core/engines/zeo/server.js +++ b/core/engines/zeo/server.js @@ -7,111 +7,115 @@ class Zeo { } 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 = () => { live = false; }; - return archae.requestPlugins([ - '/core/engines/three', - '/core/engines/tags', - '/core/engines/world', - '/core/engines/multiplayer', - '/core/engines/fs', - '/core/utils/js-utils', - '/core/utils/hash-utils', - '/core/utils/random-utils', - '/core/utils/image-utils', - ]) - .then(([ - three, - tags, - world, - multiplayer, - fs, - jsUtils, - hashUtils, - randomUtils, - imageUtils, - ]) => { - if (live) { - class ZeoThreeApi { - constructor() { - this.THREE = three.THREE; + return archae + .requestPlugins([ + '/core/engines/three', + '/core/engines/tags', + '/core/engines/world', + '/core/engines/multiplayer', + '/core/engines/fs', + '/core/utils/js-utils', + '/core/utils/hash-utils', + '/core/utils/random-utils', + '/core/utils/image-utils', + ]) + .then( + ( + [ + three, + tags, + world, + multiplayer, + fs, + jsUtils, + hashUtils, + randomUtils, + imageUtils, + ] + ) => { + if (live) { + class ZeoThreeApi { + constructor() { + this.THREE = three.THREE; + } } - } - class ZeoElementsApi { - getWorldElement() { - return tags.getWorldElement(); - } + class ZeoElementsApi { + getWorldElement() { + return tags.getWorldElement(); + } - requestElement(selector) { - return tags.requestElement(selector); - } + requestElement(selector) { + return tags.requestElement(selector); + } - registerEntity(pluginInstance, entityApi) { - tags.registerEntity(pluginInstance, entityApi); - } + registerEntity(pluginInstance, entityApi) { + tags.registerEntity(pluginInstance, entityApi); + } - unregisterEntity(pluginInstance, entityApi) { - tags.unregisterEntity(pluginInstance, entityApi); + unregisterEntity(pluginInstance, entityApi) { + tags.unregisterEntity(pluginInstance, entityApi); + } } - } - class ZeoWorldApi { - getTags() { - return world.getTags(); + class ZeoWorldApi { + getTags() { + return world.getTags(); + } } - } - class ZeoMultiplayerApi { - getPlayerStatuses() { - return multiplayer.getPlayerStatuses(); + class ZeoMultiplayerApi { + getPlayerStatuses() { + return multiplayer.getPlayerStatuses(); + } } - } - class ZeoFsApi { - makeFile(id, pathname) { - return fs.makeFile(id, pathname); + class ZeoFsApi { + makeFile(id, pathname) { + return fs.makeFile(id, pathname); + } } - } - class ZeoUtilsApi { - constructor() { - this.js = jsUtils; - this.hash = hashUtils; - this.random = randomUtils; - this.image = imageUtils; + class ZeoUtilsApi { + constructor() { + this.js = jsUtils; + this.hash = hashUtils; + this.random = randomUtils; + this.image = imageUtils; + } } - } - class ZeoApi { - constructor() { - this.three = new ZeoThreeApi(); - this.elements = new ZeoElementsApi(); - this.world = new ZeoWorldApi(); - this.fs = new ZeoFsApi(); - this.multiplayer = new ZeoMultiplayerApi(); - this.utils = new ZeoUtilsApi(); + class ZeoApi { + constructor() { + this.three = new ZeoThreeApi(); + this.elements = new ZeoElementsApi(); + this.world = new ZeoWorldApi(); + this.fs = new ZeoFsApi(); + this.multiplayer = new ZeoMultiplayerApi(); + this.utils = new ZeoUtilsApi(); + } } - } - const zeoApi = new ZeoApi(); - global.zeo = zeoApi; + const zeoApi = new ZeoApi(); + global.zeo = zeoApi; - world.initTags() - .catch(err => { + world.initTags().catch(err => { console.warn(err); }); - this._cleanup = () => {}; + this._cleanup = () => {}; - return zeoApi; + return zeoApi; + } } - }); + ); } unmount() { diff --git a/plugins/generator/client.js b/plugins/generator/client.js index 0d0c83bba..c97caee5f 100644 --- a/plugins/generator/client.js +++ b/plugins/generator/client.js @@ -21,9 +21,18 @@ class Generator { } mount() { - const {_archae: archae} = this; - const {three, render, pose, input, world, elements, stck, utils: {js: {mod, sbffr}, random: {chnkr}, hash: {murmur}}} = zeo; - const {THREE, scene, camera, renderer} = three; + const { _archae: archae } = this; + const { + three, + render, + pose, + input, + world, + elements, + stck, + utils: { js: { mod, sbffr }, random: { chnkr }, hash: { murmur } }, + } = zeo; + const { THREE, scene, camera, renderer } = three; const textureAtlas = new THREE.Texture( null, @@ -59,7 +68,10 @@ class Generator { }; function _updateModelViewMatrix(camera) { if (!modelViewMatricesValid[camera.name]) { - modelViewMatrices[camera.name].multiplyMatrices(camera.matrixWorldInverse, this.matrixWorld); + modelViewMatrices[camera.name].multiplyMatrices( + camera.matrixWorldInverse, + this.matrixWorld + ); modelViewMatricesValid[camera.name] = true; } this.modelViewMatrix = modelViewMatrices[camera.name]; @@ -80,8 +92,9 @@ class Generator { } } - const _getChunkIndex = (x, z) => (mod(x, 0xFFFF) << 16) | mod(z, 0xFFFF); - const _getObjectId = (x, z, i) => (mod(x, 0xFF) << 24) | (mod(z, 0xFF) << 16) | (i & 0xFFFF); + const _getChunkIndex = (x, z) => (mod(x, 0xffff) << 16) | mod(z, 0xffff); + const _getObjectId = (x, z, i) => + (mod(x, 0xff) << 24) | (mod(z, 0xff) << 16) | (i & 0xffff); const forwardVector = new THREE.Vector3(0, 0, -1); const localVector = new THREE.Vector3(); @@ -92,18 +105,21 @@ class Generator { const localArray16 = Array(16); const localArray162 = Array(16); - const _requestImage = src => new Promise((accept, reject) => { - const img = new Image(); - img.onload = () => { - accept(img); - }; - img.onerror = err => { - reject(img); - }; - img.src = src; - }); - 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.onload = () => { + accept(img); + }; + img.onerror = err => { + reject(img); + }; + img.src = src; + }); + const _requestImageBitmap = src => + _requestImage(src).then(img => + createImageBitmap(img, 0, 0, img.width, img.height) + ); let terrainGenerateBuffer = new ArrayBuffer(4 * NUM_POSITIONS_CHUNK); let objectsGenerateBuffer = new ArrayBuffer(NUM_POSITIONS_CHUNK); @@ -116,7 +132,9 @@ class Generator { }; let bodyObjectBuffer = new ArrayBuffer(4 * 4); - const worker = new Worker('archae/plugins/_plugins_generator/build/worker.js'); + const worker = new Worker( + 'archae/plugins/_plugins_generator/build/worker.js' + ); let queues = {}; let numRemovedQueues = 0; const _cleanupQueues = () => { @@ -152,20 +170,30 @@ class Generator { }); queues[id] = cb; }; - worker.requestTerrainGenerate = (x, y, index, numPositions, numIndices, cb) => { + worker.requestTerrainGenerate = ( + x, + y, + index, + numPositions, + numIndices, + cb + ) => { const id = _makeId(); - worker.postMessage({ - type: 'terrainGenerate', - id, - args: { - x, - y, - index, - numPositions, - numIndices, - buffer: terrainGenerateBuffer, + worker.postMessage( + { + type: 'terrainGenerate', + id, + args: { + x, + y, + index, + numPositions, + numIndices, + buffer: terrainGenerateBuffer, + }, }, - }, [terrainGenerateBuffer]); + [terrainGenerateBuffer] + ); queues[id] = newGenerateBuffer => { terrainGenerateBuffer = newGenerateBuffer; @@ -174,35 +202,49 @@ class Generator { }; worker.requestTerrainsGenerate = (specs, cb) => { const id = _makeId(); - worker.postMessage({ - type: 'terrainsGenerate', - id, - args: { - specs, - buffer: terrainGenerateBuffer, + worker.postMessage( + { + type: 'terrainsGenerate', + id, + args: { + specs, + buffer: terrainGenerateBuffer, + }, }, - }, [terrainGenerateBuffer]); + [terrainGenerateBuffer] + ); queues[id] = newGenerateBuffer => { terrainGenerateBuffer = newGenerateBuffer; cb(newGenerateBuffer); }; }; - worker.requestObjectsGenerate = (x, z, index, numPositions, numObjectIndices, numIndices, cb) => { + worker.requestObjectsGenerate = ( + x, + z, + index, + numPositions, + numObjectIndices, + numIndices, + cb + ) => { const id = _makeId(); - worker.postMessage({ - type: 'objectsGenerate', - id, - args: { - x, - z, - index, - numPositions, - numObjectIndices, - numIndices, - buffer: objectsGenerateBuffer, + worker.postMessage( + { + type: 'objectsGenerate', + id, + args: { + x, + z, + index, + numPositions, + numObjectIndices, + numIndices, + buffer: objectsGenerateBuffer, + }, }, - }, [objectsGenerateBuffer]); + [objectsGenerateBuffer] + ); queues[id] = newGenerateBuffer => { objectsGenerateBuffer = newGenerateBuffer; @@ -229,7 +271,14 @@ class Generator { clear, }); }; - worker.requestUnregisterObject = (n, added, removed, updated, set, clear) => { + worker.requestUnregisterObject = ( + n, + added, + removed, + updated, + set, + clear + ) => { worker.postMessage({ type: 'unregisterObject', n, @@ -284,18 +333,26 @@ class Generator { z, }); }; - worker.requestTerrainCull = (hmdPosition, projectionMatrix, matrixWorldInverse, cb) => { + worker.requestTerrainCull = ( + hmdPosition, + projectionMatrix, + matrixWorldInverse, + cb + ) => { const id = _makeId(); - worker.postMessage({ - type: 'terrainCull', - id, - args: { - hmdPosition: hmdPosition.toArray(localArray3), - projectionMatrix: projectionMatrix.toArray(localArray16), - matrixWorldInverse: matrixWorldInverse.toArray(localArray162), - buffer: terrainCullBuffer, + worker.postMessage( + { + type: 'terrainCull', + id, + args: { + hmdPosition: hmdPosition.toArray(localArray3), + projectionMatrix: projectionMatrix.toArray(localArray16), + matrixWorldInverse: matrixWorldInverse.toArray(localArray162), + buffer: terrainCullBuffer, + }, }, - }, [terrainCullBuffer]); + [terrainCullBuffer] + ); terrainCullBuffer = null; queues[id] = buffer => { @@ -303,18 +360,26 @@ class Generator { cb(buffer); }; }; - worker.requestObjectsCull = (hmdPosition, projectionMatrix, matrixWorldInverse, cb) => { + worker.requestObjectsCull = ( + hmdPosition, + projectionMatrix, + matrixWorldInverse, + cb + ) => { const id = _makeId(); - worker.postMessage({ - type: 'objectsCull', - id, - args: { - hmdPosition: hmdPosition.toArray(localArray3), - projectionMatrix: projectionMatrix.toArray(localArray16), - matrixWorldInverse: matrixWorldInverse.toArray(localArray162), - buffer: objectsCullBuffer, + worker.postMessage( + { + type: 'objectsCull', + id, + args: { + hmdPosition: hmdPosition.toArray(localArray3), + projectionMatrix: projectionMatrix.toArray(localArray16), + matrixWorldInverse: matrixWorldInverse.toArray(localArray162), + buffer: objectsCullBuffer, + }, }, - }, [objectsCullBuffer]); + [objectsCullBuffer] + ); objectsCullBuffer = null; queues[id] = buffer => { @@ -324,7 +389,7 @@ class Generator { }; worker.requestHoveredObjects = cb => { const id = _makeId(); - const {gamepads} = pose.getStatus(); + const { gamepads } = pose.getStatus(); const float32Array = new Float32Array(hoveredObjectsBuffer, 0, 6); float32Array[0] = gamepads.left.worldPosition.x; @@ -334,13 +399,16 @@ class Generator { float32Array[4] = gamepads.right.worldPosition.y; float32Array[5] = gamepads.right.worldPosition.z; - worker.postMessage({ - type: 'getHoveredObjects', - id, - args: { - buffer: hoveredObjectsBuffer, + worker.postMessage( + { + type: 'getHoveredObjects', + id, + args: { + buffer: hoveredObjectsBuffer, + }, }, - }, [hoveredObjectsBuffer]); + [hoveredObjectsBuffer] + ); queues[id] = newHoveredObjectsBuffer => { hoveredObjectsBuffer = newHoveredObjectsBuffer; @@ -356,13 +424,16 @@ class Generator { float32Array[1] = position.y; float32Array[2] = position.z; - worker.postMessage({ - type: 'getTeleportObject', - id, - args: { - buffer: teleportObjectBuffer, + worker.postMessage( + { + type: 'getTeleportObject', + id, + args: { + buffer: teleportObjectBuffer, + }, }, - }, [teleportObjectBuffer]); + [teleportObjectBuffer] + ); queues[id] = newTeleportObjectsBuffer => { teleportObjectBuffers[side] = newTeleportObjectsBuffer; @@ -377,13 +448,16 @@ class Generator { float32Array[1] = position.y; float32Array[2] = position.z; - worker.postMessage({ - type: 'getBodyObject', - id, - args: { - buffer: bodyObjectBuffer, + worker.postMessage( + { + type: 'getBodyObject', + id, + args: { + buffer: bodyObjectBuffer, + }, }, - }, [bodyObjectBuffer]); + [bodyObjectBuffer] + ); queues[id] = newBodyObjectBuffer => { bodyObjectBuffer = newBodyObjectBuffer; @@ -408,12 +482,12 @@ class Generator { }, transfers); }; */ worker.onmessage = e => { - const {data} = e; - const {type, args} = data; + const { data } = e; + const { type, args } = data; if (type === 'response') { const [id] = args; - const {result} = data; + const { result } = data; queues[id](result); queues[id] = null; @@ -457,14 +531,24 @@ class Generator { const objectApi = objectApis[n]; if (objectApi) { - objectApi.removedCallback(_getObjectId(x, z, objectIndex), x, z, objectIndex); + objectApi.removedCallback( + _getObjectId(x, z, objectIndex), + x, + z, + objectIndex + ); } } else if (type === 'objectUpdated') { const [n, x, z, objectIndex, position, rotation, value] = args; const objectApi = objectApis[n]; if (objectApi) { - objectApi.updateCallback(_getObjectId(x, z, objectIndex), localVector.fromArray(position), localQuaternion.fromArray(rotation), value); + objectApi.updateCallback( + _getObjectId(x, z, objectIndex), + localVector.fromArray(position), + localQuaternion.fromArray(rotation), + value + ); } } else if (type === 'blockSet') { const [n, x, y, z] = args; @@ -481,7 +565,10 @@ class Generator { objectApi.clearCallback(_getObjectId(x, y, z), x, y, z); } } else { - console.warn('generator got unknown worker message type:', JSON.stringify(type)); + console.warn( + 'generator got unknown worker message type:', + JSON.stringify(type) + ); } }; @@ -526,20 +613,50 @@ class Generator { generatorElement.requestOriginHeight = cb => { worker.requestOriginHeight(cb); }; - generatorElement.requestTerrainGenerate = (x, z, index, numPositions, numIndices, cb) => { - worker.requestTerrainGenerate(x, z, index, numPositions, numIndices, buffer => { - cb(protocolUtils.parseTerrainRenderChunk(buffer)); - }); + generatorElement.requestTerrainGenerate = ( + x, + z, + index, + numPositions, + numIndices, + cb + ) => { + worker.requestTerrainGenerate( + x, + z, + index, + numPositions, + numIndices, + buffer => { + cb(protocolUtils.parseTerrainRenderChunk(buffer)); + } + ); }; generatorElement.requestTerrainsGenerate = (specs, cb) => { worker.requestTerrainsGenerate(specs, buffer => { cb(protocolUtils.parseTerrainsRenderChunk(buffer)); }); }; - generatorElement.requestObjectsGenerate = (x, z, index, numPositions, numObjectIndices, numIndices, cb) => { - worker.requestObjectsGenerate(x, z, index, numPositions, numObjectIndices, numIndices, buffer => { - cb(protocolUtils.parseWorker(buffer)); - }); + generatorElement.requestObjectsGenerate = ( + x, + z, + index, + numPositions, + numObjectIndices, + numIndices, + cb + ) => { + worker.requestObjectsGenerate( + x, + z, + index, + numPositions, + numObjectIndices, + numIndices, + buffer => { + cb(protocolUtils.parseWorker(buffer)); + } + ); }; generatorElement.addVoxel = (x, y, z) => { worker.requestAddVoxel(x, y, z); @@ -547,7 +664,12 @@ class Generator { generatorElement.mutateVoxel = (x, y, z, v) => { worker.requestMutateVoxel(x, y, z, v); }; - generatorElement.requestAddObject = (name, position, rotation, value) => { + generatorElement.requestAddObject = ( + name, + position, + rotation, + value + ) => { worker.requestAddObject(name, position, rotation, value); }; generatorElement.requestRemoveObject = (x, z, objectIndex) => { @@ -555,7 +677,7 @@ class Generator { }; generatorElement.setData = (x, z, objectIndex, value) => { worker.requestSetObjectData(x, z, objectIndex, value); - } + }; generatorElement.getObjectApi = n => objectApis[n]; generatorElement.registerObject = objectApi => { const n = murmur(objectApi.object); @@ -589,15 +711,35 @@ class Generator { generatorElement.clearBlock = (x, y, z) => { worker.requestClearBlock(x, y, z); }; - generatorElement.requestTerrainCull = (hmdPosition, projectionMatrix, matrixWorldInverse, cb) => { - worker.requestTerrainCull(hmdPosition, projectionMatrix, matrixWorldInverse, buffer => { - cb(protocolUtils.parseTerrainCull(buffer)); - }); + generatorElement.requestTerrainCull = ( + hmdPosition, + projectionMatrix, + matrixWorldInverse, + cb + ) => { + worker.requestTerrainCull( + hmdPosition, + projectionMatrix, + matrixWorldInverse, + buffer => { + cb(protocolUtils.parseTerrainCull(buffer)); + } + ); }; - generatorElement.requestObjectsCull = (hmdPosition, projectionMatrix, matrixWorldInverse, cb) => { - worker.requestObjectsCull(hmdPosition, projectionMatrix, matrixWorldInverse, buffer => { - cb(protocolUtils.parseObjectsCull(buffer)); - }); + generatorElement.requestObjectsCull = ( + hmdPosition, + projectionMatrix, + matrixWorldInverse, + cb + ) => { + worker.requestObjectsCull( + hmdPosition, + projectionMatrix, + matrixWorldInverse, + buffer => { + cb(protocolUtils.parseObjectsCull(buffer)); + } + ); }; generatorElement.requestHoveredObjects = cb => { worker.requestHoveredObjects(cb); @@ -641,9 +783,12 @@ class Generator { }; })(); const _debouncedRequestRefreshMapChunks = _debounce(nextDebounce => { - const {hmd} = pose.getStatus(); - const {worldPosition: hmdPosition} = hmd; - const {added, removed, done} = chunker.update(hmdPosition.x, hmdPosition.z); + const { hmd } = pose.getStatus(); + const { worldPosition: hmdPosition } = hmd; + const { added, removed, done } = chunker.update( + hmdPosition.x, + hmdPosition.z + ); let running = false; const queue = []; @@ -667,7 +812,7 @@ class Generator { if (!running) { running = true; - const {x, z, lod} = chunk; + const { x, z, lod } = chunk; const index = _getChunkIndex(x, z); const oldMapChunkMesh = mapChunkMeshes[index]; if (oldMapChunkMesh) { @@ -692,7 +837,7 @@ class Generator { if (removed.length > 0) { for (let i = 0; i < removed.length; i++) { const chunk = removed[i]; - const {x, z} = chunk; + const { x, z } = chunk; worker.requestUngenerate(x, z); @@ -714,7 +859,7 @@ class Generator { let refreshChunksTimeout = null; const _recurseRefreshChunks = () => { - const {hmd: {worldPosition: hmdPosition}} = pose.getStatus(); + const { hmd: { worldPosition: hmdPosition } } = pose.getStatus(); _debouncedRequestRefreshMapChunks(); refreshChunksTimeout = setTimeout(_recurseRefreshChunks, 1000); }; diff --git a/plugins/generator/objects-tesselator.js b/plugins/generator/objects-tesselator.js index f758a24cb..4c9c096ac 100644 --- a/plugins/generator/objects-tesselator.js +++ b/plugins/generator/objects-tesselator.js @@ -1,8 +1,5 @@ const zeode = require('zeode'); -const { - GEOMETRY_BUFFER_SIZE, - BLOCK_BUFFER_SIZE, -} = zeode; +const { GEOMETRY_BUFFER_SIZE, BLOCK_BUFFER_SIZE } = zeode; const { NUM_CELLS, @@ -10,7 +7,9 @@ const { } = require('./lib/constants/constants'); const NUM_CELLS_HALF = NUM_CELLS / 2; -const NUM_CELLS_CUBE = Math.sqrt((NUM_CELLS_HALF + 16) * (NUM_CELLS_HALF + 16) * 3); // larger than the actual bounding box to account for geometry overflow +const NUM_CELLS_CUBE = Math.sqrt( + (NUM_CELLS_HALF + 16) * (NUM_CELLS_HALF + 16) * 3 +); // larger than the actual bounding box to account for geometry overflow // const NUM_VOXELS_CHUNK_HEIGHT = BLOCK_BUFFER_SIZE / 4 / NUM_CHUNKS_HEIGHT; module.exports = ({ @@ -22,106 +21,142 @@ module.exports = ({ translucentVoxels, faceUvs, }) => { - -const _makeGeometeriesBuffer = (() => { - const slab = new ArrayBuffer(GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT * 7); - let index = 0; - const result = constructor => { - const result = new constructor(slab, index, (GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT) / constructor.BYTES_PER_ELEMENT); - index += GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT; + const _makeGeometeriesBuffer = (() => { + const slab = new ArrayBuffer(GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT * 7); + let index = 0; + const result = constructor => { + const result = new constructor( + slab, + index, + GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT / constructor.BYTES_PER_ELEMENT + ); + index += GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT; + return result; + }; + result.reset = () => { + index = 0; + }; return result; - }; - result.reset = () => { - index = 0; - }; - return result; -})(); -const boundingSpheres = (() => { - const slab = new ArrayBuffer(NUM_CHUNKS_HEIGHT * 4 * Float32Array.BYTES_PER_ELEMENT); - const result = Array(NUM_CHUNKS_HEIGHT); - for (let i = 0; i < NUM_CHUNKS_HEIGHT; i++) { - result[i] = new Float32Array(slab, i * 4 * Float32Array.BYTES_PER_ELEMENT, 4); - } - return result; -})(); -const tesselate = chunk => { - _makeGeometeriesBuffer.reset(); - const geometriesPositions = _makeGeometeriesBuffer(Float32Array); - const geometriesUvs = _makeGeometeriesBuffer(Float32Array); - const geometriesSsaos = _makeGeometeriesBuffer(Uint8Array); - const geometriesFrames = _makeGeometeriesBuffer(Float32Array); - const geometriesObjectIndices = _makeGeometeriesBuffer(Float32Array); - const geometriesIndices = _makeGeometeriesBuffer(Uint32Array); - const geometriesObjects = _makeGeometeriesBuffer(Uint32Array); + })(); + const boundingSpheres = (() => { + const slab = new ArrayBuffer( + NUM_CHUNKS_HEIGHT * 4 * Float32Array.BYTES_PER_ELEMENT + ); + const result = Array(NUM_CHUNKS_HEIGHT); + for (let i = 0; i < NUM_CHUNKS_HEIGHT; i++) { + result[i] = new Float32Array( + slab, + i * 4 * Float32Array.BYTES_PER_ELEMENT, + 4 + ); + } + return result; + })(); + const tesselate = chunk => { + _makeGeometeriesBuffer.reset(); + const geometriesPositions = _makeGeometeriesBuffer(Float32Array); + const geometriesUvs = _makeGeometeriesBuffer(Float32Array); + const geometriesSsaos = _makeGeometeriesBuffer(Uint8Array); + const geometriesFrames = _makeGeometeriesBuffer(Float32Array); + const geometriesObjectIndices = _makeGeometeriesBuffer(Float32Array); + const geometriesIndices = _makeGeometeriesBuffer(Uint32Array); + const geometriesObjects = _makeGeometeriesBuffer(Uint32Array); + + const { + positions: numNewPositions, + uvs: numNewUvs, + ssaos: numNewSsaos, + frames: numNewFrames, + objectIndices: numNewObjectIndices, + indices: numNewIndices, + objects: numNewObjects, + } = vxl.objectize({ + src: chunk.getObjectBuffer(), + geometries: geometriesBuffer, + geometryIndex: geometryTypes, + blocks: chunk.getBlockBuffer(), + blockTypes, + dims: Int32Array.from([NUM_CELLS, NUM_CELLS, NUM_CELLS]), + transparentVoxels, + translucentVoxels, + faceUvs, + shift: Float32Array.from([chunk.x * NUM_CELLS, 0, chunk.z * NUM_CELLS]), + positions: geometriesPositions, + uvs: geometriesUvs, + ssaos: geometriesSsaos, + frames: geometriesFrames, + objectIndices: geometriesObjectIndices, + indices: geometriesIndices, + objects: geometriesObjects, + }); - const { - positions: numNewPositions, - uvs: numNewUvs, - ssaos: numNewSsaos, - frames: numNewFrames, - objectIndices: numNewObjectIndices, - indices: numNewIndices, - objects: numNewObjects, - } = vxl.objectize({ - src: chunk.getObjectBuffer(), - geometries: geometriesBuffer, - geometryIndex: geometryTypes, - blocks: chunk.getBlockBuffer(), - blockTypes, - dims: Int32Array.from([NUM_CELLS, NUM_CELLS, NUM_CELLS]), - transparentVoxels, - translucentVoxels, - faceUvs, - shift: Float32Array.from([chunk.x * NUM_CELLS, 0, chunk.z * NUM_CELLS]), - positions: geometriesPositions, - uvs: geometriesUvs, - ssaos: geometriesSsaos, - frames: geometriesFrames, - objectIndices: geometriesObjectIndices, - indices: geometriesIndices, - objects: geometriesObjects, - }); + const localGeometries = Array(NUM_CHUNKS_HEIGHT); + for (let i = 0; i < NUM_CHUNKS_HEIGHT; i++) { + const attributeRangeStart = i === 0 ? 0 : numNewPositions[i - 1]; + const attributeRangeCount = numNewPositions[i] - attributeRangeStart; + const indexRangeStart = i === 0 ? 0 : numNewIndices[i - 1]; + const indexRangeCount = numNewIndices[i] - indexRangeStart; - const localGeometries = Array(NUM_CHUNKS_HEIGHT); - for (let i = 0; i < NUM_CHUNKS_HEIGHT; i++) { - const attributeRangeStart = i === 0 ? 0 : numNewPositions[i - 1]; - const attributeRangeCount = numNewPositions[i] - attributeRangeStart; - const indexRangeStart = i === 0 ? 0 : numNewIndices[i - 1]; - const indexRangeCount = numNewIndices[i] - indexRangeStart; + const boundingSphere = boundingSpheres[i]; + boundingSphere[0] = chunk.x * NUM_CELLS + NUM_CELLS_HALF; + boundingSphere[1] = i * NUM_CELLS + NUM_CELLS_HALF; + boundingSphere[2] = chunk.z * NUM_CELLS + NUM_CELLS_HALF; + boundingSphere[3] = NUM_CELLS_CUBE; - const boundingSphere = boundingSpheres[i]; - boundingSphere[0] = chunk.x * NUM_CELLS + NUM_CELLS_HALF; - boundingSphere[1] = i * NUM_CELLS + NUM_CELLS_HALF; - boundingSphere[2] = chunk.z * NUM_CELLS + NUM_CELLS_HALF; - boundingSphere[3] = NUM_CELLS_CUBE; + localGeometries[i] = { + attributeRange: { + start: attributeRangeStart, + count: attributeRangeCount, + }, + indexRange: { + start: indexRangeStart, + count: indexRangeCount, + }, + boundingSphere, + }; + } - localGeometries[i] = { - attributeRange: { - start: attributeRangeStart, - count: attributeRangeCount, - }, - indexRange: { - start: indexRangeStart, - count: indexRangeCount, - }, - boundingSphere, + return { + positions: new Float32Array( + geometriesPositions.buffer, + geometriesPositions.byteOffset, + numNewPositions[NUM_CHUNKS_HEIGHT - 1] + ), + uvs: new Float32Array( + geometriesUvs.buffer, + geometriesUvs.byteOffset, + numNewUvs[NUM_CHUNKS_HEIGHT - 1] + ), + ssaos: new Uint8Array( + geometriesSsaos.buffer, + geometriesSsaos.byteOffset, + numNewSsaos[NUM_CHUNKS_HEIGHT - 1] + ), + frames: new Float32Array( + geometriesFrames.buffer, + geometriesFrames.byteOffset, + numNewFrames[NUM_CHUNKS_HEIGHT - 1] + ), + objectIndices: new Float32Array( + geometriesObjectIndices.buffer, + geometriesObjectIndices.byteOffset, + numNewObjectIndices[NUM_CHUNKS_HEIGHT - 1] + ), + indices: new Uint32Array( + geometriesIndices.buffer, + geometriesIndices.byteOffset, + numNewIndices[NUM_CHUNKS_HEIGHT - 1] + ), + objects: new Uint32Array( + geometriesObjects.buffer, + geometriesObjects.byteOffset, + numNewObjects[NUM_CHUNKS_HEIGHT - 1] + ), + geometries: localGeometries, }; }; return { - positions: new Float32Array(geometriesPositions.buffer, geometriesPositions.byteOffset, numNewPositions[NUM_CHUNKS_HEIGHT - 1]), - uvs: new Float32Array(geometriesUvs.buffer, geometriesUvs.byteOffset, numNewUvs[NUM_CHUNKS_HEIGHT - 1]), - ssaos: new Uint8Array(geometriesSsaos.buffer, geometriesSsaos.byteOffset, numNewSsaos[NUM_CHUNKS_HEIGHT - 1]), - frames: new Float32Array(geometriesFrames.buffer, geometriesFrames.byteOffset, numNewFrames[NUM_CHUNKS_HEIGHT - 1]), - objectIndices: new Float32Array(geometriesObjectIndices.buffer, geometriesObjectIndices.byteOffset, numNewObjectIndices[NUM_CHUNKS_HEIGHT - 1]), - indices: new Uint32Array(geometriesIndices.buffer, geometriesIndices.byteOffset, numNewIndices[NUM_CHUNKS_HEIGHT - 1]), - objects: new Uint32Array(geometriesObjects.buffer, geometriesObjects.byteOffset, numNewObjects[NUM_CHUNKS_HEIGHT - 1]), - geometries: localGeometries, + tesselate, }; }; - -return { - tesselate, -}; - -}; diff --git a/plugins/generator/server.js b/plugins/generator/server.js index d8662e55b..0413ce12c 100644 --- a/plugins/generator/server.js +++ b/plugins/generator/server.js @@ -3,10 +3,7 @@ const fs = require('fs'); const touch = require('touch'); const zeode = require('zeode'); -const { - BLOCK_BUFFER_SIZE, - GEOMETRY_BUFFER_SIZE, -} = zeode; +const { BLOCK_BUFFER_SIZE, GEOMETRY_BUFFER_SIZE } = zeode; const txtr = require('txtr'); const { NUM_CELLS, @@ -25,12 +22,7 @@ const protocolUtils = require('./lib/utils/protocol-utils'); const terrainTesselatorLib = require('./terrain-tesselator'); const objectsTesselatorLib = require('./objects-tesselator'); -const DIRECTIONS = [ - [-1, -1], - [-1, 1], - [1, -1], - [1, 1], -]; +const DIRECTIONS = [[-1, -1], [-1, 1], [1, -1], [1, 1]]; const lightsSymbol = Symbol(); const lightsRenderedSymbol = Symbol(); @@ -44,15 +36,24 @@ class Generator { } mount() { - const {_archae: archae} = this; - const {dirname, dataDirectory} = archae; - const {express, ws, app, wss} = archae.getCore(); - const {three, elements, utils: {js: jsUtils, hash: hashUtils, random: randomUtils, image: imageUtils}} = zeo; - const {THREE} = three; - const {mod} = jsUtils; - const {murmur} = hashUtils; - const {alea, vxlPath, vxl} = randomUtils; - const {jimp} = imageUtils; + const { _archae: archae } = this; + const { dirname, dataDirectory } = archae; + const { express, ws, app, wss } = archae.getCore(); + const { + three, + elements, + utils: { + js: jsUtils, + hash: hashUtils, + random: randomUtils, + image: imageUtils, + }, + } = zeo; + const { THREE } = three; + const { mod } = jsUtils; + const { murmur } = hashUtils; + const { alea, vxlPath, vxl } = randomUtils; + const { jimp } = imageUtils; const zeroVector = new THREE.Vector3(); const zeroVectorArray = zeroVector.toArray(); @@ -69,7 +70,7 @@ class Generator { const transparentVoxels = new Uint8Array(256); const translucentVoxels = new Uint8Array(256); const faceUvs = new Float32Array(256 * 6 * 4); - const lights = new Uint32Array(256);; + const lights = new Uint32Array(256); let lightsIndex = 0; const noiser = vxl.noiser({ seed: murmur(DEFAULT_SEED), @@ -92,10 +93,18 @@ class Generator { faceUvs, }); const zeodeDataPath = path.join(dirname, dataDirectory, 'zeode.dat'); - const texturesPngDataPath = path.join(dirname, dataDirectory, 'textures.png'); - const texturesJsonDataPath = path.join(dirname, dataDirectory, 'textures.json'); - - const _getChunkIndex = (x, z) => (mod(x, 0xFFFF) << 16) | mod(z, 0xFFFF); + const texturesPngDataPath = path.join( + dirname, + dataDirectory, + 'textures.png' + ); + const texturesJsonDataPath = path.join( + dirname, + dataDirectory, + 'textures.json' + ); + + const _getChunkIndex = (x, z) => (mod(x, 0xffff) << 16) | mod(z, 0xffff); // const _getLightsIndex = (x, y, z) => x + y * NUM_CELLS_OVERSCAN + z * NUM_CELLS_OVERSCAN * (NUM_CELLS_HEIGHT + 1); const _getLightsArrayIndex = (x, z) => x + z * 3; const _findLight = n => { @@ -113,7 +122,11 @@ class Generator { }; const _generateChunkTerrain = (chunk, opts) => { const uint32Buffer = chunk.getTerrainBuffer(); - protocolUtils.stringifyTerrainData(terrainTesselator.generate(chunk.x, chunk.z, opts), uint32Buffer.buffer, uint32Buffer.byteOffset); + protocolUtils.stringifyTerrainData( + terrainTesselator.generate(chunk.x, chunk.z, opts), + uint32Buffer.buffer, + uint32Buffer.byteOffset + ); }; const _generateChunkObjects = chunk => { for (let i = 0; i < generators.length; i++) { @@ -133,38 +146,47 @@ class Generator { }; const _decorateChunkObjectsGeometry = chunk => { const geometryBuffer = chunk.getGeometryBuffer(); - protocolUtils.stringifyGeometry(objectsTesselator.tesselate(chunk), geometryBuffer.buffer, geometryBuffer.byteOffset); + protocolUtils.stringifyGeometry( + objectsTesselator.tesselate(chunk), + geometryBuffer.buffer, + geometryBuffer.byteOffset + ); return Promise.resolve(chunk); }; const _symbolizeChunk = chunk => { if (chunk) { - chunk[lightsSymbol] = new Uint8Array(NUM_CELLS_OVERSCAN * (NUM_CELLS_HEIGHT + 1) * NUM_CELLS_OVERSCAN); + chunk[lightsSymbol] = new Uint8Array( + NUM_CELLS_OVERSCAN * (NUM_CELLS_HEIGHT + 1) * NUM_CELLS_OVERSCAN + ); chunk[lightsRenderedSymbol] = false; chunk[lightmapsSymbol] = null; - chunk[blockfieldSymbol] = new Uint8Array(NUM_CELLS * NUM_CELLS_HEIGHT * NUM_CELLS); + chunk[blockfieldSymbol] = new Uint8Array( + NUM_CELLS * NUM_CELLS_HEIGHT * NUM_CELLS + ); chunk[blockfieldRenderedSymbol] = false; } }; - const _readEnsureFile = (p, opts) => new Promise((accept, reject) => { - fs.readFile(p, opts, (err, d) => { - if (!err) { - accept(d); - } else if (err.code === 'ENOENT') { - touch(p, err => { - if (!err) { - accept(null); - } else { - reject(err); - } - }); - } else { - reject(err); - } + const _readEnsureFile = (p, opts) => + new Promise((accept, reject) => { + fs.readFile(p, opts, (err, d) => { + if (!err) { + accept(d); + } else if (err.code === 'ENOENT') { + touch(p, err => { + if (!err) { + accept(null); + } else { + reject(err); + } + }); + } else { + reject(err); + } + }); }); - }); - const _getZeode = () => _readEnsureFile(zeodeDataPath) - .then(b => { + const _getZeode = () => + _readEnsureFile(zeodeDataPath).then(b => { const zde = zeode(); if (b) { zde.load(b); @@ -175,42 +197,29 @@ class Generator { } return zde; }); - const _getTextures = () => Promise.all([ - _readEnsureFile(texturesPngDataPath) - .then(b => { - if (b !== null && b.length > 0) { - return jimp.read(b); - } else { - return Promise.resolve(new jimp(TEXTURE_SIZE, TEXTURE_SIZE)); - } - }) - .then(textureImg => { - textureImg.version = 0; - return textureImg; - }), - _readEnsureFile(texturesJsonDataPath, 'utf8') - .then(s => { + const _getTextures = () => + Promise.all([ + _readEnsureFile(texturesPngDataPath) + .then(b => { + if (b !== null && b.length > 0) { + return jimp.read(b); + } else { + return Promise.resolve(new jimp(TEXTURE_SIZE, TEXTURE_SIZE)); + } + }) + .then(textureImg => { + textureImg.version = 0; + return textureImg; + }), + _readEnsureFile(texturesJsonDataPath, 'utf8').then(s => { if (s !== null && s.length > 0) { - const {atlas, uvs} = JSON.parse(s); - return [ - txtr.fromJson(atlas), - uvs, - ]; + const { atlas, uvs } = JSON.parse(s); + return [txtr.fromJson(atlas), uvs]; } else { - return [ - txtr(TEXTURE_SIZE, TEXTURE_SIZE), - {}, - ]; + return [txtr(TEXTURE_SIZE, TEXTURE_SIZE), {}]; } }), - ]) - .then(([ - textureImg, - [ - textureAtlas, - textureUvs, - ], - ]) => ({ + ]).then(([textureImg, [textureAtlas, textureUvs]]) => ({ textureImg, textureAtlas, textureUvs, @@ -219,31 +228,24 @@ class Generator { return Promise.all([ _getZeode(), _getTextures(), - ]) - .then(([ - zde, - { - textureImg, - textureAtlas, - textureUvs, - }, - ]) => { - const _requestChunkHard = (x, z) => { - const chunk = zde.getChunk(x, z); - - if (chunk) { - return Promise.resolve(chunk); - } else { - const chunk = zde.makeChunk(x, z); - _symbolizeChunk(chunk); - _generateChunk(chunk); + ]).then(([zde, { textureImg, textureAtlas, textureUvs }]) => { + const _requestChunkHard = (x, z) => { + const chunk = zde.getChunk(x, z); + + if (chunk) { + return Promise.resolve(chunk); + } else { + const chunk = zde.makeChunk(x, z); + _symbolizeChunk(chunk); + _generateChunk(chunk); - _saveChunks(); + _saveChunks(); - return Promise.resolve(chunk); - } - }; - const _decorateChunkLights = chunk => _decorateChunkLightsRange( + return Promise.resolve(chunk); + } + }; + const _decorateChunkLights = chunk => + _decorateChunkLightsRange( chunk, (chunk.x - 1) * NUM_CELLS, (chunk.x + 2) * NUM_CELLS, @@ -253,7 +255,8 @@ class Generator { (chunk.z + 2) * NUM_CELLS, false ); - const _decorateChunkLightsSub = (chunk, x, y, z) => _decorateChunkLightsRange( + const _decorateChunkLightsSub = (chunk, x, y, z) => + _decorateChunkLightsRange( chunk, Math.max(x - 15, (chunk.x - 1) * NUM_CELLS), Math.min(x + 15, (chunk.x + 2) * NUM_CELLS), @@ -263,140 +266,209 @@ class Generator { Math.min(z + 15, (chunk.z + 2) * NUM_CELLS), true ); - /* const _ensureChunksHardRange = (minX, maxX, minZ, maxZ) => { + /* const _ensureChunksHardRange = (minX, maxX, minZ, maxZ) => { const minOX = Math.floor(minX / NUM_CELLS); const maxOX = Math.floor((maxX - 1) / NUM_CELLS); const minOZ = Math.floor(minZ / NUM_CELLS); const maxOZ = Math.floor((maxZ - 1) / NUM_CELLS); */ - const _ensureChunksHardRange = (ox, oz) => { - const promises = []; + const _ensureChunksHardRange = (ox, oz) => { + const promises = []; + for (let doz = -1; doz <= 1; doz++) { + for (let dox = -1; dox <= 1; dox++) { + /* for (let doz = minOZ; doz < maxOZ; doz++) { + for (let dox = minOX; dox < maxOX; dox++) { */ + promises.push(_requestChunkHard(ox + dox, oz + doz)); + } + } + return Promise.all(promises).then(() => {}); + }; + // const _decorateChunkLightsRange = (chunk, minX, maxX, minY, maxY, minZ, maxZ, relight) => _ensureChunksHardRange(minX, maxZ, minZ, maxZ) + const _decorateChunkLightsRange = ( + chunk, + minX, + maxX, + minY, + maxY, + minZ, + maxZ, + relight + ) => + _ensureChunksHardRange(chunk.x, chunk.z).then(() => { + const { x: ox, z: oz } = chunk; + const updatingLights = chunk[lightsRenderedSymbol]; + + const lavaArray = Array(9); + const objectLightsArray = Array(9); + const etherArray = Array(9); + const blocksArray = Array(9); + const lightsArray = Array(9); for (let doz = -1; doz <= 1; doz++) { for (let dox = -1; dox <= 1; dox++) { - /* for (let doz = minOZ; doz < maxOZ; doz++) { - for (let dox = minOX; dox < maxOX; dox++) { */ - promises.push(_requestChunkHard(ox + dox, oz + doz)); + const arrayIndex = _getLightsArrayIndex(dox + 1, doz + 1); + + const aox = ox + dox; + const aoz = oz + doz; + const chunk = zde.getChunk(aox, aoz); + const uint32Buffer = chunk.getTerrainBuffer(); + const { ether, lava } = protocolUtils.parseTerrainData( + uint32Buffer.buffer, + uint32Buffer.byteOffset + ); // XXX can be reduced to only parse the needed fields + lavaArray[arrayIndex] = lava; + + const objectLights = chunk.getLightBuffer(); + objectLightsArray[arrayIndex] = objectLights; + + etherArray[arrayIndex] = ether; + + const blocks = chunk.getBlockBuffer(); + blocksArray[arrayIndex] = blocks; + + const { [lightsSymbol]: lights } = chunk; + lightsArray[arrayIndex] = lights; } } - return Promise.all(promises).then(() => {}); - }; - // const _decorateChunkLightsRange = (chunk, minX, maxX, minY, maxY, minZ, maxZ, relight) => _ensureChunksHardRange(minX, maxZ, minZ, maxZ) - const _decorateChunkLightsRange = (chunk, minX, maxX, minY, maxY, minZ, maxZ, relight) => _ensureChunksHardRange(chunk.x, chunk.z) - .then(() => { - const {x: ox, z: oz} = chunk; - const updatingLights = chunk[lightsRenderedSymbol]; - - const lavaArray = Array(9); - const objectLightsArray = Array(9); - const etherArray = Array(9); - const blocksArray = Array(9); - const lightsArray = Array(9); - for (let doz = -1; doz <= 1; doz++) { - for (let dox = -1; dox <= 1; dox++) { - const arrayIndex = _getLightsArrayIndex(dox + 1, doz + 1); - - const aox = ox + dox; - const aoz = oz + doz; - const chunk = zde.getChunk(aox, aoz); - const uint32Buffer = chunk.getTerrainBuffer(); - const {ether, lava} = protocolUtils.parseTerrainData(uint32Buffer.buffer, uint32Buffer.byteOffset); // XXX can be reduced to only parse the needed fields - lavaArray[arrayIndex] = lava; - - const objectLights = chunk.getLightBuffer(); - objectLightsArray[arrayIndex] = objectLights; - - etherArray[arrayIndex] = ether; - - const blocks = chunk.getBlockBuffer(); - blocksArray[arrayIndex] = blocks; - - const {[lightsSymbol]: lights} = chunk; - lightsArray[arrayIndex] = lights; - } - } - vxl.light( - ox, oz, - minX, maxX, minY, maxY, minZ, maxZ, - relight, - lavaArray, - objectLightsArray, - etherArray, - blocksArray, - lightsArray, + vxl.light( + ox, + oz, + minX, + maxX, + minY, + maxY, + minZ, + maxZ, + relight, + lavaArray, + objectLightsArray, + etherArray, + blocksArray, + lightsArray + ); + chunk[lightsRenderedSymbol] = true; + chunk[lightmapsSymbol] = null; + + return chunk; + }); + const _requestChunkWithLights = chunk => + chunk[lightsRenderedSymbol] + ? Promise.resolve(chunk) + : _decorateChunkLights(chunk); + const _requestChunkWithLightmaps = chunk => { + if (chunk[lightmapsSymbol]) { + return Promise.resolve(chunk); + } else { + const _getTerrainLightmaps = () => { + const terrainBuffer = chunk.getTerrainBuffer(); + const { + positions, + staticHeightfield, + } = protocolUtils.parseTerrainData( + terrainBuffer.buffer, + terrainBuffer.byteOffset + ); + const { [lightsSymbol]: lights } = chunk; + + const numPositions = positions.length; + const numLightmaps = numPositions / 3; + const lightmapsBuffer = new ArrayBuffer(numLightmaps * 2); + const skyLightmaps = new Uint8Array( + lightmapsBuffer, + 0, + numLightmaps + ); + const torchLightmaps = new Uint8Array( + lightmapsBuffer, + numLightmaps, + numLightmaps ); - chunk[lightsRenderedSymbol] = true; - chunk[lightmapsSymbol] = null; - - return chunk; - }); - const _requestChunkWithLights = chunk => chunk[lightsRenderedSymbol] ? Promise.resolve(chunk) : _decorateChunkLights(chunk); - const _requestChunkWithLightmaps = chunk => { - if (chunk[lightmapsSymbol]) { - return Promise.resolve(chunk); - } else { - const _getTerrainLightmaps = () => { - const terrainBuffer = chunk.getTerrainBuffer(); - const {positions, staticHeightfield} = protocolUtils.parseTerrainData(terrainBuffer.buffer, terrainBuffer.byteOffset); - const {[lightsSymbol]: lights} = chunk; - - const numPositions = positions.length; - const numLightmaps = numPositions / 3; - const lightmapsBuffer = new ArrayBuffer(numLightmaps * 2); - const skyLightmaps = new Uint8Array(lightmapsBuffer, 0, numLightmaps); - const torchLightmaps = new Uint8Array(lightmapsBuffer, numLightmaps, numLightmaps); - vxl.lightmap(chunk.x, chunk.z, positions, numPositions, staticHeightfield, lights, skyLightmaps, torchLightmaps); + vxl.lightmap( + chunk.x, + chunk.z, + positions, + numPositions, + staticHeightfield, + lights, + skyLightmaps, + torchLightmaps + ); - return { - skyLightmaps, - torchLightmaps, - }; + return { + skyLightmaps, + torchLightmaps, }; - const _getObjectLightmaps = () => { - const geometryBuffer = chunk.getGeometryBuffer(); - const {positions} = protocolUtils.parseGeometry(geometryBuffer.buffer, geometryBuffer.byteOffset); - - const terrainBuffer = chunk.getTerrainBuffer(); - const {staticHeightfield} = protocolUtils.parseTerrainData(terrainBuffer.buffer, terrainBuffer.byteOffset); - const {[lightsSymbol]: lights} = chunk; + }; + const _getObjectLightmaps = () => { + const geometryBuffer = chunk.getGeometryBuffer(); + const { positions } = protocolUtils.parseGeometry( + geometryBuffer.buffer, + geometryBuffer.byteOffset + ); - const numPositions = positions.length; - const numLightmaps = numPositions / 3; - const lightmapsBuffer = new ArrayBuffer(numLightmaps * 2); - const skyLightmaps = new Uint8Array(lightmapsBuffer, 0, numLightmaps); - const torchLightmaps = new Uint8Array(lightmapsBuffer, numLightmaps, numLightmaps); + const terrainBuffer = chunk.getTerrainBuffer(); + const { staticHeightfield } = protocolUtils.parseTerrainData( + terrainBuffer.buffer, + terrainBuffer.byteOffset + ); + const { [lightsSymbol]: lights } = chunk; + + const numPositions = positions.length; + const numLightmaps = numPositions / 3; + const lightmapsBuffer = new ArrayBuffer(numLightmaps * 2); + const skyLightmaps = new Uint8Array( + lightmapsBuffer, + 0, + numLightmaps + ); + const torchLightmaps = new Uint8Array( + lightmapsBuffer, + numLightmaps, + numLightmaps + ); - vxl.lightmap(chunk.x, chunk.z, positions, numPositions, staticHeightfield, lights, skyLightmaps, torchLightmaps); + vxl.lightmap( + chunk.x, + chunk.z, + positions, + numPositions, + staticHeightfield, + lights, + skyLightmaps, + torchLightmaps + ); - return { - skyLightmaps, - torchLightmaps, - }; + return { + skyLightmaps, + torchLightmaps, }; + }; - chunk[lightmapsSymbol] = { - terrain: _getTerrainLightmaps(), - objects: _getObjectLightmaps(), - }; + chunk[lightmapsSymbol] = { + terrain: _getTerrainLightmaps(), + objects: _getObjectLightmaps(), + }; - return Promise.resolve(chunk); - } - }; - const _requestChunkWithBlockfield = chunk => { - if (chunk[blockfieldRenderedSymbol]) { - return Promise.resolve(chunk); - } else { - vxl.blockfield(chunk.getBlockBuffer(), chunk[blockfieldSymbol]); - chunk[blockfieldRenderedSymbol] = true; - return Promise.resolve(chunk); - } - }; - const _requestChunk = (x, z) => _requestChunkHard(x, z) + return Promise.resolve(chunk); + } + }; + const _requestChunkWithBlockfield = chunk => { + if (chunk[blockfieldRenderedSymbol]) { + return Promise.resolve(chunk); + } else { + vxl.blockfield(chunk.getBlockBuffer(), chunk[blockfieldSymbol]); + chunk[blockfieldRenderedSymbol] = true; + return Promise.resolve(chunk); + } + }; + const _requestChunk = (x, z) => + _requestChunkHard(x, z) .then(chunk => _requestChunkWithLights(chunk)) .then(chunk => _requestChunkWithLightmaps(chunk)) .then(chunk => _requestChunkWithBlockfield(chunk)); - const _writeFileData = (p, data, byteOffset) => new Promise((accept, reject) => { + const _writeFileData = (p, data, byteOffset) => + new Promise((accept, reject) => { const ws = fs.createWriteStream(p, { flags: 'r+', start: byteOffset, @@ -409,61 +481,82 @@ class Generator { reject(err); }); }); - const _saveChunks = _debounce(next => { - const promises = []; - zde.save((byteOffset, data) => { - promises.push(_writeFileData(zeodeDataPath, new Buffer(data.buffer, data.byteOffset, data.byteLength), byteOffset)); - }); - Promise.all(promises) - .then(() => { - next(); - }) - .catch(err => { - console.warn(err); - - next(); - }); + const _saveChunks = _debounce(next => { + const promises = []; + zde.save((byteOffset, data) => { + promises.push( + _writeFileData( + zeodeDataPath, + new Buffer(data.buffer, data.byteOffset, data.byteLength), + byteOffset + ) + ); }); - const _saveTextures = _debounce(next => { - Promise.all([ - textureImg.write(texturesPngDataPath), - _writeFileData(texturesJsonDataPath, JSON.stringify({ - atlas: textureAtlas.toJson(), - uvs: textureUvs, - }, null, 2)), - ]) - .then(() => { - next(); - }) - .catch(err => { - console.warn(err); + Promise.all(promises) + .then(() => { + next(); + }) + .catch(err => { + console.warn(err); - next(); - }); - }); + next(); + }); + }); + const _saveTextures = _debounce(next => { + Promise.all([ + textureImg.write(texturesPngDataPath), + _writeFileData( + texturesJsonDataPath, + JSON.stringify( + { + atlas: textureAtlas.toJson(), + uvs: textureUvs, + }, + null, + 2 + ) + ), + ]) + .then(() => { + next(); + }) + .catch(err => { + console.warn(err); + + next(); + }); + }); - class Generator { - getElevation(x, z) { - return noiser.getElevation(x, z); + class Generator { + getElevation(x, z) { + return noiser.getElevation(x, z); + } + getBiome(x, z) { + return noiser.getBiome(x, z); + } + registerGeometry(name, geometry) { + if (!geometry.getAttribute('ssao')) { + const ssaos = new Uint8Array( + geometry.getAttribute('position').array.length / 3 + ); + geometry.addAttribute('ssao', new THREE.BufferAttribute(ssaos, 1)); + } + if (!geometry.getAttribute('frame')) { + const frames = new Float32Array( + geometry.getAttribute('position').array.length + ); + geometry.addAttribute( + 'frame', + new THREE.BufferAttribute(frames, 3) + ); } - getBiome(x, z) { - return noiser.getBiome(x, z); + if (!geometry.boundingBox) { + geometry.computeBoundingBox(); } - registerGeometry(name, geometry) { - if (!geometry.getAttribute('ssao')) { - const ssaos = new Uint8Array(geometry.getAttribute('position').array.length / 3); - geometry.addAttribute('ssao', new THREE.BufferAttribute(ssaos, 1)); - } - if (!geometry.getAttribute('frame')) { - const frames = new Float32Array(geometry.getAttribute('position').array.length); - geometry.addAttribute('frame', new THREE.BufferAttribute(frames, 3)); - } - if (!geometry.boundingBox) { - geometry.computeBoundingBox(); - } - const offset = geometriesOffset; - geometriesOffset = protocolUtils.stringifyTemplate({ + const offset = geometriesOffset; + geometriesOffset = protocolUtils.stringifyTemplate( + { positions: geometry.getAttribute('position').array, uvs: geometry.getAttribute('uv').array, ssaos: geometry.getAttribute('ssao').array, @@ -477,216 +570,300 @@ class Generator { geometry.boundingBox.max.y, geometry.boundingBox.max.z, ]), - }, geometriesBuffer.buffer, geometriesBuffer.byteOffset + geometriesOffset)[1]; - - const index = geometriesIndex++; - geometryTypes[index * 2 + 0] = murmur(name); - geometryTypes[index * 2 + 1] = offset; - } - registerBlock(name, blockSpec) { - const index = ++blockTypesIndex; - // blockSpec.index = index; - - blockTypes[index] = murmur(name); - - transparentVoxels[index] = +blockSpec.transparent; - translucentVoxels[index] = +blockSpec.translucent; - for (let d = 0; d < 6; d++) { - faceUvs.set(Float32Array.from(blockSpec.uvs[d]), index * 6 * 4 + d * 4); - } + }, + geometriesBuffer.buffer, + geometriesBuffer.byteOffset + geometriesOffset + )[1]; + + const index = geometriesIndex++; + geometryTypes[index * 2 + 0] = murmur(name); + geometryTypes[index * 2 + 1] = offset; + } + registerBlock(name, blockSpec) { + const index = ++blockTypesIndex; + // blockSpec.index = index; + + blockTypes[index] = murmur(name); + + transparentVoxels[index] = +blockSpec.transparent; + translucentVoxels[index] = +blockSpec.translucent; + for (let d = 0; d < 6; d++) { + faceUvs.set( + Float32Array.from(blockSpec.uvs[d]), + index * 6 * 4 + d * 4 + ); } - registerLight(name, v) { - const index = ++lightsIndex; + } + registerLight(name, v) { + const index = ++lightsIndex; - lights[index * 2 + 0] = murmur(name); - lights[index * 2 + 1] = v; - } - setBlock(chunk, x, y, z, name) { - const ox = Math.floor(x / NUM_CELLS); - const oz = Math.floor(z / NUM_CELLS); - if (ox === chunk.x && oz === chunk.z && y > 0 && y < NUM_CELLS_HEIGHT) { - chunk.setBlock(x - ox * NUM_CELLS, y, z - oz * NUM_CELLS, murmur(name)); - } - } - clearBlock(chunk, x, y, z) { - const ox = Math.floor(x / NUM_CELLS); - const oz = Math.floor(z / NUM_CELLS); - if (ox === chunk.x && oz === chunk.z && y > 0 && y < NUM_CELLS_HEIGHT) { - chunk.clearBlock(x - ox * NUM_CELLS, y, z - oz * NUM_CELLS); - } + lights[index * 2 + 0] = murmur(name); + lights[index * 2 + 1] = v; + } + setBlock(chunk, x, y, z, name) { + const ox = Math.floor(x / NUM_CELLS); + const oz = Math.floor(z / NUM_CELLS); + if ( + ox === chunk.x && + oz === chunk.z && + y > 0 && + y < NUM_CELLS_HEIGHT + ) { + chunk.setBlock( + x - ox * NUM_CELLS, + y, + z - oz * NUM_CELLS, + murmur(name) + ); } - getUv(name) { - return textureUvs[murmur(name)]; + } + clearBlock(chunk, x, y, z) { + const ox = Math.floor(x / NUM_CELLS); + const oz = Math.floor(z / NUM_CELLS); + if ( + ox === chunk.x && + oz === chunk.z && + y > 0 && + y < NUM_CELLS_HEIGHT + ) { + chunk.clearBlock(x - ox * NUM_CELLS, y, z - oz * NUM_CELLS); } - getTileUv(name) { - const uv = textureUvs[murmur(name)]; + } + getUv(name) { + return textureUvs[murmur(name)]; + } + getTileUv(name) { + const uv = textureUvs[murmur(name)]; - const tileSizeU = uv[2] - uv[0]; - const tileSizeV = uv[3] - uv[1]; + const tileSizeU = uv[2] - uv[0]; + const tileSizeV = uv[3] - uv[1]; - const tileSizeIntU = Math.floor(tileSizeU * TEXTURE_SIZE) / 2; - const tileSizeIntV = Math.floor(tileSizeV * TEXTURE_SIZE) / 2; + const tileSizeIntU = Math.floor(tileSizeU * TEXTURE_SIZE) / 2; + const tileSizeIntV = Math.floor(tileSizeV * TEXTURE_SIZE) / 2; - const u = tileSizeIntU + uv[0]; - const v = tileSizeIntV + uv[1]; + const u = tileSizeIntU + uv[0]; + const v = tileSizeIntV + uv[1]; - return [-u, 1 - v, -u, 1 - v]; - } - registerTexture(name, img, {fourTap = false} = {}) { - const n = murmur(name); - - if (!textureUvs[n]) { - if (fourTap) { - const srcImg = img; - img = new jimp(srcImg.bitmap.width * 2, srcImg.bitmap.height * 2); - img.composite(srcImg, 0, 0); - img.composite(srcImg, srcImg.bitmap.width, 0); - img.composite(srcImg, 0, srcImg.bitmap.height); - img.composite(srcImg, srcImg.bitmap.width, srcImg.bitmap.height); - } - const rect = textureAtlas.pack(img.bitmap.width, img.bitmap.height); - const uv = textureAtlas.uv(rect); + return [-u, 1 - v, -u, 1 - v]; + } + registerTexture(name, img, { fourTap = false } = {}) { + const n = murmur(name); + + if (!textureUvs[n]) { + if (fourTap) { + const srcImg = img; + img = new jimp(srcImg.bitmap.width * 2, srcImg.bitmap.height * 2); + img.composite(srcImg, 0, 0); + img.composite(srcImg, srcImg.bitmap.width, 0); + img.composite(srcImg, 0, srcImg.bitmap.height); + img.composite(srcImg, srcImg.bitmap.width, srcImg.bitmap.height); + } + const rect = textureAtlas.pack(img.bitmap.width, img.bitmap.height); + const uv = textureAtlas.uv(rect); - textureImg.composite(img, rect.x, rect.y); - textureImg.version++; + textureImg.composite(img, rect.x, rect.y); + textureImg.version++; - textureUvs[n] = uv; + textureUvs[n] = uv; - _saveTextures(); - } + _saveTextures(); } - registerGenerator(name, fn) { - const n = murmur(name); - const generator = [n, fn]; - generators.push(generator); - - if (zde.chunks.length > 0) { - const promises = []; - for (let i = 0; i < zde.chunks.length; i++) { - const chunk = zde.chunks[i]; - if (_generateChunkObjectsWithGenerator(chunk, generator)) { - promises.push( - _decorateChunkObjectsGeometry(chunk) - .then(() => { - chunk.dirty = true; - }) - ); - } - } - if (promises.length > 0) { - Promise.all(promises) - .then(() => { - _saveChunks(); + } + registerGenerator(name, fn) { + const n = murmur(name); + const generator = [n, fn]; + generators.push(generator); + + if (zde.chunks.length > 0) { + const promises = []; + for (let i = 0; i < zde.chunks.length; i++) { + const chunk = zde.chunks[i]; + if (_generateChunkObjectsWithGenerator(chunk, generator)) { + promises.push( + _decorateChunkObjectsGeometry(chunk).then(() => { + chunk.dirty = true; }) - .catch(err => { - console.warn(err); - }); + ); } } - } - addObject(chunk, name, position, rotation, value) { - const n = murmur(name); - const matrix = position.toArray().concat(rotation.toArray()); - const objectIndex = chunk.addObject(n, matrix, value); - - const light = _findLight(n); - if (light) { - chunk.addLightAt(objectIndex, position.x, position.y, position.z, light); + if (promises.length > 0) { + Promise.all(promises) + .then(() => { + _saveChunks(); + }) + .catch(err => { + console.warn(err); + }); } } - requestLightmaps(x, z, positions) { - return _requestChunkHard(x, z) - .then(chunk => _requestChunkWithLights(chunk)) - .then(chunk => { - const terrainBuffer = chunk.getTerrainBuffer(); - const {staticHeightfield} = protocolUtils.parseTerrainData(terrainBuffer.buffer, terrainBuffer.byteOffset); - const {[lightsSymbol]: lights} = chunk; - - const numPositions = positions.length; - const numLightmaps = numPositions / 3; - const lightmapsBuffer = new ArrayBuffer(numLightmaps * 2); - const skyLightmaps = new Uint8Array(lightmapsBuffer, 0, numLightmaps); - const torchLightmaps = new Uint8Array(lightmapsBuffer, numLightmaps, numLightmaps); - - vxl.lightmap(chunk.x, chunk.z, positions, numPositions, staticHeightfield, lights, skyLightmaps, torchLightmaps); - - return { - skyLightmaps, - torchLightmaps, - }; - }); + } + addObject(chunk, name, position, rotation, value) { + const n = murmur(name); + const matrix = position.toArray().concat(rotation.toArray()); + const objectIndex = chunk.addObject(n, matrix, value); + + const light = _findLight(n); + if (light) { + chunk.addLightAt( + objectIndex, + position.x, + position.y, + position.z, + light + ); } - requestRelight(x, y, z) { - const promises = []; + } + requestLightmaps(x, z, positions) { + return _requestChunkHard(x, z) + .then(chunk => _requestChunkWithLights(chunk)) + .then(chunk => { + const terrainBuffer = chunk.getTerrainBuffer(); + const { staticHeightfield } = protocolUtils.parseTerrainData( + terrainBuffer.buffer, + terrainBuffer.byteOffset + ); + const { [lightsSymbol]: lights } = chunk; - const seenIndex = {}; - for (let i = 0; i < DIRECTIONS.length; i++) { - const [dx, dz] = DIRECTIONS[i]; - const ax = x + dx * (NUM_CELLS / 2); - const az = z + dz * (NUM_CELLS / 2); - const ox = Math.floor(ax / NUM_CELLS); - const oz = Math.floor(az / NUM_CELLS); - - const index = _getChunkIndex(ox, oz); - if (!seenIndex[index]) { - const chunk = zde.getChunk(ox, oz); - if (chunk && chunk[lightsRenderedSymbol]) { - promises.push(_decorateChunkLightsSub(chunk, x, y, z)); - } - seenIndex[index] = true; + const numPositions = positions.length; + const numLightmaps = numPositions / 3; + const lightmapsBuffer = new ArrayBuffer(numLightmaps * 2); + const skyLightmaps = new Uint8Array( + lightmapsBuffer, + 0, + numLightmaps + ); + const torchLightmaps = new Uint8Array( + lightmapsBuffer, + numLightmaps, + numLightmaps + ); + + vxl.lightmap( + chunk.x, + chunk.z, + positions, + numPositions, + staticHeightfield, + lights, + skyLightmaps, + torchLightmaps + ); + + return { + skyLightmaps, + torchLightmaps, + }; + }); + } + requestRelight(x, y, z) { + const promises = []; + + const seenIndex = {}; + for (let i = 0; i < DIRECTIONS.length; i++) { + const [dx, dz] = DIRECTIONS[i]; + const ax = x + dx * (NUM_CELLS / 2); + const az = z + dz * (NUM_CELLS / 2); + const ox = Math.floor(ax / NUM_CELLS); + const oz = Math.floor(az / NUM_CELLS); + + const index = _getChunkIndex(ox, oz); + if (!seenIndex[index]) { + const chunk = zde.getChunk(ox, oz); + if (chunk && chunk[lightsRenderedSymbol]) { + promises.push(_decorateChunkLightsSub(chunk, x, y, z)); } + seenIndex[index] = true; } - - return Promise.all(promises).then(() => {}); } - }; - const generatorElement = new Generator(); - elements.registerEntity(this, generatorElement); - - function serveObjectsTextureAtlas(req, res, next) { - textureImg.getBuffer('image/png', (err, buffer) => { - if (!err) { - res.type('image/png'); - res.send(buffer); - } else { - res.status(500); - res.json({ - error: err.stack, - }); - } - }); - } - app.get('/archae/objects/texture-atlas.png', serveObjectsTextureAtlas); - function serveObjectsObjectizeJs(req, res, next) { - res.type('application/javascript'); - fs.createReadStream(path.join(vxlPath, 'bin', 'objectize.js')).pipe(res); - } - app.get('/archae/objects/objectize.js', serveObjectsObjectizeJs); - function serveObjectsObjectizeWasm(req, res, next) { - res.type('application/octret-stream'); - fs.createReadStream(path.join(vxlPath, 'bin', 'objectize.wasm')).pipe(res); - } - app.get('/archae/objects/objectize.wasm', serveObjectsObjectizeWasm); - function serveObjectsTemplates(req, res, next) { - res.write(new Buffer(geometriesBuffer.buffer, geometriesBuffer.byteOffset, geometriesBuffer.byteLength)); - res.write(new Buffer(geometryTypes.buffer, geometryTypes.byteOffset, geometryTypes.byteLength)); - res.write(new Buffer(blockTypes.buffer, blockTypes.byteOffset, blockTypes.byteLength)); - res.write(new Buffer(transparentVoxels.buffer, transparentVoxels.byteOffset, transparentVoxels.byteLength)); - res.write(new Buffer(translucentVoxels.buffer, translucentVoxels.byteOffset, translucentVoxels.byteLength)); - res.write(new Buffer(faceUvs.buffer, faceUvs.byteOffset, faceUvs.byteLength)); - res.write(new Buffer(lights.buffer, lights.byteOffset, lights.byteLength)); - res.end(); + return Promise.all(promises).then(() => {}); } - app.get('/archae/objects/geometry.bin', serveObjectsTemplates); + } + const generatorElement = new Generator(); + elements.registerEntity(this, generatorElement); + + function serveObjectsTextureAtlas(req, res, next) { + textureImg.getBuffer('image/png', (err, buffer) => { + if (!err) { + res.type('image/png'); + res.send(buffer); + } else { + res.status(500); + res.json({ + error: err.stack, + }); + } + }); + } + app.get('/archae/objects/texture-atlas.png', serveObjectsTextureAtlas); + + function serveObjectsObjectizeJs(req, res, next) { + res.type('application/javascript'); + fs + .createReadStream(path.join(vxlPath, 'bin', 'objectize.js')) + .pipe(res); + } + app.get('/archae/objects/objectize.js', serveObjectsObjectizeJs); + function serveObjectsObjectizeWasm(req, res, next) { + res.type('application/octret-stream'); + fs + .createReadStream(path.join(vxlPath, 'bin', 'objectize.wasm')) + .pipe(res); + } + app.get('/archae/objects/objectize.wasm', serveObjectsObjectizeWasm); + function serveObjectsTemplates(req, res, next) { + res.write( + new Buffer( + geometriesBuffer.buffer, + geometriesBuffer.byteOffset, + geometriesBuffer.byteLength + ) + ); + res.write( + new Buffer( + geometryTypes.buffer, + geometryTypes.byteOffset, + geometryTypes.byteLength + ) + ); + res.write( + new Buffer( + blockTypes.buffer, + blockTypes.byteOffset, + blockTypes.byteLength + ) + ); + res.write( + new Buffer( + transparentVoxels.buffer, + transparentVoxels.byteOffset, + transparentVoxels.byteLength + ) + ); + res.write( + new Buffer( + translucentVoxels.buffer, + translucentVoxels.byteOffset, + translucentVoxels.byteLength + ) + ); + res.write( + new Buffer(faceUvs.buffer, faceUvs.byteOffset, faceUvs.byteLength) + ); + res.write( + new Buffer(lights.buffer, lights.byteOffset, lights.byteLength) + ); + res.end(); + } + app.get('/archae/objects/geometry.bin', serveObjectsTemplates); - function serveGeneratorChunks(req, res, next) { - const {query: {x: xs, z: zs}} = req; - const x = parseInt(xs, 10); - const z = parseInt(zs, 10); + function serveGeneratorChunks(req, res, next) { + const { query: { x: xs, z: zs } } = req; + const x = parseInt(xs, 10); + const z = parseInt(zs, 10); - if (!isNaN(x) && !isNaN(z)) { - /* new Promise((accept, reject) => { + if (!isNaN(x) && !isNaN(z)) { + /* new Promise((accept, reject) => { let chunk = zde.getChunk(x, z); if (!chunk) { chunk = _generateChunk(zde.makeChunk(x, z)); @@ -696,321 +873,386 @@ class Generator { }) .then(chunk => !chunk[lightsRenderedSymbol] ? _decorateChunkLights(chunk) : Promise.resolve(chunk)) .then(chunk => !chunk[lightmapsSymbol] ? _decorateChunkLightmaps(chunk) : Promise.resolve(chunk)) */ - _requestChunk(x, z) - .then(chunk => { - res.type('application/octet-stream'); - res.set('Texture-Atlas-Version', textureImg.version); - res.set('Geometry-Version', geometriesBuffer.version); - - const terrainBuffer = chunk.getTerrainBuffer(); - res.write(new Buffer(terrainBuffer.buffer, terrainBuffer.byteOffset, terrainBuffer.byteLength)); + _requestChunk(x, z) + .then(chunk => { + res.type('application/octet-stream'); + res.set('Texture-Atlas-Version', textureImg.version); + res.set('Geometry-Version', geometriesBuffer.version); - const objectBuffer = chunk.getObjectBuffer(); - res.write(new Buffer(objectBuffer.buffer, objectBuffer.byteOffset, objectBuffer.byteLength)); - - const blockBuffer = chunk.getBlockBuffer(); - res.write(new Buffer(blockBuffer.buffer, blockBuffer.byteOffset, blockBuffer.byteLength)); - - const lightBuffer = chunk.getLightBuffer(); - res.write(new Buffer(lightBuffer.buffer, lightBuffer.byteOffset, lightBuffer.byteLength)); - - const geometryBuffer = chunk.getGeometryBuffer(); - res.write(new Buffer(geometryBuffer.buffer, geometryBuffer.byteOffset, geometryBuffer.byteLength)); + const terrainBuffer = chunk.getTerrainBuffer(); + res.write( + new Buffer( + terrainBuffer.buffer, + terrainBuffer.byteOffset, + terrainBuffer.byteLength + ) + ); + + const objectBuffer = chunk.getObjectBuffer(); + res.write( + new Buffer( + objectBuffer.buffer, + objectBuffer.byteOffset, + objectBuffer.byteLength + ) + ); + + const blockBuffer = chunk.getBlockBuffer(); + res.write( + new Buffer( + blockBuffer.buffer, + blockBuffer.byteOffset, + blockBuffer.byteLength + ) + ); + + const lightBuffer = chunk.getLightBuffer(); + res.write( + new Buffer( + lightBuffer.buffer, + lightBuffer.byteOffset, + lightBuffer.byteLength + ) + ); - const [arrayBuffer, byteOffset] = protocolUtils.stringifyDecorations(chunk[lightmapsSymbol], chunk[blockfieldSymbol]); - res.end(new Buffer(arrayBuffer, 0, byteOffset)); - }) - .catch(err => { - console.warn(err); + const geometryBuffer = chunk.getGeometryBuffer(); + res.write( + new Buffer( + geometryBuffer.buffer, + geometryBuffer.byteOffset, + geometryBuffer.byteLength + ) + ); + + const [ + arrayBuffer, + byteOffset, + ] = protocolUtils.stringifyDecorations( + chunk[lightmapsSymbol], + chunk[blockfieldSymbol] + ); + res.end(new Buffer(arrayBuffer, 0, byteOffset)); + }) + .catch(err => { + console.warn(err); - res.status(500); - res.json({ - error: err.stack, - }); + res.status(500); + res.json({ + error: err.stack, }); - } else { - res.status(400); - res.send(); - } + }); + } else { + res.status(400); + res.send(); } - app.get('/archae/generator/chunks', serveGeneratorChunks); + } + app.get('/archae/generator/chunks', serveGeneratorChunks); - const connections = []; - const _connection = c => { - const {url} = c.upgradeReq; + const connections = []; + const _connection = c => { + const { url } = c.upgradeReq; - if (url === '/archae/generatorWs') { - const _broadcast = e => { - const es = JSON.stringify(e); + if (url === '/archae/generatorWs') { + const _broadcast = e => { + 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); - } + for (let i = 0; i < connections.length; i++) { + const connection = connections[i]; + if (connection.readyState === ws.OPEN && connection !== c) { + connection.send(es); } - }; + } + }; + + c.on('message', msg => { + const m = JSON.parse(msg); + const { method } = m; + + if (method === 'mutateVoxel') { + const { id, args } = m; + const { x, y, z, v } = args; + + const regeneratePromises = []; + const seenIndex = {}; + for (let i = 0; i < DIRECTIONS.length; i++) { + const [dx, dz] = DIRECTIONS[i]; + const ax = x + dx * 2; + const az = z + dz * 2; + const ox = Math.floor(ax / NUM_CELLS); + const oz = Math.floor(az / NUM_CELLS); + const lx = x - ox * NUM_CELLS; + const lz = z - oz * NUM_CELLS; + const newEther = Float32Array.from([lx, y, lz, v]); + + const index = _getChunkIndex(ox, oz); + if (!seenIndex[index]) { + let chunk = zde.getChunk(ox, oz); + if (chunk) { + const oldTerrainBuffer = chunk.getTerrainBuffer(); + const oldChunkData = protocolUtils.parseTerrainData( + oldTerrainBuffer.buffer, + oldTerrainBuffer.byteOffset + ); + const oldBiomes = oldChunkData.biomes.slice(); + const oldElevations = oldChunkData.elevations.slice(); + const oldEther = oldChunkData.ether.slice(); + const oldWater = oldChunkData.water.slice(); + const oldLava = oldChunkData.lava.slice(); + + _generateChunkTerrain(chunk, { + oldBiomes, + oldElevations, + oldEther, + oldWater, + oldLava, + newEther, + }); - c.on('message', msg => { - const m = JSON.parse(msg); - const {method} = m; - - if (method === 'mutateVoxel') { - const {id, args} = m; - const {x, y, z, v} = args; - - const regeneratePromises = []; - const seenIndex = {}; - for (let i = 0; i < DIRECTIONS.length; i++) { - const [dx, dz] = DIRECTIONS[i]; - const ax = x + dx * 2; - const az = z + dz * 2; - const ox = Math.floor(ax / NUM_CELLS); - const oz = Math.floor(az / NUM_CELLS); - const lx = x - (ox * NUM_CELLS); - const lz = z - (oz * NUM_CELLS); - const newEther = Float32Array.from([lx, y, lz, v]); - - const index = _getChunkIndex(ox, oz); - if (!seenIndex[index]) { - let chunk = zde.getChunk(ox, oz); - if (chunk) { - const oldTerrainBuffer = chunk.getTerrainBuffer(); - const oldChunkData = protocolUtils.parseTerrainData(oldTerrainBuffer.buffer, oldTerrainBuffer.byteOffset); - const oldBiomes = oldChunkData.biomes.slice(); - const oldElevations = oldChunkData.elevations.slice(); - const oldEther = oldChunkData.ether.slice(); - const oldWater = oldChunkData.water.slice(); - const oldLava = oldChunkData.lava.slice(); - - _generateChunkTerrain(chunk, { - oldBiomes, - oldElevations, - oldEther, - oldWater, - oldLava, - newEther, - }); - - regeneratePromises.push(generatorElement.requestRelight(x, y, z)); - } - - seenIndex[index] = true; + regeneratePromises.push( + generatorElement.requestRelight(x, y, z) + ); } + + seenIndex[index] = true; } - _saveChunks(); + } + _saveChunks(); - Promise.all(regeneratePromises) - .then(regeneratedChunks => { - c.send(JSON.stringify({ - type: 'response', - id, - result: null, - })); + Promise.all(regeneratePromises).then(regeneratedChunks => { + c.send( + JSON.stringify({ + type: 'response', + id, + result: null, + }) + ); - _broadcast({ - type: 'mutateVoxel', - args, - result: {x, y, z, v}, - }); - }); - } else if (method === 'addObject') { - const {id, args} = m; - const {n, positions, rotations, value} = args; - - const ox = Math.floor(positions[0] / NUM_CELLS); - const oz = Math.floor(positions[2] / NUM_CELLS); - - const chunk = zde.getChunk(ox, oz); - if (chunk) { - const matrix = positions.concat(rotations).concat(zeroVectorArray); - const objectIndex = chunk.addObject(n, matrix, value); - - _decorateChunkObjectsGeometry(chunk) - .then(() => { - _saveChunks(); - - return generatorElement.requestRelight(Math.floor(positions[0]), Math.floor(positions[1]), Math.floor(positions[2])); - }) - .then(() => { - c.send(JSON.stringify({ + _broadcast({ + type: 'mutateVoxel', + args, + result: { x, y, z, v }, + }); + }); + } else if (method === 'addObject') { + const { id, args } = m; + const { n, positions, rotations, value } = args; + + const ox = Math.floor(positions[0] / NUM_CELLS); + const oz = Math.floor(positions[2] / NUM_CELLS); + + const chunk = zde.getChunk(ox, oz); + if (chunk) { + const matrix = positions + .concat(rotations) + .concat(zeroVectorArray); + const objectIndex = chunk.addObject(n, matrix, value); + + _decorateChunkObjectsGeometry(chunk) + .then(() => { + _saveChunks(); + + return generatorElement.requestRelight( + Math.floor(positions[0]), + Math.floor(positions[1]), + Math.floor(positions[2]) + ); + }) + .then(() => { + c.send( + JSON.stringify({ type: 'response', id, result: objectIndex, - })); + }) + ); - _broadcast({ - type: 'addObject', - args, - result: objectIndex, - }); - }) - .catch(err => { - console.warn(err); + _broadcast({ + type: 'addObject', + args, + result: objectIndex, }); - } - } else if (method === 'removeObject') { - const {id, args} = m; - const {x, z, index} = args; - - const chunk = zde.getChunk(x, z); - if (chunk) { - const matrix = chunk.getObjectMatrix(index); - const n = chunk.removeObject(index); - chunk.removeLight(index); - - _decorateChunkObjectsGeometry(chunk) - .then(() => { - _saveChunks(); - - return generatorElement.requestRelight(Math.floor(matrix[0]), Math.floor(matrix[1]), Math.floor(matrix[2])); - }) - .then(() => { - c.send(JSON.stringify({ + }) + .catch(err => { + console.warn(err); + }); + } + } else if (method === 'removeObject') { + const { id, args } = m; + const { x, z, index } = args; + + const chunk = zde.getChunk(x, z); + if (chunk) { + const matrix = chunk.getObjectMatrix(index); + const n = chunk.removeObject(index); + chunk.removeLight(index); + + _decorateChunkObjectsGeometry(chunk) + .then(() => { + _saveChunks(); + + return generatorElement.requestRelight( + Math.floor(matrix[0]), + Math.floor(matrix[1]), + Math.floor(matrix[2]) + ); + }) + .then(() => { + c.send( + JSON.stringify({ type: 'response', id, result: n, - })); + }) + ); - _broadcast({ - type: 'removeObject', - args, - result: n, - }); - }) - .catch(err => { - console.warn(err); + _broadcast({ + type: 'removeObject', + args, + result: n, }); - } - } else if (method === 'setObjectData') { - const {id, args} = m; - const {x, z, index, value} = args; + }) + .catch(err => { + console.warn(err); + }); + } + } else if (method === 'setObjectData') { + const { id, args } = m; + const { x, z, index, value } = args; - const chunk = zde.getChunk(x, z); - if (chunk) { - chunk.setObjectData(index, value); + const chunk = zde.getChunk(x, z); + if (chunk) { + chunk.setObjectData(index, value); - _saveChunks(); + _saveChunks(); - c.send(JSON.stringify({ + c.send( + JSON.stringify({ type: 'response', id, result: null, - })); + }) + ); - _broadcast({ - type: 'setObjectData', - args, - result: null, - }); - } - } else if (method === 'setBlock') { - const {id, args} = m; - const {x, y, z, v} = args; - - const ox = Math.floor(x / NUM_CELLS); - const oz = Math.floor(z / NUM_CELLS); - const chunk = zde.getChunk(ox, oz); - if (chunk) { - chunk.setBlock(x - ox * NUM_CELLS, y, z - oz * NUM_CELLS, v); - chunk[blockfieldRenderedSymbol] = false; - - _decorateChunkObjectsGeometry(chunk) - .then(() => { - _saveChunks(); - - return generatorElement.requestRelight(x, y, z); - }) - .then(() => { - c.send(JSON.stringify({ + _broadcast({ + type: 'setObjectData', + args, + result: null, + }); + } + } else if (method === 'setBlock') { + const { id, args } = m; + const { x, y, z, v } = args; + + const ox = Math.floor(x / NUM_CELLS); + const oz = Math.floor(z / NUM_CELLS); + const chunk = zde.getChunk(ox, oz); + if (chunk) { + chunk.setBlock(x - ox * NUM_CELLS, y, z - oz * NUM_CELLS, v); + chunk[blockfieldRenderedSymbol] = false; + + _decorateChunkObjectsGeometry(chunk) + .then(() => { + _saveChunks(); + + return generatorElement.requestRelight(x, y, z); + }) + .then(() => { + c.send( + JSON.stringify({ type: 'response', id, result: null, - })); + }) + ); - _broadcast({ - type: 'setBlock', - args, - result: null, - }); - }) - .catch(err => { - console.warn(err); + _broadcast({ + type: 'setBlock', + args, + result: null, }); - } - } else if (method === 'clearBlock') { - const {id, args} = m; - const {x, y, z} = args; - - const ox = Math.floor(x / NUM_CELLS); - const oz = Math.floor(z / NUM_CELLS); - const chunk = zde.getChunk(ox, oz); - if (chunk) { - chunk.clearBlock(x - ox * NUM_CELLS, y, z - oz * NUM_CELLS); - chunk[blockfieldRenderedSymbol] = false; - - _decorateChunkObjectsGeometry(chunk) - .then(() => { - _saveChunks(); - - return generatorElement.requestRelight(x, y, z); - }) - .then(() => { - c.send(JSON.stringify({ + }) + .catch(err => { + console.warn(err); + }); + } + } else if (method === 'clearBlock') { + const { id, args } = m; + const { x, y, z } = args; + + const ox = Math.floor(x / NUM_CELLS); + const oz = Math.floor(z / NUM_CELLS); + const chunk = zde.getChunk(ox, oz); + if (chunk) { + chunk.clearBlock(x - ox * NUM_CELLS, y, z - oz * NUM_CELLS); + chunk[blockfieldRenderedSymbol] = false; + + _decorateChunkObjectsGeometry(chunk) + .then(() => { + _saveChunks(); + + return generatorElement.requestRelight(x, y, z); + }) + .then(() => { + c.send( + JSON.stringify({ type: 'response', id, result: null, - })); + }) + ); - _broadcast({ - type: 'clearBlock', - args, - result: null, - }); - }) - .catch(err => { - console.warn(err); + _broadcast({ + type: 'clearBlock', + args, + result: null, }); - } - } else { - console.warn('objects server got unknown method:', JSON.stringify(method)); + }) + .catch(err => { + console.warn(err); + }); } - }); - c.on('close', () => { - connections.splice(connections.indexOf(c), 1); - }); + } else { + console.warn( + 'objects server got unknown method:', + JSON.stringify(method) + ); + } + }); + c.on('close', () => { + connections.splice(connections.indexOf(c), 1); + }); - connections.push(c); + connections.push(c); + } + }; + wss.on('connection', _connection); + + this._cleanup = () => { + function removeMiddlewares(route, i, routes) { + if ( + route.handle.name === 'serveObjectsTextureAtlas' || + route.handle.name === 'serveObjectsObjectizeJs' || + route.handle.name === 'serveObjectsObjectizeWasm' || + route.handle.name === 'serveObjectsTemplates' || + route.handle.name === 'serveGeneratorChunks' || + route.handle.name === 'serveGeneratorVoxels' + ) { + routes.splice(i, 1); } - }; - wss.on('connection', _connection); - - this._cleanup = () => { - function removeMiddlewares(route, i, routes) { - if ( - route.handle.name === 'serveObjectsTextureAtlas' || - route.handle.name === 'serveObjectsObjectizeJs' || - route.handle.name === 'serveObjectsObjectizeWasm' || - route.handle.name === 'serveObjectsTemplates' || - route.handle.name === 'serveGeneratorChunks' || - route.handle.name === 'serveGeneratorVoxels' - ) { - 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); - for (let i = 0; i < connections.length; i++) { - connections[i].close(); - } - wss.removeListener('connection', _connection); + for (let i = 0; i < connections.length; i++) { + connections[i].close(); + } + wss.removeListener('connection', _connection); - elements.unregisterEntity(this, generatorElement); - }; - }); + elements.unregisterEntity(this, generatorElement); + }; + }); } unmount() { diff --git a/plugins/generator/terrain-tesselator.js b/plugins/generator/terrain-tesselator.js index 5b4eb5a24..e3c98f1de 100644 --- a/plugins/generator/terrain-tesselator.js +++ b/plugins/generator/terrain-tesselator.js @@ -18,129 +18,192 @@ const NUM_CELLS_HALF = NUM_CELLS / 2; const NUM_CELLS_CUBE = Math.sqrt(NUM_CELLS_HALF * NUM_CELLS_HALF * 3); const NUM_CELLS_OVERSCAN_Y = NUM_CELLS_HEIGHT + OVERSCAN; -module.exports = ({ - THREE, - mod, - murmur, - alea, - vxl, - noiser, -}) => { - -const _align = (n, alignment) => { - let alignDiff = n % alignment; - if (alignDiff > 0) { - n += alignment - alignDiff; - } - return n; -}; - -const slab = (() => { - const BIOMES_SIZE = _align(NUM_CELLS_OVERSCAN * NUM_CELLS_OVERSCAN * Uint8Array.BYTES_PER_ELEMENT, Float32Array.BYTES_PER_ELEMENT); - const ELEVATIONS_SIZE = NUM_CELLS_OVERSCAN * NUM_CELLS_OVERSCAN * Float32Array.BYTES_PER_ELEMENT; - const ETHER_SIZE = ((NUM_CELLS + 1) * (NUM_CELLS_HEIGHT + 1) * (NUM_CELLS + 1)) * Float32Array.BYTES_PER_ELEMENT; - const WATER_SIZE = ((NUM_CELLS + 1) * (NUM_CELLS_HEIGHT + 1) * (NUM_CELLS + 1)) * Float32Array.BYTES_PER_ELEMENT; - const LAVA_SIZE = ((NUM_CELLS + 1) * (NUM_CELLS_HEIGHT + 1) * (NUM_CELLS + 1)) * Float32Array.BYTES_PER_ELEMENT; - const POSITIONS_SIZE = NUM_POSITIONS_CHUNK * Float32Array.BYTES_PER_ELEMENT; - const INDICES_SIZE = NUM_POSITIONS_CHUNK * Uint32Array.BYTES_PER_ELEMENT; - const COLORS_SIZE = NUM_POSITIONS_CHUNK * Float32Array.BYTES_PER_ELEMENT; - const HEIGHTFIELD_SIZE = NUM_CELLS_OVERSCAN * NUM_CELLS_OVERSCAN * HEIGHTFIELD_DEPTH * Float32Array.BYTES_PER_ELEMENT; - const STATIC_HEIGHTFIELD_SIZE = NUM_CELLS_OVERSCAN * NUM_CELLS_OVERSCAN * Float32Array.BYTES_PER_ELEMENT; - const ATTRIBUTE_RANGES_SIZE = NUM_CHUNKS_HEIGHT * 6 * Uint32Array.BYTES_PER_ELEMENT; - const INDEX_RANGES_SIZE = NUM_CHUNKS_HEIGHT * 6 * Uint32Array.BYTES_PER_ELEMENT; - const PEEK_SIZE = 16 * Uint8Array.BYTES_PER_ELEMENT; - const PEEKS_ARRAY_SIZE = PEEK_SIZE * NUM_CHUNKS_HEIGHT; - - const buffer = new ArrayBuffer( - BIOMES_SIZE + - ELEVATIONS_SIZE + - ETHER_SIZE + - WATER_SIZE + - LAVA_SIZE + - POSITIONS_SIZE + - INDICES_SIZE + - COLORS_SIZE + - HEIGHTFIELD_SIZE + - STATIC_HEIGHTFIELD_SIZE + - ATTRIBUTE_RANGES_SIZE + - INDEX_RANGES_SIZE + - PEEKS_ARRAY_SIZE - ); - - let index = 0; - const biomes = new Uint8Array(buffer, index, BIOMES_SIZE / Uint8Array.BYTES_PER_ELEMENT); - index += BIOMES_SIZE; - const elevations = new Float32Array(buffer, index, ELEVATIONS_SIZE / Float32Array.BYTES_PER_ELEMENT); - index += ELEVATIONS_SIZE; - const ether = new Float32Array(buffer, index, ETHER_SIZE / Float32Array.BYTES_PER_ELEMENT); - index += ETHER_SIZE; - const water = new Float32Array(buffer, index, WATER_SIZE / Float32Array.BYTES_PER_ELEMENT); - index += WATER_SIZE; - const lava = new Float32Array(buffer, index, LAVA_SIZE / Float32Array.BYTES_PER_ELEMENT); - index += LAVA_SIZE; - const positions = new Float32Array(buffer, index, POSITIONS_SIZE / Float32Array.BYTES_PER_ELEMENT); - index += POSITIONS_SIZE; - const indices = new Uint32Array(buffer, index, INDICES_SIZE / Uint32Array.BYTES_PER_ELEMENT); - index += INDICES_SIZE; - const colors = new Float32Array(buffer, index, COLORS_SIZE / Float32Array.BYTES_PER_ELEMENT); - index += COLORS_SIZE; - const heightfield = new Float32Array(buffer, index, HEIGHTFIELD_SIZE / Float32Array.BYTES_PER_ELEMENT); - index += HEIGHTFIELD_SIZE; - const staticHeightfield = new Float32Array(buffer, index, STATIC_HEIGHTFIELD_SIZE / Float32Array.BYTES_PER_ELEMENT); - index += STATIC_HEIGHTFIELD_SIZE; - const attributeRanges = new Uint32Array(ATTRIBUTE_RANGES_SIZE / Uint32Array.BYTES_PER_ELEMENT); - index += ATTRIBUTE_RANGES_SIZE; - const indexRanges = new Uint32Array(INDEX_RANGES_SIZE / Uint32Array.BYTES_PER_ELEMENT); - index += INDEX_RANGES_SIZE; - const peeks = new Uint8Array(buffer, index, PEEK_SIZE * NUM_CHUNKS_HEIGHT); - const peeksArray = Array(NUM_CHUNKS_HEIGHT); - for (let i = 0; i < NUM_CHUNKS_HEIGHT; i++) { - peeksArray[i] = new Uint8Array(buffer, index, PEEK_SIZE / Uint8Array.BYTES_PER_ELEMENT); - index += PEEK_SIZE; - } - - return { - biomes, - elevations, - ether, - water, - lava, - positions, - indices, - colors, - heightfield, - staticHeightfield, - attributeRanges, - indexRanges, - peeks, - peeksArray, +module.exports = ({ THREE, mod, murmur, alea, vxl, noiser }) => { + const _align = (n, alignment) => { + let alignDiff = n % alignment; + if (alignDiff > 0) { + n += alignment - alignDiff; + } + return n; }; -})(); - -const _generateMapChunk = (ox, oy, opts) => { - // generate - - let biomes = opts.oldBiomes; - let fillBiomes = false; - if (!biomes) { - biomes = slab.biomes; - fillBiomes = true; - } - - let elevations = opts.oldElevations; - let fillElevations = false; - if (!elevations) { - elevations = slab.elevations; - fillElevations = true; - } - - let ether = opts.oldEther; - let fillEther = false; - if (!ether) { - ether = slab.ether; - fillEther = true; - - /* let index = 0; + + const slab = (() => { + const BIOMES_SIZE = _align( + NUM_CELLS_OVERSCAN * NUM_CELLS_OVERSCAN * Uint8Array.BYTES_PER_ELEMENT, + Float32Array.BYTES_PER_ELEMENT + ); + const ELEVATIONS_SIZE = + NUM_CELLS_OVERSCAN * NUM_CELLS_OVERSCAN * Float32Array.BYTES_PER_ELEMENT; + const ETHER_SIZE = + (NUM_CELLS + 1) * + (NUM_CELLS_HEIGHT + 1) * + (NUM_CELLS + 1) * + Float32Array.BYTES_PER_ELEMENT; + const WATER_SIZE = + (NUM_CELLS + 1) * + (NUM_CELLS_HEIGHT + 1) * + (NUM_CELLS + 1) * + Float32Array.BYTES_PER_ELEMENT; + const LAVA_SIZE = + (NUM_CELLS + 1) * + (NUM_CELLS_HEIGHT + 1) * + (NUM_CELLS + 1) * + Float32Array.BYTES_PER_ELEMENT; + const POSITIONS_SIZE = NUM_POSITIONS_CHUNK * Float32Array.BYTES_PER_ELEMENT; + const INDICES_SIZE = NUM_POSITIONS_CHUNK * Uint32Array.BYTES_PER_ELEMENT; + const COLORS_SIZE = NUM_POSITIONS_CHUNK * Float32Array.BYTES_PER_ELEMENT; + const HEIGHTFIELD_SIZE = + NUM_CELLS_OVERSCAN * + NUM_CELLS_OVERSCAN * + HEIGHTFIELD_DEPTH * + Float32Array.BYTES_PER_ELEMENT; + const STATIC_HEIGHTFIELD_SIZE = + NUM_CELLS_OVERSCAN * NUM_CELLS_OVERSCAN * Float32Array.BYTES_PER_ELEMENT; + const ATTRIBUTE_RANGES_SIZE = + NUM_CHUNKS_HEIGHT * 6 * Uint32Array.BYTES_PER_ELEMENT; + const INDEX_RANGES_SIZE = + NUM_CHUNKS_HEIGHT * 6 * Uint32Array.BYTES_PER_ELEMENT; + const PEEK_SIZE = 16 * Uint8Array.BYTES_PER_ELEMENT; + const PEEKS_ARRAY_SIZE = PEEK_SIZE * NUM_CHUNKS_HEIGHT; + + const buffer = new ArrayBuffer( + BIOMES_SIZE + + ELEVATIONS_SIZE + + ETHER_SIZE + + WATER_SIZE + + LAVA_SIZE + + POSITIONS_SIZE + + INDICES_SIZE + + COLORS_SIZE + + HEIGHTFIELD_SIZE + + STATIC_HEIGHTFIELD_SIZE + + ATTRIBUTE_RANGES_SIZE + + INDEX_RANGES_SIZE + + PEEKS_ARRAY_SIZE + ); + + let index = 0; + const biomes = new Uint8Array( + buffer, + index, + BIOMES_SIZE / Uint8Array.BYTES_PER_ELEMENT + ); + index += BIOMES_SIZE; + const elevations = new Float32Array( + buffer, + index, + ELEVATIONS_SIZE / Float32Array.BYTES_PER_ELEMENT + ); + index += ELEVATIONS_SIZE; + const ether = new Float32Array( + buffer, + index, + ETHER_SIZE / Float32Array.BYTES_PER_ELEMENT + ); + index += ETHER_SIZE; + const water = new Float32Array( + buffer, + index, + WATER_SIZE / Float32Array.BYTES_PER_ELEMENT + ); + index += WATER_SIZE; + const lava = new Float32Array( + buffer, + index, + LAVA_SIZE / Float32Array.BYTES_PER_ELEMENT + ); + index += LAVA_SIZE; + const positions = new Float32Array( + buffer, + index, + POSITIONS_SIZE / Float32Array.BYTES_PER_ELEMENT + ); + index += POSITIONS_SIZE; + const indices = new Uint32Array( + buffer, + index, + INDICES_SIZE / Uint32Array.BYTES_PER_ELEMENT + ); + index += INDICES_SIZE; + const colors = new Float32Array( + buffer, + index, + COLORS_SIZE / Float32Array.BYTES_PER_ELEMENT + ); + index += COLORS_SIZE; + const heightfield = new Float32Array( + buffer, + index, + HEIGHTFIELD_SIZE / Float32Array.BYTES_PER_ELEMENT + ); + index += HEIGHTFIELD_SIZE; + const staticHeightfield = new Float32Array( + buffer, + index, + STATIC_HEIGHTFIELD_SIZE / Float32Array.BYTES_PER_ELEMENT + ); + index += STATIC_HEIGHTFIELD_SIZE; + const attributeRanges = new Uint32Array( + ATTRIBUTE_RANGES_SIZE / Uint32Array.BYTES_PER_ELEMENT + ); + index += ATTRIBUTE_RANGES_SIZE; + const indexRanges = new Uint32Array( + INDEX_RANGES_SIZE / Uint32Array.BYTES_PER_ELEMENT + ); + index += INDEX_RANGES_SIZE; + const peeks = new Uint8Array(buffer, index, PEEK_SIZE * NUM_CHUNKS_HEIGHT); + const peeksArray = Array(NUM_CHUNKS_HEIGHT); + for (let i = 0; i < NUM_CHUNKS_HEIGHT; i++) { + peeksArray[i] = new Uint8Array( + buffer, + index, + PEEK_SIZE / Uint8Array.BYTES_PER_ELEMENT + ); + index += PEEK_SIZE; + } + + return { + biomes, + elevations, + ether, + water, + lava, + positions, + indices, + colors, + heightfield, + staticHeightfield, + attributeRanges, + indexRanges, + peeks, + peeksArray, + }; + })(); + + const _generateMapChunk = (ox, oy, opts) => { + // generate + + let biomes = opts.oldBiomes; + let fillBiomes = false; + if (!biomes) { + biomes = slab.biomes; + fillBiomes = true; + } + + let elevations = opts.oldElevations; + let fillElevations = false; + if (!elevations) { + elevations = slab.elevations; + fillElevations = true; + } + + let ether = opts.oldEther; + let fillEther = false; + if (!ether) { + ether = slab.ether; + fillEther = true; + + /* let index = 0; for (let z = 0; z < NUM_CELLS_OVERSCAN; z++) { for (let x = 0; x < NUM_CELLS_OVERSCAN; x++) { const elevation = elevations[index++]; @@ -150,7 +213,7 @@ const _generateMapChunk = (ox, oy, opts) => { } } */ - /* const _fillOblateSpheroid = (centerX, centerY, centerZ, minX, minZ, maxX, maxZ, radius) => { + /* const _fillOblateSpheroid = (centerX, centerY, centerZ, minX, minZ, maxX, maxZ, radius) => { for (let z = -radius; z <= radius; z++) { const lz = centerZ + z; if (lz >= minZ && lz < (maxZ + 1)) { @@ -234,60 +297,70 @@ const _generateMapChunk = (ox, oy, opts) => { } } } */ - } - - let water = opts.oldWater; - let lava = opts.oldLava; - let fillLiquid = false; - if (!water || !lava) { - water = slab.water; - lava = slab.lava; - fillLiquid = true; - } - - let newEther; - let numNewEthers = 0; - if (opts.newEther && opts.newEther.length > 0) { - newEther = opts.newEther; - numNewEthers = opts.newEther.length; - } else { - newEther = new Float32Array(0); - } - - const {heightfield, staticHeightfield, colors, attributeRanges, indexRanges, peeks} = slab; - noiser.fill( - ox, - oy, - biomes, - fillBiomes, - elevations, - fillElevations, - ether, - fillEther, - water, - lava, - fillLiquid, - newEther, - numNewEthers, - slab.positions, - slab.indices, - attributeRanges, - indexRanges, - heightfield, - staticHeightfield, - colors, - peeks - ); - - const attributeIndex = attributeRanges[attributeRanges.length - 2] + attributeRanges[attributeRanges.length - 1]; - const indexIndex = indexRanges[indexRanges.length - 2] + indexRanges[indexRanges.length - 1]; - const positions = slab.positions.subarray(0, attributeIndex); - const indices = slab.indices.subarray(0, indexIndex); - - const geometries = Array(NUM_CHUNKS_HEIGHT); - for (let i = 0; i < NUM_CHUNKS_HEIGHT; i++) { - geometries[i] = { - /* attributeRange: { + } + + let water = opts.oldWater; + let lava = opts.oldLava; + let fillLiquid = false; + if (!water || !lava) { + water = slab.water; + lava = slab.lava; + fillLiquid = true; + } + + let newEther; + let numNewEthers = 0; + if (opts.newEther && opts.newEther.length > 0) { + newEther = opts.newEther; + numNewEthers = opts.newEther.length; + } else { + newEther = new Float32Array(0); + } + + const { + heightfield, + staticHeightfield, + colors, + attributeRanges, + indexRanges, + peeks, + } = slab; + noiser.fill( + ox, + oy, + biomes, + fillBiomes, + elevations, + fillElevations, + ether, + fillEther, + water, + lava, + fillLiquid, + newEther, + numNewEthers, + slab.positions, + slab.indices, + attributeRanges, + indexRanges, + heightfield, + staticHeightfield, + colors, + peeks + ); + + const attributeIndex = + attributeRanges[attributeRanges.length - 2] + + attributeRanges[attributeRanges.length - 1]; + const indexIndex = + indexRanges[indexRanges.length - 2] + indexRanges[indexRanges.length - 1]; + const positions = slab.positions.subarray(0, attributeIndex); + const indices = slab.indices.subarray(0, indexIndex); + + const geometries = Array(NUM_CHUNKS_HEIGHT); + for (let i = 0; i < NUM_CHUNKS_HEIGHT; i++) { + geometries[i] = { + /* attributeRange: { landStart: attributeRanges[i * 6 + 0], landCount: attributeRanges[i * 6 + 1], waterStart: attributeRanges[i * 6 + 2], @@ -295,43 +368,46 @@ const _generateMapChunk = (ox, oy, opts) => { lavaStart: attributeRanges[i * 6 + 4], lavaCount: attributeRanges[i * 6 + 5], }, */ - indexRange: { - landStart: indexRanges[i * 6 + 0], - landCount: indexRanges[i * 6 + 1], - waterStart: indexRanges[i * 6 + 2], - waterCount: indexRanges[i * 6 + 3], - lavaStart: indexRanges[i * 6 + 4], - lavaCount: indexRanges[i * 6 + 5], - }, - boundingSphere: Float32Array.from([ - ox * NUM_CELLS + NUM_CELLS_HALF, - i * NUM_CELLS + NUM_CELLS_HALF, - oy * NUM_CELLS + NUM_CELLS_HALF, - NUM_CELLS_CUBE, - ]), - peeks: slab.peeksArray[i], + indexRange: { + landStart: indexRanges[i * 6 + 0], + landCount: indexRanges[i * 6 + 1], + waterStart: indexRanges[i * 6 + 2], + waterCount: indexRanges[i * 6 + 3], + lavaStart: indexRanges[i * 6 + 4], + lavaCount: indexRanges[i * 6 + 5], + }, + boundingSphere: Float32Array.from([ + ox * NUM_CELLS + NUM_CELLS_HALF, + i * NUM_CELLS + NUM_CELLS_HALF, + oy * NUM_CELLS + NUM_CELLS_HALF, + NUM_CELLS_CUBE, + ]), + peeks: slab.peeksArray[i], + }; + } + + return { + positions, + colors: new Float32Array( + colors.buffer, + colors.byteOffset, + attributeIndex + ), + indices, + geometries, + heightfield, + staticHeightfield, + biomes, + elevations, + ether, + water, + lava, }; - } - - return { - positions, - colors: new Float32Array(colors.buffer, colors.byteOffset, attributeIndex), - indices, - geometries, - heightfield, - staticHeightfield, - biomes, - elevations, - ether, - water, - lava, }; -}; - -const generate = (x, y, opts = {}) => _generateMapChunk(x, y, opts); -return { - generate, -}; + const generate = (x, y, opts = {}) => _generateMapChunk(x, y, opts); + return { + generate, + }; }; diff --git a/plugins/generator/worker.js b/plugins/generator/worker.js index 87bd8cd78..ce6e00c28 100644 --- a/plugins/generator/worker.js +++ b/plugins/generator/worker.js @@ -1,38 +1,74 @@ importScripts('/archae/assets/three.js'); -const {exports: THREE} = self.module; +const { exports: THREE } = self.module; importScripts('/archae/assets/murmurhash.js'); -const {exports: murmur} = self.module; +const { exports: murmur } = self.module; importScripts('/archae/assets/autows.js'); -const {exports: Autows} = self.module; +const { exports: Autows } = self.module; importScripts('/archae/assets/alea.js'); -const {exports: alea} = self.module; +const { exports: alea } = self.module; self.module = {}; let slab = null; Module = { - print(text) { console.log(text); }, - printErr(text) { console.warn(text); }, + print(text) { + console.log(text); + }, + printErr(text) { + console.warn(text); + }, wasmBinaryFile: '/archae/objects/objectize.wasm', onRuntimeInitialized: () => { slab = (() => { - const BIOMES_SIZE = _align(NUM_CELLS_OVERSCAN * NUM_CELLS_OVERSCAN * Uint8Array.BYTES_PER_ELEMENT, Float32Array.BYTES_PER_ELEMENT); - const ELEVATIONS_SIZE = NUM_CELLS_OVERSCAN * NUM_CELLS_OVERSCAN * Float32Array.BYTES_PER_ELEMENT; - const ETHER_SIZE = ((NUM_CELLS + 1) * (NUM_CELLS_HEIGHT + 1) * (NUM_CELLS + 1)) * Float32Array.BYTES_PER_ELEMENT; - const WATER_SIZE = ((NUM_CELLS + 1) * (NUM_CELLS_HEIGHT + 1) * (NUM_CELLS + 1)) * Float32Array.BYTES_PER_ELEMENT; - const LAVA_SIZE = ((NUM_CELLS + 1) * (NUM_CELLS_HEIGHT + 1) * (NUM_CELLS + 1)) * Float32Array.BYTES_PER_ELEMENT; - const POSITIONS_SIZE = NUM_POSITIONS_CHUNK * Float32Array.BYTES_PER_ELEMENT; + const BIOMES_SIZE = _align( + NUM_CELLS_OVERSCAN * NUM_CELLS_OVERSCAN * Uint8Array.BYTES_PER_ELEMENT, + Float32Array.BYTES_PER_ELEMENT + ); + const ELEVATIONS_SIZE = + NUM_CELLS_OVERSCAN * + NUM_CELLS_OVERSCAN * + Float32Array.BYTES_PER_ELEMENT; + const ETHER_SIZE = + (NUM_CELLS + 1) * + (NUM_CELLS_HEIGHT + 1) * + (NUM_CELLS + 1) * + Float32Array.BYTES_PER_ELEMENT; + const WATER_SIZE = + (NUM_CELLS + 1) * + (NUM_CELLS_HEIGHT + 1) * + (NUM_CELLS + 1) * + Float32Array.BYTES_PER_ELEMENT; + const LAVA_SIZE = + (NUM_CELLS + 1) * + (NUM_CELLS_HEIGHT + 1) * + (NUM_CELLS + 1) * + Float32Array.BYTES_PER_ELEMENT; + const POSITIONS_SIZE = + NUM_POSITIONS_CHUNK * Float32Array.BYTES_PER_ELEMENT; const INDICES_SIZE = NUM_POSITIONS_CHUNK * Uint32Array.BYTES_PER_ELEMENT; const COLORS_SIZE = NUM_POSITIONS_CHUNK * Float32Array.BYTES_PER_ELEMENT; - const HEIGHTFIELD_SIZE = NUM_CELLS_OVERSCAN * NUM_CELLS_OVERSCAN * HEIGHTFIELD_DEPTH * Float32Array.BYTES_PER_ELEMENT; - const STATIC_HEIGHTFIELD_SIZE = NUM_CELLS_OVERSCAN * NUM_CELLS_OVERSCAN * Float32Array.BYTES_PER_ELEMENT; - const ATTRIBUTE_RANGES_SIZE = NUM_CHUNKS_HEIGHT * 6 * Uint32Array.BYTES_PER_ELEMENT; - const INDEX_RANGES_SIZE = NUM_CHUNKS_HEIGHT * 6 * Uint32Array.BYTES_PER_ELEMENT; + const HEIGHTFIELD_SIZE = + NUM_CELLS_OVERSCAN * + NUM_CELLS_OVERSCAN * + HEIGHTFIELD_DEPTH * + Float32Array.BYTES_PER_ELEMENT; + const STATIC_HEIGHTFIELD_SIZE = + NUM_CELLS_OVERSCAN * + NUM_CELLS_OVERSCAN * + Float32Array.BYTES_PER_ELEMENT; + const ATTRIBUTE_RANGES_SIZE = + NUM_CHUNKS_HEIGHT * 6 * Uint32Array.BYTES_PER_ELEMENT; + const INDEX_RANGES_SIZE = + NUM_CHUNKS_HEIGHT * 6 * Uint32Array.BYTES_PER_ELEMENT; const PEEK_SIZE = 16 * Uint8Array.BYTES_PER_ELEMENT; const PEEKS_ARRAY_SIZE = PEEK_SIZE * NUM_CHUNKS_HEIGHT; const _alloc = (constructor, size) => { const offset = Module._malloc(size); - const b = new constructor(Module.HEAP8.buffer, Module.HEAP8.byteOffset + offset, size / constructor.BYTES_PER_ELEMENT); + const b = new constructor( + Module.HEAP8.buffer, + Module.HEAP8.byteOffset + offset, + size / constructor.BYTES_PER_ELEMENT + ); b.offset = offset; return b; }; @@ -52,15 +88,40 @@ Module = { const peeks = _alloc(Uint8Array, PEEK_SIZE * NUM_CHUNKS_HEIGHT); const peeksArray = Array(NUM_CHUNKS_HEIGHT); for (let i = 0; i < NUM_CHUNKS_HEIGHT; i++) { - peeksArray[i] = new Uint8Array(peeks.buffer, peeks.byteOffset + i * PEEK_SIZE, PEEK_SIZE / Uint8Array.BYTES_PER_ELEMENT); + peeksArray[i] = new Uint8Array( + peeks.buffer, + peeks.byteOffset + i * PEEK_SIZE, + PEEK_SIZE / Uint8Array.BYTES_PER_ELEMENT + ); } - const geometriesPositions = _alloc(Float32Array, GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT); - const geometriesUvs = _alloc(Float32Array, GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT); - const geometriesSsaos = _alloc(Uint8Array, GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT); - const geometriesFrames = _alloc(Float32Array, GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT); - const geometriesObjectIndices = _alloc(Float32Array, GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT); - const geometriesIndices = _alloc(Uint32Array, GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT); - const geometriesObjects = _alloc(Uint32Array, GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT); + const geometriesPositions = _alloc( + Float32Array, + GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT + ); + const geometriesUvs = _alloc( + Float32Array, + GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT + ); + const geometriesSsaos = _alloc( + Uint8Array, + GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT + ); + const geometriesFrames = _alloc( + Float32Array, + GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT + ); + const geometriesObjectIndices = _alloc( + Float32Array, + GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT + ); + const geometriesIndices = _alloc( + Uint32Array, + GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT + ); + const geometriesObjects = _alloc( + Uint32Array, + GEOMETRY_BUFFER_SIZE * NUM_CHUNKS_HEIGHT + ); const tesselateObjectsResult = _alloc(Uint32Array, 7 * 8 * 4); return { @@ -123,15 +184,12 @@ const { const protocolUtils = require('./lib/utils/protocol-utils'); const NUM_CELLS_HALF = NUM_CELLS / 2; -const NUM_CELLS_CUBE = Math.sqrt((NUM_CELLS_HALF + 16) * (NUM_CELLS_HALF + 16) * 3); // larger than the actual bounding box to account for geometry overflow +const NUM_CELLS_CUBE = Math.sqrt( + (NUM_CELLS_HALF + 16) * (NUM_CELLS_HALF + 16) * 3 +); // larger than the actual bounding box to account for geometry overflow const NUM_VOXELS_CHUNK_HEIGHT = BLOCK_BUFFER_SIZE / 4 / NUM_CHUNKS_HEIGHT; -const DIRECTIONS = [ - [-1, -1], - [-1, 1], - [1, -1], - [1, 1], -]; +const DIRECTIONS = [[-1, -1], [-1, 1], [1, -1], [1, 1]]; const CROSS_DIRECTIONS = [ [-1, -1], [0, -1], @@ -182,10 +240,16 @@ let geometryVersion = ''; const objectApis = {}; const boundingSpheres = (() => { - const slab = new ArrayBuffer(NUM_CHUNKS_HEIGHT * 4 * Float32Array.BYTES_PER_ELEMENT); + const slab = new ArrayBuffer( + NUM_CHUNKS_HEIGHT * 4 * Float32Array.BYTES_PER_ELEMENT + ); const result = Array(NUM_CHUNKS_HEIGHT); for (let i = 0; i < NUM_CHUNKS_HEIGHT; i++) { - result[i] = new Float32Array(slab, i * 4 * Float32Array.BYTES_PER_ELEMENT, 4); + result[i] = new Float32Array( + slab, + i * 4 * Float32Array.BYTES_PER_ELEMENT, + 4 + ); } return result; })(); @@ -199,19 +263,31 @@ class Allocator { allocBuffer(b) { const offset = Module._malloc(b.byteLength); this.offsets.push(offset); - new b.constructor(Module.HEAP8.buffer, Module.HEAP8.byteOffset + offset, b.length).set(b); + new b.constructor( + Module.HEAP8.buffer, + Module.HEAP8.byteOffset + offset, + b.length + ).set(b); return offset; } allocBufferArray(bs) { const offset = Module._malloc(bs.length * Uint32Array.BYTES_PER_ELEMENT); this.offsets.push(offset); - const array = new Uint32Array(Module.HEAP8.buffer, Module.HEAP8.byteOffset + offset, bs.length); + const array = new Uint32Array( + Module.HEAP8.buffer, + Module.HEAP8.byteOffset + offset, + bs.length + ); for (let i = 0; i < bs.length; i++) { const b = bs[i]; const offset = Module._malloc(b.byteLength); this.offsets.push(offset); - const shadowBuffer = new b.constructor(Module.HEAP8.buffer, Module.HEAP8.byteOffset + offset, b.length); + const shadowBuffer = new b.constructor( + Module.HEAP8.buffer, + Module.HEAP8.byteOffset + offset, + b.length + ); shadowBuffer.set(b); array[i] = offset; } @@ -221,29 +297,35 @@ class Allocator { allocShadowBuffer(b) { const offset = Module._malloc(b.byteLength); this.offsets.push(offset); - const shadowBuffer = new b.constructor(Module.HEAP8.buffer, Module.HEAP8.byteOffset + offset, b.length); + const shadowBuffer = new b.constructor( + Module.HEAP8.buffer, + Module.HEAP8.byteOffset + offset, + b.length + ); shadowBuffer.set(b); - this.backbuffers.push([ - b, - shadowBuffer - ]); + this.backbuffers.push([b, shadowBuffer]); return offset; } allocShadowBufferArray(bs) { const offset = Module._malloc(bs.length * Uint32Array.BYTES_PER_ELEMENT); this.offsets.push(offset); - const array = new Uint32Array(Module.HEAP8.buffer, Module.HEAP8.byteOffset + offset, bs.length); + const array = new Uint32Array( + Module.HEAP8.buffer, + Module.HEAP8.byteOffset + offset, + bs.length + ); for (let i = 0; i < bs.length; i++) { const b = bs[i]; const offset = Module._malloc(b.byteLength); this.offsets.push(offset); - const shadowBuffer = new b.constructor(Module.HEAP8.buffer, Module.HEAP8.byteOffset + offset, b.length); + const shadowBuffer = new b.constructor( + Module.HEAP8.buffer, + Module.HEAP8.byteOffset + offset, + b.length + ); shadowBuffer.set(b); - this.backbuffers.push([ - b, - shadowBuffer - ]); + this.backbuffers.push([b, shadowBuffer]); array[i] = offset; } return offset; @@ -263,10 +345,12 @@ class Allocator { } } - const _retesselateTerrain = (chunk, newEther) => { const oldTerrainBuffer = chunk.getTerrainBuffer(); - const oldChunkData = protocolUtils.parseTerrainData(oldTerrainBuffer.buffer, oldTerrainBuffer.byteOffset); + const oldChunkData = protocolUtils.parseTerrainData( + oldTerrainBuffer.buffer, + oldTerrainBuffer.byteOffset + ); const oldBiomes = oldChunkData.biomes.slice(); const oldElevations = oldChunkData.elevations.slice(); const oldEther = oldChunkData.ether.slice(); @@ -275,7 +359,13 @@ const _retesselateTerrain = (chunk, newEther) => { const allocator = new Allocator(); - const {attributeRanges, indexRanges, heightfield, staticHeightfield, peeks} = slab; + const { + attributeRanges, + indexRanges, + heightfield, + staticHeightfield, + peeks, + } = slab; const noiser = Module._make_noiser(murmur(DEFAULT_SEED)); Module._noiser_fill( noiser, @@ -304,11 +394,18 @@ const _retesselateTerrain = (chunk, newEther) => { allocator.unshadow(); - const attributeIndex = attributeRanges[attributeRanges.length - 2] + attributeRanges[attributeRanges.length - 1]; - const indexIndex = indexRanges[indexRanges.length - 2] + indexRanges[indexRanges.length - 1]; + const attributeIndex = + attributeRanges[attributeRanges.length - 2] + + attributeRanges[attributeRanges.length - 1]; + const indexIndex = + indexRanges[indexRanges.length - 2] + indexRanges[indexRanges.length - 1]; const positions = slab.positions.subarray(0, attributeIndex); const indices = slab.indices.subarray(0, indexIndex); - const colors = new Float32Array(slab.colors.buffer, slab.colors.byteOffset, attributeIndex); + const colors = new Float32Array( + slab.colors.buffer, + slab.colors.byteOffset, + attributeIndex + ); const geometries = Array(NUM_CHUNKS_HEIGHT); for (let i = 0; i < NUM_CHUNKS_HEIGHT; i++) { @@ -354,9 +451,16 @@ const _retesselateTerrain = (chunk, newEther) => { }; const terrainBuffer = chunk.getTerrainBuffer(); - protocolUtils.stringifyTerrainData(chunkDataSpec, terrainBuffer.buffer, terrainBuffer.byteOffset); + protocolUtils.stringifyTerrainData( + chunkDataSpec, + terrainBuffer.buffer, + terrainBuffer.byteOffset + ); - const chunkData = protocolUtils.parseTerrainData(terrainBuffer.buffer, terrainBuffer.byteOffset); + const chunkData = protocolUtils.parseTerrainData( + terrainBuffer.buffer, + terrainBuffer.byteOffset + ); chunk.chunkData.terrain = chunkData; chunk.chunkData.decorations.terrain = { skyLightmaps: new Uint8Array(chunkData.positions.length / 3), @@ -370,7 +474,16 @@ const _retesselateTerrain = (chunk, newEther) => { const _retesselateObjects = chunk => { const allocator = new Allocator(); - const {geometriesPositions, geometriesUvs, geometriesSsaos, geometriesFrames, geometriesObjectIndices, geometriesIndices, geometriesObjects, tesselateObjectsResult} = slab; + const { + geometriesPositions, + geometriesUvs, + geometriesSsaos, + geometriesFrames, + geometriesObjectIndices, + geometriesIndices, + geometriesObjects, + tesselateObjectsResult, + } = slab; Module._objectize( allocator.allocBuffer(chunk.getObjectBuffer()), @@ -382,7 +495,9 @@ const _retesselateObjects = chunk => { allocator.allocBuffer(transparentVoxels), allocator.allocBuffer(translucentVoxels), allocator.allocBuffer(faceUvs), - allocator.allocBuffer(Float32Array.from([chunk.x * NUM_CELLS, 0, chunk.z * NUM_CELLS])), + allocator.allocBuffer( + Float32Array.from([chunk.x * NUM_CELLS, 0, chunk.z * NUM_CELLS]) + ), geometriesPositions.offset, geometriesUvs.offset, geometriesSsaos.offset, @@ -395,13 +510,34 @@ const _retesselateObjects = chunk => { allocator.unshadow(); - const numNewPositions = tesselateObjectsResult.subarray(NUM_CHUNKS_HEIGHT * 0, NUM_CHUNKS_HEIGHT * 1); - const numNewUvs = tesselateObjectsResult.subarray(NUM_CHUNKS_HEIGHT * 1, NUM_CHUNKS_HEIGHT * 2); - const numNewSsaos = tesselateObjectsResult.subarray(NUM_CHUNKS_HEIGHT * 2, NUM_CHUNKS_HEIGHT * 3); - const numNewFrames = tesselateObjectsResult.subarray(NUM_CHUNKS_HEIGHT * 3, NUM_CHUNKS_HEIGHT * 4); - const numNewObjectIndices = tesselateObjectsResult.subarray(NUM_CHUNKS_HEIGHT * 4, NUM_CHUNKS_HEIGHT * 5); - const numNewIndices = tesselateObjectsResult.subarray(NUM_CHUNKS_HEIGHT * 5, NUM_CHUNKS_HEIGHT * 6); - const numNewObjects = tesselateObjectsResult.subarray(NUM_CHUNKS_HEIGHT * 6, NUM_CHUNKS_HEIGHT * 7); + const numNewPositions = tesselateObjectsResult.subarray( + NUM_CHUNKS_HEIGHT * 0, + NUM_CHUNKS_HEIGHT * 1 + ); + const numNewUvs = tesselateObjectsResult.subarray( + NUM_CHUNKS_HEIGHT * 1, + NUM_CHUNKS_HEIGHT * 2 + ); + const numNewSsaos = tesselateObjectsResult.subarray( + NUM_CHUNKS_HEIGHT * 2, + NUM_CHUNKS_HEIGHT * 3 + ); + const numNewFrames = tesselateObjectsResult.subarray( + NUM_CHUNKS_HEIGHT * 3, + NUM_CHUNKS_HEIGHT * 4 + ); + const numNewObjectIndices = tesselateObjectsResult.subarray( + NUM_CHUNKS_HEIGHT * 4, + NUM_CHUNKS_HEIGHT * 5 + ); + const numNewIndices = tesselateObjectsResult.subarray( + NUM_CHUNKS_HEIGHT * 5, + NUM_CHUNKS_HEIGHT * 6 + ); + const numNewObjects = tesselateObjectsResult.subarray( + NUM_CHUNKS_HEIGHT * 6, + NUM_CHUNKS_HEIGHT * 7 + ); const localGeometries = Array(NUM_CHUNKS_HEIGHT); for (let i = 0; i < NUM_CHUNKS_HEIGHT; i++) { @@ -427,22 +563,57 @@ const _retesselateObjects = chunk => { }, boundingSphere, }; - }; + } const chunkDataSpec = { - positions: new Float32Array(geometriesPositions.buffer, geometriesPositions.byteOffset, numNewPositions[NUM_CHUNKS_HEIGHT - 1]), - uvs: new Float32Array(geometriesUvs.buffer, geometriesUvs.byteOffset, numNewUvs[NUM_CHUNKS_HEIGHT - 1]), - ssaos: new Uint8Array(geometriesSsaos.buffer, geometriesSsaos.byteOffset, numNewSsaos[NUM_CHUNKS_HEIGHT - 1]), - frames: new Float32Array(geometriesFrames.buffer, geometriesFrames.byteOffset, numNewFrames[NUM_CHUNKS_HEIGHT - 1]), - objectIndices: new Float32Array(geometriesObjectIndices.buffer, geometriesObjectIndices.byteOffset, numNewObjectIndices[NUM_CHUNKS_HEIGHT - 1]), - indices: new Uint32Array(geometriesIndices.buffer, geometriesIndices.byteOffset, numNewIndices[NUM_CHUNKS_HEIGHT - 1]), - objects: new Uint32Array(geometriesObjects.buffer, geometriesObjects.byteOffset, numNewObjects[NUM_CHUNKS_HEIGHT - 1]), + positions: new Float32Array( + geometriesPositions.buffer, + geometriesPositions.byteOffset, + numNewPositions[NUM_CHUNKS_HEIGHT - 1] + ), + uvs: new Float32Array( + geometriesUvs.buffer, + geometriesUvs.byteOffset, + numNewUvs[NUM_CHUNKS_HEIGHT - 1] + ), + ssaos: new Uint8Array( + geometriesSsaos.buffer, + geometriesSsaos.byteOffset, + numNewSsaos[NUM_CHUNKS_HEIGHT - 1] + ), + frames: new Float32Array( + geometriesFrames.buffer, + geometriesFrames.byteOffset, + numNewFrames[NUM_CHUNKS_HEIGHT - 1] + ), + objectIndices: new Float32Array( + geometriesObjectIndices.buffer, + geometriesObjectIndices.byteOffset, + numNewObjectIndices[NUM_CHUNKS_HEIGHT - 1] + ), + indices: new Uint32Array( + geometriesIndices.buffer, + geometriesIndices.byteOffset, + numNewIndices[NUM_CHUNKS_HEIGHT - 1] + ), + objects: new Uint32Array( + geometriesObjects.buffer, + geometriesObjects.byteOffset, + numNewObjects[NUM_CHUNKS_HEIGHT - 1] + ), geometries: localGeometries, }; const geometryBuffer = chunk.getGeometryBuffer(); - protocolUtils.stringifyGeometry(chunkDataSpec, geometryBuffer.buffer, geometryBuffer.byteOffset); + protocolUtils.stringifyGeometry( + chunkDataSpec, + geometryBuffer.buffer, + geometryBuffer.byteOffset + ); - const chunkData = protocolUtils.parseGeometry(geometryBuffer.buffer, geometryBuffer.byteOffset); + const chunkData = protocolUtils.parseGeometry( + geometryBuffer.buffer, + geometryBuffer.byteOffset + ); chunk.chunkData.objects = chunkData; chunk.chunkData.decorations.objects = { skyLightmaps: new Uint8Array(chunkData.positions.length / 3), @@ -454,18 +625,28 @@ const _retesselateObjects = chunk => { allocator.destroy(); }; const _relight = (chunk, x, y, z) => { - const _decorateChunkLightsSub = (chunk, x, y, z) => _decorateChunkLightsRange( + const _decorateChunkLightsSub = (chunk, x, y, z) => + _decorateChunkLightsRange( + chunk, + Math.max(x - 15, (chunk.x - 1) * NUM_CELLS), + Math.min(x + 15, (chunk.x + 2) * NUM_CELLS), + Math.max(y - 15, 0), + Math.min(y + 15, NUM_CELLS_HEIGHT), + Math.max(z - 15, (chunk.z - 1) * NUM_CELLS), + Math.min(z + 15, (chunk.z + 2) * NUM_CELLS), + true + ); + const _decorateChunkLightsRange = ( chunk, - Math.max(x - 15, (chunk.x - 1) * NUM_CELLS), - Math.min(x + 15, (chunk.x + 2) * NUM_CELLS), - Math.max(y - 15, 0), - Math.min(y + 15, NUM_CELLS_HEIGHT), - Math.max(z - 15, (chunk.z - 1) * NUM_CELLS), - Math.min(z + 15, (chunk.z + 2) * NUM_CELLS), - true - ); - const _decorateChunkLightsRange = (chunk, minX, maxX, minY, maxY, minZ, maxZ, relight) => { - const {x: ox, z: oz} = chunk; + minX, + maxX, + minY, + maxY, + minZ, + maxZ, + relight + ) => { + const { x: ox, z: oz } = chunk; const updatingLights = chunk[lightsRenderedSymbol]; const lavaArray = Array(9); @@ -473,7 +654,8 @@ const _relight = (chunk, x, y, z) => { const etherArray = Array(9); const blocksArray = Array(9); const lightsArray = Array(9); - for (let doz = -1; doz <= 1; doz++) { // XXX can be reduced to use only the relight range + for (let doz = -1; doz <= 1; doz++) { + // XXX can be reduced to use only the relight range for (let dox = -1; dox <= 1; dox++) { const arrayIndex = _getLightsArrayIndex(dox + 1, doz + 1); @@ -481,7 +663,10 @@ const _relight = (chunk, x, y, z) => { const aoz = oz + doz; const chunk = zde.getChunk(aox, aoz); const uint32Buffer = chunk.getTerrainBuffer(); - const {ether, lava} = protocolUtils.parseTerrainData(uint32Buffer.buffer, uint32Buffer.byteOffset); // XXX can be reduced to only parse the needed fields + const { ether, lava } = protocolUtils.parseTerrainData( + uint32Buffer.buffer, + uint32Buffer.byteOffset + ); // XXX can be reduced to only parse the needed fields lavaArray[arrayIndex] = lava; const objectLights = chunk.getLightBuffer(); @@ -494,7 +679,9 @@ const _relight = (chunk, x, y, z) => { let lights = chunk[lightsSymbol]; if (!lights) { - lights = new Uint8Array(NUM_CELLS_OVERSCAN * (NUM_CELLS_HEIGHT + 1) * NUM_CELLS_OVERSCAN); + lights = new Uint8Array( + NUM_CELLS_OVERSCAN * (NUM_CELLS_HEIGHT + 1) * NUM_CELLS_OVERSCAN + ); chunk[lightsSymbol] = lights; } lightsArray[arrayIndex] = lights; @@ -504,14 +691,20 @@ const _relight = (chunk, x, y, z) => { const allocator = new Allocator(); Module._lght( - ox, oz, - minX, maxX, minY, maxY, minZ, maxZ, + ox, + oz, + minX, + maxX, + minY, + maxY, + minZ, + maxZ, +relight, allocator.allocBufferArray(lavaArray), allocator.allocBufferArray(objectLightsArray), allocator.allocBufferArray(etherArray), allocator.allocBufferArray(blocksArray), - allocator.allocShadowBufferArray(lightsArray), + allocator.allocShadowBufferArray(lightsArray) ); allocator.unshadow(); @@ -524,19 +717,31 @@ const _relight = (chunk, x, y, z) => { const _relightmap = chunk => { const _relightmapTerrain = () => { const terrainBuffer = chunk.getTerrainBuffer(); - const {positions} = protocolUtils.parseTerrainData(terrainBuffer.buffer, terrainBuffer.byteOffset); - const {skyLightmaps, torchLightmaps} = chunk.chunkData.decorations.terrain; - _relightmapSpec({positions, skyLightmaps, torchLightmaps}); + const { positions } = protocolUtils.parseTerrainData( + terrainBuffer.buffer, + terrainBuffer.byteOffset + ); + const { + skyLightmaps, + torchLightmaps, + } = chunk.chunkData.decorations.terrain; + _relightmapSpec({ positions, skyLightmaps, torchLightmaps }); }; const _relightmapObjects = () => { - const {positions} = chunk.chunkData.objects; - const {skyLightmaps, torchLightmaps} = chunk.chunkData.decorations.objects; - _relightmapSpec({positions, skyLightmaps, torchLightmaps}); + const { positions } = chunk.chunkData.objects; + const { + skyLightmaps, + torchLightmaps, + } = chunk.chunkData.decorations.objects; + _relightmapSpec({ positions, skyLightmaps, torchLightmaps }); }; - const _relightmapSpec = ({positions, skyLightmaps, torchLightmaps}) => { + const _relightmapSpec = ({ positions, skyLightmaps, torchLightmaps }) => { const terrainBuffer = chunk.getTerrainBuffer(); - const {staticHeightfield} = protocolUtils.parseTerrainData(terrainBuffer.buffer, terrainBuffer.byteOffset); - const {[lightsSymbol]: lights} = chunk; + const { staticHeightfield } = protocolUtils.parseTerrainData( + terrainBuffer.buffer, + terrainBuffer.byteOffset + ); + const { [lightsSymbol]: lights } = chunk; const numPositions = positions.length; @@ -587,35 +792,42 @@ const _getHoveredTrackedObject = (x, y, z, buffer, byteOffset) => { for (const index in zde.chunks) { const chunk = zde.chunks[index]; - if (chunk && chunk[objectsDecorationsSymbol] && localCoord.set(chunk.x - ox, chunk.z - oz).lengthSq() <= 2) { - const chunkResult = chunk.forEachObject((n, matrix, value, objectIndex) => { - const position = localVector2.fromArray(matrix, 0); - const rotation = localQuaternion.fromArray(matrix, 3); - const rotationInverse = localQuaternion2.copy(rotation).inverse(); - const objectArray = chunk.objectsMap[objectIndex]; - localBox.min.fromArray(objectArray, 0); - localBox.max.fromArray(objectArray, 3); - - localVector3.copy(controllerPosition) - .sub(position) - .applyQuaternion(rotationInverse); + if ( + chunk && + chunk[objectsDecorationsSymbol] && + localCoord.set(chunk.x - ox, chunk.z - oz).lengthSq() <= 2 + ) { + const chunkResult = chunk.forEachObject( + (n, matrix, value, objectIndex) => { + const position = localVector2.fromArray(matrix, 0); + const rotation = localQuaternion.fromArray(matrix, 3); + const rotationInverse = localQuaternion2.copy(rotation).inverse(); + const objectArray = chunk.objectsMap[objectIndex]; + localBox.min.fromArray(objectArray, 0); + localBox.max.fromArray(objectArray, 3); + + localVector3 + .copy(controllerPosition) + .sub(position) + .applyQuaternion(rotationInverse); // .add(position); - if (localBox.containsPoint(localVector3)) { - uint32Array[0] = n; - int32Array[1] = chunk.x; - int32Array[2] = chunk.z; - uint32Array[3] = objectIndex; - // uint32Array[3] = objectIndex + chunk.offsets.index * chunk.offsets.numObjectIndices; - float32Array[4] = position.x; - float32Array[5] = position.y; - float32Array[6] = position.z; - - return false; - } else { - return true; + if (localBox.containsPoint(localVector3)) { + uint32Array[0] = n; + int32Array[1] = chunk.x; + int32Array[2] = chunk.z; + uint32Array[3] = objectIndex; + // uint32Array[3] = objectIndex + chunk.offsets.index * chunk.offsets.numObjectIndices; + float32Array[4] = position.x; + float32Array[5] = position.y; + float32Array[6] = position.z; + + return false; + } else { + return true; + } } - }); + ); if (chunkResult === false) { break; @@ -657,41 +869,55 @@ const _getTeleportObject = (x, y, z, buffer) => { for (const index in zde.chunks) { const chunk = zde.chunks[index]; - if (chunk && chunk[objectsDecorationsSymbol] && localCoord.set(chunk.x - ox, chunk.z - oz).lengthSq() <= 2) { - const chunkResult = chunk.forEachObject((n, matrix, value, objectIndex) => { - const position = localVector.fromArray(matrix, 0); - const rotation = localQuaternion.fromArray(matrix, 3); - const rotationInverse = localQuaternion2.copy(rotation).inverse(); - const objectArray = chunk.objectsMap[objectIndex]; - localBox.min.fromArray(objectArray, 0); - localBox.max.fromArray(objectArray, 3); - - localRay2.origin.copy(localRay.origin) - .sub(position) - .applyQuaternion(rotationInverse); + if ( + chunk && + chunk[objectsDecorationsSymbol] && + localCoord.set(chunk.x - ox, chunk.z - oz).lengthSq() <= 2 + ) { + const chunkResult = chunk.forEachObject( + (n, matrix, value, objectIndex) => { + const position = localVector.fromArray(matrix, 0); + const rotation = localQuaternion.fromArray(matrix, 3); + const rotationInverse = localQuaternion2.copy(rotation).inverse(); + const objectArray = chunk.objectsMap[objectIndex]; + localBox.min.fromArray(objectArray, 0); + localBox.max.fromArray(objectArray, 3); + + localRay2.origin + .copy(localRay.origin) + .sub(position) + .applyQuaternion(rotationInverse); // .add(position); - localRay2.direction.copy(localRay.direction); - - const intersectionPoint = localRay2.intersectBox(localBox, localVector2); - if (intersectionPoint && intersectionPoint.y > topY) { - topY = intersectionPoint.y; - topPosition = position; - topRotation = rotation; - topRotationInverse = rotationInverse; - topBox = localBox2.copy(localBox); - - return false; - } else { - return true; + localRay2.direction.copy(localRay.direction); + + const intersectionPoint = localRay2.intersectBox( + localBox, + localVector2 + ); + if (intersectionPoint && intersectionPoint.y > topY) { + topY = intersectionPoint.y; + topPosition = position; + topRotation = rotation; + topRotationInverse = rotationInverse; + topBox = localBox2.copy(localBox); + + return false; + } else { + return true; + } } - }); + ); if (chunkResult === false) { let byteOffset = 0; new Uint32Array(buffer, byteOffset, 1)[0] = 1; byteOffset += 4; - const float32Array = new Float32Array(buffer, byteOffset, 3 + 3 + 3 + 4 + 4); + const float32Array = new Float32Array( + buffer, + byteOffset, + 3 + 3 + 3 + 4 + 4 + ); topBox.min.toArray(float32Array, 0); topBox.max.toArray(float32Array, 3); topPosition.toArray(float32Array, 6); @@ -718,32 +944,39 @@ const _getBodyObject = (x, y, z, buffer) => { for (const index in zde.chunks) { const chunk = zde.chunks[index]; - if (chunk && chunk[objectsDecorationsSymbol] && localCoord.set(chunk.x - ox, chunk.z - oz).lengthSq() <= 2) { - const chunkResult = chunk.forEachObject((n, matrix, value, objectIndex) => { - const position = localVector2.fromArray(matrix, 0); - const rotation = localQuaternion.fromArray(matrix, 3); - const rotationInverse = localQuaternion2.copy(rotation).inverse(); - const objectArray = chunk.objectsMap[objectIndex]; - localBox.min.fromArray(objectArray, 0); - localBox.max.fromArray(objectArray, 3); - - localVector3.copy(bodyCenterPoint) - .sub(position) - .applyQuaternion(rotationInverse); + if ( + chunk && + chunk[objectsDecorationsSymbol] && + localCoord.set(chunk.x - ox, chunk.z - oz).lengthSq() <= 2 + ) { + const chunkResult = chunk.forEachObject( + (n, matrix, value, objectIndex) => { + const position = localVector2.fromArray(matrix, 0); + const rotation = localQuaternion.fromArray(matrix, 3); + const rotationInverse = localQuaternion2.copy(rotation).inverse(); + const objectArray = chunk.objectsMap[objectIndex]; + localBox.min.fromArray(objectArray, 0); + localBox.max.fromArray(objectArray, 3); + + localVector3 + .copy(bodyCenterPoint) + .sub(position) + .applyQuaternion(rotationInverse); // .add(position); - const distance = localBox.distanceToPoint(localVector3); - if (distance < 0.3 && (distance < topDistance)) { - topDistance = distance; - topN = n; - topChunkX = chunk.x; - topChunkZ = chunk.z; - topObjectIndex = objectIndex; - return false; - } else { - return true; + const distance = localBox.distanceToPoint(localVector3); + if (distance < 0.3 && distance < topDistance) { + topDistance = distance; + topN = n; + topChunkX = chunk.x; + topChunkZ = chunk.z; + topObjectIndex = objectIndex; + return false; + } else { + return true; + } } - }); + ); if (chunkResult === false) { const uint32Array = new Uint32Array(buffer, 0, 4); @@ -765,12 +998,12 @@ const queue = []; let pendingMessage = null; const connection = new AutoWs(_wsUrl('/archae/generatorWs')); connection.on('message', e => { - const {data} = e; + const { data } = e; const m = JSON.parse(data); - const {type} = m; + const { type } = m; if (type === 'addObject') { - const {args: {n, positions, rotations, value, result: objectIndex}} = m; + const { args: { n, positions, rotations, value, result: objectIndex } } = m; const ox = Math.floor(positions[0] / NUM_CELLS); const oz = Math.floor(positions[2] / NUM_CELLS); @@ -788,13 +1021,21 @@ connection.on('message', e => { const light = _findLight(n); if (light) { - oldChunk.addLightAt(objectIndex, positions[0], positions[1], positions[2], light); + oldChunk.addLightAt( + objectIndex, + positions[0], + positions[1], + positions[2], + light + ); for (let i = 0; i < CROSS_DIRECTIONS.length; i++) { const [dx, dz] = CROSS_DIRECTIONS[i]; const ox = Math.floor((x + dx * light) / NUM_CELLS); const oz = Math.floor((z + dz * light) / NUM_CELLS); - if (!updateSpecs.some(update => update[0] === ox && update[1] === oz)) { + if ( + !updateSpecs.some(update => update[0] === ox && update[1] === oz) + ) { updateSpecs.push([ox, oz]); } } @@ -820,12 +1061,20 @@ connection.on('message', e => { if (objectApi && objectApi.added) { postMessage({ type: 'objectAdded', - args: [n, ox, oz, objectIndex, matrix.slice(0, 3), matrix.slice(3, 7), value], + args: [ + n, + ox, + oz, + objectIndex, + matrix.slice(0, 3), + matrix.slice(3, 7), + value, + ], }); } } } else if (type === 'removeObject') { - const {args: {x: ox, z: oz, index: objectIndex}} = m; + const { args: { x: ox, z: oz, index: objectIndex } } = m; const oldChunk = zde.getChunk(ox, oz); if (oldChunk) { @@ -845,7 +1094,9 @@ connection.on('message', e => { const [dx, dz] = CROSS_DIRECTIONS[i]; const ox = Math.floor((x + dx * light) / NUM_CELLS); const oz = Math.floor((z + dz * light) / NUM_CELLS); - if (!updateSpecs.some(update => update[0] === ox && update[1] === oz)) { + if ( + !updateSpecs.some(update => update[0] === ox && update[1] === oz) + ) { updateSpecs.push([ox, oz]); } } @@ -878,7 +1129,7 @@ connection.on('message', e => { } } } else if (type === 'setObjectData') { - const {args: {x, z, index: objectIndex, value}} = m; + const { args: { x, z, index: objectIndex, value } } = m; const chunk = zde.getChunk(x, z); if (chunk) { @@ -891,12 +1142,20 @@ connection.on('message', e => { postMessage({ type: 'objectUpdated', - args: [n, x, z, objectIndex, matrix.slice(0, 3), matrix.slice(3, 7), value], + args: [ + n, + x, + z, + objectIndex, + matrix.slice(0, 3), + matrix.slice(3, 7), + value, + ], }); } } } else if (type === 'setBlock') { - const {args: {x, y, z, v}} = m; + const { args: { x, y, z, v } } = m; const ox = Math.floor(x / NUM_CELLS); const oz = Math.floor(z / NUM_CELLS); @@ -922,7 +1181,7 @@ connection.on('message', e => { } } } else if (type === 'clearBlock') { - const {args: {x, y, z}} = m; + const { args: { x, y, z } } = m; const ox = Math.floor(x / NUM_CELLS); const oz = Math.floor(z / NUM_CELLS); @@ -948,7 +1207,7 @@ connection.on('message', e => { } } } else if (type === 'mutateVoxel') { - const {args: {x, y, z, v}} = m; + const { args: { x, y, z, v } } = m; const seenChunks = []; for (let i = 0; i < DIRECTIONS.length; i++) { @@ -961,8 +1220,8 @@ connection.on('message', e => { if (!seenChunks.some(([x, z]) => x === ox && z === oz)) { const oldChunk = zde.getChunk(ox, oz); - const lx = x - (ox * NUM_CELLS); - const lz = z - (oz * NUM_CELLS); + const lx = x - ox * NUM_CELLS; + const lz = z - oz * NUM_CELLS; const newEther = Float32Array.from([lx, y, lz, v]); _retesselateTerrain(oldChunk, newEther); @@ -978,7 +1237,7 @@ connection.on('message', e => { args: seenChunks, }); } else if (type === 'response') { - const {id, result} = m; + const { id, result } = m; queues[id](result); queues[id] = null; @@ -990,84 +1249,96 @@ connection.on('message', e => { }); connection.mutateVoxel = (x, y, z, v, cb) => { const id = _makeId(); - connection.send(JSON.stringify({ - method: 'mutateVoxel', - id, - args: { - x, - y, - z, - v, - }, - })); + connection.send( + JSON.stringify({ + method: 'mutateVoxel', + id, + args: { + x, + y, + z, + v, + }, + }) + ); queues[id] = cb; }; connection.addObject = (n, positions, rotations, value, cb) => { const id = _makeId(); - connection.send(JSON.stringify({ - method: 'addObject', - id, - args: { - n, - positions, - rotations, - value, - }, - })); + connection.send( + JSON.stringify({ + method: 'addObject', + id, + args: { + n, + positions, + rotations, + value, + }, + }) + ); queues[id] = cb; }; connection.removeObject = (x, z, index, cb) => { const id = _makeId(); - connection.send(JSON.stringify({ - method: 'removeObject', - id, - args: { - x, - z, - index, - }, - })); + connection.send( + JSON.stringify({ + method: 'removeObject', + id, + args: { + x, + z, + index, + }, + }) + ); queues[id] = cb; }; connection.setObjectData = (x, z, index, value, cb) => { const id = _makeId(); - connection.send(JSON.stringify({ - method: 'setObjectData', - id, - args: { - x, - z, - index, - value, - }, - })); + connection.send( + JSON.stringify({ + method: 'setObjectData', + id, + args: { + x, + z, + index, + value, + }, + }) + ); queues[id] = cb; }; connection.setBlock = (x, y, z, v, cb) => { const id = _makeId(); - connection.send(JSON.stringify({ - method: 'setBlock', - id, - args: { - x, - y, - z, - v, - }, - })); + connection.send( + JSON.stringify({ + method: 'setBlock', + id, + args: { + x, + y, + z, + v, + }, + }) + ); queues[id] = cb; }; connection.clearBlock = (x, y, z, cb) => { const id = _makeId(); - connection.send(JSON.stringify({ - method: 'clearBlock', - id, - args: { - x, - y, - z, - }, - })); + connection.send( + JSON.stringify({ + method: 'clearBlock', + id, + args: { + x, + y, + z, + }, + }) + ); queues[id] = cb; }; const _getOriginHeight = () => 64; @@ -1083,11 +1354,10 @@ const _resArrayBuffer = res => { }; const _resArrayBufferHeaders = res => { if (res.status >= 200 && res.status < 300) { - return res.arrayBuffer() - .then(buffer => ({ - buffer, - headers: res.headers, - })); + return res.arrayBuffer().then(buffer => ({ + buffer, + headers: res.headers, + })); } else { return Promise.reject({ status: res.status, @@ -1107,12 +1377,14 @@ const _resBlob = res => { }; function mod(value, divisor) { var n = value % divisor; - return n < 0 ? (divisor + n) : n; + return n < 0 ? divisor + n : n; } -const _getChunkIndex = (x, z) => (mod(x, 0xFFFF) << 16) | mod(z, 0xFFFF); +const _getChunkIndex = (x, z) => (mod(x, 0xffff) << 16) | mod(z, 0xffff); const NUM_MAP_CHUNK_MESHES = 512; -const terrainMapChunkMeshes = new Int32Array(NUM_MAP_CHUNK_MESHES * NUM_CELLS_HEIGHT * 14); +const terrainMapChunkMeshes = new Int32Array( + NUM_MAP_CHUNK_MESHES * NUM_CELLS_HEIGHT * 14 +); let terrainMapChunkMeshesIndex = 0; const _findFreeTerrainMapChunkMeshIndex = () => { let baseIndex = 0; @@ -1126,7 +1398,9 @@ const _findFreeTerrainMapChunkMeshIndex = () => { throw new Error('ran out of map chunk mesh buffer'); return -1; }; -const objectsMapChunkMeshes = new Int32Array(NUM_MAP_CHUNK_MESHES * (1 + 2 + NUM_CHUNKS_HEIGHT * 2)); +const objectsMapChunkMeshes = new Int32Array( + NUM_MAP_CHUNK_MESHES * (1 + 2 + NUM_CHUNKS_HEIGHT * 2) +); let objectsMapChunkMeshesIndex = 0; const _findFreeObjectsMapChunkMeshIndex = () => { let baseIndex = 0; @@ -1150,7 +1424,7 @@ const _requestChunk = (x, z) => { credentials: 'include', }) .then(_resArrayBufferHeaders) - .then(({buffer, headers}) => { + .then(({ buffer, headers }) => { const newTextureAtlasVersion = headers.get('Texture-Atlas-Version'); if (newTextureAtlasVersion !== textureAtlasVersion) { textureAtlasVersion = newTextureAtlasVersion; @@ -1165,31 +1439,59 @@ const _requestChunk = (x, z) => { } let index = 0; - const terrainBuffer = new Uint32Array(buffer, index, TERRAIN_BUFFER_SIZE / Uint32Array.BYTES_PER_ELEMENT); + const terrainBuffer = new Uint32Array( + buffer, + index, + TERRAIN_BUFFER_SIZE / Uint32Array.BYTES_PER_ELEMENT + ); index += TERRAIN_BUFFER_SIZE; - const objectBuffer = new Uint32Array(buffer, index, OBJECT_BUFFER_SIZE / Uint32Array.BYTES_PER_ELEMENT); + const objectBuffer = new Uint32Array( + buffer, + index, + OBJECT_BUFFER_SIZE / Uint32Array.BYTES_PER_ELEMENT + ); index += OBJECT_BUFFER_SIZE; - const blockBuffer = new Uint32Array(buffer, index, BLOCK_BUFFER_SIZE / Uint32Array.BYTES_PER_ELEMENT); + const blockBuffer = new Uint32Array( + buffer, + index, + BLOCK_BUFFER_SIZE / Uint32Array.BYTES_PER_ELEMENT + ); index += BLOCK_BUFFER_SIZE; - const lightBuffer = new Float32Array(buffer, index, LIGHT_BUFFER_SIZE / Float32Array.BYTES_PER_ELEMENT); + const lightBuffer = new Float32Array( + buffer, + index, + LIGHT_BUFFER_SIZE / Float32Array.BYTES_PER_ELEMENT + ); index += LIGHT_BUFFER_SIZE; - const geometryBuffer = new Uint8Array(buffer, index, GEOMETRY_BUFFER_SIZE / Uint8Array.BYTES_PER_ELEMENT); + const geometryBuffer = new Uint8Array( + buffer, + index, + GEOMETRY_BUFFER_SIZE / Uint8Array.BYTES_PER_ELEMENT + ); index += GEOMETRY_BUFFER_SIZE; const decorationsBuffer = new Uint8Array(buffer, index); - const chunk = new zeode.Chunk(x, z, 0, terrainBuffer, objectBuffer, blockBuffer, lightBuffer, geometryBuffer); chunk.chunkData = { - terrain: protocolUtils.parseTerrainData(terrainBuffer.buffer, terrainBuffer.byteOffset), - objects: protocolUtils.parseGeometry(geometryBuffer.buffer, geometryBuffer.byteOffset), - decorations: protocolUtils.parseDecorations(decorationsBuffer.buffer, decorationsBuffer.byteOffset), + terrain: protocolUtils.parseTerrainData( + terrainBuffer.buffer, + terrainBuffer.byteOffset + ), + objects: protocolUtils.parseGeometry( + geometryBuffer.buffer, + geometryBuffer.byteOffset + ), + decorations: protocolUtils.parseDecorations( + decorationsBuffer.buffer, + decorationsBuffer.byteOffset + ), }; zde.pushChunk(chunk); return chunk; }); } }; -const _requestTerrainChunk = (x, y, index, numPositions, numIndices) => _requestChunk(x, y) - .then(chunk => { +const _requestTerrainChunk = (x, y, index, numPositions, numIndices) => + _requestChunk(x, y).then(chunk => { _decorateTerrainChunk(chunk, index, numPositions, numIndices); return chunk; }); @@ -1198,13 +1500,26 @@ const _getTerrainChunk = (x, y, index, numPositions, numIndices) => { _decorateTerrainChunk(chunk, index, numPositions, numIndices); return chunk; }; -const _requestObjectsChunk = (x, z, index, numPositions, numObjectIndices, numIndices) => _requestChunk(x, z) - .then(chunk => { - _decorateObjectsChunk(chunk, index, numPositions, numObjectIndices, numIndices); +const _requestObjectsChunk = ( + x, + z, + index, + numPositions, + numObjectIndices, + numIndices +) => + _requestChunk(x, z).then(chunk => { + _decorateObjectsChunk( + chunk, + index, + numPositions, + numObjectIndices, + numIndices + ); return chunk; }); const _offsetChunkData = (chunkData, index, numPositions) => { - const {indices} = chunkData; + const { indices } = chunkData; const positionOffset = index * (numPositions / 3); for (let i = 0; i < indices.length; i++) { indices[i] += positionOffset; @@ -1212,11 +1527,11 @@ const _offsetChunkData = (chunkData, index, numPositions) => { }; const _decorateTerrainChunk = (chunk, index, numPositions, numIndices) => { if (!chunk[terrainDecorationsSymbol]) { - const {x, z} = chunk; + const { x, z } = chunk; const terrainMapChunkMeshIndices = Array(NUM_CHUNKS_HEIGHT); for (let i = 0; i < NUM_CHUNKS_HEIGHT; i++) { - const {indexRange, peeks} = chunk.chunkData.terrain.geometries[i]; + const { indexRange, peeks } = chunk.chunkData.terrain.geometries[i]; const indexOffset = index * numIndices; const terrainMapChunkMeshIndex = _findFreeTerrainMapChunkMeshIndex(); @@ -1225,12 +1540,18 @@ const _decorateTerrainChunk = (chunk, index, numPositions, numIndices) => { terrainMapChunkMeshes[baseIndex + 1] = x; terrainMapChunkMeshes[baseIndex + 2] = i; terrainMapChunkMeshes[baseIndex + 3] = z; - new Uint8Array(terrainMapChunkMeshes.buffer, terrainMapChunkMeshes.byteOffset + (baseIndex + 4) * 4, 16).set(peeks); + new Uint8Array( + terrainMapChunkMeshes.buffer, + terrainMapChunkMeshes.byteOffset + (baseIndex + 4) * 4, + 16 + ).set(peeks); terrainMapChunkMeshes[baseIndex + 8] = indexRange.landStart + indexOffset; terrainMapChunkMeshes[baseIndex + 9] = indexRange.landCount; - terrainMapChunkMeshes[baseIndex + 10] = indexRange.waterStart + indexOffset; + terrainMapChunkMeshes[baseIndex + 10] = + indexRange.waterStart + indexOffset; terrainMapChunkMeshes[baseIndex + 11] = indexRange.waterCount; - terrainMapChunkMeshes[baseIndex + 12] = indexRange.lavaStart + indexOffset; + terrainMapChunkMeshes[baseIndex + 12] = + indexRange.lavaStart + indexOffset; terrainMapChunkMeshes[baseIndex + 13] = indexRange.lavaCount; terrainMapChunkMeshIndices[i] = terrainMapChunkMeshIndex; @@ -1254,7 +1575,13 @@ const _undecorateTerrainChunk = chunk => { } }; let ids = 0; -const _decorateObjectsChunk = (chunk, index, numPositions, numObjectIndices, numIndices) => { +const _decorateObjectsChunk = ( + chunk, + index, + numPositions, + numObjectIndices, + numIndices +) => { if (!chunk[objectsDecorationsSymbol]) { chunk.id = ids++; @@ -1266,31 +1593,37 @@ const _decorateObjectsChunk = (chunk, index, numPositions, numObjectIndices, num }; const objectsMap = {}; - const {objects} = chunk.chunkData.objects; + const { objects } = chunk.chunkData.objects; const numObjects = objects.length / 7; for (let i = 0; i < numObjects; i++) { const baseIndex = i * 7; const index = objects[baseIndex]; - objectsMap[index] = new Float32Array(objects.buffer, objects.byteOffset + ((baseIndex + 1) * 4), 6); + objectsMap[index] = new Float32Array( + objects.buffer, + objects.byteOffset + (baseIndex + 1) * 4, + 6 + ); } chunk.objectsMap = objectsMap; - const {objectIndices} = chunk.chunkData.objects; + const { objectIndices } = chunk.chunkData.objects; const objectIndexOffset = index * numObjectIndices; for (let i = 0; i < objectIndices.length; i++) { objectIndices[i] += objectIndexOffset; } const objectsMapChunkMeshIndex = _findFreeObjectsMapChunkMeshIndex(); - const baseIndex = objectsMapChunkMeshIndex * (1 + 2 + NUM_CHUNKS_HEIGHT * 2); + const baseIndex = + objectsMapChunkMeshIndex * (1 + 2 + NUM_CHUNKS_HEIGHT * 2); objectsMapChunkMeshes[baseIndex + 0] = 1; objectsMapChunkMeshes[baseIndex + 1] = chunk.x; objectsMapChunkMeshes[baseIndex + 2] = chunk.z; - const {geometries} = chunk.chunkData.objects; + const { geometries } = chunk.chunkData.objects; const indexOffset = index * numIndices; for (let i = 0; i < NUM_CHUNKS_HEIGHT; i++) { - const {indexRange} = geometries[i]; - objectsMapChunkMeshes[baseIndex + 3 + i * 2 + 0] = indexRange.start + indexOffset; + const { indexRange } = geometries[i]; + objectsMapChunkMeshes[baseIndex + 3 + i * 2 + 0] = + indexRange.start + indexOffset; objectsMapChunkMeshes[baseIndex + 3 + i * 2 + 1] = indexRange.count; } @@ -1299,10 +1632,10 @@ const _decorateObjectsChunk = (chunk, index, numPositions, numObjectIndices, num const allocator = new Allocator(); const blocks = chunk.getBlockBuffer(); - const {blockfield} = chunk.chunkData.decorations.objects; + const { blockfield } = chunk.chunkData.decorations.objects; Module._blockfield( allocator.allocBuffer(blocks), - allocator.allocShadowBuffer(blockfield), + allocator.allocShadowBuffer(blockfield) ); allocator.unshadow(); @@ -1314,12 +1647,21 @@ const _decorateObjectsChunk = (chunk, index, numPositions, numObjectIndices, num }; } if (!chunk[objectsCallbacksSymbol]) { - chunk.forEachObject((n, matrix, value, objectIndex) => { // XXX can optimize this with some kind of index + chunk.forEachObject((n, matrix, value, objectIndex) => { + // XXX can optimize this with some kind of index const entry = objectApis[n]; if (entry && entry.added) { postMessage({ type: 'objectAdded', - args: [n, chunk.x, chunk.z, objectIndex, matrix.slice(0, 3), matrix.slice(3, 7), value], + args: [ + n, + chunk.x, + chunk.z, + objectIndex, + matrix.slice(0, 3), + matrix.slice(3, 7), + value, + ], }); } }); @@ -1339,7 +1681,15 @@ const _decorateObjectsChunk = (chunk, index, numPositions, numObjectIndices, num if (entry && entry.removed) { postMessage({ type: 'objectRemoved', - args: [n, chunk.x, chunk.z, objectIndex, matrix.slice(0, 3), matrix.slice(3, 7), value], + args: [ + n, + chunk.x, + chunk.z, + objectIndex, + matrix.slice(0, 3), + matrix.slice(3, 7), + value, + ], }); } }); @@ -1376,14 +1726,19 @@ const _updateTextureAtlas = _debounce(next => { credentials: 'include', }) .then(_resBlob) - .then(blob => createImageBitmap(blob, 0, 0, TEXTURE_SIZE, TEXTURE_SIZE, { - imageOrientation: 'flipY', - })) + .then(blob => + createImageBitmap(blob, 0, 0, TEXTURE_SIZE, TEXTURE_SIZE, { + imageOrientation: 'flipY', + }) + ) .then(imageBitmap => { - postMessage({ - type: 'textureAtlas', - args: [imageBitmap], - }, [imageBitmap]); + postMessage( + { + type: 'textureAtlas', + args: [imageBitmap], + }, + [imageBitmap] + ); next(); }) @@ -1455,16 +1810,16 @@ const _cleanupQueues = () => { }; function _wsUrl(s) { const l = self.location; - return ((l.protocol === 'https:') ? 'wss://' : 'ws://') + l.host + s; + return (l.protocol === 'https:' ? 'wss://' : 'ws://') + l.host + s; } self.onmessage = e => { - const {data} = e; - const {type} = data; + const { data } = e; + const { type } = data; switch (type) { case 'getOriginHeight': { - const {id} = data; + const { id } = data; postMessage({ type: 'response', @@ -1474,7 +1829,7 @@ self.onmessage = e => { break; } case 'registerObject': { - const {n, added, removed, updated, set, clear} = data; + const { n, added, removed, updated, set, clear } = data; let entry = objectApis[n]; if (!entry) { @@ -1491,11 +1846,20 @@ self.onmessage = e => { entry.added++; if (entry.added === 1) { - zde.forEachObject((localN, matrix, value, objectIndex) => { // XXX also need to do this efficiently when the chunk loads + zde.forEachObject((localN, matrix, value, objectIndex) => { + // XXX also need to do this efficiently when the chunk loads if (localN === n) { postMessage({ type: 'objectAdded', - args: [localN, chunk.x, chunk.z, objectIndex, matrix.slice(0, 3), matrix.slice(3, 7), value], + args: [ + localN, + chunk.x, + chunk.z, + objectIndex, + matrix.slice(0, 3), + matrix.slice(3, 7), + value, + ], }); } }); @@ -1517,7 +1881,12 @@ self.onmessage = e => { if (localN === n) { postMessage({ type: 'blockSet', - args: [n, chunk.x * NUM_CELLS + x, y, chunk.z * NUM_CELLS + z], + args: [ + n, + chunk.x * NUM_CELLS + x, + y, + chunk.z * NUM_CELLS + z, + ], }); } }); @@ -1530,7 +1899,7 @@ self.onmessage = e => { break; } case 'unregisterObject': { - const {n, added, removed, updated, set, clear} = data; + const { n, added, removed, updated, set, clear } = data; const entry = objectApis[n]; if (added) { entry.added--; @@ -1581,13 +1950,19 @@ self.onmessage = e => { } } */ } - if (entry.added === 0 && entry.removed === 0 && entry.updated === 0 && entry.set === 0 && entry.clear === 0) { + if ( + entry.added === 0 && + entry.removed === 0 && + entry.updated === 0 && + entry.set === 0 && + entry.clear === 0 + ) { objectApis[n] = null; } break; } case 'addObject': { - const {name, position: positions, rotation: rotations, value} = data; + const { name, position: positions, rotation: rotations, value } = data; const ox = Math.floor(positions[0] / NUM_CELLS); const oz = Math.floor(positions[2] / NUM_CELLS); @@ -1608,13 +1983,21 @@ self.onmessage = e => { const light = _findLight(n); if (light) { - oldChunk.addLightAt(objectIndex, positions[0], positions[1], positions[2], light); + oldChunk.addLightAt( + objectIndex, + positions[0], + positions[1], + positions[2], + light + ); for (let i = 0; i < CROSS_DIRECTIONS.length; i++) { const [dx, dz] = CROSS_DIRECTIONS[i]; const ox = Math.floor((x + dx * light) / NUM_CELLS); const oz = Math.floor((z + dz * light) / NUM_CELLS); - if (!updateSpecs.some(update => update[0] === ox && update[1] === oz)) { + if ( + !updateSpecs.some(update => update[0] === ox && update[1] === oz) + ) { updateSpecs.push([ox, oz]); } } @@ -1640,14 +2023,22 @@ self.onmessage = e => { if (objectApi && objectApi.added) { postMessage({ type: 'objectAdded', - args: [n, ox, oz, objectIndex, matrix.slice(0, 3), matrix.slice(3, 7), value], + args: [ + n, + ox, + oz, + objectIndex, + matrix.slice(0, 3), + matrix.slice(3, 7), + value, + ], }); } } break; } case 'removeObject': { - const {x: ox, z: oz, index: objectIndex} = data; + const { x: ox, z: oz, index: objectIndex } = data; const oldChunk = zde.getChunk(ox, oz); if (oldChunk) { @@ -1669,7 +2060,11 @@ self.onmessage = e => { const [dx, dz] = CROSS_DIRECTIONS[i]; const ox = Math.floor((x + dx * light) / NUM_CELLS); const oz = Math.floor((z + dz * light) / NUM_CELLS); - if (!updateSpecs.some(update => update[0] === ox && update[1] === oz)) { + if ( + !updateSpecs.some( + update => update[0] === ox && update[1] === oz + ) + ) { updateSpecs.push([ox, oz]); } } @@ -1708,7 +2103,7 @@ self.onmessage = e => { break; } case 'setObjectData': { - const {x, z, index, value} = data; + const { x, z, index, value } = data; const chunk = zde.getChunk(x, z); if (chunk) { @@ -1722,7 +2117,15 @@ self.onmessage = e => { postMessage({ type: 'objectUpdated', - args: [n, x, z, index, matrix.slice(0, 3), matrix.slice(3, 7), value], + args: [ + n, + x, + z, + index, + matrix.slice(0, 3), + matrix.slice(3, 7), + value, + ], }); } }); @@ -1730,7 +2133,7 @@ self.onmessage = e => { break; } case 'setBlock': { - const {x, y, z, v} = data; + const { x, y, z, v } = data; const ox = Math.floor(x / NUM_CELLS); const oz = Math.floor(z / NUM_CELLS); @@ -1760,7 +2163,7 @@ self.onmessage = e => { break; } case 'clearBlock': { - const {x, y, z} = data; + const { x, y, z } = data; const ox = Math.floor(x / NUM_CELLS); const oz = Math.floor(z / NUM_CELLS); @@ -1768,7 +2171,11 @@ self.onmessage = e => { if (oldChunk) { connection.clearBlock(x, y, z, () => {}); - const n = oldChunk.clearBlock(x - ox * NUM_CELLS, y, z - oz * NUM_CELLS); + const n = oldChunk.clearBlock( + x - ox * NUM_CELLS, + y, + z - oz * NUM_CELLS + ); _retesselateObjects(oldChunk); _relight(oldChunk, x, y, z); @@ -1790,8 +2197,8 @@ self.onmessage = e => { break; } case 'generate': { - const {id, args} = data; - const {x, y} = args; + const { id, args } = data; + const { x, y } = args; _requestChunk(x, y) .then(() => { @@ -1807,19 +2214,27 @@ self.onmessage = e => { break; } case 'terrainGenerate': { - const {id, args} = data; - const {x, y, index, numPositions, numIndices} = args; - let {buffer} = args; + const { id, args } = data; + const { x, y, index, numPositions, numIndices } = args; + let { buffer } = args; _requestTerrainChunk(x, y, index, numPositions, numIndices) .then(chunk => { - protocolUtils.stringifyTerrainRenderChunk(chunk.chunkData.terrain, chunk.chunkData.decorations.terrain, buffer, 0); - - postMessage({ - type: 'response', - args: [id], - result: buffer, - }, [buffer]); + protocolUtils.stringifyTerrainRenderChunk( + chunk.chunkData.terrain, + chunk.chunkData.decorations.terrain, + buffer, + 0 + ); + + postMessage( + { + type: 'response', + args: [id], + result: buffer, + }, + [buffer] + ); }) .catch(err => { console.warn(err); @@ -1827,36 +2242,56 @@ self.onmessage = e => { break; } case 'terrainsGenerate': { - const {id, args} = data; - const {specs} = args; - let {buffer} = args; + const { id, args } = data; + const { specs } = args; + let { buffer } = args; - const chunks = specs.map(({x, y, index, numPositions, numIndices}) => _getTerrainChunk(x, y, index, numPositions, numIndices)); + const chunks = specs.map(({ x, y, index, numPositions, numIndices }) => + _getTerrainChunk(x, y, index, numPositions, numIndices) + ); protocolUtils.stringifyTerrainsRenderChunk(chunks, buffer, 0); - postMessage({ - type: 'response', - args: [id], - result: buffer, - }, [buffer]); + postMessage( + { + type: 'response', + args: [id], + result: buffer, + }, + [buffer] + ); break; } case 'objectsGenerate': { - const {id, args} = data; - const {x, z, index, numPositions, numObjectIndices, numIndices} = args; - let {buffer} = args; - - _requestObjectsChunk(x, z, index, numPositions, numObjectIndices, numIndices) + const { id, args } = data; + const { x, z, index, numPositions, numObjectIndices, numIndices } = args; + let { buffer } = args; + + _requestObjectsChunk( + x, + z, + index, + numPositions, + numObjectIndices, + numIndices + ) .then(chunk => { zde.pushChunk(chunk); - protocolUtils.stringifyWorker(chunk.chunkData.objects, chunk.chunkData.decorations.objects, buffer, 0); - - postMessage({ - type: 'response', - args: [id], - result: buffer, - }, [buffer]); + protocolUtils.stringifyWorker( + chunk.chunkData.objects, + chunk.chunkData.decorations.objects, + buffer, + 0 + ); + + postMessage( + { + type: 'response', + args: [id], + result: buffer, + }, + [buffer] + ); chunk.forEachObject((n, matrix, value, objectIndex) => { const objectApi = objectApis[n]; @@ -1864,7 +2299,15 @@ self.onmessage = e => { if (objectApi && objectApi.added) { postMessage({ type: 'objectAdded', - args: [n, x, z, objectIndex, matrix.slice(0, 3), matrix.slice(3, 7), value], + args: [ + n, + x, + z, + objectIndex, + matrix.slice(0, 3), + matrix.slice(3, 7), + value, + ], }); } }); @@ -1875,8 +2318,8 @@ self.onmessage = e => { break; } case 'ungenerate': { - const {args} = data; - const {x, z} = args; + const { args } = data; + const { x, z } = args; const chunk = _unrequestChunk(x, z); chunk.forEachObject((n, matrix, value, objectIndex) => { @@ -1926,8 +2369,8 @@ self.onmessage = e => { break; } */ case 'mutateVoxel': { - const {id, args} = data; - const {position: [x, y, z, v]} = args; + const { id, args } = data; + const { position: [x, y, z, v] } = args; connection.mutateVoxel(x, y, z, v, () => {}); @@ -1942,8 +2385,8 @@ self.onmessage = e => { if (!seenChunks.some(([x, z]) => x === ox && z === oz)) { const oldChunk = zde.getChunk(ox, oz); - const lx = x - (ox * NUM_CELLS); - const lz = z - (oz * NUM_CELLS); + const lx = x - ox * NUM_CELLS; + const lz = z - oz * NUM_CELLS; const newEther = Float32Array.from([lx, y, lz, v]); _retesselateTerrain(oldChunk, newEther); @@ -1961,33 +2404,57 @@ self.onmessage = e => { break; } case 'terrainCull': { - const {id, args} = data; - const {hmdPosition, projectionMatrix, matrixWorldInverse, buffer} = args; - - const groups = _getTerrainCull(hmdPosition, projectionMatrix, matrixWorldInverse); + const { id, args } = data; + const { + hmdPosition, + projectionMatrix, + matrixWorldInverse, + buffer, + } = args; + + const groups = _getTerrainCull( + hmdPosition, + projectionMatrix, + matrixWorldInverse + ); protocolUtils.stringifyTerrainCull(groups, buffer, 0); - postMessage({ - type: 'response', - args: [id], - result: buffer, - }, [buffer]); + postMessage( + { + type: 'response', + args: [id], + result: buffer, + }, + [buffer] + ); break; } case 'objectsCull': { - const {id, args} = data; - const {hmdPosition, projectionMatrix, matrixWorldInverse, buffer} = args; - - const chunks = _getObjectsCull(hmdPosition, projectionMatrix, matrixWorldInverse); + const { id, args } = data; + const { + hmdPosition, + projectionMatrix, + matrixWorldInverse, + buffer, + } = args; + + const chunks = _getObjectsCull( + hmdPosition, + projectionMatrix, + matrixWorldInverse + ); protocolUtils.stringifyObjectsCull(chunks, buffer, 0); - postMessage({ - type: 'response', - args: [id], - result: buffer, - }, [buffer]); + postMessage( + { + type: 'response', + args: [id], + result: buffer, + }, + [buffer] + ); break; } case 'getHoveredObjects': { - const {id, args: {buffer}} = data; + const { id, args: { buffer } } = data; const float32Array = new Float32Array(buffer); const lx = float32Array[0]; @@ -1996,44 +2463,58 @@ self.onmessage = e => { const rx = float32Array[3]; const ry = float32Array[4]; const rz = float32Array[5]; - _getHoveredTrackedObject(lx, ly, lz, buffer, 0) + _getHoveredTrackedObject(lx, ly, lz, buffer, 0); _getHoveredTrackedObject(rx, ry, rz, buffer, 12 * 4); - postMessage({ - type: 'response', - args: [id], - result: buffer, - }, [buffer]); + postMessage( + { + type: 'response', + args: [id], + result: buffer, + }, + [buffer] + ); break; } case 'getTeleportObject': { - const {id, args: {buffer}} = data; + const { id, args: { buffer } } = data; const float32Array = new Float32Array(buffer, 0, 3); - _getTeleportObject(float32Array[0], float32Array[1], float32Array[2], buffer); - - postMessage({ - type: 'response', - args: [id], - result: buffer, - }, [buffer]); + _getTeleportObject( + float32Array[0], + float32Array[1], + float32Array[2], + buffer + ); + + postMessage( + { + type: 'response', + args: [id], + result: buffer, + }, + [buffer] + ); break; } case 'getBodyObject': { - const {id, args: {buffer}} = data; + const { id, args: { buffer } } = data; const float32Array = new Float32Array(buffer, 0, 3); _getBodyObject(float32Array[0], float32Array[1], float32Array[2], buffer); - postMessage({ - type: 'response', - args: [id], - result: buffer, - }, [buffer]); + postMessage( + { + type: 'response', + args: [id], + result: buffer, + }, + [buffer] + ); break; } case 'response': { - const {id, result} = data; + const { id, result } = data; queues[id](result); queues[id] = null; @@ -2054,7 +2535,11 @@ const _getTerrainCull = (hmdPosition, projectionMatrix, matrixWorldInverse) => { const resultSize = NUM_MAP_CHUNK_MESHES * (1 + NUM_RENDER_GROUPS * 6); const resultOffset = Module._malloc(resultSize * 4); allocator.offsets.push(resultOffset); - const groups = new Uint32Array(Module.HEAP8.buffer, Module.HEAP8.byteOffset + resultOffset, resultSize); + const groups = new Uint32Array( + Module.HEAP8.buffer, + Module.HEAP8.byteOffset + resultOffset, + resultSize + ); const groupsIndex = Module._cllTerrain( allocator.allocBuffer(Float32Array.from(hmdPosition)), @@ -2077,7 +2562,11 @@ const _getObjectsCull = (hmdPosition, projectionMatrix, matrixWorldInverse) => { const resultSize = NUM_MAP_CHUNK_MESHES * (1 + NUM_RENDER_GROUPS * 2); const resultOffset = Module._malloc(resultSize * 4); allocator.offsets.push(resultOffset); - const groups = new Uint32Array(Module.HEAP8.buffer, Module.HEAP8.byteOffset + resultOffset, resultSize); + const groups = new Uint32Array( + Module.HEAP8.buffer, + Module.HEAP8.byteOffset + resultOffset, + resultSize + ); const groupsIndex = Module._cllObjects( allocator.allocBuffer(Float32Array.from(hmdPosition)),