formatting
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0>
|
||||
<head>
|
||||
<meta
|
||||
name="viewport"
|
||||
width="device-width,"
|
||||
initial-scale="1.0,"
|
||||
maximum-scale="1.0,"
|
||||
user-scalable="0"
|
||||
/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.min.js"></script>
|
||||
<script src="cPoint.js"></script>
|
||||
<script src="cLine.js"></script>
|
||||
<script src="sketch.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
</head>
|
||||
|
||||
<body></body>
|
||||
</html>
|
||||
|
||||
@@ -1,184 +1,191 @@
|
||||
<DOCTYPE! html>
|
||||
<html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.min.js"></script>
|
||||
</head>
|
||||
|
||||
<head>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.min.js"></script>
|
||||
</head>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #fafafa;
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #fafafa;
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
#container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="container">
|
||||
<h1>Ellipse Construction</h1>
|
||||
|
||||
<p>
|
||||
Inspired by a Richard Feyman's
|
||||
<a href="https://en.wikipedia.org/wiki/Feynman's_Lost_Lecture"
|
||||
>lost lecture</a
|
||||
>
|
||||
&
|
||||
<a href="https://www.youtube.com/watch?v=xdIjYBtnvZU"
|
||||
>3blue1brown's video</a
|
||||
>.
|
||||
</p>
|
||||
<p>A geometric proof as to why planetary orbits are ellipitcal.</p>
|
||||
|
||||
<h2 id="PIbox"></h2>
|
||||
<h3 id="percentage"></h3>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
class dot {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
var ux, uy;
|
||||
var points = [];
|
||||
var numericValue = 15;
|
||||
|
||||
function setup() {
|
||||
createCanvas(800, 800);
|
||||
background(41);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
translate(width / 2, height / 2);
|
||||
background(41);
|
||||
|
||||
stroke(75, 75, 215);
|
||||
strokeWeight(3);
|
||||
noFill();
|
||||
ellipse(0, 0, width - 0.0625 * width, height - 0.0625 * height);
|
||||
|
||||
stroke(215, 215, 15);
|
||||
strokeWeight(7);
|
||||
point(0, 0);
|
||||
if (mouseY > 0 && mouseY < height && mouseX > 0 && mouseX < width) {
|
||||
if (mouseIsPressed == true && mouseButton == LEFT) {
|
||||
ux = mouseX - width / 2;
|
||||
uy = mouseY - height / 2;
|
||||
point(ux, uy);
|
||||
getCirclePoints();
|
||||
drawLines(ux, uy);
|
||||
drawTLines(ux, uy);
|
||||
} else if (mouseIsPressed == true && mouseButton == RIGHT) {
|
||||
ux = mouseX - width / 2;
|
||||
uy = mouseY - height / 2;
|
||||
point(ux, uy);
|
||||
getCirclePoints();
|
||||
drawTLines(ux, uy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
function getCirclePoints() {
|
||||
var r = (width - 0.0625 * width) / 2;
|
||||
var step = 1 / numericValue;
|
||||
var index = 0;
|
||||
for (var i = 0; i < TWO_PI; i += step) {
|
||||
var cx = r * Math.sin(i);
|
||||
var cy = r * Math.cos(i);
|
||||
points[index] = new dot(cx, cy);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
function drawLines(startX, startY) {
|
||||
strokeWeight(0.4);
|
||||
stroke(255, 100);
|
||||
|
||||
for (var i = 0; i < points.length; i++) {
|
||||
line(startX, startY, points[i].x, points[i].y);
|
||||
//findMidpoint(startX, startY, points[i].x, points[i].y);
|
||||
}
|
||||
}
|
||||
|
||||
function drawTLines(startX, startY) {
|
||||
strokeWeight(0.4);
|
||||
stroke(255);
|
||||
|
||||
for (var i = 0; i < points.length; i++) {
|
||||
findMidpoint(startX, startY, points[i].x, points[i].y);
|
||||
}
|
||||
}
|
||||
|
||||
function findMidpoint(x1, y1, x2, y2) {
|
||||
//find center
|
||||
var cx = (x1 + x2) / 2;
|
||||
var cy = (y1 + y2) / 2;
|
||||
|
||||
//move line to the center on the origin
|
||||
x1 -= cx;
|
||||
y1 -= cy;
|
||||
x2 -= cx;
|
||||
y2 -= cy;
|
||||
|
||||
//rotate both points
|
||||
xtemp = x1;
|
||||
ytemp = y1;
|
||||
x1 = -ytemp;
|
||||
y1 = xtemp;
|
||||
|
||||
xtemp = x2;
|
||||
ytemp = y2;
|
||||
x2 = -ytemp;
|
||||
y2 = xtemp;
|
||||
|
||||
//move the center point back to where it was
|
||||
x1 += cx;
|
||||
y1 += cy;
|
||||
x2 += cx;
|
||||
y2 += cy;
|
||||
stroke(255, 0, 0);
|
||||
line(x1, y1, x2, y2);
|
||||
stroke(255);
|
||||
}
|
||||
|
||||
function genLines() {
|
||||
var pointOk = false;
|
||||
do {
|
||||
ux = random(width);
|
||||
uy = random(height);
|
||||
if (getDist(0, ux, 0, uy) <= (width - 0.0625 * width) / 2) {
|
||||
pointOk = true;
|
||||
}
|
||||
} while (!pointOk);
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin: 10px 0;
|
||||
point(ux, uy);
|
||||
getCirclePoints();
|
||||
drawLines(ux, uy);
|
||||
drawTLines(ux, uy);
|
||||
}
|
||||
|
||||
function genTLines() {
|
||||
var pointOk = false;
|
||||
do {
|
||||
ux = random(width);
|
||||
uy = random(height);
|
||||
if (getDist(0, ux, 0, uy) <= (width - 0.0625 * width) / 2) {
|
||||
pointOk = true;
|
||||
}
|
||||
</style>
|
||||
} while (!pointOk);
|
||||
|
||||
<div id="container">
|
||||
<h1>Ellipse Construction</h1>
|
||||
point(ux, uy);
|
||||
getCirclePoints();
|
||||
drawTLines(ux, uy);
|
||||
}
|
||||
|
||||
<p>
|
||||
Inspired by a Richard Feyman's <a href="https://en.wikipedia.org/wiki/Feynman's_Lost_Lecture">lost
|
||||
lecture</a> & <a href="https://www.youtube.com/watch?v=xdIjYBtnvZU">3blue1brown's video</a>.
|
||||
</p>
|
||||
<p>A geometric proof as to why planetary orbits are ellipitcal.</p>
|
||||
|
||||
<h2 id="PIbox"></h2>
|
||||
<h3 id="percentage"></h3>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
class dot {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
var ux, uy;
|
||||
var points = [];
|
||||
var numericValue = 15;
|
||||
|
||||
function setup() {
|
||||
createCanvas(800, 800);
|
||||
background(41);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
translate(width / 2, height / 2);
|
||||
background(41);
|
||||
|
||||
stroke(75, 75, 215);
|
||||
strokeWeight(3);
|
||||
noFill();
|
||||
ellipse(0, 0, width - (0.0625 * width), height - (0.0625 * height));
|
||||
|
||||
stroke(215, 215, 15);
|
||||
strokeWeight(7);
|
||||
point(0, 0);
|
||||
if (mouseY > 0 && mouseY < height && mouseX > 0 && mouseX < width) {
|
||||
if (mouseIsPressed == true && mouseButton == LEFT) {
|
||||
|
||||
ux = mouseX - width / 2;
|
||||
uy = mouseY - height / 2;
|
||||
point(ux, uy);
|
||||
getCirclePoints();
|
||||
drawLines(ux, uy);
|
||||
drawTLines(ux, uy);
|
||||
|
||||
} else if (mouseIsPressed == true && mouseButton == RIGHT) {
|
||||
ux = mouseX - width / 2;
|
||||
uy = mouseY - height / 2;
|
||||
point(ux, uy);
|
||||
getCirclePoints();
|
||||
drawTLines(ux, uy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCirclePoints() {
|
||||
var r = (width - (0.0625 * width)) / 2;
|
||||
var step = 1 / numericValue;
|
||||
var index = 0;
|
||||
for (var i = 0; i < TWO_PI; i += step) {
|
||||
var cx = r * Math.sin(i);
|
||||
var cy = r * Math.cos(i);
|
||||
points[index] = new dot(cx, cy);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
function drawLines(startX, startY) {
|
||||
strokeWeight(0.4);
|
||||
stroke(255, 100);
|
||||
|
||||
for (var i = 0; i < points.length; i++) {
|
||||
line(startX, startY, points[i].x, points[i].y);
|
||||
//findMidpoint(startX, startY, points[i].x, points[i].y);
|
||||
}
|
||||
}
|
||||
|
||||
function drawTLines(startX, startY) {
|
||||
strokeWeight(0.4);
|
||||
stroke(255);
|
||||
|
||||
for (var i = 0; i < points.length; i++) {
|
||||
findMidpoint(startX, startY, points[i].x, points[i].y);
|
||||
}
|
||||
}
|
||||
|
||||
function findMidpoint(x1, y1, x2, y2) {
|
||||
//find center
|
||||
var cx = (x1 + x2) / 2;
|
||||
var cy = (y1 + y2) / 2;
|
||||
|
||||
//move line to the center on the origin
|
||||
x1 -= cx; y1 -= cy;
|
||||
x2 -= cx; y2 -= cy;
|
||||
|
||||
//rotate both points
|
||||
xtemp = x1; ytemp = y1;
|
||||
x1 = -ytemp; y1 = xtemp;
|
||||
|
||||
xtemp = x2; ytemp = y2;
|
||||
x2 = -ytemp; y2 = xtemp;
|
||||
|
||||
//move the center point back to where it was
|
||||
x1 += cx; y1 += cy;
|
||||
x2 += cx; y2 += cy;
|
||||
stroke(255, 0, 0);
|
||||
line(x1, y1, x2, y2);
|
||||
stroke(255);
|
||||
}
|
||||
|
||||
function genLines() {
|
||||
var pointOk = false;
|
||||
do {
|
||||
ux = random(width);
|
||||
uy = random(height);
|
||||
if (getDist(0, ux, 0, uy) <= (width - (0.0625 * width)) / 2) {
|
||||
pointOk = true;
|
||||
}
|
||||
}
|
||||
while (!pointOk);
|
||||
|
||||
point(ux, uy);
|
||||
getCirclePoints();
|
||||
drawLines(ux, uy);
|
||||
drawTLines(ux, uy);
|
||||
}
|
||||
|
||||
function genTLines() {
|
||||
var pointOk = false;
|
||||
do {
|
||||
ux = random(width);
|
||||
uy = random(height);
|
||||
if (getDist(0, ux, 0, uy) <= (width - (0.0625 * width)) / 2) {
|
||||
pointOk = true;
|
||||
}
|
||||
}
|
||||
while (!pointOk);
|
||||
|
||||
point(ux, uy);
|
||||
getCirclePoints();
|
||||
drawTLines(ux, uy);
|
||||
}
|
||||
|
||||
function getDist(x1, x2, y1, y2) {
|
||||
return Math.sqrt((x1 - x2) ^ 2 + (y1 - y2) ^ 2);
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
function getDist(x1, x2, y1, y2) {
|
||||
return Math.sqrt((x1 - x2) ^ (2 + (y1 - y2)) ^ 2);
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -1,78 +1,106 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Flocking Boids - WASM</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<canvas id="canvas"></canvas>
|
||||
<aside id="controls" aria-expanded="true">
|
||||
<div id="controlsHeader">
|
||||
<h2 id="controlsTitle">Flocking Controls</h2>
|
||||
<button id="controlsToggle" type="button" aria-controls="controlsBody" aria-expanded="true" aria-label="Collapse controls">›</button>
|
||||
<canvas id="canvas"></canvas>
|
||||
<aside id="controls" aria-expanded="true">
|
||||
<div id="controlsHeader">
|
||||
<h2 id="controlsTitle">Flocking Controls</h2>
|
||||
<button
|
||||
id="controlsToggle"
|
||||
type="button"
|
||||
aria-controls="controlsBody"
|
||||
aria-expanded="true"
|
||||
aria-label="Collapse controls"
|
||||
>
|
||||
›
|
||||
</button>
|
||||
</div>
|
||||
<div id="controlsBody">
|
||||
<div class="control-group">
|
||||
<label for="alignSlider">Alignment</label>
|
||||
<input
|
||||
type="range"
|
||||
id="alignSlider"
|
||||
min="0"
|
||||
max="5"
|
||||
step="0.1"
|
||||
value="1.0"
|
||||
/>
|
||||
<div class="value-display">
|
||||
<span class="stat-label">Weight:</span>
|
||||
<strong id="alignValue">1.00</strong>
|
||||
</div>
|
||||
<div id="controlsBody">
|
||||
<div class="control-group">
|
||||
<label for="alignSlider">Alignment</label>
|
||||
<input type="range" id="alignSlider" min="0" max="5" step="0.1" value="1.0">
|
||||
<div class="value-display">
|
||||
<span class="stat-label">Weight:</span>
|
||||
<strong id="alignValue">1.00</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="cohesionSlider">Cohesion</label>
|
||||
<input type="range" id="cohesionSlider" min="0" max="5" step="0.1" value="0.9">
|
||||
<div class="value-display">
|
||||
<span class="stat-label">Weight:</span>
|
||||
<strong id="cohesionValue">0.90</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="separationSlider">Separation</label>
|
||||
<input type="range" id="separationSlider" min="0" max="5" step="0.1" value="1.4">
|
||||
<div class="value-display">
|
||||
<span class="stat-label">Weight:</span>
|
||||
<strong id="separationValue">1.40</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="addBtn">Boid Count (Step: 10)</label>
|
||||
<div class="button-group">
|
||||
<button id="removeBtn" type="button">−10 Remove</button>
|
||||
<button id="addBtn" type="button">+10 Add</button>
|
||||
</div>
|
||||
<div class="value-display">
|
||||
<span class="stat-label">Total:</span>
|
||||
<strong id="boidCount">250</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Canvas:</span>
|
||||
<span class="stat-value" id="canvasSize">0x0</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">FPS:</span>
|
||||
<span class="stat-value" id="fps">60</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label for="cohesionSlider">Cohesion</label>
|
||||
<input
|
||||
type="range"
|
||||
id="cohesionSlider"
|
||||
min="0"
|
||||
max="5"
|
||||
step="0.1"
|
||||
value="0.9"
|
||||
/>
|
||||
<div class="value-display">
|
||||
<span class="stat-label">Weight:</span>
|
||||
<strong id="cohesionValue">0.90</strong>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="separationSlider">Separation</label>
|
||||
<input
|
||||
type="range"
|
||||
id="separationSlider"
|
||||
min="0"
|
||||
max="5"
|
||||
step="0.1"
|
||||
value="1.4"
|
||||
/>
|
||||
<div class="value-display">
|
||||
<span class="stat-label">Weight:</span>
|
||||
<strong id="separationValue">1.40</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="addBtn">Boid Count (Step: 10)</label>
|
||||
<div class="button-group">
|
||||
<button id="removeBtn" type="button">−10 Remove</button>
|
||||
<button id="addBtn" type="button">+10 Add</button>
|
||||
</div>
|
||||
<div class="value-display">
|
||||
<span class="stat-label">Total:</span>
|
||||
<strong id="boidCount">250</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Canvas:</span>
|
||||
<span class="stat-value" id="canvasSize">0x0</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">FPS:</span>
|
||||
<span class="stat-value" id="fps">60</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<script type="module" src="script.js"></script>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -1,122 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Fourier Series — Epicycles (p5.js)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #fafafa;
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #fafafa;
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
display: block;
|
||||
margin: 10px auto 0;
|
||||
width: 260px;
|
||||
}
|
||||
input[type="range"] {
|
||||
display: block;
|
||||
margin: 10px auto 0;
|
||||
width: 260px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="container">
|
||||
<h1>Fourier Series</h1>
|
||||
<p>
|
||||
This interactive sketch visualizes a Fourier series using epicycles. Each circle represents
|
||||
an odd harmonic; as you add more epicycles with the slider, the path on the right converges toward a
|
||||
square wave. The rotating vectors sum to a point whose vertical position is traced over time.
|
||||
</p>
|
||||
<h1>Fourier Series</h1>
|
||||
<p>
|
||||
This interactive sketch visualizes a Fourier series using epicycles.
|
||||
Each circle represents an odd harmonic; as you add more epicycles with
|
||||
the slider, the path on the right converges toward a square wave. The
|
||||
rotating vectors sum to a point whose vertical position is traced over
|
||||
time.
|
||||
</p>
|
||||
|
||||
<div class="canvas-holder">
|
||||
</div>
|
||||
<div class="canvas-holder"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
class dot {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
class dot {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
class circle {
|
||||
constructor(cx, cy, r, a) {
|
||||
this.cx = cx;
|
||||
this.cy = cy;
|
||||
this.r = r;
|
||||
this.a = a;
|
||||
}
|
||||
class circle {
|
||||
constructor(cx, cy, r, a) {
|
||||
this.cx = cx;
|
||||
this.cy = cy;
|
||||
this.r = r;
|
||||
this.a = a;
|
||||
}
|
||||
}
|
||||
|
||||
let time = 0;
|
||||
let wave = [];
|
||||
let time = 0;
|
||||
let wave = [];
|
||||
|
||||
let slider;
|
||||
let slider;
|
||||
|
||||
function setup() {
|
||||
createCanvas(900, 800);
|
||||
slider = createSlider(1, 50, 5);
|
||||
function setup() {
|
||||
createCanvas(900, 800);
|
||||
slider = createSlider(1, 50, 5);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
background(41);
|
||||
translate(250, height / 2);
|
||||
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
for (let i = 0; i < slider.value(); i++) {
|
||||
let prevx = x;
|
||||
let prevy = y;
|
||||
|
||||
let n = i * 2 + 1;
|
||||
let radius = 100 * (4 / (n * PI));
|
||||
x += radius * cos(n * time);
|
||||
y += radius * sin(n * time);
|
||||
|
||||
stroke(255, 100);
|
||||
noFill();
|
||||
ellipse(prevx, prevy, radius * 2);
|
||||
|
||||
//fill(255);
|
||||
stroke(255);
|
||||
line(prevx, prevy, x, y);
|
||||
//ellipse(x, y, 8);
|
||||
}
|
||||
wave.unshift(y);
|
||||
|
||||
function draw() {
|
||||
background(41);
|
||||
translate(250, height / 2);
|
||||
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
for (let i = 0; i < slider.value(); i++) {
|
||||
let prevx = x;
|
||||
let prevy = y;
|
||||
|
||||
let n = i * 2 + 1;
|
||||
let radius = 100 * (4 / (n * PI));
|
||||
x += radius * cos(n * time);
|
||||
y += radius * sin(n * time);
|
||||
|
||||
stroke(255, 100);
|
||||
noFill();
|
||||
ellipse(prevx, prevy, radius * 2);
|
||||
|
||||
//fill(255);
|
||||
stroke(255);
|
||||
line(prevx, prevy, x, y);
|
||||
//ellipse(x, y, 8);
|
||||
}
|
||||
wave.unshift(y);
|
||||
|
||||
translate(200, 0);
|
||||
line(x - 200, y, 0, wave[0]);
|
||||
beginShape();
|
||||
noFill();
|
||||
for (let i = 0; i < wave.length; i++) {
|
||||
vertex(i, wave[i]);
|
||||
}
|
||||
endShape();
|
||||
|
||||
time += 0.02;
|
||||
|
||||
if (wave.length > 450) {
|
||||
wave.pop();
|
||||
}
|
||||
translate(200, 0);
|
||||
line(x - 200, y, 0, wave[0]);
|
||||
beginShape();
|
||||
noFill();
|
||||
for (let i = 0; i < wave.length; i++) {
|
||||
vertex(i, wave[i]);
|
||||
}
|
||||
endShape();
|
||||
|
||||
time += 0.02;
|
||||
|
||||
if (wave.length > 450) {
|
||||
wave.pop();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,224 +1,247 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0>
|
||||
<head>
|
||||
<meta
|
||||
name="viewport"
|
||||
width="device-width,"
|
||||
initial-scale="1.0,"
|
||||
maximum-scale="1.0,"
|
||||
user-scalable="0"
|
||||
/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.min.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
|
||||
margin: 16px;
|
||||
background: #fafafa;
|
||||
color: #222;
|
||||
}
|
||||
body {
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
Segoe UI,
|
||||
Roboto,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
margin: 16px;
|
||||
background: #fafafa;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.intro {
|
||||
max-width: 900px;
|
||||
margin: 0 auto 18px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
.intro {
|
||||
max-width: 900px;
|
||||
margin: 0 auto 18px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.intro h1 {
|
||||
font-size: 1.5rem;
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
.intro h1 {
|
||||
font-size: 1.5rem;
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
|
||||
.intro p {
|
||||
margin: 4px 0 10px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.intro p {
|
||||
margin: 4px 0 10px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
canvas {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<body>
|
||||
<div class="intro">
|
||||
<h1>Marching Squares Field Visualizer</h1>
|
||||
<p>
|
||||
This sketch uses the <strong>Marching Squares</strong> algorithm to generate and display contour boundaries
|
||||
on a grid. Each cell is either active or inactive, the algorithm determines the correct line or triangle to
|
||||
draw based on the surrounding corners.
|
||||
</p>
|
||||
<p>
|
||||
<b>Left-click</b> toggles a cell between the two states.
|
||||
</p>
|
||||
<p>
|
||||
<b>Right‑click</b> shows or hides the contour lines produced by the algorithm.
|
||||
</p>
|
||||
<h1>Marching Squares Field Visualizer</h1>
|
||||
<p>
|
||||
This sketch uses the <strong>Marching Squares</strong> algorithm to
|
||||
generate and display contour boundaries on a grid. Each cell is either
|
||||
active or inactive, the algorithm determines the correct line or
|
||||
triangle to draw based on the surrounding corners.
|
||||
</p>
|
||||
<p><b>Left-click</b> toggles a cell between the two states.</p>
|
||||
<p>
|
||||
<b>Right‑click</b> shows or hides the contour lines produced by the
|
||||
algorithm.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var reso = 50;
|
||||
var cols, rows;
|
||||
var reso = 50;
|
||||
var cols, rows;
|
||||
|
||||
var field = [];
|
||||
var field = [];
|
||||
|
||||
var draw_lines = true;
|
||||
var draw_lines = true;
|
||||
|
||||
function setup() {
|
||||
createCanvas(800, 600);
|
||||
function setup() {
|
||||
createCanvas(800, 600);
|
||||
|
||||
cols = width / reso + 1;
|
||||
rows = height / reso + 1;
|
||||
cols = width / reso + 1;
|
||||
rows = height / reso + 1;
|
||||
|
||||
for (var i = 0; i < cols * rows; i++)
|
||||
field[i] = Math.floor(random(2));
|
||||
for (var i = 0; i < cols * rows; i++) field[i] = Math.floor(random(2));
|
||||
}
|
||||
|
||||
function draw() {
|
||||
background(100);
|
||||
const cBlue = color(55, 120, 232);
|
||||
const cGreen = color(146, 232, 55);
|
||||
|
||||
for (var i = 0; i < cols * rows; i++) {
|
||||
let x = i % cols;
|
||||
let y = Math.floor(i / cols);
|
||||
|
||||
let c = field[i] ? cBlue : cGreen;
|
||||
fill(c);
|
||||
|
||||
rect(x * reso - reso * 0.5, y * reso - reso * 0.5, reso, reso);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
background(100);
|
||||
const cBlue = color(55, 120, 232);
|
||||
const cGreen = color(146, 232, 55);
|
||||
for (var i = 0; i < cols * rows; i++) {
|
||||
if (i + cols + 1 > cols * rows) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (var i = 0; i < cols * rows; i++) {
|
||||
let x = i % cols;
|
||||
let y = Math.floor(i / cols);
|
||||
var x = (i % cols) * reso;
|
||||
var y = Math.floor(i / cols) * reso;
|
||||
|
||||
let c = field[i] ? cBlue : cGreen;
|
||||
fill(c);
|
||||
const a = [x + reso * 0.5, y];
|
||||
const b = [x + reso, y + reso * 0.5];
|
||||
const c = [x + reso * 0.5, y + reso];
|
||||
const d = [x, y + reso * 0.5];
|
||||
|
||||
rect(x * reso - reso * 0.5, y * reso - reso * 0.5, reso, reso);
|
||||
}
|
||||
var s = getState(
|
||||
field[i],
|
||||
field[i + 1],
|
||||
field[i + cols + 1],
|
||||
field[i + cols],
|
||||
);
|
||||
noStroke();
|
||||
|
||||
for (var i = 0; i < cols * rows; i++) {
|
||||
if (i + cols + 1 > cols * rows) {break;}
|
||||
|
||||
var x = (i % cols) * reso;
|
||||
var y = (Math.floor(i / cols)) * reso;
|
||||
|
||||
const a = [x + reso * 0.5, y];
|
||||
const b = [x + reso, y + reso * 0.5];
|
||||
const c = [x + reso * 0.5, y + reso];
|
||||
const d = [x, y + reso * 0.5];
|
||||
|
||||
var s = getState(field[i], field[i + 1], field[i + cols + 1], field[i + cols]);
|
||||
noStroke();
|
||||
|
||||
switch (s) {
|
||||
case 1:
|
||||
fill(cGreen);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, c[0], c[1], d[0], d[1]);
|
||||
break;
|
||||
case 14:
|
||||
fill(cBlue);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, c[0], c[1], d[0], d[1]);
|
||||
break;
|
||||
case 2:
|
||||
fill(cGreen);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, b[0], b[1], c[0], c[1]);
|
||||
break;
|
||||
case 13:
|
||||
fill(cBlue);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, b[0], b[1], c[0], c[1]);
|
||||
break;
|
||||
case 3:
|
||||
fill(cBlue);
|
||||
rect(d[0], d[1], reso, reso * 0.5);
|
||||
break;
|
||||
case 12:
|
||||
fill(cGreen);
|
||||
rect(d[0], d[1], reso, reso * 0.5);
|
||||
break;
|
||||
case 4:
|
||||
fill(cGreen);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, a[0], a[1], b[0], b[1]);
|
||||
break;
|
||||
case 11:
|
||||
fill(cBlue);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, a[0], a[1], b[0], b[1]);
|
||||
break;
|
||||
case 5:
|
||||
fill(cBlue);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, a[0], a[1], d[0], d[1]);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, b[0], b[1], c[0], c[1]);
|
||||
break;
|
||||
case 6:
|
||||
fill(cBlue);
|
||||
rect(a[0], a[1], reso * 0.5, reso);
|
||||
break;
|
||||
case 9:
|
||||
fill(cGreen);
|
||||
rect(a[0], a[1], reso * 0.5, reso);
|
||||
break;
|
||||
case 7:
|
||||
fill(cBlue);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, a[0], a[1], d[0], d[1]);
|
||||
break;
|
||||
case 8:
|
||||
fill(cGreen);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, a[0], a[1], d[0], d[1]);
|
||||
break;
|
||||
case 10:
|
||||
fill(cBlue);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, a[0], a[1], b[0], b[1]);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, c[0], c[1], d[0], d[1]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
if (draw_lines) {drawLines(s, a, b, c, d);}
|
||||
}
|
||||
switch (s) {
|
||||
case 1:
|
||||
fill(cGreen);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, c[0], c[1], d[0], d[1]);
|
||||
break;
|
||||
case 14:
|
||||
fill(cBlue);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, c[0], c[1], d[0], d[1]);
|
||||
break;
|
||||
case 2:
|
||||
fill(cGreen);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, b[0], b[1], c[0], c[1]);
|
||||
break;
|
||||
case 13:
|
||||
fill(cBlue);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, b[0], b[1], c[0], c[1]);
|
||||
break;
|
||||
case 3:
|
||||
fill(cBlue);
|
||||
rect(d[0], d[1], reso, reso * 0.5);
|
||||
break;
|
||||
case 12:
|
||||
fill(cGreen);
|
||||
rect(d[0], d[1], reso, reso * 0.5);
|
||||
break;
|
||||
case 4:
|
||||
fill(cGreen);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, a[0], a[1], b[0], b[1]);
|
||||
break;
|
||||
case 11:
|
||||
fill(cBlue);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, a[0], a[1], b[0], b[1]);
|
||||
break;
|
||||
case 5:
|
||||
fill(cBlue);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, a[0], a[1], d[0], d[1]);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, b[0], b[1], c[0], c[1]);
|
||||
break;
|
||||
case 6:
|
||||
fill(cBlue);
|
||||
rect(a[0], a[1], reso * 0.5, reso);
|
||||
break;
|
||||
case 9:
|
||||
fill(cGreen);
|
||||
rect(a[0], a[1], reso * 0.5, reso);
|
||||
break;
|
||||
case 7:
|
||||
fill(cBlue);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, a[0], a[1], d[0], d[1]);
|
||||
break;
|
||||
case 8:
|
||||
fill(cGreen);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, a[0], a[1], d[0], d[1]);
|
||||
break;
|
||||
case 10:
|
||||
fill(cBlue);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, a[0], a[1], b[0], b[1]);
|
||||
triangle(x + reso * 0.5, y + reso * 0.5, c[0], c[1], d[0], d[1]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (draw_lines) {
|
||||
drawLines(s, a, b, c, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getState(a, b, c, d) {
|
||||
return d * 1 + c * 2 + b * 4 + a * 8;
|
||||
function getState(a, b, c, d) {
|
||||
return d * 1 + c * 2 + b * 4 + a * 8;
|
||||
}
|
||||
|
||||
function drawLines(s, a, b, c, d) {
|
||||
stroke(232, 229, 55);
|
||||
strokeWeight(reso * 0.1);
|
||||
switch (s) {
|
||||
case 1:
|
||||
case 14:
|
||||
line(c[0], c[1], d[0], d[1]);
|
||||
break;
|
||||
case 2:
|
||||
case 13:
|
||||
line(b[0], b[1], c[0], c[1]);
|
||||
break;
|
||||
case 3:
|
||||
case 12:
|
||||
line(b[0], b[1], d[0], d[1]);
|
||||
break;
|
||||
case 4:
|
||||
case 11:
|
||||
line(a[0], a[1], b[0], b[1]);
|
||||
break;
|
||||
case 5:
|
||||
line(a[0], a[1], d[0], d[1]);
|
||||
line(b[0], b[1], c[0], c[1]);
|
||||
break;
|
||||
case 6:
|
||||
case 9:
|
||||
line(a[0], a[1], c[0], c[1]);
|
||||
break;
|
||||
case 7:
|
||||
case 8:
|
||||
line(a[0], a[1], d[0], d[1]);
|
||||
break;
|
||||
case 10:
|
||||
line(a[0], a[1], b[0], b[1]);
|
||||
line(c[0], c[1], d[0], d[1]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
noStroke();
|
||||
}
|
||||
|
||||
function drawLines(s, a, b, c, d) {
|
||||
stroke(232, 229, 55);
|
||||
strokeWeight(reso * 0.1);
|
||||
switch (s) {
|
||||
case 1: case 14:
|
||||
line(c[0], c[1], d[0], d[1]);
|
||||
break;
|
||||
case 2: case 13:
|
||||
line(b[0], b[1], c[0], c[1]);
|
||||
break;
|
||||
case 3: case 12:
|
||||
line(b[0], b[1], d[0], d[1]);
|
||||
break;
|
||||
case 4: case 11:
|
||||
line(a[0], a[1], b[0], b[1]);
|
||||
break;
|
||||
case 5:
|
||||
line(a[0], a[1], d[0], d[1]);
|
||||
line(b[0], b[1], c[0], c[1]);
|
||||
break;
|
||||
case 6: case 9:
|
||||
line(a[0], a[1], c[0], c[1]);
|
||||
break;
|
||||
case 7: case 8:
|
||||
line(a[0], a[1], d[0], d[1]);
|
||||
break;
|
||||
case 10:
|
||||
line(a[0], a[1], b[0], b[1]);
|
||||
line(c[0], c[1], d[0], d[1]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
noStroke();
|
||||
}
|
||||
|
||||
function mousePressed() {
|
||||
if (mouseButton == LEFT) {
|
||||
var x = Math.floor((mouseX + 0.5 * reso) / reso);
|
||||
var y = Math.floor((mouseY + 0.5 * reso) / reso);
|
||||
var i = Math.floor(y * cols + x);
|
||||
|
||||
field[i] = (field[i] + 1) % 2;
|
||||
} else if (mouseButton == RIGHT) {
|
||||
draw_lines = !draw_lines;
|
||||
}
|
||||
function mousePressed() {
|
||||
if (mouseButton == LEFT) {
|
||||
var x = Math.floor((mouseX + 0.5 * reso) / reso);
|
||||
var y = Math.floor((mouseY + 0.5 * reso) / reso);
|
||||
var i = Math.floor(y * cols + x);
|
||||
|
||||
field[i] = (field[i] + 1) % 2;
|
||||
} else if (mouseButton == RIGHT) {
|
||||
draw_lines = !draw_lines;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0>
|
||||
<head>
|
||||
<meta
|
||||
name="viewport"
|
||||
width="device-width,"
|
||||
initial-scale="1.0,"
|
||||
maximum-scale="1.0,"
|
||||
user-scalable="0"
|
||||
/>
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.min.js"></script>
|
||||
<script src="sketch.js"></script>
|
||||
@@ -14,39 +19,34 @@
|
||||
<title>Maze Generator</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #fafafa;
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #fafafa;
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
</head>
|
||||
|
||||
|
||||
<div id="container">
|
||||
<div id="container">
|
||||
<h1>Maze Generator</h1>
|
||||
|
||||
<p>
|
||||
Toy maze generator using DFS & recursice back tracking.
|
||||
</p>
|
||||
<p>Toy maze generator using DFS & recursice back tracking.</p>
|
||||
|
||||
<h2 id="PIbox"></h2>
|
||||
<h3 id="percentage"></h3>
|
||||
</div>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
</div>
|
||||
|
||||
<body></body>
|
||||
</html>
|
||||
|
||||
@@ -1,73 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Percolation</title>
|
||||
<link rel="stylesheet" href="style.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a href="/#projects" class="back-button">← Back</a>
|
||||
<a href="/#projects" class="back-button">← Back</a>
|
||||
|
||||
<header>
|
||||
<h1>Percolation</h1>
|
||||
<p class="subtitle">A simulation written in C & Raylib.</p>
|
||||
</header>
|
||||
<header>
|
||||
<h1>Percolation</h1>
|
||||
<p class="subtitle">A simulation written in C & Raylib.</p>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<p>
|
||||
<strong>Percolation</strong> is a simple model for connectivity on a grid. Each cell is either open or closed.
|
||||
This simulation stops as soon as there is a path from the top of the grid to the bottom through the open cells.
|
||||
</p>
|
||||
<div class="info-box">
|
||||
<p>
|
||||
In this simulation, cells are randomly opened on a grid. Percolation occurs when there exists
|
||||
a connected path of open cells from the top to the bottom of the grid. It is a neat way to visualise
|
||||
threshold behaviour: below a certain probability nothing connects, and above it large connected regions
|
||||
suddenly begin to appear.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<p>
|
||||
<strong>Percolation</strong> is a simple model for connectivity on a
|
||||
grid. Each cell is either open or closed. This simulation stops as
|
||||
soon as there is a path from the top of the grid to the bottom through
|
||||
the open cells.
|
||||
</p>
|
||||
<div class="info-box">
|
||||
<p>
|
||||
In this simulation, cells are randomly opened on a grid. Percolation
|
||||
occurs when there exists a connected path of open cells from the top
|
||||
to the bottom of the grid. It is a neat way to visualise threshold
|
||||
behaviour: below a certain probability nothing connects, and above
|
||||
it large connected regions suddenly begin to appear.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="canvas-container">
|
||||
<div class="canvas-toolbar">
|
||||
<p>Canvas</p>
|
||||
<button id="fullscreenButton" class="action-button" type="button">Fullscreen</button>
|
||||
</div>
|
||||
|
||||
<div class="canvas-shell">
|
||||
<canvas id="canvas" aria-label="Percolation simulation canvas" oncontextmenu="event.preventDefault()"></canvas>
|
||||
</div>
|
||||
|
||||
<div id="status" class="status">Downloading...</div>
|
||||
<progress id="progress" value="0" max="100" hidden></progress>
|
||||
|
||||
<details>
|
||||
<summary>Show console output</summary>
|
||||
<label class="visually-hidden" for="output">Console output</label>
|
||||
<textarea id="output" rows="8" readonly></textarea>
|
||||
</details>
|
||||
<div class="canvas-container">
|
||||
<div class="canvas-toolbar">
|
||||
<p>Canvas</p>
|
||||
<button id="fullscreenButton" class="action-button" type="button">
|
||||
Fullscreen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h3>Technical Details</h3>
|
||||
<p>
|
||||
The simulation uses a <strong>disjoint-set (Union-Find)</strong> style connectivity algorithm. As random cells open,
|
||||
each open cell is union-ed with its open neighbours, and two virtual nodes represent the top and bottom edges
|
||||
of the grid. Percolation is detected the moment those two virtual nodes become connected.
|
||||
</p>
|
||||
<p>
|
||||
This makes each update fast and scalable even for larger grids.
|
||||
</p>
|
||||
</section>
|
||||
<div class="canvas-shell">
|
||||
<canvas
|
||||
id="canvas"
|
||||
aria-label="Percolation simulation canvas"
|
||||
oncontextmenu="event.preventDefault()"
|
||||
></canvas>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>Built with C, Raylib, and WebAssembly</p>
|
||||
</footer>
|
||||
<div id="status" class="status">Downloading...</div>
|
||||
<progress id="progress" value="0" max="100" hidden></progress>
|
||||
|
||||
<details>
|
||||
<summary>Show console output</summary>
|
||||
<label class="visually-hidden" for="output">Console output</label>
|
||||
<textarea id="output" rows="8" readonly></textarea>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h3>Technical Details</h3>
|
||||
<p>
|
||||
The simulation uses a <strong>disjoint-set (Union-Find)</strong> style
|
||||
connectivity algorithm. As random cells open, each open cell is
|
||||
union-ed with its open neighbours, and two virtual nodes represent the
|
||||
top and bottom edges of the grid. Percolation is detected the moment
|
||||
those two virtual nodes become connected.
|
||||
</p>
|
||||
<p>This makes each update fast and scalable even for larger grids.</p>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<p>Built with C, Raylib, and WebAssembly</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
<script async src="index.js"></script>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,197 +1,197 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: white;
|
||||
color: black;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
color: black;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-bottom: 20px;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 10px;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.8rem;
|
||||
margin: 20px 0 15px;
|
||||
font-size: 1.8rem;
|
||||
margin: 20px 0 15px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.3rem;
|
||||
margin: 15px 0;
|
||||
font-size: 1.3rem;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px 15px;
|
||||
background: #f0f0f0;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px 15px;
|
||||
background: #f0f0f0;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.back-button:hover,
|
||||
.action-button:hover {
|
||||
background: #e0e0e0;
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #f9f9f9;
|
||||
border-left: 4px solid #333;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
background: #f9f9f9;
|
||||
border-left: 4px solid #333;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
background: #f9f9f9;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
margin: 30px 0;
|
||||
background: #f9f9f9;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.canvas-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 15px;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.canvas-toolbar p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
padding: 8px 12px;
|
||||
background: #f0f0f0;
|
||||
color: black;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
padding: 8px 12px;
|
||||
background: #f0f0f0;
|
||||
color: black;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.canvas-shell {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 500px;
|
||||
overflow: auto;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 500px;
|
||||
overflow: auto;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
#canvas {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
background: black;
|
||||
outline: none;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
background: black;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.status {
|
||||
margin-top: 15px;
|
||||
min-height: 24px;
|
||||
color: #666;
|
||||
margin-top: 15px;
|
||||
min-height: 24px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
progress {
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
progress[hidden] {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
details {
|
||||
margin-top: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
font-family: "Courier New", monospace;
|
||||
font-size: 0.9rem;
|
||||
resize: vertical;
|
||||
background: white;
|
||||
color: black;
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
font-family: "Courier New", monospace;
|
||||
font-size: 0.9rem;
|
||||
resize: vertical;
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ccc;
|
||||
font-size: 0.85rem;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ccc;
|
||||
font-size: 0.85rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 16px;
|
||||
}
|
||||
body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
padding: 15px;
|
||||
}
|
||||
.canvas-container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.canvas-shell {
|
||||
min-height: 360px;
|
||||
}
|
||||
.canvas-shell {
|
||||
min-height: 360px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.min.js"></script>
|
||||
|
||||
<h1 id="PIbox"></h1>
|
||||
<h2 id="percentage"></h2>
|
||||
|
||||
<h1 id="PIbox"></h1>
|
||||
<h2 id="percentage"></h2>
|
||||
|
||||
<style>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #fafafa;
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
font-family: Arial, sans-serif;
|
||||
background: #fafafa;
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin: 10px 0;
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
|
||||
<div id="container">
|
||||
<div id="container">
|
||||
<h1>Monte Carlo π Estimator</h1>
|
||||
|
||||
<p>
|
||||
A small demonstation on how the Monte Carlo method can approximate the value of π
|
||||
by randomly placing points inside a square and checking how many fall within
|
||||
the inner circle. The closer the ratio gets to π/4, the more accurate
|
||||
the estimation becomes.
|
||||
A small demonstation on how the Monte Carlo method can approximate the
|
||||
value of π by randomly placing points inside a square and checking how
|
||||
many fall within the inner circle. The closer the ratio gets to π/4, the
|
||||
more accurate the estimation becomes.
|
||||
</p>
|
||||
|
||||
<h2 id="PIbox"></h2>
|
||||
<h3 id="percentage"></h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
<script>
|
||||
const r = 400;
|
||||
var circleCount, total;
|
||||
var x, y;
|
||||
@@ -52,53 +49,51 @@
|
||||
var BestPI = 50;
|
||||
|
||||
function setup() {
|
||||
createCanvas(r * 2, r * 2);
|
||||
background(0);
|
||||
stroke(255, 0, 0);
|
||||
strokeWeight(3);
|
||||
noFill()
|
||||
translate(width / 2, height / 2);
|
||||
rectMode(CENTER);
|
||||
rect(0, 0, r * 2, r * 2);
|
||||
ellipse(0, 0, r * 2, r * 2);
|
||||
circleCount = 0;
|
||||
total = 0;
|
||||
createCanvas(r * 2, r * 2);
|
||||
background(0);
|
||||
stroke(255, 0, 0);
|
||||
strokeWeight(3);
|
||||
noFill();
|
||||
translate(width / 2, height / 2);
|
||||
rectMode(CENTER);
|
||||
rect(0, 0, r * 2, r * 2);
|
||||
ellipse(0, 0, r * 2, r * 2);
|
||||
circleCount = 0;
|
||||
total = 0;
|
||||
}
|
||||
|
||||
function draw() {
|
||||
translate(width / 2, height / 2);
|
||||
x = random(-r, r);
|
||||
y = random(-r, r);
|
||||
|
||||
translate(width / 2, height / 2);
|
||||
x = random(-r, r)
|
||||
y = random(-r, r)
|
||||
stroke(0, 255, 0);
|
||||
|
||||
stroke(0, 255, 0)
|
||||
total += 1;
|
||||
|
||||
total += 1
|
||||
var d = dist(x, y, 0, 0);
|
||||
if (d < r) {
|
||||
circleCount += 1;
|
||||
stroke(0, 0, 255);
|
||||
}
|
||||
|
||||
var d = dist(x, y, 0, 0)
|
||||
if (d < r) {
|
||||
circleCount += 1
|
||||
stroke(0, 0, 255)
|
||||
}
|
||||
point(x, y);
|
||||
|
||||
point(x, y);
|
||||
calcPI = 4 * (circleCount / total);
|
||||
if (abs(PI - calcPI) < abs(PI - BestPI)) {
|
||||
DisplayPI = calcPI;
|
||||
BestPI = calcPI;
|
||||
|
||||
calcPI = 4 * (circleCount / total);
|
||||
if (abs(PI - calcPI) < abs(PI - BestPI)) {
|
||||
DisplayPI = calcPI;
|
||||
BestPI = calcPI;
|
||||
var PIDisplay = document.getElementById("PIbox");
|
||||
PIDisplay.innerHTML = DisplayPI;
|
||||
|
||||
var PIDisplay = document.getElementById("PIbox");
|
||||
PIDisplay.innerHTML = DisplayPI;
|
||||
|
||||
var ErrorDisplay = document.getElementById("percentage");
|
||||
var DisplayError = "a";
|
||||
var Perror = (calcPI - PI) / PI
|
||||
Perror *= 100;
|
||||
DisplayError = nfc(Perror, 10);
|
||||
ErrorDisplay.innerHTML = DisplayError + '%';
|
||||
}
|
||||
var ErrorDisplay = document.getElementById("percentage");
|
||||
var DisplayError = "a";
|
||||
var Perror = (calcPI - PI) / PI;
|
||||
Perror *= 100;
|
||||
DisplayError = nfc(Perror, 10);
|
||||
ErrorDisplay.innerHTML = DisplayError + "%";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.min.js"></script>
|
||||
<script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.min.js"></script>
|
||||
<script>
|
||||
var r = 10; //circle size
|
||||
var k = 30;
|
||||
var grid = [];
|
||||
@@ -11,94 +11,93 @@
|
||||
var ordered = [];
|
||||
|
||||
function setup() {
|
||||
createCanvas(900, 900);
|
||||
background(0);
|
||||
strokeWeight(4);
|
||||
colorMode(HSB);
|
||||
createCanvas(900, 900);
|
||||
background(0);
|
||||
strokeWeight(4);
|
||||
colorMode(HSB);
|
||||
|
||||
cols = floor(width / w);
|
||||
rows = floor(height / w);
|
||||
for (var i = 0; i < cols * rows; i++) {
|
||||
grid[i] = undefined;
|
||||
}
|
||||
|
||||
cols = floor(width / w);
|
||||
rows = floor(height / w);
|
||||
for (var i = 0; i < cols * rows; i++) {
|
||||
grid[i] = undefined;
|
||||
}
|
||||
|
||||
|
||||
var x = width / 2;
|
||||
var y = height / 2;
|
||||
var i = floor(x / w);
|
||||
var j = floor(y / w);
|
||||
var pos = createVector(x, y);
|
||||
grid[i + j * cols] = pos;
|
||||
active.push(pos);
|
||||
|
||||
var x = width / 2;
|
||||
var y = height / 2;
|
||||
var i = floor(x / w);
|
||||
var j = floor(y / w);
|
||||
var pos = createVector(x, y);
|
||||
grid[i + j * cols] = pos;
|
||||
active.push(pos);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
background(0);
|
||||
background(0);
|
||||
|
||||
for (var total = 0; total < 30; total++) {
|
||||
if (active.length > 0) {
|
||||
var randIndex = floor(random(active.length));
|
||||
var pos = active[randIndex];
|
||||
var found = false;
|
||||
for (var n = 0; n < k; n++) {
|
||||
var sample = p5.Vector.random2D();
|
||||
var m = random(r, 2 * r);
|
||||
sample.setMag(m);
|
||||
sample.add(pos);
|
||||
|
||||
for (var total = 0; total < 30; total++) {
|
||||
if (active.length > 0) {
|
||||
var randIndex = floor(random(active.length));
|
||||
var pos = active[randIndex];
|
||||
var found = false;
|
||||
for (var n = 0; n < k; n++) {
|
||||
var sample = p5.Vector.random2D();
|
||||
var m = random(r, 2 * r);
|
||||
sample.setMag(m);
|
||||
sample.add(pos);
|
||||
var col = floor(sample.x / w);
|
||||
var row = floor(sample.y / w);
|
||||
|
||||
var col = floor(sample.x / w);
|
||||
var row = floor(sample.y / w);
|
||||
|
||||
if (col > -1 && row > -1 && col < cols && row < rows && !grid[col + row * cols]) {
|
||||
var flag = true;
|
||||
for (var i = -1; i <= 1; i++) {
|
||||
for (var j = -1; j <= 1; j++) {
|
||||
var index = (col + i) + (row + j) * cols;
|
||||
var neighbor = grid[index];
|
||||
if (neighbor) {
|
||||
var d = p5.Vector.dist(sample, neighbor);
|
||||
if (d < r) {
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
found = true;
|
||||
grid[col + row * cols] = sample;
|
||||
active.push(sample);
|
||||
ordered.push(sample);
|
||||
|
||||
//break;
|
||||
}
|
||||
if (
|
||||
col > -1 &&
|
||||
row > -1 &&
|
||||
col < cols &&
|
||||
row < rows &&
|
||||
!grid[col + row * cols]
|
||||
) {
|
||||
var flag = true;
|
||||
for (var i = -1; i <= 1; i++) {
|
||||
for (var j = -1; j <= 1; j++) {
|
||||
var index = col + i + (row + j) * cols;
|
||||
var neighbor = grid[index];
|
||||
if (neighbor) {
|
||||
var d = p5.Vector.dist(sample, neighbor);
|
||||
if (d < r) {
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
found = true;
|
||||
grid[col + row * cols] = sample;
|
||||
active.push(sample);
|
||||
ordered.push(sample);
|
||||
|
||||
if (!found) {
|
||||
active.splice(randIndex, 1);
|
||||
}
|
||||
|
||||
//break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < ordered.length; i++) {
|
||||
|
||||
stroke(255, 0, 255);
|
||||
strokeWeight(r * 0.5);
|
||||
point(ordered[i].x, ordered[i].y);
|
||||
if (!found) {
|
||||
active.splice(randIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < active.length; i++) {
|
||||
if (ordered[i]) {
|
||||
stroke(i % 360, 100, 100);
|
||||
strokeWeight(r * 0.5);
|
||||
point(active[i].x, active[i].y);
|
||||
}
|
||||
for (var i = 0; i < ordered.length; i++) {
|
||||
stroke(255, 0, 255);
|
||||
strokeWeight(r * 0.5);
|
||||
point(ordered[i].x, ordered[i].y);
|
||||
}
|
||||
|
||||
for (var i = 0; i < active.length; i++) {
|
||||
if (ordered[i]) {
|
||||
stroke(i % 360, 100, 100);
|
||||
strokeWeight(r * 0.5);
|
||||
point(active[i].x, active[i].y);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -1,72 +1,91 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="Thin Ice - a cute pixel art winter puzzle game." />
|
||||
<title>Thin Ice | havox</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a href="/#projects" class="back-button">← Back</a>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Thin Ice - a cute pixel art winter puzzle game."
|
||||
/>
|
||||
<title>Thin Ice | havox</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a href="/#projects" class="back-button">← Back</a>
|
||||
|
||||
<header>
|
||||
<h1>Thin Ice</h1>
|
||||
<p class="subtitle">Inspired by Club Penguin's Thin Ice mini-game.</p>
|
||||
</header>
|
||||
<header>
|
||||
<h1>Thin Ice</h1>
|
||||
<p class="subtitle">Inspired by Club Penguin's Thin Ice mini-game.</p>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<p>
|
||||
You start on safe ice, breaking ice where you walk, trying to reach the finish tile before trapping yourself.
|
||||
</p>
|
||||
<div class="info-box">
|
||||
<section>
|
||||
<p>
|
||||
See if you can get a gold medal on all 6 levels by breaking every single tile of ice!
|
||||
You start on safe ice, breaking ice where you walk, trying to reach
|
||||
the finish tile before trapping yourself.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<div class="info-box">
|
||||
<p>
|
||||
See if you can get a gold medal on all 6 levels by breaking every
|
||||
single tile of ice!
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="canvas-container">
|
||||
<div class="canvas-toolbar">
|
||||
<p>Game</p>
|
||||
<button id="fullscreenButton" class="action-button" type="button">Fullscreen</button>
|
||||
<div class="canvas-container">
|
||||
<div class="canvas-toolbar">
|
||||
<p>Game</p>
|
||||
<button id="fullscreenButton" class="action-button" type="button">
|
||||
Fullscreen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="canvas-shell">
|
||||
<canvas
|
||||
id="canvas"
|
||||
class="emscripten"
|
||||
aria-label="Thin Ice game canvas"
|
||||
oncontextmenu="event.preventDefault()"
|
||||
tabindex="-1"
|
||||
></canvas>
|
||||
</div>
|
||||
|
||||
<div class="canvas-options">
|
||||
<label
|
||||
><input type="checkbox" id="resize" /> Resize canvas in
|
||||
fullscreen</label
|
||||
>
|
||||
<label
|
||||
><input type="checkbox" id="pointerLock" checked /> Lock pointer in
|
||||
fullscreen</label
|
||||
>
|
||||
</div>
|
||||
|
||||
<div id="status" class="status">Downloading...</div>
|
||||
<progress id="progress" value="0" max="100" hidden></progress>
|
||||
|
||||
<details>
|
||||
<summary>Show console output</summary>
|
||||
<label class="visually-hidden" for="output">Console output</label>
|
||||
<textarea id="output" rows="8" readonly></textarea>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div class="canvas-shell">
|
||||
<canvas id="canvas" class="emscripten" aria-label="Thin Ice game canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
|
||||
</div>
|
||||
<section>
|
||||
<h2>How to Play</h2>
|
||||
<ul class="feature-list">
|
||||
<li>Use WASM keys to move across the ice.</li>
|
||||
<li>Plan ahead so you do not strand yourself on broken tiles.</li>
|
||||
<li>Reach the end tile without falling into the water.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<div class="canvas-options">
|
||||
<label><input type="checkbox" id="resize" /> Resize canvas in fullscreen</label>
|
||||
<label><input type="checkbox" id="pointerLock" checked /> Lock pointer in fullscreen</label>
|
||||
</div>
|
||||
|
||||
<div id="status" class="status">Downloading...</div>
|
||||
<progress id="progress" value="0" max="100" hidden></progress>
|
||||
|
||||
<details>
|
||||
<summary>Show console output</summary>
|
||||
<label class="visually-hidden" for="output">Console output</label>
|
||||
<textarea id="output" rows="8" readonly></textarea>
|
||||
</details>
|
||||
<footer>
|
||||
<p>Built with C++ & Raylib, compiled to WASM with emscripten.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h2>How to Play</h2>
|
||||
<ul class="feature-list">
|
||||
<li>Use WASM keys to move across the ice.</li>
|
||||
<li>Plan ahead so you do not strand yourself on broken tiles.</li>
|
||||
<li>Reach the end tile without falling into the water.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<p>Built with C++ & Raylib, compiled to WASM with emscripten.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
<script async src="thin_ice.js"></script>
|
||||
</body>
|
||||
<script src="script.js"></script>
|
||||
<script async src="thin_ice.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,74 +1,107 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Travelling Salesman Problem</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a href="/#projects" class="back-button">← Back</a>
|
||||
<a href="/#projects" class="back-button">← Back</a>
|
||||
|
||||
<header>
|
||||
<h1>Travelling Salesman Problem</h1>
|
||||
<p class="subtitle">An interactive route visualiser built with WebGL and WebAssembly.</p>
|
||||
</header>
|
||||
<header>
|
||||
<h1>Travelling Salesman Problem</h1>
|
||||
<p class="subtitle">
|
||||
An interactive route visualiser built with WebGL and WebAssembly.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<p>
|
||||
After writing one of my first tutorials on the Travelling Salesman Problem, I wanted to revisit it with a slightly more technical stack.
|
||||
The backend is written in Zig (compiled to WebAssembly), and the rendering is done directly in WebGL instead of p5.js or Raylib.
|
||||
</p>
|
||||
<section>
|
||||
<p>
|
||||
After writing one of my first tutorials on the Travelling Salesman
|
||||
Problem, I wanted to revisit it with a slightly more technical stack.
|
||||
The backend is written in Zig (compiled to WebAssembly), and the
|
||||
rendering is done directly in WebGL instead of p5.js or Raylib.
|
||||
</p>
|
||||
<p>
|
||||
This project was also inspired by
|
||||
<a href="https://www.youtube.com/@sphaerophoria">sphaerophoria</a> who
|
||||
started a
|
||||
<a
|
||||
href="https://www.youtube.com/watch?v=RiLjPBci6Og&list=PL980gcR1LE3L8RoIMSNBFfw4dFfS3rrsk"
|
||||
>series</a
|
||||
>
|
||||
on creating his own map.
|
||||
</p>
|
||||
|
||||
<div class="info-box">
|
||||
<p>
|
||||
This project was also inspired by <a href="https://www.youtube.com/@sphaerophoria">sphaerophoria</a> who started a <a href="https://www.youtube.com/watch?v=RiLjPBci6Og&list=PL980gcR1LE3L8RoIMSNBFfw4dFfS3rrsk">series</a> on creating his own map.
|
||||
Drag points around the canvas and watch the route update in real
|
||||
time.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="info-box">
|
||||
<p>
|
||||
Drag points around the canvas and watch the route update in real time.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="canvas-container">
|
||||
<div class="canvas-toolbar">
|
||||
<div class="control-group" aria-label="TSP controls">
|
||||
<label for="pointCountInput">Points</label>
|
||||
<input id="pointCountInput" type="number" min="5" max="100" step="1" value="36" />
|
||||
<button id="applyPointCountButton" class="action-button" type="button">Regenerate</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="canvas-shell">
|
||||
<canvas id="tsp-canvas" aria-label="Travelling Salesman Problem canvas">
|
||||
HTML5 canvas not supported in browser
|
||||
</canvas>
|
||||
</div>
|
||||
|
||||
<div class="status-row">
|
||||
<div id="pointCountStatus" class="status">36 points</div>
|
||||
<p class="canvas-note">Tip: click and drag a point to recompute the route.</p>
|
||||
</div>
|
||||
<div class="canvas-container">
|
||||
<div class="canvas-toolbar">
|
||||
<div class="control-group" aria-label="TSP controls">
|
||||
<label for="pointCountInput">Points</label>
|
||||
<input
|
||||
id="pointCountInput"
|
||||
type="number"
|
||||
min="5"
|
||||
max="100"
|
||||
step="1"
|
||||
value="36"
|
||||
/>
|
||||
<button
|
||||
id="applyPointCountButton"
|
||||
class="action-button"
|
||||
type="button"
|
||||
>
|
||||
Regenerate
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h3>Technical Details</h3>
|
||||
<p>
|
||||
The backend is written in Zig, and the route is built in two passes: a nearest-neighbour pass followed by a 2-opt local search.
|
||||
</p>
|
||||
<p>
|
||||
Point coordinates are stored in WebAssembly memory, and the route order is recalculated whenever the layout changes.
|
||||
Rendering is handled in WebGL to keep interaction smooth as the point count increases.
|
||||
</p>
|
||||
</section>
|
||||
<div class="canvas-shell">
|
||||
<canvas
|
||||
id="tsp-canvas"
|
||||
aria-label="Travelling Salesman Problem canvas"
|
||||
>
|
||||
HTML5 canvas not supported in browser
|
||||
</canvas>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>Built with Zig, WebGL2 and WebAssembly</p>
|
||||
</footer>
|
||||
<div class="status-row">
|
||||
<div id="pointCountStatus" class="status">36 points</div>
|
||||
<p class="canvas-note">
|
||||
Tip: click and drag a point to recompute the route.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h3>Technical Details</h3>
|
||||
<p>
|
||||
The backend is written in Zig, and the route is built in two passes: a
|
||||
nearest-neighbour pass followed by a 2-opt local search.
|
||||
</p>
|
||||
<p>
|
||||
Point coordinates are stored in WebAssembly memory, and the route
|
||||
order is recalculated whenever the layout changes. Rendering is
|
||||
handled in WebGL to keep interaction smooth as the point count
|
||||
increases.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<p>Built with Zig, WebGL2 and WebAssembly</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="./point.js"></script>
|
||||
<script type="module" src="./main.js"></script>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -199,4 +199,3 @@ footer {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,166 +1,222 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0>
|
||||
<head>
|
||||
<meta
|
||||
name="viewport"
|
||||
width="device-width,"
|
||||
initial-scale="1.0,"
|
||||
maximum-scale="1.0,"
|
||||
user-scalable="0"
|
||||
/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.min.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
|
||||
background: #fafafa;
|
||||
}
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
Segoe UI,
|
||||
Roboto,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.intro {
|
||||
max-width: 900px;
|
||||
margin: 20px auto 10px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
.intro {
|
||||
max-width: 900px;
|
||||
margin: 20px auto 10px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.intro h1 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.intro h1 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.intro p {
|
||||
margin: 4px 0 14px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.intro p {
|
||||
margin: 4px 0 14px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.slidecontainer {
|
||||
width: 100%;
|
||||
/* Width of the outside container */
|
||||
}
|
||||
.slidecontainer {
|
||||
width: 100%;
|
||||
/* Width of the outside container */
|
||||
}
|
||||
|
||||
/* The slider itself */
|
||||
.slider {
|
||||
-webkit-appearance: none;
|
||||
/* Override default CSS styles */
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
/* Full-width */
|
||||
height: 25px;
|
||||
/* Specified height */
|
||||
background: #d3d3d3;
|
||||
/* Grey background */
|
||||
outline: none;
|
||||
/* Remove outline */
|
||||
opacity: 1;
|
||||
/* Set transparency (for mouse-over effects on hover) */
|
||||
-webkit-transition: .2s;
|
||||
/* 0.2 seconds transition on hover */
|
||||
transition: opacity .2s;
|
||||
}
|
||||
/* The slider itself */
|
||||
.slider {
|
||||
-webkit-appearance: none;
|
||||
/* Override default CSS styles */
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
/* Full-width */
|
||||
height: 25px;
|
||||
/* Specified height */
|
||||
background: #d3d3d3;
|
||||
/* Grey background */
|
||||
outline: none;
|
||||
/* Remove outline */
|
||||
opacity: 1;
|
||||
/* Set transparency (for mouse-over effects on hover) */
|
||||
-webkit-transition: 0.2s;
|
||||
/* 0.2 seconds transition on hover */
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
/* Override default look */
|
||||
appearance: none;
|
||||
width: 25px;
|
||||
/* Set a specific slider handle width */
|
||||
height: 25px;
|
||||
/* Slider handle height */
|
||||
background: #434343;
|
||||
/* Grey background */
|
||||
cursor: pointer;
|
||||
/* Cursor on hover */
|
||||
}
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
/* Override default look */
|
||||
appearance: none;
|
||||
width: 25px;
|
||||
/* Set a specific slider handle width */
|
||||
height: 25px;
|
||||
/* Slider handle height */
|
||||
background: #434343;
|
||||
/* Grey background */
|
||||
cursor: pointer;
|
||||
/* Cursor on hover */
|
||||
}
|
||||
|
||||
.slider::-moz-range-thumb {
|
||||
width: 25px;
|
||||
/* Set a specific slider handle width */
|
||||
height: 25px;
|
||||
/* Slider handle height */
|
||||
background: #434343;
|
||||
/* Grey background */
|
||||
cursor: pointer;
|
||||
/* Cursor on hover */
|
||||
}
|
||||
.slider::-moz-range-thumb {
|
||||
width: 25px;
|
||||
/* Set a specific slider handle width */
|
||||
height: 25px;
|
||||
/* Slider handle height */
|
||||
background: #434343;
|
||||
/* Grey background */
|
||||
cursor: pointer;
|
||||
/* Cursor on hover */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="intro">
|
||||
<h1>Oscillating Arrow Field Visualizer</h1>
|
||||
<p>
|
||||
A sketch to <s>hopefully</s> demonstrate the illusion of the <a
|
||||
href="https://en.wikipedia.org/wiki/M%C3%BCller-Lyer_illusion">Müller-Lyer Illusion</a>
|
||||
</p>
|
||||
<p>The lines - that are the same length, should look longer and shorter based on the angle of the arrow heads.
|
||||
</p>
|
||||
<p>
|
||||
Increasing the value adds more horizontal slices, creating a denser and more detailed pattern.
|
||||
Tap or click to pause and resume the motion.
|
||||
</p>
|
||||
<h1>Oscillating Arrow Field Visualizer</h1>
|
||||
<p>
|
||||
A sketch to <s>hopefully</s> demonstrate the illusion of the
|
||||
<a href="https://en.wikipedia.org/wiki/M%C3%BCller-Lyer_illusion"
|
||||
>Müller-Lyer Illusion</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
The lines - that are the same length, should look longer and shorter
|
||||
based on the angle of the arrow heads.
|
||||
</p>
|
||||
<p>
|
||||
Increasing the value adds more horizontal slices, creating a denser and
|
||||
more detailed pattern. Tap or click to pause and resume the motion.
|
||||
</p>
|
||||
</div>
|
||||
<div class="slidecontainer">
|
||||
<input type="range" min="2" max="11" step="1" value="2" class="slider" id="myRange">
|
||||
<input
|
||||
type="range"
|
||||
min="2"
|
||||
max="11"
|
||||
step="1"
|
||||
value="2"
|
||||
class="slider"
|
||||
id="myRange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let vMargin, hMargin, arrowHeight, t, lineNum;
|
||||
let lineMove = true;
|
||||
let vMargin, hMargin, arrowHeight, t, lineNum;
|
||||
let lineMove = true;
|
||||
|
||||
function setup() {
|
||||
createCanvas(windowWidth, windowHeight * 0.6);
|
||||
hMargin = 12;
|
||||
arrowHeight = 25;
|
||||
t = 0;
|
||||
lineNum = 1;
|
||||
function setup() {
|
||||
createCanvas(windowWidth, windowHeight * 0.6);
|
||||
hMargin = 12;
|
||||
arrowHeight = 25;
|
||||
t = 0;
|
||||
lineNum = 1;
|
||||
}
|
||||
|
||||
function windowResized() {
|
||||
resizeCanvas(windowWidth, windowHeight * 0.6);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
background(43);
|
||||
angleMode(DEGREES);
|
||||
lineNum = document.getElementById("myRange").value;
|
||||
|
||||
drawLine(hMargin, lineNum + 1);
|
||||
|
||||
if (lineMove) {
|
||||
t++;
|
||||
}
|
||||
|
||||
function windowResized() {
|
||||
resizeCanvas(windowWidth, windowHeight * 0.6);
|
||||
if (t == 360) {
|
||||
t = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function draw() {
|
||||
background(43);
|
||||
angleMode(DEGREES);
|
||||
lineNum = document.getElementById('myRange').value;
|
||||
function drawLine(hm, vm) {
|
||||
//let changex = map(sin(t), -1, 1, -22, 22);
|
||||
let changex = sin(t) * 18;
|
||||
vm /= 10;
|
||||
for (let j = 1; j < vm - 1; j++) {
|
||||
stroke(255, 34, 63);
|
||||
strokeWeight(4);
|
||||
line(width / hm, (j * height) / vm, width / 2, (j * height) / vm); //red line
|
||||
|
||||
drawLine(hMargin, lineNum + 1);
|
||||
stroke(69, 98, 255);
|
||||
line(
|
||||
width / 2,
|
||||
(j * height) / vm,
|
||||
((hm - 1) * width) / hm,
|
||||
(j * height) / vm,
|
||||
); //blue line
|
||||
|
||||
if (lineMove) {
|
||||
t++;
|
||||
}
|
||||
stroke(255);
|
||||
line(
|
||||
width / hm,
|
||||
(j * height) / vm,
|
||||
width / hm + changex,
|
||||
(j * height) / vm - arrowHeight,
|
||||
); //left top
|
||||
line(
|
||||
width / hm + changex,
|
||||
(j * height) / vm + arrowHeight,
|
||||
width / hm,
|
||||
(j * height) / vm,
|
||||
); //left bottom
|
||||
|
||||
if (t == 360) {
|
||||
t = 0;
|
||||
}
|
||||
line(
|
||||
((hm - 1) * width) / hm + changex,
|
||||
(j * height) / vm + arrowHeight,
|
||||
((hm - 1) * width) / hm,
|
||||
(j * height) / vm,
|
||||
); //right bottom
|
||||
line(
|
||||
width / 2 - changex,
|
||||
(j * height) / vm + arrowHeight,
|
||||
width / 2,
|
||||
(j * height) / vm,
|
||||
); //center bottom
|
||||
|
||||
line(
|
||||
((hm - 1) * width) / hm,
|
||||
(j * height) / vm,
|
||||
((hm - 1) * width) / hm + changex,
|
||||
(j * height) / vm - arrowHeight,
|
||||
); //right bottom
|
||||
line(
|
||||
width / 2,
|
||||
(j * height) / vm,
|
||||
width / 2 - changex,
|
||||
(j * height) / vm - arrowHeight,
|
||||
); //center top
|
||||
}
|
||||
}
|
||||
|
||||
function drawLine(hm, vm) {
|
||||
//let changex = map(sin(t), -1, 1, -22, 22);
|
||||
let changex = sin(t) * 18;
|
||||
vm /= 10;
|
||||
for (let j = 1; j < vm - 1; j++) {
|
||||
|
||||
stroke(255, 34, 63);
|
||||
strokeWeight(4);
|
||||
line(width / hm, j * height / vm, width / 2, j * height / vm); //red line
|
||||
|
||||
stroke(69, 98, 255);
|
||||
line(width / 2, j * height / vm, (hm - 1) * width / hm, j * height / vm); //blue line
|
||||
|
||||
stroke(255);
|
||||
line(width / hm, j * height / vm, width / hm + changex, (j * height / vm) - arrowHeight); //left top
|
||||
line(width / hm + changex, (j * height / vm) + arrowHeight, width / hm, j * height / vm); //left bottom
|
||||
|
||||
line((hm - 1) * width / hm + changex, j * height / vm + arrowHeight, (hm - 1) * width / hm, j * height / vm); //right bottom
|
||||
line(width / 2 - changex, j * height / vm + arrowHeight, width / 2, j * height / vm); //center bottom
|
||||
|
||||
line((hm - 1) * width / hm, j * height / vm, (hm - 1) * width / hm + changex, (j * height / vm) - arrowHeight); //right bottom
|
||||
line(width / 2, j * height / vm, width / 2 - changex, (j * height / vm) - arrowHeight); //center top
|
||||
}
|
||||
}
|
||||
|
||||
function mouseClicked() {
|
||||
lineMove = !lineMove;
|
||||
}
|
||||
function mouseClicked() {
|
||||
lineMove = !lineMove;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user