How give an object a square spiral movement with p5 - javascript

The point of this exercise is for the ball to travel a smaller distance every cycle, so it leaves an espiral like trace, and ends up in the middle of the canvas.
My logic told me to make a variable "del" that gets bigger with every cycle and add it to the limit of the travel distance. It works fine for all the sides except the left one (where the cycle ends) if I add the variable to that side the ball never hits a limit.
How can I fix it? Thank you so much in advance!
let speed = 7;
let speedX = speed;
let speedY = 0;
let ellipseD = 50;
let ellipseR = ellipseD / 2;
let x = 0;
let y = ellipseR;
let limit = 25;
let del = 0;
function setup() {
createCanvas(500, 500);
}
function draw() {
background(166, 236, 255, 10);
fill(255);
strokeWeight(2);
stroke(21, 171, 212);
ellipse(x, y, ellipseD, ellipseD);
x = x + speedX;
y = y + speedY;
if (x >= width - (limit + del)) {
speedX = 0;
speedY = speed;
}
if (y >= height - (limit + del)) {
speedX = -speed;
speedY = 0;
}
if (y >= height - (limit + del) && x < limit) {
speedX = 0;
speedY = -speed;
}
if (y < limit + del && x <= limit) {
speedX = speed;
speedY = 0;
del = del + 50;
console.log(del);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

The first approach I tried is changing your last branch (the one that shrinks the square space by incrementing del) to this:
if (y < limit + (del + 50) && x <= limit) {
The 50 here is the increment amount, which ensures that the right turn occurs before the snake overlaps with the area it's already visited.
The problem is, the condition then becomes too aggressive and fires prematurely. The fix I used is to add a check for the speedX and speedY in each branch to make sure the snake is moving in the right direction before triggering the condition. Since all conditions should be disjoint per frame, I made them else ifs for clarity.
Finally, if speed doesn't cleanly divide ellipseD and ellipseR, you might see some artifacts and gaps in the snake pattern, so I used 7, 42 and 21 (42 / 2) respectively to clean up the gaps a bit. You might need to play with these sizes a bit to get the desired effect.
const speed = 7;
let speedX = speed;
let speedY = 0;
const ellipseD = 42;
const ellipseR = ellipseD / 2;
let x = 0;
let y = ellipseR;
const limit = 25;
let del = 0;
function setup() {
createCanvas(500, 500);
}
function draw() {
background(166, 236, 255, 10);
fill(255);
strokeWeight(2);
stroke(21, 171, 212);
ellipse(x, y, ellipseD, ellipseD);
x += speedX;
y += speedY;
if (x >= width - (limit + del) && speedX > 0) {
speedX = 0;
speedY = speed;
}
else if (y >= height - (limit + del) && speedY > 0) {
speedX = -speed;
speedY = 0;
}
else if (y >= height - (limit + del) &&
x < (limit + del) && speedX < 0) {
speedX = 0;
speedY = -speed;
}
else if (y < limit + ellipseD + del && speedY < 0) {
speedX = speed;
speedY = 0;
del += ellipseD;
if (del > width / 2) {
noLoop();
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.js"></script>

I think the most obvious issue with the behavior on the left hand side is that you are not taking del into account when checking x:
// This checks if the ellipse is in the bottom left
if (y >= height - (limit + del) && x < limit) {
speedX = 0;
speedY = -speed;
}
The problem with this code is that x < limit should be x < limit + del. And a similar issue exists in the check for when the ellipse is in the top left.
However, once you "fix" that you will start having issues because the check for the top left will fire repeatedly as del increases faster than the ellipse moves. So it is necessary to add checks to see what direction the ellipse is moving in before updating speed and the value of del.
let speed = 7;
let speedX = speed;
let speedY = 0;
let ellipseD = 50;
let ellipseR = ellipseD / 2;
let x = ellipseR;
let y = ellipseR;
let limit = ellipseR;
let del = 0;
function setup() {
createCanvas(500, 500);
}
function draw() {
background(166, 236, 255, 10);
fill(255);
strokeWeight(2);
stroke(21, 171, 212);
ellipse(x, y, ellipseD, ellipseD);
x = x + speedX;
y = y + speedY;
if (x >= width - (limit + del) && x >= width - (limit + del) && speedX > 0) {
speedX = 0;
speedY = speed;
console.log('top right');
}
if (y >= height - (limit + del) && x >= width - (limit + del) && speedY > 0) {
speedX = -speed;
speedY = 0;
console.log('bottom right');
}
if (y >= height - (limit + del) && x <= (limit + del) && speedX < 0) {
speedX = 0;
speedY = -speed;
console.log('bottom left');
}
if (y < (limit + del) && x <= limit + del && speedY < 0) {
speedX = speed;
speedY = 0;
del = del + 50;
console.log(del);
console.log('top left');
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

Related

P5.js curveVertex function is closing at a point

I've created a noise function that pairs with a circle function to create a random noise circle thing that looks pretty cool. My problem is the curveVertex function in P5.js works correctly except for the connection of the first and last vertex. My code is:
let start = Array(50).fill(0); // dont change
let amount = 1; // amount of shapes
let gap = 30; // between shapes
let amplify = 50; // 0 -->
let colorSpeed = 1; // 1 - 9
let colorSeparation = 3; // 0 - 80 recomended 0 - 10
function setup() {
createCanvas(windowWidth, windowHeight);
for(let i = 0 ; i < start.length; i++){
start[i] = random(i);
}
}
function draw() {
background(0);
for(let dnc = (amount + 1) * gap; dnc > gap; dnc -= gap){
drawNoiseCircle(dnc, getNoise(start.length));
}
start = start.map( c => c + 0.01 );
}
function getNoise(amount){
let lengths = [];
for(let i = 1; i < amount + 1; i++){
let n1 = noise(start[i - 1]);
let noise1 = map(n1, 0, 1, -amplify, amplify);
lengths.push(abs(-noise1));
}
return lengths;
}
function drawNoiseCircle(radius, lengths){
colorMode(HSB);
fill(((frameCount + radius) * colorSeparation)/-map(colorSpeed, 1, 10, -10, -1) % 360, 100, 50);
noStroke()
let x;
let y;
beginShape();
for(let l = 0; l < lengths.length; l++){
x = Math.cos(radians(l * 360 / lengths.length)) * (radius + lengths[l]) + width/2;
y = Math.sin(radians(l * 360 / lengths.length)) * (radius + lengths[l]) + height/2;
curveVertex(x, y);
}
endShape(CLOSE);
stroke("black");
line(width/2, height/2, width, height/2);
line(width/2, height/2 + 9, width, height/2 + 9);
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.js"></script>
I understand the endShape(CLOSED) closes the shape with a straight line, but I'm not sure any other way to close the shape.
You can see the pointed edge on the right side, directly in the middle of the shape.
!EDIT!
I've added lines to the shape to show the line segment that isn't affected by the curve vertex. Also, I understand it may not be a very significant problem, but if the amount of vertexes shrink, it becomes a much bigger problem (eg. a square or a triangle).
Unfortunately I won't have time to dive deep and debug the actual issue with curveVertex (or it's math) at the time, but it seems there's something interesting with curveVertex() in particular.
#Ouoborus point makes sense and the function "should" behave that way (and it was with vertex(), but not curveVertex()). For some reason curveVertex() requires looping over the not just the first point again, but the second and third.
Here's basic example:
function setup() {
createCanvas(300, 300);
background(220);
let numPoints = 6;
let angleIncrement = TWO_PI / numPoints;
let radius = 120;
beginShape();
for(let i = 0 ; i < numPoints + 3; i++){
let angle = angleIncrement * i;
let x = 150 + cos(angle) * radius;
let y = 150 + sin(angle) * radius;
curveVertex(x, y);
}
endShape();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.min.js"></script>
Try decreasing numPoints + 3 to numPoints + 2 or notice the behaviour you're describing.
(I could speculate it might have something to do with how curveVertex() (Catmull-Rom splines) are implemented in p5 and how many coordinates/points it requires, but this isn't accurate without reading the source code and debugging a bit)
Here's a version of your code using the above notes:
let start = Array(30).fill(0);
let colorSpeed = 1; // 1 - 9
let colorSeparation = 3; // 0 - 80 recomended 0 - 10
function setup() {
createCanvas(600, 600);
colorMode(HSB);
noStroke();
// init noise seeds
for(let i = 0 ; i < start.length; i++){
start[i] = random(i);
}
}
function getNoise(seeds, amplify = 50){
let amount = seeds.length;
let lengths = [];
for(let i = 1; i < amount + 1; i++){
let n1 = noise(seeds[i - 1]);
let noise1 = map(n1, 0, 1, -amplify, amplify);
lengths.push(abs(-noise1));
}
return lengths;
}
function drawNoiseCircle(radius, lengths){
let sides = lengths.length;
let ai = TWO_PI / sides;
let cx = width * 0.5;
let cy = height * 0.5;
fill(((frameCount + radius) * colorSeparation)/-map(colorSpeed, 1, 10, -10, -1) % 360, 100, 50);
beginShape();
for(let i = 0 ; i < sides + 3; i++){
let noiseRadius = radius + lengths[i % sides];
let a = ai * i;
let x = cx + cos(a) * noiseRadius;
let y = cy + sin(a) * noiseRadius;
curveVertex(x, y);
}
endShape();
}
function draw() {
background(0);
// draw with updated perlin noise values
drawNoiseCircle(120, getNoise(start));
// increment noise seed
start = start.map( c => c + 0.01 );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.min.js"></script>

Ball not coming back after colliding in p5.js

var x, y, speed, speed2, speedx, speedy;
function setup() {
createCanvas(1200, 630);
x = 50;
y = 300;
speed = 20;
speed2 = 20;
speedx = createSlider(2, 50, 20);
speedx.position(1000, 100);
speedx.style('width', '200px');
speedy = createSlider(10, 50, 20);
speedy.position(1000, 150);
speedy.style('width', '200px');
}
function draw() {
background("white");
x = x + speedx.value();
y = y + speedy.value();
if (x > 1170) {
x = x - speedx.value();
}
if (x < 10) {
x = x + speedx.value();
}
if (y > 610) {
y = y - speedy.value();
}
if (y < 15) {
x = x + speedx.value();
}
let color1 = color("black");
fill(color1);
ellipse(x, y, 20);
}
**
I am new to p5.js and made this (my first code) but it is not working as I expected
Please help me by answering this code
**
Currently your code isn't going to make the ball "come back" because while it is limiting the x and y positions, it doesn't change the x and y velocities (so once the ball gets to the edge it is just going to stick there). You also have a few defects in to edge limiting logic.
Your sliders are always positive so the ball can only move down and to the right.
When you check the y position against the minimum you modify the x position instead of the y position.
When you check the x position against the minimum value you are adding the speeds, but presumably when this happens speed would be negative (i.e. moving to the left), so you still want to subtract.
var x, y, speedx, speedy;
function setup() {
// make the canvas cover the window
createCanvas(windowWidth, windowHeight);
x = width / 2;
y = height / 2;
// create slider ranges such that things don't always go down and to the right
speedx = createSlider(-10, 10, 2);
speedx.position(10, 10);
speedx.style("width", "200px");
speedy = createSlider(-10, 10, 2);
speedy.position(10, 50);
speedy.style("width", "200px");
}
function draw() {
background("white");
x = x + speedx.value();
y = y + speedy.value();
if (x > width - 10) {
x = x - speedx.value();
}
if (x < 10) {
// presumably the reason we're going off the screen is that speedx is negative.
x = x - speedx.value();
}
if (y > height - 10) {
y = y - speedy.value();
}
if (y < 10) {
y = y - speedy.value();
}
let color1 = color("black");
fill(color1);
ellipse(x, y, 20);
}
html, body {margin:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
You can do something like this (visit codecademy for details: https://www.codecademy.com/courses/learn-p5js/lessons/p5js-animation)
speedx *= -1
You can do the same thing with speedy

browser freezing when in while loop (snake game)

I'm making the popular snake game for js homework.
I have a function which is meant to ensure that after an apple is eaten it moves to a different location on the canvas while not being on any part of the snake,
the function without the loop works fine (though apples are placed on the snake too):
move() {
let onSnake = true;
let x = this.getRandomNumber(0, canvas.width - 1); //UnitSize);;
let y = this.getRandomNumber(0, canvas.height - 1); //UnitSize);;
this.x = x;
this.y = y;
this.draw();
}
the function though with the loop ends up freezing after a few apples are eaten (right before hitting an apple):
move() {
let onSnake = true;
let x = this.getRandomNumber(0, canvas.width - 1); //UnitSize);;
let y = this.getRandomNumber(0, canvas.height - 1); //UnitSize);;
while (onSnake) {
onSnake = false;
x = this.getRandomNumber(0, canvas.width - 1); //UnitSize);
y = this.getRandomNumber(0, canvas.height - 1); //UnitSize);
for (let index = 0; index < snake.parts.length; index++) {
if (x === snake.parts[index].x || y === snake.parts[index].y) {
onSnake = true;
break;
}
}
}
this.x = x;
this.y = y;
this.draw();
}
with getRandomNumber being:
getRandomNumber(min, max) {
let r = Math.floor(Math.random() * (max - min + 1)) + min;
r = r - (r % UnitSize);
return r;
}
this is my second question on SO, don't fry me please....
I didn't get a moment to test but I suspect it's as simple as the following evalutation.
if (x === snake.parts[index].x || y === snake.parts[index].y) {
onSnake = true;
break;
}
You are accepting either X or Y collision. Meaning the apple cannot share any X or Y coordinates with any of the snake pieces. What you want instead is and I believe. It's like saying we both live on the same house because we live on the same street. No, we need to also have the same address.
As a safety, you could add a limit to how many iterations of the loop can run in the event their is no more space remaining for an apple.
let counter = 0;
while (onSnake && counter < 5000) {
onSnake = false;
x = this.getRandomNumber(0, canvas.width - 1); //UnitSize);
y = this.getRandomNumber(0, canvas.height - 1); //UnitSize);
for (let index = 0; index < snake.parts.length; index++) {
if (x === snake.parts[index].x && y === snake.parts[index].y) {
onSnake = true;
break;
}
}
counter += 1;
}
if (counter == 5000) {
alert("Could not find space for any more apples!");
}

Why can't I change the variables x and y to 0? (x and y are snake head coords)

When x and y are reaching the end of the canvas, they aren't changing to 0, they keep increasing. The size of canvas is 300 x 300. When I change the position of x and y to a number like 1, it is changing.
I tried a lot of things, but it doesn't work. Even by changing their value to 0 in Chrome Console, they remain the same.
let x = 150, y = 150;
/* main game loop */ {
// ...
if (x >= canvas.width) {
x = 0;
}
if (x <= 0) {
x = canvas.width;
}
if (y >= canvas.height) {
y = 0;
}
if (y <= 0) {
y = canvas.height;
}
// ...
}
You forgot to use else if.
This should fix it:
if (x >= canvas.width) {
x = 0;
}
else if (x <= 0) {
x = canvas.width;
}
if (y >= canvas.height) {
y = 0;
}
else if (y <= 0) {
y = canvas.height;
}

Raycasting walls arent drawed equally wide

I am working on a raycaster and followed this tutorial: https://dev.opera.com/articles/3d-games-with-canvas-and-raycasting-part-1/
My questions is about a Bug which draws / calculates Walls in diffrent Width depending on where on the canvas its drawn (so how big the ray angle is to the players point of direction (center view)):
The Walls in drawn in the middle (1.) are small, but those on the left or right (2.) side of the screen are drawn wider. Its easiest to understand when you look at the image. I think I just got the Math wrong, maybe rounded up somewhere I shouldnt but I havent found it yet or could think of any reason this error accures. Its made in a HTML Canvas using JavaScript.
In my first function I am sending out a ray for each x pixel of my canvas:
let resolution = Math.ceil(canvas.width / this.resolution); //canvas width = 1600, resolution = 1
let id = 0;
for (let x = 0; x < resolution; x++) {
let viewDist = (canvas.width / this.resolution) / Math.tan((this.fov / 2)); //fov 90 in rad
let rayx = (-resolution / 2 + x) * this.resolution;
let rayDist = Math.sqrt(rayx * rayx + viewDist * viewDist);
let rayAngle = Math.asin(rayx / rayDist);
let wall = this.castWall(this.pod * Math.PI / 180 + rayAngle);
this.drawWall(x, wall);
}
But I dont think theres anything wrong here. In the second function I am castinbg each ray and giving back the distance to the hit wall. My blocks / walls are 50 wide. My map is stored in and 2D number Array -> this.map.grid, this.map.width holds how many block there are in x direction, this.map.height holds the count in y direction.
castWall(angle) {
const PI2 = Math.PI * 2;
angle %= PI2;
if (angle < 0) {
angle += PI2;
}
let right = angle > PI2 * 0.75 || angle < PI2 * 0.25;
let up = angle < 0 || angle > Math.PI;
let sin = Math.sin(angle);
let cos = Math.cos(angle);
let dist = 0;
let textureX;
let texture;
let slope = sin / cos;
let dXVer = right ? 1 : -1;
let dYVer = dXVer * slope;
let px = this.x / 50;
let py = this.y / 50;
let x = right ? Math.ceil(px) : Math.floor(px);
let y = py + (x - px) * slope;
while (x >= 0 && x < this.map.width && y >= 0 && y < this.map.height) {
let wallX = Math.floor(x + (right ? 0 : -1));
let wallY = Math.floor(y);
if (this.map.grid[wallY][wallX] > 0) {
dist = Math.sqrt(Math.pow(x - px, 2) + Math.pow(y - py, 2));
texture = this.map.grid[wallY][wallX];
textureX = (y * 50) % 50;
if (right) {
textureX = 50 - textureX;
}
break;
}
x += dXVer;
y += dYVer;
}
slope = cos / sin;
let dYHor = up ? -1 : 1;
let dXHor = dYHor * slope;
y = up ? Math.floor(py) : Math.ceil(py);
x = px + (y - py) * slope;
while (x >= 0 && x < this.map.width && y >= 0 && y < this.map.height) {
let wallY = Math.floor(y + (up ? -1 : 0));
let wallX = Math.floor(x);
if (this.map.grid[wallY][wallX] > 0) {
let distHor = Math.sqrt(Math.pow(x - px, 2) + Math.pow(y - py, 2));
if (dist === 0 || distHor < dist) {
dist = distHor;
texture = this.map.grid[wallY][wallX];
textureX = (x * 50) % 50;
if (up) {
textureX = 50 - textureX;
}
}
break;
}
x += dXHor;
y += dYHor;
}
return {
distance: dist,
texture: texture,
textureX: textureX
};`
Ive also tried raycasting with other algorithms (Bresenham & DDA) but I never got them really to work. This is the only one which works for me. If you have any questions about the code feel free to ask.

Categories

Resources