xxxxxxxxxx
169
// Adpated from:
// 2023 January Creative Coding Journal
// https://github.com/carlynorama/2023January-30DaysNatureOfCode/
//
// /22-path-following/01-scalar-projections/sketch.ts
// calynorama 2023 Jan 22
//
// Resources:
// - https://thecodingtrain.com/tracks/the-nature-of-code-2/noc/5-autonomous-agents/6-scalar-projection
// - https://radzion.com/blog/linear-algebra/vectors
// - https://www.khanacademy.org/math/linear-algebra/matrix-transformations/lin-trans-examples/v/introduction-to-projections
// - https://www.khanacademy.org/math/trigonometry/trig-with-general-triangles/law-of-cosines/v/law-of-cosines
//Base vector and rotating vectors are random vectors, generated everytime sketch is run.
let base;
let secondV;
let projection;
let subtraction;
let angle_inc = Math.PI / 180;
function setup() {
createCanvas(400, 400);
colorMode(HSB);
background(0, 0, 0);
base = createVector(random(-width / 3, width / 3), random(-height / 3, height / 3));
secondV = createVector(random(-width / 3, width / 3), random(-height / 3, height / 3));
console.log("-------- DONE SETUP --------");
}
function draw() {
//calculate vectors.
secondV = p5.Vector.fromAngle(secondV.heading() + angle_inc, secondV.mag());
projection = createProjection(base, secondV);
subtraction = secondV.copy().sub(projection);
//move to center
translate(width / 2, height / 2);
//draw the scene
drawBackground(base, secondV);
drawBoundsInterceptVectors(base, 1, 85);
//draw the subtration vector from the tip of the projection to the
push();
translate(projection.x, projection.y);
drawGrayVector(subtraction, 1, 40);
pop();
//draw everyone else
drawGrayVector(base, 2, 95);
drawGrayVector(secondV, 2, 20);
drawVector(projection, 3, 200);
}
//--------------------------------------------------- CALCULATIONS
function createProjection(base, toProject) {
const normalized = base.copy().normalize();
//the magnitude is the component of toProject that is in the base direction,
//as if base was i-hat
//dot products are about that directional relationship. See links above.
return normalized.mult(toProject.dot(normalized))
}
function createNormalTo(vector) {
const perpAngle = Math.atan2(vector.x, -vector.y)
return p5.Vector.fromAngle(perpAngle, vector.mag());
}
function getBoundsIntercept(vector) {
//assumes vector origin is at center of bounds.
const halfWidth = width / 2;
const halfHeight = height / 2;
//console.log("vector", vector.x, vector.y);
const slope = vector.y / vector.x;
let testX = halfWidth;
if (vector.x < 0) {
testX *= -1;
}
const heightIntercept = slope * (testX);
//console.log(slope, testX, heightIntercept);
if (heightIntercept <= halfHeight && heightIntercept >= -halfHeight) {
//console.log("intercepts vertical at",heightIntercept);
return createVector(testX, heightIntercept);
}
let testY = halfHeight;
if (vector.y < 0) {
testY *= -1;
}
const widthIntercept = testY / slope;
//console.log(testY, widthIntercept);
if (widthIntercept <= halfWidth && widthIntercept >= -halfWidth) {
//console.log("intercepts horizontal at", widthIntercept);
return createVector(widthIntercept, testY);
}
return vector;
}
//---------------------------------------------- DRAWING FUNCTIONS
function drawBoundsInterceptVectors(vector, weight, brightness) {
let drawThis = getBoundsIntercept(vector);
drawGrayVector(drawThis, weight, brightness);
//draw the inverse vector.
drawGrayVector(createVector(-drawThis.x, -drawThis.y), weight, brightness);
}
function drawVector(vector, weight, hue) {
push();
strokeWeight(weight);
stroke(hue, 60, 80);
line(0, 0, vector.x, vector.y);
translate(vector.x, vector.y);
rotate(vector.heading());
const arrowTip = 8;
line(0, 0, -arrowTip, -arrowTip / 2);
line(0, 0, -arrowTip, arrowTip / 2);
pop();
}
function drawGrayVector(vector, weight, brightness) {
push();
strokeWeight(weight);
stroke(0, 0, brightness);
line(0, 0, vector.x, vector.y);
translate(vector.x, vector.y);
rotate(vector.heading());
const arrowTip = 8;
line(0, 0, -arrowTip, -arrowTip / 2);
line(0, 0, -arrowTip, arrowTip / 2);
pop();
}
function drawBackground(baseVector, second) {
//assume canvas is translated to base of base vector
//works because canvas is square.
let horizonLength = width * Math.SQRT2;
let horizonHeight = height / 2 * Math.SQRT2;
let normalVector = createNormalTo(baseVector);
push();
rotate(baseVector.heading());
//If the dot product of the rotating vector("second") and the
//normal of the baseVector (the vector pointing 90 deg from it)
//is negative it means the rotating vector and the normal are
//facing in opposite directions. In this case it doesn't matter
//how much. We just want to know what orientation the
//background should be in.
//
if (second.dot(normalVector) < 0) {
horizonHeight *= -1;
}
//A bit sloppy, rects protrude from canvas.
noStroke();
fill(0, 0, 60);
rect(-horizonLength / 2, -horizonHeight, horizonLength, horizonHeight);
fill(0, 0, 70);
rect(-horizonLength / 2, horizonHeight, horizonLength, -horizonHeight);
pop();
}