Mix vertex and curveVertex in a shape? - p5.js

I am trying to create a shape with both straight lines and curved parts. I want all points to connect and then to fill the interior with a color. I cannot seem to locate this information or a way to do it in a single shape. Is there a way to do this? For example a rectangle with an inverted curve on each end? I can do it with arcs and lines like the below code but I have to think there is an easier way using beginShape somehow or something similar? Also even using the lines and arcs, I am not sure how to fill it.
arc(200, 200, 150, 150, radians(0), radians(90), OPEN);
arc(200, 200, 50, 50, radians(0), radians(90), OPEN);
let x1 = 200 + Math.cos(radians(0)) * 25;
let y1 = 200 + Math.sin(radians(0)) * 25;
let x2 = 200 + Math.cos(radians(90)) * 25;
let y2 = 200 + Math.sin(radians(90)) * 25;
let x3 = 200 + Math.cos(radians(0)) * 75;
let y3 = 200 + Math.sin(radians(0)) * 75;
let x4 = 200 + Math.cos(radians(90)) * 75;
let y4 = 200 + Math.sin(radians(90)) * 75;
line(x1, y1, x3, y3)
line(x2, y2, x4, y4)

This should answer part of your question experimentally. As you can see by playing with the sketch below, if there is a single curveVertex() in the shape all of the vertices created with vertex() will also be treated as curveVertex() type vertices.
I think you should be able to achieve your goal with bezierVertex().
const InteractionThreshold = 5;
const SqInteractionThreshold = InteractionThreshold * InteractionThreshold;
function lineEditor(p) {
let controlPoints = [{
x: 50,
y: 50,
type: 'vertex'
},
{
x: 250,
y: 250,
type: 'vertex'
},
];
p.setup = () => {
p.createCanvas(300, 300);
p.rectMode(p.RADIUS);
p.ellipseMode(p.RADIUS);
p.noFill();
p.noLoop();
};
p.draw = () => {
p.background(200);
p.beginShape();
for (let i = 0; i < controlPoints.length; i++) {
let pt = controlPoints[i];
switch (pt.type) {
case 'vertex':
p.vertex(pt.x, pt.y);
break;
case 'curve':
p.curveVertex(pt.x, pt.y);
break;
}
}
p.endShape();
p.push();
p.stroke('green');
p.drawingContext.setLineDash([5, 15]);
p.beginShape();
for (let i = 0; i < controlPoints.length; i++) {
let pt = controlPoints[i];
p.vertex(pt.x, pt.y);
}
p.endShape();
p.drawingContext.setLineDash([]);
p.pop();
p.push();
p.fill("red");
p.noStroke();
for (let i = 0; i < controlPoints.length; i++) {
let pt = controlPoints[i];
switch (pt.type) {
case 'curve':
p.circle(pt.x, pt.y, 4);
break;
case 'vertex':
p.push();
p.translate(pt.x, pt.y);
p.rotate(p.PI / 4);
p.square(0, 0, 4);
p.pop();
break;
}
}
p.pop();
};
let dragging;
p.mousePressed = function() {
// is the mouse over a point
let closest = getClosest(p.mouseX, p.mouseY);
if (closest && closest.sqdist < SqInteractionThreshold) {
dragging = closest.ix;
}
};
p.mouseReleased = function() {
dragging = undefined;
};
p.mouseDragged = function() {
if (dragging !== undefined) {
controlPoints[dragging].x = p.constrain(p.mouseX, 0, p.width);
controlPoints[dragging].y = p.constrain(p.mouseY, 0, p.height);
p.redraw();
}
};
p.mouseClicked = function() {
if (p.keyIsDown(p.SHIFT)) {
// is the mouse over a point
let closest = getClosest(p.mouseX, p.mouseY);
if (closest && closest.sqdist < SqInteractionThreshold) {
let pt = controlPoints[closest.ix];
switch (pt.type) {
case 'vertex':
pt.type = 'curve';
break;
case 'curve':
pt.type = 'vertex';
break;
}
p.redraw();
}
}
}
p.doubleClicked = function() {
if (p.keyIsDown(p.SHIFT)) {
if (controlPoints.length > 2) {
// delete
let closest = getClosest(p.mouseX, p.mouseY);
if (closest && closest.sqdist < SqInteractionThreshold) {
controlPoints.splice(closest.ix, 1);
p.redraw();
}
}
} else {
// insert
let closest = getClosestSegment(p.mouseX, p.mouseY);
if (closest.error < InteractionThreshold) {
controlPoints.splice(closest.end, 0, {
x: p.mouseX,
y: p.mouseY,
type: 'vertex'
});
p.redraw();
}
}
};
function getClosest(x, y) {
let closest;
for (let i = 0; i < controlPoints.length; i++) {
let dx = x - controlPoints[i].x;
let dy = y - controlPoints[i].y;
let sqdist = dx * dx + dy * dy;
if (!closest || sqdist < closest.sqdist) {
closest = {
sqdist,
ix: i
};
}
}
return closest;
}
function getClosestSegment(x, y) {
let closest;
for (let i = 0; i < controlPoints.length - 1; i++) {
let start = controlPoints[i];
let end = controlPoints[i + 1];
let angle = p.atan2(end.y - start.y, end.x - start.x);
let len = p.dist(start.x, start.y, end.x, end.y);
// convert [mouseX, mouseY] to a position relative to start
let vec = p.createVector(x - start.x, y - start.y);
vec = p.createVector(
p.cos(angle) * vec.x + p.sin(angle) * vec.y, -p.sin(angle) * vec.x + p.cos(angle) * vec.y,
);
let error;
if (vec.x < 0) {
error = p.dist(0, 0, vec.x, vec.y);
} else if (vec.x > len) {
error = p.dist(len, 0, vec.x, vec.y);
} else {
// There is no X error, only consider the Y
error = p.abs(vec.y);
}
if (!closest || error < closest.error) {
closest = {
error,
start: i,
end: i + 1
};
}
}
return closest;
}
}
let lineEditorSketch = new p5(lineEditor);
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

Related

Determine the next point on the Bézier curve based on the straight-line distance from the previous point

I need to keep the same distance between the balls on the Bézier curve. Now I'm using a simple iteration of possible positions on the curve, adding some step amount for each iteration until position with required distance, greater or equal, is found.
It works, but can be resource-intensive when there are a large amount of balls. CodeSandbox
class TestScene extends Phaser.Scene {
create() {
// prettier-ignore
const curve = new Phaser.Curves.QuadraticBezier(
[
55, 310,
40, 0,
250, 310
]
);
const graphicsLayer = this.add.graphics({
lineStyle: {
width: 2,
color: 0x0000ff
}
});
curve.draw(graphicsLayer);
const balls = this.initBalls(6);
this.curve = curve;
this.balls = balls;
}
initBalls(quantity) {
const balls = [];
for (let i = 0; i < quantity; i++) {
const ball = this.add.circle(0, 0, 12, 0x00ff00);
ball.t = 0;
balls.push(ball);
}
return balls;
}
calcNextBallT(previous) {
const ballDiameter = previous.radius * 2;
const curve = this.curve;
const curveLen = curve.getLength();
const previousPos = new Phaser.Math.Vector2(previous);
const previousT = previous.t;
const startingT = (1 / curveLen) * ballDiameter + previousT;
const step = 1 / 1000;
let nextT = startingT;
let nextPos;
let currentDistance = 0;
while (nextT >= 0 && nextT <= 1) {
nextPos = curve.getPointAt(nextT);
currentDistance = previousPos.distance(nextPos);
if (currentDistance >= ballDiameter) {
break;
}
nextT += step;
}
return nextT;
}
update() {
const {
curve,
balls
} = this;
if (!curve || !balls) {
return;
}
const speed = 1;
const curveLen = curve.getLength();
const step = (1 / curveLen) * speed;
balls.forEach((ball, index, array) => {
let nextT = ball.t;
if (index === 0) {
nextT += step;
if (nextT > 1) {
nextT = 0;
}
} else {
const previous = array[index - 1];
nextT = this.calcNextBallT(previous);
}
ball.t = nextT;
ball.copyPosition(curve.getPointAt(nextT));
});
}
}
const game = new Phaser.Game({
width: 320,
height: 320,
powerPreference: 'low-power',
audio: {
noAudio: true
},
scene: [TestScene]
});
<script src="https://unpkg.com/phaser#3.55.2/dist/phaser.min.js"></script>
I think maybe there is a mathematical or algorithmic solution. Something like finding the intersection of a larger circle with a curve, but as I understood it is impossible.
Is there a better way to determine the next point on the Bézier curve based on the straight-line distance from the previous point?
UPDATE 1: A solution using Binary Search, as suggested by #MBo, shows good stability with an average of 5-10 iterations, even with varying curves and ball sizes. CodeSandbox
class TestScene extends Phaser.Scene {
init() {
this.input.on('drag', (pointer, gameObject, dragX, dragY) => {
gameObject.setPosition(dragX, dragY);
});
this.iters = [];
}
create() {
// prettier-ignore
const p0 = this.add.circle(55, 310, 4, 0xffff00),
p1 = this.add.circle(40, 5, 4, 0xffff00),
p2 = this.add.circle(250, 310, 4, 0xffff00);
const curve = new Phaser.Curves.QuadraticBezier(p0, p1, p2);
const graphicsLayer = this.add.graphics({
lineStyle: {
width: 2,
color: 0x0000ff
}
});
curve.draw(graphicsLayer);
const curveDragHandler = () => {
curve.updateArcLengths();
graphicsLayer.clear();
curve.draw(graphicsLayer);
};
[p0, p1, p2].forEach((p) => {
p.setDepth(10).setInteractive();
this.input.setDraggable(p);
p.on('drag', curveDragHandler);
});
const balls = this.initBalls(3, 50);
const text = this.add.text(0, 0, '', {
color: 'yellow'
});
this.curve = curve;
this.balls = balls;
this.text = text;
}
initBalls(quantity, diameter) {
const radius = diameter / 2;
const balls = [];
for (let i = 0; i < quantity; i++) {
const ball = this.add.circle(0, 0, radius, 0x00ff00);
ball.t = 0;
balls.push(ball);
}
return balls;
}
calcNextBallT(previous) {
const ballDiameter = previous.radius * 2;
const curve = this.curve;
const previousPos = new Phaser.Math.Vector2(previous);
const previousT = previous.t;
let nextT = 1;
let lowT = previousT;
let highT = 1;
let iter = 1;
const skip = previousPos.distance(curve.getEndPoint()) <= ballDiameter;
while (lowT <= highT && !skip) {
nextT = lowT + (highT - lowT) / 2;
const nextPos = curve.getPointAt(nextT);
const currentDistance = previousPos.distance(nextPos);
if (fuzzySame(currentDistance, ballDiameter)) {
break;
}
if (currentDistance > ballDiameter) {
highT = nextT;
} else {
lowT = nextT;
}
iter++;
}
if (!skip) {
this.iters.push(iter);
}
return nextT;
}
update() {
const {
curve,
balls
} = this;
if (!curve || !balls) {
return;
}
const speed = 1;
const curveLen = curve.getLength();
const step = (1 / curveLen) * speed;
balls.forEach((ball, index, array) => {
let nextT = ball.t;
if (index === 0) {
nextT += step;
if (nextT > 1) {
const average = findAverage(this.iters).toFixed(2);
const maximum = findMaximum(this.iters);
this.text.setText(`Average: ${average}\nMaximum: ${maximum}`);
this.iters = [];
nextT = 0;
}
} else {
const previous = array[index - 1];
nextT = this.calcNextBallT(previous);
}
ball.t = nextT;
ball.copyPosition(curve.getPointAt(nextT));
});
}
}
const fuzzySame = (num1, num2) => {
const tolerance = 0.2;
return num1 >= num2 - tolerance && num1 <= num2 + tolerance;
};
const findAverage = (array) => {
const len = array.length;
let total = 0;
array.forEach((num) => (total += num));
return total / len;
};
const findMaximum = (array) => {
return [...array].sort((a, b) => b - a)[0];
};
const game = new Phaser.Game({
width: 320,
height: 320,
powerPreference: 'low-power',
audio: {
noAudio: true
},
scene: [TestScene]
});
<script src="https://unpkg.com/phaser#3.55.2/dist/phaser.min.js"></script>
Instead of straight iterations use binary search over t range.
Math solution requires solving equation of 6-th degree (for cubic curve) - there is no closed formula, so one needs to use numerical methods (iterations again)

How to access a value in a array

I need to access the wall array inside the C variable. I can find what c is, but not what any specific value of c. I probably made a mistake of how to access the value from this array, but I tried to do c[3], and it always says undefined. This is the full program link https://editor.p5js.org/Meowmeow/sketches/b4AhGA4xH.
function index(i, j) {
if (i < 0 || j < 0 || i > cols - 1 || j > cols - 1) {
return -1;
}
return i + j * cols
}
function Cell(i, j) {
this.visited = false;
this.i = i;
this.j = j;
this.walls = [true, true, true, true];
this.checkNeighbors = function() {
var neighbors = []
var top = grid[index(i, j - 1)]
var right = grid[index(i + 1, j)]
var left = grid[index(i - 1, j)]
var bottom = grid[index(i, j + 1)]
var Wall = this.walls;
var wall=[]
wall.push(i,j,Wall)
if (top && !top.visited) {
neighbors.push(top);
}
if (right && !right.visited) {
neighbors.push(right);
}
if (bottom && !bottom.visited) {
neighbors.push(bottom);
}
if (left && !left.visited) {
neighbors.push(left);
}
if (neighbors.length > 0) {
var r = floor(random(0, neighbors.length))
return neighbors[r];
} else {
return undefined;
}
}
this.highlight = function() {
var x = this.i * w;
var y = this.j * w;
noStroke();
fill(0, 0, 255, 100)
rect(x, y, w, w);
}
this.show = function() {
var x = this.i * w;
var y = this.j * w;
stroke(255);
noFill();
if (this.walls[0]) {
line(x, y, x + w, y);
}
if (this.walls[1]) {
line(x + w, y, x + w, y + w);
}
if (this.walls[2]) {
line(x + w, y + w, x, y + w);
}
if (this.walls[3]) {
line(x, y + w, x, y);
}
if (this.visited) {
fill(255, 0, 255, 100);
noStroke();
rect(x, y, w, w);
}
}
};
function removeWalls(a, b) {
var x = a.i - b.i;
if (x === 1) {
a.walls[3] = false;
b.walls[1] = false;
} else if (x === -1) {
a.walls[1] = false;
b.walls[3] = false;
}
var y = a.j - b.j;
if (y === 1) {
a.walls[0] = false;
b.walls[2] = false;
} else if (y === -1) {
a.walls[2] = false;
b.walls[0] = false;
}
}
function draw() {
background(51);
for (var i = 0; i < grid.length; i++) {
grid[i].show()
}
//Step One
current.visited = true;
current.highlight();
var next = current.checkNeighbors()
if (next) {
next.visited = true;
//Step 2
stack.push(current);
check.push(current)
//Step 3
removeWalls(current, next)
{}
//Step 4
current = next;
} else if (stack.length > 0) {
current = stack.pop();
}
if (stack[0] === undefined) {
fill(0, 105, 100)
rect(0, 0, w, w)
rect(width - w, height - w, w, w)
frameRate(8)
var c = check.find(cell => cell.i == ~~(k/w) && cell.j == ~~(m/w));
console.log(c)
if(c===undefined)
I'm not sure if I needed to put in this much of the code, but I didn't want to leave anything important out.
You're definitely on the right track.
I would just finish that check to see if a cell has been found before attempting to access the walls property. For example:
if(c===undefined){
console.log('no cell found');
}else{
console.log('i',c.i,'j', c.j,'walls', c.walls);
}
as you press the arrow keys you should see different cell row/col indices and walls.

Recursive division does not guarantee a path

I implemented the algorithm from the answer in here Maze (recursive division) algorithm design.
but I ran into a problem that there isn't always a path from start(+) to finish(-)
like this image.
also there is another problem that the start(+) or finish(-) node will be surrounded by wall see start node (+)
but I think it's easy to fix.
remove the walls around that node [x-1][y], [x][y+1], [x+1][y] and [x][y-1].
I don't know if it's the right fix or not.
that's my maze class
class Maze {
private G: INode[][];
private rows: number;
private cols: number;
constructor(G: INode[][]) {
this.G = G.map((row) =>
row.map((node) => ({
...node,
isWall: false,
}))
);
this.rows = this.G.length;
this.cols = this.G[0].length;
// border walls
for (let i = 0; i < this.rows; i++) {
this.G[i][0].isWall = true;
this.G[i][this.cols - 1].isWall = true;
}
for (let i = 0; i < this.cols; i++) {
this.G[0][i].isWall = true;
this.G[this.rows - 1][i].isWall = true;
}
}
private rand(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
private isNodeStartOrFinish(row: number, col: number) {
return this.G[row][col].isStart || this.G[row][col].isFinish;
}
// call perfectMaze to get the generate the maze
public perfectMaze() {
this.divide(true, 1, this.cols - 2, 1, this.rows - 2);
}
private divide(
h: boolean,
minX: number,
maxX: number,
minY: number,
maxY: number
) {
if (h) {
if (maxX - minX < 2) return;
const y = Math.floor(this.rand(minY, maxY) / 2) * 2;
this.addHWall(minX, maxX, y);
this.divide(!h, minX, maxX, minY, y - 1);
this.divide(!h, minX, maxX, y + 1, maxY);
} else {
if (maxY - minY < 2) return;
const x = Math.floor(this.rand(minX, maxX) / 2) * 2;
this.addVWall(minY, maxY, x);
this.divide(!h, minX, x - 1, minY, maxY);
this.divide(!h, x + 1, maxX, minY, maxY);
}
}
private addHWall(minX: number, maxX: number, y: number) {
const hole = Math.floor(this.rand(minX, maxX) / 2) * 2 + 1;
for (let i = minX; i <= maxX; i++) {
if (i !== hole && !this.isNodeStartOrFinish(y, i)) {
this.G[y][i].isWall = true;
}
}
}
private addVWall(minY: number, maxY: number, x: number) {
const hole = Math.floor(this.rand(minY, maxY) / 2) * 2 + 1;
for (let i = minY; i <= maxY; i++) {
if (i !== hole && !this.isNodeStartOrFinish(i, x)) {
this.G[i][x].isWall = true;
}
}
}
public getMaze() {
return this.G;
}
}
okay i was stupid
i didn't remove the wall when i === hole
in the addHWall and addVWall methods
you must delete the wall if i === hole otherwise the wall will be added again
private addHWall(minX: number, maxX: number, y: number) {
const hole = Math.floor(this.rand(minX, maxX) / 2) * 2 + 1;
for (let i = minX; i <= maxX; i++) {
if (i === hole || this.isNodeStartOrFinish(y, i)) {
this.G[y][i].isWall = false;
} else {
this.G[y][i].isWall = true;
}
}
}
the same with addVWall
if (i === hole || this.isNodeStartOrFinish(i, x)) {
this.G[y][i].isWall = false;
} else {
this.G[i][x].isWall = true;
}
and i ended up deleting the walls around the start and finish node still don't know if it's the right solution or not

How do I find beginning/end vertices of branches on an L-Systems tree? (p5.js)

I'm trying to get the x, y coordinates of branch endpoints on a simple L-Systems tree. The idea is to create a p5.Vector(x, y) and push it to an array.
Right now, I'm able to draw ellipses marking the desired points by setting their origin to (0, -len), but I have a problem. When I try to push (0, -len) as a new p5.Vector(x, y) to an array, every single point has an x coordinate of 0, albeit with the correct y coordinate.
I know that it has something to do with translating the coordinate back to (width/2, height), but I'm just not able to figure out the correct calculation. I've even tried tan(angle) * (y1 - y2) but it's not quite right. TIA!
var axiom = 'F';
var sentence = axiom;
var len = 300;
var count = 0;
var flowerArr = [];
var rules = [];
rules[0] = {
a: 'F',
b: 'G[+F][-F]GF'
};
rules[1] = {
a: 'G',
b: 'GG'
};
function setup() {
createCanvas(window.innerWidth, window.innerHeight);
stroke(10);
smooth();
turtle();
}
function turtle() {
background(255);
strokeWeight(1);
angle = radians(Math.random() * (25 - 15) + 15);
resetMatrix();
translate(width / 2, height);
for (var i = 0; i < sentence.length; i++) {
var current = sentence.charAt(i);
var randomSeed = 2;
if (current == 'F' || current == 'G') {
ellipse(0, -len, 5);
line(0, 0, 0, -len);
translate(0, -len);
} else if (current == '+') {
let positiveRotation = angle * Math.random() * randomSeed;
rotate(positiveRotation);
} else if (current == '-') {
let negativeRotation = -angle * Math.random() * randomSeed;
rotate(negativeRotation);
} else if (current == '[') {
push();
} else if (current == ']') {
pop();
count++;
}
}
if (i >= sentence.length) {
finished = true;
console.log("done", count);
}
}
function generateStems(iterations) {
for (i = iterations - 1; i > 0 ; i--) {
branch();
}
}
function branch() {
len *= Math.random() * (.52 - .45) + .45;
var nextSentence = '';
for (var i = 0; i < sentence.length; i++) {
var current = sentence.charAt(i);
var found = false;
for (var j = 0; j < rules.length; j++) {
if (current == rules[j].a) {
found = true;
nextSentence += rules[j].b;
break;
}
}
if (!found) {
nextSentence += current;
}
}
sentence = nextSentence;
turtle();
}
function draw() {
generateStems(4);
noLoop();
}
As far as I know, at the moment, p5.js support for vector/matrix operations and coordinate space conversion isn't quite there yet.
In theory you could manually keep track of every single transformation (translate/rotate) and manually compute it to get the transformed positions, howeve, in practice this may be error prone and cumbersome.
In Processing you could rely on PMatrix's mult(PVector) method to transform a point from one coordinate system to another, but not in p5.js at the moment.
Functions like screenX()/screenY() simplify this even further.
Here's a basic example (note the usage of P3D):
PVector v1 = new PVector();
float len = 100;
void setup(){
size(300,300,P3D);
noFill();
strokeWeight(3);
}
void draw(){
background(255);
// isolate coordinate system
pushMatrix();
// apply a set of transformations
translate(width / 2, height);
translate(0,-len);
rotate(radians(45));
// draw a blue rectangle from the corner to illustrate this transformed position
stroke(0,0,192);
rect(0,0,30,30);
// further transform
translate(90,0);
// draw a rect rectangle
stroke(192,0,0);
rect(0,0,30,30);
// use screenX/screenY to calculate the transformed coordinates
v1.set(screenX(0,0,0),screenY(0,0,0));
popMatrix();
// simply draw a (green) circle on top at the same transformed coordinates, without being in that local coordinate system
stroke(0,192,0);
ellipse(v1.x, v1.y, 30, 30);
}
At the moment, for practical reasons, if computing the transformed locations is a must, I would recommend porting your code to Processing.
Update Based on your comment it is easier to use the L-System to introduce a new rule for the flower.
Let's say * represents a flower, you could modify your rule to include it for example as the last instruction: b: 'G[+F][-F]GF' becomes b: 'G[+F][-F]GF*'
then it's just a matter of handling that symbol as you traverse the current sentence:
var axiom = 'F';
var sentence = axiom;
var len = 300;
var count = 0;
var flowerArr = [];
var rules = [];
rules[0] = {
a: 'F',
b: 'G[+F][-F]GF*'
};
rules[1] = {
a: 'G',
b: 'GG'
};
function setup() {
createCanvas(630, 630);
stroke(10);
noFill();
smooth();
turtle();
}
function turtle() {
background(255);
strokeWeight(1);
angle = radians(Math.random() * (25 - 15) + 15);
resetMatrix();
translate(width / 2, height);
for (var i = 0; i < sentence.length; i++) {
var current = sentence.charAt(i);
var randomSeed = 2;
if (current == 'F' || current == 'G') {
ellipse(0, -len, 5);
line(0, 0, 0, -len);
translate(0, -len);
// flower rule
} else if (current == '*') {
flower(6,len * 0.618033);
} else if (current == '+') {
let positiveRotation = angle * Math.random() * randomSeed;
rotate(positiveRotation);
} else if (current == '-') {
let negativeRotation = -angle * Math.random() * randomSeed;
rotate(negativeRotation);
} else if (current == '[') {
push();
} else if (current == ']') {
pop();
count++;
}
}
if (i >= sentence.length) {
finished = true;
// console.log("done", count);
}
}
function flower(sides, sideLength){
beginShape();
let angleIncrement = TWO_PI / sides;
for(let i = 0 ; i <= sides; i++){
vertex(cos(angleIncrement * i) * sideLength,
sin(angleIncrement * i) * sideLength);
}
endShape();
}
function generateStems(iterations) {
for (i = iterations - 1; i > 0 ; i--) {
branch();
}
}
function branch() {
len *= Math.random() * (.52 - .45) + .45;
var nextSentence = '';
for (var i = 0; i < sentence.length; i++) {
var current = sentence.charAt(i);
var found = false;
for (var j = 0; j < rules.length; j++) {
if (current == rules[j].a) {
found = true;
nextSentence += rules[j].b;
break;
}
}
if (!found) {
nextSentence += current;
}
}
sentence = nextSentence;
turtle();
}
function draw() {
generateStems(5);
noLoop();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.min.js"></script>
As further explorations here are a couple of fun JS implementations of L-system to play with:
https://jsxgraph.uni-bayreuth.de/wiki/index.php/L-systems
http://www.plastaq.com/elsy/

p5js- Uncaught TypeError: Cannot read property 'eats' of undefined (sketch: line 76)

Can someone please help me with what I'm doing wrong here. I'm completely new to programming, and am currently learning p5js at university. I'm trying to create 'cells' floating around in space, and when they hit each other they combine together. It works for a minute but then I get an error
"Uncaught TypeError: Cannot read property 'eats' of undefined (sketch: line 76)"
This is my code:
function Cell(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
this.speedx = random(-1, 1);
this.speedy = random(-1, 1);
this.width2 = 0.5;
this.col = color(255);
this.show = function() {
fill(this.col);
ellipse(this.x, this.y, this.r * 2);
}
this.move = function() {
this.x = this.x + this.speedx;
this.y = this.y + this.speedy;
}
this.bounce = function() {
if (this.x > width || this.x < 0) {
this.speedx = -this.speedx;
}
if (this.y > height || this.y < 0) {
this.speedy = -this.speedy;
}
}
// this.changeColor = function (){
//this.col = color(random(255), random (255), random(255));
//}
this.eats = function(other) {
var d = dist(this.x, this.y, other.x, other.y);
if (d < this.r + other.r) {
var sum = PI * this.r * this.r + PI * other.r * other.r;
this.r = sqrt(sum / PI);
//this.r += other.r;
return true;
} else {
return false;
}
}
// this.intersects = function(other){
// var d= dist(this.x, this.y, other.x, other.y);
//if (d < this.r + other.r) {
//var sum = PI * this.r * this.r + PI * other.r * other.r;
//this.r = sqrt(sum / PI);
//this.r += other.r;
//return true;
//} else {
//return false;
//}
//}
}
var cells = [];
function setup() {
createCanvas(600, 600);
for (var i = 0; i < 50; i++) {
cells[i] = new Cell(random(width), random(height), 10);
}
}
function draw() {
background(0);
for (var i = cells.length - 1; i >= 0; i--) {
cells[i].show();
cells[i].move();
cells[i].bounce();
for (var j = cells.length - 1; j >= 0; j--) {
if (j != i && cells[i].eats(cells[j])) {
// cells[i].changeColor();
// cells[j].changeColor();
cells.splice(j, 1);
}
}
}
}
You are removing cells from cells array using cells.splice(j, 1);. So when it checks for that on next loop, its throwing error.

Resources