276 lines
9.3 KiB
JavaScript
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);
|
|
})()
|