diff --git a/README.md b/README.md index 2b77752..6025891 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ - [Classic Matrix code](https://rezmason.github.io/matrix) - [3D mode](https://rezmason.github.io/matrix?volumetric=true) +- [Holographic version](https://rezmason.github.io/matrix?version=holoplay) (requires a Looking Glass display; see it in action [here](https://www.youtube.com/watch?v=gwA9hfq1Ing)) - [Matrix Resurrections code (WIP)](https://rezmason.github.io/matrix?version=resurrections) - [Matrix Resurrections's updated code (WIP)](https://rezmason.github.io/matrix?version=updated) - [Operator Matrix code (with ripple effects)](https://rezmason.github.io/matrix?version=operator) diff --git a/js/config.js b/js/config.js index c69af06..7f87111 100644 --- a/js/config.js +++ b/js/config.js @@ -210,7 +210,7 @@ const versions = { holoplay: { ...defaults, ...fonts.resurrections, - numColumns: 40, + numColumns: 20, fallSpeed: 0.35, cycleStyle: "cycleRandomly", cycleSpeed: 0.8, diff --git a/js/regl/lkgHelper.js b/js/regl/lkgHelper.js new file mode 100644 index 0000000..fe0beab --- /dev/null +++ b/js/regl/lkgHelper.js @@ -0,0 +1,89 @@ +import * as HoloPlayCore from "../../lib/holoplaycore.module.js"; + +const recordedDevice = { + 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], +}; + +const getDetectedDevice = new Promise( + (resolve, reject) => + new HoloPlayCore.Client( + (data) => resolve(data.devices?.[0]), + (error) => resolve(null) + ) +); + +const interpretDevice = (device) => { + if (device == null) { + return { enabled: false, tileX: 1, tileY: 1 }; + } + + const fov = 15; + + 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 defaultQuilt = device.defaultQuilt; + + const quiltViewPortion = [ + (Math.floor(defaultQuilt.quiltX / defaultQuilt.tileX) * defaultQuilt.tileX) / defaultQuilt.quiltX, + (Math.floor(defaultQuilt.quiltY / defaultQuilt.tileY) * defaultQuilt.tileY) / defaultQuilt.quiltY, + ]; + + return { + ...defaultQuilt, + ...calibration, + pitch, + tilt, + subp, + + quiltViewPortion, + fov, + enabled: true, + }; +}; + +export default async (useRecordedDevice = false) => { + const detectedDevice = await getDetectedDevice; + if (detectedDevice == null && useRecordedDevice) { + return interpretDevice(recordedDevice); + } + return interpretDevice(detectedDevice); +}; diff --git a/js/regl/main.js b/js/regl/main.js index 1f8f1af..14b6f67 100644 --- a/js/regl/main.js +++ b/js/regl/main.js @@ -7,8 +7,7 @@ import makeStripePass from "./stripePass.js"; import makeImagePass from "./imagePass.js"; import makeResurrectionPass from "./resurrectionPass.js"; import makeQuiltPass from "./quiltPass.js"; - -import * as HoloPlayCore from "../../lib/holoplaycore.module.js"; +import getLKG from "./lkgHelper.js"; const effects = { none: null, @@ -42,6 +41,15 @@ export default async (canvas, config) => { canvas.height = Math.ceil(canvas.clientHeight * config.resolution); }; window.onresize = resize; + if (document.fullscreenEnabled) { + window.onclick = () => { + if (document.fullscreenElement == null) { + document.documentElement.requestFullscreen(); + } else { + document.exitFullscreen(); + } + }; + } resize(); const regl = createREGL({ @@ -51,99 +59,7 @@ export default async (canvas, config) => { optionalExtensions: ["EXT_color_buffer_half_float", "WEBGL_color_buffer_float", "OES_standard_derivatives"], }); - const noDeviceLKG = { tileX: 1, tileY: 1, fov: 90 }; - 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(noDeviceLKG); - 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); - }, (error) => { - console.warn("Holoplay connection error:", error); - resolve(noDeviceLKG); - }); - }); + const lkg = await getLKG(true); // All this takes place in a full screen quad. const fullScreenQuad = makeFullScreenQuad(regl); diff --git a/js/regl/quiltPass.js b/js/regl/quiltPass.js index fac6a5c..c9ecf46 100644 --- a/js/regl/quiltPass.js +++ b/js/regl/quiltPass.js @@ -3,11 +3,7 @@ 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) { + if (!lkg.enabled) { return makePass({ primary: inputs.primary, }); diff --git a/js/regl/rainPass.js b/js/regl/rainPass.js index bdbe96f..9dd2f74 100644 --- a/js/regl/rainPass.js +++ b/js/regl/rainPass.js @@ -163,13 +163,12 @@ export default ({ regl, config, lkg }) => { 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)); + } else if (lkg.enabled) { + mat4.translate(transform, transform, vec3.fromValues(0, 0, -1.1)); + mat4.scale(transform, transform, vec3.fromValues(1, 1, 1)); + mat4.scale(transform, transform, vec3.fromValues(0.15, 0.15, 0.15)); } else { 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(); @@ -186,7 +185,8 @@ export default ({ regl, config, lkg }) => { const [numTileColumns, numTileRows] = [lkg.tileX, lkg.tileY]; const numVantagePoints = numTileRows * numTileColumns; - const tileSize = [Math.floor(w /*lkg.quiltX*/ / numTileColumns), Math.floor(h /*lkg.quiltY*/ / numTileRows)]; + const tileWidth = Math.floor(w / numTileColumns); + const tileHeight = Math.floor(h / numTileRows); vantagePoints.length = 0; for (let row = 0; row < numTileRows; row++) { for (let column = 0; column < numTileColumns; column++) { @@ -199,12 +199,10 @@ export default ({ regl, config, lkg }) => { } 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); + } else if (lkg.enabled) { + mat4.perspective(camera, (Math.PI / 180) * lkg.fov, lkg.quiltAspect, 0.0001, 1000); - mat4.translate(camera, camera, vec3.fromValues(0, 0, -1)); - - const distanceToTarget = 1; // TODO: Get from somewhere else + 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; @@ -213,14 +211,16 @@ export default ({ regl, config, lkg }) => { 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?? + camera[8] = -xOffset / (distanceToTarget * Math.tan((Math.PI / 180) * 0.5 * lkg.fov) * lkg.quiltAspect); // Is this right?? + } else { + mat4.perspective(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000); } const viewport = { - x: column * tileSize[0], - y: row * tileSize[1], - width: tileSize[0], - height: tileSize[1], + x: column * tileWidth, + y: row * tileHeight, + width: tileWidth, + height: tileHeight, }; vantagePoints.push({ camera, viewport }); } @@ -235,14 +235,6 @@ export default ({ regl, config, lkg }) => { framebuffer: output, }); - // 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() }); }