Last active
June 30, 2017 04:51
-
-
Save bmershon/231160f11b33ac780bd86b5a7e891576 to your computer and use it in GitHub Desktop.
An Interesting Lattice
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
height: 960 | |
border: no | |
license: MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<title>Reflections</title> | |
<style> | |
body { | |
cursor: crosshair; | |
} | |
.vertex { | |
pointer-events: none; | |
stroke: none; | |
} | |
.vertex.coprime { | |
stroke: #000; | |
stroke-width: 1px; | |
} | |
.vertex.invalid { | |
fill-opacity: 0.1; | |
stroke-opacity: 0.3; | |
} | |
.node { | |
fill: #fff; | |
fill-opacity: 0; | |
stroke-width: 0; | |
} | |
.node.active { | |
stroke-width: 1px; | |
stroke: #ccc; | |
stroke-dasharray: 5, 5; | |
} | |
.node.terminal { | |
stroke-width: 1px; | |
stroke: #000; | |
} | |
.edge { | |
stroke: #ccc; | |
stroke-width: 1px; | |
pointer-events: none; | |
} | |
.edge.reflection { | |
stroke: #333; | |
stroke-width: 1px; | |
} | |
.beam { | |
stroke-width: 1.5px; | |
stroke: rgba(240, 0, 0, 0.8); | |
} | |
.extension { | |
stroke-width: 1px; | |
stroke: rgba(240, 0, 0, 0.8); | |
stroke-dasharray: 5, 5; | |
} | |
text { | |
fill: #aaa; | |
font-size: 16px; | |
font-family: monospace; | |
pointer-events: none; | |
} | |
text.valid { | |
fill: #000; | |
} | |
</style> | |
<svg width="960" height="960"></svg> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="intersection.debug.js"></script> | |
<script> | |
// 2017, Brooks Mershon | |
var svg = d3.select("svg"), | |
width = +svg.attr("width"), | |
height = +svg.attr("height"), | |
margin = { top: 20, left: 20, right: 20, bottom: 20}; | |
// skewX(30) rotate(240) | |
var g = svg.append("g") | |
.attr("transform", "translate(" + width / 2 + "," + margin.top + ")"); | |
// The number of dots in the last row. | |
var N = 25; | |
var side = (width - margin.left - margin.right) / N; | |
var height = side * Math.sqrt(3) / 2; | |
var emitter = new Vertex(0, 0, 0, 0, 0); | |
var vertices = [ emitter ]; | |
// Hash table for looking up vertices. | |
var table = { 0: { 0: vertices[0] }}; | |
var edges = []; | |
var beam = { | |
source: vertices[0], | |
target: vertices[0] | |
}; | |
var extendedBeam = { | |
source: vertices[0], | |
target: vertices[0] | |
}; | |
// Create layout of vertices and edges. | |
previousRow = vertices.slice(0, 1); | |
for (let i = 1; i < N; i++) { | |
let row = new Array(i + 1); | |
for (let j = 0; j < i + 1; j++) { | |
let label, | |
newVertex, | |
newEdge; | |
// Compute position. | |
let x = -side * i / 2 + j * side; | |
let y = i * height; | |
// Infer label. | |
if (j === 0) { // First vertex in row. | |
let t = previousRow[0]; | |
label = (t.label + 1) % 3; | |
newVertex = new Vertex(x, y, label, i, j); | |
edges.push(new Edge(t, newVertex, 1)); | |
} else { // Insect two other vertices of the triangle to infer vertex label. | |
let p = row[j - 1]; | |
let q = previousRow[j - 1]; | |
let r = previousRow[j]; | |
label = inferLabel(p.label, q.label); | |
newVertex = new Vertex(x, y, label, i, j); | |
edges.push( | |
new Edge(p, newVertex, 1), | |
new Edge(q, newVertex, 1)); | |
if (j < i) { | |
edges.push(new Edge(r, newVertex, 1)); | |
} | |
} | |
if (!table[i]) { | |
table[i] = {}; | |
} | |
table[i][j] = newVertex; | |
row[j] = newVertex; | |
} | |
// Keep auxiliary information for the next row to infer colorings using the | |
// previously created row of vertices. | |
previousRow = row; | |
vertices = vertices.concat(row); | |
} | |
// Create a vertex. | |
function Vertex(x, y, label, row, index) { | |
this.x = x; | |
this.y = y; | |
this.label = label; | |
this.row = row; | |
this.index = index; | |
} | |
// Create an edge. | |
function Edge(a, b, label) { | |
this.a = a; | |
this.b = b; | |
this.label = label; | |
} | |
// Returns the implied coloring if the other two vertices of a triangle are | |
// colored x and y. The colors are 0, 1, or 2. | |
function inferLabel(x, y) { | |
return ((3 - (x + y) % 3) % 3); | |
} | |
var vertexColor = d3.scaleOrdinal() | |
.domain([0, 2]) | |
.range(["#EB9394", "#96D096", "#8FBBDA"]); | |
var laser = g.append("line") | |
.datum(beam) | |
.classed("beam", true) | |
.attr("x1", (d) => d.source.x) | |
.attr("y1", (d) => d.source.y) | |
.attr("x2", (d) => d.target.x) | |
.attr("y2", (d) => d.target.y); | |
var extension = g.append("line") | |
.datum(extendedBeam) | |
.classed("extension", true) | |
.attr("x1", (d) => d.source.x) | |
.attr("y1", (d) => d.source.y) | |
.attr("x2", (d) => d.target.x) | |
.attr("y2", (d) => d.target.y); | |
var line = g.selectAll(".edge") | |
.data(edges) | |
.enter().append("line") | |
.attr("x1", (d) => d.a.x) | |
.attr("y1", (d) => d.a.y) | |
.attr("x2", (d) => d.b.x) | |
.attr("y2", (d) => d.b.y) | |
.classed("edge", true); | |
var circle = g.selectAll(".vertex") | |
.data(vertices); | |
var circleGroup = circle.enter().append("g"); | |
var node = circleGroup.append("circle") | |
.attr("cx", (d) => d.x) | |
.attr("cy", (d) => d.y) | |
.classed("node", true) | |
.attr("r", side * 0.49) | |
.on("mouseover", inspect) | |
.on("mouseleave", function(d, i) { | |
d3.select(this).classed("active", false); | |
d3.select(this).classed("terminal", false); | |
}); | |
var vertex = circleGroup.append("circle") | |
.attr("cx", (d) => d.x) | |
.attr("cy", (d) => d.y) | |
.attr("r", Math.min(0.1 * side, 5)) | |
.classed("vertex", true) | |
.classed("coprime", (d) => coprime(d.row, d.index)) | |
.attr("fill", (d, i) => vertexColor(d.label)); | |
function inspect(d, i) { | |
let reflections = 0; | |
let terminal = d; | |
let factor = gcd(terminal.row, terminal.index); | |
if (factor > 1) { | |
terminal = table[terminal.row / factor][terminal.index / factor]; | |
} | |
beam.target = terminal; | |
extendedBeam.source = beam.target; | |
extendedBeam.target = d; | |
d3.select(this).classed((d === terminal && d !== emitter) | |
? "terminal" | |
: "active", true); | |
vertex | |
.classed("invalid", (vertex) => { | |
return d.label === 0 && d === terminal | |
&& ((!coprime(vertex.row, vertex.index) | |
|| vertex.row !== d.row | |
|| vertex.label > 0)); | |
}); | |
// Horrible intersection test against all edges. | |
line.each(function(edge, i) { | |
let include = false; | |
if (edge.a.row < terminal.row && edge.b.row < terminal.row | |
&& emitter !== edge.a && emitter !== edge.b | |
&& terminal !== edge.a && terminal !== edge.b) { | |
include = intersection(0, 0, terminal.x, terminal.y, | |
edge.a.x, edge.a.y, edge.b.x, edge.b.y); | |
if (include) reflections++; | |
} | |
d3.select(this).classed("reflection", include); | |
}); | |
laser | |
.datum(beam) | |
.attr("x1", (d) => d.source.x) | |
.attr("y1", (d) => d.source.y) | |
.attr("x2", (d) => d.target.x) | |
.attr("y2", (d) => d.target.y); | |
extension | |
.datum(extendedBeam) | |
.attr("x1", (d) => d.source.x) | |
.attr("y1", (d) => d.source.y) | |
.attr("x2", (d) => d.target.x) | |
.attr("y2", (d) => d.target.y); | |
} | |
// Returns the greatest common denominator. | |
function gcd(a, b) { | |
if (!b) { | |
return a; | |
} | |
return gcd(b, a % b); | |
}; | |
function coprime(a, b) { | |
return gcd(a, b) === 1; | |
} | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Code snippet used from https://gist.github.com/Joncom/e8e8d18ebe7fe55c3894. | |
// Returns true if an intersection exists and false otherwise. | |
function intersection(p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, p3_x, p3_y) { | |
let s1_x, s1_y, s2_x, s2_y; | |
s1_x = p1_x - p0_x; | |
s1_y = p1_y - p0_y; | |
s2_x = p3_x - p2_x; | |
s2_y = p3_y - p2_y; | |
let s, t; | |
s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y); | |
t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y); | |
return (s >= 0 && s <= 1 && t >= 0 && t <= 1); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment