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; }