Home Reference Source Repository

src/geometry/coordinates.js

/**
 * @fileOverview Coordinate systems conversions. openGL, SOFA, and Spat4 (Ircam).
 *
 * @author Jean-Philippe.Lambert@ircam.fr
 * @copyright 2015-2016 IRCAM, Paris, France
 * @license BSD-3-Clause
 */

import degree from './degree';

/**
 * Coordinates as an array of 3 values:
 * [x, y, z] or [azimuth, elevation, distance], depending on system
 *
 * @typedef {vec3} Coordinates
 */

/**
 * Coordinate system: `gl`, `sofaCartesian`, `sofaSpherical`,
 * `spat4Cartesian`, or `spat4Spherical`.
 *
 * @typedef {String} CoordinateSystem
 */

// ----------------------------- SOFA

/**
 * SOFA cartesian coordinate system: `sofaCartesian`.
 *
 * SOFA distances are in metres.
 *
 * <pre>
 *
 * SOFA          +z  +x             openGL    +y
 *                | /                          |
 *                |/                           |
 *         +y ----o                            o---- +x
 *                                            /
 *                                           /
 *                                          +z
 *
 * SOFA.x = -openGL.z               openGL.x = -SOFA.y
 * SOFA.y = -openGL.x               openGL.y =  SOFA.z
 * SOFA.z =  openGL.y               openGL.z = -SOFA.x
 *
 * </pre>
 *
 * @typedef {Coordinates} SofaCartesian
 */

/**
 * SOFA spherical coordinate system:  `sofaSpherical`.
 *
 * SOFA angles are in degrees.
 *
 * <pre>
 *
 * SOFA.azimuth = atan2(SOFA.y, SOFA.x)
 * SOFA.elevation = atan2(SOFA.z, sqrt(SOFA.x * SOFA.x + SOFA.y * SOFA.y) );
 * SOFA.distance = sqrt(SOFA.x * SOFA.x + SOFA.y * SOFA.y + SOFA.z * SOFA.z)
 *
 * </pre>
 *
 * @typedef {Coordinates} SofaSpherical
 */

/**
 * Convert SOFA cartesian coordinates to openGL.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @returns {Coordinates} out
 */
export function sofaCartesianToGl(out, a) {
  // copy to handle in-place
  const x = a[0];
  const y = a[1];
  const z = a[2];

  out[0] = 0 - y;
  out[1] = z;
  out[2] = 0 - x;

  return out;
}

/**
 * Convert openGL coordinates to SOFA cartesian.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @returns {Coordinates} out
 */
export function glToSofaCartesian(out, a) {
  // copy to handle in-place
  const x = a[0];
  const y = a[1];
  const z = a[2];

  out[0] = 0 - z;
  out[1] = 0 - x;
  out[2] = y;

  return out;
}

/**
 * Convert SOFA cartesian coordinates to SOFA spherical.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @returns {Coordinates} out
 */
export function sofaCartesianToSofaSpherical(out, a) {
  // copy to handle in-place
  const x = a[0];
  const y = a[1];
  const z = a[2];

  const x2y2 = x * x + y * y;

  // from [-180, 180] to [0, 360);
  out[0] = (degree.atan2(y, x) + 360) % 360;

  out[1] = degree.atan2(z, Math.sqrt(x2y2) );
  out[2] = Math.sqrt(x2y2 + z * z);

  return out;
}

/**
 * Convert SOFA spherical coordinates to SOFA spherical.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @returns {Coordinates} out
 */
export function sofaSphericalToSofaCartesian(out, a) {
  // copy to handle in-place
  const azimuth = a[0];
  const elevation = a[1];
  const distance = a[2];

  const cosE = degree.cos(elevation);
  out[0] = distance * cosE * degree.cos(azimuth); // SOFA.x
  out[1] = distance * cosE * degree.sin(azimuth); // SOFA.y
  out[2] = distance * degree.sin(elevation); // SOFA.z

  return out;
}

/**
 * Convert SOFA spherical coordinates to openGL.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @returns {Coordinates} out
 */
export function sofaSphericalToGl(out, a) {
  // copy to handle in-place
  const azimuth = a[0];
  const elevation = a[1];
  const distance = a[2];

  const cosE = degree.cos(elevation);
  out[0] = 0 - distance * cosE * degree.sin(azimuth); // -SOFA.y
  out[1] = distance * degree.sin(elevation); // SOFA.z
  out[2] = 0 - distance * cosE * degree.cos(azimuth); // -SOFA.x

  return out;
}

/**
 * Convert openGL coordinates to SOFA spherical.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @returns {Coordinates} out
 */
export function glToSofaSpherical(out, a) {
  // copy to handle in-place
  // difference to avoid generating -0 out of 0
  const x = 0 - a[2]; // -openGL.z
  const y = 0 - a[0]; // -openGL.x
  const z = a[1]; // openGL.y

  const x2y2 = x * x + y * y;

  // from [-180, 180] to [0, 360);
  out[0] = (degree.atan2(y, x) + 360) % 360;

  out[1] = degree.atan2(z, Math.sqrt(x2y2) );
  out[2] = Math.sqrt(x2y2 + z * z);

  return out;
}

/**
 * Convert coordinates to SOFA cartesian.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @param {CoordinateSystem} system
 * @returns {Coordinates} out
 * @throws {Error} when the system is unknown.
 */
export function sofaToSofaCartesian(out, a, system) {
  switch (system) {
    case 'sofaCartesian':
      out[0] = a[0];
      out[1] = a[1];
      out[2] = a[2];
      break;

    case 'sofaSpherical':
      sofaSphericalToSofaCartesian(out, a);
      break;

    default:
      throw new Error('Bad coordinate system');
  }
  return out;
}

// ---------------- Spat4

/**
 * Spat4 cartesian coordinate system: `spat4Cartesian`.
 *
 * Spat4 distances are in metres.
 *
 * <pre>
 *
 * Spat4         +z  +y             openGL    +y
 *                | /                          |
 *                |/                           |
 *                o---- +x                     o---- +x
 *                                            /
 *                                           /
 *                                         +z
 *
 * Spat4.x =  openGL.x               openGL.x =  Spat4.x
 * Spat4.y = -openGL.z               openGL.y =  Spat4.z
 * Spat4.z =  openGL.y               openGL.z = -Spat4.y
 *
 * </pre>
 *
 * @typedef {Coordinates} Spat4Cartesian
 */

/**
 * Spat4 spherical coordinate system: `spat4Spherical`.
 *
 * Spat4 angles are in degrees.
 *
 * <pre>
 *
 * Spat4.azimuth = atan2(Spat4.x, Spat4.y)
 * Spat4.elevation = atan2(Spat4.z, sqrt(Spat4.x * Spat4.x + Spat4.y * Spat4.y) );
 * Spat4.distance = sqrt(Spat4.x * Spat4.x + Spat4.y * Spat4.y + Spat4.z * Spat4.z)
 *
 * </pre>
 *
 * @typedef {Coordinates} Spat4Spherical
 */

/**
 * Convert Spat4 cartesian coordinates to openGL.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @returns {Coordinates} out
 */
export function spat4CartesianToGl(out, a) {
  // copy to handle in-place
  const x = a[0];
  const y = a[1];
  const z = a[2];

  out[0] = x;
  out[1] = z;
  out[2] = 0 - y;

  return out;
}

/**
 * Convert openGL coordinates to Spat4 cartesian.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @returns {Coordinates} out
 */
export function glToSpat4Cartesian(out, a) {
  // copy to handle in-place
  const x = a[0];
  const y = a[1];
  const z = a[2];

  out[0] = x;
  out[1] = 0 - z;
  out[2] = y;

  return out;
}

/**
 * Convert Spat4 cartesian coordinates to Spat4 spherical.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @returns {Coordinates} out
 */
export function spat4CartesianToSpat4Spherical(out, a) {
  // copy to handle in-place
  const x = a[0];
  const y = a[1];
  const z = a[2];

  const x2y2 = x * x + y * y;

  out[0] = degree.atan2(x, y);
  out[1] = degree.atan2(z, Math.sqrt(x2y2) );
  out[2] = Math.sqrt(x2y2 + z * z);

  return out;
}

/**
 * Convert Spat4 spherical coordinates to Spat4 spherical.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @returns {Coordinates} out
 */
export function spat4SphericalToSpat4Cartesian(out, a) {
  // copy to handle in-place
  const azimuth = a[0];
  const elevation = a[1];
  const distance = a[2];

  const cosE = degree.cos(elevation);
  out[0] = distance * cosE * degree.sin(azimuth); // Spat4.x
  out[1] = distance * cosE * degree.cos(azimuth); // Spat4.y
  out[2] = distance * degree.sin(elevation); // Spat4.z

  return out;
}

/**
 * Convert Spat4 spherical coordinates to openGL.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @returns {Coordinates} out
 */
export function spat4SphericalToGl(out, a) {
  // copy to handle in-place
  const azimuth = a[0];
  const elevation = a[1];
  const distance = a[2];

  const cosE = degree.cos(elevation);
  out[0] = distance * cosE * degree.sin(azimuth); // Spat4.x
  out[1] = distance * degree.sin(elevation); // Spat4.z
  out[2] = 0 - distance * cosE * degree.cos(azimuth); // -Spat4.y

  return out;
}

/**
 * Convert openGL coordinates to Spat4 spherical.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @returns {Coordinates} out
 */
export function glToSpat4Spherical(out, a) {
  // copy to handle in-place
  // difference to avoid generating -0 out of 0
  const x = a[0]; // openGL.x
  const y = 0 - a[2]; // -openGL.z
  const z = a[1]; // openGL.y

  const x2y2 = x * x + y * y;

  out[0] = degree.atan2(x, y);
  out[1] = degree.atan2(z, Math.sqrt(x2y2) );
  out[2] = Math.sqrt(x2y2 + z * z);

  return out;
}

// ---------------- named coordinate systems

/**
 * Get the coordinate system general type (cartesian or spherical).
 *
 * @param {String} system
 * @returns {String} 'cartesian' or 'spherical', if `system` if of cartesian
 * or spherical type.
 */
export function systemType(system) {
  let type;
  if (system === 'sofaCartesian'
     || system === 'spat4Cartesian'
     || system === 'gl') {
    type = 'cartesian';
  } else if (system === 'sofaSpherical'
             || system === 'spat4Spherical') {
    type = 'spherical';
  } else {
    throw new Error(`Unknown coordinate system type ${system}`);
  }
  return type;
}

/**
 * Convert coordinates to openGL.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @param {CoordinateSystem} system
 * @returns {Coordinates} out
 * @throws {Error} when the system is unknown.
 */
export function systemToGl(out, a, system) {
  switch (system) {
    case 'gl':
      out[0] = a[0];
      out[1] = a[1];
      out[2] = a[2];
      break;

    case 'sofaCartesian':
      sofaCartesianToGl(out, a);
      break;

    case 'sofaSpherical':
      sofaSphericalToGl(out, a);
      break;

    case 'spat4Cartesian':
      spat4CartesianToGl(out, a);
      break;

    case 'spat4Spherical':
      spat4SphericalToGl(out, a);
      break;

    default:
      throw new Error('Bad coordinate system');
  }
  return out;
}

/**
 * Convert openGL coordinates to other system.
 *
 * @param {Coordinates} out in-place if out === a.
 * @param {Coordinates} a
 * @param {CoordinateSystem} system
 * @returns {Coordinates} out
 * @throws {Error} when the system is unknown.
 */
export function glToSystem(out, a, system) {
  switch (system) {
    case 'gl':
      out[0] = a[0];
      out[1] = a[1];
      out[2] = a[2];
      break;

    case 'sofaCartesian':
      glToSofaCartesian(out, a);
      break;

    case 'sofaSpherical':
      glToSofaSpherical(out, a);
      break;

    case 'spat4Cartesian':
      glToSpat4Cartesian(out, a);
      break;

    case 'spat4Spherical':
      glToSpat4Spherical(out, a);
      break;

    default:
      throw new Error('Bad coordinate system');
  }
  return out;
}

export default {
  glToSofaCartesian,
  glToSofaSpherical,
  glToSpat4Cartesian,
  glToSpat4Spherical,
  glToSystem,
  sofaCartesianToGl,
  sofaCartesianToSofaSpherical,
  sofaSphericalToGl,
  sofaSphericalToSofaCartesian,
  sofaToSofaCartesian,
  spat4CartesianToGl,
  spat4CartesianToSpat4Spherical,
  spat4SphericalToGl,
  spat4SphericalToSpat4Cartesian,
  systemToGl,
  systemType,
};