Add projects and tutorials
This commit is contained in:
7
tutorials/convex_hull/dot.js
Normal file
7
tutorials/convex_hull/dot.js
Normal file
@@ -0,0 +1,7 @@
|
||||
class dot{
|
||||
|
||||
constructor(x, y){
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
95
tutorials/convex_hull/index.html
Normal file
95
tutorials/convex_hull/index.html
Normal file
@@ -0,0 +1,95 @@
|
||||
<DOCTYPE! html>
|
||||
<html>
|
||||
<title>Convex Hull</title>
|
||||
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Inconsolata" rel="stylesheet">
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.2.0/p5.min.js"
|
||||
integrity="sha512-b/htz6gIyFi3dwSoZ0Uv3cuv3Ony7EeKkacgrcVg8CMzu90n777qveu0PBcbZUA7TzyENGtU+qZRuFAkfqgyoQ=="
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/latest.js?config=AM_CHTML"></script>
|
||||
|
||||
<button id="backButton" onclick="window.location.href='/#tutorials'">Back</button>
|
||||
|
||||
|
||||
<head>
|
||||
<h1>Convex Hull - Jarvis' Marsh</h1>
|
||||
|
||||
<p>A convex hull is where a convex polygon is constructed to bound a set of points on a 2D plane. So, given all
|
||||
points in the set P, what is the smallest convex polygon we can construct that bounds all points in P.</p>
|
||||
<br>
|
||||
<p>Pseudo code:</p>
|
||||
<div class="psudoCode">
|
||||
<ul>
|
||||
<li>Randomly place n points on a 2D plane.</li>
|
||||
<li>Find the leftmost point/the point with the smallest x value. Called `gamma`.</li>
|
||||
<li>Find the vector which is the <i>most left</i> which connects `gamma` to point q in set P.</li>
|
||||
<li>Store `gamma` and q in a list and repeat until q = `gamma`.</li>
|
||||
</div>
|
||||
<br>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p>This canvas starts off by generating a set amount of points, placing them at random locations around the
|
||||
screen and adding them to an array. It then finds the index of the point in the array - `vertices[]` - with
|
||||
the lowest x value. It then loops through the array finding all vector's originating from `gamma`,
|
||||
<a>`vec(gammaP(i))`</a>.
|
||||
</p>
|
||||
<p>We then examine each vertex and evaluate which one is the <i>leftmost</i> from the perspective of point
|
||||
`gamma`. Say in this case `P(q)` is the leftmost vector so we push q onto the stack.</p><br>
|
||||
<p>Stack: `((q), (gamma))`</p><br>
|
||||
<p>We then repeat until we reach our starting point, which in this case means our starting point is the same as
|
||||
the next point (`q=gamma`)</p>
|
||||
<img class="examples" src="stepByStep.png">
|
||||
|
||||
<p>Now the question of how to calculate the <i>leftness</i> of a vector compared to another. We can use the
|
||||
<a>cross product</a> in 2D.
|
||||
</p>
|
||||
<p><a>`vecu times vecv = u`<sub>`x`</sub>`*v`<sub>`y`</sub>` - u`<sub>`y`</sub>`*v`<sub>`x`</sub></a> If the
|
||||
cross product is greater than 0, the vector is to the left. If it is equal to 0 then the vectors are
|
||||
colinear</p>
|
||||
|
||||
<p><b>Note:</b> to convert coordinates to a vector, find the difference between your origin point and your
|
||||
target point</p>
|
||||
|
||||
<div class="psudoCode">
|
||||
<a>for (var i = 0; i < vertices.length; i++){</a><br>
|
||||
<a> if (i != originPoint){</a><br>
|
||||
<a> x1 = vertices[recordLeftVector].x - vertices[originPoint].x;</a><br>
|
||||
<a> y1 = vertices[recordLeftVector].y - vertices[originPoint].y;</a><br><br>
|
||||
|
||||
<a> x2 = vertices[i].x - vertices[originPoint].x;</a><br>
|
||||
<a> y2 = vertices[i].y - vertices[originPoint].y;</a><br><br>
|
||||
|
||||
<a> if ((y2 * x1) - (y1 * x2) <= 0){</a><br>
|
||||
<a> recordLeftVector = i;</a><br>
|
||||
<a> }</a><br>
|
||||
|
||||
<a> }</a><br>
|
||||
<a>}</a><br>
|
||||
</div>
|
||||
|
||||
<p>Now we just have to add in a little piece of code at the bottom of this function to: increment the iteration
|
||||
number so we don't recurse until eternity, add our leftmost vector to the route stack and call the function
|
||||
again with the next point as a parameter.</p>
|
||||
|
||||
<div class="psudoCode">
|
||||
<a>if (originPoint != recordLeftVector && iterations < 75){</a><br>
|
||||
<a> route[iterations] = recordLeftVector;</a><br>
|
||||
<a> iterations += 1;</a><br>
|
||||
<a> findLeftLine(recordLeftVector);</a><br>
|
||||
|
||||
<a>}</a><br>
|
||||
</div><br><br>
|
||||
</body>
|
||||
|
||||
<script src="sketch.js"></script>
|
||||
<script src="dot.js"></script>
|
||||
|
||||
<!--https://www.youtube.com/watch?v=Vu84lmMzP2o<br>
|
||||
http://jeffe.cs.illinois.edu/teaching/compgeom/notes/01-convexhull.pdf</p>-->
|
||||
|
||||
</html>
|
||||
102
tutorials/convex_hull/sketch.js
Normal file
102
tutorials/convex_hull/sketch.js
Normal file
@@ -0,0 +1,102 @@
|
||||
var jarvisMarsh = function( p ){
|
||||
|
||||
const numPoints = 12;
|
||||
var vertices = [];
|
||||
var route = [];
|
||||
let iterations = 0;
|
||||
|
||||
p.setup = function(){
|
||||
p.createCanvas(750, 500);
|
||||
p.background(217);
|
||||
|
||||
button = p.createButton('Reset Button')
|
||||
button.mousePressed(p.resetSketch);
|
||||
|
||||
p.initValues();
|
||||
|
||||
}
|
||||
|
||||
p.draw = function(){}
|
||||
|
||||
|
||||
p.drawLine = function(){
|
||||
let r = p.findLeftPoint();
|
||||
route[iterations] = r;
|
||||
|
||||
p.findLeftLine(r);
|
||||
|
||||
p.stroke(215, 75, 75);
|
||||
p.strokeWeight(1);
|
||||
for (var i = 0; i < route.length - 1; i++){
|
||||
p.line(vertices[route[i]].x, vertices[route[i]].y, vertices[route[i+1]].x, vertices[route[i+1]].y)
|
||||
}
|
||||
}
|
||||
|
||||
p.findLeftLine = function(originPoint){
|
||||
var recordLeftVector = 1;
|
||||
let x1, x2, y1, y2;
|
||||
|
||||
for (var i = 0; i < vertices.length; i++){
|
||||
if (i != originPoint){
|
||||
x1 = vertices[recordLeftVector].x - vertices[originPoint].x;
|
||||
y1 = vertices[recordLeftVector].y - vertices[originPoint].y;
|
||||
|
||||
|
||||
|
||||
x2 = vertices[i].x - vertices[originPoint].x;
|
||||
y2 = vertices[i].y - vertices[originPoint].y;
|
||||
|
||||
//if the result if positive then vector u is left of vector v
|
||||
//where u and v are both vectors from the target point
|
||||
//If its equal to 0 then they're colinear. This is also good :)
|
||||
if ((y2 * x1) - (y1 * x2) <= 0){
|
||||
recordLeftVector = i;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (originPoint != recordLeftVector && iterations < 75){
|
||||
route[iterations] = recordLeftVector;
|
||||
iterations += 1;
|
||||
p.findLeftLine(recordLeftVector);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
p.findLeftPoint = function(){
|
||||
let recordLeft = 0;
|
||||
|
||||
for (var i = 0; i < vertices.length; i++){
|
||||
if (vertices[i].x < vertices[recordLeft].x){
|
||||
recordLeft = i;
|
||||
}
|
||||
}
|
||||
return recordLeft;
|
||||
}
|
||||
|
||||
p.resetSketch = function(){
|
||||
vertices.length = 0;
|
||||
p.clear()
|
||||
p.background(217);
|
||||
p.initValues();
|
||||
p.findLeftLine();
|
||||
p.draw();
|
||||
}
|
||||
|
||||
p.initValues = function(){
|
||||
p.stroke(0);
|
||||
p.strokeWeight(7);
|
||||
|
||||
var bufferx = p.width * 0.0625;
|
||||
var buffery = p.height * 0.0625;
|
||||
|
||||
for (var i = 0; i < numPoints; i++){
|
||||
vertices[i] = new dot(p.random(bufferx, p.width - bufferx), p.random(buffery, p.height - buffery));
|
||||
p.point(vertices[i].x, vertices[i].y);
|
||||
}
|
||||
iterations = 0;
|
||||
p.drawLine();
|
||||
}
|
||||
};
|
||||
var myp5 = new p5(jarvisMarsh, 'c1');
|
||||
BIN
tutorials/convex_hull/stepByStep.png
Normal file
BIN
tutorials/convex_hull/stepByStep.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
71
tutorials/convex_hull/style.css
Normal file
71
tutorials/convex_hull/style.css
Normal file
@@ -0,0 +1,71 @@
|
||||
h1{
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0 0 15px 0;
|
||||
font-weight: 700;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
h2{
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
margin: 0;
|
||||
padding: 15px 0 15px 0;
|
||||
font-weight: 650;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
p{
|
||||
margin: 1px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 19px;
|
||||
padding: 5px 0px 5px 0px;
|
||||
}
|
||||
|
||||
.psudoCode{
|
||||
font-family: 'Inconsolata', monospace;
|
||||
font-size: 19px;
|
||||
background-color: #D9D9D9;
|
||||
border: 1px solid;
|
||||
padding: 5px;
|
||||
box-shadow: 5px 5px #888888;
|
||||
}
|
||||
|
||||
a{
|
||||
font-family: 'Inconsolata', monospace;
|
||||
font-size: 19px;
|
||||
background-color: #D9D9D9;
|
||||
|
||||
}
|
||||
|
||||
button{
|
||||
background-color: white;
|
||||
border: 2px solid #555555;
|
||||
color: black;
|
||||
padding: 16px 32px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
margin: 4px 2px;
|
||||
-webkit-transition-duration: 0.4s; /* Safari */
|
||||
transition-duration: 0.4s;
|
||||
cursor: pointer;
|
||||
float:right;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #555555;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.examples{
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
html{
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE10+/Edge */
|
||||
user-select: none; /* Standard */
|
||||
}
|
||||
BIN
tutorials/midpoint_displacement/Jagged_Terrain.jpg
Normal file
BIN
tutorials/midpoint_displacement/Jagged_Terrain.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
8
tutorials/midpoint_displacement/dot.js
Normal file
8
tutorials/midpoint_displacement/dot.js
Normal file
@@ -0,0 +1,8 @@
|
||||
class dot{
|
||||
|
||||
constructor(x, y){
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
}
|
||||
104
tutorials/midpoint_displacement/index.html
Normal file
104
tutorials/midpoint_displacement/index.html
Normal file
@@ -0,0 +1,104 @@
|
||||
<html>
|
||||
<title>Midpoint Displacement</title>
|
||||
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Inconsolata" rel="stylesheet">
|
||||
<link rel"shortcut icon" type"image/ico" href"/Media/iconImage.ico">
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
|
||||
<button id="backButton" onclick="window.location.href='/#tutorials'">Back</button>
|
||||
|
||||
<head>
|
||||
|
||||
<h1>Midpoint Displacement</h1>
|
||||
|
||||
<p>Midpoint displacement is a common way to generate terrains in video
|
||||
games. Instead of manually creating a stage you can make an
|
||||
algorithm create one based off randomness. This is faster to
|
||||
calculate than a perlin noise stage which is why it can be found
|
||||
frequently in older video game titles.</p><br>
|
||||
<p>Psudo code:</p>
|
||||
<div class="psudoCode">
|
||||
<ul>
|
||||
<li>Create two points.</li>
|
||||
<li>Find the center x coordinate.</li>
|
||||
<li>Create a new point in between the two first points with some
|
||||
offset in the y coorindate.</li>
|
||||
<li>Repeat. (can be recursive)</li>
|
||||
</div>
|
||||
<br>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p>This canvas starts off with two points in an array called
|
||||
<a>points[]</a>, one at <a>height/2</a> and one at
|
||||
<a>math.random(-vertical_displacement, vertical_displacement)</a>.
|
||||
The center point is found and a new point is made and added to the
|
||||
array. This new point is offset on the y-axis by an amount however
|
||||
we can't use the same <a>vertical_displacement</a> as we did before
|
||||
as we'd end up with a very jagged terrain.
|
||||
</p>
|
||||
<img class="examples" src="Jagged_Terrain.jpg">
|
||||
|
||||
<p>Instead we have to iterate our variable -
|
||||
<a>vertical_displacement</a> - based on the iteration number of the
|
||||
sketch, in order to generate a smooth stage/terrain.
|
||||
</p>
|
||||
|
||||
<div class="psudoCode">
|
||||
<a>vertical_displacement = points[0].y + points[1].y / 2;</a><br>
|
||||
<a>vertical_displacement *= 2 ** (-roughness);</a>
|
||||
</div>
|
||||
|
||||
<p>The top formula is the inital value. The second is applied very
|
||||
iteration of the sketch.</p>
|
||||
<p>Right now we can add one point to the list, time to add several! This
|
||||
means for loops. But before we can loop through our array how do we
|
||||
know which point should connect to which? We need to sort the array
|
||||
based on x value, so that the points read off from left to right.
|
||||
</p>
|
||||
<p>I just used a bubble sort for simplicity however it's worth pointing
|
||||
out that the number of points grows exponetially - <a>O(2^n)</a>
|
||||
where n = iterations - so after just 10 iterations <a>points.length
|
||||
= 1024</a>. A merge sort could be used to speed up computation
|
||||
times.</p>
|
||||
<p>Now we can use a for loop to go through our array and compare the ith
|
||||
item to the (i+1)th item, and then update <a>iterations</a> and
|
||||
<a>vertical_displacement</a>.
|
||||
</p>
|
||||
|
||||
<div class="psudoCode">
|
||||
<a>var pLength = points.length;</a><br><br>
|
||||
|
||||
<a> for (var i = 0; i < pLength - 1; i++){</a><br>
|
||||
|
||||
<a> var midX = (points[i].x + points[i +
|
||||
1].x) / 2;</a><br>
|
||||
<a> var midY = (points[i].y + points[i +
|
||||
1].y) / 2;</a><br>
|
||||
<a> midY += random(-vertical_displacement,
|
||||
vertical_displacement);</a><br>
|
||||
|
||||
<a> points[pLength + i] = new point(midX,
|
||||
midY);</a><br>
|
||||
<a> }</a><br><br>
|
||||
|
||||
<a>iterations += 1;</a><br>
|
||||
<a>vertical_displacement *= 2 ** (-roughness);</a><br>
|
||||
|
||||
<a>bubbleSort();</a><br>
|
||||
</div>
|
||||
<h2>NOTE: Do not use points.length to define your for loop as we are
|
||||
increasing the length of the array within the loop</h2>
|
||||
<!--<span id="c1"></span>-->
|
||||
</body>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.2.0/p5.min.js"
|
||||
integrity="sha512-b/htz6gIyFi3dwSoZ0Uv3cuv3Ony7EeKkacgrcVg8CMzu90n777qveu0PBcbZUA7TzyENGtU+qZRuFAkfqgyoQ=="
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="sketch.js"></script>
|
||||
<script src="dot.js"></script>
|
||||
|
||||
|
||||
</html>
|
||||
90
tutorials/midpoint_displacement/sketch.js
Normal file
90
tutorials/midpoint_displacement/sketch.js
Normal file
@@ -0,0 +1,90 @@
|
||||
var MidDisplacement = function( p ){
|
||||
|
||||
var points = [];
|
||||
var iterations = 1;
|
||||
const roughness = 1;
|
||||
var vertical_displacement;
|
||||
let button;
|
||||
|
||||
p.setup = function(){
|
||||
p.createCanvas(800, 500);
|
||||
p.frameRate(2);
|
||||
p.background(217);
|
||||
|
||||
button = p.createButton('Reset Button')
|
||||
//button.position(p.width, p.height);
|
||||
button.mousePressed(p.resetSketch);
|
||||
p.initValues();
|
||||
|
||||
}
|
||||
|
||||
p.draw = function(){}
|
||||
|
||||
p.bubbleSort = function() {
|
||||
var length = points.length;
|
||||
//Number of passes
|
||||
for (var i = 0; i < length; i++) {
|
||||
//Notice that j < (length - i)
|
||||
for (var j = 0; j < (length - i - 1); j++) {
|
||||
//Compare the x values
|
||||
if(points[j].x > points[j+1].x) {
|
||||
//Swap the numbers
|
||||
var tmp = points[j]; //Temporary variable to hold the current number
|
||||
points[j] = points[j+1]; //Replace current number with adjacent number
|
||||
points[j+1] = tmp; //Replace adjacent number with current number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.mouseClicked = function(){
|
||||
if (iterations < 11){
|
||||
var pLength = points.length;
|
||||
|
||||
for (var i = 0; i < pLength - 1; i++){
|
||||
|
||||
var midX = (points[i].x + points[i + 1].x) / 2;
|
||||
var midY = (points[i].y + points[i + 1].y) / 2;
|
||||
midY += p.random(-vertical_displacement, vertical_displacement);
|
||||
|
||||
points[pLength + i] = new dot(midX, midY);
|
||||
}
|
||||
|
||||
iterations += 1;
|
||||
vertical_displacement *= 2 ** (-roughness);
|
||||
|
||||
p.bubbleSort();
|
||||
|
||||
p.clear();
|
||||
p.background(215);
|
||||
|
||||
for (var i = 0; i < pLength; i++){
|
||||
p.strokeWeight(4);
|
||||
p.stroke(0);
|
||||
p.point(points[i].x * 2, points[i].y);
|
||||
|
||||
p.strokeWeight(1);
|
||||
p.stroke(215, 15, 15);
|
||||
p.line(points[i].x * 2, points[i].y, points[i+1].x * 2, points[i+1].y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.initValues = function(){
|
||||
points[0] = new dot(0, p.height/2);
|
||||
points[1] = new dot(p.width, p.height/2);
|
||||
|
||||
vertical_displacement = points[0].y + points[1].y / 2;
|
||||
vertical_displacement *= 2 ** (-roughness);
|
||||
|
||||
iterations = 1;
|
||||
}
|
||||
|
||||
p.resetSketch = function(){
|
||||
points.length = 0;
|
||||
p.clear()
|
||||
p.background(217);
|
||||
p.initValues();
|
||||
}
|
||||
};
|
||||
var myp5 = new p5(MidDisplacement, 'c1');
|
||||
71
tutorials/midpoint_displacement/style.css
Normal file
71
tutorials/midpoint_displacement/style.css
Normal file
@@ -0,0 +1,71 @@
|
||||
h1{
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0 0 15px 0;
|
||||
font-weight: 700;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
h2{
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
margin: 0;
|
||||
padding: 15px 0 15px 0;
|
||||
font-weight: 650;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
p{
|
||||
margin: 1px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 19px;
|
||||
padding: 5px 0px 5px 0px;
|
||||
}
|
||||
|
||||
.psudoCode{
|
||||
font-family: 'Inconsolata', monospace;
|
||||
font-size: 19px;
|
||||
background-color: #D9D9D9;
|
||||
border: 1px solid;
|
||||
padding: 5px;
|
||||
box-shadow: 5px 5px #888888;
|
||||
}
|
||||
|
||||
a{
|
||||
font-family: 'Inconsolata', monospace;
|
||||
font-size: 19px;
|
||||
background-color: #D9D9D9;
|
||||
|
||||
}
|
||||
|
||||
button{
|
||||
background-color: white;
|
||||
border: 2px solid #555555;
|
||||
color: black;
|
||||
padding: 16px 32px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
margin: 4px 2px;
|
||||
-webkit-transition-duration: 0.4s; /* Safari */
|
||||
transition-duration: 0.4s;
|
||||
cursor: pointer;
|
||||
float:right;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #555555;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.examples{
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
html{
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE10+/Edge */
|
||||
user-select: none; /* Standard */
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
83
tutorials/summed_area/cStyle.scss
Normal file
83
tutorials/summed_area/cStyle.scss
Normal file
@@ -0,0 +1,83 @@
|
||||
@import "../nord.scss";
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
|
||||
|
||||
$roboto-slab: 'Roboto', sans-serif;
|
||||
|
||||
html {
|
||||
background-color: $nord0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: $nord6;
|
||||
font-family: $roboto-slab;
|
||||
font-size: 48pt;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $nord4;
|
||||
font-family: $roboto-slab;
|
||||
font-size: 16pt;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 14pt;
|
||||
border: 4px solid $nord13;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $nord4;
|
||||
font-family: $roboto-slab;
|
||||
font-size: 24pt;
|
||||
}
|
||||
|
||||
#subsetOutput {
|
||||
color: $nord8;
|
||||
}
|
||||
|
||||
#wholeOutput {
|
||||
color: $nord15;
|
||||
}
|
||||
|
||||
#tlOutput{
|
||||
color: $nord12;
|
||||
}
|
||||
|
||||
#tOutput{
|
||||
color: $nord13;
|
||||
}
|
||||
|
||||
#lOutput{
|
||||
color: $nord14;
|
||||
}
|
||||
|
||||
#sumDiv {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
display: inline-block;
|
||||
width: 25%;
|
||||
padding: 10px 25px;
|
||||
margin: 0.1em;
|
||||
border: 4px solid $nord8;
|
||||
box-sizing: border-box;
|
||||
text-decoration: none;
|
||||
font-family: 'Roboto',sans-serif;
|
||||
font-size: 14pt;
|
||||
color: $nord2;
|
||||
background-color: $nord6;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
background-color: $nord3;
|
||||
color: $nord6;
|
||||
border-color: $nord10;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: 4px solid $nord8;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
86
tutorials/summed_area/index.html
Normal file
86
tutorials/summed_area/index.html
Normal file
@@ -0,0 +1,86 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/atom-one-dark.min.css" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.2.0/p5.min.js"
|
||||
integrity="sha512-b/htz6gIyFi3dwSoZ0Uv3cuv3Ony7EeKkacgrcVg8CMzu90n777qveu0PBcbZUA7TzyENGtU+qZRuFAkfqgyoQ=="
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="script.js"></script>
|
||||
<script>
|
||||
fetch("snippets/defineCount.js")
|
||||
.then(r => r.text())
|
||||
.then(code => {
|
||||
document.getElementById("define-count").textContent = code;
|
||||
if (window.hljs) hljs.highlightElement(document.getElementById("define-count"));
|
||||
});
|
||||
fetch("snippets/countSquares.js")
|
||||
.then(r => r.text())
|
||||
.then(code => {
|
||||
document.getElementById("count-squares").textContent = code;
|
||||
if (window.hljs) hljs.highlightElement(document.getElementById("count-squares"));
|
||||
});
|
||||
</script>
|
||||
|
||||
<head>
|
||||
<h1>Summed-area Table</h1>
|
||||
<p>
|
||||
A Summed-area table is a data structure and algorithm for quickly and
|
||||
efficiently generating the sum of values in a rectangular grid. In image
|
||||
processing it is also known as an integral image.
|
||||
</p>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>
|
||||
To generate a summed-area table, each cell is given a value which is the
|
||||
sum of all values in a rectangular subset where the top left corner is the
|
||||
first cell in the grid and where the bottom right cell is the cell you're
|
||||
calculating the value for (assumuing the array starts top left).
|
||||
</p>
|
||||
<pre>
|
||||
<code id="define-count" class="js">
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Now each cell has its own count value. This isn't much help on it's own,
|
||||
however we can use it to calculate the sum value of any subset of the grid
|
||||
by interrogating 4 cells and performing only 4 additions. NOTE: we have to
|
||||
add the 'top left' region as it as been subtracted twice.
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
<code id="count-squares" class="js">
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<p><b>Click</b> a sqaure on the grid to turn it on or off.</p>
|
||||
<p><b>Shift+Click</b> two square to define a rectangle.</p>
|
||||
<p>
|
||||
Press the <b>show count</b> button to display the sum value of each cell
|
||||
(in this example a cell's value can only be 0 or 1).
|
||||
</p>
|
||||
<p>
|
||||
Press <b>show rectangles</b> to see the different regions simplified and
|
||||
see how
|
||||
it changes the sum below.
|
||||
</p>
|
||||
<div id="sumDiv">
|
||||
<a id="subsetOutput">0</a>
|
||||
<a> = </a>
|
||||
<a id="wholeOutput"> 0</a>
|
||||
<a> + </a>
|
||||
<a id="tlOutput"> 0</a>
|
||||
<a> - </a>
|
||||
<a id="lOutput"> 0</a>
|
||||
<a> - </a>
|
||||
<a id="tOutput"> 0</a>
|
||||
</div>
|
||||
|
||||
<button onclick="showCountToggle()">Show count</button>
|
||||
<button onclick="toggleRectMode()">Show rectangles</button>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
278
tutorials/summed_area/script.js
Normal file
278
tutorials/summed_area/script.js
Normal file
@@ -0,0 +1,278 @@
|
||||
let myWidth = 800, myHeight = 800, offset = 5;
|
||||
let r, n = 10;
|
||||
|
||||
let t = [];
|
||||
let tl = null
|
||||
let br = null;
|
||||
|
||||
let showCount = false;
|
||||
let drawRectMode = 0;
|
||||
|
||||
let sOutput, wOutput, tlOutput, tOutput, lOutput;
|
||||
|
||||
|
||||
function setup()
|
||||
{
|
||||
createCanvas(myWidth + 2*offset, myHeight + 2*offset);
|
||||
r = myWidth / n;
|
||||
for (y = 0; y < n; y ++)
|
||||
{
|
||||
for (x = 0; x < n; x ++)
|
||||
{
|
||||
t.push(new cTile(x*r + offset, y*r + offset, r));
|
||||
}
|
||||
}
|
||||
sOutput = document.getElementById('subsetOutput');
|
||||
wOutput = document.getElementById('wholeOutput');
|
||||
tlOutput = document.getElementById('tlOutput');
|
||||
tOutput = document.getElementById('tOutput');
|
||||
lOutput = document.getElementById('lOutput');
|
||||
|
||||
}
|
||||
|
||||
function draw()
|
||||
{
|
||||
background(46, 52, 64);
|
||||
t.forEach(e => e.show());
|
||||
|
||||
stroke(156,212,228);
|
||||
noFill();
|
||||
strokeWeight(5);
|
||||
if (tl)
|
||||
{
|
||||
if (!br)
|
||||
{
|
||||
rect(tl.x, tl.y, tl.w);
|
||||
} else {
|
||||
rect(tl.x, tl.y, br.x - tl.x + r, br.y - tl.y + r);
|
||||
drawRect(drawRectMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function defineCount(br)
|
||||
{
|
||||
let cols = Math.floor(br.x/ r) + 1;
|
||||
let rows = Math.floor(br.y/ r) + 1;
|
||||
let c = 0;
|
||||
// let index = cols + n * rows;
|
||||
|
||||
for (y = 0; y < rows; y++) {
|
||||
for (x = 0; x < cols; x++){
|
||||
var i = x + n * y;
|
||||
if (t[i].s){
|
||||
c++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
function countSquares(tl, br)
|
||||
{
|
||||
let topLeft, left, top, whole;
|
||||
let tlx, tly, brx, bry;
|
||||
|
||||
tlx = Math.floor(tl.x / r);
|
||||
tly = Math.floor(tl.y / r);
|
||||
brx = Math.floor(br.x / r);
|
||||
bry = Math.floor(br.y / r);
|
||||
|
||||
topLeft = t[(tlx-1) + n * (tly-1)].count;
|
||||
left = t[(tlx-1) + n * bry ].count;
|
||||
top = t[brx + n * (tly-1)].count;
|
||||
whole = t[brx + n * bry ].count;
|
||||
|
||||
var s = whole + topLeft - top - left;
|
||||
if (s < 0)
|
||||
s = 0;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
function drawRect(n)
|
||||
{
|
||||
strokeWeight(5);
|
||||
noFill();
|
||||
|
||||
switch(n)
|
||||
{
|
||||
case 1:
|
||||
//whole
|
||||
stroke(180,142,173);
|
||||
fill('rgba(180, 142, 173, 0.05)');
|
||||
rect(offset, offset, br.x - offset + br.w, br.y - offset + br.w);
|
||||
break;
|
||||
case 2:
|
||||
//topleft
|
||||
stroke(208,135,112);
|
||||
fill('rgba(208,135,112, 0.05)');
|
||||
rect(offset, offset, tl.x - offset, tl.y - offset);
|
||||
break;
|
||||
case 3:
|
||||
//left
|
||||
stroke(163,190,140);
|
||||
fill('rgba(163,190,140, 0.05)');
|
||||
rect(offset, offset, tl.x - offset, br.y - offset + br.w);
|
||||
break;
|
||||
case 4:
|
||||
//top
|
||||
stroke(235,203,139);
|
||||
fill('rgba(235,203,139, 0.05)');
|
||||
rect(offset, offset, br.x - offset + br.w, tl.y - offset);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
strokeWeight(1);
|
||||
}
|
||||
|
||||
function showCountToggle(){
|
||||
showCount = !showCount;
|
||||
}
|
||||
|
||||
function toggleRectMode(){
|
||||
if (tl && br)
|
||||
{
|
||||
var tlx = Math.floor(tl.x / r);
|
||||
var tly = Math.floor(tl.y / r);
|
||||
var brx = Math.floor(br.x / r);
|
||||
var bry = Math.floor(br.y / r);
|
||||
|
||||
var topLeft = t[(tlx-1) + n * (tly-1)].count;
|
||||
var left = t[(tlx-1) + n * bry ].count;
|
||||
var top = t[brx + n * (tly-1)].count;
|
||||
var whole = t[brx + n * bry ].count;
|
||||
|
||||
if (drawRectMode == 0){
|
||||
wOutput.innerHTML = whole.toString(10);
|
||||
wOutput.style.fontWeight = "bold";
|
||||
drawRectMode ++;
|
||||
} else if (drawRectMode == 1){
|
||||
tlOutput.innerHTML = topLeft.toString(10);
|
||||
tlOutput.style.fontWeight = "bold";
|
||||
wOutput.style.fontWeight = "normal";
|
||||
drawRectMode ++;
|
||||
} else if (drawRectMode == 2){
|
||||
lOutput.innerHTML = left.toString(10);
|
||||
lOutput.style.fontWeight = "bold";
|
||||
tlOutput.style.fontWeight = "normal";
|
||||
drawRectMode ++;
|
||||
} else if (drawRectMode == 3) {
|
||||
tOutput.innerHTML = top.toString(10);
|
||||
tOutput.style.fontWeight = "bold";
|
||||
lOutput.style.fontWeight = "normal";
|
||||
drawRectMode ++;
|
||||
} else if (drawRectMode == 4) {
|
||||
tOutput.style.fontWeight = "normal";
|
||||
drawRectMode = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateSum()
|
||||
{
|
||||
var tlx = Math.floor(tl.x / r);
|
||||
var tly = Math.floor(tl.y / r);
|
||||
var brx = Math.floor(br.x / r);
|
||||
var bry = Math.floor(br.y / r);
|
||||
|
||||
var topLeft = t[(tlx-1) + n * (tly-1)].count;
|
||||
var left = t[(tlx-1) + n * bry ].count;
|
||||
var top = t[brx + n * (tly-1)].count;
|
||||
var whole = t[brx + n * bry ].count;
|
||||
|
||||
wOutput.innerHTML = whole.toString(10);
|
||||
tlOutput.innerHTML = topLeft.toString(10);
|
||||
lOutput.innerHTML = left.toString(10);
|
||||
tOutput.innerHTML = top.toString(10);
|
||||
sOutput.innerHTML = countSquares(tl,br).toString(10);
|
||||
}
|
||||
|
||||
function mouseClicked(event)
|
||||
{
|
||||
if (event.shiftKey)
|
||||
{
|
||||
if (mouseX > offset && mouseX < myWidth && mouseY > offset && mouseY < myHeight)
|
||||
{
|
||||
let col = Math.floor(mouseX / r);
|
||||
let row = Math.floor(mouseY / r);
|
||||
|
||||
if (tl != null && br != null)
|
||||
{
|
||||
tl = null;
|
||||
br = null;
|
||||
}
|
||||
|
||||
if (!tl)
|
||||
{
|
||||
tl = t[col + n * row];
|
||||
} else {
|
||||
br = t[col + n * row];
|
||||
}
|
||||
|
||||
if (tl != null && br != null)
|
||||
{
|
||||
updateSum();
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
if (mouseX > 0 && mouseX < myWidth && mouseY > 0 && mouseY < myHeight)
|
||||
{
|
||||
let col = Math.floor(mouseX / r);
|
||||
let row = Math.floor(mouseY / r);
|
||||
t[col + n * row].setState();
|
||||
|
||||
t.forEach(tile => tile.setCount(defineCount(tile)));
|
||||
|
||||
if (tl != null && br != null)
|
||||
{
|
||||
updateSum();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class cTile
|
||||
{
|
||||
constructor(_x, _y, _w)
|
||||
{
|
||||
this.x = _x;
|
||||
this.y = _y;
|
||||
this.s = false;
|
||||
this.w = _w;
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
show()
|
||||
{
|
||||
if (this.s)
|
||||
{
|
||||
fill(191,97,106);
|
||||
} else {
|
||||
noFill();
|
||||
}
|
||||
stroke(236,239,244);
|
||||
strokeWeight(1);
|
||||
rect(this.x, this.y, this.w, this.w);
|
||||
if (showCount)
|
||||
{
|
||||
textSize(18);
|
||||
stroke(255);
|
||||
fill(255);
|
||||
text(this.count.toString(10), this.x + this.w/2 - 3, this.y + this.w/2 + 5);
|
||||
}
|
||||
}
|
||||
|
||||
setState()
|
||||
{
|
||||
this.s = !this.s;
|
||||
}
|
||||
|
||||
setCount(c)
|
||||
{
|
||||
this.count = c;
|
||||
}
|
||||
};
|
||||
14
tutorials/summed_area/snippets/countSquares.js
Normal file
14
tutorials/summed_area/snippets/countSquares.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// where tl and br are the top left and bottom right cell respectively
|
||||
function countSquares(tl, br) {
|
||||
|
||||
// these are the four cells to interrogate
|
||||
let topLeft, left, top, whole;
|
||||
|
||||
// 1d index = x + #rows * y
|
||||
topLeft = t[(tl.x - 1) + n * (tl.y - 1)].count;
|
||||
left = t[(tl.x - 1) + n * bry].count;
|
||||
top = t[br.x + n * (tl.y - 1)].count;
|
||||
whole = t[br.x + n * br.y].count;
|
||||
|
||||
return whole + topLeft - top - left;
|
||||
}
|
||||
16
tutorials/summed_area/snippets/defineCount.js
Normal file
16
tutorials/summed_area/snippets/defineCount.js
Normal file
@@ -0,0 +1,16 @@
|
||||
// where br is the cell we're calculating
|
||||
function defineCount(br) {
|
||||
let c = 0;
|
||||
|
||||
// br.x is the column number
|
||||
// br.y is the row number
|
||||
for (y = 0; y <= br.y; y++) { // loop through all rows above br (and the row br is on)
|
||||
for (x = 0; x <= br.x; x++) { // loop through all columns to the left of br (and br column)
|
||||
var i = x + n * y; // convert 2d array format to 1d index
|
||||
if (t[i].s) { // if cell[i].status==true then increase count
|
||||
c++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
68
tutorials/summed_area/style.css
Normal file
68
tutorials/summed_area/style.css
Normal file
@@ -0,0 +1,68 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");
|
||||
html {
|
||||
background-color: #2e3440; }
|
||||
|
||||
h1 {
|
||||
color: #eceff4;
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: 48pt; }
|
||||
|
||||
p {
|
||||
color: #d8dee9;
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: 16pt; }
|
||||
|
||||
code {
|
||||
font-size: 14pt;
|
||||
border: 4px solid #ebcb8b; }
|
||||
|
||||
a {
|
||||
color: #d8dee9;
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: 24pt; }
|
||||
|
||||
#subsetOutput {
|
||||
color: #88c0d0; }
|
||||
|
||||
#wholeOutput {
|
||||
color: #b48ead; }
|
||||
|
||||
#tlOutput {
|
||||
color: #d08770; }
|
||||
|
||||
#tOutput {
|
||||
color: #ebcb8b; }
|
||||
|
||||
#lOutput {
|
||||
color: #a3be8c; }
|
||||
|
||||
#sumDiv {
|
||||
text-align: center; }
|
||||
|
||||
button {
|
||||
display: inline-block;
|
||||
width: 25%;
|
||||
padding: 10px 25px;
|
||||
margin: 0.1em;
|
||||
border: 4px solid #88c0d0;
|
||||
box-sizing: border-box;
|
||||
text-decoration: none;
|
||||
font-family: 'Roboto',sans-serif;
|
||||
font-size: 14pt;
|
||||
color: #434c5e;
|
||||
background-color: #eceff4;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
transition: all 0.15s; }
|
||||
button:hover {
|
||||
background-color: #4c566a;
|
||||
color: #eceff4;
|
||||
border-color: #5e81ac; }
|
||||
button:focus {
|
||||
border: 4px solid #88c0d0;
|
||||
box-sizing: border-box; }
|
||||
|
||||
#chillin {
|
||||
display: none; }
|
||||
|
||||
/*# sourceMappingURL=style.css.map */
|
||||
7
tutorials/summed_area/style.css.map
Normal file
7
tutorials/summed_area/style.css.map
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 3,
|
||||
"mappings": "AACQ,2EAAmE;AAI3E,IAAK;EACD,gBAAgB,EC+BZ,OAAO;;AD5Bf,EAAG;EACC,KAAK,ECuID,OAAO;EDtIX,WAAW,EARD,oBAAoB;EAS9B,SAAS,EAAE,IAAI;;AAGnB,CAAE;EACE,KAAK,EC2GD,OAAO;ED1GX,WAAW,EAdD,oBAAoB;EAe9B,SAAS,EAAE,IAAI;;AAGnB,IAAK;EACD,SAAS,EAAE,IAAI;EACf,MAAM,EAAE,iBAAiB;;AAG7B,CAAE;EACE,KAAK,ECgGD,OAAO;ED/FX,WAAW,EAzBD,oBAAoB;EA0B9B,SAAS,EAAE,IAAI;;AAGnB,aAAc;EACV,KAAK,ECuID,OAAO;;ADpIf,YAAa;EACT,KAAK,ECqMA,OAAO;;ADlMhB,SAAS;EACL,KAAK,ECoKA,OAAO;;ADjKhB,QAAQ;EACJ,KAAK,EC0KA,OAAO;;ADvKhB,QAAQ;EACJ,KAAK,ECgLA,OAAO;;AD7KhB,OAAQ;EACJ,UAAU,EAAE,MAAM;;AAGtB,MAAO;EACH,OAAO,EAAE,YAAY;EACrB,KAAK,EAAE,GAAG;EACV,OAAO,EAAE,SAAS;EAClB,MAAM,EAAE,KAAK;EACb,MAAM,EAAE,iBAAgB;EACxB,UAAU,EAAE,UAAU;EACtB,eAAe,EAAE,IAAI;EACrB,WAAW,EAAE,mBAAmB;EAChC,SAAS,EAAE,IAAI;EACf,KAAK,ECMD,OAAO;EDLX,gBAAgB,EC8EZ,OAAO;ED7EX,UAAU,EAAE,MAAM;EAClB,QAAQ,EAAE,QAAQ;EAClB,UAAU,EAAE,SAAS;EAErB,YAAQ;IACJ,gBAAgB,EC+BhB,OAAO;ID9BP,KAAK,ECuEL,OAAO;IDtEP,YAAY,ECgHX,OAAO;ED7GZ,YAAQ;IACJ,MAAM,EAAE,iBAAgB;IACxB,UAAU,EAAE,UAAU;;AAI9B,QAAS;EACL,OAAO,EAAE,IAAI",
|
||||
"sources": ["cStyle.scss","../nord.scss"],
|
||||
"names": [],
|
||||
"file": "style.css"
|
||||
}
|
||||
79
tutorials/tsp/index.html
Normal file
79
tutorials/tsp/index.html
Normal file
@@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<link href="style.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.2.0/p5.min.js"
|
||||
integrity="sha512-b/htz6gIyFi3dwSoZ0Uv3cuv3Ony7EeKkacgrcVg8CMzu90n777qveu0PBcbZUA7TzyENGtU+qZRuFAkfqgyoQ=="
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<title>Travelling Sales Person</title>
|
||||
<h1>Travelling Sales Person Problem</h1>
|
||||
<button id="backButton" onclick="window.location.href='/#tutorials'">Back</button>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<div class="pictureContainer">
|
||||
<img src="https://optimization.mccormick.northwestern.edu/images/e/ea/48StatesTSP.png" alt="TSP Problem"
|
||||
id="Conway">
|
||||
|
||||
</div>
|
||||
|
||||
<p>The travelling salesman problem (TSP) asks the following question: "Given a list of cities and the distances
|
||||
between each pair of cities, what is the shortest possible route that visits each city and returns to the origin
|
||||
city?" </p>
|
||||
<p>The problem was first formulated in 1930 and is one of the most intensively studied problems in optimization. It
|
||||
is used as a benchmark for many optimization methods. Even though the problem is computationally difficult, a
|
||||
large number of heuristics and exact algorithms are known, so that some instances with tens of thousands of
|
||||
cities can be solved completely and even problems with millions of cities can be approximated within a small
|
||||
fraction of 1%.</p>
|
||||
<p>The TSP has several applications even in its purest formulation, such as planning, logistics, and the manufacture
|
||||
of microchips. Slightly modified, it appears as a sub-problem in many areas, such as DNA sequencing. In these
|
||||
applications, the concept city represents, for example, customers, soldering points, or DNA fragments, and the
|
||||
concept distance represents travelling times or cost, or a similarity measure between DNA fragments. The TSP
|
||||
also appears in astronomy, as astronomers observing many sources will want to minimize the time spent moving the
|
||||
telescope between the sources. In many applications, additional constraints such as limited resources or time
|
||||
windows may be imposed.</p>
|
||||
<h2>These are some of the algorithms I used</h2>
|
||||
<p>Note the purple route is the best route it's found so far and the thin white lines are the routes it's trying
|
||||
real time.</p>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="canvasBody">
|
||||
<h3>Random Sort</h3>
|
||||
<span id="c1"></span>
|
||||
<p class="canvasText">This canvas sorts through random possiblities. Every frame the program chooses two random
|
||||
points (cities) and swaps them around. eg say the order was London, Paris, Madrid, the program would swap
|
||||
London and Paris so that the new order is: Paris, London, Madrid. The program then compares the distance
|
||||
against the record distance to decide whether the new order is better than the old order. This search method
|
||||
is the most inefficient way, the worst case scenario is never ending, as the point swaping is random the
|
||||
program may never reach the optimum route</p><br>
|
||||
<h3>Lexicographic Order</h3>
|
||||
<span id="c2"></span>
|
||||
<p class="canvasText">This canvas sorts through all possible orders sequentially, so after n! (where n is the
|
||||
number of points) this algorithm is guaranteed to have found the quickest possible route. However it is
|
||||
highly inefficient always taking n! frames to complete and as n increases, time taken increases
|
||||
exponentially.</p>
|
||||
<a target="_blank"
|
||||
href="https://www.quora.com/How-would-you-explain-an-algorithm-that-generates-permutations-using-lexicographic-ordering">Click
|
||||
here to learn more about the algorithm</a><br>
|
||||
<h3>Genetic Algorithm</h3>
|
||||
<span id="c3"></span>
|
||||
<p class="canvasText">This canvas is the most efficient at finding the quickest route, it is a mixture of the
|
||||
two methods above. It starts off by creating a population of orders, a fitness is then generated for each
|
||||
order in the population. This fitness decides how likely the order is to be picked and is based on the
|
||||
distance it takes (lower distance is better). When two orders are picked, the algorithm splices the two
|
||||
together at a random term, it's then mutated and compared against the record distance. This takes the least
|
||||
amount of time to find the shortest distance as the algorithm doesn't search through permuations that are
|
||||
obviously longer due to the order.</p><br>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script src="sketch.js"></script>
|
||||
|
||||
<footer>
|
||||
<p>This page was inspired by The Coding Train</p><a
|
||||
href="https://www.youtube.com/channel/UCvjgXvBlbQiydffZU7m1_aw">Check him out here</a>
|
||||
</footer>
|
||||
|
||||
</html>
|
||||
382
tutorials/tsp/sketch.js
Normal file
382
tutorials/tsp/sketch.js
Normal file
@@ -0,0 +1,382 @@
|
||||
/*var seedSlider = document.getElementById('sliderRange')
|
||||
seedSlider.onChange = function(){
|
||||
return this.value;
|
||||
}
|
||||
|
||||
var citiesSlider = document.getElementById('nRange')
|
||||
citiesSlider.onChange = function(){
|
||||
return this.value;
|
||||
}*/
|
||||
|
||||
var Seed = 21//seedChange(11);
|
||||
var totalCities = 25//citiesChange(9);
|
||||
// Sketch One
|
||||
var randomSearch = function( p ) { // p could be any variable name
|
||||
var cities = [];
|
||||
var recordDistance;
|
||||
var bestEver;
|
||||
var localSeed = Seed;
|
||||
var localCities = totalCities;
|
||||
p.setup = function() {
|
||||
p.createCanvas(400, 400);
|
||||
p.setupPoints(Seed, totalCities);
|
||||
}
|
||||
|
||||
p.setupPoints = function(rngSeed, numCities){
|
||||
p.randomSeed(rngSeed);
|
||||
for (var i = 0; i < numCities; i++) {
|
||||
var v = p.createVector(p.random(p.width), p.random(p.height/2));
|
||||
cities[i] = v;
|
||||
}
|
||||
|
||||
var d = p.calcDistance(cities);
|
||||
recordDistance = d;
|
||||
bestEver = cities.slice();
|
||||
}
|
||||
|
||||
|
||||
p.draw = function() {
|
||||
if(localSeed != Seed){
|
||||
p.setupPoints(Seed, localCities);
|
||||
localSeed = Seed;
|
||||
}
|
||||
if(localCities != totalCities){
|
||||
setupPoints(localSeed, totalCities);
|
||||
localCities = totalCities;
|
||||
}
|
||||
|
||||
p.background(0);
|
||||
|
||||
p.fill(255);
|
||||
p.stroke(255);
|
||||
for (var i = 0; i < cities.length; i++) {
|
||||
p.ellipse(cities[i].x, cities[i].y, 8, 8);
|
||||
}
|
||||
|
||||
p.stroke(255, 0, 255);
|
||||
p.strokeWeight(4);
|
||||
p.noFill();
|
||||
p.beginShape();
|
||||
for (var i = 0; i < cities.length; i++) {
|
||||
p.vertex(bestEver[i].x, bestEver[i].y);
|
||||
}
|
||||
p.endShape();
|
||||
|
||||
p.stroke(255);
|
||||
p.translate(0 , p.height/2);
|
||||
p.strokeWeight(1);
|
||||
p.noFill();
|
||||
p.beginShape();
|
||||
for (var i = 0; i < cities.length; i++) {
|
||||
p.vertex(cities[i].x, cities[i].y);
|
||||
}
|
||||
p.endShape();
|
||||
|
||||
var i = p.floor(p.random(cities.length));
|
||||
var j = p.floor(p.random(cities.length));
|
||||
p.swap(cities, i, j);
|
||||
|
||||
var d = p.calcDistance(cities);
|
||||
if (d < recordDistance) {
|
||||
recordDistance = d;
|
||||
bestEver = cities.slice();
|
||||
}
|
||||
}
|
||||
|
||||
p.swap = function(a, i, j) {
|
||||
var temp = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = temp;
|
||||
}
|
||||
|
||||
|
||||
p.calcDistance = function(points) {
|
||||
var sum = 0;
|
||||
for (var i = 0; i < points.length - 1; i++) {
|
||||
var d = p.dist(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y);
|
||||
sum += d;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
};
|
||||
|
||||
var myp5 = new p5(randomSearch, 'c1');
|
||||
|
||||
// Sketch Two
|
||||
var lexicographicOrder = function( q ) {
|
||||
var cities = [];
|
||||
var order = [];
|
||||
var totalPermutations;
|
||||
var count = 0;
|
||||
var recordDistance;
|
||||
var bestEver;
|
||||
|
||||
q.setup = function() {
|
||||
q.randomSeed(Seed);
|
||||
q.createCanvas(400, 400);
|
||||
for (var i = 0; i < totalCities; i++) {
|
||||
var v = q.createVector(q.random(q.width), q.random(q.height / 2));
|
||||
cities[i] = v;
|
||||
order[i] = i;
|
||||
}
|
||||
|
||||
var d = q.calcDistance(cities, order);
|
||||
recordDistance = d;
|
||||
bestEver = order.slice();
|
||||
}
|
||||
|
||||
q.draw = function() {
|
||||
q.background(0);
|
||||
|
||||
q.fill(255);
|
||||
for (var i = 0; i < cities.length; i++) {
|
||||
q.ellipse(cities[i].x, cities[i].y, 8, 8);
|
||||
}
|
||||
|
||||
q.stroke(255, 0, 255);
|
||||
q.strokeWeight(4);
|
||||
q.noFill();
|
||||
q.beginShape();
|
||||
for (var i = 0; i < order.length; i++) {
|
||||
var n = bestEver[i];
|
||||
q.vertex(cities[n].x, cities[n].y);
|
||||
}
|
||||
q.endShape();
|
||||
|
||||
|
||||
q.translate(0, q.height / 2);
|
||||
q.stroke(255);
|
||||
q.strokeWeight(1);
|
||||
q.noFill();
|
||||
q.beginShape();
|
||||
for (var i = 0; i < order.length; i++) {
|
||||
var n = order[i];
|
||||
q.vertex(cities[n].x, cities[n].y);
|
||||
}
|
||||
q.endShape();
|
||||
|
||||
var d = q.calcDistance(cities, order);
|
||||
if (d < recordDistance) {
|
||||
recordDistance = d;
|
||||
bestEver = order.slice();
|
||||
}
|
||||
|
||||
q.nextOrder();
|
||||
}
|
||||
|
||||
q.swap = function(a, i, j) {
|
||||
var temp = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = temp;
|
||||
}
|
||||
|
||||
|
||||
q.calcDistance = function(points, order) {
|
||||
var sum = 0;
|
||||
for (var i = 0; i < order.length - 1; i++) {
|
||||
var cityAIndex = order[i];
|
||||
var cityA = points[cityAIndex];
|
||||
var cityBIndex = order[i + 1];
|
||||
var cityB = points[cityBIndex];
|
||||
var d = q.dist(cityA.x, cityA.y, cityB.x, cityB.y);
|
||||
sum += d;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
// This is my lexical order algorithm
|
||||
|
||||
q.nextOrder = function() {
|
||||
|
||||
// STEP 1 of the algorithm
|
||||
// https://www.quora.com/How-would-you-explain-an-algorithm-that-generates-permutations-using-lexicographic-ordering
|
||||
var largestI = -1;
|
||||
for (var i = 0; i < order.length - 1; i++) {
|
||||
if (order[i] < order[i + 1]) {
|
||||
largestI = i;
|
||||
}
|
||||
}
|
||||
if (largestI == -1) {
|
||||
q.noLoop();
|
||||
}
|
||||
|
||||
// STEP 2
|
||||
var largestJ = -1;
|
||||
for (var j = 0; j < order.length; j++) {
|
||||
if (order[largestI] < order[j]) {
|
||||
largestJ = j;
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 3
|
||||
q.swap(order, largestI, largestJ);
|
||||
|
||||
// STEP 4: reverse from largestI + 1 to the end
|
||||
var endArray = order.splice(largestI + 1);
|
||||
endArray.reverse();
|
||||
order = order.concat(endArray);
|
||||
}
|
||||
|
||||
};
|
||||
var myp5 = new p5(lexicographicOrder, 'c2');
|
||||
|
||||
var geneticSearch = function( p ) {
|
||||
var cities = [];
|
||||
var popSize = 500;
|
||||
var population = [];
|
||||
var fitness = [];
|
||||
var recordDistance = Infinity;
|
||||
var bestEver;
|
||||
var currentBest;
|
||||
var statusP;
|
||||
|
||||
p.setup = function(){
|
||||
p.randomSeed(Seed);
|
||||
p.createCanvas(400, 400);
|
||||
var order = [];
|
||||
for (var i = 0; i < totalCities; i++) {
|
||||
var v = p.createVector(p.random(p.width), p.random(p.height / 2));
|
||||
cities[i] = v;
|
||||
order[i] = i;
|
||||
}
|
||||
|
||||
for (var i = 0; i < popSize; i++) {
|
||||
population[i] = p.shuffle(order);
|
||||
}
|
||||
statusP = p.createP('').style('font-size', '32pt');
|
||||
}
|
||||
|
||||
p.draw = function() {
|
||||
p.background(0);
|
||||
p.stroke(255);
|
||||
p.fill(255);
|
||||
for (var i = 0; i < cities.length; i++) {
|
||||
p.ellipse(cities[i].x, cities[i].y, 8, 8);
|
||||
}
|
||||
|
||||
p.calculateFitness();
|
||||
p.normalizeFitness();
|
||||
p.nextGeneration();
|
||||
|
||||
p.noFill();
|
||||
p.strokeWeight(4);
|
||||
p.beginShape();
|
||||
for (var i = 0; i < bestEver.length; i++) {
|
||||
var n = bestEver[i];
|
||||
p.stroke(255, 0, 255);
|
||||
p.vertex(cities[n].x, cities[n].y);
|
||||
//p.stroke(255);
|
||||
//p.ellipse(cities[n].x, cities[n].y, 8, 8);
|
||||
}
|
||||
p.endShape();
|
||||
|
||||
p.translate(0, p.height / 2);
|
||||
p.stroke(255);
|
||||
p.strokeWeight(1);
|
||||
p.noFill();
|
||||
p.beginShape();
|
||||
for (var i = 0; i < currentBest.length; i++) {
|
||||
var n = currentBest[i];
|
||||
p.vertex(cities[n].x, cities[n].y);
|
||||
//p.ellipse(cities[n].x, cities[n].y, 8, 8);
|
||||
}
|
||||
p.endShape();
|
||||
}
|
||||
|
||||
p.swap = function(a, i, j) {
|
||||
var temp = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = temp;
|
||||
}
|
||||
|
||||
p.calcDistance = function(points, order) {
|
||||
var sum = 0;
|
||||
for (var i = 0; i < order.length - 1; i++) {
|
||||
var cityAIndex = order[i];
|
||||
var cityA = points[cityAIndex];
|
||||
var cityBIndex = order[i + 1];
|
||||
var cityB = points[cityBIndex];
|
||||
var d = p.dist(cityA.x, cityA.y, cityB.x, cityB.y);
|
||||
sum += d;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
p.calculateFitness = function() {
|
||||
var currentRecord = Infinity;
|
||||
for (var i = 0; i < population.length; i++) {
|
||||
var d = p.calcDistance(cities, population[i]);
|
||||
if (d < recordDistance) {
|
||||
recordDistance = d;
|
||||
bestEver = population[i];
|
||||
}
|
||||
if (d < currentRecord) {
|
||||
currentRecord = d;
|
||||
currentBest = population[i];
|
||||
}
|
||||
|
||||
fitness[i] = 1 / (p.pow(d, 8) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
p.normalizeFitness = function() {
|
||||
var sum = 0;
|
||||
for (var i = 0; i < fitness.length; i++) {
|
||||
sum += fitness[i];
|
||||
}
|
||||
for (var i = 0; i < fitness.length; i++) {
|
||||
fitness[i] = fitness[i] / sum;;
|
||||
}
|
||||
}
|
||||
|
||||
p.nextGeneration = function() {
|
||||
var newPopulation = [];
|
||||
for (var i = 0; i < population.length; i++) {
|
||||
var orderA = p.pickOne(population, fitness);
|
||||
var orderB = p.pickOne(population, fitness);
|
||||
var order = p.crossOver(orderA, orderB);
|
||||
p.mutate(order, 0.01);
|
||||
newPopulation[i] = order;
|
||||
}
|
||||
population = newPopulation;
|
||||
|
||||
}
|
||||
|
||||
p.pickOne = function(list, prob) {
|
||||
var index = 0;
|
||||
var r = p.random(1);
|
||||
|
||||
while (r > 0) {
|
||||
r = r - prob[index];
|
||||
index++;
|
||||
}
|
||||
index--;
|
||||
return list[index].slice();
|
||||
}
|
||||
|
||||
p.crossOver = function(orderA, orderB) {
|
||||
var start = p.floor(p.random(orderA.length));
|
||||
var end = p.floor(p.random(start + 1, orderA.length));
|
||||
var neworder = orderA.slice(start, end);
|
||||
// var left = totalCities - neworder.length;
|
||||
for (var i = 0; i < orderB.length; i++) {
|
||||
var city = orderB[i];
|
||||
if (!neworder.includes(city)) {
|
||||
neworder.push(city);
|
||||
}
|
||||
}
|
||||
return neworder;
|
||||
}
|
||||
|
||||
|
||||
p.mutate = function(order, mutationRate) {
|
||||
for (var i = 0; i < totalCities; i++) {
|
||||
if (p.random(1) < mutationRate) {
|
||||
var indexA = p.floor(p.random(order.length));
|
||||
var indexB = (indexA + 1) % totalCities;
|
||||
p.swap(order, indexA, indexB);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var myp5 = new p5(geneticSearch, 'c3');
|
||||
160
tutorials/tsp/style.css
Normal file
160
tutorials/tsp/style.css
Normal file
@@ -0,0 +1,160 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Roboto+Condensed');
|
||||
|
||||
a{font-family: 'Roboto Condensed', sans-serif; font-size: 18pt;}
|
||||
|
||||
h1 {
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0 0 15px 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0 0 15px 0;
|
||||
text-align: center;
|
||||
text-decoration: bold;
|
||||
}
|
||||
|
||||
@media (min-width: 350px) {
|
||||
h1 {font-size: 3.25em;}
|
||||
img{height: 40px;}
|
||||
p{font-size: 10px;}
|
||||
h2{font-size: 17px;}
|
||||
}
|
||||
|
||||
@media (min-width: 400px) {
|
||||
h1 {font-size: 3.25em;}
|
||||
img{height: 45px;}
|
||||
p{font-size: 15px;}
|
||||
h2{font-size: 17px;}
|
||||
}
|
||||
@media (min-width: 440px) {
|
||||
h1 {font-size: 3.5em;}
|
||||
img {height: 100px;}
|
||||
p{font-size: 16px;}
|
||||
h2{font-size: 18px;}
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
h1 {font-size: 3.75em;}
|
||||
img{height: 125px;}
|
||||
p{font-size: 16px;}
|
||||
h2{font-size: 19px;}
|
||||
}
|
||||
@media (min-width: 630px) {
|
||||
h1 {font-size: 5em;}
|
||||
img{height: 150px;}
|
||||
p{font-size: 20px;}
|
||||
h2{font-size: 24px;}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
h1 {
|
||||
font-size: 5em;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
img{height: 175px;}
|
||||
p{font-size: 22px;}
|
||||
h2{font-size: 26px;}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {font-size: 8em;}
|
||||
img{height: 250px;}
|
||||
p{font-size: 24px;}
|
||||
h2{font-size: 28px;}
|
||||
}
|
||||
|
||||
p{
|
||||
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
}
|
||||
|
||||
h3{
|
||||
text-align: center;
|
||||
font-size: 30px;
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
}
|
||||
|
||||
footer{
|
||||
padding: 20px;
|
||||
background-color: #e0e0e0;
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
}
|
||||
|
||||
@keyframes dimImg{
|
||||
from {opacity: 1;
|
||||
filter: alpha(opacity=100);}
|
||||
to {opacity: 0.4;
|
||||
filter: alpha(opacity=50);}
|
||||
}
|
||||
|
||||
@keyframes revealText{
|
||||
from {opacity: 0.4;
|
||||
filter: alpha(opacity=50);}
|
||||
to {opacity: 1;
|
||||
filter: alpha(opacity=100);}
|
||||
}
|
||||
|
||||
.pictureContainer{
|
||||
float: right;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pictureContainer a{
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
|
||||
.pictureContainer:hover img{
|
||||
|
||||
animation-name: dimImg;
|
||||
animation-duration: 1s;
|
||||
opacity: 0.4;
|
||||
filter: alpha(opacity=50);
|
||||
}
|
||||
|
||||
.pictureContainer:hover a{
|
||||
animation-name: revealText;
|
||||
animation-duration: 1s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.canvasText{
|
||||
margin: 0px;
|
||||
display: inline-block;
|
||||
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#c1, #c2, #c3{
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.button{
|
||||
padding: 16px 32px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
margin: 4px 2px;
|
||||
-webkit-transition-duration: 0.4s; /* Safari */
|
||||
transition-duration: 0.4s;
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
color: black;
|
||||
border: 2px solid #555555;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #555555;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user