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