Skip to content

Instantly share code, notes, and snippets.

@chriswmackey
Last active October 23, 2019 20:39
Show Gist options
  • Save chriswmackey/deacf5787b3f1a427809395ea6bceb43 to your computer and use it in GitHub Desktop.
Save chriswmackey/deacf5787b3f1a427809395ea6bceb43 to your computer and use it in GitHub Desktop.
Direct Sun Through a Facade
license: mit
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
#inputSliders { font-family:sans-serif;outline:none;margin-top:10px}
.inputgroup {border:none;}
.slider { width:400px;float:left;padding:10px;}
.slider2 { width:210px;float:left;padding:10px;}
label { float:left;font-weight:bold;padding-bottom:10px;}
input[type=range] { float:left;clear:left;margin-right:10px;width:320px;}
.slider2 input[type=range] { float:left;clear:left;margin-right:10px;width:130px;}
input[type=range]::-ms-track { background: transparent;border-color: transparent;color: transparent;-webkit-appearance: none}
input[type=range]::-webkit-slider-runnable-track { height: 5px;background:#7c7c7c; margin-top: -4px;}
input[type=range]::-webkit-slider-thumb { margin-top:-6px;}
#inputSliders p {padding-top:10px;}
</style>
</head>
<body>
<div id="inputSliders">
<form id="sliders" autocomplete="off">
<fieldset class="inputgroup">
<div class="slider" id="altitude">
<label>Altitude</label>
<input type="range" name="alt" id="alt" value="55" min="0" max="90" step = "1"><p id="altoutput">55°</p></div>
<div class="slider" id="azimuth">
<label>Azimuth</label>
<input type="range" name="az" id="az" value="150" min="0" max="360" step = "1"><p id="azoutput">150°</p></div>
<div class="slider2">
<label>Window Width</label>
<input type="range" name="width" id="width" value="5" min="1" max="15" step = "0.5"><p id="widthoutput">5</p></div>
<div class="slider2">
<label>Window Height</label>
<input type="range" name="height" id="height" value="8" min="1" max="12" step = "0.5"><p id="heightoutput">8</p></div>
<div class="slider2">
<label>Window X</label>
<input type="range" name="wx" id="wx" value="4" min="0" max="15" step = "0.5"><p id="xoutput">4</p></div>
<div class="slider2">
<label>Window Y</label>
<input type="range" name="wy" id="wy" value="4" min="0" max="15" step = "0.5"><p id="youtput">4</p></div>
</fieldset>
</form>
</div>
<div id="content">
</div>
<script>
// Global variables
var dimensions = {x: 60, y:30}
var gridSize = .25
var increment = gridSize/2
var floorH = 3
// Create a colored grid of results.
var svgWidth = 960
var svgHeight = 400
var padding = {top: 10, left:100, right:100}
var cellDim = parseInt((svgWidth - padding.left - padding.right)/dimensions.x)
// Create a grid of points
var svg = d3.select("#content").append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
var pointGrid = []
var dataset = []
for (var i = 0; i < dimensions.x; i++) {
for (var j = 0; j < dimensions.y; j++) {
pointGrid.push({x:(i*gridSize)+increment, y:-(j*gridSize)-increment, z:floorH})
svg.append("rect")
.attr("x", padding.left + cellDim*i)
.attr("y", padding.top + cellDim*j)
.attr("width", cellDim)
.attr("height", cellDim)
.attr('fill', 'red')
.style("stroke", "#000")
.style("stroke-width", "0.05em");
}
}
// Create the facade graphic
var facadeSvg = d3.select("#content").append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
facadeSvg.append("rect")
.attr("x", padding.left)
.attr("y", padding.top)
.attr("width", (cellDim*dimensions.x))
.attr("height", (cellDim*15))
.attr('fill', '#b5b5b5')
.style("stroke-width", "0.05em");
// Get inputs
var Alt = parseFloat($("#alt").val()); // Altitude
var Az = parseFloat($("#az").val()); // Azimuth
var Width = parseFloat($("#width").val());
var Height = parseFloat($("#height").val());
var WX = parseFloat($("#wx").val());
var WY = parseFloat($("#wy").val());
// Update the displayed altitude and azimuth
$("#alt").on("input", function(event) {
Alt = parseFloat($(this).val());
$("#altoutput").text(Alt.toString() + '°');
sunVec = polarToCartesian(Az, Alt, 1);
updateData();
updateChart();
});
$("#az").on("input", function(event) {
Az = parseFloat($(this).val());
$("#azoutput").text(Az.toString() + '°');
sunVec = polarToCartesian(Az, Alt, 1);
updateData();
updateChart();
});
$("#width").on("input", function(event) {
Width = parseFloat($(this).val());
$("#widthoutput").text(Width.toString());
lines = updateWindowDim()
updateData();
updateChart();
});
$("#height").on("input", function(event) {
Height = parseFloat($(this).val());
$("#heightoutput").text(Height.toString());
lines = updateWindowDim()
updateData();
updateChart();
});
$("#wx").on("input", function(event) {
WX = parseFloat($(this).val());
$("#xoutput").text(WX.toString());
lines = updateWindowDim()
updateData();
updateChart();
});
$("#wy").on("input", function(event) {
WY = parseFloat($(this).val());
$("#youtput").text(WY.toString());
lines = updateWindowDim()
updateData();
updateChart();
});
// Window dimensions
function updateWindowDim(){
var windowDim = [{x:WX, y:WY}, {x:WX+Width, y:WY+Height}]
var linex = [[windowDim[0].x, -dimensions.y*gridSize], [windowDim[1].x, -dimensions.y*gridSize]]
var liney = [[-dimensions.y*gridSize, windowDim[0].y], [-dimensions.y*gridSize, windowDim[1].y]]
facadeSvg.selectAll('.window').remove();
facadeSvg.append("rect")
.attr("x", padding.left + (cellDim*WX)/gridSize)
.attr("y", padding.top + (cellDim*15) - (cellDim*WY) - (cellDim*Height))
.attr("width", cellDim*Width*(1/gridSize))
.attr("height", cellDim*Height)
.attr('fill', "#bee9ee")
.attr("class", "window");
return [linex, liney]
}
// Convert degrees to radians.
var RAD2DEG = 180 / Math.PI
var DEG2RAD = Math.PI / 180
// Convert sphereical to cartesian.
function polarToCartesian( lon, lat, radius ) {
var phi = ( 90 - lat ) * DEG2RAD
var theta = ( -lon ) * DEG2RAD
return {
x: -(radius * Math.sin(phi) * Math.sin(theta)),
y: radius * Math.sin(phi) * Math.cos(theta),
z: radius * Math.cos(phi),
}
}
// 2D Vector Math
function subtract(a, b){
return [a[0]-b[0], a[1]-b[1]];
}
function dotProduct(a, b){
return a[0] * b[0] + a[1] * b[1];
}
function crossProduct(a, b){
return a[0] * b[1] - b[0] * a[1]
}
// Check for intersection of ray and line.
function rayLineIntersect(rayOrigin, rayDirection, point1, point2){
v1 = subtract(rayOrigin, point1);
v2 = subtract(point2, point1);
v3 = [-rayDirection[1], rayDirection[0]];
dot = dotProduct(v2, v3);
if (Math.abs(dot) < 0.000001) {
return false;
}
t1 = (crossProduct(v2, v1)) / dot;
t2 = dotProduct(v1,v3) / dot;
if (t1 >= 0.0 && (t2 >= 0.0 && t2 <= 1.0)){
return true;
}
return false;
}
// Perform intersection calculation.
var sunVec = polarToCartesian(Az, Alt, 1)
function updateData(){
xySunVec = [sunVec.x, sunVec.y]
yzSunVec = [sunVec.y, sunVec.z]
dataset = []
for (var i = 0; i < pointGrid.length; i++) {
rayOriginxy = [pointGrid[i].x,pointGrid[i].y]
rayOriginyz = [pointGrid[i].y,pointGrid[i].z]
xyIntersect = rayLineIntersect(rayOriginxy, xySunVec, lines[0][0], lines[0][1])
yzIntersect = rayLineIntersect(rayOriginyz, yzSunVec, lines[1][0], lines[1][1])
if (xyIntersect == true && yzIntersect == true){
dataset.push(true)
} else {
dataset.push(false)
}
}
}
function updateChart() {
svg.selectAll('rect')
.data(dataset)
.attr('fill', function(d){if(d == true){return 'red'}else{return 'blue'}})
}
var lines = updateWindowDim()
updateData()
updateChart()
d3.select(self.frameElement).style("height", 960 + "px");
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment