// Copyright (c) 2015, Google Inc. Please see the AUTHORS file for details.
// All rights reserved. Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

part of '../../vector_math.dart';

/// 3D Matrix.
/// Values are stored in column major order.
class Matrix3 {
  final Float32List _m3storage;

  /// The components of the matrix.
  Float32List get storage => _m3storage;

  /// Solve [A] * [x] = [b].
  static void solve2(Matrix3 A, Vector2 x, Vector2 b) {
    final a11 = A.entry(0, 0);
    final a12 = A.entry(0, 1);
    final a21 = A.entry(1, 0);
    final a22 = A.entry(1, 1);
    final bx = b.x - A.storage[6];
    final by = b.y - A.storage[7];
    var det = a11 * a22 - a12 * a21;

    if (det != 0.0) {
      det = 1.0 / det;
    }

    x
      ..x = det * (a22 * bx - a12 * by)
      ..y = det * (a11 * by - a21 * bx);
  }

  /// Solve [A] * [x] = [b].
  static void solve(Matrix3 A, Vector3 x, Vector3 b) {
    final A0x = A.entry(0, 0);
    final A0y = A.entry(1, 0);
    final A0z = A.entry(2, 0);
    final A1x = A.entry(0, 1);
    final A1y = A.entry(1, 1);
    final A1z = A.entry(2, 1);
    final A2x = A.entry(0, 2);
    final A2y = A.entry(1, 2);
    final A2z = A.entry(2, 2);
    double rx, ry, rz;
    double det;

    // Column1 cross Column 2
    rx = A1y * A2z - A1z * A2y;
    ry = A1z * A2x - A1x * A2z;
    rz = A1x * A2y - A1y * A2x;

    // A.getColumn(0).dot(x)
    det = A0x * rx + A0y * ry + A0z * rz;
    if (det != 0.0) {
      det = 1.0 / det;
    }

    // b dot [Column1 cross Column 2]
    final x_ = det * (b.x * rx + b.y * ry + b.z * rz);

    // Column2 cross b
    rx = -(A2y * b.z - A2z * b.y);
    ry = -(A2z * b.x - A2x * b.z);
    rz = -(A2x * b.y - A2y * b.x);
    // Column0 dot -[Column2 cross b (Column3)]
    final y_ = det * (A0x * rx + A0y * ry + A0z * rz);

    // b cross Column 1
    rx = -(b.y * A1z - b.z * A1y);
    ry = -(b.z * A1x - b.x * A1z);
    rz = -(b.x * A1y - b.y * A1x);
    // Column0 dot -[b cross Column 1]
    final z_ = det * (A0x * rx + A0y * ry + A0z * rz);

    x
      ..x = x_
      ..y = y_
      ..z = z_;
  }

  /// Return index in storage for [row], [col] value.
  int index(int row, int col) => (col * 3) + row;

  /// Value at [row], [col].
  double entry(int row, int col) {
    assert((row >= 0) && (row < dimension));
    assert((col >= 0) && (col < dimension));

    return _m3storage[index(row, col)];
  }

  /// Set value at [row], [col] to be [v].
  void setEntry(int row, int col, double v) {
    assert((row >= 0) && (row < dimension));
    assert((col >= 0) && (col < dimension));

    _m3storage[index(row, col)] = v;
  }

  /// New matrix with specified values.
  factory Matrix3(double arg0, double arg1, double arg2, double arg3,
          double arg4, double arg5, double arg6, double arg7, double arg8) =>
      Matrix3.zero()
        ..setValues(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);

  /// New matrix from [values].
  factory Matrix3.fromList(List<double> values) => Matrix3.zero()
    ..setValues(values[0], values[1], values[2], values[3], values[4],
        values[5], values[6], values[7], values[8]);

  /// Constructs a new [Matrix3] filled with zeros.
  Matrix3.zero() : _m3storage = Float32List(9);

  /// Identity matrix.
  factory Matrix3.identity() => Matrix3.zero()..setIdentity();

  /// Copes values from [other].
  factory Matrix3.copy(Matrix3 other) => Matrix3.zero()..setFrom(other);

  /// Constructs a new mat3 from columns.
  factory Matrix3.columns(Vector3 arg0, Vector3 arg1, Vector3 arg2) =>
      Matrix3.zero()..setColumns(arg0, arg1, arg2);

  /// Outer product of [u] and [v].
  factory Matrix3.outer(Vector3 u, Vector3 v) => Matrix3.zero()..setOuter(u, v);

  /// Rotation of [radians] around X axis.
  factory Matrix3.rotationX(double radians) =>
      Matrix3.zero()..setRotationX(radians);

  /// Rotation of [radians] around Y axis.
  factory Matrix3.rotationY(double radians) =>
      Matrix3.zero()..setRotationY(radians);

  /// Rotation of [radians] around Z axis.
  factory Matrix3.rotationZ(double radians) =>
      Matrix3.zero()..setRotationZ(radians);

  /// Sets the matrix with specified values.
  void setValues(double arg0, double arg1, double arg2, double arg3,
      double arg4, double arg5, double arg6, double arg7, double arg8) {
    _m3storage[8] = arg8;
    _m3storage[7] = arg7;
    _m3storage[6] = arg6;
    _m3storage[5] = arg5;
    _m3storage[4] = arg4;
    _m3storage[3] = arg3;
    _m3storage[2] = arg2;
    _m3storage[1] = arg1;
    _m3storage[0] = arg0;
  }

  /// Sets the entire matrix to the column values.
  void setColumns(Vector3 arg0, Vector3 arg1, Vector3 arg2) {
    final arg0Storage = arg0._v3storage;
    final arg1Storage = arg1._v3storage;
    final arg2Storage = arg2._v3storage;
    _m3storage[0] = arg0Storage[0];
    _m3storage[1] = arg0Storage[1];
    _m3storage[2] = arg0Storage[2];
    _m3storage[3] = arg1Storage[0];
    _m3storage[4] = arg1Storage[1];
    _m3storage[5] = arg1Storage[2];
    _m3storage[6] = arg2Storage[0];
    _m3storage[7] = arg2Storage[1];
    _m3storage[8] = arg2Storage[2];
  }

  /// Sets the entire matrix to the matrix in [arg].
  void setFrom(Matrix3 arg) {
    final argStorage = arg._m3storage;
    _m3storage[8] = argStorage[8];
    _m3storage[7] = argStorage[7];
    _m3storage[6] = argStorage[6];
    _m3storage[5] = argStorage[5];
    _m3storage[4] = argStorage[4];
    _m3storage[3] = argStorage[3];
    _m3storage[2] = argStorage[2];
    _m3storage[1] = argStorage[1];
    _m3storage[0] = argStorage[0];
  }

  /// Set this to the outer product of [u] and [v].
  void setOuter(Vector3 u, Vector3 v) {
    final uStorage = u._v3storage;
    final vStorage = v._v3storage;
    _m3storage[0] = uStorage[0] * vStorage[0];
    _m3storage[1] = uStorage[0] * vStorage[1];
    _m3storage[2] = uStorage[0] * vStorage[2];
    _m3storage[3] = uStorage[1] * vStorage[0];
    _m3storage[4] = uStorage[1] * vStorage[1];
    _m3storage[5] = uStorage[1] * vStorage[2];
    _m3storage[6] = uStorage[2] * vStorage[0];
    _m3storage[7] = uStorage[2] * vStorage[1];
    _m3storage[8] = uStorage[2] * vStorage[2];
  }

  /// Set the diagonal of the matrix.
  void splatDiagonal(double arg) {
    _m3storage[0] = arg;
    _m3storage[4] = arg;
    _m3storage[8] = arg;
  }

  /// Set the diagonal of the matrix.
  void setDiagonal(Vector3 arg) {
    _m3storage[0] = arg.storage[0];
    _m3storage[4] = arg.storage[1];
    _m3storage[8] = arg.storage[2];
  }

  /// Sets the upper 2x2 of the matrix to be [arg].
  void setUpper2x2(Matrix2 arg) {
    final argStorage = arg._m2storage;
    _m3storage[0] = argStorage[0];
    _m3storage[1] = argStorage[1];
    _m3storage[3] = argStorage[2];
    _m3storage[4] = argStorage[3];
  }

  /// Returns a printable string
  @override
  String toString() => '[0] ${getRow(0)}\n[1] ${getRow(1)}\n[2] ${getRow(2)}\n';

  /// Dimension of the matrix.
  int get dimension => 3;

  /// Access the element of the matrix at the index [i].
  double operator [](int i) => _m3storage[i];

  /// Set the element of the matrix at the index [i].
  void operator []=(int i, double v) {
    _m3storage[i] = v;
  }

  /// Check if two matrices are the same.
  @override
  bool operator ==(Object other) =>
      (other is Matrix3) &&
      (_m3storage[0] == other._m3storage[0]) &&
      (_m3storage[1] == other._m3storage[1]) &&
      (_m3storage[2] == other._m3storage[2]) &&
      (_m3storage[3] == other._m3storage[3]) &&
      (_m3storage[4] == other._m3storage[4]) &&
      (_m3storage[5] == other._m3storage[5]) &&
      (_m3storage[6] == other._m3storage[6]) &&
      (_m3storage[7] == other._m3storage[7]) &&
      (_m3storage[8] == other._m3storage[8]);

  @override
  int get hashCode => Object.hashAll(_m3storage);

  /// Returns row 0
  Vector3 get row0 => getRow(0);

  /// Returns row 1
  Vector3 get row1 => getRow(1);

  /// Returns row 2
  Vector3 get row2 => getRow(2);

  /// Sets row 0 to [arg]
  set row0(Vector3 arg) => setRow(0, arg);

  /// Sets row 1 to [arg]
  set row1(Vector3 arg) => setRow(1, arg);

  /// Sets row 2 to [arg]
  set row2(Vector3 arg) => setRow(2, arg);

  /// Assigns the [row] of to [arg].
  void setRow(int row, Vector3 arg) {
    final argStorage = arg._v3storage;
    _m3storage[index(row, 0)] = argStorage[0];
    _m3storage[index(row, 1)] = argStorage[1];
    _m3storage[index(row, 2)] = argStorage[2];
  }

  /// Gets the [row] of the matrix
  Vector3 getRow(int row) {
    final r = Vector3.zero();
    final rStorage = r._v3storage;
    rStorage[0] = _m3storage[index(row, 0)];
    rStorage[1] = _m3storage[index(row, 1)];
    rStorage[2] = _m3storage[index(row, 2)];
    return r;
  }

  /// Assigns the [column] of the matrix [arg]
  void setColumn(int column, Vector3 arg) {
    final argStorage = arg._v3storage;
    final entry = column * 3;
    _m3storage[entry + 2] = argStorage[2];
    _m3storage[entry + 1] = argStorage[1];
    _m3storage[entry + 0] = argStorage[0];
  }

  /// Gets the [column] of the matrix
  Vector3 getColumn(int column) {
    final r = Vector3.zero();
    final rStorage = r._v3storage;
    final entry = column * 3;
    rStorage[2] = _m3storage[entry + 2];
    rStorage[1] = _m3storage[entry + 1];
    rStorage[0] = _m3storage[entry + 0];
    return r;
  }

  /// Clone of this.
  Matrix3 clone() => Matrix3.copy(this);

  /// Copy this into [arg].
  Matrix3 copyInto(Matrix3 arg) {
    final argStorage = arg._m3storage;
    argStorage[0] = _m3storage[0];
    argStorage[1] = _m3storage[1];
    argStorage[2] = _m3storage[2];
    argStorage[3] = _m3storage[3];
    argStorage[4] = _m3storage[4];
    argStorage[5] = _m3storage[5];
    argStorage[6] = _m3storage[6];
    argStorage[7] = _m3storage[7];
    argStorage[8] = _m3storage[8];
    return arg;
  }

  /// Returns a new vector or matrix by multiplying this with [arg].
  @pragma('wasm:prefer-inline')
  @pragma('vm:prefer-inline')
  @pragma('dart2js:prefer-inline')
  dynamic operator *(dynamic arg) {
    final Object result;
    if (arg is double) {
      result = scaled(arg);
    } else if (arg is Vector3) {
      result = transformed(arg);
    } else if (arg is Matrix3) {
      result = multiplied(arg);
    } else {
      throw ArgumentError(arg);
    }
    return result;
  }

  /// Returns new matrix after component wise this + [arg]
  Matrix3 operator +(Matrix3 arg) => clone()..add(arg);

  /// Returns new matrix after component wise this - [arg]
  Matrix3 operator -(Matrix3 arg) => clone()..sub(arg);

  /// Returns new matrix -this
  Matrix3 operator -() => clone()..negate();

  /// Zeros this.
  void setZero() {
    _m3storage[0] = 0.0;
    _m3storage[1] = 0.0;
    _m3storage[2] = 0.0;
    _m3storage[3] = 0.0;
    _m3storage[4] = 0.0;
    _m3storage[5] = 0.0;
    _m3storage[6] = 0.0;
    _m3storage[7] = 0.0;
    _m3storage[8] = 0.0;
  }

  /// Makes this into the identity matrix.
  void setIdentity() {
    _m3storage[0] = 1.0;
    _m3storage[1] = 0.0;
    _m3storage[2] = 0.0;
    _m3storage[3] = 0.0;
    _m3storage[4] = 1.0;
    _m3storage[5] = 0.0;
    _m3storage[6] = 0.0;
    _m3storage[7] = 0.0;
    _m3storage[8] = 1.0;
  }

  /// Returns the tranpose of this.
  Matrix3 transposed() => clone()..transpose();

  /// Transpose this.
  void transpose() {
    double temp;
    temp = _m3storage[3];
    _m3storage[3] = _m3storage[1];
    _m3storage[1] = temp;
    temp = _m3storage[6];
    _m3storage[6] = _m3storage[2];
    _m3storage[2] = temp;
    temp = _m3storage[7];
    _m3storage[7] = _m3storage[5];
    _m3storage[5] = temp;
  }

  /// Returns the component wise absolute value of this.
  Matrix3 absolute() {
    final r = Matrix3.zero();
    final rStorage = r._m3storage;
    rStorage[0] = _m3storage[0].abs();
    rStorage[1] = _m3storage[1].abs();
    rStorage[2] = _m3storage[2].abs();
    rStorage[3] = _m3storage[3].abs();
    rStorage[4] = _m3storage[4].abs();
    rStorage[5] = _m3storage[5].abs();
    rStorage[6] = _m3storage[6].abs();
    rStorage[7] = _m3storage[7].abs();
    rStorage[8] = _m3storage[8].abs();
    return r;
  }

  /// Returns the determinant of this matrix.
  double determinant() {
    final x = _m3storage[0] *
        ((_m3storage[4] * _m3storage[8]) - (_m3storage[5] * _m3storage[7]));
    final y = _m3storage[1] *
        ((_m3storage[3] * _m3storage[8]) - (_m3storage[5] * _m3storage[6]));
    final z = _m3storage[2] *
        ((_m3storage[3] * _m3storage[7]) - (_m3storage[4] * _m3storage[6]));
    return x - y + z;
  }

  /// Returns the dot product of row [i] and [v].
  double dotRow(int i, Vector3 v) {
    final vStorage = v._v3storage;
    return _m3storage[i] * vStorage[0] +
        _m3storage[3 + i] * vStorage[1] +
        _m3storage[6 + i] * vStorage[2];
  }

  /// Returns the dot product of column [j] and [v].
  double dotColumn(int j, Vector3 v) {
    final vStorage = v._v3storage;
    return _m3storage[j * 3] * vStorage[0] +
        _m3storage[j * 3 + 1] * vStorage[1] +
        _m3storage[j * 3 + 2] * vStorage[2];
  }

  /// Returns the trace of the matrix. The trace of a matrix is the sum of
  /// the diagonal entries.
  double trace() {
    var t = 0.0;
    t += _m3storage[0];
    t += _m3storage[4];
    t += _m3storage[8];
    return t;
  }

  /// Returns infinity norm of the matrix. Used for numerical analysis.
  double infinityNorm() {
    var norm = 0.0;
    {
      var row_norm = 0.0;
      row_norm += _m3storage[0].abs();
      row_norm += _m3storage[1].abs();
      row_norm += _m3storage[2].abs();
      norm = row_norm > norm ? row_norm : norm;
    }
    {
      var row_norm = 0.0;
      row_norm += _m3storage[3].abs();
      row_norm += _m3storage[4].abs();
      row_norm += _m3storage[5].abs();
      norm = row_norm > norm ? row_norm : norm;
    }
    {
      var row_norm = 0.0;
      row_norm += _m3storage[6].abs();
      row_norm += _m3storage[7].abs();
      row_norm += _m3storage[8].abs();
      norm = row_norm > norm ? row_norm : norm;
    }
    return norm;
  }

  /// Returns relative error between this and [correct]
  double relativeError(Matrix3 correct) {
    final diff = correct - this;
    final correct_norm = correct.infinityNorm();
    final diff_norm = diff.infinityNorm();
    return diff_norm / correct_norm;
  }

  /// Returns absolute error between this and [correct]
  double absoluteError(Matrix3 correct) {
    final this_norm = infinityNorm();
    final correct_norm = correct.infinityNorm();
    final diff_norm = (this_norm - correct_norm).abs();
    return diff_norm;
  }

  /// Invert the matrix. Returns the determinant.
  double invert() => copyInverse(this);

  /// Set this matrix to be the inverse of [arg]
  double copyInverse(Matrix3 arg) {
    final det = arg.determinant();
    if (det == 0.0) {
      setFrom(arg);
      return 0.0;
    }
    final invDet = 1.0 / det;
    final argStorage = arg._m3storage;
    final ix = invDet *
        (argStorage[4] * argStorage[8] - argStorage[5] * argStorage[7]);
    final iy = invDet *
        (argStorage[2] * argStorage[7] - argStorage[1] * argStorage[8]);
    final iz = invDet *
        (argStorage[1] * argStorage[5] - argStorage[2] * argStorage[4]);
    final jx = invDet *
        (argStorage[5] * argStorage[6] - argStorage[3] * argStorage[8]);
    final jy = invDet *
        (argStorage[0] * argStorage[8] - argStorage[2] * argStorage[6]);
    final jz = invDet *
        (argStorage[2] * argStorage[3] - argStorage[0] * argStorage[5]);
    final kx = invDet *
        (argStorage[3] * argStorage[7] - argStorage[4] * argStorage[6]);
    final ky = invDet *
        (argStorage[1] * argStorage[6] - argStorage[0] * argStorage[7]);
    final kz = invDet *
        (argStorage[0] * argStorage[4] - argStorage[1] * argStorage[3]);
    _m3storage[0] = ix;
    _m3storage[1] = iy;
    _m3storage[2] = iz;
    _m3storage[3] = jx;
    _m3storage[4] = jy;
    _m3storage[5] = jz;
    _m3storage[6] = kx;
    _m3storage[7] = ky;
    _m3storage[8] = kz;
    return det;
  }

  /// Set this matrix to be the normal matrix of [arg].
  void copyNormalMatrix(Matrix4 arg) {
    copyInverse(arg.getRotation());
    transpose();
  }

  /// Turns the matrix into a rotation of [radians] around X
  void setRotationX(double radians) {
    final c = math.cos(radians);
    final s = math.sin(radians);
    _m3storage[0] = 1.0;
    _m3storage[1] = 0.0;
    _m3storage[2] = 0.0;
    _m3storage[3] = 0.0;
    _m3storage[4] = c;
    _m3storage[5] = s;
    _m3storage[6] = 0.0;
    _m3storage[7] = -s;
    _m3storage[8] = c;
  }

  /// Turns the matrix into a rotation of [radians] around Y
  void setRotationY(double radians) {
    final c = math.cos(radians);
    final s = math.sin(radians);
    _m3storage[0] = c;
    _m3storage[1] = 0.0;
    _m3storage[2] = -s;
    _m3storage[3] = 0.0;
    _m3storage[4] = 1.0;
    _m3storage[5] = 0.0;
    _m3storage[6] = s;
    _m3storage[7] = 0.0;
    _m3storage[8] = c;
  }

  /// Turns the matrix into a rotation of [radians] around Z
  void setRotationZ(double radians) {
    final c = math.cos(radians);
    final s = math.sin(radians);
    _m3storage[0] = c;
    _m3storage[1] = s;
    _m3storage[2] = 0.0;
    _m3storage[3] = -s;
    _m3storage[4] = c;
    _m3storage[5] = 0.0;
    _m3storage[6] = 0.0;
    _m3storage[7] = 0.0;
    _m3storage[8] = 1.0;
  }

  /// Converts into Adjugate matrix and scales by [scale]
  void scaleAdjoint(double scale) {
    final m00 = _m3storage[0];
    final m01 = _m3storage[3];
    final m02 = _m3storage[6];
    final m10 = _m3storage[1];
    final m11 = _m3storage[4];
    final m12 = _m3storage[7];
    final m20 = _m3storage[2];
    final m21 = _m3storage[5];
    final m22 = _m3storage[8];
    _m3storage[0] = (m11 * m22 - m12 * m21) * scale;
    _m3storage[1] = (m12 * m20 - m10 * m22) * scale;
    _m3storage[2] = (m10 * m21 - m11 * m20) * scale;
    _m3storage[3] = (m02 * m21 - m01 * m22) * scale;
    _m3storage[4] = (m00 * m22 - m02 * m20) * scale;
    _m3storage[5] = (m01 * m20 - m00 * m21) * scale;
    _m3storage[6] = (m01 * m12 - m02 * m11) * scale;
    _m3storage[7] = (m02 * m10 - m00 * m12) * scale;
    _m3storage[8] = (m00 * m11 - m01 * m10) * scale;
  }

  /// Rotates [arg] by the absolute rotation of this
  /// Returns [arg].
  /// Primarily used by AABB transformation code.
  Vector3 absoluteRotate(Vector3 arg) {
    final m00 = _m3storage[0].abs();
    final m01 = _m3storage[3].abs();
    final m02 = _m3storage[6].abs();
    final m10 = _m3storage[1].abs();
    final m11 = _m3storage[4].abs();
    final m12 = _m3storage[7].abs();
    final m20 = _m3storage[2].abs();
    final m21 = _m3storage[5].abs();
    final m22 = _m3storage[8].abs();
    final argStorage = arg._v3storage;
    final x = argStorage[0];
    final y = argStorage[1];
    final z = argStorage[2];
    argStorage[0] = x * m00 + y * m01 + z * m02;
    argStorage[1] = x * m10 + y * m11 + z * m12;
    argStorage[2] = x * m20 + y * m21 + z * m22;
    return arg;
  }

  /// Rotates [arg] by the absolute rotation of this
  /// Returns [arg].
  /// Primarily used by AABB transformation code.
  Vector2 absoluteRotate2(Vector2 arg) {
    final m00 = _m3storage[0].abs();
    final m01 = _m3storage[3].abs();
    final m10 = _m3storage[1].abs();
    final m11 = _m3storage[4].abs();
    final argStorage = arg._v2storage;
    final x = argStorage[0];
    final y = argStorage[1];
    argStorage[0] = x * m00 + y * m01;
    argStorage[1] = x * m10 + y * m11;
    return arg;
  }

  /// Transforms [arg] with this.
  Vector2 transform2(Vector2 arg) {
    final argStorage = arg._v2storage;
    final x_ = (_m3storage[0] * argStorage[0]) +
        (_m3storage[3] * argStorage[1]) +
        _m3storage[6];
    final y_ = (_m3storage[1] * argStorage[0]) +
        (_m3storage[4] * argStorage[1]) +
        _m3storage[7];
    argStorage[0] = x_;
    argStorage[1] = y_;
    return arg;
  }

  /// Scales this by [scale].
  void scale(double scale) {
    _m3storage[0] = _m3storage[0] * scale;
    _m3storage[1] = _m3storage[1] * scale;
    _m3storage[2] = _m3storage[2] * scale;
    _m3storage[3] = _m3storage[3] * scale;
    _m3storage[4] = _m3storage[4] * scale;
    _m3storage[5] = _m3storage[5] * scale;
    _m3storage[6] = _m3storage[6] * scale;
    _m3storage[7] = _m3storage[7] * scale;
    _m3storage[8] = _m3storage[8] * scale;
  }

  /// Create a copy of this and scale it by [scale].
  Matrix3 scaled(double scale) => clone()..scale(scale);

  /// Add [o] to this.
  void add(Matrix3 o) {
    final oStorage = o._m3storage;
    _m3storage[0] = _m3storage[0] + oStorage[0];
    _m3storage[1] = _m3storage[1] + oStorage[1];
    _m3storage[2] = _m3storage[2] + oStorage[2];
    _m3storage[3] = _m3storage[3] + oStorage[3];
    _m3storage[4] = _m3storage[4] + oStorage[4];
    _m3storage[5] = _m3storage[5] + oStorage[5];
    _m3storage[6] = _m3storage[6] + oStorage[6];
    _m3storage[7] = _m3storage[7] + oStorage[7];
    _m3storage[8] = _m3storage[8] + oStorage[8];
  }

  /// Subtract [o] from this.
  void sub(Matrix3 o) {
    final oStorage = o._m3storage;
    _m3storage[0] = _m3storage[0] - oStorage[0];
    _m3storage[1] = _m3storage[1] - oStorage[1];
    _m3storage[2] = _m3storage[2] - oStorage[2];
    _m3storage[3] = _m3storage[3] - oStorage[3];
    _m3storage[4] = _m3storage[4] - oStorage[4];
    _m3storage[5] = _m3storage[5] - oStorage[5];
    _m3storage[6] = _m3storage[6] - oStorage[6];
    _m3storage[7] = _m3storage[7] - oStorage[7];
    _m3storage[8] = _m3storage[8] - oStorage[8];
  }

  /// Negate this.
  void negate() {
    _m3storage[0] = -_m3storage[0];
    _m3storage[1] = -_m3storage[1];
    _m3storage[2] = -_m3storage[2];
    _m3storage[3] = -_m3storage[3];
    _m3storage[4] = -_m3storage[4];
    _m3storage[5] = -_m3storage[5];
    _m3storage[6] = -_m3storage[6];
    _m3storage[7] = -_m3storage[7];
    _m3storage[8] = -_m3storage[8];
  }

  /// Multiply this by [arg].
  void multiply(Matrix3 arg) {
    final m00 = _m3storage[0];
    final m01 = _m3storage[3];
    final m02 = _m3storage[6];
    final m10 = _m3storage[1];
    final m11 = _m3storage[4];
    final m12 = _m3storage[7];
    final m20 = _m3storage[2];
    final m21 = _m3storage[5];
    final m22 = _m3storage[8];
    final argStorage = arg._m3storage;
    final n00 = argStorage[0];
    final n01 = argStorage[3];
    final n02 = argStorage[6];
    final n10 = argStorage[1];
    final n11 = argStorage[4];
    final n12 = argStorage[7];
    final n20 = argStorage[2];
    final n21 = argStorage[5];
    final n22 = argStorage[8];
    _m3storage[0] = (m00 * n00) + (m01 * n10) + (m02 * n20);
    _m3storage[3] = (m00 * n01) + (m01 * n11) + (m02 * n21);
    _m3storage[6] = (m00 * n02) + (m01 * n12) + (m02 * n22);
    _m3storage[1] = (m10 * n00) + (m11 * n10) + (m12 * n20);
    _m3storage[4] = (m10 * n01) + (m11 * n11) + (m12 * n21);
    _m3storage[7] = (m10 * n02) + (m11 * n12) + (m12 * n22);
    _m3storage[2] = (m20 * n00) + (m21 * n10) + (m22 * n20);
    _m3storage[5] = (m20 * n01) + (m21 * n11) + (m22 * n21);
    _m3storage[8] = (m20 * n02) + (m21 * n12) + (m22 * n22);
  }

  /// Create a copy of this and multiply it by [arg].
  Matrix3 multiplied(Matrix3 arg) => clone()..multiply(arg);

  void transposeMultiply(Matrix3 arg) {
    final m00 = _m3storage[0];
    final m01 = _m3storage[1];
    final m02 = _m3storage[2];
    final m10 = _m3storage[3];
    final m11 = _m3storage[4];
    final m12 = _m3storage[5];
    final m20 = _m3storage[6];
    final m21 = _m3storage[7];
    final m22 = _m3storage[8];
    final argStorage = arg._m3storage;
    _m3storage[0] =
        (m00 * argStorage[0]) + (m01 * argStorage[1]) + (m02 * argStorage[2]);
    _m3storage[3] =
        (m00 * argStorage[3]) + (m01 * argStorage[4]) + (m02 * argStorage[5]);
    _m3storage[6] =
        (m00 * argStorage[6]) + (m01 * argStorage[7]) + (m02 * argStorage[8]);
    _m3storage[1] =
        (m10 * argStorage[0]) + (m11 * argStorage[1]) + (m12 * argStorage[2]);
    _m3storage[4] =
        (m10 * argStorage[3]) + (m11 * argStorage[4]) + (m12 * argStorage[5]);
    _m3storage[7] =
        (m10 * argStorage[6]) + (m11 * argStorage[7]) + (m12 * argStorage[8]);
    _m3storage[2] =
        (m20 * argStorage[0]) + (m21 * argStorage[1]) + (m22 * argStorage[2]);
    _m3storage[5] =
        (m20 * argStorage[3]) + (m21 * argStorage[4]) + (m22 * argStorage[5]);
    _m3storage[8] =
        (m20 * argStorage[6]) + (m21 * argStorage[7]) + (m22 * argStorage[8]);
  }

  void multiplyTranspose(Matrix3 arg) {
    final m00 = _m3storage[0];
    final m01 = _m3storage[3];
    final m02 = _m3storage[6];
    final m10 = _m3storage[1];
    final m11 = _m3storage[4];
    final m12 = _m3storage[7];
    final m20 = _m3storage[2];
    final m21 = _m3storage[5];
    final m22 = _m3storage[8];
    final argStorage = arg._m3storage;
    _m3storage[0] =
        (m00 * argStorage[0]) + (m01 * argStorage[3]) + (m02 * argStorage[6]);
    _m3storage[3] =
        (m00 * argStorage[1]) + (m01 * argStorage[4]) + (m02 * argStorage[7]);
    _m3storage[6] =
        (m00 * argStorage[2]) + (m01 * argStorage[5]) + (m02 * argStorage[8]);
    _m3storage[1] =
        (m10 * argStorage[0]) + (m11 * argStorage[3]) + (m12 * argStorage[6]);
    _m3storage[4] =
        (m10 * argStorage[1]) + (m11 * argStorage[4]) + (m12 * argStorage[7]);
    _m3storage[7] =
        (m10 * argStorage[2]) + (m11 * argStorage[5]) + (m12 * argStorage[8]);
    _m3storage[2] =
        (m20 * argStorage[0]) + (m21 * argStorage[3]) + (m22 * argStorage[6]);
    _m3storage[5] =
        (m20 * argStorage[1]) + (m21 * argStorage[4]) + (m22 * argStorage[7]);
    _m3storage[8] =
        (m20 * argStorage[2]) + (m21 * argStorage[5]) + (m22 * argStorage[8]);
  }

  /// Transform [arg] of type [Vector3] using the transformation defined by
  /// this.
  Vector3 transform(Vector3 arg) {
    final argStorage = arg._v3storage;
    final x_ = (_m3storage[0] * argStorage[0]) +
        (_m3storage[3] * argStorage[1]) +
        (_m3storage[6] * argStorage[2]);
    final y_ = (_m3storage[1] * argStorage[0]) +
        (_m3storage[4] * argStorage[1]) +
        (_m3storage[7] * argStorage[2]);
    final z_ = (_m3storage[2] * argStorage[0]) +
        (_m3storage[5] * argStorage[1]) +
        (_m3storage[8] * argStorage[2]);
    arg
      ..x = x_
      ..y = y_
      ..z = z_;
    return arg;
  }

  /// Transform a copy of [arg] of type [Vector3] using the transformation
  /// defined by this. If a [out] parameter is supplied, the copy is stored in
  /// [out].
  Vector3 transformed(Vector3 arg, [Vector3? out]) {
    if (out == null) {
      out = Vector3.copy(arg);
    } else {
      out.setFrom(arg);
    }
    return transform(out);
  }

  /// Copies this into [array] starting at [offset].
  void copyIntoArray(List<num> array, [int offset = 0]) {
    final i = offset;
    array[i + 8] = _m3storage[8];
    array[i + 7] = _m3storage[7];
    array[i + 6] = _m3storage[6];
    array[i + 5] = _m3storage[5];
    array[i + 4] = _m3storage[4];
    array[i + 3] = _m3storage[3];
    array[i + 2] = _m3storage[2];
    array[i + 1] = _m3storage[1];
    array[i + 0] = _m3storage[0];
  }

  /// Copies elements from [array] into this starting at [offset].
  void copyFromArray(List<double> array, [int offset = 0]) {
    final i = offset;
    _m3storage[8] = array[i + 8];
    _m3storage[7] = array[i + 7];
    _m3storage[6] = array[i + 6];
    _m3storage[5] = array[i + 5];
    _m3storage[4] = array[i + 4];
    _m3storage[3] = array[i + 3];
    _m3storage[2] = array[i + 2];
    _m3storage[1] = array[i + 1];
    _m3storage[0] = array[i + 0];
  }

  /// Multiply this to each set of xyz values in [array] starting at [offset].
  List<double> applyToVector3Array(List<double> array, [int offset = 0]) {
    for (var i = 0, j = offset; i < array.length; i += 3, j += 3) {
      final v = Vector3.array(array, j)..applyMatrix3(this);
      array[j] = v.storage[0];
      array[j + 1] = v.storage[1];
      array[j + 2] = v.storage[2];
    }

    return array;
  }

  Vector3 get right {
    final x = _m3storage[0];
    final y = _m3storage[1];
    final z = _m3storage[2];
    return Vector3(x, y, z);
  }

  Vector3 get up {
    final x = _m3storage[3];
    final y = _m3storage[4];
    final z = _m3storage[5];
    return Vector3(x, y, z);
  }

  Vector3 get forward {
    final x = _m3storage[6];
    final y = _m3storage[7];
    final z = _m3storage[8];
    return Vector3(x, y, z);
  }

  /// Is this the identity matrix?
  bool isIdentity() =>
      _m3storage[0] == 1.0 // col 1
      &&
      _m3storage[1] == 0.0 &&
      _m3storage[2] == 0.0 &&
      _m3storage[3] == 0.0 // col 2
      &&
      _m3storage[4] == 1.0 &&
      _m3storage[5] == 0.0 &&
      _m3storage[6] == 0.0 // col 3
      &&
      _m3storage[7] == 0.0 &&
      _m3storage[8] == 1.0;

  /// Is this the zero matrix?
  bool isZero() =>
      _m3storage[0] == 0.0 // col 1
      &&
      _m3storage[1] == 0.0 &&
      _m3storage[2] == 0.0 &&
      _m3storage[3] == 0.0 // col 2
      &&
      _m3storage[4] == 0.0 &&
      _m3storage[5] == 0.0 &&
      _m3storage[6] == 0.0 // col 3
      &&
      _m3storage[7] == 0.0 &&
      _m3storage[8] == 0.0;
}
