Prism

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