Pink Floyd - Dark Side of the Moon
The code still needs A LOT of work and cleanup, but the basics work and I thought it already looks pretty cool.
Todo: clean-up, make intersection code work for all vectors, add different shapes (now only prism), make originating ray easily configurable
Log in to post a comment.
let prismSize = 70; //min=30, max=100, step=1
let baseIndex = 1.17; // min=1.05, max=1.5, step=0.01
// You can find the Turtle API reference here: https://turtletoy.net/syntax
var fullOpacity = 100
Canvas.setpenopacity(-1 / fullOpacity);
function Bale(size) { this.population = []; for(var i = 0; i < size; i++) { this.population.push(new Turtle()); } }
Bale.prototype.forward = function(distance) { this.population.forEach(function(item) { item.forward(distance); }) }
Bale.prototype.fd = function(distance) { this.population.forEach(function(item) { item.fd(distance); }) }
Bale.prototype.backward = function(distance) { this.population.forEach(function(item) { item.backward(distance); }) }
Bale.prototype.bk = function(distance) { this.population.forEach(function(item) { item.bk(distance); }) }
Bale.prototype.right = function(angle) { this.population.forEach(function(item) { item.right(angle); }) }
Bale.prototype.rt = function(angle) { this.population.forEach(function(item) { item.rt(angle); }) }
Bale.prototype.left = function(angle) { this.population.forEach(function(item) { item.left(angle); }) }
Bale.prototype.lt = function(angle) { this.population.forEach(function(item) { item.lt(angle); }) }
Bale.prototype.goto = function(x, y) { this.population.forEach(function(item) { item.goto(x, y); }) }
Bale.prototype.setpos = function(x, y) { this.population.forEach(function(item) { item.setpos(distx, yance); }) }
Bale.prototype.setposition = function(x, y) { this.population.forEach(function(item) { item.setposition(x, y); }) }
Bale.prototype.jump = function(x, y) { this.population.forEach(function(item) { item.jump(x, y); }) }
Bale.prototype.jmp = function(x, y) { this.population.forEach(function(item) { item.jmp(x, y); }) }
Bale.prototype.setx = function(x) { this.population.forEach(function(item) { item.setx(x); }) }
Bale.prototype.sety = function(y) { this.population.forEach(function(item) { item.sety(y); }) }
Bale.prototype.setheading = function(to_angle) { this.population.forEach(function(item) { item.setheading(to_angle); }) }
Bale.prototype.seth = function(to_angle) { this.population.forEach(function(item) { item.seth(to_angle); }) }
Bale.prototype.home = function() { this.population.forEach(function(item) { item.home(); }) }
Bale.prototype.circle = function(radius, extent, steps) { this.population.forEach(function(item) { item.circle(radius, extent, steps); }) }
Bale.prototype.position = function() { return this.population[0].position(); }
Bale.prototype.pos = function() { return this.population[0].pos(); }
Bale.prototype.xcor = function() { return this.population[0].xcor(); }
Bale.prototype.x = function() { return this.population[0].x(); }
Bale.prototype.ycor = function() { return this.population[0].ycor(); }
Bale.prototype.y = function() { return this.population[0].y(); }
Bale.prototype.heading = function() { return this.population[0].heading(); }
Bale.prototype.h = function() { return this.population[0].h(); }
Bale.prototype.degrees = function(fullcircle = 360.0) { this.population.forEach(function(item) { item.degrees(fullcircle); }) }
Bale.prototype.radians = function() { this.population.forEach(function(item) { item.radians(); }) }
Bale.prototype.pendown = function() { this.population.forEach(function(item) { item.pendown(); }) }
Bale.prototype.pd = function() { this.population.forEach(function(item) { item.pd(); }) }
Bale.prototype.down = function() { this.population.forEach(function(item) { item.down(); }) }
Bale.prototype.penup = function() { this.population.forEach(function(item) { item.penup(); }) }
Bale.prototype.pu = function() { this.population.forEach(function(item) { item.pu(); }) }
Bale.prototype.up = function() { this.population.forEach(function(item) { item.up; }) }
function Ray(origin, direction) {
this.o = origin;
this.d = direction;
}
Ray.prototype.getOrigin = function() {
return [this.o[0], this.o[1]];
}
Ray.prototype.getDirection = function() {
return [this.d[0], this.d[1]];
}
Ray.prototype.draw = function(turtle) {
turtle.jump(this.o);
turtle.goto(addVec(this.o, this.d));
}
Ray.prototype.getIntersectInfo = function(ray) {
//r1 = ray.origin + (t1 * ray.direction);
//r2 = this.origin + (t2 * this.direction);
//r1 = r2
// console.log('This: ' + this.toString());
// console.log('That: ' + ray.toString());
//todo: improve
var x = (( ray.d[1]*ray.o[0] / ray.d[0]) - ( this.d[1]*this.o[0] / this.d[0]) + ( this.d[1]*this.o[1] / this.d[1]) - ( ray.d[1]*ray.o[1] / ray.d[1])) / ((ray.d[1]/ ray.d[0]) - (this.d[1]/ this.d[0]));
return {
angle: vecAngle(ray.d, this.d)
,x: x
,y: this.o[1] + ((this.d[1] / this.d[0]) * (x - this.o[0])) //todo: improve
}
}
Ray.prototype.toString = function() {
return '[' + this.o[0] + ', ' + this.o[1] + '] + t[' + this.d[0] + ', ' + this.d[1] + ']';
}
function Triangle(origin, vector1, vector2) {
this.o = origin;
this.vec1 = vector1;
this.vec2 = vector2;
this.cacheProperties();
}
Triangle.prototype.cacheProperties = function() {
this.sides = [
this.vec1
,this.vec2
,subtractVec(this.vec2, this.vec1)
];
this.edges = [
this.o
,addVec(this.o, this.vec1)
,addVec(this.o, this.vec2)
]
this.rays = [
new Ray(this.o, this.vec1)
,new Ray(this.o, this.vec2)
,new Ray(this.edges[1], this.sides[2])
]
this.sideCenters = [
addVec(this.o, multiplyVec(this.sides[0], .5))
,addVec(this.o, multiplyVec(this.sides[1], .5))
,addVec(addVec(this.o, this.sides[0]), multiplyVec(this.sides[2], .5))
];
var p1 = addVec(this.o, this.vec1);
var p2 = addVec(this.o, this.vec2);
this.centerOfMass = [(this.o[0] + p1[0] + p2[0]) / 3,(this.o[1] + p1[1] + p2[1]) / 3];
this.orthogonals = [
[this.sides[0][1], this.sides[0][0] * -1]
,[this.sides[1][1] * -1, this.sides[1][0]]
,[this.sides[2][1], this.sides[2][0] * -1]
];
}
Triangle.prototype.draw = function(turtle) {
turtle.jump(this.o);
turtle.goto(addVec(this.o, this.vec1));
turtle.goto(addVec(this.o, this.vec2));
turtle.goto(this.o);
}
Triangle.prototype.getClosestSideCenter = function(vec) {
for(var i = 0; i < this.sideCenters.length; i++) {
var testD = vecLength( subtractVec(vec, this.sideCenters[i]));
if(i == 0 || testD < minD) {
minIdx = i;
minD = testD;
}
}
return this.sideCenters[minIdx];
}
Triangle.prototype.getIntersectInfo = function(ray) {
var infos = [
this.rays[0].getIntersectInfo(ray)
,this.rays[1].getIntersectInfo(ray)
,this.rays[2].getIntersectInfo(ray)
]
for(var i = 0; i < infos.length; i++) {
var orthoDot = vecDot(this.orthogonals[i], ray.d);
infos[i].angleWithOrthogonal = (90 - infos[i].angle) * (orthoDot < 0? 1: -1)
}
//negative angleWithOrthogonal means ray is going out of triangle
//todo: improve
infos = infos.sort(function(a, b) { return a.x < b.x? -1: 1; })
//console.log(infos)
return infos;
}
// Global code will be evaluated once.
const colors = [];
for(var j = 0; j < fullOpacity; j++) {
colors[j] = new Bale( getBaleSize(j) );
}
var prism = new Triangle(
[0, -1/4 * prismSize * Math.sqrt(3)]
, [prismSize / 2, Math.sqrt(Math.pow(prismSize, 2) - Math.pow(prismSize /2 , 2))]
, [prismSize / -2, Math.sqrt(Math.pow(prismSize, 2) - Math.pow(prismSize /2 , 2))]
);
prism.draw(new Bale(fullOpacity / 1.5));
var rayOrigin = [-100, Math.sqrt(Math.pow(prismSize, 2) - Math.pow(prismSize /2 , 2)) / 2];
var rayTarget = subtractVec( prism.getClosestSideCenter(rayOrigin), rayOrigin );
//var testRay = new Ray(rayOrigin, rayTarget);
//var info = prism.getIntersectInfo(testRay);
function drawVec(origin, vector, turtle) {
turtle.jump(origin);
turtle.goto(addVec(origin, vector));
}
// The walk function will be called until it returns false.
function walk(i) {
//TODO: CLEANUP BIG TIME!!1
var r = new Ray(rayOrigin, rayTarget);
var bale = new Bale(100 - i);
var index = baseIndex + ((i * i) / (2 * fullOpacity * fullOpacity));//2.4 = diamond for potasium wavelength;
var rt = [rayTarget[0], rayTarget[1]];
r.draw(bale);
var info = prism.getIntersectInfo(r);
// index = sin(in) / sin(out)
var angleOut = Math.asin( Math.sin(info[0].angleWithOrthogonal / 360 * (2*Math.PI)) / index );
var adjustmentAngle = ((info[0].angleWithOrthogonal / 360) * (2 * Math.PI)) - angleOut;
adjustmentAngle *= info[0].angleWithOrthogonal < 0? -1: 1;
rt[0] = (rt[0] * Math.cos(adjustmentAngle)) - (rt[1] * Math.sin(adjustmentAngle));
rt[1] = (rt[0] * Math.sin(adjustmentAngle)) + (rt[1] * Math.cos(adjustmentAngle));
var nextR = new Ray([info[0].x, info[0].y], rt);
var info2 = prism.getIntersectInfo(nextR);
var angleOut2 = Math.asin( Math.sin(info[1].angleWithOrthogonal / 360 * (2*Math.PI)) / index );
var adjustmentAngle2 = ((info2[1].angleWithOrthogonal / 360) * (2 * Math.PI)) - angleOut2;
adjustmentAngle2 *= info2[1].angleWithOrthogonal < 0? -1: 1;
var rt2 = [rt[0], rt[1]];
rt2[0] = (rt2[0] * Math.cos(adjustmentAngle2)) - (rt2[1] * Math.sin(adjustmentAngle2));
rt2[1] = (rt2[0] * Math.sin(adjustmentAngle2)) + (rt2[1] * Math.cos(adjustmentAngle2));
var nextR2 = new Ray([info2[1].x, info2[1].y], multiplyVec(rt2, 2));
nextR.d = subtractVec(nextR2.o, nextR.o);
nextR.draw(bale);
nextR2.draw(bale);
return i < fullOpacity;
}
function getBaleSize(j) {
return fullOpacity * (Math.cos( Math.PI + ((j / fullOpacity) * (Math.PI / 2)) ) + 1);
}
function subtractVec(v1, v2) {
return [v1[0] - v2[0], v1[1] - v2[1]];
}
function addVec(v1, v2) {
return [v1[0] + v2[0], v1[1] + v2[1]];
}
function multiplyVec(vec, scalar) {
return [vec[0] * scalar, vec[1] * scalar];
}
function vecLength(vec) {
return Math.sqrt(Math.pow(vec[0], 2) + Math.pow(vec[1], 2));
}
function vecDot(vec1, vec2) {
return (vec1[0]*vec2[0]) + (vec1[1]*vec2[1]);
}
function vecAngle(vec1, vec2) {
// cos(a) = (vec1 · vec2) / ( |vec1| * |vec2| )
var dot = vecDot(vec1, vec2);
var rads = Math.acos(dot / (vecLength(vec1) * vecLength(vec2)));
var angle = (rads * 360) / (2 * Math.PI);
if(angle > 90) {
angle = 180 - angle;
}
return angle;
}