public static SilverLiningSpectrum operator *(SilverLiningSpectrum s1, SilverLiningSpectrum s2)
    {
        SilverLiningSpectrum sOut = new SilverLiningSpectrum();

        for (int i = 0; i < NSAMPLES; i++)
        {
            sOut.powers[i] = s1.powers[i] * s2.powers[i];
        }

        return(sOut);
    }
    private void ComputeSun (double altitude)
    {
        Vector3 sunPos = ephemeris.GetSunPositionHorizon ();
        sunPos.Normalize ();
        double cosZenith = sunPos.y;
        
        if (lastSunT != T || lastSunZenith != cosZenith) {
            lastSunT = T;
            lastSunZenith = cosZenith;
            
            lightingChanged = true;
			
			double solarAltitude = DEGREES(Math.Asin(sunPos.y));
			if (solarAltitude > 1.0)
		    {
		        perezBlend = 0;
		    } else if (solarAltitude < 0) {
		        perezBlend = 1.0f;
		    } else {
		        perezBlend = 1.0f - (float)solarAltitude;
		    }
			
            
            if (cosZenith > 0) {
                double zenithAngle = Math.Acos (cosZenith);
                
                SilverLiningSpectrum solarDirect = new SilverLiningSpectrum ();
                SilverLiningSpectrum solarScattered = new SilverLiningSpectrum ();
                
                sunSpectrum.ApplyAtmosphericTransmittance (zenithAngle, cosZenith, T, altitude, ref solarDirect, ref solarScattered);
                
                sunTransmittedLuminance = solarDirect.ToXYZ ();
                sunScatteredLuminance = solarScattered.ToXYZ ();
                
                // Apply sunset color tweaks
                double alpha = zenithAngle / (PI * 0.5);
                for (int i = 0; i < boostExp; i++)
                    alpha *= alpha;
                sunScatteredLuminance.x *= (float)(1.0 + alpha * XBoost);
                sunScatteredLuminance.y *= (float)(1.0 + alpha * YBoost);
                sunScatteredLuminance.z *= (float)(1.0 + alpha * ZBoost);
            } else {
                // In twilight conditions, we lookup luminance based on experimental results
                // on cloudless nights.
                
                int lower = (int)Math.Floor (solarAltitude);
                int higher = (int)Math.Ceiling (solarAltitude);
                
                float alpha = (float)(solarAltitude - lower);
                
                float a = 0;
                float b = 0;
                if (lower >= -16 && higher >= -16) {
                    a = twilightLuminance[lower];
                    b = twilightLuminance[higher];
                }
                
                // Blend light from sunset
                const double epsilon = 0.001;
                SilverLiningSpectrum solarDirect = new SilverLiningSpectrum ();
                SilverLiningSpectrum solarScattered = new SilverLiningSpectrum ();
                
                double zenithAngle = PI * 0.5 - epsilon;
                sunSpectrum.ApplyAtmosphericTransmittance (zenithAngle, Math.Cos (zenithAngle), T, altitude, ref solarDirect, ref solarScattered);
                sunTransmittedLuminance = solarDirect.ToXYZ ();
                sunScatteredLuminance = solarScattered.ToXYZ ();
                
                float Y = (1 - alpha) * a + alpha * b;
                // luminance per lookup table
                float x = 0.25f;
                float y = 0.25f;
                float minDirectional = 0.1f;
                
                float X = x * (Y / y);
                float Z = (1.0f - x - y) * (Y / y);
                
                alpha = -(float)solarAltitude / 2.0f;
                if (alpha > 1.0f)
                    alpha = 1.0f;
                if (alpha < 0)
                    alpha = 0;
                alpha = alpha * alpha;
                sunTransmittedLuminance = sunTransmittedLuminance * Y * minDirectional * alpha + sunTransmittedLuminance * (1.0f - alpha);
                Vector3 twilight = new Vector3 (X, Y, Z);
                sunScatteredLuminance = twilight * alpha + sunScatteredLuminance * (1.0f - alpha);
                
                // Apply sunset color tweaks
                sunScatteredLuminance.x *= 1.0f + XBoost;
                sunScatteredLuminance.y *= 1.0f + YBoost;
                sunScatteredLuminance.z *= 1.0f + ZBoost;
            }
            
            if (isOvercast) {
                sunTransmittedLuminance = (sunTransmittedLuminance * (overcastBlend * overcastTransmission)) + (sunTransmittedLuminance * (1.0f - overcastBlend));
                sunScatteredLuminance = (sunScatteredLuminance * (overcastBlend * overcastTransmission)) + (sunScatteredLuminance * (1.0f - overcastBlend));
            }
            
            
            sunTransmittedLuminance = sunTransmittedLuminance * sunTransmissionScale;
            sunScatteredLuminance = sunScatteredLuminance * sunScatteredScale;
        }
    }
    public SilverLiningSky()
    {
        sunDistance *= (float)SilverLining.unitScale;
        moonDistance *= (float)SilverLining.unitScale;
        H *= (float)SilverLining.unitScale;

        ephemeris = new SilverLiningEphemeris ();
        InitTwilightLuminances ();
        sunSpectrum = new SilverLiningSolarSpectrum ();
        lunarSpectrum = new SilverLiningLunarSpectrum ();
        XYZ2RGB = new SilverLiningMatrix3 (3.240479, -0.969256, 0.055648, -1.537150, 1.875992, -0.204043, -0.498535, 0.041556, 1.057311);

        XYZ2RGB4 = new Matrix4x4 ();
        XYZ2RGB4[0, 0] = 3.240479f;
        XYZ2RGB4[0, 1] = -0.969256f;
        XYZ2RGB4[0, 2] = 0.055648f;
        XYZ2RGB4[0, 3] = 0.0f;
        XYZ2RGB4[1, 0] = -1.537150f;
        XYZ2RGB4[1, 1] = 1.875992f;
        XYZ2RGB4[1, 2] = -0.204043f;
        XYZ2RGB4[1, 3] = 0.0f;
        XYZ2RGB4[2, 0] = -0.498535f;
        XYZ2RGB4[2, 1] = 0.041556f;
        XYZ2RGB4[2, 2] = 1.057311f;
        XYZ2RGB4[2, 3] = 0.0f;
        XYZ2RGB4[3, 0] = 0.0f;
        XYZ2RGB4[3, 1] = 0.0f;
        XYZ2RGB4[3, 2] = 0.0f;
        XYZ2RGB4[3, 3] = 1.0f;
    }
    public void ApplyAtmosphericTransmittance(double zenithAngle, double cosZenith, double T, double alt,
                                              ref SilverLiningSpectrum directIrradiance,
                                              ref SilverLiningSpectrum scatteredIrradiance)
    {
        double beta = 0.04608 * T - 0.04586;

        double zenithDeg = DEGREES(zenithAngle);

        const double modelLimit = 90.0;     //93.885

        if (zenithDeg < modelLimit)
        {
            // SPECTRL2 air mass model
            double m = 1.0 / (cosZenith + 0.50572 * Math.Pow(96.07995 - zenithDeg, -1.6364));
            m *= airMassMultiplier;

            // Account for high altitude. As you lose atmosphere, less scattering occurs.
            H *= SilverLining.unitScale;
            double isothermalEffect = Math.Exp(-(alt / H));
            m *= isothermalEffect;

            // ozone mass
            double Mo = 1.003454 / Math.Sqrt(cosZenith * cosZenith + 0.006908);

            const double omega  = 0.945;   // single scaterring albedo, 0.4 microns
            const double omegap = 0.095;   // Wavelength variation factor
            const double assym  = 0.65;    // aerosol assymetry factor

            double alg = Math.Log(1.0 - assym);
            double afs = alg * (1.459 + alg * (0.1595 + alg * 0.4129));
            double bfs = alg * (0.0783 + alg * (-0.3824 - alg * 0.5874));
            double fsp = 1.0 - 0.5 * Math.Exp((afs + bfs / 1.8) / 1.8);
            double fs  = 1.0 - 0.5 * Math.Exp((afs + bfs * cosZenith) * cosZenith);

            for (int i = 0; i < NSAMPLES; i++)
            {
                double um = 0.380 + (i * 0.005);

                // Rayleigh scattering
                double Tr = Math.Exp(-m / (Math.Pow(um, 4.0) * (115.6406 - (1.3366 / (um * um)))));

                // Aerosols
                double a;
                if (um < 0.5)
                {
                    a = 1.0274;
                }
                else
                {
                    a = 1.2060;
                }

                double c1 = beta * Math.Pow(2.0 * um, -a);
                double Ta = Math.Exp(-c1 * m);

                // Water vapor
                double aWM = Aw[i] * W * m;
                double Tw  = Math.Exp(-0.2385 * aWM / Math.Pow(1.0 + 20.07 * aWM, 0.45));

                // Ozone
                double To = Math.Exp(-Ao[i] * O3 * Mo);

                // Mixed gas is only important in infrared
                double Tm = Math.Exp((-1.41 * Au[i] * m) / Math.Pow(1.0 + 118.3 * Au[i] * m, 0.45));

                // Aerosol scattering
                double logUmOver4 = Math.Log(um / 0.4);
                double omegl      = omega * Math.Exp(-omegap * logUmOver4 * logUmOver4);
                double Tas        = Math.Exp(-omegl * c1 * m);

                // Aerosol absorptance
                double Taa = Math.Exp((omegl - 1.0) * c1 * m);

                // Primed Rayleigh scattering (m = 1.8)
                double Trp = Math.Exp(-1.8 / (um * um * um * um) * (115.6406 - 1.3366 / (um * um)));

                // Primed water vapor scattering
                double Twp = Math.Exp(-0.4293 * Aw[i] * W / Math.Pow((1.0 + 36.126 * Aw[i] * W), 0.45));

                // Mixed gas
                double Tup = Math.Exp(-2.538 * Au[i] / Math.Pow((1.0 + 212.94 * Au[i]), 0.45));

                // Primed aerosol scattering
                double Tasp = Math.Exp(-omegl * c1 * 1.8);

                // Primed aerosol absorptance
                double Taap = Math.Exp((omegl - 1.0) * c1 * 1.8);

                // Direct energy
                double xmit = Tr * Ta * Tw * To * Tm;

                directIrradiance.powers[i] = powers[i] * xmit;

                // diffuse energy
                double c2 = powers[i] * To * Tw * Tm * cosZenith * Taa;
                double c4 = 1.0;
                if (um <= 0.45)
                {
                    c4 = Math.Pow((um + 0.55), 1.8);
                }

                double rhoa = Twp * Tup * Taap * (0.5 * (1.0 - Trp) + (1.0 - fsp) * Trp * (1.0 - Tasp));
                double dray = c2 * (1.0 - Math.Pow(Tr, 0.95)) / 2.0;
                double daer = c2 * Math.Pow(Tr, 1.5) * (1.0 - Tas) * fs;
                double drgd = (directIrradiance.powers[i] * cosZenith + dray + daer) * rho * rhoa / (1.0 - rho * rhoa);

                scatteredIrradiance.powers[i] = (dray + daer + drgd) * c4;

                if (scatteredIrradiance.powers[i] < 0)
                {
                    scatteredIrradiance.powers[i] = 0;
                }
            }
        }
        else
        {
            for (int i = 0; i < NSAMPLES; i++)
            {
                directIrradiance.powers[i]    = 0;
                scatteredIrradiance.powers[i] = 0;
            }
        }
    }
	public void ApplyAtmosphericTransmittance(double zenithAngle, double cosZenith, double T, double alt,
	                                          ref SilverLiningSpectrum directIrradiance, 
	                                          ref SilverLiningSpectrum scatteredIrradiance)
	{
	
	    double beta = 0.04608 * T - 0.04586;
	
	    double zenithDeg = DEGREES(zenithAngle);
	
	    const double modelLimit = 90.0; //93.885
	
	    if (zenithDeg < modelLimit)
	    {
	    	// SPECTRL2 air mass model
	        double m = 1.0 / (cosZenith + 0.50572 * Math.Pow(96.07995 - zenithDeg, -1.6364));	
	        m *= airMassMultiplier;
	
	        // Account for high altitude. As you lose atmosphere, less scattering occurs.
	        H *= SilverLining.unitScale;
	        double isothermalEffect = Math.Exp(-(alt / H));
	        m *= isothermalEffect;
	
	        // ozone mass
	        double Mo = 1.003454 / Math.Sqrt ( cosZenith * cosZenith + 0.006908 );
	
	        const double omega = 0.945; // single scaterring albedo, 0.4 microns
	        const double omegap = 0.095; // Wavelength variation factor
	        const double assym = 0.65; // aerosol assymetry factor
	
	        double alg = Math.Log(1.0 - assym);
	        double afs = alg * (1.459 + alg * (0.1595 + alg * 0.4129));
	        double bfs = alg * (0.0783 + alg * (-0.3824 - alg * 0.5874));
	        double fsp = 1.0 - 0.5 * Math.Exp((afs + bfs / 1.8) / 1.8);
	        double fs = 1.0 - 0.5 * Math.Exp((afs + bfs * cosZenith) * cosZenith);
	
	        for (int i = 0; i < NSAMPLES; i++)
	        {
	            double um = 0.380 + (i * 0.005);
	
	            // Rayleigh scattering
	            double Tr = Math.Exp(-m / (Math.Pow(um, 4.0) * (115.6406 - (1.3366 / (um * um)))));
	
	            // Aerosols
	            double a;
				if (um < 0.5) {
					a = 1.0274;
				} else {
					a = 1.2060;
				}
				
	            double c1 = beta * Math.Pow(2.0 * um, -a);
	            double Ta = Math.Exp(-c1 * m);
	
	            // Water vapor
	            double aWM = Aw[i] * W * m;
	            double Tw = Math.Exp(-0.2385 * aWM / Math.Pow(1.0 + 20.07 * aWM, 0.45));
	
	            // Ozone
	            double To = Math.Exp(-Ao[i] * O3 * Mo);
	
	            // Mixed gas is only important in infrared
	            double Tm = Math.Exp((-1.41 * Au[i] * m) / Math.Pow(1.0 + 118.3 * Au[i] * m, 0.45));
	
	            // Aerosol scattering
	            double logUmOver4 = Math.Log(um / 0.4);
	            double omegl = omega * Math.Exp (-omegap * logUmOver4 * logUmOver4);
	            double Tas = Math.Exp(-omegl * c1 * m);
	
	            // Aerosol absorptance
	            double Taa = Math.Exp((omegl - 1.0) * c1 * m);
	
	            // Primed Rayleigh scattering (m = 1.8)
	            double Trp = Math.Exp(-1.8 / (um * um * um * um) * (115.6406 - 1.3366 / (um * um)));
	
	            // Primed water vapor scattering
	            double Twp = Math.Exp(-0.4293 * Aw[i] * W / Math.Pow((1.0 + 36.126 * Aw[i] * W), 0.45));
	
	            // Mixed gas
	            double Tup = Math.Exp(-2.538 * Au[i] / Math.Pow((1.0 + 212.94 * Au[i]), 0.45));
	
	            // Primed aerosol scattering
	            double Tasp = Math.Exp(-omegl * c1 * 1.8);
	
	            // Primed aerosol absorptance
	            double Taap = Math.Exp((omegl - 1.0) * c1 * 1.8);
	
	            // Direct energy
	            double xmit = Tr * Ta * Tw * To * Tm;
	
	            directIrradiance.powers[i] = powers[i] * xmit;
	
	            // diffuse energy
	            double c2 = powers[i] * To * Tw * Tm * cosZenith * Taa;
	            double c4 = 1.0;
	            if (um <= 0.45) {
	                c4 = Math.Pow( (um + 0.55), 1.8);
	            }
	
	            double rhoa = Twp * Tup * Taap * (0.5 * (1.0 - Trp) + (1.0 - fsp) * Trp * (1.0 - Tasp) );
	            double dray = c2 * (1.0 - Math.Pow(Tr, 0.95)) / 2.0;
	            double daer = c2 * Math.Pow(Tr, 1.5) * (1.0 - Tas) * fs;
	            double drgd = ( directIrradiance.powers[i] * cosZenith + dray + daer) * rho * rhoa / (1.0 - rho * rhoa);
	
	            scatteredIrradiance.powers[i] = (dray + daer + drgd) * c4;
	
	            if (scatteredIrradiance.powers[i] < 0)
	                scatteredIrradiance.powers[i] = 0;
	        }
	    }
	    else
	    {
	        for (int i = 0; i < NSAMPLES; i++)
	        {
	            directIrradiance.powers[i] = 0;
	            scatteredIrradiance.powers[i] = 0;
	        }
	    }
	}
	public static SilverLiningSpectrum operator * (SilverLiningSpectrum s1, SilverLiningSpectrum s2)
	{
	    SilverLiningSpectrum sOut = new SilverLiningSpectrum();
	
	    for (int i = 0; i < NSAMPLES; i++)
	    {
	        sOut.powers[i] = s1.powers[i] * s2.powers[i];
	    }
	
	    return sOut;
	}