Hi!
I’ve been trying to create a very basic version of Robert Hodgin (aka Flight 404) 's Meander program but I am having some difficulty and I’m not sure what the problem is.
The sketch I am working on is here: https://www.openprocessing.org/sketch/904851.
I have been using his write up and this how I have implemented the steps:
-
Implement the “bitangent”. (I would normally refer to this as a normal rather than a bitangent–is this right? I’ll stick to his wording though so as not to complicate this.)
His implementation: http://roberthodgin.com/v2/wp-content/uploads/2020/05/process_2_bitangent.png
My implemenation: https://imgur.com/a/MDIDcAcThis seems to be working fine. This is the code I have been using for it:
//m=Δy/Δx
let tangentGradient = (currentPoint.y - previousPoint.y) / (currentPoint.x - previousPoint.x);
//convert from gradient to angle
let tangentAngle = atan(tangentGradient) + ((currentPoint.x - previousPoint.x) < 0 ? -PI : 0);
// if (tangentAngle > TAU / 4 && tangentAngle < TAU * 3 / 4) tangentAngle -= PI;
//rotate 90 degrees
let bitangentAngle = tangentAngle - HALF_PI;
let bitangent = createVector(1, 0).rotate(bitangentAngle);
-
Modify the bitangent by scaling according to the curvature at that point. This seemed to work for me too. I used this code to find the curvature and to find out whether it is negative or positive I checked to see if the change in angle was positive or negative.
This is the part of the code where I calulate the curvature:
//-----calculate curvature-----
let curvatures = [];
//leave out the beginning and end points as they only have one neighbour
for (let i = 1; i < riverPoints.length - 2; i++) {
//calulate the curvature at point i using the method given here:
//https://uk.mathworks.com/matlabcentral/answers/284245-matlab-code-for-computing-curvature-equation#answer_222173
let a = p5.Vector.dist(riverPoints[i - 1], riverPoints[i]);
let b = p5.Vector.dist(riverPoints[i], riverPoints[i + 1]);
let c = p5.Vector.dist(riverPoints[i - 1], riverPoints[i + 1]);
//area of the triangle that connects the three points
s = (a+b+c)/2;
A = sqrt(s*(s-a)*(s-b)*(s-c)); //Area of triangle
//calulate whether the curvature is positive or negative
let gradientA = (riverPoints[i].y -riverPoints[i-1].y)/(riverPoints[i].x -riverPoints[i-1].x);
let angleA = atan(gradientA) + ((riverPoints[i].x -riverPoints[i-1].x) < 0 ? -PI : 0);
let gradientB = (riverPoints[i+1].y -riverPoints[i].y)/(riverPoints[i+1].x -riverPoints[i].x);
let angleB = atan(gradientB) + ((riverPoints[i+1].x -riverPoints[i].x) < 0 ? -PI : 0);
let angleChange = angleB - angleA;
if(angleChange > TAU/2) angleChange -= TAU;
if(angleChange < -TAU/2) angleChange += TAU;
if(angleChange < 0) A*=-1;
//curvature of the circumscribing circle (K)
curvatures[i] = 4 * A / (a * b * c);
}
I then add on the curvature to the bitangent:
let modifiedBitangent = p5.Vector.mult(bitangent, curvature);
.
This seems to work.
This is what I get: https://imgur.com/a/v0pk02Y and this is what Robert shows in his write-up: http://roberthodgin.com/v2/wp-content/uploads/2020/05/process_2_bitangent_mod.png.
-
Add the tangent to the modified bitangent to get the velocity (or the blended directional vector as Hodgin calls it). I am not adding the tangent to the velocity here (the velocity is just the modified bitangent) as I am pretty sure the problem lies in the modified bitangent not the tangent part.
-
Move the points by adding the velocity to each of them.
So the problem is this:
Once I start trying to move the river (by adding the velocity to each point), after a few iterations I get these crazy zig-zag patterns: https://imgur.com/a/4zyOUFs and after a while it becomes this: https://imgur.com/a/PkL5Gqx
Why is it doing this? Before I start moving the points everything seems to be calculated properly but once it starts I get these wacky patterns.
Full code from the Open Processing sketch linked above
const speed = 1;
const numberOfPoints = 400;
let riverPoints = [];
function setup() {
createCanvas(windowWidth, windowHeight);
//intital set of points
//used perlin noise so that the line we start off with is a bit wiggly
for (let x = 0; x <= width; x += width/(numberOfPoints-1)) {
let y = height / 2 + map(noise(x * 0.002), 0, 1, -400, 400);
riverPoints.push(createVector(x + map(noise(x * 0.002, 345), 0, 1, -100, 100), y));
}
strokeWeight(2);
stroke("#f5ea80");
}
function draw() {
background("#3A2D3D");
//-----calculate curvature-----
let curvatures = [];
//leave out the beginning and end points as they only have one neighbour
for (let i = 1; i < riverPoints.length - 2; i++) {
//calulate the curvature at point i using the method given here:
//https://uk.mathworks.com/matlabcentral/answers/284245-matlab-code-for-computing-curvature-equation#answer_222173
let a = p5.Vector.dist(riverPoints[i - 1], riverPoints[i]);
let b = p5.Vector.dist(riverPoints[i], riverPoints[i + 1]);
let c = p5.Vector.dist(riverPoints[i - 1], riverPoints[i + 1]);
//area of the triangle that connects the three points
s = (a+b+c)/2;
A = sqrt(s*(s-a)*(s-b)*(s-c)); //Area of triangle
//calulate whether the curvature is positive or negative
let gradientA = (riverPoints[i].y -riverPoints[i-1].y)/(riverPoints[i].x -riverPoints[i-1].x);
let angleA = atan(gradientA) + ((riverPoints[i].x -riverPoints[i-1].x) < 0 ? -PI : 0);
let gradientB = (riverPoints[i+1].y -riverPoints[i].y)/(riverPoints[i+1].x -riverPoints[i].x);
let angleB = atan(gradientB) + ((riverPoints[i+1].x -riverPoints[i].x) < 0 ? -PI : 0);
let angleChange = angleB - angleA;
if(angleChange > TAU/2) angleChange -= TAU;
if(angleChange < -TAU/2) angleChange += TAU;
if(angleChange < 0) A*=-1;
//curvature of the circumscribing circle (K)
curvatures[i] = 4 * A / (a * b * c);
}
//-----calculate tangent, bitangent and modified bitangent and draw lines-----
let previousPoint = createVector(riverPoints[0].x - width/numberOfPoints, riverPoints[0].y + 0.001);
for (let i = 0; i < riverPoints.length - 1; i++) {
let currentPoint = riverPoints[i];
//draw line
line(currentPoint.x, currentPoint.y, previousPoint.x, previousPoint.y);
//m=Δy/Δx
let tangentGradient = (currentPoint.y - previousPoint.y) / (currentPoint.x - previousPoint.x);
//convert from gradient to angle
let tangentAngle = atan(tangentGradient) + ((currentPoint.x - previousPoint.x) < 0 ? -PI : 0);
// if (tangentAngle > TAU / 4 && tangentAngle < TAU * 3 / 4) tangentAngle -= PI;
//rotate 90 degrees
let bitangentAngle = tangentAngle - HALF_PI;
let curvature = curvatures[i] ?? 0;
//curvature = curvature > 0 ? 1 : -1;
let bitangent = createVector(1, 0).rotate(bitangentAngle);
let modifiedBitangent = p5.Vector.mult(bitangent, curvature);
//modifiedBitangent.mult(10);
let tangent = createVector(1, 0).rotate(tangentAngle);
//tangent.setMag(1);
let velocity = p5.Vector.add(modifiedBitangent, tangent);
//velocity.setMag(1);
//draw the modified bitangent line
stroke("#D3414D"); //#1DC1A1
line(currentPoint.x, currentPoint.y, currentPoint.x + modifiedBitangent.x * 700, currentPoint.y + modifiedBitangent.y * 700);
stroke("#f5ea80");
previousPoint = currentPoint.copy();
velocity = modifiedBitangent;
velocity.mult(speed);
currentPoint.add(velocity);
}
}
function keyPressed() {
if (key === "s") save();
}
Sorry for the external links to images, I couldn’t get inline images to work.