324 lines
9.5 KiB
TypeScript
324 lines
9.5 KiB
TypeScript
import * as THREE from 'three';
|
|
// @ts-ignore
|
|
import { UpdateRequest, Update } from "/proto/mmelodies.proto";
|
|
|
|
// Subscribe to the audioprocess event
|
|
let socket: WebSocket | null = null;
|
|
function connect_ws() {
|
|
const url = new URL("/api", location.href);
|
|
|
|
url.protocol = "ws";
|
|
if (location.protocol.startsWith("https")) {
|
|
url.protocol = "wss";
|
|
}
|
|
|
|
const ws = new WebSocket(url);
|
|
ws.binaryType = "arraybuffer";
|
|
|
|
ws.addEventListener("open", () => {
|
|
const message = UpdateRequest.create({ paramRefresh: true });
|
|
const request = UpdateRequest.encode(message).finish();
|
|
if (socket) {
|
|
socket.send(request);
|
|
}
|
|
});
|
|
|
|
ws.addEventListener("error", (event) => {
|
|
console.log(event);
|
|
});
|
|
|
|
ws.addEventListener("close", () => {
|
|
socket = null;
|
|
setTimeout(() => {
|
|
socket = connect_ws();
|
|
}, 2500);
|
|
});
|
|
|
|
// Listen for messages
|
|
ws.addEventListener("message", (event) => {
|
|
const message = Update.decode(new Uint8Array(event.data));
|
|
console.log(message);
|
|
});
|
|
|
|
return ws;
|
|
}
|
|
socket = connect_ws();
|
|
|
|
// Select all range input elements
|
|
const sliders = document.querySelectorAll(
|
|
"input[type=range]",
|
|
) as NodeListOf<HTMLInputElement>;
|
|
|
|
// Add an event listener to each slider
|
|
sliders.forEach((slider, i) => {
|
|
slider.addEventListener("input", function () {
|
|
console.log(`Slider ${this.id} position: ${this.value}`);
|
|
|
|
if (socket) {
|
|
const message = UpdateRequest.create({ paramChanges: [{ id: i, value: this.value }] });
|
|
const buffer = UpdateRequest.encode(message).finish();
|
|
|
|
socket.send(buffer);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Declare a global interface for the window object
|
|
declare global {
|
|
interface Window {
|
|
Accelerometer: any;
|
|
Gyroscope: any;
|
|
AbsoluteOrientationSensor: any;
|
|
}
|
|
}
|
|
|
|
// Show elements hidden by default
|
|
function show(element: HTMLElement) {
|
|
if (element) {
|
|
element.style.display = "flex";
|
|
}
|
|
}
|
|
|
|
|
|
function checkPerms() {
|
|
const permissionsToCheck = ['accelerometer', 'gyroscope', 'magnetometer'];
|
|
const permissionPromises = permissionsToCheck.map(permissionName => {
|
|
if (!navigator.permissions.query) {
|
|
console.log('Permissions API not supported');
|
|
return Promise.resolve(null);
|
|
}
|
|
return navigator.permissions.query({ name: permissionName as any })
|
|
.catch(error => {
|
|
console.log(`Permission not supported: ${permissionName}`);
|
|
return null;
|
|
});
|
|
}).filter(Boolean);
|
|
|
|
Promise.all(permissionPromises).then((results: (PermissionStatus | null)[]) => {
|
|
const warning = document.getElementById("unsupported-warning") as HTMLElement;
|
|
const gyro = document.getElementById("gyroBox") as HTMLElement;
|
|
const accel = document.getElementById("accelBox") as HTMLElement;
|
|
const orient = document.getElementById("orientBox") as HTMLElement;
|
|
let isAccelerometerSupported = "Accelerometer" in window;
|
|
let isGyroscopeSupported = "Gyroscope" in window;
|
|
let isAbsoluteOrientationSensorSupported = "AbsoluteOrientationSensor" in window;
|
|
|
|
if (!results.every((result) => result !== null && result.state === "granted")) {
|
|
show(warning)
|
|
console.log("shown")
|
|
} else {
|
|
if (isAccelerometerSupported && false) {
|
|
console.log("Accelerometer is supported.");
|
|
show(accel)
|
|
sensorStart("Accelerometer", accel, "accel");
|
|
}
|
|
if (isGyroscopeSupported && false) {
|
|
console.log("Gyroscope is supported.");
|
|
show(gyro)
|
|
sensorStart("Gyroscope", gyro, "gyro");
|
|
}
|
|
if (isAccelerometerSupported) {
|
|
console.log("AbsoluteOrientationSensor is supported.");
|
|
show(orient)
|
|
sensorStart("AbsoluteOrientationSensor", orient, "orient");
|
|
}
|
|
|
|
if (!isAccelerometerSupported && !isGyroscopeSupported && !isAbsoluteOrientationSensorSupported) {
|
|
console.log("None of the interfaces are supported.");
|
|
show(warning)
|
|
}
|
|
}
|
|
})
|
|
};
|
|
|
|
function setupThreeJsScene() {
|
|
const sceneSize = new THREE.Vector3(100, 100, 100);
|
|
const renderer = new THREE.WebGLRenderer({ alpha: true });
|
|
renderer.setSize(sceneSize.x, sceneSize.y);
|
|
renderer.setPixelRatio(window.devicePixelRatio / 2.5);
|
|
const scene = new THREE.Scene();
|
|
|
|
const axes = new THREE.AxesHelper(5);
|
|
scene.add(axes);
|
|
|
|
const camera = new THREE.PerspectiveCamera(75, sceneSize.x / sceneSize.y, 0.1, 1000);
|
|
camera.translateOnAxis(scene.up.clone(), 2);
|
|
camera.lookAt(axes.position);
|
|
|
|
return { renderer, scene, axes, camera };
|
|
}
|
|
|
|
function handleSensorReading(
|
|
sensor: any,
|
|
scene: THREE.Scene,
|
|
calibratedDirection: THREE.Vector3 | null,
|
|
currentDirection: THREE.Vector3 | null,
|
|
relativeDirection: THREE.Vector3 | null,
|
|
render: (direction: THREE.Quaternion | null) => void,
|
|
checkbox: HTMLInputElement | null
|
|
) {
|
|
let fps = 0;
|
|
const setFPS = 60;
|
|
sensor.addEventListener('reading', () => {
|
|
// Apply the sensor's quaternion to the vector
|
|
const orientation = new THREE.Quaternion(...sensor.quaternion).normalize();
|
|
// Set the initial orientation if it's not set already
|
|
currentDirection = scene.up.clone().applyQuaternion(orientation);
|
|
if (!calibratedDirection) {
|
|
calibratedDirection = currentDirection;
|
|
console.log("cal", calibratedDirection);
|
|
}
|
|
console.log("debug")
|
|
relativeDirection = calibratedDirection.clone().sub(currentDirection).normalize();
|
|
const relativeOrientation = new THREE.Quaternion().setFromUnitVectors(calibratedDirection, currentDirection);
|
|
render(relativeOrientation);
|
|
});
|
|
|
|
// Check if the button was found
|
|
if (checkbox) {
|
|
// Add a click event listener to the button
|
|
checkbox.addEventListener('change', function () {
|
|
// If sensor is already running, stop it
|
|
if (!this.checked) {
|
|
sensor.stop();
|
|
console.log("stopped");
|
|
fps = 0;
|
|
render(null);
|
|
} else {
|
|
// Otherwise, start the sensor
|
|
sensor.start();
|
|
console.log("started");
|
|
calibratedDirection = null;
|
|
fps = setFPS;
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
console.log(checkbox)
|
|
}
|
|
}
|
|
|
|
type sensorType = "Accelerometer" | "Gyroscope" | "AbsoluteOrientationSensor";
|
|
function sensorStart(sensorName: sensorType, box: HTMLElement, checkboxId: string) {
|
|
const sensorConstructors: { [K in sensorType]: any } = {
|
|
"Accelerometer": window.Accelerometer,
|
|
"Gyroscope": window.Gyroscope,
|
|
"AbsoluteOrientationSensor": window.AbsoluteOrientationSensor
|
|
};
|
|
const SensorConstructor = sensorConstructors[sensorName];
|
|
if (SensorConstructor) {
|
|
const sensor = new SensorConstructor({ frequency: 60, referenceFrame: 'device' });
|
|
|
|
// Set up Three.js scene
|
|
const { renderer, scene, axes, camera } = setupThreeJsScene();
|
|
box.appendChild(renderer.domElement)
|
|
|
|
// Define render function
|
|
const render = (direction: THREE.Quaternion | null) => {
|
|
if (!direction) {
|
|
direction = new THREE.Quaternion().identity();
|
|
}
|
|
|
|
axes.setRotationFromQuaternion(direction);
|
|
renderer.render(scene, camera);
|
|
}
|
|
|
|
// Initialize default orientation
|
|
let calibratedDirection: THREE.Vector3 | null = null;
|
|
let currentDirection: THREE.Vector3 | null = null;
|
|
let relativeDirection: THREE.Vector3 | null = null;
|
|
|
|
var checkbox = document.getElementById(checkboxId) as HTMLInputElement;
|
|
|
|
// Handle sensor readings
|
|
handleSensorReading(sensor, scene, calibratedDirection, currentDirection, relativeDirection, render, checkbox);
|
|
render(null);
|
|
|
|
// Render the first frame
|
|
} else {
|
|
console.log(`No constructor found for sensor: ${sensorName}`);
|
|
}
|
|
}
|
|
|
|
|
|
function gyroStart() {
|
|
// Create an AbsoluteOrientationSensor object
|
|
const sensor = new window.AbsoluteOrientationSensor({ frequency: 60, referenceFrame: 'device' });
|
|
|
|
|
|
// Create a renderer
|
|
const sceneSize = new THREE.Vector3(100, 100, 100);
|
|
const renderer = new THREE.WebGLRenderer({ alpha: true });
|
|
renderer.setSize(sceneSize.x, sceneSize.y);
|
|
renderer.setPixelRatio(window.devicePixelRatio / 2.5);
|
|
const scene = new THREE.Scene();
|
|
|
|
const axes = new THREE.AxesHelper(5);
|
|
scene.add(axes);
|
|
|
|
const camera = new THREE.PerspectiveCamera(75, sceneSize.x / sceneSize.y, 0.1, 1000);
|
|
camera.translateOnAxis(scene.up.clone(), 2);
|
|
camera.lookAt(axes.position);
|
|
|
|
|
|
// Get the checkbox element
|
|
const gyroBox = document.getElementById("gyroBox") as HTMLElement;
|
|
const checkbox = document.getElementById("gyro") as HTMLInputElement;
|
|
(gyroBox || document.body).appendChild(renderer.domElement);
|
|
show(gyroBox);
|
|
|
|
// Create an arrow
|
|
|
|
// Profiling
|
|
let sampleCount = 0; // Number of frames since the last second
|
|
let sampleSec = 0; // Time last second was recorded for a frame
|
|
|
|
// Animation loop
|
|
let setFPS = 60; // Desired framerate
|
|
let fps = 0; // Startup fps
|
|
|
|
// Initialize default orientation
|
|
let calibratedDirection: THREE.Vector3 | null;
|
|
let currentDirection: THREE.Vector3 | null;
|
|
let relativeDirection: THREE.Vector3 | null;
|
|
|
|
const render = (direction: THREE.Quaternion | null) => {
|
|
if (!direction) {
|
|
direction = new THREE.Quaternion().identity();
|
|
}
|
|
|
|
axes.setRotationFromQuaternion(direction);
|
|
|
|
renderer.render(scene, camera);
|
|
}
|
|
|
|
// Add an event listener for sensor readings
|
|
sensor.addEventListener('reading', () => {
|
|
// Apply the sensor's quaternion to the vector
|
|
const orientation = new THREE.Quaternion(...sensor.quaternion).normalize();
|
|
// Set the initial orientation if it's not set already
|
|
currentDirection = scene.up.clone().applyQuaternion(orientation);
|
|
if (!calibratedDirection) {
|
|
calibratedDirection = currentDirection;
|
|
console.log("cal", calibratedDirection);
|
|
}
|
|
relativeDirection = calibratedDirection.clone().sub(currentDirection).normalize();
|
|
const relativeOrientation = new THREE.Quaternion().setFromUnitVectors(calibratedDirection, currentDirection);
|
|
render(relativeOrientation);
|
|
// Profiling
|
|
let currTime = new Date().getTime();
|
|
if (sampleSec < currTime - 1000) {
|
|
console.log("Samples:", sampleCount)
|
|
sampleCount = 0;
|
|
sampleSec = currTime;
|
|
}
|
|
else { sampleCount++; }
|
|
});
|
|
|
|
|
|
|
|
// Render the first frame
|
|
render(null);
|
|
}
|
|
|
|
checkPerms(); |