
	/////////////////////////////////////////////
	// louver_shading.js
	// Copyright 2008, 1995 Sustainable By Design
	/////////////////////////////////////////////
	

	/////////////////////////////////////////////////////////
	//
	//  constants
	//
	/////////////////////////////////////////////////////////	
			
		var       degreesToRadians =    3.1416 /  180.0000;
		var       radiansToDegrees =  180.0000 /    3.1416;
		var           feetToMeters =    1.0000 /    3.2800;
		var degreeMinutesToDecimal =    1.0000 /   60.0000;
		var degreeSecondsToDecimal =    1.0000 / 3600.0000;				
	

	/////////////////////////////////////////////////////////
	//
	//  initialization
	//
	/////////////////////////////////////////////////////////
	
 		var firstHour =  4;
 		var  lastHour = 20;
 		

	/////////////////////////////////////////////////////////
	//
	//  FUNCTION:  CheckInputs
	//
	/////////////////////////////////////////////////////////
	
		function CheckInputs () {
		
			var error = false;
		
			var error_message = "The following inputs must be corrected before the shading can be calculated:\n\n";
		
			var f = document.theForm;
		
			var inputLatitude    = f.inputLatitude.value        - 0;
			
			var inputSlatDepth     = f.inputSlatDepth.value     - 0;
			var inputSlatThickness = f.inputSlatThickness.value - 0;
			var inputSlatSpacing   = f.inputSlatSpacing.value   - 0;
			var inputSlatTilt      = f.inputSlatTilt.value      - 0;
		
			// LATITUDE
		
			var latitudeOkay = false;
		
			if (inputLatitude.search ("^[0-9]+[dD][0-9]+[mM][0-9]+[sS]$") > -1) { latitudeOkay = true; }
			
			else { if ((inputLatitude >= 0 ) && (inputLatitude <= 90)) { latitudeOkay = true; } }
		
			if (! latitudeOkay) {
				
				error_message = error_message + "* The latitude must be between 0 and 90 degrees (click on the latitude label for details)\n";
				
				error = true;
			}
			
			// SLAT LENGTH
		
			if (inputSlatDepth.search ("[^0-9. ]") > -1) {
		
				error_message = error_message + "* The slat length must be a number, with no units (click on the slat length label for details)\n";
				
				error = true;
			}
			
			if (inputSlatDepth == 0) {
		
				error_message = error_message + "* The slat length must be greater than zero (click on the slat length label for details)\n";
				
				error = true;
			}
			
			// SLAT WIDTH
		
			if (inputSlatThickness.search ("[^0-9. ]") > -1) {
		
				error_message = error_message + "* The slat width must be a number, with no units (click on the slat width label for details)\n";
				
				error = true;
			}
			
			// SLAT SPACING
		
			if (inputSlatSpacing.search ("[^0-9. ]") > -1) {
		
				error_message = error_message + "* The slat spacing must be a number, with no units (click on the slat spacing label for details)\n";
				
				error = true;
			}
			
			if (inputSlatSpacing == 0) {
		
				error_message = error_message + "* The slat spacing must be greater than zero (click on the slat spacing label for details)\n";
				
				error = true;
			}
			
			else if (inputSlatSpacing <= inputSlatThickness) {
		
				error_message = error_message + "* The slat spacing must be greater than the slat width (click on the slat spacing label for details)\n";
				
				error = true;
			}
			
			// SLAT TILT
		
			if (inputSlatTilt.search ("[^0-9. ]") > -1) {
		
				error_message = error_message + "* The slat tilt must be a number, with no units (click on the slat tilt label for details)\n";
				
				error = true;
			}
			
			// ALERT / RETURN
		
			if (error == true) { alert (error_message); }
		
			return (! error);
		}
		

	/////////////////////////////////////////////////////////
	//
	//  FUNCTION:  Compute
	//
	/////////////////////////////////////////////////////////	
 		
		function Compute () {
		
			if (1) {
		
				var f = document.theForm;

		// ---------------------------------------------
		// get inputs
		// ---------------------------------------------
	
				var inputLatitude    = f.inputLatitude.value          - 0;
				var inputSlatDepth  = f.inputSlatDepth.value          - 0;
				var inputSlatThickness   = f.inputSlatThickness.value - 0;
				var inputSlatSpacing = f.inputSlatSpacing.value       - 0;
				var inputSlatTilt    = f.inputSlatTilt.value          - 0;
	
				var inputOrientation = f.inputOrientation.options[f.inputOrientation.selectedIndex].value - 0;
				var inputNorthSouth  = f.inputNorthSouth.options[f.inputNorthSouth.selectedIndex].text;
				var inputLabels      = f.inputLabels.options[f.inputLabels.selectedIndex].value;
				var inputLouverType  = f.inputLouverType.options[f.inputLouverType.selectedIndex].value;

		// ---------------------------------------------
		// avoid math errors due to latitude = 0
		// ---------------------------------------------
 
				if (inputLatitude  == 0) { inputLatitude  = 0.000000001; }
		
		// ---------------------------------------------
		// latitude north-south adjustment
		// ---------------------------------------------
 
				var signedLatitude = inputLatitude;

				if (inputNorthSouth == "South") signedLatitude *= -1;

		// ---------------------------------------------
		// calculate value for each month and hour
		// ---------------------------------------------
 			
				var months = new Array ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
				
				for (var monthNum = 1; monthNum <= 12; monthNum ++) {
				
					var monthName = months[monthNum - 1];
					
					for (var hour = firstHour; hour <= lastHour; hour ++) {
					
						// get sun angles
				
						var sunAngles = new Object ();
						
						CalculateAltitudeAzimuth (sunAngles, signedLatitude, monthNum, hour);
						
						var altitudeAngle = sunAngles['altitude'] - 0;
						
						var azimuthAngle = sunAngles['azimuth'] - 0;
						
						// calculate solar-surface azimuth angle
						
						var solarSurfaceAzimuth = azimuthAngle + inputOrientation;
						
						if (solarSurfaceAzimuth > 180) {
						
							solarSurfaceAzimuth = solarSurfaceAzimuth - 360;
						}
						
						if (solarSurfaceAzimuth < -180) {
						
							solarSurfaceAzimuth = solarSurfaceAzimuth + 360;
						}
						
						// calculate shading if sun is shining on louvers
						
						if (altitudeAngle > 0) {
						
							if ((solarSurfaceAzimuth > -90) && (solarSurfaceAzimuth < 90)) {
						
								var projectedAltitudeAngle = Math.atan (Math.tan (altitudeAngle * degreesToRadians) / Math.cos (solarSurfaceAzimuth * degreesToRadians)) * radiansToDegrees;
	
								// adjust altitude angle for louver system type (default is 'blinds_up')
								
								if (inputLouverType == 'blinds_down') {
								
									projectedAltitudeAngle = projectedAltitudeAngle * -1;
								}
								
								else if (inputLouverType == 'trellis_away') {
								
									projectedAltitudeAngle = 90 - projectedAltitudeAngle;
								}
								
								else if (inputLouverType == 'trellis_toward') {
								
									projectedAltitudeAngle = (90 - projectedAltitudeAngle) * -1;
								}
								
								// if altitude angle > slat tilt
	
								if (projectedAltitudeAngle > inputSlatTilt) {
								
									var tipShadow = Math.sqrt (Math.pow (inputSlatDepth, 2) + Math.pow (inputSlatThickness, 2)) * (Math.cos ((inputSlatTilt - (Math.atan (inputSlatThickness / inputSlatDepth) * radiansToDegrees)) * degreesToRadians) * Math.tan (projectedAltitudeAngle * degreesToRadians) - Math.sin ((inputSlatTilt - (Math.atan (inputSlatThickness / inputSlatDepth) * radiansToDegrees)) * degreesToRadians));
	
									if (tipShadow > inputSlatSpacing) { 
									
										var shadingFraction = 1;
									}
									
									else {
									
										var shadingFraction = tipShadow / inputSlatSpacing;
									}
								}
								
								// if altitude angle < slat tilt
	
								else {
								
									var headShadow = -1 * ((inputSlatDepth * Math.sin ((90 - inputSlatTilt) * degreesToRadians) / Math.tan ((90 - projectedAltitudeAngle) * degreesToRadians)) - (inputSlatDepth * Math.cos ((90 - inputSlatTilt) * degreesToRadians)));
	
									var footShadow = inputSlatThickness * (Math.cos (inputSlatTilt * degreesToRadians) + (Math.sin (inputSlatTilt * degreesToRadians) * Math.tan (projectedAltitudeAngle * degreesToRadians)));
	
									if (headShadow < 0) { headShadow = 0; }
								
									if (footShadow < 0) { footShadow = 0; }
									
									var totalShadow = headShadow + footShadow;
								
									var shadingFraction = totalShadow / inputSlatSpacing;
								}
								
								// calculate sun percentage
								
								if (shadingFraction <= 0) {
								
									var sun = 10;
									
									var sunPercentage = '100';
								}
								
								else if (shadingFraction >= 1) {
								
									var sun = 0;
									
									var sunPercentage = '0';
								}
								
								else {
								
									var sun = 10 - Math.floor ((shadingFraction + 0.05) * 10);
									
									var sunPercentage = sun * 10;
								}
								
								// -----------   ---------   ----------- //
								// -----------   DEBUGGING   ----------- //
								// -----------   ---------   ----------- //
	
	/*
	
								if ((monthNum == 5) && (hour == 10)) {
								
									var feedback = "alt: " + FormatFloatString (altitudeAngle) + "<br>proj. alt: " + FormatFloatString (projectedAltitudeAngle) + "<br>shade fraction: " + FormatFloatString (shadingFraction);
								
									if (projectedAltitudeAngle > inputSlatTilt) {
	
										feedback = feedback + "<br>tip shadow: " + FormatFloatString (tipShadow);
									}
									
									else {
									
										feedback = feedback + "<br>head shadow: " + FormatFloatString (headShadow) + "<br>foot shadow: " + FormatFloatString (footShadow);
									}
									
									SetDivContent ('debug', feedback); 
								}
	*/
	
							}
						
							else {
							
								var sun = 'z';   // z = sun is above horizon but not shining on facade
	
								var sunPercentage = 0;
							}
						}
						
						// if sun is not shining on louvers
						
						else {
						
							var sun = 'x';   // x = sun is below horizon

							var sunPercentage = 0;
						}
						
						// output shading to cell
						
						var id = monthName + "_" + hour;
							
						var newClass = 'sun_' + sun;
						
						ChangeClass (id, newClass);
						
						if ((sun == 'x') || (sun == 'z') || (inputLabels == 'None')) {
						
							SetDivContent (id, '&nbsp;');
						}
						
						else {

							if (inputLabels == 'Shading') {
							
								sunPercentage = 100 - sunPercentage;
							}
							
							sunPercentage = sunPercentage + '%';
							
							SetDivContent (id, sunPercentage);
						}
					}
				}
			}
		}


	/////////////////////////////////////////////////////////
	//
	//  FUNCTION:  CalculateAltitudeAzimuth
	//
	/////////////////////////////////////////////////////////
	
 		function CalculateAltitudeAzimuth (sunAngles, signedLatitude, monthNum, hour) {

			var UT = hour;

			if (monthNum > 2) {
				var correctedYear = 2007;
				var correctedMonth = monthNum - 3;
			}
			
			else {
				var correctedYear = 2007 - 1;
				var correctedMonth = monthNum  + 9;
			}
	
			var t = ((UT / 24.0) + 15 + Math.floor (30.6 * correctedMonth + 0.5) + Math.floor (365.25 * (correctedYear - 1976)) - 8707.5) / 36525.0;
	
			var G = 357.528 + 35999.05 * t;
			G = NormalizeTo360 (G);
		
			var C = (1.915 * Math.sin (G * degreesToRadians)) + (0.020 * Math.sin (2.0 * G * degreesToRadians));
			
			var L = 280.460 + (36000.770 * t) + C;
			L = NormalizeTo360 (L);
			
			var alpha = L - 2.466 * Math.sin (2.0 * L * degreesToRadians) + 0.053 *  Math.sin (4.0 * L * degreesToRadians);
			
			var GHA = UT * 15 - 180 - C + L - alpha;
			GHA = NormalizeTo360 (GHA);
	
			var obliquity = 23.4393 - 0.013 * t;
			
			var declination = Math.atan (Math.tan (obliquity * degreesToRadians) * Math.sin (alpha * degreesToRadians)) * radiansToDegrees;

			var solarHourAngle = 15 * (UT - 12);
			
			var apparentSolarTime = UT;
			
			var solarMinutesAfterMidnight = hour * 60;

			var altitudeAngle = radiansToDegrees  * ArcSin (
				(Math.sin (signedLatitude         * degreesToRadians)  *
				 Math.sin (declination            * degreesToRadians)) -
				(Math.cos (signedLatitude         * degreesToRadians)  *
				 Math.cos (declination            * degreesToRadians)  *
				 Math.cos ((solarHourAngle + 180) * degreesToRadians)));

			var preAzimuthAngle = radiansToDegrees * ArcCos (
				 (Math.cos (declination    * degreesToRadians)   *
				((Math.cos (signedLatitude * degreesToRadians)   *
				  Math.tan (declination    * degreesToRadians))   +
				 (Math.sin (signedLatitude * degreesToRadians)   *
				  Math.cos ((solarHourAngle + 180) * degreesToRadians)))) /
				  Math.cos (altitudeAngle  * degreesToRadians));
				 
			if ((solarHourAngle > 0) && (solarHourAngle < 180)) { 
			
				var azimuthAngle = (360.0 - preAzimuthAngle) + (0 - 180.0);
			}

			else { 
			
				var azimuthAngle = preAzimuthAngle + (0 - 180.0);
			}
			
 			sunAngles['altitude'] = altitudeAngle;
 			sunAngles['azimuth' ] =  azimuthAngle;
 			
 			return true;
 		}





////////////////////////////////////////////////////////////////////////////////////
//  OTHER FUNCTIONS  
////////////////////////////////////////////////////////////////////////////////////

function NoEnter () {

	return !(window.event && window.event.keyCode == 13);
}

function ArcSin (theThing) {

	return (Math.asin (theThing));
}
	

function ArcCos (theThing) {

	return (Math.acos (theThing));
}


function MinutesToClockTime (totalMinutes, amPM) {

	var theHours = Math.floor (totalMinutes / 60);
	
	var theMinutes = Math.floor (totalMinutes % 60);

	if (theMinutes < 10) theMinutes = "0" + theMinutes;  // add leading "0" if necessary
	
	if (amPM == "24 hr") {
		if (theHours < 10) theHours = "0" + theHours;  // add leading "0" if necessary
		returnString = theHours + "" + theMinutes;
	}
	
	else {
		if (theHours < 12) {
			if (theHours == 0) theHours = 12;
			returnString = theHours + ":" + theMinutes + "am";
		}
		else {
			if (theHours == 12) theHours = 24;
			returnString = (theHours - 12) + ":" + theMinutes + "pm"
		}
	}
	
	return (returnString);
}


function NormalizeTo360 (theThing) {

	return (theThing - Math.floor (theThing / 360.0) * 360);
}


function NormalizeTo180 (theThing) {

	theThing = NormalizeTo360 (theThing);

	if (theThing > 180) { theThing = theThing - 360; }

	return (theThing);
}


function NormalizeTo24 (theThing) {

	return (theThing - Math.floor (theThing / 24.0) * 24);
}


function MonthStringToDays (whichMonth) {

	if (whichMonth == "Jan") return   (0);
	if (whichMonth == "Feb") return  (31);
	if (whichMonth == "Mar") return  (59);
	if (whichMonth == "Apr") return  (90);
	if (whichMonth == "May") return (120);
	if (whichMonth == "Jun") return (151);
	if (whichMonth == "Jul") return (181);
	if (whichMonth == "Aug") return (212);
	if (whichMonth == "Sep") return (243);
	if (whichMonth == "Oct") return (273);
	if (whichMonth == "Nov") return (304);
	if (whichMonth == "Dec") return (334);
		
	return ("Zeke the Solar Cat"); // you can't get here but I swear it made me do this
}


function MonthStringToMonthNum (whichMonth) {

	if (whichMonth == "Jan") return  (1);
	if (whichMonth == "Feb") return  (2);
	if (whichMonth == "Mar") return  (3);
	if (whichMonth == "Apr") return  (4);
	if (whichMonth == "May") return  (5);
	if (whichMonth == "Jun") return  (6);
	if (whichMonth == "Jul") return  (7);
	if (whichMonth == "Aug") return  (8);
	if (whichMonth == "Sep") return  (9);
	if (whichMonth == "Oct") return (10);
	if (whichMonth == "Nov") return (11);
	if (whichMonth == "Dec") return (12);
		
	return ("Zeke the Solar Cat"); // you can't get here but I swear it made me do this
}

function RemoveSemicolon (inputString) {

	// if second or third character is a semicolon, remove it (need to
	// do better error checking on input soon)
	
	if (inputString.substring (1,2) == ":") {
		return (inputString.substring(0,1) + inputString.substring(2,4));
	}

	if (inputString.substring (2,3) == ":") {
		return (inputString.substring(0,2) + inputString.substring(3,5));
	}

	return (inputString);
}

function FormatFloatString (theInput) {

	var negativeNumber = false;
	if (theInput < 0) {
		negativeNumber = true;
		theInput *= -1;
	}

	integerPortion = Math.floor (theInput); 
	decimalPortion = Math.round (theInput * 100) % 100;

	// added 7/17/99 to correct problem with 0.999 being rounded to 0.000 in decimal conversion:
	if ((decimalPortion == 0) && ((theInput - integerPortion) > 0.5)) {
		integerPortion += 1;
	}
	
	if (integerPortion < 10) integerPortionString = " " + integerPortion;   // add a space at beginning if necessary
 	else integerPortionString = "" + integerPortion;
        
	if (decimalPortion < 10) decimalPortionString = "0" + decimalPortion;   // add a leading zero if necessary
	else decimalPortionString = "" + decimalPortion;
	
	if (negativeNumber == true) return ("-" + integerPortionString + "." + decimalPortionString);
	else return (" " + integerPortionString + "." + decimalPortionString);
}



