view360-canex
Version:
360 integrated viewing solution from inside-out view to outside-in view. It provides user-friendly service by rotating 360 degrees through various user interaction such as motion sensor and touch.
167 lines (138 loc) • 5.9 kB
JavaScript
/*
* Copyright 2015 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var SensorSample = require('./sensor-sample.js');
var MathUtil = require('./math-util.js');
var Util = require('./util.js');
/**
* An implementation of a simple complementary filter, which fuses gyroscope and
* accelerometer data from the 'devicemotion' event.
*
* Accelerometer data is very noisy, but stable over the long term.
* Gyroscope data is smooth, but tends to drift over the long term.
*
* This fusion is relatively simple:
* 1. Get orientation estimates from accelerometer by applying a low-pass filter
* on that data.
* 2. Get orientation estimates from gyroscope by integrating over time.
* 3. Combine the two estimates, weighing (1) in the long term, but (2) for the
* short term.
*/
function ComplementaryFilter(kFilter) {
this.kFilter = kFilter;
// Raw sensor measurements.
this.currentAccelMeasurement = new SensorSample();
this.currentGyroMeasurement = new SensorSample();
this.previousGyroMeasurement = new SensorSample();
// Set default look direction to be in the correct direction.
if (Util.isIOS()) {
this.filterQ = new MathUtil.Quaternion(-1, 0, 0, 1);
} else {
this.filterQ = new MathUtil.Quaternion(1, 0, 0, 1);
}
this.previousFilterQ = new MathUtil.Quaternion();
this.previousFilterQ.copy(this.filterQ);
// Orientation based on the accelerometer.
this.accelQ = new MathUtil.Quaternion();
// Whether or not the orientation has been initialized.
this.isOrientationInitialized = false;
// Running estimate of gravity based on the current orientation.
this.estimatedGravity = new MathUtil.Vector3();
// Measured gravity based on accelerometer.
this.measuredGravity = new MathUtil.Vector3();
// Debug only quaternion of gyro-based orientation.
this.gyroIntegralQ = new MathUtil.Quaternion();
}
ComplementaryFilter.prototype.addAccelMeasurement = function(vector, timestampS) {
this.currentAccelMeasurement.set(vector, timestampS);
};
ComplementaryFilter.prototype.addGyroMeasurement = function(vector, timestampS) {
this.currentGyroMeasurement.set(vector, timestampS);
var deltaT = timestampS - this.previousGyroMeasurement.timestampS;
if (Util.isTimestampDeltaValid(deltaT)) {
this.run_();
}
this.previousGyroMeasurement.copy(this.currentGyroMeasurement);
};
ComplementaryFilter.prototype.run_ = function() {
if (!this.isOrientationInitialized) {
this.accelQ = this.accelToQuaternion_(this.currentAccelMeasurement.sample);
this.previousFilterQ.copy(this.accelQ);
this.isOrientationInitialized = true;
return;
}
var deltaT = this.currentGyroMeasurement.timestampS -
this.previousGyroMeasurement.timestampS;
// Convert gyro rotation vector to a quaternion delta.
var gyroDeltaQ = this.gyroToQuaternionDelta_(this.currentGyroMeasurement.sample, deltaT);
this.gyroIntegralQ.multiply(gyroDeltaQ);
// filter_1 = K * (filter_0 + gyro * dT) + (1 - K) * accel.
this.filterQ.copy(this.previousFilterQ);
this.filterQ.multiply(gyroDeltaQ);
// Calculate the delta between the current estimated gravity and the real
// gravity vector from accelerometer.
var invFilterQ = new MathUtil.Quaternion();
invFilterQ.copy(this.filterQ);
invFilterQ.inverse();
this.estimatedGravity.set(0, 0, -1);
this.estimatedGravity.applyQuaternion(invFilterQ);
this.estimatedGravity.normalize();
this.measuredGravity.copy(this.currentAccelMeasurement.sample);
this.measuredGravity.normalize();
// Compare estimated gravity with measured gravity, get the delta quaternion
// between the two.
var deltaQ = new MathUtil.Quaternion();
deltaQ.setFromUnitVectors(this.estimatedGravity, this.measuredGravity);
deltaQ.inverse();
if (Util.isDebug()) {
console.log('Delta: %d deg, G_est: (%s, %s, %s), G_meas: (%s, %s, %s)',
MathUtil.radToDeg * Util.getQuaternionAngle(deltaQ),
(this.estimatedGravity.x).toFixed(1),
(this.estimatedGravity.y).toFixed(1),
(this.estimatedGravity.z).toFixed(1),
(this.measuredGravity.x).toFixed(1),
(this.measuredGravity.y).toFixed(1),
(this.measuredGravity.z).toFixed(1));
}
// Calculate the SLERP target: current orientation plus the measured-estimated
// quaternion delta.
var targetQ = new MathUtil.Quaternion();
targetQ.copy(this.filterQ);
targetQ.multiply(deltaQ);
// SLERP factor: 0 is pure gyro, 1 is pure accel.
this.filterQ.slerp(targetQ, 1 - this.kFilter);
this.previousFilterQ.copy(this.filterQ);
};
ComplementaryFilter.prototype.getOrientation = function() {
return this.filterQ;
};
ComplementaryFilter.prototype.accelToQuaternion_ = function(accel) {
var normAccel = new MathUtil.Vector3();
normAccel.copy(accel);
normAccel.normalize();
var quat = new MathUtil.Quaternion();
quat.setFromUnitVectors(new MathUtil.Vector3(0, 0, -1), normAccel);
quat.inverse();
return quat;
};
ComplementaryFilter.prototype.gyroToQuaternionDelta_ = function(gyro, dt) {
// Extract axis and angle from the gyroscope data.
var quat = new MathUtil.Quaternion();
var axis = new MathUtil.Vector3();
axis.copy(gyro);
axis.normalize();
quat.setFromAxisAngle(axis, gyro.length() * dt);
return quat;
};
module.exports = ComplementaryFilter;