trisquel-icecat/icecat/extensions/gnu/jsr@javascriptrestrictor/wrappingS-SENSOR-ORIENT.js

276 lines
9.3 KiB
JavaScript

/** \file
* \brief Wrappers for the AbsoluteOrientationSensor and RelativeOrientationSensor
*
* \see https://www.w3.org/TR/orientation-sensor
* \see https://www.w3.org/TR/orientation-sensor/#absoluteorientationsensor-model
* \see https://www.w3.org/TR/orientation-sensor/#relativeorientationsensor-model
*
* \author Copyright (C) 2021 Radek Hranicky
*
* \license SPDX-License-Identifier: GPL-3.0-or-later
*/
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
/** \file
* \ingroup wrappers
*
* MOTIVATION
* Device orientation sensors can be easily used for fingerprinting. As it highly
* unlikely that two devices visiting the same site will be oriented exactly
* the same, the orientation itself can serve as a fingerprint.
*
*
* WRAPPING
* AbsoluteOrientationSensor returns a quaterion decribing the physical
* orientation of the device in relation to the Earth's reference coordinate
* system. The faked orientation of the device is saved inside the "orient"
* global variable that is accessible to all wrappers. The value is chosen
* pseudorandomly from the domain hash. The wrappper supports possible change
* of orientation. With each reading, it loads the "orient"'s contents,
* converts the rotation matrix to a quaternion that is returned by the wrapped
* getter.
*
* RelativeOrientationSensor also describes the orientation, but without
* regard to the Earth's reference coordinate system. We suppose the coordinate
* system is chosen at the beginning of the sensor instance creation.
* As we observed, no matter how the device is oriented, there is always a slight
* difference from the AbsoluteOrientationSensor's in at least one axis.
* When the device moves, both sensors' readings change. But their difference
* should be always constant. And thus, we pseudorandomly generate a deviation
* from the Earth's reference coordinate system. And for each reading, we
* take the values from the fake AbsoluteOrientationSensor and modify them
* by the constant deviation.
*
* POSSIBLE IMPROVEMENTS
* Study the supported coordinate systems of the RelativeOrientationSensor
* and modify the wrapper behavior if needed.
*/
/*
* Create private namespace
*/
(function() {
/*
* \brief Initialization of data for storing sensor readings
*/
var init_data = `
var currentReading = currentReading || {quaternion: null, fake_quaternion: null, fake_quaternion_rel: null, timestamp: null};
var previousReading = previousReading || {quaternion: null, fake_quaternion: null, fake_quaternion_rel: null, timestamp: null};
var debugMode = false;
const TWOPI = 2 * Math.PI;
`;
/*
* \brief Property getters of the original sensor object
*/
var orig_getters = `
var origGetQuaternion = Object.getOwnPropertyDescriptor(OrientationSensor.prototype, "quaternion").get;
var origGetTimestamp = Object.getOwnPropertyDescriptor(Sensor.prototype, "timestamp").get;
`;
/*
* \brief Convert a given 3D rotation matrix to quaternion
*
* \param Rotation matrix
*/
function matrixToQuaternion(rot) {
var q = {x: null, y: null, z: null, w: null};
var m;
if (rot[2][2] < 0) {
if (rot[0][0] > rot[1][1]) {
m = 1 + rot[0][0] -rot[1][1] -rot[2][2];
q.x = m;
q.y = rot[0][1]+rot[1][0];
q.z = rot[2][0]+rot[0][2];
q.w = rot[1][2]-rot[2][1];
} else {
m = 1 -rot[0][0] + rot[1][1] -rot[2][2];
q.x = rot[0][1]+rot[1][0];
q.y = m;
q.z = rot[1][2]+rot[2][1];
q.w = rot[2][0]-rot[0][2];
}
} else {
if (rot[0][0] < -rot[1][1]) {
m = 1 -rot[0][0] -rot[1][1] + rot[2][2];
q.x = rot[2][0]+rot[0][2];
q.y = rot[1][2]+rot[2][1];
q.z = m;
q.w = rot[0][1]-rot[1][0];
} else {
m = 1 + rot[0][0] + rot[1][1] + rot[2][2];
q.x = rot[1][2]-rot[2][1];
q.y = rot[2][0]-rot[0][2];
q.z = rot[0][1]-rot[1][0];
q.w = m;
}
}
var multiplier = 0.5 / Math.sqrt(m);
q.x *= multiplier;
q.y *= multiplier;
q.z *= multiplier;
q.w *= multiplier;
return q;
}
/*
* \brief The fake quaternion generator class
* Note: Requires "orient" global var to be set.
*/
class QuaternionGenerator {
constructor() {
this.DEVIATION_MIN = 0;
this.DEVIATION_MAX = (Math.PI / 2) / 90 * 10; // 10°
this.quaternion = null;
this.quaternion_rel = null;
this.yawDeviation = this.generateDeviation();
this.pitchDeviation = this.generateDeviation();
this.rollDeviation = this.generateDeviation();
}
/*
* \brief Generates the rotation deviation
*/
generateDeviation() {
var devi = sen_prng() * (this.DEVIATION_MAX - this.DEVIATION_MIN) + this.DEVIATION_MIN;
devi *= Math.round(sen_prng()) ? 1 : -1;
return devi;
}
/*
* \brief Updates the fake quaternions
*
* \param Current timestamp from the sensor object
*/
update(t) {
// Calculate quaternion for absolute orientation
var rotMat = orient.rotMat; // Get the device rotation matrix
var q = matrixToQuaternion(rotMat);
this.quaternion = [
fixedNumber(q.x, 3),
fixedNumber(q.y, 3),
fixedNumber(q.z, 3),
fixedNumber(q.w, 3)
];
// Calculate quaternion for relative orientation
var relYaw = (orient.yaw + this.yawDeviation) % TWOPI;
var relPitch = (orient.pitch + this.pitchDeviation) % TWOPI;
var relRoll = (orient.roll + this.rollDeviation) % TWOPI;
var relMat = calculateRotationMatrix(relYaw, relPitch, relRoll);
var qr = matrixToQuaternion(relMat);
this.quaternion_rel = [
fixedNumber(qr.x, 3),
fixedNumber(qr.y, 3),
fixedNumber(qr.z, 3),
fixedNumber(qr.w, 3)
];
}
}
/*
* \brief Updates the stored (both real and fake) sensor readings
* according to the data from the sensor object.
*
* \param The sensor object
*/
function updateReadings(sensorObject) {
// We need the original reading's timestamp to see if it differs
// from the previous sample. If so, we need to update the faked quaternion
let previousTimestamp = previousReading.timestamp;
let currentTimestamp = origGetTimestamp.call(sensorObject);
if (debugMode) {
// [!] Debug mode: overriding timestamp
// This allows test suites to set a custom timestamp externally
// by modifying the property of the Magnetometer object directly.
currentTimestamp = sensorObject.timestamp;
}
if (currentTimestamp === previousTimestamp) {
// No new reading, nothing to update
return;
}
// Rotate the readings: previous <- current
previousReading = JSON.parse(JSON.stringify(currentReading));
// Update current reading
// NOTE: Original values are also stored for possible future use
// in improvements of the magnetic field generator
currentReading.orig_quaterion = origGetQuaternion.call(sensorObject);
currentReading.timestamp = currentTimestamp;
quaternionGenerator.update(currentTimestamp);
currentReading.fake_quaternion = quaternionGenerator.quaternion;
currentReading.fake_quaternion_rel = quaternionGenerator.quaternion_rel;
if (debugMode) {
}
}
/*
* \brief Initializes the related generators
*/
var generators = `
// Initialize the quaternion generator, if not initialized before
var quaternionGenerator = quaternionGenerator || new QuaternionGenerator();
`;
var helping_functions = sensorapi_prng_functions + device_orientation_functions
+ matrixToQuaternion + QuaternionGenerator + updateReadings;
var hc = init_data + orig_getters + helping_functions + generators;
var wrappers = [
{
parent_object: "OrientationSensor.prototype",
parent_object_property: "quaternion",
wrapped_objects: [],
helping_code: hc,
post_wrapping_code: [
{
code_type: "object_properties",
parent_object: "OrientationSensor.prototype",
parent_object_property: "quaternion",
wrapped_objects: [],
/** \brief replaces OrientationSensor.quaternion getter to return a faked value
*/
wrapped_properties: [
{
property_name: "get",
property_value: `
function() {
updateReadings(this);
if (this.__proto__.constructor.name === 'AbsoluteOrientationSensor') {
// AbsoluteOrientationSensor
return currentReading.fake_quaternion;
} else {
// RelativeOrientationSensor
return currentReading.fake_quaternion_rel;
}
}`,
},
],
}
],
}
]
add_wrappers(wrappers);
})()