// Copyright (c) Xenko contributors (https://xenko.com) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
#pragma warning disable SA1003 // Symbols must be spaced correctly
#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly
#pragma warning disable SA1009 // Closing parenthesis must be spaced correctly
#pragma warning disable SA1010 // Opening square brackets must be spaced correctly
#pragma warning disable SA1025 // Code must not contain multiple whitespace in a row
#pragma warning disable SA1119 // Statement must not use unnecessary parenthesis
#pragma warning disable SA1402 // File may only contain a single class
using System;
using System.Runtime.Serialization;
namespace math
{
///
/// A representation of a sphere of values via Spherical Harmonics (SH).
///
/// The type of data contained by the sphere
[DataContract( Name = "SphericalHarmonicsGeneric")]
public abstract class SphericalHarmonics
{
///
/// The maximum order supported.
///
public const int MaximumOrder = 5;
private int order;
///
/// The order of calculation of the spherical harmonic.
///
[DataMember( Order = 0 )]
public int Order
{
get { return order; }
internal set
{
if (order>5)
throw new NotSupportedException("Only orders inferior or equal to 5 are supported");
order = Math.Max(1, value);
}
}
///
/// Get the coefficients defining the spherical harmonics (the spherical coordinates x{l,m} multiplying the spherical base Y{l,m}).
///
[DataMember( Order = 1 )]
public TDataType[] Coefficients { get; internal set; }
///
/// Initializes a new instance of the class (null, for serialization).
///
internal SphericalHarmonics()
{
}
///
/// Initializes a new instance of the class.
///
/// The order of the harmonics
protected SphericalHarmonics(int order)
{
this.order = order;
Coefficients = new TDataType[order * order];
}
///
/// Evaluate the value of the spherical harmonics in the provided direction.
///
/// The direction
/// The value of the spherical harmonics in the direction
public abstract TDataType Evaluate(Vec3 direction);
///
/// Returns the coefficient x{l,m} of the spherical harmonics (the {l,m} spherical coordinate corresponding to the spherical base Y{l,m}).
///
/// the l index of the coefficient
/// the m index of the coefficient
/// the value of the coefficient
public TDataType this[int l, int m]
{
get
{
CheckIndicesValidity(l, m, order);
return Coefficients[LmToCoefficientIndex(l, m)];
}
set
{
CheckIndicesValidity(l, m, order);
Coefficients[LmToCoefficientIndex(l, m)] = value;
}
}
// ReSharper disable UnusedParameter.Local
private static void CheckIndicesValidity(int l, int m, int maxOrder)
// ReSharper restore UnusedParameter.Local
{
if (l > maxOrder - 1)
throw new IndexOutOfRangeException($"'l' parameter should be between '0' and '{maxOrder-1}' (order-1).");
if (Math.Abs(m) > l)
throw new IndexOutOfRangeException("'m' parameter should be between '-l' and '+l'.");
}
private static int LmToCoefficientIndex(int l, int m)
{
return l * l + l + m;
}
}
///
/// A spherical harmonics representation of a cubemap.
///
[DataContract( Name = "SphericalHarmonics")]
public class SphericalHarmonics : SphericalHarmonics
{
private readonly float[] baseValues;
private const float Pi4 = 4 * MathUtil.Pi;
private const float Pi16 = 16 * MathUtil.Pi;
private const float Pi64 = 64 * MathUtil.Pi;
private static readonly float SqrtPi = (float)Math.Sqrt(MathUtil.Pi);
///
/// Base coefficients for SH.
///
public static readonly float[] BaseCoefficients =
{
(float)(1.0/(2.0*SqrtPi)),
(float)(-Math.Sqrt(3.0/Pi4)),
(float)(Math.Sqrt(3.0/Pi4)),
(float)(-Math.Sqrt(3.0/Pi4)),
(float)(Math.Sqrt(15.0/Pi4)),
(float)(-Math.Sqrt(15.0/Pi4)),
(float)(Math.Sqrt(5.0/Pi16)),
(float)(-Math.Sqrt(15.0/Pi4)),
(float)(Math.Sqrt(15.0/Pi16)),
-(float)Math.Sqrt(70/Pi64),
(float)Math.Sqrt(105/Pi4),
-(float)Math.Sqrt(42/Pi64),
(float)Math.Sqrt(7/Pi16),
-(float)Math.Sqrt(42/Pi64),
(float)Math.Sqrt(105/Pi16),
-(float)Math.Sqrt(70/Pi64),
3*(float)Math.Sqrt(35/Pi16),
-3*(float)Math.Sqrt(70/Pi64),
3*(float)Math.Sqrt(5/Pi16),
-3*(float)Math.Sqrt(10/Pi64),
(float)(1.0/(16.0*SqrtPi)),
-3*(float)Math.Sqrt(10/Pi64),
3*(float)Math.Sqrt(5/Pi64),
-3*(float)Math.Sqrt(70/Pi64),
3*(float)Math.Sqrt(35/(4*Pi64)),
};
///
/// Initializes a new instance of the class (null, for serialization).
///
internal SphericalHarmonics()
{
}
///
/// Initializes a new instance of the class.
///
/// The order of the harmonics
public SphericalHarmonics(int order)
: base(order)
{
baseValues = new float[order * order];
}
///
/// Evaluates the color for the specified direction.
///
/// The direction to evaluate.
/// The color computed for this direction.
public override Color3 Evaluate(Vec3 direction)
{
var x = direction.X;
var y = direction.Y;
var z = direction.Z;
var x2 = x*x;
var y2 = y*y;
var z2 = z*z;
var z3 = (float)Math.Pow(z, 3.0);
var x4 = (float)Math.Pow(x, 4.0);
var y4 = (float)Math.Pow(y, 4.0);
var z4 = (float)Math.Pow(z, 4.0);
//Equations based on data from: http://ppsloan.org/publications/StupidSH36.pdf
baseValues[ 0] = 1/(2*SqrtPi);
if (Order > 1)
{
baseValues[ 1] = -(float)Math.Sqrt(3/Pi4)*y;
baseValues[ 2] = (float)Math.Sqrt(3/Pi4)*z;
baseValues[ 3] = -(float)Math.Sqrt(3/Pi4)*x;
if (Order > 2)
{
baseValues[ 4] = (float)Math.Sqrt(15/Pi4)*y*x;
baseValues[ 5] = -(float)Math.Sqrt(15/Pi4)*y*z;
baseValues[ 6] = (float)Math.Sqrt(5/Pi16)*(3*z2-1);
baseValues[ 7] = -(float)Math.Sqrt(15/Pi4)*x*z;
baseValues[ 8] = (float)Math.Sqrt(15/Pi16)*(x2-y2);
if (Order > 3)
{
baseValues[ 9] = -(float)Math.Sqrt( 70/Pi64)*y*(3*x2-y2);
baseValues[10] = (float)Math.Sqrt(105/ Pi4)*y*x*z;
baseValues[11] = -(float)Math.Sqrt( 42/Pi64)*y*(-1+5*z2);
baseValues[12] = (float)Math.Sqrt( 7/Pi16)*(5*z3-3*z);
baseValues[13] = -(float)Math.Sqrt( 42/Pi64)*x*(-1+5*z2);
baseValues[14] = (float)Math.Sqrt(105/Pi16)*(x2-y2)*z;
baseValues[15] = -(float)Math.Sqrt( 70/Pi64)*x*(x2-3*y2);
if (Order > 4)
{
baseValues[16] = 3*(float)Math.Sqrt(35/Pi16)*x*y*(x2-y2);
baseValues[17] = -3*(float)Math.Sqrt(70/Pi64)*y*z*(3*x2-y2);
baseValues[18] = 3*(float)Math.Sqrt( 5/Pi16)*y*x*(-1+7*z2);
baseValues[19] = -3*(float)Math.Sqrt(10/Pi64)*y*z*(-3+7*z2);
baseValues[20] = (105*z4-90*z2+9)/(16*SqrtPi);
baseValues[21] = -3*(float)Math.Sqrt(10/Pi64)*x*z*(-3+7*z2);
baseValues[22] = 3*(float)Math.Sqrt( 5/Pi64)*(x2-y2)*(-1+7*z2);
baseValues[23] = -3*(float)Math.Sqrt(70/Pi64)*x*z*(x2-3*y2);
baseValues[24] = 3*(float)Math.Sqrt(35/(4*Pi64))*(x4-6*y2*x2+y4);
}
}
}
}
var data = new Color3();
for (int i = 0; i < baseValues.Length; i++)
data += Coefficients[i] * baseValues[i];
return data;
}
}
}