/// <summary>
 /// Setup constructor
 /// </summary>
 public WorkItem( BackgroundWorker worker, AtmosphereBuildModel model, AtmosphereBuildParameters parameters )
 {
     m_Worker = worker;
     m_AtmosphereModel = model;
     m_AtmosphereBuildParameters = parameters;
 }
 /// <summary>
 /// Sets up stored and derived parameters from an atmosphere model
 /// </summary>
 /// <remarks>
 /// The inner radius is diminished by a constant amount, because otherwise, the quantised
 /// view directions hit the inner sphere and cause a blue band to appear when the actual
 /// view direction approaches it.
 /// </remarks>
 private void SetupModelAndParameters( AtmosphereBuildParameters parameters, AtmosphereBuildModel model )
 {
     m_RayleighCoefficients = ( float[] )model.RayleighCoefficients.Clone( );
     m_MieCoefficients = ( float[] )model.MieCoefficients.Clone( );
     m_InnerRadius = model.InnerRadiusMetres;
     m_OuterRadius = model.InnerRadiusMetres + model.AtmosphereThicknessMetres;
     m_InnerSphere = new Sphere3( Point3.Origin, m_InnerRadius * model.GroundRadiusMultiplier );	//	NOTE: AP: See remarks
     m_OuterSphere = new Sphere3( Point3.Origin, m_OuterRadius );
     m_AttenuationSamples = parameters.AttenuationSamples;
     float heightRange = ( m_OuterRadius - m_InnerRadius );
     m_InvRH0 = -1.0f / ( heightRange * model.RayleighDensityScaleHeightFraction );
     m_InvMH0 = -1.0f / ( heightRange * model.MieDensityScaleHeightFraction );
     m_InscatterDistanceFudgeFactor = model.InscatterDistanceFudgeFactor;
     m_OutscatterDistanceFudgeFactor = model.OutscatterDistanceFudgeFactor;
     m_MieFudge = model.MieFudgeFactor;
     m_RayleighFudge = model.RayleighFudgeFactor;
     m_OutscatterFudge = model.OutscatterFudgeFactor;
 }
        /// <summary>
        /// Builds a 2D lookup texture containing optical depth for a paramerization of the view frame
        /// </summary>
        /// <param name="buildParams">Atmosphere build parameters</param>
        /// <param name="pixels">Atmosphere texture pixels</param>
        /// <param name="progress">Progress indicator</param>
        /// <returns>Returns true if the build completed (wasn't cancelled)</returns>
        private unsafe bool BuildOpticalDepthTexture( AtmosphereBuildParameters buildParams, byte* pixels, AtmosphereBuildProgress progress )
        {
            //	TODO: AP: Because we know that the view to pos angle range will never be > pi, can optimise this later
            int viewSamples = buildParams.OpticalDepthResolution;
            int heightSamples = buildParams.OpticalDepthResolution;
            float viewAngleInc = Constants.Pi / ( viewSamples - 1 );
            float heightRange = ( m_OuterRadius - m_InnerRadius );
            float heightInc = ( heightRange * 0.9999f ) / ( heightSamples - 1 );	//	Push height range in slightly to allow simplification of sphere intersections

            float* rAccum = stackalloc float[ 3 ];
            float* mAccum = stackalloc float[ 3 ];
            float height = m_InnerRadius;
            for ( int heightSample = 0; heightSample < heightSamples; ++heightSample, height += heightInc )
            {
                Point3 pos = new Point3( 0, height, 0 );
                //	Start the view angle at pi, and count down to 0. This is because it is quickest to address
                //	the 2D texture using the dot of the view vector and the view position, saving a (1-th) operation
                float viewAngle = Constants.Pi;
                Point3 lastAtmInt = pos;
                for ( int viewSample = 0; viewSample < viewSamples; ++viewSample, viewAngle -= viewAngleInc )
                {
                    Vector3 viewDir = new Vector3( Functions.Sin( viewAngle ), Functions.Cos( viewAngle ), 0 );

                    //	NOTE: If ray intersection fails, the previous intersection position is used...
                    Point3 atmInt;
                //	if ( !GetRayPlanetAndAtmosphereIntersection( pos, viewDir, out atmInt ) )
                    if ( !GetRayAtmosphereIntersection( pos, viewDir, out atmInt ) )
                    {
                        atmInt = lastAtmInt;
                    }
                    else
                    {
                        lastAtmInt = atmInt;
                    }

                    Vector3 step = ( atmInt - pos ) / m_AttenuationSamples;

                    rAccum[ 0 ] = rAccum[ 1 ] = rAccum[ 2 ] = 0;
                    mAccum[ 0 ] = mAccum[ 1 ] = mAccum[ 2 ] = 0;
                    CalculateOutScatter( pos, step, rAccum, mAccum );

                //	float oR = CalculateCombinedOutScatter( pos, step, m_RayleighCoefficients[ 0 ], m_MieCoefficients[ 0 ] );
                //	float oG = CalculateCombinedOutScatter( pos, step, m_RayleighCoefficients[ 1 ], m_MieCoefficients[ 1 ] );
                //	float oB = CalculateCombinedOutScatter( pos, step, m_RayleighCoefficients[ 2 ], m_MieCoefficients[ 2 ] );

                    float attR = ExtinctionCoefficient( rAccum[ 0 ], mAccum[ 0 ] );
                    float attG = ExtinctionCoefficient( rAccum[ 1 ], mAccum[ 1 ] );
                    float attB = ExtinctionCoefficient( rAccum[ 2 ], mAccum[ 2 ] );

                    pixels[ 2 ] = ( byte )( Utils.Clamp( attR, 0, 1 ) * 255.0f );
                    pixels[ 1 ] = ( byte )( Utils.Clamp( attG, 0, 1 ) * 255.0f );
                    pixels[ 0 ] = ( byte )( Utils.Clamp( attB, 0, 1 ) * 255.0f );
                    pixels += 3;
                }

                if ( progress != null )
                {
                    progress.OnSliceCompleted( heightSample / ( float )( heightSamples - 1 ) );
                    if ( progress.Cancel )
                    {
                        return false;
                    }
                }
            }
            return true;
        }
        /// <summary>
        /// Builds a 3D lookup texture containing the scattering values for a parameterization of
        /// the view frame and sun position.
        /// </summary>
        /// <param name="parameters">Parameters for the build process</param>
        /// <param name="progress">Progress object</param>
        /// <param name="voxels">3d texture voxels</param>
        /// <returns>true if build completed (wasn't cancelled)</returns>
        private unsafe bool BuildScatteringTexture( AtmosphereBuildParameters parameters, byte* voxels, AtmosphereBuildProgress progress )
        {
            float viewAngle = Constants.Pi;
            float viewAngleInc = Constants.Pi  / ( parameters.ViewAngleSamples - 1 );
            float heightRange = ( m_OuterRadius - m_InnerRadius );
            float heightInc = ( heightRange * 0.9999f ) / ( parameters.HeightSamples - 1 );	//	Push height range in slightly to allow simplification of sphere intersections
            float sunAngleInc = ( Constants.Pi ) / ( parameters.SunAngleSamples - 1 );

            for ( int viewAngleSample = 0; viewAngleSample < parameters.ViewAngleSamples; ++viewAngleSample, viewAngle -= viewAngleInc )
            {
                Vector3 viewDir = new Vector3( Functions.Sin( viewAngle ), Functions.Cos( viewAngle ), 0 );

                float sunAngle = 0;
                for ( int sunAngleSample = 0; sunAngleSample < parameters.SunAngleSamples; ++sunAngleSample, sunAngle += sunAngleInc )
                {
                    Vector3 sunDir = new Vector3( Functions.Sin( sunAngle ), Functions.Cos( sunAngle ), 0 );
                    float height = m_InnerRadius;
                    for ( int heightSample = 0; heightSample < parameters.HeightSamples; ++heightSample, height += heightInc )
                    {
                        ComputeScattering( viewDir, sunDir, height, voxels );
                        voxels += 4;
                    }
                }

                if ( progress != null )
                {
                    progress.OnSliceCompleted( viewAngleSample / ( float )( parameters.ViewAngleSamples - 1 ) );

                    if ( progress.Cancel )
                    {
                        return false;
                    }
                }
            }
            return true;
        }
        //
        //    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 );
        }