// 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;
}
}
}