/// <summary>
        /// Builds a 3d noise texture
        /// </summary>
        /// <param name="parameters">Builder parameters</param>
        /// <returns>Returns a <see cref="Texture3dData"/> object containing the generated noise texture</returns>
        /// <exception cref="System.ArgumentNullException">Thrown if parameters is null</exception>
        /// <exception cref="System.IO.InvalidDataException">Thrown if parameters are invalid</exception>
        public static unsafe Texture3dData Build( Noise3dTextureBuilderParameters parameters )
        {
            Arguments.CheckNotNull( parameters, "parameters" );
            parameters.Validate( );

            Texture3dData data = new Texture3dData( parameters.Width, parameters.Height, parameters.Depth, parameters.Format );

            fixed( byte* voxels = data.Bytes )
            {
                GenerateNoise( data, parameters, voxels );
            }

            return data;
        }
        /// <summary>
        /// Writes a 3d texture to a stream
        /// </summary>
        /// <param name="textureDataArray">Texture data array</param>
        /// <param name="stream">Stream to write to</param>
        public static void WriteTextureToStream( Texture3dData[] textureDataArray, Stream stream )
        {
            Arguments.CheckNotNullAndContainsNoNulls( textureDataArray, "textureDataArray" );
            Arguments.CheckNotNull( stream, "stream" );

            if ( textureDataArray.Length == 0 )
            {
                throw new ArgumentException( "Texture data array was empty - must contain at least one element", "textureDataArray" );
            }

            using ( BinaryWriter writer = new BinaryWriter( stream ) )
            {
                writer.Write( TextureFileFormatVersion1.TextureFileIdentifier );

                TextureFileFormatVersion1.Group headerGroup = new TextureFileFormatVersion1.Group( );
                headerGroup.GroupId = GroupIdentifier.TextureHeaderGroup;
                long startOfHeaderGroup = BeginGroup( writer, headerGroup );

                //	Write the header
                TextureFileFormatVersion1.Header header = new TextureFileFormatVersion1.Header( );
                header.Dimensions = 3;
                header.Format = textureDataArray[ 0 ].Format;
                header.TextureDataEntries = textureDataArray.Length;
                header.Write( writer );

                //	Write texture data
                TextureFileFormatVersion1.Group textureDataGroup = new TextureFileFormatVersion1.Group( );
                textureDataGroup.GroupId = GroupIdentifier.Texture3dDataGroup;
                for ( int textureDataIndex = 0; textureDataIndex < textureDataArray.Length; ++textureDataIndex )
                {
                    //	Write the texture group
                    long startOfTextureGroup = BeginGroup( writer, textureDataGroup );
                    Texture3dData textureData = textureDataArray[ textureDataIndex ];

                    //	Write texture data
                    writer.Write( textureData.Width );
                    writer.Write( textureData.Height );
                    writer.Write( textureData.Depth );
                    writer.Write( textureData.Bytes );

                    //	Update the texture group
                    EndGroup( writer, startOfTextureGroup );
                }

                //	Update group with correct size
                EndGroup( writer, startOfHeaderGroup );
            }
        }
        /// <summary>
        /// Loads 3d texture data from the specified stream
        /// </summary>
        private static Texture3dData[] Load3dTextureData( BinaryReader reader, TextureFileFormatVersion1.Header header )
        {
            Texture3dData[] textureDataArray = new Texture3dData[ header.TextureDataEntries ];
            for ( int textureDataCount = 0; textureDataCount < header.TextureDataEntries; ++textureDataCount )
            {
                TextureFileFormatVersion1.Group textureGroup = TextureFileFormatVersion1.Group.Read( reader );
                if ( textureGroup.GroupId != GroupIdentifier.Texture3dDataGroup )
                {
                    throw new FileLoadException( "Expected texture group" );
                }

                int width = reader.ReadInt32( );
                int height = reader.ReadInt32( );
                int depth = reader.ReadInt32( );

                Texture3dData texData = new Texture3dData( );
                texData.Create( width, height, depth, header.Format );

                reader.Read( texData.Bytes, 0, texData.Bytes.Length );

                textureDataArray[ textureDataCount ] = texData;
            }

            return textureDataArray;
        }
        //
        //    Discrepancies in atmospheric modelling papers:
        //        - HG:
        //            - Neilsen: HG(t,g) = (1-g2)/(4pi(1 + g2 - 2cos(t))^1.5)
        //            - Wolfram: HG(t,g) = (1-g2)/(1 + g2 - 2.g.cos(t))^1.5
        //            - O'Neil (adapted HG): HG(t,g) = 3(1-g2)/2(2+g2) . (1+t2)/(1+g2 - 2gt)^1.5
        //            - O'Neil and Wolfram work well. Neilsen doesn't (denominator can evalute to < 0 before raising to 3/2 power)
        //    - Scattering coefficients: (# is wavelength)
        //            - Neilsen Rayleigh coefficient: 8pi^2(n2-1)^2/3N#^4
        //            - Neilsen Mie coefficient: 0.434c.pi.4pi^2/#^2K
        //                - K=non-wavelength dependent fudge factor for Bm: ~0.67
        //                - c=concentration factor(0.6544T-0.6510).10e-16 (T=turbidity. ~555nm)
        //
        //
        //
        //
        //    Realtime rendering of atmospheric scattering for flight simulators:
        //        http://www2.imm.dtu.dk/pubdb/views/edoc_download.php/2554/pdf/imm2554.pdf
        //
        //    Display of The Earth Taking into Account Atmospheric Scattering: (Nishita)
        //        http://nis-lab.is.s.u-tokyo.ac.jp/~nis/abs_sig.html#sig93
        //
        //    Implementation of Nishita's paper:
        //        http://www.gamedev.net/reference/articles/article2093.asp
        //
        //    A practical analytical model of daylight:
        //        http://www.cs.utah.edu/vissim/papers/sunsky/sunsky.pdf
        //
        //    Discussion about atmospheric shaders:
        //        http://www.gamedev.net/community/forums/topic.asp?topic_id=335023
        //
        //    Paper that this model is based on:
        //        http://www.vis.uni-stuttgart.de/~schafhts/HomePage/pubs/wscg07-schafhitzel.pdf
        //
        //    Heyney-Greenstein on Mathworld:
        //        http://scienceworld.wolfram.com/physics/Heyney-GreensteinPhaseFunction.html
        //
        /// <summary>
        /// Builds a 3D lookup texture
        /// </summary>
        /// <param name="model">The atmosphere model</param>
        /// <param name="parameters">Parameters for the build process</param>
        /// <param name="progress">Optional progress object</param>
        /// <returns>Returns a 3d texture data. Returns null if cancel was flagged in the progress object</returns>
        public unsafe AtmosphereBuildOutputs Build( AtmosphereBuildModel model, AtmosphereBuildParameters parameters, AtmosphereBuildProgress progress )
        {
            Texture2dData opticalDepthTexture = new Texture2dData( );
            Texture3dData scatteringTexture = new Texture3dData( );
            scatteringTexture.Create( parameters.ViewAngleSamples, parameters.SunAngleSamples, parameters.HeightSamples, TextureFormat.R8G8B8A8 );

            SetupModelAndParameters( parameters, model );
            progress = progress ?? new AtmosphereBuildProgress( );

            fixed ( byte* voxels = scatteringTexture.Bytes )
            {
                if ( !BuildScatteringTexture( parameters, voxels, progress ) )
                {
                    return null;
                }
            }
            if ( progress.Cancel )
            {
                return null;
            }

            opticalDepthTexture.Create( parameters.OpticalDepthResolution, parameters.OpticalDepthResolution, TextureFormat.R8G8B8 );
            fixed ( byte* pixels = opticalDepthTexture.Bytes )
            {
                if ( !BuildOpticalDepthTexture( parameters, pixels, progress ) )
                {
                    return null;
                }
            }

            return new AtmosphereBuildOutputs( scatteringTexture, opticalDepthTexture );
        }
        /// <summary>
        /// Generates a toroidally tiled noise
        /// </summary>
        private static unsafe void GenerateNoise( Texture3dData data, Noise3dTextureBuilderParameters parameters, byte* voxels )
        {
            SimpleNoise3d sNoise = new SimpleNoise3d();
            Noise noise = new Noise( );
            float nW	= parameters.NoiseWidth;
            float nH	= parameters.NoiseHeight;
            float nD	= parameters.NoiseDepth;
            float nX0	= parameters.NoiseX;
            float nY0	= parameters.NoiseY;
            float nZ0	= parameters.NoiseZ;
            float nXInc = nW / data.Width;
            float nYInc = nH / data.Height;
            float nZInc = nD / data.Depth;
            byte* voxel = voxels;
            int bytesPerVoxel = TextureFormatInfo.GetSizeInBytes( parameters.Format );
            if ( bytesPerVoxel != 3 && bytesPerVoxel != 4 )
            {

                throw new InvalidDataException( string.Format( "Unexpected voxel stride of {0} - expected either 3 or 4 from format {1}", bytesPerVoxel, parameters.Format ) );
            }

            //	Check hacked channel expectations...
            bool hasAlpha = TextureFormatInfo.HasAlphaChannel( parameters.Format );
            float nZ = nZ0;
            float maxNoise = float.MinValue;
            float minNoise = float.MaxValue;
            for ( int z = 0; z < parameters.Depth; ++z, nZ += nZInc )
            {
                float nY = nY0;
                for ( int y = 0; y < parameters.Height; ++y, nY += nYInc )
                {
                    float nX = nX0;
                    for ( int x = 0; x < parameters.Width; ++x, nX += nXInc )
                    {
                        switch ( parameters.GenerationType )
                        {
                            case NoiseGenerationType.Grayscale :
                                {
                                    float n = ( 1.0f + sNoise.GetTilingNoise( nX, nY, nZ, nW, nH, nD ) ) / 2;

                                    maxNoise = Math.Max( maxNoise, n );
                                    minNoise = Math.Min( minNoise, n );

                                //	float n = 0.5f + noise.GetNoise( nX, nY, nZ );
                                    voxel[ 0 ] = NoiseToByte( n );
                                    voxel[ 1 ] = voxel[ 0 ];
                                    voxel[ 2 ] = voxel[ 0 ];
                                    if ( hasAlpha )
                                    {
                                        voxel[ 3 ] = voxel[ 0 ];
                                    }
                                    break;
                                }
                            case NoiseGenerationType.MultiChannel :
                                {
                                    float r = noise.GetNoise( nX, nY, nZ );
                                    float g = noise.GetNoise( nX + nW, nY, nZ );
                                    float b = noise.GetNoise( nX, nY + nH, nZ );
                                    voxel[ 0 ] = NoiseToByte( r );
                                    voxel[ 1 ] = NoiseToByte( g );
                                    voxel[ 2 ] = NoiseToByte( b );
                                    if ( hasAlpha )
                                    {
                                        float a = noise.GetNoise( nX, nY, nZ + parameters.NoiseDepth );
                                        voxel[ 3 ] = NoiseToByte( a );
                                    }
                                    break;
                                }
                            case NoiseGenerationType.TilingGrayscale :
                                {
                                    float n = GetTiledNoise( noise, nX, nY, nZ, nX0, nY0, nZ0, nW, nH, nD );
                                    voxel[ 0 ] = NoiseToByte( n );
                                    voxel[ 1 ] = voxel[ 0 ];
                                    voxel[ 2 ] = voxel[ 0 ];
                                    if ( hasAlpha )
                                    {
                                        voxel[ 3 ] = voxel[ 0 ];
                                    }
                                    break;
                                }
                            case NoiseGenerationType.TilingMultiChannel :
                                {
                                    float r = GetTiledNoise( noise, nX, nY, nZ, nX0, nY0, nZ0, nW, nH, nD );
                                    float g = GetTiledNoise( noise, nX + nW, nY, nZ, nX0 + nW, nY0, nZ0, nW, nH, nD );
                                    float b = GetTiledNoise( noise, nX, nY + nH, nZ, nX0, nY0 + nH, nZ0, nW, nH, nD );
                                    voxel[ 0 ] = NoiseToByte( r );
                                    voxel[ 1 ] = NoiseToByte( g );
                                    voxel[ 2 ] = NoiseToByte( b );
                                    if ( hasAlpha )
                                    {
                                        float a = GetTiledNoise( noise, nX, nY, nZ + nD, nX0, nY0, nZ0 + nD, nW, nH, nD );
                                        voxel[ 3 ] = NoiseToByte( a );
                                    }
                                    break;
                                }
                            case NoiseGenerationType.BigNoise :
                                {
                                    float real = 0.5f + GetTiledNoise( noise, nX, nY, nZ, nX0, nY0, nZ0, nW, nH, nD );
                                    float imag = 0.5f + GetTiledNoise( noise, nX + nW, nY + nH, nZ + nD, nX0, nY0, nZ0, nW, nH, nD );

                                //	maxNoise = Math.Max( maxNoise, real );
                                //	minNoise = Math.Min( minNoise, real );

                                    double a = Math.PI + Math.PI * real;

                                    voxel[ 0 ] = NoiseToByte( real );
                                    voxel[ 1 ] = NoiseToByte( imag );
                                    voxel[ 2 ] = ( byte )Utils.Clamp( ( Math.Sin( a ) + 1 ) * 127.5, 0, 255 );
                                    voxel[ 3 ] = ( byte )Utils.Clamp( ( Math.Cos( a ) + 1 ) * 127.5, 0, 255 );

                                    break;
                                }
                            default :
                                throw new NotSupportedException( string.Format( "Noise generation type \"{0}\" is not supported yet", parameters.GenerationType ) );
                        }

                        voxel += bytesPerVoxel;
                    }
                }
            }
        }
 /// <summary>
 /// Sets up the build outputs
 /// </summary>
 /// <param name="scatteringTexture">Scattering texture data</param>
 /// <param name="opticalDepthTexture">Optical depth texture data</param>
 public AtmosphereBuildOutputs( Texture3dData scatteringTexture, Texture2dData opticalDepthTexture )
 {
     m_ScatteringTexture = scatteringTexture;
     m_OpticalDepthTexture = opticalDepthTexture;
 }
        /// <summary>
        /// Creates this texture from a texture data object
        /// </summary>
        public unsafe void Create( Texture3dData data )
        {
            m_Width = data.Width;
            m_Height = data.Height;
            m_Depth = data.Depth;
            m_Format = data.Format;
            m_Handle = OpenGlTextureHandle.CreateHandle( );

            OpenGlTexture2dBuilder.TextureInfo info = OpenGlTexture2dBuilder.CheckTextureFormat( m_Format );

            int target = Gl.GL_TEXTURE_3D;
            int border = 0;

            Gl.glEnable( target );
            Gl.glBindTexture( target, m_Handle );
            fixed ( void* bytes = data.Bytes )
            {
                Gl.glTexImage3D( target, 0, info.GlInternalFormat, m_Width, m_Height, m_Depth, border, info.GlFormat, info.GlType, new IntPtr( bytes ) );
            }
        }