Sim/Metrics.cs
using System;
using System.Collections.Generic;

namespace SquishyTerry.Sim;

/// <summary>Mesh measurements that drive the audio (computeVolume / computeArea / ...).</summary>
public static class Metrics
{
	public static float Volume( IReadOnlyList<Vec3> p, IReadOnlyList<int> idx )
	{
		float v = 0;
		for ( int t = 0; t + 2 < idx.Count; t += 3 )
			v += Vec3.Dot( p[idx[t]], Vec3.Cross( p[idx[t + 1]], p[idx[t + 2]] ) ) / 6.0f;
		return MathF.Abs( v );
	}

	/// <summary>Array fast path of <see cref="Volume(IReadOnlyList{Vec3}, IReadOnlyList{int})"/> (same arithmetic order).</summary>
	public static float Volume( Vec3[] p, int[] idx )
	{
		float v = 0;
		int count = idx.Length - 2;
		for ( int t = 0; t < count; t += 3 )
		{
			ref Vec3 a = ref p[idx[t]];
			ref Vec3 b = ref p[idx[t + 1]];
			ref Vec3 c = ref p[idx[t + 2]];
			float cx = b.Y * c.Z - b.Z * c.Y, cy = b.Z * c.X - b.X * c.Z, cz = b.X * c.Y - b.Y * c.X;
			v += (a.X * cx + a.Y * cy + a.Z * cz) / 6.0f;
		}
		return MathF.Abs( v );
	}

	public static float Area( IReadOnlyList<Vec3> p, IReadOnlyList<int> idx )
	{
		float a = 0;
		for ( int t = 0; t + 2 < idx.Count; t += 3 )
			a += Vec3.Cross( p[idx[t + 1]] - p[idx[t]], p[idx[t + 2]] - p[idx[t]] ).Length * 0.5f;
		return a;
	}

	/// <summary>Array fast path of <see cref="Area(IReadOnlyList{Vec3}, IReadOnlyList{int})"/> (same arithmetic order).</summary>
	public static float Area( Vec3[] p, int[] idx )
	{
		float a = 0;
		int count = idx.Length - 2;
		for ( int t = 0; t < count; t += 3 )
		{
			ref Vec3 p0 = ref p[idx[t]];
			ref Vec3 p1 = ref p[idx[t + 1]];
			ref Vec3 p2 = ref p[idx[t + 2]];
			float ux = p1.X - p0.X, uy = p1.Y - p0.Y, uz = p1.Z - p0.Z;
			float vx = p2.X - p0.X, vy = p2.Y - p0.Y, vz = p2.Z - p0.Z;
			float nx = uy * vz - uz * vy, ny = uz * vx - ux * vz, nz = ux * vy - uy * vx;
			a += MathF.Sqrt( nx * nx + ny * ny + nz * nz ) * 0.5f;
		}
		return a;
	}

	public static float AverageSpeed( IReadOnlyList<Vec3> vel )
	{
		if ( vel.Count == 0 ) return 0;
		float s = 0;
		for ( int i = 0; i < vel.Count; i++ ) s += vel[i].Length;
		return s / vel.Count;
	}

	/// <summary>Array fast path of <see cref="AverageSpeed(IReadOnlyList{Vec3})"/> (same arithmetic order).</summary>
	public static float AverageSpeed( Vec3[] vel )
	{
		if ( vel.Length == 0 ) return 0;
		float s = 0;
		for ( int i = 0; i < vel.Length; i++ )
		{
			ref Vec3 v = ref vel[i];
			s += MathF.Sqrt( v.X * v.X + v.Y * v.Y + v.Z * v.Z );
		}
		return s / vel.Length;
	}
}