mirror of
https://github.com/Rezmason/matrix.git
synced 2026-04-17 05:49:30 -07:00
rainPass now renders multiple cameras and viewports, using data from the hardware.
Added quiltPass (which uses holoplay’s quilting shader). Added a holoplay effect version. (Versions can also now specify a preferred renderer.)
This commit is contained in:
26
js/config.js
26
js/config.js
@@ -75,6 +75,7 @@ const defaults = {
|
|||||||
slant: 0, // The angle at which rain falls; the orientation of the glyph grid
|
slant: 0, // The angle at which rain falls; the orientation of the glyph grid
|
||||||
resolution: 1, // An overall scale multiplier
|
resolution: 1, // An overall scale multiplier
|
||||||
useHalfFloat: false,
|
useHalfFloat: false,
|
||||||
|
renderer: "webgpu", // The preferred web graphics API
|
||||||
};
|
};
|
||||||
|
|
||||||
const versions = {
|
const versions = {
|
||||||
@@ -205,6 +206,30 @@ const versions = {
|
|||||||
{ hsl: [0.1, 1.0, 0.9], at: 1.0 },
|
{ hsl: [0.1, 1.0, 0.9], at: 1.0 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
holoplay: {
|
||||||
|
...defaults,
|
||||||
|
...fonts.resurrections,
|
||||||
|
numColumns: 40,
|
||||||
|
fallSpeed: 0.35,
|
||||||
|
cycleStyle: "cycleRandomly",
|
||||||
|
cycleSpeed: 0.8,
|
||||||
|
glyphEdgeCrop: 0.1,
|
||||||
|
paletteEntries: [
|
||||||
|
{ hsl: [0.39, 0.9, 0.0], at: 0.0 },
|
||||||
|
{ hsl: [0.39, 1.0, 0.6], at: 0.5 },
|
||||||
|
{ hsl: [0.39, 1.0, 1.0], at: 1.0 },
|
||||||
|
],
|
||||||
|
raindropLength: 1.4,
|
||||||
|
highPassThreshold: 0.2,
|
||||||
|
cursorEffectThreshold: 0.8,
|
||||||
|
|
||||||
|
renderer: "regl",
|
||||||
|
bloomSize: 0,
|
||||||
|
volumetric: true,
|
||||||
|
forwardSpeed: 0,
|
||||||
|
density: 3,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
versions.throwback = versions.operator;
|
versions.throwback = versions.operator;
|
||||||
versions["1999"] = versions.operator;
|
versions["1999"] = versions.operator;
|
||||||
@@ -247,6 +272,7 @@ const paramMapping = {
|
|||||||
stripeColors: { key: "stripeColors", parser: (s) => s },
|
stripeColors: { key: "stripeColors", parser: (s) => s },
|
||||||
backgroundColor: { key: "backgroundColor", parser: (s) => s.split(",").map(parseFloat) },
|
backgroundColor: { key: "backgroundColor", parser: (s) => s.split(",").map(parseFloat) },
|
||||||
volumetric: { key: "volumetric", parser: (s) => s.toLowerCase().includes("true") },
|
volumetric: { key: "volumetric", parser: (s) => s.toLowerCase().includes("true") },
|
||||||
|
renderer: { key: "renderer", parser: (s) => s },
|
||||||
};
|
};
|
||||||
paramMapping.dropLength = paramMapping.raindropLength;
|
paramMapping.dropLength = paramMapping.raindropLength;
|
||||||
paramMapping.angle = paramMapping.slant;
|
paramMapping.angle = paramMapping.slant;
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ const supportsWebGPU = async () => {
|
|||||||
|
|
||||||
document.body.onload = async () => {
|
document.body.onload = async () => {
|
||||||
const urlParams = Object.fromEntries(new URLSearchParams(window.location.search).entries());
|
const urlParams = Object.fromEntries(new URLSearchParams(window.location.search).entries());
|
||||||
const useREGL = !(await supportsWebGPU()) || ["webgl", "regl"].includes(urlParams.renderer?.toLowerCase());
|
|
||||||
const solution = import(`./${useREGL ? "regl" : "webgpu"}/main.js`);
|
|
||||||
const config = makeConfig(urlParams);
|
const config = makeConfig(urlParams);
|
||||||
|
const useREGL = !(await supportsWebGPU()) || ["webgl", "regl"].includes(config.renderer?.toLowerCase());
|
||||||
|
const solution = import(`./${useREGL ? "regl" : "webgpu"}/main.js`);
|
||||||
(await solution).default(canvas, config);
|
(await solution).default(canvas, config);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import makePalettePass from "./palettePass.js";
|
|||||||
import makeStripePass from "./stripePass.js";
|
import makeStripePass from "./stripePass.js";
|
||||||
import makeImagePass from "./imagePass.js";
|
import makeImagePass from "./imagePass.js";
|
||||||
import makeResurrectionPass from "./resurrectionPass.js";
|
import makeResurrectionPass from "./resurrectionPass.js";
|
||||||
|
import makeQuiltPass from "./quiltPass.js";
|
||||||
|
|
||||||
|
import * as HoloPlayCore from "../../lib/holoplaycore.module.js";
|
||||||
|
|
||||||
const effects = {
|
const effects = {
|
||||||
none: null,
|
none: null,
|
||||||
@@ -48,10 +51,100 @@ export default async (canvas, config) => {
|
|||||||
optionalExtensions: ["EXT_color_buffer_half_float", "WEBGL_color_buffer_float", "OES_standard_derivatives"],
|
optionalExtensions: ["EXT_color_buffer_half_float", "WEBGL_color_buffer_float", "OES_standard_derivatives"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const lkg = await new Promise((resolve, reject) => {
|
||||||
|
const client = new HoloPlayCore.Client((data) => {
|
||||||
|
/*
|
||||||
|
data = {
|
||||||
|
devices: [
|
||||||
|
{
|
||||||
|
buttons: [ 0, 0, 0, 0 ],
|
||||||
|
calibration:
|
||||||
|
{
|
||||||
|
DPI: { value: 324 },
|
||||||
|
center: { value: 0.15018756687641144 },
|
||||||
|
configVersion: "3.0",
|
||||||
|
flipImageX: { value: 0 },
|
||||||
|
flipImageY: { value: 0 },
|
||||||
|
flipSubp: { value: 0 },
|
||||||
|
fringe: { value: 0 },
|
||||||
|
invView: { value: 1 },
|
||||||
|
pitch: { value: 52.58013153076172 },
|
||||||
|
screenH: { value: 2048 },
|
||||||
|
screenW: { value: 1536 },
|
||||||
|
slope: { value: -7.145165920257568 },
|
||||||
|
verticalAngle: { value: 0 },
|
||||||
|
viewCone: { value: 40 }
|
||||||
|
},
|
||||||
|
defaultQuilt:
|
||||||
|
{
|
||||||
|
quiltAspect: 0.75,
|
||||||
|
quiltX: 3840,
|
||||||
|
quiltY: 3840,
|
||||||
|
tileX: 8,
|
||||||
|
tileY: 6
|
||||||
|
},
|
||||||
|
hardwareVersion: "portrait",
|
||||||
|
hwid: "LKG-P11063",
|
||||||
|
index: 0,
|
||||||
|
joystickIndex: -1,
|
||||||
|
state: "ok",
|
||||||
|
unityIndex: 1,
|
||||||
|
windowCoords: [ 1440, 900 ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
error: 0,
|
||||||
|
version: "1.2.2"
|
||||||
|
};
|
||||||
|
/**/
|
||||||
|
|
||||||
|
|
||||||
|
if (data.devices.length === 0) {
|
||||||
|
resolve({ tileX: 1, tileY: 1, fov: 90 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const device = data.devices[0];
|
||||||
|
const defaultQuilt = device.defaultQuilt;
|
||||||
|
|
||||||
|
const {quiltX, quiltY, tileX, tileY} = defaultQuilt;
|
||||||
|
|
||||||
|
const fov = 15; // But is it?
|
||||||
|
|
||||||
|
const calibration = Object.fromEntries(
|
||||||
|
Object.entries(device.calibration)
|
||||||
|
.map(([key, value]) => ([key, value.value]))
|
||||||
|
.filter(([key, value]) => (value != null))
|
||||||
|
);
|
||||||
|
|
||||||
|
const screenInches = calibration.screenW / calibration.DPI;
|
||||||
|
const pitch = calibration.pitch * screenInches * Math.cos(Math.atan(1.0 / calibration.slope));
|
||||||
|
const tilt = calibration.screenH / (calibration.screenW * calibration.slope) * (calibration.flipImageX * 2 - 1);
|
||||||
|
const subp = 1 / (calibration.screenW * 3);
|
||||||
|
|
||||||
|
const quiltViewPortion = [
|
||||||
|
(Math.floor(quiltX / tileX) * tileX) / quiltX,
|
||||||
|
(Math.floor(quiltY / tileY) * tileY) / quiltY,
|
||||||
|
];
|
||||||
|
|
||||||
|
const output = {
|
||||||
|
...defaultQuilt,
|
||||||
|
...calibration,
|
||||||
|
pitch,
|
||||||
|
tilt,
|
||||||
|
subp,
|
||||||
|
|
||||||
|
quiltViewPortion,
|
||||||
|
fov
|
||||||
|
};
|
||||||
|
|
||||||
|
resolve(output);
|
||||||
|
}, reject);
|
||||||
|
});
|
||||||
|
|
||||||
// All this takes place in a full screen quad.
|
// All this takes place in a full screen quad.
|
||||||
const fullScreenQuad = makeFullScreenQuad(regl);
|
const fullScreenQuad = makeFullScreenQuad(regl);
|
||||||
const effectName = config.effect in effects ? config.effect : "plain";
|
const effectName = config.effect in effects ? config.effect : "plain";
|
||||||
const pipeline = makePipeline({ regl, config }, [makeRain, makeBloomPass, effects[effectName]]);
|
const pipeline = makePipeline({ regl, config, lkg }, [makeRain, makeBloomPass, effects[effectName], makeQuiltPass]);
|
||||||
const screenUniforms = { tex: pipeline[pipeline.length - 1].outputs.primary };
|
const screenUniforms = { tex: pipeline[pipeline.length - 1].outputs.primary };
|
||||||
const drawToScreen = regl({ uniforms: screenUniforms });
|
const drawToScreen = regl({ uniforms: screenUniforms });
|
||||||
await Promise.all(pipeline.map((step) => step.ready));
|
await Promise.all(pipeline.map((step) => step.ready));
|
||||||
|
|||||||
34
js/regl/quiltPass.js
Normal file
34
js/regl/quiltPass.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { loadImage, loadText, makePassFBO, makePass } from "./utils.js";
|
||||||
|
|
||||||
|
// Multiplies the rendered rain and bloom by a loaded in image
|
||||||
|
|
||||||
|
export default ({ regl, config, lkg }, inputs) => {
|
||||||
|
let enabled = lkg.tileX * lkg.tileY > 1;
|
||||||
|
|
||||||
|
// enabled = false;
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
return makePass({
|
||||||
|
primary: inputs.primary,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = makePassFBO(regl, config.useHalfFloat);
|
||||||
|
const quiltPassFrag = loadText("shaders/glsl/quiltPass.frag.glsl");
|
||||||
|
const render = regl({
|
||||||
|
frag: regl.prop("frag"),
|
||||||
|
uniforms: {
|
||||||
|
quiltTexture: inputs.primary,
|
||||||
|
...lkg,
|
||||||
|
},
|
||||||
|
framebuffer: output,
|
||||||
|
});
|
||||||
|
return makePass(
|
||||||
|
{
|
||||||
|
primary: output,
|
||||||
|
},
|
||||||
|
Promise.all([quiltPassFrag.loaded]),
|
||||||
|
(w, h) => output.resize(w, h),
|
||||||
|
() => render({ frag: quiltPassFrag.text() })
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -19,7 +19,7 @@ const blVert = [1, 0];
|
|||||||
const brVert = [1, 1];
|
const brVert = [1, 1];
|
||||||
const quadVertices = [tlVert, trVert, brVert, tlVert, brVert, blVert];
|
const quadVertices = [tlVert, trVert, brVert, tlVert, brVert, blVert];
|
||||||
|
|
||||||
export default ({ regl, config }) => {
|
export default ({ regl, config, lkg }) => {
|
||||||
// The volumetric mode multiplies the number of columns
|
// The volumetric mode multiplies the number of columns
|
||||||
// to reach the desired density, and then overlaps them
|
// to reach the desired density, and then overlaps them
|
||||||
const volumetric = config.volumetric;
|
const volumetric = config.volumetric;
|
||||||
@@ -143,6 +143,8 @@ export default ({ regl, config }) => {
|
|||||||
screenSize: regl.prop("screenSize"),
|
screenSize: regl.prop("screenSize"),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
viewport: regl.prop("viewport"),
|
||||||
|
|
||||||
attributes: {
|
attributes: {
|
||||||
aPosition: quadPositions,
|
aPosition: quadPositions,
|
||||||
aCorner: Array(numQuads).fill(quadVertices),
|
aCorner: Array(numQuads).fill(quadVertices),
|
||||||
@@ -163,9 +165,16 @@ export default ({ regl, config }) => {
|
|||||||
mat4.scale(transform, transform, vec3.fromValues(1, 1, 2));
|
mat4.scale(transform, transform, vec3.fromValues(1, 1, 2));
|
||||||
} else {
|
} else {
|
||||||
mat4.translate(transform, transform, vec3.fromValues(0, 0, -1));
|
mat4.translate(transform, transform, vec3.fromValues(0, 0, -1));
|
||||||
|
|
||||||
|
// mat4.rotateX(transform, transform, (Math.PI * 1) / 8);
|
||||||
|
// mat4.rotateY(transform, transform, (Math.PI * 1) / 4);
|
||||||
|
// mat4.translate(transform, transform, vec3.fromValues(0, 0, -1));
|
||||||
|
// mat4.scale(transform, transform, vec3.fromValues(1, 1, 2));
|
||||||
}
|
}
|
||||||
const camera = mat4.create();
|
const camera = mat4.create();
|
||||||
|
|
||||||
|
const vantagePoints = [];
|
||||||
|
|
||||||
return makePass(
|
return makePass(
|
||||||
{
|
{
|
||||||
primary: output,
|
primary: output,
|
||||||
@@ -174,14 +183,47 @@ export default ({ regl, config }) => {
|
|||||||
(w, h) => {
|
(w, h) => {
|
||||||
output.resize(w, h);
|
output.resize(w, h);
|
||||||
const aspectRatio = w / h;
|
const aspectRatio = w / h;
|
||||||
if (config.effect === "none") {
|
|
||||||
if (aspectRatio > 1) {
|
const [numTileColumns, numTileRows] = [lkg.tileX, lkg.tileY];
|
||||||
mat4.ortho(camera, -1.5 * aspectRatio, 1.5 * aspectRatio, -1.5, 1.5, -1000, 1000);
|
const numVantagePoints = numTileRows * numTileColumns;
|
||||||
} else {
|
const tileSize = [Math.floor(w /*lkg.quiltX*/ / numTileColumns), Math.floor(h /*lkg.quiltY*/ / numTileRows)];
|
||||||
mat4.ortho(camera, -1.5, 1.5, -1.5 / aspectRatio, 1.5 / aspectRatio, -1000, 1000);
|
vantagePoints.length = 0;
|
||||||
|
for (let row = 0; row < numTileRows; row++) {
|
||||||
|
for (let column = 0; column < numTileColumns; column++) {
|
||||||
|
const index = column + row * numTileColumns;
|
||||||
|
const camera = mat4.create();
|
||||||
|
|
||||||
|
if (config.effect === "none") {
|
||||||
|
if (aspectRatio > 1) {
|
||||||
|
mat4.ortho(camera, -1.5 * aspectRatio, 1.5 * aspectRatio, -1.5, 1.5, -1000, 1000);
|
||||||
|
} else {
|
||||||
|
mat4.ortho(camera, -1.5, 1.5, -1.5 / aspectRatio, 1.5 / aspectRatio, -1000, 1000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mat4.perspective(camera, (Math.PI / 180) * lkg.fov, aspectRatio, 0.0001, 1000);
|
||||||
|
|
||||||
|
mat4.translate(camera, camera, vec3.fromValues(0, 0, -1));
|
||||||
|
|
||||||
|
const distanceToTarget = 1; // TODO: Get from somewhere else
|
||||||
|
let vantagePointAngle = (Math.PI / 180) * lkg.viewCone * (index / (numVantagePoints - 1) - 0.5);
|
||||||
|
if (isNaN(vantagePointAngle)) {
|
||||||
|
vantagePointAngle = 0;
|
||||||
|
}
|
||||||
|
const xOffset = distanceToTarget * Math.tan(vantagePointAngle);
|
||||||
|
|
||||||
|
mat4.translate(camera, camera, vec3.fromValues(xOffset, 0, 0));
|
||||||
|
|
||||||
|
camera[8] = -xOffset / (distanceToTarget * Math.tan((Math.PI / 180) * 0.5 * lkg.fov) * aspectRatio); // Is this right??
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewport = {
|
||||||
|
x: column * tileSize[0],
|
||||||
|
y: row * tileSize[1],
|
||||||
|
width: tileSize[0],
|
||||||
|
height: tileSize[1],
|
||||||
|
};
|
||||||
|
vantagePoints.push({ camera, viewport });
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
mat4.perspective(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
|
|
||||||
}
|
}
|
||||||
[screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
|
[screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
|
||||||
},
|
},
|
||||||
@@ -192,7 +234,18 @@ export default ({ regl, config }) => {
|
|||||||
color: [0, 0, 0, 1],
|
color: [0, 0, 0, 1],
|
||||||
framebuffer: output,
|
framebuffer: output,
|
||||||
});
|
});
|
||||||
render({ camera, transform, screenSize, vert: rainPassVert.text(), frag: rainPassFrag.text() });
|
|
||||||
|
// const now = Date.now();
|
||||||
|
|
||||||
|
// mat4.identity(transform);
|
||||||
|
// mat4.rotateX(transform, transform, (Math.PI * 1) / 8);
|
||||||
|
// mat4.rotateY(transform, transform, Math.sin(0.001 * now));
|
||||||
|
// mat4.translate(transform, transform, vec3.fromValues(0, 0, -1));
|
||||||
|
// mat4.scale(transform, transform, vec3.fromValues(1, 1, 2));
|
||||||
|
|
||||||
|
for (const vantagePoint of vantagePoints) {
|
||||||
|
render({ ...vantagePoint, transform, screenSize, vert: rainPassVert.text(), frag: rainPassFrag.text() });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
787
lib/holoplaycore.module.js
Normal file
787
lib/holoplaycore.module.js
Normal file
@@ -0,0 +1,787 @@
|
|||||||
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
||||||
|
|
||||||
|
function createCommonjsModule(fn, module) {
|
||||||
|
return module = { exports: {} }, fn(module, module.exports), module.exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cbor = createCommonjsModule(function (module) {
|
||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Patrick Gansterer <paroga@paroga.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function(global, undefined$1) {var POW_2_24 = Math.pow(2, -24),
|
||||||
|
POW_2_32 = Math.pow(2, 32),
|
||||||
|
POW_2_53 = Math.pow(2, 53);
|
||||||
|
|
||||||
|
function encode(value) {
|
||||||
|
var data = new ArrayBuffer(256);
|
||||||
|
var dataView = new DataView(data);
|
||||||
|
var lastLength;
|
||||||
|
var offset = 0;
|
||||||
|
|
||||||
|
function ensureSpace(length) {
|
||||||
|
var newByteLength = data.byteLength;
|
||||||
|
var requiredLength = offset + length;
|
||||||
|
while (newByteLength < requiredLength)
|
||||||
|
newByteLength *= 2;
|
||||||
|
if (newByteLength !== data.byteLength) {
|
||||||
|
var oldDataView = dataView;
|
||||||
|
data = new ArrayBuffer(newByteLength);
|
||||||
|
dataView = new DataView(data);
|
||||||
|
var uint32count = (offset + 3) >> 2;
|
||||||
|
for (var i = 0; i < uint32count; ++i)
|
||||||
|
dataView.setUint32(i * 4, oldDataView.getUint32(i * 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
lastLength = length;
|
||||||
|
return dataView;
|
||||||
|
}
|
||||||
|
function write() {
|
||||||
|
offset += lastLength;
|
||||||
|
}
|
||||||
|
function writeFloat64(value) {
|
||||||
|
write(ensureSpace(8).setFloat64(offset, value));
|
||||||
|
}
|
||||||
|
function writeUint8(value) {
|
||||||
|
write(ensureSpace(1).setUint8(offset, value));
|
||||||
|
}
|
||||||
|
function writeUint8Array(value) {
|
||||||
|
var dataView = ensureSpace(value.length);
|
||||||
|
for (var i = 0; i < value.length; ++i)
|
||||||
|
dataView.setUint8(offset + i, value[i]);
|
||||||
|
write();
|
||||||
|
}
|
||||||
|
function writeUint16(value) {
|
||||||
|
write(ensureSpace(2).setUint16(offset, value));
|
||||||
|
}
|
||||||
|
function writeUint32(value) {
|
||||||
|
write(ensureSpace(4).setUint32(offset, value));
|
||||||
|
}
|
||||||
|
function writeUint64(value) {
|
||||||
|
var low = value % POW_2_32;
|
||||||
|
var high = (value - low) / POW_2_32;
|
||||||
|
var dataView = ensureSpace(8);
|
||||||
|
dataView.setUint32(offset, high);
|
||||||
|
dataView.setUint32(offset + 4, low);
|
||||||
|
write();
|
||||||
|
}
|
||||||
|
function writeTypeAndLength(type, length) {
|
||||||
|
if (length < 24) {
|
||||||
|
writeUint8(type << 5 | length);
|
||||||
|
} else if (length < 0x100) {
|
||||||
|
writeUint8(type << 5 | 24);
|
||||||
|
writeUint8(length);
|
||||||
|
} else if (length < 0x10000) {
|
||||||
|
writeUint8(type << 5 | 25);
|
||||||
|
writeUint16(length);
|
||||||
|
} else if (length < 0x100000000) {
|
||||||
|
writeUint8(type << 5 | 26);
|
||||||
|
writeUint32(length);
|
||||||
|
} else {
|
||||||
|
writeUint8(type << 5 | 27);
|
||||||
|
writeUint64(length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeItem(value) {
|
||||||
|
var i;
|
||||||
|
|
||||||
|
if (value === false)
|
||||||
|
return writeUint8(0xf4);
|
||||||
|
if (value === true)
|
||||||
|
return writeUint8(0xf5);
|
||||||
|
if (value === null)
|
||||||
|
return writeUint8(0xf6);
|
||||||
|
if (value === undefined$1)
|
||||||
|
return writeUint8(0xf7);
|
||||||
|
|
||||||
|
switch (typeof value) {
|
||||||
|
case "number":
|
||||||
|
if (Math.floor(value) === value) {
|
||||||
|
if (0 <= value && value <= POW_2_53)
|
||||||
|
return writeTypeAndLength(0, value);
|
||||||
|
if (-POW_2_53 <= value && value < 0)
|
||||||
|
return writeTypeAndLength(1, -(value + 1));
|
||||||
|
}
|
||||||
|
writeUint8(0xfb);
|
||||||
|
return writeFloat64(value);
|
||||||
|
|
||||||
|
case "string":
|
||||||
|
var utf8data = [];
|
||||||
|
for (i = 0; i < value.length; ++i) {
|
||||||
|
var charCode = value.charCodeAt(i);
|
||||||
|
if (charCode < 0x80) {
|
||||||
|
utf8data.push(charCode);
|
||||||
|
} else if (charCode < 0x800) {
|
||||||
|
utf8data.push(0xc0 | charCode >> 6);
|
||||||
|
utf8data.push(0x80 | charCode & 0x3f);
|
||||||
|
} else if (charCode < 0xd800) {
|
||||||
|
utf8data.push(0xe0 | charCode >> 12);
|
||||||
|
utf8data.push(0x80 | (charCode >> 6) & 0x3f);
|
||||||
|
utf8data.push(0x80 | charCode & 0x3f);
|
||||||
|
} else {
|
||||||
|
charCode = (charCode & 0x3ff) << 10;
|
||||||
|
charCode |= value.charCodeAt(++i) & 0x3ff;
|
||||||
|
charCode += 0x10000;
|
||||||
|
|
||||||
|
utf8data.push(0xf0 | charCode >> 18);
|
||||||
|
utf8data.push(0x80 | (charCode >> 12) & 0x3f);
|
||||||
|
utf8data.push(0x80 | (charCode >> 6) & 0x3f);
|
||||||
|
utf8data.push(0x80 | charCode & 0x3f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeTypeAndLength(3, utf8data.length);
|
||||||
|
return writeUint8Array(utf8data);
|
||||||
|
|
||||||
|
default:
|
||||||
|
var length;
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
length = value.length;
|
||||||
|
writeTypeAndLength(4, length);
|
||||||
|
for (i = 0; i < length; ++i)
|
||||||
|
encodeItem(value[i]);
|
||||||
|
} else if (value instanceof Uint8Array) {
|
||||||
|
writeTypeAndLength(2, value.length);
|
||||||
|
writeUint8Array(value);
|
||||||
|
} else {
|
||||||
|
var keys = Object.keys(value);
|
||||||
|
length = keys.length;
|
||||||
|
writeTypeAndLength(5, length);
|
||||||
|
for (i = 0; i < length; ++i) {
|
||||||
|
var key = keys[i];
|
||||||
|
encodeItem(key);
|
||||||
|
encodeItem(value[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encodeItem(value);
|
||||||
|
|
||||||
|
if ("slice" in data)
|
||||||
|
return data.slice(0, offset);
|
||||||
|
|
||||||
|
var ret = new ArrayBuffer(offset);
|
||||||
|
var retView = new DataView(ret);
|
||||||
|
for (var i = 0; i < offset; ++i)
|
||||||
|
retView.setUint8(i, dataView.getUint8(i));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decode(data, tagger, simpleValue) {
|
||||||
|
var dataView = new DataView(data);
|
||||||
|
var offset = 0;
|
||||||
|
|
||||||
|
if (typeof tagger !== "function")
|
||||||
|
tagger = function(value) { return value; };
|
||||||
|
if (typeof simpleValue !== "function")
|
||||||
|
simpleValue = function() { return undefined$1; };
|
||||||
|
|
||||||
|
function read(value, length) {
|
||||||
|
offset += length;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
function readArrayBuffer(length) {
|
||||||
|
return read(new Uint8Array(data, offset, length), length);
|
||||||
|
}
|
||||||
|
function readFloat16() {
|
||||||
|
var tempArrayBuffer = new ArrayBuffer(4);
|
||||||
|
var tempDataView = new DataView(tempArrayBuffer);
|
||||||
|
var value = readUint16();
|
||||||
|
|
||||||
|
var sign = value & 0x8000;
|
||||||
|
var exponent = value & 0x7c00;
|
||||||
|
var fraction = value & 0x03ff;
|
||||||
|
|
||||||
|
if (exponent === 0x7c00)
|
||||||
|
exponent = 0xff << 10;
|
||||||
|
else if (exponent !== 0)
|
||||||
|
exponent += (127 - 15) << 10;
|
||||||
|
else if (fraction !== 0)
|
||||||
|
return fraction * POW_2_24;
|
||||||
|
|
||||||
|
tempDataView.setUint32(0, sign << 16 | exponent << 13 | fraction << 13);
|
||||||
|
return tempDataView.getFloat32(0);
|
||||||
|
}
|
||||||
|
function readFloat32() {
|
||||||
|
return read(dataView.getFloat32(offset), 4);
|
||||||
|
}
|
||||||
|
function readFloat64() {
|
||||||
|
return read(dataView.getFloat64(offset), 8);
|
||||||
|
}
|
||||||
|
function readUint8() {
|
||||||
|
return read(dataView.getUint8(offset), 1);
|
||||||
|
}
|
||||||
|
function readUint16() {
|
||||||
|
return read(dataView.getUint16(offset), 2);
|
||||||
|
}
|
||||||
|
function readUint32() {
|
||||||
|
return read(dataView.getUint32(offset), 4);
|
||||||
|
}
|
||||||
|
function readUint64() {
|
||||||
|
return readUint32() * POW_2_32 + readUint32();
|
||||||
|
}
|
||||||
|
function readBreak() {
|
||||||
|
if (dataView.getUint8(offset) !== 0xff)
|
||||||
|
return false;
|
||||||
|
offset += 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function readLength(additionalInformation) {
|
||||||
|
if (additionalInformation < 24)
|
||||||
|
return additionalInformation;
|
||||||
|
if (additionalInformation === 24)
|
||||||
|
return readUint8();
|
||||||
|
if (additionalInformation === 25)
|
||||||
|
return readUint16();
|
||||||
|
if (additionalInformation === 26)
|
||||||
|
return readUint32();
|
||||||
|
if (additionalInformation === 27)
|
||||||
|
return readUint64();
|
||||||
|
if (additionalInformation === 31)
|
||||||
|
return -1;
|
||||||
|
throw "Invalid length encoding";
|
||||||
|
}
|
||||||
|
function readIndefiniteStringLength(majorType) {
|
||||||
|
var initialByte = readUint8();
|
||||||
|
if (initialByte === 0xff)
|
||||||
|
return -1;
|
||||||
|
var length = readLength(initialByte & 0x1f);
|
||||||
|
if (length < 0 || (initialByte >> 5) !== majorType)
|
||||||
|
throw "Invalid indefinite length element";
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendUtf16data(utf16data, length) {
|
||||||
|
for (var i = 0; i < length; ++i) {
|
||||||
|
var value = readUint8();
|
||||||
|
if (value & 0x80) {
|
||||||
|
if (value < 0xe0) {
|
||||||
|
value = (value & 0x1f) << 6
|
||||||
|
| (readUint8() & 0x3f);
|
||||||
|
length -= 1;
|
||||||
|
} else if (value < 0xf0) {
|
||||||
|
value = (value & 0x0f) << 12
|
||||||
|
| (readUint8() & 0x3f) << 6
|
||||||
|
| (readUint8() & 0x3f);
|
||||||
|
length -= 2;
|
||||||
|
} else {
|
||||||
|
value = (value & 0x0f) << 18
|
||||||
|
| (readUint8() & 0x3f) << 12
|
||||||
|
| (readUint8() & 0x3f) << 6
|
||||||
|
| (readUint8() & 0x3f);
|
||||||
|
length -= 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value < 0x10000) {
|
||||||
|
utf16data.push(value);
|
||||||
|
} else {
|
||||||
|
value -= 0x10000;
|
||||||
|
utf16data.push(0xd800 | (value >> 10));
|
||||||
|
utf16data.push(0xdc00 | (value & 0x3ff));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeItem() {
|
||||||
|
var initialByte = readUint8();
|
||||||
|
var majorType = initialByte >> 5;
|
||||||
|
var additionalInformation = initialByte & 0x1f;
|
||||||
|
var i;
|
||||||
|
var length;
|
||||||
|
|
||||||
|
if (majorType === 7) {
|
||||||
|
switch (additionalInformation) {
|
||||||
|
case 25:
|
||||||
|
return readFloat16();
|
||||||
|
case 26:
|
||||||
|
return readFloat32();
|
||||||
|
case 27:
|
||||||
|
return readFloat64();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
length = readLength(additionalInformation);
|
||||||
|
if (length < 0 && (majorType < 2 || 6 < majorType))
|
||||||
|
throw "Invalid length";
|
||||||
|
|
||||||
|
switch (majorType) {
|
||||||
|
case 0:
|
||||||
|
return length;
|
||||||
|
case 1:
|
||||||
|
return -1 - length;
|
||||||
|
case 2:
|
||||||
|
if (length < 0) {
|
||||||
|
var elements = [];
|
||||||
|
var fullArrayLength = 0;
|
||||||
|
while ((length = readIndefiniteStringLength(majorType)) >= 0) {
|
||||||
|
fullArrayLength += length;
|
||||||
|
elements.push(readArrayBuffer(length));
|
||||||
|
}
|
||||||
|
var fullArray = new Uint8Array(fullArrayLength);
|
||||||
|
var fullArrayOffset = 0;
|
||||||
|
for (i = 0; i < elements.length; ++i) {
|
||||||
|
fullArray.set(elements[i], fullArrayOffset);
|
||||||
|
fullArrayOffset += elements[i].length;
|
||||||
|
}
|
||||||
|
return fullArray;
|
||||||
|
}
|
||||||
|
return readArrayBuffer(length);
|
||||||
|
case 3:
|
||||||
|
var utf16data = [];
|
||||||
|
if (length < 0) {
|
||||||
|
while ((length = readIndefiniteStringLength(majorType)) >= 0)
|
||||||
|
appendUtf16data(utf16data, length);
|
||||||
|
} else
|
||||||
|
appendUtf16data(utf16data, length);
|
||||||
|
return String.fromCharCode.apply(null, utf16data);
|
||||||
|
case 4:
|
||||||
|
var retArray;
|
||||||
|
if (length < 0) {
|
||||||
|
retArray = [];
|
||||||
|
while (!readBreak())
|
||||||
|
retArray.push(decodeItem());
|
||||||
|
} else {
|
||||||
|
retArray = new Array(length);
|
||||||
|
for (i = 0; i < length; ++i)
|
||||||
|
retArray[i] = decodeItem();
|
||||||
|
}
|
||||||
|
return retArray;
|
||||||
|
case 5:
|
||||||
|
var retObject = {};
|
||||||
|
for (i = 0; i < length || length < 0 && !readBreak(); ++i) {
|
||||||
|
var key = decodeItem();
|
||||||
|
retObject[key] = decodeItem();
|
||||||
|
}
|
||||||
|
return retObject;
|
||||||
|
case 6:
|
||||||
|
return tagger(decodeItem(), length);
|
||||||
|
case 7:
|
||||||
|
switch (length) {
|
||||||
|
case 20:
|
||||||
|
return false;
|
||||||
|
case 21:
|
||||||
|
return true;
|
||||||
|
case 22:
|
||||||
|
return null;
|
||||||
|
case 23:
|
||||||
|
return undefined$1;
|
||||||
|
default:
|
||||||
|
return simpleValue(length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret = decodeItem();
|
||||||
|
if (offset !== data.byteLength)
|
||||||
|
throw "Remaining bytes";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var obj = { encode: encode, decode: decode };
|
||||||
|
|
||||||
|
if (typeof undefined$1 === "function" && undefined$1.amd)
|
||||||
|
undefined$1("cbor/cbor", obj);
|
||||||
|
else if ( module.exports)
|
||||||
|
module.exports = obj;
|
||||||
|
else if (!global.CBOR)
|
||||||
|
global.CBOR = obj;
|
||||||
|
|
||||||
|
})(commonjsGlobal);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This files defines the HoloPlayClient class and Message class.
|
||||||
|
*
|
||||||
|
* Copyright (c) [2019] [Looking Glass Factory]
|
||||||
|
*
|
||||||
|
* @link https://lookingglassfactory.com/
|
||||||
|
* @file This files defines the HoloPlayClient class and Message class.
|
||||||
|
* @author Looking Glass Factory.
|
||||||
|
* @version 0.0.8
|
||||||
|
* @license SEE LICENSE IN LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Polyfill WebSocket for nodejs applications.
|
||||||
|
const WebSocket =
|
||||||
|
typeof window === 'undefined' ? require('ws') : window.WebSocket;
|
||||||
|
|
||||||
|
/** Class representing a client to communicates with the HoloPlayService. */
|
||||||
|
class Client {
|
||||||
|
/**
|
||||||
|
* Establish a client to talk to HoloPlayService.
|
||||||
|
* @constructor
|
||||||
|
* @param {function} initCallback - optional; a function to trigger when
|
||||||
|
* response is received
|
||||||
|
* @param {function} errCallback - optional; a function to trigger when there
|
||||||
|
* is a connection error
|
||||||
|
* @param {function} closeCallback - optional; a function to trigger when the
|
||||||
|
* socket is closed
|
||||||
|
* @param {boolean} debug - optional; default is false
|
||||||
|
* @param {string} appId - optional
|
||||||
|
* @param {boolean} isGreedy - optional
|
||||||
|
* @param {string} oncloseBehavior - optional, can be 'wipe', 'hide', 'none'
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
initCallback, errCallback, closeCallback, debug = false, appId, isGreedy,
|
||||||
|
oncloseBehavior) {
|
||||||
|
this.reqs = [];
|
||||||
|
this.reps = [];
|
||||||
|
this.requestId = this.getRequestId();
|
||||||
|
this.debug = debug;
|
||||||
|
this.isGreedy = isGreedy;
|
||||||
|
this.errCallback = errCallback;
|
||||||
|
this.closeCallback = closeCallback;
|
||||||
|
this.alwaysdebug = false;
|
||||||
|
this.isConnected = false;
|
||||||
|
let initCmd = null;
|
||||||
|
if (appId || isGreedy || oncloseBehavior) {
|
||||||
|
initCmd = new InitMessage(appId, isGreedy, oncloseBehavior, this.debug);
|
||||||
|
} else {
|
||||||
|
if (debug) this.alwaysdebug = true;
|
||||||
|
if (typeof initCallback == 'function') initCmd = new InfoMessage();
|
||||||
|
}
|
||||||
|
this.openWebsocket(initCmd, initCallback);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Send a message over the websocket to HoloPlayService.
|
||||||
|
* @public
|
||||||
|
* @param {Message} msg - message object
|
||||||
|
* @param {integer} timeoutSecs - optional, default is 60 seconds
|
||||||
|
*/
|
||||||
|
sendMessage(msg, timeoutSecs = 60) {
|
||||||
|
if (this.alwaysdebug) msg.cmd.debug = true;
|
||||||
|
let cborData = msg.toCbor();
|
||||||
|
return this.sendRequestObj(cborData, timeoutSecs);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Disconnects from the web socket.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
disconnect() {
|
||||||
|
this.ws.close();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Open a websocket and set handlers
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
openWebsocket(firstCmd = null, initCallback = null) {
|
||||||
|
this.ws =
|
||||||
|
new WebSocket('ws://localhost:11222/driver', ['rep.sp.nanomsg.org']);
|
||||||
|
this.ws.parent = this;
|
||||||
|
this.ws.binaryType = 'arraybuffer';
|
||||||
|
this.ws.onmessage = this.messageHandler;
|
||||||
|
this.ws.onopen = (() => {
|
||||||
|
this.isConnected = true;
|
||||||
|
if (this.debug) {
|
||||||
|
console.log('socket open');
|
||||||
|
}
|
||||||
|
if (firstCmd != null) {
|
||||||
|
this.sendMessage(firstCmd).then(initCallback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.ws.onerror = this.onSocketError;
|
||||||
|
this.ws.onclose = this.onClose;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Send a request object over websocket
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
sendRequestObj(data, timeoutSecs) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let reqObj = {
|
||||||
|
id: this.requestId++,
|
||||||
|
parent: this,
|
||||||
|
payload: data,
|
||||||
|
success: resolve,
|
||||||
|
error: reject,
|
||||||
|
send: function() {
|
||||||
|
if (this.debug)
|
||||||
|
console.log('attemtping to send request with ID ' + this.id);
|
||||||
|
this.timeout = setTimeout(reqObj.send.bind(this), timeoutSecs * 1000);
|
||||||
|
let tmp = new Uint8Array(data.byteLength + 4);
|
||||||
|
let view = new DataView(tmp.buffer);
|
||||||
|
view.setUint32(0, this.id);
|
||||||
|
tmp.set(new Uint8Array(this.payload), 4);
|
||||||
|
this.parent.ws.send(tmp.buffer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.reqs.push(reqObj);
|
||||||
|
reqObj.send();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Handles a message when received
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
messageHandler(event) {
|
||||||
|
console.log('message');
|
||||||
|
let data = event.data;
|
||||||
|
if (data.byteLength < 4) return;
|
||||||
|
let view = new DataView(data);
|
||||||
|
let replyId = view.getUint32(0);
|
||||||
|
if (replyId < 0x80000000) {
|
||||||
|
this.parent.err('bad nng header');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = this.parent.findReqIndex(replyId);
|
||||||
|
if (i == -1) {
|
||||||
|
this.parent.err('got reply that doesn\'t match known request!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let rep = {id: replyId, payload: cbor.decode(data.slice(4))};
|
||||||
|
if (rep.payload.error == 0) {
|
||||||
|
this.parent.reqs[i].success(rep.payload);
|
||||||
|
} else {
|
||||||
|
this.parent.reqs[i].error(rep.payload);
|
||||||
|
}
|
||||||
|
clearTimeout(this.parent.reqs[i].timeout);
|
||||||
|
this.parent.reqs.splice(i, 1);
|
||||||
|
this.parent.reps.push(rep);
|
||||||
|
if (this.debug) {
|
||||||
|
console.log(rep.payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getRequestId() {
|
||||||
|
return Math.floor(this.prng() * (0x7fffffff)) + 0x80000000;
|
||||||
|
}
|
||||||
|
onClose(event) {
|
||||||
|
this.parent.isConnected = false;
|
||||||
|
if (this.parent.debug) {
|
||||||
|
console.log('socket closed');
|
||||||
|
}
|
||||||
|
if (typeof this.parent.closeCallback == 'function')
|
||||||
|
this.parent.closeCallback(event);
|
||||||
|
}
|
||||||
|
onSocketError(error) {
|
||||||
|
if (this.parent.debug) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
if (typeof this.parent.errCallback == 'function') {
|
||||||
|
this.parent.errCallback(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err(errorMsg) {
|
||||||
|
if (this.debug) {
|
||||||
|
console.log('[DRIVER ERROR]' + errorMsg);
|
||||||
|
}
|
||||||
|
// TODO : make this return an event obj rather than a string
|
||||||
|
// if (typeof this.errCallback == 'function')
|
||||||
|
// this.errCallback(errorMsg);
|
||||||
|
}
|
||||||
|
findReqIndex(replyId) {
|
||||||
|
let i = 0;
|
||||||
|
for (; i < this.reqs.length; i++) {
|
||||||
|
if (this.reqs[i].id == replyId) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
prng() {
|
||||||
|
if (this.rng == undefined) {
|
||||||
|
this.rng = generateRng();
|
||||||
|
}
|
||||||
|
return this.rng();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A class to represent messages being sent over to HoloPlay Service */
|
||||||
|
class Message {
|
||||||
|
/**
|
||||||
|
* Construct a barebone message.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
constructor(cmd, bin) {
|
||||||
|
this.cmd = cmd;
|
||||||
|
this.bin = bin;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Convert the class instance to the CBOR format
|
||||||
|
* @public
|
||||||
|
* @returns {CBOR} - cbor object of the message
|
||||||
|
*/
|
||||||
|
toCbor() {
|
||||||
|
return cbor.encode(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Message to init. Extends the base Message class. */
|
||||||
|
class InitMessage extends Message {
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* @param {string} appId - a unique id for app
|
||||||
|
* @param {boolean} isGreedy - will it take over screen
|
||||||
|
* @param {string} oncloseBehavior - can be 'wipe', 'hide', 'none'
|
||||||
|
*/
|
||||||
|
constructor(appId = '', isGreedy = false, onclose = '', debug = false) {
|
||||||
|
let cmd = {'init': {}};
|
||||||
|
if (appId != '') cmd['init'].appid = appId;
|
||||||
|
if (onclose != '') cmd['init'].onclose = onclose;
|
||||||
|
if (isGreedy) cmd['init'].greedy = true;
|
||||||
|
if (debug) cmd['init'].debug = true;
|
||||||
|
super(cmd, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Delete a quilt from HoloPlayService. Extends the base Message class. */
|
||||||
|
class DeleteMessage extends Message {
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* @param {string} name - name of the quilt
|
||||||
|
*/
|
||||||
|
constructor(name = '') {
|
||||||
|
let cmd = {'delete': {'name': name}};
|
||||||
|
super(cmd, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Check if a quilt exist in cache. Extends the base Message class. */
|
||||||
|
class CheckMessage extends Message {
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* @param {string} name - name of the quilt
|
||||||
|
*/
|
||||||
|
constructor(name = '') {
|
||||||
|
let cmd = {'check': {'name': name}};
|
||||||
|
super(cmd, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Wipes the image in Looking Glass and displays the background image */
|
||||||
|
class WipeMessage extends Message {
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* @param {number} targetDisplay - optional, if not provided, default is 0
|
||||||
|
*/
|
||||||
|
constructor(targetDisplay = null) {
|
||||||
|
let cmd = {'wipe': {}};
|
||||||
|
if (targetDisplay != null) cmd['wipe'].targetDisplay = targetDisplay;
|
||||||
|
super(cmd, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Get info from the HoloPlayService */
|
||||||
|
class InfoMessage extends Message {
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
let cmd = {'info': {}};
|
||||||
|
super(cmd, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Get shader uniforms from HoloPlayService */
|
||||||
|
class UniformsMessage extends Message {
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* @param {object}
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
let cmd = {'uniforms': {}};
|
||||||
|
super(cmd, bindata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Get GLSL shader code from HoloPlayService */
|
||||||
|
class ShaderMessage extends Message {
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* @param {object}
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
let cmd = {'shader': {}};
|
||||||
|
super(cmd, bindata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Show a quilt in the Looking Glass with the binary data of quilt provided */
|
||||||
|
class ShowMessage extends Message {
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* @param {object}
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
settings = {vx: 5, vy: 9, aspect: 1.6}, bindata = '',
|
||||||
|
targetDisplay = null) {
|
||||||
|
let cmd = {
|
||||||
|
'show': {
|
||||||
|
'source': 'bindata',
|
||||||
|
'quilt': {'type': 'image', 'settings': settings}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (targetDisplay != null) cmd['show']['targetDisplay'] = targetDisplay;
|
||||||
|
super(cmd, bindata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** extends the base Message class */
|
||||||
|
class CacheMessage extends Message {
|
||||||
|
constructor(
|
||||||
|
name, settings = {vx: 5, vy: 9, aspect: 1.6}, bindata = '',
|
||||||
|
show = false) {
|
||||||
|
let cmd = {
|
||||||
|
'cache': {
|
||||||
|
'show': show,
|
||||||
|
'quilt': {
|
||||||
|
'name': name,
|
||||||
|
'type': 'image',
|
||||||
|
'settings': settings,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
super(cmd, bindata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShowCachedMessage extends Message {
|
||||||
|
constructor(name, targetDisplay = null, settings = null) {
|
||||||
|
let cmd = {'show': {'source': 'cache', 'quilt': {'name': name}}};
|
||||||
|
if (targetDisplay != null) cmd['show']['targetDisplay'] = targetDisplay;
|
||||||
|
if (settings != null) cmd['show']['quilt'].settings = settings;
|
||||||
|
super(cmd, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* helper function */
|
||||||
|
function generateRng() {
|
||||||
|
function xmur3(str) {
|
||||||
|
for (var i = 0, h = 1779033703 ^ str.length; i < str.length; i++)
|
||||||
|
h = Math.imul(h ^ str.charCodeAt(i), 3432918353), h = h << 13 | h >>> 19;
|
||||||
|
return function() {
|
||||||
|
h = Math.imul(h ^ h >>> 16, 2246822507);
|
||||||
|
h = Math.imul(h ^ h >>> 13, 3266489909);
|
||||||
|
return (h ^= h >>> 16) >>> 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function xoshiro128ss(a, b, c, d) {
|
||||||
|
return (() => {
|
||||||
|
var t = b << 9, r = a * 5;
|
||||||
|
r = (r << 7 | r >>> 25) * 9;
|
||||||
|
c ^= a;
|
||||||
|
d ^= b;
|
||||||
|
b ^= c;
|
||||||
|
a ^= d;
|
||||||
|
c ^= t;
|
||||||
|
d = d << 11 | d >>> 21;
|
||||||
|
return (r >>> 0) / 4294967296;
|
||||||
|
})
|
||||||
|
} var state = Date.now();
|
||||||
|
var seed = xmur3(state.toString());
|
||||||
|
return xoshiro128ss(seed(), seed(), seed(), seed());
|
||||||
|
}
|
||||||
|
|
||||||
|
export { CacheMessage, CheckMessage, Client, DeleteMessage, InfoMessage, InitMessage, Message, ShaderMessage, ShowCachedMessage, ShowMessage, UniformsMessage, WipeMessage };
|
||||||
42
shaders/glsl/quiltPass.frag.glsl
Normal file
42
shaders/glsl/quiltPass.frag.glsl
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
precision mediump float;
|
||||||
|
uniform sampler2D quiltTexture;
|
||||||
|
uniform float pitch;
|
||||||
|
uniform float tilt;
|
||||||
|
uniform float center;
|
||||||
|
uniform float invView;
|
||||||
|
uniform float flipImageX;
|
||||||
|
uniform float flipImageY;
|
||||||
|
uniform float subp;
|
||||||
|
uniform float tileX;
|
||||||
|
uniform float tileY;
|
||||||
|
uniform vec2 quiltViewPortion;
|
||||||
|
varying vec2 vUV;
|
||||||
|
|
||||||
|
vec2 texArr(vec3 uvz) {
|
||||||
|
float z = floor(uvz.z * tileX * tileY);
|
||||||
|
float x = (mod(z, tileX) + uvz.x) / tileX;
|
||||||
|
float y = (floor(z / tileX) + uvz.y) / tileY;
|
||||||
|
return vec2(x, y) * quiltViewPortion;
|
||||||
|
}
|
||||||
|
|
||||||
|
float remap(float value, float from1, float to1, float from2, float to2) {
|
||||||
|
return (value - from1) / (to1 - from1) * (to2 - from2) + from2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 rgb[3];
|
||||||
|
vec3 nuv = vec3(vUV.xy, 0.0);
|
||||||
|
|
||||||
|
// Flip UVs if necessary
|
||||||
|
nuv.x = (1.0 - flipImageX) * nuv.x + flipImageX * (1.0 - nuv.x);
|
||||||
|
nuv.y = (1.0 - flipImageY) * nuv.y + flipImageY * (1.0 - nuv.y);
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
nuv.z = (vUV.x + float(i) * subp + vUV.y * tilt) * pitch - center;
|
||||||
|
nuv.z = mod(nuv.z + ceil(abs(nuv.z)), 1.0);
|
||||||
|
nuv.z = (1.0 - invView) * nuv.z + invView * (1.0 - nuv.z);
|
||||||
|
rgb[i] = texture2D(quiltTexture, texArr(vec3(vUV.x, vUV.y, nuv.z)));
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_FragColor = vec4(rgb[0].r, rgb[1].g, rgb[2].b, 1);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user