// 对称 3×3 矩阵的特征分解。
//
// 两个地方要用:转动惯量的主惯量(质量属性)、有向包围盒的主轴(PCA)。
// 都是「对称 3×3 矩阵求特征值/特征向量」,抽出来共用,不写两遍。
//
// 对称矩阵的特征值一定是实数,所以有闭式解(Cardano 公式),不用迭代。
// 非对称的矩阵没这个性质,那就不在这个文件的职责范围里了。

///|
/// 对称 3×3 矩阵的特征值,**从大到小**。
///
/// 输入按行排列的 9 个数([0..2] 第一行,[3..5] 第二行,[6..8] 第三行),
/// 只用得到上三角——对称矩阵的下三角是重复的。
pub fn symmetric_eigenvalues(m : FixedArray[Double]) -> FixedArray[Double] {
  let a = m[0]
  let b = m[1]
  let c = m[2]
  let d = m[4]
  let e = m[5]
  let f = m[8]

  // 特征多项式:λ³ − p λ² + q λ − r = 0
  let p = a + d + f
  let q = a * d + d * f + f * a - b * b - c * c - e * e
  let r = a * (d * f - e * e) - b * (b * f - c * e) + c * (b * e - c * d)

  // 消掉二次项:λ = x + p/3,得到 x³ + p2·x + q2 = 0
  //
  // 常数项是 s³ − p·s² + q·s − r(s = p/3)展开的结果:
  //     p³/27 − p³/9 + pq/3 − r = −2p³/27 + pq/3 − r
  //
  // 这三个符号**都要小心**:抄反一个,特征值就全错。而且错得很有欺骗性——
  // 三个根加起来还是等于 p(迹是不变量),光检查「和等于迹」发现不了
  let shift = p / 3.0
  let p2 = q - p * p / 3.0
  let q2 = -2.0 * p * p * p / 27.0 + p * q / 3.0 - r

  let half = q2 / 2.0
  let third = p2 / 3.0
  let disc = half * half + third * third * third
  let roots : FixedArray[Double] = FixedArray::make(3, 0.0)
  if disc >= 0.0 {
    // 一个实根 + 一对复根。对称实矩阵不会出现这种情况,
    // 但浮点误差可能让重根附近算出一个极小的正数,所以这条路得留着
    let s = disc.sqrt()
    let u = @math.cbrt(-half + s)
    let v = @math.cbrt(-half - s)
    roots[0] = u + v + shift
    roots[1] = -(u + v) / 2.0 + shift
    roots[2] = roots[1]
  } else {
    // 三个不同的实根(对称矩阵的正常情况),走三角函数形式
    let radius = (-third).sqrt()
    let cos_arg = -half / (radius * radius * radius)
    // acos 的定义域是 [-1, 1],浮点误差会让它偶尔越界一点点
    let clamped = if cos_arg > 1.0 {
      1.0
    } else if cos_arg < -1.0 {
      -1.0
    } else {
      cos_arg
    }
    let theta = @math.acos(clamped)
    for k = 0; k < 3; k = k + 1 {
      let angle = (theta - 2.0 * 3.141592653589793 * k.to_double()) / 3.0
      roots[k] = 2.0 * radius * @math.cos(angle) + shift
    }
  }
  roots.sort()
  [roots[2], roots[1], roots[0]]
}

///|
/// 求某个特征值对应的单位特征向量。
///
/// 做法:算 B = A − λI,然后取 B 的三行两两叉乘,挑模长最大的那个。
///
/// 为什么不直接解方程组:λ 是数值算出来的,代入之后 B 通常不满秩,
/// 靠选主元解出来的向量可能全是噪声。而叉乘天然给出「垂直于两行」的方向,
/// 哪个叉乘最大就说明哪两行最独立——这也顺带躲开了「某一行恰好是零行」的情况。
pub fn symmetric_eigenvector(m : FixedArray[Double], lambda : Double) -> Vec3 {
  let rows = [
    Vec3::new(m[0] - lambda, m[1], m[2]),
    Vec3::new(m[3], m[4] - lambda, m[5]),
    Vec3::new(m[6], m[7], m[8] - lambda),
  ]
  let candidates = [
    rows[0].cross(rows[1]),
    rows[1].cross(rows[2]),
    rows[2].cross(rows[0]),
  ]
  let mut best = Vec3::zero()
  let mut best_len = -1.0
  for v in candidates {
    let len = v.length()
    if len > best_len {
      best_len = len
      best = v
    }
  }
  best.normalize()
}