From 49f87f08c492456f214fbf752e094cdde2ef56c3 Mon Sep 17 00:00:00 2001 From: "dusk (they/them)" <43045568+duskvirkus@users.noreply.github.com> Date: Wed, 13 Oct 2021 09:28:11 -0600 Subject: [PATCH] Pages for NOC 5.6 and 5.7 (#3371) * adding page for noc 5.6 * couple fixes * adding page for noc 5.7 * updating video ID * updating description * Update 5.7-path-following.md Co-authored-by: Daniel Shiffman --- _learning/nature-of-code/5.6-dot-product.md | 60 +++++ .../nature-of-code/5.7-path-following.md | 54 +++++ .../5.6-dot-product/angle-between/index.html | 14 ++ .../5.6-dot-product/angle-between/sketch.js | 61 +++++ .../scalar-projection/index.html | 14 ++ .../scalar-projection/sketch.js | 57 +++++ .../complex-path/index.html | 16 ++ .../5.7-path-following/complex-path/path.js | 48 ++++ .../5.7-path-following/complex-path/sketch.js | 75 ++++++ .../complex-path/vehicle.js | 219 ++++++++++++++++++ .../path-following/index.html | 16 ++ .../5.7-path-following/path-following/path.js | 26 +++ .../path-following/sketch.js | 33 +++ .../path-following/vehicle.js | 105 +++++++++ 14 files changed, 798 insertions(+) create mode 100644 _learning/nature-of-code/5.6-dot-product.md create mode 100644 _learning/nature-of-code/5.7-path-following.md create mode 100644 learning/nature-of-code/5.6-dot-product/angle-between/index.html create mode 100644 learning/nature-of-code/5.6-dot-product/angle-between/sketch.js create mode 100644 learning/nature-of-code/5.6-dot-product/scalar-projection/index.html create mode 100644 learning/nature-of-code/5.6-dot-product/scalar-projection/sketch.js create mode 100644 learning/nature-of-code/5.7-path-following/complex-path/index.html create mode 100644 learning/nature-of-code/5.7-path-following/complex-path/path.js create mode 100644 learning/nature-of-code/5.7-path-following/complex-path/sketch.js create mode 100644 learning/nature-of-code/5.7-path-following/complex-path/vehicle.js create mode 100644 learning/nature-of-code/5.7-path-following/path-following/index.html create mode 100644 learning/nature-of-code/5.7-path-following/path-following/path.js create mode 100644 learning/nature-of-code/5.7-path-following/path-following/sketch.js create mode 100644 learning/nature-of-code/5.7-path-following/path-following/vehicle.js diff --git a/_learning/nature-of-code/5.6-dot-product.md b/_learning/nature-of-code/5.6-dot-product.md new file mode 100644 index 0000000000..23509b3c77 --- /dev/null +++ b/_learning/nature-of-code/5.6-dot-product.md @@ -0,0 +1,60 @@ +--- +title: Vector Dot Product (Scalar Projection) +video_number: 5.6 +date: 2021-09-27 +video_id: DHPfoqiE4yQ +repository: nature-of-code/5.6-dot-product +can_contribute: true + +variations: + - name: "Angle Between" + folder: "angle-between" + lang: "p5js" + web_editor: ORP5Yx7JX + - name: "Scalar Projection" + folder: "scalar-projection" + lang: "p5js" + web_editor: c4jmHLFQI + +links: + - title: "Nature of Code Path Following Example" + url: https://editor.p5js.org/natureofcode/sketches/SkMI894DX + - title: "Steering Behaviors For Autonomous Characters" + author: Craig Reynolds + url: http://www.red3d.com/cwr/steer/ + - title: "Scalar Projection (Wikipedia)" + url: https://en.wikipedia.org/wiki/Scalar_projection + - title: "Vector Projection (Wikipedia)" + url: https://en.wikipedia.org/wiki/Vector_projection + +videos: + - title: "3D Rendering with Rotation and Projection - Coding Challenge 112" + author: "The Coding Train" + url: /CodingChallenges/112-3d-rendering + - title: "Unit Vector (Normalize) - Nature of Code 1.5" + author: "The Coding Train" + url: /learning/nature-of-code/1.5-unit-vector + - title: "Dot products and duality" + author: "3Blue1Brown" + video_id: LyGKycYT2v0 + +topics: + - title: "Welcome! What are we looking at today?" + time: "0:00" + - title: "Explain! What is scalar projection?" + time: "1:01" + - title: "Explain! How do we use dot product to find the scalar projection?" + time: "3:04" + - title: "Code! Let's create a scalarProjection() function." + time: "6:13" + - title: "Code! Modifying the function to be vectorProjection()." + time: "8:01" + - title: "Explain! How is this useful for path following?" + time: "9:42" + - title: "Code! Let's see if it works for finding a point on a path?" + time: "11:39" + - title: "Thanks for watching! See you in the next video about path following." + time: "13:04" +--- + +This video covers the dot product and scalar projection with p5.js and vectors, concepts that I'll need for finding the distance between a point and a line which will lead to the path following steering behavior in the next video! diff --git a/_learning/nature-of-code/5.7-path-following.md b/_learning/nature-of-code/5.7-path-following.md new file mode 100644 index 0000000000..d9b09c01fc --- /dev/null +++ b/_learning/nature-of-code/5.7-path-following.md @@ -0,0 +1,54 @@ +--- +title: Path Following +video_number: 5.7 +date: 2021-09-24 +video_id: rlZYT-uvmGQ +repository: nature-of-code/5.7-path-following +can_contribute: true + +variations: + - name: "Path Following" + folder: "path-following" + lang: "p5js" + web_editor: dqM054vBV + - name: "Complex Path" + folder: "complex-path" + lang: "p5js" + web_editor: 2FFzvxwVt + +links: + - title: "Steering Behaviors For Autonomous Characters" + author: Craig Reynolds + url: http://www.red3d.com/cwr/steer/ + +videos: + - title: "Scalar Projection - Nature of Code 5.6" + author: "The Coding Train" + url: /learning/nature-of-code/5.6-dot-product + +topics: + - title: "Follow along, I look at path finding!" + time: "0:00" + - title: "Code! Let's create a Path class." + time: "0:23" + - title: "Code! Now we need a follow force." + time: "1:18" + - title: "Explain! What are the steps to path following?" + time: "1:51" + - title: "Code! Step one: predict future position." + time: "5:08" + - title: "Code! Modify vectorProjection() to findProjection()." + time: "5:40" + - title: "Code! Use findProjection() and the rest of the steps." + time: "8:12" + - title: "Code! Refining the example." + time: "10:30" + - title: "Ideas? What could you create?" + time: "11:49" + - title: "Amendment! I forgot to talk about direction!" + time: "13:28" + - title: "No idea what's next but hope to see you there!" + time: "14:43" +--- + +Continuing the quest to implement all of Craig Reynolds' steering behaviors in JavaScript with p5.js, in this video I tackle path following! diff --git a/learning/nature-of-code/5.6-dot-product/angle-between/index.html b/learning/nature-of-code/5.6-dot-product/angle-between/index.html new file mode 100644 index 0000000000..b63f80337a --- /dev/null +++ b/learning/nature-of-code/5.6-dot-product/angle-between/index.html @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/learning/nature-of-code/5.6-dot-product/angle-between/sketch.js b/learning/nature-of-code/5.6-dot-product/angle-between/sketch.js new file mode 100644 index 0000000000..af26ed4081 --- /dev/null +++ b/learning/nature-of-code/5.6-dot-product/angle-between/sketch.js @@ -0,0 +1,61 @@ +// Scalar Projection (Angle Between) +// The Nature of Code +// The Coding Train / Daniel Shiffman +// https://youtu.be/DHPfoqiE4yQ +// https://thecodingtrain.com/learning/nature-of-code/5.6-dot-product.html + +// Angle Between: https://editor.p5js.org/codingtrain/sketches/ORP5Yx7JX +// Scalar Projection: https://editor.p5js.org/codingtrain/sketches/c4jmHLFQI + +function setup() { + createCanvas(windowWidth, windowHeight); +} + +function draw() { + background(255); + + // A "vector" (really a point) to store the mouse position and screen center position + const mouseLoc = createVector(mouseX, mouseY); + const centerLoc = createVector(width / 2, height / 2); + + // Aha, a vector to store the displacement between the mouse and center + const v = p5.Vector.sub(mouseLoc, centerLoc); + v.normalize(); + v.mult(75); + + const xaxis = createVector(75, 0); + // Render the vector + drawVector(v, createVector(100, height / 2), 1.0); + drawVector(xaxis, createVector(100, height / 2), 1.0); + + const theta = v.angleBetween(xaxis); + + fill(0); + textSize(32); + textFont("courier"); + textAlign(LEFT, CENTER); + text( + `${int(degrees(theta))} degrees\n${nf(theta, 1, 3)} radians`, + 200, + height / 2 + ); +} + +// Renders a vector object 'v' as an arrow and a position 'loc' +function drawVector(v, pos, scayl) { + push(); + const arrowsize = 6; + // Translate to position to render vector + translate(pos.x, pos.y); + stroke(0); + strokeWeight(2); + // Call vector heading function to get direction (pointing up is a heading of 0) + rotate(v.heading()); + // Calculate length of vector & scale it to be bigger or smaller if necessary + const len = v.mag() * scayl; + // Draw three lines to make an arrow + line(0, 0, len, 0); + line(len, 0, len - arrowsize, +arrowsize / 2); + line(len, 0, len - arrowsize, -arrowsize / 2); + pop(); +} diff --git a/learning/nature-of-code/5.6-dot-product/scalar-projection/index.html b/learning/nature-of-code/5.6-dot-product/scalar-projection/index.html new file mode 100644 index 0000000000..b63f80337a --- /dev/null +++ b/learning/nature-of-code/5.6-dot-product/scalar-projection/index.html @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/learning/nature-of-code/5.6-dot-product/scalar-projection/sketch.js b/learning/nature-of-code/5.6-dot-product/scalar-projection/sketch.js new file mode 100644 index 0000000000..146d06a6c8 --- /dev/null +++ b/learning/nature-of-code/5.6-dot-product/scalar-projection/sketch.js @@ -0,0 +1,57 @@ +// Scalar Projection (Scalar Projection) +// The Nature of Code +// The Coding Train / Daniel Shiffman +// https://youtu.be/DHPfoqiE4yQ +// https://thecodingtrain.com/learning/nature-of-code/5.6-dot-product.html + +// Angle Between: https://editor.p5js.org/codingtrain/sketches/ORP5Yx7JX +// Scalar Projection: https://editor.p5js.org/codingtrain/sketches/c4jmHLFQI + +let path; + +function setup() { + createCanvas(400, 400); + path = createVector(200, 60); +} + +function vectorProjection(a, b) { + let bCopy = b.copy().normalize(); + let sp = a.dot(bCopy); + bCopy.mult(sp); + return bCopy; +} + +function draw() { + background(0); + strokeWeight(4); + stroke(255); + let pos = createVector(100, 200); + + let mouse = createVector(mouseX, mouseY); + let a = p5.Vector.sub(mouse, pos); + + // line(pos.x, pos.y, pos.x + a.x, pos.y + a.y); + line(pos.x, pos.y, pos.x + path.x, pos.y + path.y); + + let v = vectorProjection(a, path); + + strokeWeight(8); + stroke(0, 0, 255); + //line(pos.x, pos.y, pos.x + v.x, pos.y + v.y); + + strokeWeight(1); + stroke(255); + // line(pos.x + a.x, pos.y + a.y, v.x + pos.x, v.y + pos.y); + + fill(0, 255, 0); + noStroke(); + circle(pos.x + a.x, pos.y + a.y, 16); + + fill(255, 0, 0); + noStroke(); + circle(v.x + pos.x, v.y + pos.y, 16); + + fill(0, 255, 0); + noStroke(); + // circle(pos.x, pos.y, 16); +} diff --git a/learning/nature-of-code/5.7-path-following/complex-path/index.html b/learning/nature-of-code/5.7-path-following/complex-path/index.html new file mode 100644 index 0000000000..536f247985 --- /dev/null +++ b/learning/nature-of-code/5.7-path-following/complex-path/index.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/learning/nature-of-code/5.7-path-following/complex-path/path.js b/learning/nature-of-code/5.7-path-following/complex-path/path.js new file mode 100644 index 0000000000..5c384f9a19 --- /dev/null +++ b/learning/nature-of-code/5.7-path-following/complex-path/path.js @@ -0,0 +1,48 @@ +// Path Following (Complex Path) +// The Nature of Code +// The Coding Train / Daniel Shiffman +// https://youtu.be/LrnR6dc2IfM +// https://thecodingtrain.com/learning/nature-of-code/5.7-path-following.html + +// Path Following: https://editor.p5js.org/codingtrain/sketches/dqM054vBV +// Complex Path: https://editor.p5js.org/codingtrain/sketches/2FFzvxwVt + +class Path { + constructor() { + // Arbitrary radius of 20 + // A path has a radius, i.e how far is it ok for the boid to wander off + this.radius = 20; + // A Path is an arraylist of points (PVector objects) + this.points = []; + } + + // Add a point to the path + addPoint(x, y) { + let point = createVector(x, y); + this.points.push(point); + } + + // Draw the path + display() { + strokeJoin(ROUND); + + // Draw thick line for radius + stroke(175); + strokeWeight(this.radius * 2); + noFill(); + beginShape(); + for (let v of this.points) { + vertex(v.x, v.y); + } + endShape(CLOSE); + // Draw thin line for center of path + stroke(0); + strokeWeight(1); + noFill(); + beginShape(); + for (let v of this.points) { + vertex(v.x, v.y); + } + endShape(CLOSE); + } +} diff --git a/learning/nature-of-code/5.7-path-following/complex-path/sketch.js b/learning/nature-of-code/5.7-path-following/complex-path/sketch.js new file mode 100644 index 0000000000..662eefedfa --- /dev/null +++ b/learning/nature-of-code/5.7-path-following/complex-path/sketch.js @@ -0,0 +1,75 @@ +// Path Following (Complex Path) +// The Nature of Code +// The Coding Train / Daniel Shiffman +// https://youtu.be/LrnR6dc2IfM +// https://thecodingtrain.com/learning/nature-of-code/5.7-path-following.html + +// Path Following: https://editor.p5js.org/codingtrain/sketches/dqM054vBV +// Complex Path: https://editor.p5js.org/codingtrain/sketches/2FFzvxwVt + +// Crowd Path Following +// Via Reynolds: http://www.red3d.com/cwr/steer/CrowdPath.html + +// Using this variable to decide whether to draw all the stuff +let debug = false; + +// A path object (series of connected points) +let path; + +// Two vehicles +let vehicles = []; + +function setup() { + createCanvas(640, 360); + // Call a function to generate new Path object + newPath(); + + // We are now making random vehicles and storing them in an ArrayList + for (let i = 0; i < 120; i++) { + newVehicle(random(width), random(height)); + } + createP( + "Hit 'd' to toggle debugging lines.
Click the mouse to generate new vehicles." + ); +} + +function draw() { + background(240); + // Display the path + path.display(); + + for (let v of vehicles) { + // Path following and separation are worked on in this function + v.applyBehaviors(vehicles, path); + // Call the generic run method (update, borders, display, etc.) + v.run(); + } +} + +function newPath() { + // A path is a series of connected points + // A more sophisticated path might be a curve + path = new Path(); + let offset = 30; + path.addPoint(offset, offset); + path.addPoint(width - offset, offset); + path.addPoint(width - offset, height - offset); + path.addPoint(width / 2, height - offset * 3); + path.addPoint(offset, height - offset); +} + +function newVehicle(x, y) { + let maxspeed = random(2, 4); + let maxforce = 0.3; + vehicles.push(new Vehicle(x, y, maxspeed, maxforce)); +} + +function keyPressed() { + if (key == "d") { + debug = !debug; + } +} + +function mousePressed() { + newVehicle(mouseX, mouseY); +} diff --git a/learning/nature-of-code/5.7-path-following/complex-path/vehicle.js b/learning/nature-of-code/5.7-path-following/complex-path/vehicle.js new file mode 100644 index 0000000000..0e451dc503 --- /dev/null +++ b/learning/nature-of-code/5.7-path-following/complex-path/vehicle.js @@ -0,0 +1,219 @@ +// Path Following (Complex Path) +// The Nature of Code +// The Coding Train / Daniel Shiffman +// https://youtu.be/LrnR6dc2IfM +// https://thecodingtrain.com/learning/nature-of-code/5.7-path-following.html + +// Path Following: https://editor.p5js.org/codingtrain/sketches/dqM054vBV +// Complex Path: https://editor.p5js.org/codingtrain/sketches/2FFzvxwVt + +// Path Following +// Vehicle class + +class Vehicle { + // Constructor initialize all values + constructor(x, y, ms, mf) { + this.position = createVector(x, y); + this.r = 12; + this.maxspeed = ms; + this.maxforce = mf; + this.acceleration = createVector(0, 0); + this.velocity = createVector(this.maxspeed, 0); + } + + // A function to deal with path following and separation + applyBehaviors(vehicles, path) { + // Follow path force + let f = this.follow(path); + // Separate from other boids force + let s = this.separate(vehicles); + // Arbitrary weighting + f.mult(3); + s.mult(1); + // Accumulate in acceleration + this.applyForce(f); + this.applyForce(s); + } + + applyForce(force) { + // We could add mass here if we want A = F / M + this.acceleration.add(force); + } + + // Main "run" function + run() { + this.update(); + this.render(); + } + + // This function implements Craig Reynolds' path following algorithm + // http://www.red3d.com/cwr/steer/PathFollow.html + follow(path) { + // Predict position 25 (arbitrary choice) frames ahead + let predict = this.velocity.copy(); + predict.normalize(); + predict.mult(25); + let predictpos = p5.Vector.add(this.position, predict); + + // Now we must find the normal to the path from the predicted position + // We look at the normal for each line segment and pick out the closest one + let normal = null; + let target = null; + let worldRecord = 1000000; // Start with a very high worldRecord distance that can easily be beaten + + // Loop through all points of the path + for (let i = 0; i < path.points.length; i++) { + // Look at a line segment + let a = path.points[i]; + let b = path.points[(i + 1) % path.points.length]; // Note Path has to wraparound + + // Get the normal point to that line + let normalPoint = getNormalPoint(predictpos, a, b); + + // Check if normal is on line segment + let dir = p5.Vector.sub(b, a); + // If it's not within the line segment, consider the normal to just be the end of the line segment (point b) + //if (da + db > line.mag()+1) { + if ( + normalPoint.x < min(a.x, b.x) || + normalPoint.x > max(a.x, b.x) || + normalPoint.y < min(a.y, b.y) || + normalPoint.y > max(a.y, b.y) + ) { + normalPoint = b.copy(); + // If we're at the end we really want the next line segment for looking ahead + a = path.points[(i + 1) % path.points.length]; + b = path.points[(i + 2) % path.points.length]; // Path wraps around + dir = p5.Vector.sub(b, a); + } + + // How far away are we from the path? + let d = p5.Vector.dist(predictpos, normalPoint); + // Did we beat the worldRecord and find the closest line segment? + if (d < worldRecord) { + worldRecord = d; + normal = normalPoint; + + // Look at the direction of the line segment so we can seek a little bit ahead of the normal + dir.normalize(); + // This is an oversimplification + // Should be based on distance to path & velocity + dir.mult(25); + target = normal.copy(); + target.add(dir); + } + } + + // Draw the debugging stuff + if (debug) { + // Draw predicted future position + stroke(0); + fill(0); + line(this.position.x, this.position.y, predictpos.x, predictpos.y); + ellipse(predictpos.x, predictpos.y, 4, 4); + + // Draw normal position + stroke(0); + fill(0); + ellipse(normal.x, normal.y, 4, 4); + // Draw actual target (red if steering towards it) + line(predictpos.x, predictpos.y, target.x, target.y); + if (worldRecord > path.radius) fill(255, 0, 0); + noStroke(); + ellipse(target.x, target.y, 8, 8); + } + + // Only if the distance is greater than the path's radius do we bother to steer + if (worldRecord > path.radius) { + return this.seek(target); + } else { + return createVector(0, 0); + } + } + + // Separation + // Method checks for nearby boids and steers away + separate(boids) { + let desiredseparation = this.r * 2; + let steer = createVector(0, 0, 0); + let count = 0; + // For every boid in the system, check if it's too close + for (let i = 0; i < boids.length; i++) { + let other = boids[i]; + let d = p5.Vector.dist(this.position, other.position); + // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself) + if (d > 0 && d < desiredseparation) { + // Calculate vector pointing away from neighbor + let diff = p5.Vector.sub(this.position, other.position); + diff.normalize(); + diff.div(d); // Weight by distance + steer.add(diff); + count++; // Keep track of how many + } + } + // Average -- divide by how many + if (count > 0) { + steer.div(count); + } + + // As long as the vector is greater than 0 + if (steer.mag() > 0) { + // Implement Reynolds: Steering = Desired - Velocity + steer.normalize(); + steer.mult(this.maxspeed); + steer.sub(this.velocity); + steer.limit(this.maxforce); + } + return steer; + } + + // Method to update position + update() { + // Update velocity + this.velocity.add(this.acceleration); + // Limit speed + this.velocity.limit(this.maxspeed); + this.position.add(this.velocity); + // Reset accelertion to 0 each cycle + this.acceleration.mult(0); + } + + // A method that calculates and applies a steering force towards a target + // STEER = DESIRED MINUS VELOCITY + seek(target) { + let desired = p5.Vector.sub(target, this.position); // A vector pointing from the position to the target + + // Normalize desired and scale to maximum speed + desired.normalize(); + desired.mult(this.maxspeed); + // Steering = Desired minus Vepositionity + let steer = p5.Vector.sub(desired, this.velocity); + steer.limit(this.maxforce); // Limit to maximum steering force + + return steer; + } + + render() { + // Simpler boid is just a circle + fill(75); + stroke(0); + push(); + translate(this.position.x, this.position.y); + ellipse(0, 0, this.r, this.r); + pop(); + } +} + +// A function to get the normal point from a point (p) to a line segment (a-b) +// This function could be optimized to make fewer new Vector objects +function getNormalPoint(p, a, b) { + // Vector from a to p + let ap = p5.Vector.sub(p, a); + // Vector from a to b + let ab = p5.Vector.sub(b, a); + ab.normalize(); // Normalize the line + // Project vector "diff" onto line by using the dot product + ab.mult(ap.dot(ab)); + let normalPoint = p5.Vector.add(a, ab); + return normalPoint; +} diff --git a/learning/nature-of-code/5.7-path-following/path-following/index.html b/learning/nature-of-code/5.7-path-following/path-following/index.html new file mode 100644 index 0000000000..536f247985 --- /dev/null +++ b/learning/nature-of-code/5.7-path-following/path-following/index.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/learning/nature-of-code/5.7-path-following/path-following/path.js b/learning/nature-of-code/5.7-path-following/path-following/path.js new file mode 100644 index 0000000000..e24c4a144f --- /dev/null +++ b/learning/nature-of-code/5.7-path-following/path-following/path.js @@ -0,0 +1,26 @@ +// Path Following (Path Following) +// The Nature of Code +// The Coding Train / Daniel Shiffman +// https://youtu.be/LrnR6dc2IfM +// https://thecodingtrain.com/learning/nature-of-code/5.7-path-following.html + +// Path Following: https://editor.p5js.org/codingtrain/sketches/dqM054vBV +// Complex Path: https://editor.p5js.org/codingtrain/sketches/2FFzvxwVt + +class Path { + constructor(x1, y1, x2, y2) { + this.start = createVector(x1, y1); + this.end = createVector(x2, y2); + this.radius = 20; + } + + show() { + stroke(255); + strokeWeight(2); + line(this.start.x, this.start.y, this.end.x, this.end.y); + + stroke(255, 100); + strokeWeight(this.radius * 2); + line(this.start.x, this.start.y, this.end.x, this.end.y); + } +} diff --git a/learning/nature-of-code/5.7-path-following/path-following/sketch.js b/learning/nature-of-code/5.7-path-following/path-following/sketch.js new file mode 100644 index 0000000000..d362c98623 --- /dev/null +++ b/learning/nature-of-code/5.7-path-following/path-following/sketch.js @@ -0,0 +1,33 @@ +// Path Following (Path Following) +// The Nature of Code +// The Coding Train / Daniel Shiffman +// https://youtu.be/LrnR6dc2IfM +// https://thecodingtrain.com/learning/nature-of-code/5.7-path-following.html + +// Path Following: https://editor.p5js.org/codingtrain/sketches/dqM054vBV +// Complex Path: https://editor.p5js.org/codingtrain/sketches/2FFzvxwVt + +let vehicle; +let path; + +function setup() { + createCanvas(800, 400); + vehicle = new Vehicle(100, 100); + vehicle.vel.x = 2; + path = new Path(0, height / 2, width, height / 2); +} + +function draw() { + background(0); + + path.end.y = mouseY; + + let force = vehicle.follow(path); + vehicle.applyForce(force); + + vehicle.edges(); + vehicle.update(); + vehicle.show(); + + path.show(); +} diff --git a/learning/nature-of-code/5.7-path-following/path-following/vehicle.js b/learning/nature-of-code/5.7-path-following/path-following/vehicle.js new file mode 100644 index 0000000000..b43c6bca58 --- /dev/null +++ b/learning/nature-of-code/5.7-path-following/path-following/vehicle.js @@ -0,0 +1,105 @@ +// Path Following (Path Following) +// The Nature of Code +// The Coding Train / Daniel Shiffman +// https://youtu.be/LrnR6dc2IfM +// https://thecodingtrain.com/learning/nature-of-code/5.7-path-following.html + +// Path Following: https://editor.p5js.org/codingtrain/sketches/dqM054vBV +// Complex Path: https://editor.p5js.org/codingtrain/sketches/2FFzvxwVt + +function findProjection(pos, a, b) { + let v1 = p5.Vector.sub(a, pos); + let v2 = p5.Vector.sub(b, pos); + v2.normalize(); + let sp = v1.dot(v2); + v2.mult(sp); + v2.add(pos); + return v2; +} + +class Vehicle { + constructor(x, y) { + this.pos = createVector(x, y); + this.vel = createVector(0, 0); + this.acc = createVector(0, 0); + this.maxSpeed = 6; + this.maxForce = 0.1; + this.r = 16; + } + + follow(path) { + // Path following algorithm here!! + + // Step 1 calculate future position + let future = this.vel.copy(); + future.mult(20); + future.add(this.pos); + fill(255, 0, 0); + noStroke(); + circle(future.x, future.y, 16); + + // Step 2 Is future on path? + let target = findProjection(path.start, future, path.end); + fill(0, 255, 0); + noStroke(); + circle(target.x, target.y, 16); + + let d = p5.Vector.dist(future, target); + if (d > path.radius) { + return this.seek(target); + } else { + return createVector(0, 0); + } + } + + seek(target, arrival = false) { + let force = p5.Vector.sub(target, this.pos); + let desiredSpeed = this.maxSpeed; + if (arrival) { + let slowRadius = 100; + let distance = force.mag(); + if (distance < slowRadius) { + desiredSpeed = map(distance, 0, slowRadius, 0, this.maxSpeed); + } + } + force.setMag(desiredSpeed); + force.sub(this.vel); + force.limit(this.maxForce); + return force; + } + + applyForce(force) { + this.acc.add(force); + } + + update() { + this.vel.add(this.acc); + this.vel.limit(this.maxSpeed); + this.pos.add(this.vel); + this.acc.set(0, 0); + } + + show() { + stroke(255); + strokeWeight(2); + fill(255); + push(); + translate(this.pos.x, this.pos.y); + rotate(this.vel.heading()); + triangle(-this.r, -this.r / 2, -this.r, this.r / 2, this.r, 0); + pop(); + } + + edges() { + if (this.pos.x > width + this.r) { + this.pos.x = -this.r; + } else if (this.pos.x < -this.r) { + this.pos.x = width + this.r; + } + if (this.pos.y > height + this.r) { + this.pos.y = -this.r; + } else if (this.pos.y < -this.r) { + this.pos.y = height + this.r; + } + } +}